美文网首页
OpenGL正背面剔除、深度测试

OpenGL正背面剔除、深度测试

作者: 东旭39 | 来源:发表于2020-07-12 15:47 被阅读0次

OpenGL在进行透视投影要经过 model matrix->view matrix -> projection matrix ->viewport transform 这几个矩阵变换,才能看到我们想要的形状。
已甜甜圈案例开始
opengl提供了甜甜圈的API

    //参数1:GLTriangleBatch 容器帮助类
    //参数2:外边缘半径
    //参数3:内边缘半径
    //参数4、5:主半径和从半径的细分单元数量
    gltMakeTorus(torusBatch, 1.0f, 0.3f, 52, 26);

其次在渲染的回调函数中进行渲染, glutDisplayFunc(RenderScene);

//渲染场景
void RenderScene()
{
    //1.清除窗口和深度缓冲区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    //2.把摄像机矩阵压入模型矩阵中
    modelViewMatix.PushMatrix(viewFrame);
    
    //3.设置绘图颜色
    GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };
    
    //使用默认光源着色器
    //通过光源、阴影效果跟提现立体效果
    //参数1:GLT_SHADER_DEFAULT_LIGHT 默认光源着色器
    //参数2:模型视图矩阵
    //参数3:投影矩阵
    //参数4:基本颜色值
    shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
    
    //5.绘制
    torusBatch.Draw();

    //6.出栈 绘制完成恢复
    modelViewMatix.PopMatrix();
    
    //7.交换缓存区
    glutSwapBuffers();
}

甜甜圈使用了默认光源着色器,。如下图


甜甜圈

但是如果对甜甜圈进行选装,就会出现bug


甜甜圈2

原因,由于开启了默认光源着色器,那么旋转甜甜圈就会有可视面与阴影面。对于观察者可见的部分要渲染,对观察者不可见的部分要丢弃。
而传统的油画算法在遇到重叠图形并不能很好的解决这个问题。
解决方法,正背面剔除可以有效的解决这个问题。

正背面剔除

OpenGL是如果判断哪个是正面哪个是背面?

  • 根据顶点的顺序,默认情况下,顶点图元装配的时候逆时针为正面,顺时针为背面。


    截屏2020-07-12 14.49.46.png

    开启/关闭正背面剔除功能

    if (iCull) {
        glEnable(GL_CULL_FACE);
        glFrontFace(GL_CCW);
        glCullFace(GL_BACK);
    }else
    {
        glDisable(GL_CULL_FACE);
    }

glCullFace的参数:GL_FRONT,GL_BACK,GL_FRONT_AND_BACK,你让GL_BACK
[图片上传中...(截屏2020-07-12 14.51.50.png-c12140-1594536803110-0)]


截屏2020-07-12 14.51.50.png

此时选择甜甜圈没有上图的问题,但是继续渲染又会出现下图的问题,

截屏2020-07-12 14.52.03.png

甜甜圈缺少了一块

原因->在旋转的时候,OpenGL不清楚重叠部分要显示的是前面的部分还是后面部分。
此时就有了深度测试

什么是深度

深度就是在openGL坐标系中,像素点的 Z 坐标距离观察者的距离. 当观察者可以放在坐标系的任意位置,。

  • 如果观察者在Z轴的正⽅向, Z值越⼤则靠近观察者;
  • 如果观察者在Z轴的负⽅向, Z值越⼩则靠近观察者;

深度缓冲区(DepthBuffer):

  • 深度缓冲区存储在显存中;
  • 原理->把距离观察者平⾯(近裁剪⾯)的深度值 与 窗⼝中每个像素点1对1进⾏关联以及存储

为什么要使用深度缓冲区

  1. 在不使⽤深度测试的时候,如果我们先绘制⼀个距离⽐较近的物体,再绘制距离较远的物理,则距离远的位图因为后绘制,会把距离近的物体覆盖掉.
  2. 有了深度缓冲区后,绘制物体的顺序就不那么要的.只要存在深度缓冲区,OpenGL 都会把像素的深度值写⼊到缓冲区中. 除⾮调⽤glDepthMask(GL_FALSE)来禁⽌写⼊.
    清空深度缓冲区
