您当前的位置: 首页 >  qt

龚建波

暂无认证

  • 3浏览

    0关注

    312博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

OpenGL with QtWidgets:练习之甜甜圈

龚建波 发布时间:2022-02-22 23:58:10 ,浏览量:3

甜甜圈是 《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()            
关注
打赏
1655829268
查看更多评论
0.1783s