甜甜圈是 《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 <QOpenGLWidget>
#include <QOpenGLFunctions_4_5_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QVector3D>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QMenu>//甜甜圈-环面
//参考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<QVector3D> 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 <QtMath>
#include <QPainter>
#include <QCursor>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()<<"compiler vertex failed!"<<shaderProgram.log();}//片段着色器success=shaderProgram.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment,fragment_str);if(!success){qDebug()<<"compiler fragment failed!"<<shaderProgram.log();}success = shaderProgram.link();if(!success){qDebug()<<"link shader failed!"<<shaderProgram.log();}float a,x,y;const float out_r=1.0f;//外圆半径const float in_r=0.5f;//内圆半径const float section_r=(out_r-in_r)/2;//截面圆环半径const int section_slice=24;//截面圆边定点数const int torus_slice=24;//整体圆环截面数//1.先求一个截面的顶点做基准QVector<QVector3D> section_vec;for(int i=0;i<section_slice;i++){a=qDegreesToRadians(360.0f/section_slice*i);x=section_r*cos(a);y=section_r*sin(a);QVector3D vec(x,y,0);section_vec.push_back(vec);}//2.再对截面定点进行坐标变换,形成一个环面的顶点//3.对顶点进行组织,使之能围成三角QMatrix4x4 cur_mat;//当前截面的变换矩阵QMatrix4x4 next_mat;//下一个截面的变换矩阵next_mat.setToIdentity();next_mat.translate(out_r-section_r,0,0);for(int i=0;i<torus_slice;i++){cur_mat=next_mat;next_mat.setToIdentity();next_mat.rotate(360.0f/torus_slice*(i+1),0,1.0f,0);next_mat.translate(out_r-section_r,0,0);for(int j=0;j<section_vec.size();j++){vertex.push_back(cur_mat * section_vec.at(j));vertex.push_back(next_mat * section_vec.at(j));}vertex.push_back(vertex.at(vertex.size()-section_vec.size()*2));vertex.push_back(vertex.at(vertex.size()-section_vec.size()*2));}vao.create();vao.bind();vbo=QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);vbo.create();vbo.bind();vbo.allocate((void *)vertex.data(), sizeof(GLfloat) * vertex.size() * 3);//setAttributeBuffer(int location, GLenum type, int offset, int tupleSize, int stride = 0)shaderProgram.setAttributeBuffer(0, GL_FLOAT, 0, 3, sizeof(GLfloat) * 3);shaderProgram.enableAttributeArray(0);vbo.release();vao.release();//清屏设置glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
}void MyTorus::paintGL()
{//深度测试if(enableDepthTest){glEnable(GL_DEPTH_TEST);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);}else{glClear(GL_COLOR_BUFFER_BIT);}//面剔除if(enableCullBackFace){glEnable(GL_CULL_FACE);}else{glDisable(GL_CULL_FACE);}//绘制模式if(drawMode==0){//正反面都设置为填充glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);}else if(drawMode==1){glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);}else{glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);glPointSize(4.0f);}shaderProgram.bind();//观察矩阵QMatrix4x4 view;float radius = 3.0f;view.translate(0.0f, 0.0f, -radius);view.rotate(rotationQuat);//透视投影QMatrix4x4 projection;projection.perspective(projectionFovy, 1.0f * width() / height(), 0.1f, 100.0f);shaderProgram.setUniformValue("mvp", projection * view);if(!vertex.isEmpty()){QOpenGLVertexArrayObject::Binder vao_bind(&vao); Q_UNUSED(vao_bind);//使用当前激活的着色器和顶点属性配置和VBO(通过VAO间接绑定)来绘制图元//void glDrawArrays(GLenum mode​, GLint first​, GLsizei count​);//参数1为图元类型//参数2指定顶点数组的起始索引//参数3指定顶点个数//GL_TRIANGLE_STRIP三角形带glDrawArrays(GL_TRIANGLE_STRIP, 0, vertex.size());}shaderProgram.release();//设置为fill,不然会影响QPainter绘制的图glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);QPainter painter(this);painter.setPen(Qt::white);painter.setFont(QFont("Microsoft YaHei",16));painter.drawText(20, 50, "Click right mouse button popup menu");
}void MyTorus::resizeGL(int width, int height)
{glViewport(0, 0, width, height);
}void MyTorus::mousePressEvent(QMouseEvent *event)
{event->accept();if(event->button()==Qt::RightButton){menu->popup(QCursor::pos());}else{mousePos = event->pos();}
}void MyTorus::mouseReleaseEvent(QMouseEvent *event)
{event->accept();
}void MyTorus::mouseMoveEvent(QMouseEvent *event)
{event->accept();//参照示例cubeQVector2D diff = QVector2D(event->pos()) - QVector2D(mousePos);mousePos = event->pos();QVector3D n = QVector3D(diff.y(), diff.x(), 0.0).normalized();rotationAxis = (rotationAxis + n).normalized();//不能对换乘的顺序rotationQuat = QQuaternion::fromAxisAndAngle(rotationAxis, 3.0f) * rotationQuat;update();
}void MyTorus::wheelEvent(QWheelEvent *event)
{event->accept();//fovy越小,模型看起来越大if(event->delta() < 0){//鼠标向下滑动为-,这里作为zoom outprojectionFovy += 0.5f;if(projectionFovy > 90)projectionFovy = 90;}else{//鼠标向上滑动为+,这里作为zoom inprojectionFovy -= 0.5f;if(projectionFovy < 1)projectionFovy = 1;}update();
}

