甜甜圈是 《OpenGL 超级宝典》上的一个示例,用来演示面剔除和深度测试应用,原本的代码顶点和着色器部分不便于学习,我就重新写了下,略去了法线和光照相关。
当我们对渲染出来的甜甜圈进行旋转的时候,会出现一些不符合预期的渲染效果,一些原本应该被遮挡的部分被渲染了出来。
如果只是简单的不渲染被遮挡的背面,可以使用面剔除。因为渲染的面减少了,这也提高了性能。如果确定正面还是背面呢?OpenGL 是通过三角顶点的绕序来决定的,从观察者角度看,默认逆时针的顶点连接顺序被定义为三角形的正面,同时面剔除默认是剔除背面。
如图,逆时针的三角在近处时可见,但是旋转到立方体另一面时,观察着角度看到的是顺时针方向旋转,于是会被剔除。面剔除设置方式:
//使能面剔除
glEnable(GL_CULL_FACE);
//GL_FRONT剔除正面,GL_BACK剔除背面(默认),GL_FRONT_AND_BACK剔除正反面
glCullFace(GL_FRONT);
//设置正面的环绕方式,GL_CCW逆时针(默认),GL_CW顺时针
glFrontFace(GL_CW);
启用背面剔除后,再旋转甜甜圈,发现还有另一个渲染问题,从侧面看的时候有一个缺口渲染成了内环的表面。
想要按照 z 轴的远近正确的渲染,需要开启深度测试。对于一些半透明的效果,也是不能直接剔除掉的。
深度缓冲(也叫 z-buffer)就像颜色缓冲(Color Buffer)那样存储每个片段的信息,(通常) 和颜色缓冲区有相同的宽度和高度。深度缓冲由窗口系统自动创建并将其深度值存储为 16、 24 或 32 位浮点数。在大多数系统中深度缓冲区为24位。
当深度测试启用的时候, OpenGL 测试深度缓冲区内的深度值。OpenGL 执行深度测试的时候,如果此测试通过,深度缓冲内的值将被设为新的深度值。如果深度测试失败,则丢弃该片段。深度测试在片段着色器和模板测试运行之后在屏幕空间进行。
深度测试设置方式:
//深度测试默认是关闭的,需要用GL_DEPTH_TEST选项启用深度测试
glEnable(GL_DEPTH_TEST);
//在某些情况下我们需要进行深度测试并相应地丢弃片段,但我们不希望更新深度缓冲区,
//基本上,可以使用一个只读的深度缓冲区;
//OpenGL允许我们通过将其深度掩码设置为GL_FALSE禁用深度缓冲区写入:
//glDepthMask(GL_FALSE);
//可以修改深度测试使用的比较规则,默认GL_LESS丢弃深度大于等于的
//glDepthFunc(GL_LESS);
//渲染之前使用GL_DEPTH_BUFFER_BIT清除深度缓冲区
//否则深度缓冲区将保留上一次进行深度测试时所写的深度值
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
最终效果:
深度测试也不是万能的,当两个面太近时(如正反面),就无法通过 z 轴来确认哪一个靠前了,这被称为深度冲突(z-fighting),LearnOpenGL 上提供了一些防止深度冲突的方法:
不要离太近,在之间制造一点细微的偏移;把近平面设置远一点提高精度;或者更高的深度值精度,如 32 位。
本文代码链接(MyTorus 类):https://github.com/gongjianbo/OpenGLwithQtWidgets
主要代码:
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
//甜甜圈-环面
//参考OpenGL超级宝典第三章Demo
//QOpenGLWidget窗口上下文
//QOpenGLFunctions访问OpenGL接口,可以不继承作为成员变量使用
class MyTorus
: public QOpenGLWidget
, protected QOpenGLFunctions_4_5_Core
{
Q_OBJECT
public:
explicit MyTorus(QWidget *parent = nullptr);
~MyTorus();
protected:
//【】继承QOpenGLWidget后重写这三个虚函数
//设置OpenGL资源和状态。在第一次调用resizeGL或paintGL之前被调用一次
void initializeGL() override;
//渲染OpenGL场景,每当需要更新小部件时使用
void paintGL() override;
//设置OpenGL视口、投影等,每当尺寸大小改变时调用
void resizeGL(int width, int height) override;
//鼠标操作,重载Qt的事件处理
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
private:
//使用Qt提供的便捷类
QOpenGLShaderProgram shaderProgram;
QOpenGLVertexArrayObject vao;
QOpenGLBuffer vbo;
//顶点数据
QVector vertex;
//
QVector3D rotationAxis;
QQuaternion rotationQuat;
//透视投影的fovy参数,视野范围
float projectionFovy{45.0f};
//鼠标位置
QPoint mousePos;
//右键菜单
QMenu *menu;
bool enableDepthTest{false};
bool enableCullBackFace{false};
int drawMode{0};
};
#include "MyTorus.h"
#include
#include
#include
MyTorus::MyTorus(QWidget *parent)
: QOpenGLWidget(parent)
{
setFocusPolicy(Qt::ClickFocus); //默认没有焦点
rotationQuat = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, 90.0f);
//设置成core核心模式QPainter才能正常的绘制
QSurfaceFormat fmt = format();
fmt.setRenderableType(QSurfaceFormat::OpenGL);
fmt.setProfile(QSurfaceFormat::CoreProfile);
fmt.setVersion(4, 5);
setFormat(fmt);
//右键菜单
menu = new QMenu(this);
menu->addAction("Toggle depth test", [this]{
enableDepthTest = !enableDepthTest;
update();
});
menu->addAction("Toggle cull backface", [this]{
enableCullBackFace = !enableCullBackFace;
update();
});
menu->addAction("Set Fill Mode", [this]{
drawMode = 0;
update();
});
menu->addAction("Set Line Mode", [this]{
drawMode = 1;
update();
});
menu->addAction("Set Point Mode", [this]{
drawMode = 2;
update();
});
}
MyTorus::~MyTorus()
{
//initializeGL在显示时才调用,释放未初始化的会异常
if(!isValid())
return;
//QOpenGLWidget
//三个虚函数不需要makeCurrent,对应的操作已由框架完成
//但是释放时需要设置当前上下文
makeCurrent();
vao.destroy();
vbo.destroy();
doneCurrent();
}
void MyTorus::initializeGL()
{
//QOpenGLFunctions
//为当前上下文初始化opengl函数解析
initializeOpenGLFunctions();
//着色器代码
//in输入,out输出,uniform从cpu向gpu发送
const char *vertex_str=R"(#version 450 core
layout (location = 0) in vec3 vPos;
uniform mat4 mvp;
out vec3 thePos;
void main() {
gl_Position = mvp * vec4(vPos, 1.0f);
thePos = vPos;
})";
const char *fragment_str=R"(#version 450 core
in vec3 thePos;
out vec4 fragColor;
void main() {
float red = abs(sqrt(thePos.x * thePos.x + thePos.z * thePos.z));
fragColor = vec4(red, 0.0f, 0.0f, 1.0f);
})";
//顶点着色器
//可以直接add着色器代码,也可以借助QOpenGLShader类
bool success=shaderProgram.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex,vertex_str);
if(!success){
qDebug()
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?