OpenGL

渲染过程中可能产生的问题

油画渲染

正面&背面剔除

深度测试

多边形模型

多边形偏移

裁剪

颜色混合

解决方案

油画法

正背面剔除

正面 逆时针 

背面 顺时针

    /// 开启表面剔除(默认背面剔除)
    glEnable(GL_CULL_FACE);
    /// 关闭表面剔除(默认背面剔除)
    glDisable(GL_CULL_FACE);
    /// 用户选择剔除哪个面(正面/背面)
    //glCullFace(GLenum mode)
    //model 参数为 GL_FRONT GL_BACK GL_FRONT_AND_BACK 默认为GL_BACK
    //用户指定绕序哪个为正面
    //glFrontFace(<#GLenum mode#>)
    //    model 参数为: GL_CW GL_CCW 默认 GL_CCW
    //例如剔除正面实现
    //glCullFace(GL_BACK)
    //glFrontFace(GL_CW)
    
    //例如剔除正面实现
    //glCullFace(GL_FRONT)

深度测试

深度:其实就是该像素在3d世界中距离摄像机的距离Z值

深度缓冲区:深度缓冲区就是一块内存区域 专门存储每个像素点(绘制在屏幕上的)深度值。深度值(z值)越大则离摄像机越远。

为什么需要深度缓冲区

在不使用深度测试的时候如果我们先绘制一个距离比较近的物理,再绘制较远的物理,则距离远的位图因为后绘制会把距离近的物体覆盖掉。有了深度缓冲区后绘制物体的顺序就不那么重要。实际上只要存在深度缓冲区OpenGL都会把像素的深度值写入到缓冲区中。除非调用glDepthMask(GL_FALSE)来禁止写入

深度测试

    深度缓冲区(DepthBuffer)和颜色缓冲区(ColorBuffer)是对应的。颜色缓存区存储像素的颜色信息,而深度缓冲区存储像素的深度信息。在决定是否绘制一个物体表面时,首先要将表面对应的像素深度值与当前深度缓冲区中的值进行比较。如果大于深度缓冲区中的值则丢弃这部分。否则利用这个像素对应的深度值和颜色值。分别更新深度缓冲区和颜色缓存区这个过程称为深度测试。

使用深度测试

深度缓冲区,一般由窗口管理系统GLFW创建深度值一半由16位24位32位值表示,通常是24位,位数越高深度精确度更好。

开启深度测试

glEnable(GL_DEPTH_TEST)

在绘制场景前,清除颜色缓存区深度缓冲

glClearColor(0.0f, 0.0f, 0.0f, 1.0f)

glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

深度缓冲区默认值为1.0;表示最大的深度值,深度值的范围为(0,1)之间。值越小表示越靠近观察者,值越大表示离观察者越远

