1.简介
OpenGL能够检查所有面向观察者的面,并渲染它们,而丢弃那些背向的面,节省我们很多的片段着色器调用(它们的开销很大!)。但我们仍要告诉OpenGL哪些面是正向面,哪些面是背向面。OpenGL使用了一个很聪明的技巧,分析顶点数据的环绕顺序。
2.环绕顺序
当我们定义一组三角形顶点时,我们会以特定的环绕顺序来定义它们,可能是顺时针的,也可能是逆时针的。每个三角形由3个顶点所组成,我们会从三角形中间来看,为这3个顶点设定一个环绕顺序。
可以看到,我们首先定义了顶点1,之后我们可以选择定义顶点2或者顶点3,这个选择将定义了这个三角形的环绕顺序。
float vertices[] = {
// 顺时针
vertices[0], // 顶点1
vertices[1], // 顶点2
vertices[2], // 顶点3
// 逆时针
vertices[0], // 顶点1
vertices[2], // 顶点3
vertices[1] // 顶点2
};
OpenGL在渲染图元的时候将使用这个信息来决定一个三角形是一个正向三角形还是背向三角形。默认情况下,逆时针顶点所定义的三角形将会被处理为正向三角形。
在顶点数据中,我们定义的是两个逆时针顺序的三角形。然而,从观察者的方面看,后面的三角形是顺时针的,如果我们仍以1、2、3的顺序以观察者当面的视野看的话。即使我们以逆时针顺序定义后面的三角形,它现在还是变为顺时针。它正是我们打算剔除(丢弃)的不可见的面!
3.面剔除
要想启用面剔除,我们只需要启用OpenGL的GL_CULL_FACE选项:
glEnable(GL_CULL_FACE);
从这一句代码之后,所有背向面都将被丢弃。我们在渲染片段的时候能够节省50%以上的性能,但注意这只对像立方体这样的封闭形状有效。
OpenGL允许我们改变需要剔除的面的类型,可以调用glCullFace来实现:
glCullFace(GL_FRONT);
glCullFace函数有三个可用的选项:
GL_BACK
:只剔除背向面。GL_FRONT
:只剔除正向面。GL_FRONT_AND_BACK
:剔除正向面和背向面。
glCullFace的初始值是GL_BACK。除了需要剔除的面之外,我们也可以通过调用glFrontFace,告诉OpenGL我们希望将顺时针的面(而不是逆时针的面)定义为正向面:
glFrontFace(GL_CCW);
默认值是GL_CCW,它代表的是逆时针的环绕顺序,另一个选项是GL_CW,它(显然)代表的是顺时针顺序。
4.示例
以下是原始图形:
开启面剔除以后:
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);//剔除背向面
glFrontFace(GL_CW);//顺时针的环绕顺序是正向面,逆时针的就会被剔除
修改一下顶点顺序:
#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLTexture>
#include <QImage>
#include <QOpenGLShaderProgram>
class MyOpenGLWidget : public QOpenGLWidget,public QOpenGLFunctions_3_3_Core
{
public:
MyOpenGLWidget(QWidget *parent = nullptr);
protected:
virtual void initializeGL();
virtual void paintGL();
virtual void resizeGL(int w, int h);
private:
QOpenGLTexture *m_wall;
QOpenGLTexture *m_face;
QOpenGLShaderProgram *m_program;
};
#endif // MYOPENGLWIDGET_H
#include "myopenglwidget.h"
#include <QMatrix4x4>
#include <QTime>
#include <QTimer>
#include <math.h>
#include <QVector3D>
#include <QVector>
float vertices[] = {
//坐标 //纹理坐标
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, //交换这两行顺序
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, //交换这两行顺序
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};
GLuint indices[] = {
0, 1, 3,
1, 2, 3
};
//顶点着色器语言
const GLchar* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 position;\n"
"layout (location = 1) in vec2 texCoord;\n"
"out vec2 outTexCoord;\n"
"uniform mat4 model;\n"
"uniform mat4 view;\n"
"uniform mat4 projection;\n"
"void main()\n"
"{\n"
"gl_Position = projection * view * model * vec4(position,1.0);\n"
"outTexCoord = texCoord;\n"
"}\n\0";
//片段着色器语言
//texture函数会使用之前设置的纹理参数对相应的颜色值进行采样
//mix按一定的比例,混合两个纹理颜色
const GLchar* fragmentShaderSource = "#version 330 core\n"
"out vec4 color;\n"
"uniform sampler2D ourTexture1;\n"
"uniform sampler2D ourTexture2;\n"
"in vec2 outTexCoord;\n"
"void main()\n"
"{\n"
"color = mix(texture(ourTexture1, outTexCoord),texture(ourTexture2, vec2(outTexCoord.x, outTexCoord.y)),0.45);\n"
"}\n\0";
GLuint VBO, VAO,EBO;
GLuint shaderProgram;
QTimer *timer;
QVector<QVector3D> cubePositions = {
QVector3D( 0.0f, 0.0f, 0.0f),
QVector3D( 2.0f, 5.0f, -15.0f),
QVector3D(-1.5f, -2.2f, -2.5f),
QVector3D(-3.8f, -2.0f, -12.3f),
QVector3D( 2.4f, -0.4f, -3.5f),
QVector3D(-1.7f, 3.0f, -7.5f),
QVector3D( 1.3f, -2.0f, -2.5f),
QVector3D( 1.5f, 2.0f, -2.5f),
QVector3D( 1.5f, 0.2f, -1.5f),
QVector3D(-1.3f, 1.0f, -1.5f)
};
MyOpenGLWidget::MyOpenGLWidget(QWidget *parent)
: QOpenGLWidget(parent)
{
timer = new QTimer();
timer->start(50);
connect(timer,&QTimer::timeout,[=]{
update();
});
}
void MyOpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
m_program = new QOpenGLShaderProgram();
m_program->addShaderFromSourceCode(QOpenGLShader::Vertex,vertexShaderSource);
m_program->addShaderFromSourceCode(QOpenGLShader::Fragment,fragmentShaderSource);
m_program->link();
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);//绑定VAO
glBindBuffer(GL_ARRAY_BUFFER, VBO);//顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//把顶点数据复制到缓冲的内存中GL_STATIC_DRAW :数据不会或几乎不会改变。
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glBindVertexArray(0);//解绑VAO
m_wall = new QOpenGLTexture(QImage("./container.jpg").mirrored());
m_face = new QOpenGLTexture(QImage("./awesomeface.png").mirrored());
m_program->bind();
m_program->setUniformValue("ourTexture1",0);
m_program->setUniformValue("ourTexture2",1);
//设置透视投影矩阵
QMatrix4x4 projection;
projection.perspective(45,(float)(width())/(height()),0.1,100);
m_program->setUniformValue("projection",projection);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);//剔除背向面
glFrontFace(GL_CW);//顺时针的环绕顺序是正向面,逆时针的就会被剔除
}
void MyOpenGLWidget::paintGL()
{
glClearColor(0.2f,0.3f,0.3f,1.0f);
glEnable(GL_DEPTH_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
QMatrix4x4 model;
QMatrix4x4 view;
view.translate(0,0,-4);
m_program->bind();
glBindVertexArray(VAO);//绑定VAO
m_wall->bind(0);
m_face->bind(1);
//设置观察矩阵
m_program->setUniformValue("view",view);
// foreach(auto pos , cubePositions)
// {
// //设置为单位矩阵
// model.setToIdentity();
// //平移
// model.translate(pos);
// //设置模型矩阵
// m_program->setUniformValue("model",model);
// glDrawArrays(GL_TRIANGLES,0,36);
// }
model.setToIdentity();
model.rotate(45,0.3,0.3);
m_program->setUniformValue("model",model);
glDrawArrays(GL_TRIANGLES,0,36);
}
void MyOpenGLWidget::resizeGL(int w, int h)
{
}