OpenGL with QtWidgets:练习之甜甜圈相关推荐

  1. OpenGL with QtWidgets:练习之扑克翻转

    (本文是LearnOpenGL的学习笔记,教程中文翻译地址https://learnopengl-cn.github.io/(备用地址https://learnopengl-cn.readthedoc ...

  2. OpenGL with QtWidgets:投光物、多光源

     (本文是LearnOpenGL的学习笔记, 教程中文翻译地址https://learnopengl-cn.github.io/(备用地址https://learnopengl-cn.readthed ...

  3. OpenGL with QtWidgets:练习之绘制2D环形进度条

    1.实现思路 这里主要涉及几个点:绘制圆环,绘制文字,动画,抗锯齿. 绘制圆环网上有些人是计算好圆边的顶点后传入的,我这里直接在片段着色器里根据距离圆心的距离来渲染的圆环. void main() { ...

  4. pd.read_excel()和 pd.to_excel() 参数详解

    pandas.read_excel(io,sheet_name = 0,header = 0,names = None,index_col = None,usecols = None,squeeze ...

  5. opengl嵌入pyqt5编译的分割窗口中

    opengl嵌入pyqt5编译的分割窗口中 向大部分商业仿真软件一样,使用opengl显示三维模型,首先需要将opengl的窗口嵌入到pyqt5编译的界面中 下面是一个小例子: [python] vi ...

  6. python调用opengl_Python运行OpenGL示例

    说明 python的opengl库叫PyOpenGL,安装这个库的时候要注意位数版本要正确,比如我的python37是32位的,安装PyOpenGL-3.1.5-cp37-cp37m-win32.wh ...

  7. OPenGL 基本知识(根据自己理解整理)

    1.坐标系 计算机利用OpenGL可以把三维世界中的三维物体,在二维屏幕上显示出来.如下图(来源于网络): OpenGL图形渲染管线(Pipeline)学习 一部摄像机放在视椎体的顶部,也就是视椎体四 ...

  8. oZone3D FurMark(甜甜圈furmark显卡压力测试软件)绿色单文件版V1.9.2 | 电脑烤机测试软件

    FurMark是来自oZone3D开发的一款OpenGL基准测试工具,通过皮毛渲染算法来衡量显卡的性能,可以对显卡进行地狱一般的折磨,借此考验显卡的稳定性,就是大家常说的显卡压力测试软件,俗称甜甜圈f ...

  9. pyqtgraph 在Opengl模式下开启抗锯齿效果

    pyqtgraph 在Opengl模式下开启抗锯齿效果 修改pyqtgraph源码文件pyqtgraph/widgets/GraphicsView.py def useOpenGL(self, b=T ...

最新文章

  1. 【怎样写代码】工厂三兄弟之抽象工厂模式(六):扩展案例II
  2. struts tags 学习
  3. php 微信公众号 记录数据库,PHP构建微信公众号关键字数据库多图文回复
  4. linux安装redis集群+常见报错
  5. vue从入门到精通之进阶篇(二)组件通信:兄弟组件通信
  6. 牛客网【每日一题】5月22日 [CQOI2009]中位数图
  7. 少儿编程100讲轻松学python(十七)-pycharm如何配置python环境
  8. 人工智能技术将重塑银行业
  9. 分治法在排序算法中的应用(JAVA)--快速排序(Lomuto划分、Hoare划分、随机化快排)
  10. 配置cocos2d-js 开发环境 通过CMD 创建工程
  11. 方维模板修改,发布分享、主题有商品时,标签需自动写到input里,不要再手动去点击添加,手动点击可取消...
  12. Linux bind DNS配置
  13. DLL导出类避免地狱问题的完美解决方案
  14. 型材行业ERP-MES应用点滴
  15. wsdl文件怎么看服务器地址,wsdl文件 服务器地址
  16. matlab imagesc叠加+矩阵定位
  17. 新加坡经验:大数据时代政府的角色
  18. C语言编程-对数字进行分类
  19. 神奇的Python图片处理库exifread
  20. html img素材,html使用img标签和背景图片之间的区别

热门文章

  1. CF869E The Untended Antiquity(二维数状数组+差分+hash)
  2. Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
  3. js二维码样式生成插件easy.qrcode.js
  4. python爬虫(20)使用真实浏览器打开网页的两种方法
  5. 教程 | 10分钟入门简笔画(哆啦A梦篇)
  6. 这样给宝宝起小名,好听不俗气
  7. 猫狗图像识别(卷积神经网络算法,TensorFlow安装)
  8. 创建图 figure figcaption
  9. HACCP认证咨询,食品生产过程中的主要危害是什么
  10. 微信开发者工具预览二维码无法显示