指定深度测试判断方式

 glDepthFunc(<#GLenum func#>)

通过传入参数控制

OpenGL_第1张图片

打开/阻断 深度缓存区写入

GL_TRUE 开启深度缓冲区写入 

GL_FALSE 关闭深度缓冲区写入 

glDepthMask(<#GLboolean flag#>)

ZFighting 闪烁问题的原因

为什么会出现ZFighting

因为开启深度测试后OpenGL 就不会再去绘制模型被遮挡的部分,这样实现的显示更加真实,但是由于深度缓冲区精度的限制对于深度相差非常小的情况下(例如在同一瓶面上进行2次绘制)OpenGL就有可能出现不能正确判断两者的深度值会导致深度测试的结果不可预测。显示出来的现象时交错闪烁 前面两个画面交错出现

解决方案

第一步,启用 Polygon offset 方法解决

解决方法:让深度值之间产生间隔,如果2个图形之间有间隔就意味着不会产生干涉,可以理解为在执行深度测试前将立方体的深度值做一些细微的增加,于是就能将重叠的2个图形深度值之前有所区分。

            GL_POLYGON_OFFSET_FILL  //对应光栅化模式GL_FILL
            GL_POLYGON_OFFSET_LINE //对应光栅化模式GL_LINE
            GL_POLYGON_OFFSET_POINT //对应光栅化模式:GL_POINT
            /// 开启Polygon offset 方式
            glEnable(GL_POLYGON_OFFSET_FILL)

    //第二步指定偏移量

    通过glPolygonOffset 来指定 factor units

    每个Fragment 的深度值都会增加如下所示的偏移量

    offset = (m * factor) + (r *units)

    m:多边形的深度斜率的最大值 理解一个多边形越是与近裁剪面平行 m就越接近于0

    r:能产生于窗口坐标系的深度值中可分辨的差异最小值 r是由具体OpenGL平台指定的一个常量

    一个大雨0的offset 会把模型推到离你(摄像机)更远的位置 相应的一个小于0的offset会把模型拉近

    一般而言只要将-1.0和 1.0 这样简单赋值给glPolyOffset基本可以满足需求

    Depth offset = (DZ * factor) + r * units;

    DZ 深度值z值

    r:使得深度缓冲区产生变化的最小值

    负值,将使得z值距离我们更近而正值 将使得z值距离我们更远

    glPolygonOffset(GLfloat factor, GLfloat units)

第三步 关闭glPolygonOffset

    glDisable(GL_POLYGON_OFFSET_FILL)

预防:

 不要将两个物体靠的太近,避免渲染时三角形登在一起。这种方式要求对场景中物体插入一个少量的
偏移,那么就可能避免ZFighting 现象。例如上面的立方体和平面问题中,将平面下移0.001(可以解
決这个问题。当然手动去插入这个小的偏移是要付出代价的。
•尽可能将近裁剪面设置得离观察者远一些。上面我们看到,在近裁剪平面附近,深度的精确度是很高
的,因此尽可能让近裁剪面远一些的话,会使整个裁剪范围内的精确度变高一些。但是这种方式会使
离观察者较近的物体被裁减掉,因此需要调试好裁剪面参数。
• 使用更高位数的深度缓冲区,通常使用的深度缓冲区是24位的,现在有一些硬件使用使用32位的缓冲
区,使精确度得到提高

裁剪
在OpenGL 中提高渲染的一种方式.只刷新屏幕上发生变化的部分.OpenGL 允许将要进行渲染的窗口只
去指定一个裁剪框.
基本原理:用于渲染时限制绘制区域,通过此技术可以再屏幕(帧缓冲)指定一个矩形区域。启用剪裁
测试之后,不在此矩形区域内的片元被丟弃,只有在此矩形区域内的片元才有可能进入帧缓冲。因此实
际达到的效果就是在屏幕上开辟了一个小窗口,可以再其中进行指定内容的绘制。
们开启裁剪测试
glEnable(GL_SCISSOR_TEST);
1/2.关闭裁剪测试
gIDisable(GL_SCISSOR_ TEST);
13.指定裁剪窗口
void gIScissor(Glint ×, Glint y, GLSize width,GLSize height);
x.y:指定裁剪框左下角位置;
width,height:指定裁剪尺寸

理解窗口,视口,裁剪区域
•窗口:就是显示界面
•视口:就是窗口中用来显示图形的一块矩形区域,它可以和窗口等大,也可以比窗口大或者小。只有绘
制在视口区域中的图形才能被显示,如果图形有一部分超出了视口区域,那么那一部分是看不到的。
通过glViewport0西数设置。
•裁剪区域(平行投影):就是视口矩形区域的最小最大x坐标 (left,right)和最小最大y坐标
(bottom,top),而不是窗口的最小最大x坐标和y坐标。通过glOrthoD西数设置,这个西数还需指定最近
最远z坐标,形成一个立体的裁剪区域。

#include "GLTools.h"
#include "GLMatrixStack.h"
#include "GLFrame.h"
#include "GLFrustum.h"
#include "GLGeometryTransform.h"
#include 
#ifdef __APPLE__
#include 
#else
#include 
#endif
GLFrame viewFrame;
GLFrustum viewFrustum;
GLTriangleBatch toursBatch;
GLMatrixStack modelViewMatix;
GLMatrixStack projectionMatrix;
GLGeometryTransform transformPipelinel;
GLShaderManager shaderManager;

int iCull = 0;
int iDepth = 0;
void ProcessMenu(int value) {
    switch (value) {
        case 1:
            /// 开启正背面剔除
            iCull = !iCull;
            break;
        case 2:
            ///是否开启深度测试
            iDepth = !iDepth;
            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;
        default:
            break;
    }
    glutPostRedisplay();
}
void SetupRC() {
    glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
    shaderManager.InitializeStockShaders();
    viewFrame.MoveForward(7.0f);
    /*
     gltMakeTorus(GLTriangleBatch &torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor)
     torusBatch:绘图容器工具 三角形批次类
     majorRadius 外边缘半径
     minorRadius内边缘半径
     numMajor numMinor 主半径到从半径的细分单元三角形数量
     */
    gltMakeTorus(toursBatch, 1.0f, 0.3f, 52, 26);
    ///设置点的大小
    glPointSize(4.0f);
}
void ChangeSize(int w, int h) {
    if (h == 0) {
        h = 1;
    }
    glViewport(0, 0, w, h);
    // setPerspective 函数的参数是一个从顶点方向看去的视场角度(角度值)
    //设置透视模式 初始化透视矩阵
    viewFrustum.SetPerspective(35.0,float(w)/float(h),1.0f, 100.f);
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    transformPipelinel.SetMatrixStacks(modelViewMatix, projectionMatrix);
}
void SpecialKeys(int key, int x, int y) {
    if (key == GLUT_KEY_UP) {
        viewFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
    }
    if (key == GLUT_KEY_DOWN) {
        viewFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
    }
    if (key == GLUT_KEY_LEFT) {
        viewFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f,0.0f);
    }
    
    if (key == GLUT_KEY_RIGHT) {
        viewFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
    }
    glutPostRedisplay();
}
void RenderScene() {
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    if (iCull){
        
        
        ///开启背面剔除
        glEnable(GL_CULL_FACE);
        /// 指定逆时针顺序三角形为正面 /指定顺水针下三角形为正面
        glFrontFace(GL_CCW);
        /// 切除哪个面
        glCullFace(GL_BACK);
        
    } else {
        /// 关闭FaceCulling
        glDisable(GL_CULL_FACE);
    }
    
    if (iDepth) {
        glEnable(GL_DEPTH_TEST);
    } else {
        glDisable(GL_DEPTH_TEST);
    }
    /// 模型视图 图形发生变化:平移/旋转/缩放 仿射变化 矩阵值
    // 投影矩阵
    modelViewMatix.PushMatrix(viewFrame);
    GLfloat vRed[] = {1.0f,0.0f,0.0f,1.0f};
    
    ///平面着色器  GetModelViewProjectionMatrix 两个矩阵的乘积
//    shaderManager.UseStockShader(GLT_SHADER_FLAT,transformPipelinel.GetModelViewProjectionMatrix(),vRed);
    ///GLT_SHADER_DEFAULT_LIGHT  默认光源着色器类型
    /// 顶点着色器和存储着色器有什么关系
    /// 1.顶点着色器是可编程管线下的着色器 必须实现
    /// 2.存储着色器是固定管线下的进行封装的着色器 平面着色器GLT_SHADER_FLAT GLT_SHADER_DEFAULT_LIGHT
    /**
     参数1:着色器类型
     参数2:模型视图矩阵
     参数3:投影矩阵
     参数4:绘制图形的颜色
     */
    shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT,transformPipelinel.GetModelViewMatrix(),transformPipelinel.GetProjectionMatrix(),vRed);
    toursBatch.Draw();
    
    modelViewMatix.PopMatrix();
   /*
    /// 裁剪
    glClearColor(0.0f, 1.0f, 0.0f,  1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    //2.
    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glScissor(100, 100, 600, 400);
    glEnable(GL_SCISSOR_TEST);
    glClear(GL_COLOR_BUFFER_BIT);
    //3.
    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
    glScissor(200, 200, 200, 200);
    glEnable(GL_SCISSOR_TEST);
    glClear(GL_COLOR_BUFFER_BIT);*/

    glEnable(GL_SCISSOR_TEST);
    glutSwapBuffers();
    
}
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);
    GLenum err = glewInit();
    if (err != GLEW_OK) {
        fprintf(stderr, "glew error: %s\n",glewGetErrorString(err));
        
        return 1;
    }
   
    /// 设置右击菜单
    glutCreateMenu(ProcessMenu);
    glutAddMenuEntry("Toggle cull", 1);

    glutAddMenuEntry("Toggle deapth test", 2);
    glutAddMenuEntry("set fill mode", 3);
    glutAddMenuEntry("set line mode", 4);
    glutAddMenuEntry("set point mode", 5);

    glutAttachMenu(GLUT_RIGHT_BUTTON);

    SetupRC();
    glutMainLoop();
    return 0;
}