glclear(GL_DEPT_BUFFER_BIT)

开启深度测试

glEnable(GL_DEPTH_TEST)

深度测试

深度缓冲区(DepthBuffer)和颜⾊缓存区(ColorBuffer)是对应的。颜⾊缓存区存储像素的颜⾊信息,⽽深度缓冲区存储像素的深度信息.
在决定是否绘制⼀个物体表⾯时,⾸先要将表⾯对应的像素的深度值与当前深度缓冲区中的值进⾏⽐较。如果⼤于深度缓冲区中的值,则丢弃这部分.否则利⽤这个像素对应的深度值和颜⾊值.分别更新深度缓冲区和颜⾊缓存区。这个过程称为”深度测试”


截屏2020-07-12 15.21.25.png

已上面这张图为例,图中两个view部分相互重叠,且蓝色视图比橙色视图离观察者更近,那么蓝色视图的深度值要比橙色的值大。因此OpenGL在进行深度测试的时候重叠部分替换为蓝色。

修改深度测试的测试规则

glDepthFunc(GLEbum func)

深度测试的潜在风险

z-fighting (z冲突)(但是现在设备很少会出现)
原因,z值非常的接近,导致无法非常精确的知道位置。

  • 解决方案
    在2个图层之间加⼊⼀个微妙的间隔。OpenGL 提供⼀个解决⽅案, "多边形偏移"
  1. 启用多边形偏移 Polygon Offset
//启⽤Polygon Offset ⽅式 
glEnable(GL_POLYGON_OFFSET_FILL) 
参数列表:
GL_POLYGON_OFFSET_POINT 对应模式: GL_POINT
GL_POLYGON_OFFSET_LINE 对应模式: GL_LINE 
GL_POLYGON_OFFSET_FILL 对应模式: GL_FILL
  1. 指定偏移量
    ⼀般⽽⾔,只需要将-1 和 -1 这样简单赋值给glPolygonOffset 基本可以满⾜需求.
  2. 关闭多边形偏移
 glDisable(GL_POLYGON_OFFSET_FILL)

完整代码

//演示了OpenGL背面剔除,深度测试,和多边形模式
#include "GLTools.h"    
#include "GLMatrixStack.h"
#include "GLFrame.h"
#include "GLFrustum.h"
#include "GLGeometryTransform.h"

#include <math.h>
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif

////设置角色帧,作为相机
GLFrame             viewFrame;
//使用GLFrustum类来设置透视投影
GLFrustum           viewFrustum;
GLTriangleBatch     torusBatch;
GLMatrixStack       modelViewMatix;
GLMatrixStack       projectionMatrix;
GLGeometryTransform transformPipeline;
GLShaderManager     shaderManager;

//标记:背面剔除、深度测试
int iCull = 0;
int iDepth = 0;

//右键菜单栏选项
void ProcessMenu(int value)
{
    switch(value)
    {
        case 1:
            iDepth = !iDepth;
            break;
            
        case 2:
            iCull = !iCull;
            break;
            
        case 3:
            //多边形
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
            break;
            
        case 4:
            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
            break;
            
        case 5:
            glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
            break;
    }
    
    glutPostRedisplay();
}


