引言
本文主要对于一些缓冲,混合和测试进行分析和处理。
深度测试
深度测试是用来保证物体的先后顺序,深度值小的值会被渲染到前面,深度值比较大的值会被渲染到后面,所以深度测试对于3D渲染是非常重要的,在深度测试中,深度测试的值是属于[0 1]的,这个值越小,就会距离屏幕近,越大就会距离屏幕越远。
在opengl中,初始值是1,距离最远,这里是一些关于深度测试的常用的函数。
js
glEnable(GL_DEPTH_TEST); //开启深度测试
glClear( GL_DEPTH_BUFFER_BIT); //清楚深度缓冲
glDepthMask(GL_FALSE); //禁用深度写入
glDepthFunc(GL_LESS); //深度测试函数,小于的时候通过
在计算的过程中,需要计算出每一个点的深度值,一般情况有线性插值和非线性插值:
在一般时候,是不会使用线性插值,是使用第二种插值。设在相机下的空间的坐标是(x,y,z,w),设在投影变化后的空间的坐标是(x',y',z',w').由于在变化的过程中,深度值不再是线性的关系,对Z进行了一个压缩,如果想要得到真实的深度,只能先通过屏幕的深度值进行一个"逆NDC变换"和一个逆投影变化。
先给出正投影的变化矩阵:
设在NDC空间下的空间坐标是(x_NDC,y_NDC,z_NDC,w_NDC),而屏幕空间下的深度为depth,则有:
通过这个可以反推:
这样z、z_NDC和depth之间的关系我们就捋清楚了,z就是真实线性深度,depth是屏幕空间的非线性深度,之间做个转换即可:
js
#version 330 core
out vec4 FragColor;
float near = 0.1;
float far = 100.0;
float LinearizeDepth(float depth)
{
float z = depth * 2.0 - 1.0;
return (2.0 * near * far) / (far + near - z * (far - near));
}
void main()
{
float depth = LinearizeDepth(gl_FragCoord.z) / far;
FragColor = vec4(vec3(depth), 1.0);
}
关于深度冲突一般有三种方法来进行处理,首先是不要把物体放置的过于的近,然后是近平面远一点,或者使用更高精度的缓冲。
模版测试
模板测试和深度测试还是很相似,一般被用来控制是否渲染,不同的是,模板的初始的值为0.
js
glEnable(GL_STENCIL_TEST); //开启模板测试
glClear(GL_STENCIL_BUFFER_BIT); //清楚模板缓冲
glStencilMask(0xFF); // 每一位写入模板缓冲时都保持原样(正常写入)
glStencilMask(0x00); // 每一位在写入模板缓冲时都会变成0(禁用写入)
glStencilFunc(GL_EQUAL, 1, 0xFF) //当前的模板值和oxff与运算,不等于1时通过模板测试
glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass) //模板失败 模板成功深度失败 都成功
一般这个可以被用来检测轮廓算法
js
//轮廓算法
glEnable(GL_DEPTH_TEST); //开启深度测试 初始值为1,为最远的距离
glDepthFunc(GL_LESS); //小于深度值通过
glEnable(GL_STENCIL_TEST); //开启模板测试
glStencilFunc(GL_NOTEQUAL, 1, 0xff); //通过的条件
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); //检测控制,当通过的时候模板值切换为1
while (!glfwWindowShouldClose(window))
{
glClearColor(0.1f,0.1f,0.1f,1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); //清楚颜色,深度,模板缓冲
glStencilMask(0x00); //禁用模板写入
shader.use(); //普通着色器
glDrawArrays(GL_TRIANGLES, 0, 6); //渲染其他物体(不需要轮廓)
//准备渲染有轮廓的物体
glStencilFunc(GL_ALWAYS, 1, 0xFF); //设置模板函数,所有的值全部通过
glStencilMask(0xFF); //开启模板写入,写入模板值为1(ref)
glDrawArrays(GL_TRIANGLES, 0, 36); //渲染需要轮廓的物体,(可以切换该物体的着色器)
glStencilFunc(GL_NOTEQUAL, 1, 0xFF); //更改模板函数,不等于的1模板值通过
glStencilMask(0x00); //禁用模板写入
glDisable(GL_DEPTH_TEST); //关闭深度着色器
shaderSingleColor.use(); //轮廓着色器
float scale = 1.1f; //实现顶点外扩
glDrawArrays(GL_TRIANGLES, 0, 36); //渲染边界
glStencilMask(0xFF); //开启模板测试
glStencilFunc(GL_ALWAYS, 0, 0xFF); //设置模板函数
glEnable(GL_DEPTH_TEST); //关闭深度测试
}
主要是渲染了原来的大小,通过顶点外扩变为原来的1.1倍,多了0.1,占用到了模板值为0的区域,通过设置模板函数只渲染模板值为0的地方就实现了轮廓。
混合
混合和透明度息息相关,是用来实现透明度的一种方法,值为a,当a为0的时候,完全透明,为1的时候,完全不透明。
关于混合的相关的函数
js
glEnable(GL_BLEND); //开启混合
glBlendFunc(GLenum sfactor, GLenum dfactor); //混合函数
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO); //设置不同的选项
关于半透明纹理的渲染,先绘制所有的不透明的物体,然后对于所有的透明的物体的进行排序,由远到近绘制所有的物体。