混合
我们把OpenGL 渲染时会把颜色值存在颜色缓存区中,每个片段的深度值也是放在深度缓冲区。当深度缓冲区被关闭时,新的颜色将简单的覆盖原来颜色缓存区存在的颜色值,当深度缓冲区再次打开时,新的颜色片段只是当它们比原来的值更接近邻近的裁剪平面才会替换原来的颜色片段。

组合颜色
目标颜色:已经存储在颜色缓存区的颜色值
源颜色:作为当前渲染命令结果进入颜色缓存区的颜色值
当混合功能被启动时,源颜色和目标颜色的组合方式是混合方程式控制的。在默认情况下,
混合方程式如下所示:
Cf = (Cs * S) + (Cd * D)
Cf:最终计算参数的颜色
Cs:源颜色
Cd:目标颜色
S:源混合因子
D:目标混合因子

下面通过一个常见的混合西数组合来说明问题:
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
如果颜色缓存区已经有一种颜色红色 (1.0f,0.0f,0.0f0.0f),这个目标颜色Cd,如果在这上面用一
种alpha为0.6的蓝色 (0.0f,0.0f,1.0f,0.6f)
Cd(目标颜色)= (1.01.0.01,0.0.0.0f)
Cs(源颜色)= (0.0f,0.0f,1.0f,0.6f)
S=源alpha值 = 0.6f
D=1-源alpha值=1-0.6f=0.4f
方程式Cf = (Cs* S)+(Cd * D)
等价于= (Blue * 0.6f) + (Red * 0.4f)