// 召唤场景
void RenderScene(void)
{
    //清除窗口和深度缓冲区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    //根据设置iCull标记来判断是否开启背面剔除
    if(iCull)
    {
        glEnable(GL_CULL_FACE);
        glFrontFace(GL_CCW);
        glCullFace(GL_BACK);
    }
    else
        glDisable(GL_CULL_FACE);
    
    //根据设置iDepth标记来判断是否开启深度测试
    if(iDepth)
        glEnable(GL_DEPTH_TEST);
    else
        glDisable(GL_DEPTH_TEST);
    
    //把摄像机矩阵压入模型矩阵中
    modelViewMatix.PushMatrix(viewFrame);
    
    GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };
    
    //使用默认光源着色器
    //通过光源、阴影效果跟提现立体效果
    //参数1:GLT_SHADER_DEFAULT_LIGHT 默认光源着色器
    //参数2:模型视图矩阵
    //参数3:投影矩阵
    //参数4:基本颜色值
    shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
    
    //绘制
    torusBatch.Draw();
    //出栈
    modelViewMatix.PopMatrix();
    glutSwapBuffers();
}


void SetupRC()
{
    // 设置背景颜色
    glClearColor(0.3f, 0.3f, 0.3f, 1.0f );
    
    //初始化着色器管理器
    shaderManager.InitializeStockShaders();
    
    //将相机向后移动7个单元:肉眼到物体之间的距离
    viewFrame.MoveForward(7.0);
    
    //创建一个甜甜圈
    //void gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);
    //参数1:GLTriangleBatch 容器帮助类
    //参数2:外边缘半径
    //参数3:内边缘半径
    //参数4、5:主半径和从半径的细分单元数量
   
    gltMakeTorus(torusBatch, 1.0f, 0.3f, 52, 26);
   //点的大小
    glPointSize(4.0f);
}

//键位设置,通过不同的键位对其进行设置
//控制Camera的移动,从而改变视口
void SpecialKeys(int key, int x, int y)
{
    if(key == GLUT_KEY_UP)
        viewFrame.RotateWorld(m3dDegToRad(-5.0), 1.0f, 0.0f, 0.0f);
    
    if(key == GLUT_KEY_DOWN)
        viewFrame.RotateWorld(m3dDegToRad(5.0), 1.0f, 0.0f, 0.0f);
    
    if(key == GLUT_KEY_LEFT)
        viewFrame.RotateWorld(m3dDegToRad(-5.0), 0.0f, 1.0f, 0.0f);
    
    if(key == GLUT_KEY_RIGHT)
        viewFrame.RotateWorld(m3dDegToRad(5.0), 0.0f, 1.0f, 0.0f);
    
    //重新刷新window
    glutPostRedisplay();
}


void ChangeSize(int w, int h)
{
    //1.防止h变为0
    if(h == 0)
        h = 1;
    
    //2.设置视口窗口尺寸
    glViewport(0, 0, w, h);
    
    //3.setPerspective函数的参数是一个从顶点方向看去的视场角度(用角度值表示)
    // 设置透视模式,初始化其透视矩阵
    viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 100.0f);
    
    //4.把透视矩阵加载到透视矩阵对阵中
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    
    //5.初始化渲染管线
    transformPipeline.SetMatrixStacks(modelViewMatix, projectionMatrix);
}


int main(int argc, char* argv[])
{
    gltSetWorkingDirectory(argv[0]);
    
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    glutInitWindowSize(800, 600);
    glutCreateWindow("Geometry Test Program");
    glutReshapeFunc(ChangeSize);
    glutSpecialFunc(SpecialKeys);
    glutDisplayFunc(RenderScene);
    
    // Create the Menu
    glutCreateMenu(ProcessMenu);
    glutAddMenuEntry("Toggle depth test",1);
    glutAddMenuEntry("Toggle cull backface",2);
    glutAddMenuEntry("Set Fill Mode", 3);
    glutAddMenuEntry("Set Line Mode", 4);
    glutAddMenuEntry("Set Point Mode", 5);
    
    glutAttachMenu(GLUT_RIGHT_BUTTON);
    
    
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
        return 1;
    }
    
    SetupRC();
    
    glutMainLoop();
    return 0;
}

相关文章

网友评论

      本文标题:OpenGL正背面剔除、深度测试

      本文链接:https://www.haomeiwen.com/subject/mfbicktx.html