最终颜色是以原先的红色(目标颜色)与 后来的蓝色(源颜色)进行组合。源颜色的alpha值
越高,添加的蓝色颜色成分越高,目标颜色所保留的成分就会越少。
混合函数经常用于实现在其他一些不透明的物体前面绘制一个透明物体的效果。

//
//  main4.cpp
//  01 OpenGL 环境搭建
//
//  Created by KING on 2023/12/25.
//  Copyright © 2023 Miss CC. All rights reserved.
//

#include "main4.hpp"
#include "GLTools.h"
#include "GLShaderManager.h"
#ifdef __APPLE__
#include 
#else
#include 
#endif
GLBatch    squareBatch;
GLBatch greenBatch;
GLBatch redBatch;
GLBatch blueBatch;
GLBatch blackBatch;

static GLShaderManager    shaderManager;


GLfloat blockSize = 0.2f;
GLfloat vVerts[] = { -blockSize, -blockSize, 0.0f,
    blockSize, -blockSize, 0.0f,
    blockSize,  blockSize, 0.0f,
    -blockSize,  blockSize, 0.0f};



void SetupRect() {
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f );
    shaderManager.InitializeStockShaders();

    //绘制1个移动矩形
    squareBatch.Begin(GL_TRIANGLE_FAN, 4);
    squareBatch.CopyVertexData3f(vVerts);
    squareBatch.End();
    
    //绘制4个固定矩形
    GLfloat vBlock[] = { 0.25f, 0.25f, 0.0f,
        0.75f, 0.25f, 0.0f,
        0.75f, 0.75f, 0.0f,
        0.25f, 0.75f, 0.0f};
    
    greenBatch.Begin(GL_TRIANGLE_FAN, 4);
    greenBatch.CopyVertexData3f(vBlock);
    greenBatch.End();
    
    
    GLfloat vBlock2[] = { -0.75f, 0.25f, 0.0f,
        -0.25f, 0.25f, 0.0f,
        -0.25f, 0.75f, 0.0f,
        -0.75f, 0.75f, 0.0f};
    
    redBatch.Begin(GL_TRIANGLE_FAN, 4);
    redBatch.CopyVertexData3f(vBlock2);
    redBatch.End();
    
    
    GLfloat vBlock3[] = { -0.75f, -0.75f, 0.0f,
        -0.25f, -0.75f, 0.0f,
        -0.25f, -0.25f, 0.0f,
        -0.75f, -0.25f, 0.0f};
    
    blueBatch.Begin(GL_TRIANGLE_FAN, 4);
    blueBatch.CopyVertexData3f(vBlock3);
    blueBatch.End();
    
    
    GLfloat vBlock4[] = { 0.25f, -0.75f, 0.0f,
        0.75f, -0.75f, 0.0f,
        0.75f, -0.25f, 0.0f,
        0.25f, -0.25f, 0.0f};
    
    blackBatch.Begin(GL_TRIANGLE_FAN, 4);
    blackBatch.CopyVertexData3f(vBlock4);
    blackBatch.End();
}

void PressSpecialKeys(int key, int x, int y) {
    GLfloat stepSize = 0.025f;
    
    GLfloat blockX = vVerts[0];
    GLfloat blockY = vVerts[7];
    
    if(key == GLUT_KEY_UP)
        blockY += stepSize;
    
    if(key == GLUT_KEY_DOWN)
        blockY -= stepSize;
    
    if(key == GLUT_KEY_LEFT)
        blockX -= stepSize;
    
    if(key == GLUT_KEY_RIGHT)
        blockX += stepSize;
    
    
    if(blockX < -1.0f) blockX = -1.0f;
    if(blockX > (1.0f - blockSize * 2)) blockX = 1.0f - blockSize * 2;;
    if(blockY < -1.0f + blockSize * 2)  blockY = -1.0f + blockSize * 2;
    if(blockY > 1.0f) blockY = 1.0f;
    
    
    vVerts[0] = blockX;
    vVerts[1] = blockY - blockSize*2;
    
    vVerts[3] = blockX + blockSize*2;
    vVerts[4] = blockY - blockSize*2;
    
    vVerts[6] = blockX + blockSize*2;
    vVerts[7] = blockY;
    
    vVerts[9] = blockX;
    vVerts[10] = blockY;
    
    squareBatch.CopyVertexData3f(vVerts);
    
    glutPostRedisplay();
}
void reShapeSize(int w, int h) {
    glViewport(0,0, w, h);
}

void display_render() {
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
    
    //定义4种颜色
    GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 0.5f };
    GLfloat vGreen[] = { 0.0f, 1.0f, 0.0f, 1.0f };
    GLfloat vBlue[] = { 0.0f, 0.0f, 1.0f, 1.0f };
    GLfloat vBlack[] = { 0.0f, 0.0f, 0.0f, 1.0f };
    
    //召唤场景的时候,将4个固定矩形绘制好
    //使用 单位着色器
    //参数1:简单的使用默认笛卡尔坐标系(-1,1),所有片段都应用一种颜色。GLT_SHADER_IDENTITY
    //参数2:着色器颜色
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vGreen);
    greenBatch.Draw();
    
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);
    redBatch.Draw();
    
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vBlue);
    blueBatch.Draw();
    
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vBlack);
    blackBatch.Draw();
    
    
//    //组合核心代码
//    //1.开启混合
//    glEnable(GL_BLEND);
//    //2.开启组合函数 计算混合颜色因子
//    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//    //3.使用着色器管理器
//    //*使用 单位着色器
//    //参数1:简单的使用默认笛卡尔坐标系(-1,1),所有片段都应用一种颜色。GLT_SHADER_IDENTITY
//    //参数2:着色器颜色
//    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);
//    //4.容器类开始绘制
//    squareBatch.Draw();
//    //5.关闭混合功能
//    glDisable(GL_BLEND);
    
    
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vRed);
    squareBatch.Draw();
    glDisable(GL_BLEND);
    
    
    //同步绘制命令
    glutSwapBuffers();
}
int main4(int argc, char* argv[]){
    
    gltSetWorkingDirectory(argv[0]);
    glutInitDisplayMode(GLUT_DEPTH | GLUT_RGBA | GLUT_DOUBLE);
    glutInitWindowSize(600, 800);
    glutInit(&argc, argv);
    glutCreateWindow("混合");
    
    GLenum err = glewInit();
    if (GLEW_OK != err)
    {
        fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
        return 1;
    }
    
    glutDisplayFunc(display_render);
    glutReshapeFunc(reShapeSize);
    
    glutSpecialFunc(PressSpecialKeys);
    SetupRect();
    glutMainLoop();
    return 0;
}

你可能感兴趣的:(图形渲染)