深度测试
OpenGL-ES 深度测试是指在片段着色器执行之后,利用深度缓冲区所保存的深度值决定当前片段是否被丢弃的过程
深度缓冲区通常和颜色缓冲区有着相同的宽度和高度,一般由窗口系统自动创建并将其深度值存储为 16、 24 或 32 位浮点数。(注意只保存深度值)
当深度测试开启的时候, OpenGL-ES 才会测试深度缓冲区中的深度值;如果此测试通过,深度缓冲内的值可以被设为新的深度值;如果深度测试失败,则丢弃该片段。
深度测试是在片段着色器运行之后(并且在模板测试运行之后)在屏幕空间 (screen space) 中执行的。
与屏幕空间坐标相关的视区是由 OpenGL-ES 的视口设置函数 glViewport 函数给定,并且可以通过片段着色器中内置的 gl_FragCoord 变量访问。
gl_FragCoord 的 X 和 y 表示该片段的屏幕空间坐标 ((0,0) 在左下角),其取值范围由 glViewport 函数决定,屏幕空间坐标原点位于左下角。
gl_FragCoord 还包含一个 z 坐标,它包含了片段的实际深度值,此 z 坐标值是与深度缓冲区的内容进行比较的值。
深度缓冲区中包含深度值介于 0.0 和 1.0 之间,物体接近近平面的时候,深度值接近 0.0 ,物体接近远平面时,深度接近 1.0。
深度测试实现
开启深度测试后,如果片段通过深度测试,OpenGL-ES 自动在深度缓冲区存储片段的 gl_FragCoord.z 值,如果深度测试失败,那么相应地丢弃该片段。
如果启用深度测试,那么需要在渲染之前使用 **glClear(GL_DEPTH_BUFFER_BIT); **清除深度缓冲区,否则深度缓冲区将保留上一次进行深度测试时所写的深度值。
另外在一些场景中,我们需要进行深度测试并相应地丢弃片段,但我们不希望更新深度缓冲区,那么可以设置深度掩码**glDepthMask(GL_FALSE);**实现禁用深度缓冲区的写入(只有在深度测试开启时才有效)。
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
OpenGL-ES 深度测试是通过深度测试函数 glDepthFunc 控制深度测试是否通过和如何更新深度缓冲区。
参数 | 作用说明 |
---|---|
GL_ALWAYS | 永远通过测试 |
GL_NEVER | 永远不通过测试 |
GL_LESS | 片段值小于深度缓冲区通过测试 |
GL_EQUAL | 片段值等于深度缓冲区通过测试 |
GL_LEQUAL | 片段值小于等于深度缓冲区通过测试 |
GL_NOTEQUAL | 片段值不等于等于深度缓冲区通过测试 |
GL_GREATER | 片段值大于深度缓冲区通过测试 |
GL_GEQUAL | 片段值大于等于深度缓冲区通过测试 |
深度测试启用后,默认情况下深度测试函数使用 GL_LESS,这将丢弃深度值高于或等于当前深度缓冲区的值的片段。
代码实现
原理: 绘制了两张图片,并且设置投影矩阵,使其都绕着 Y 轴旋转,注意这两张图片的初始化的 Z 轴坐标是不一致的,所以会出现不同的深度,此时的深度可以理解为 Camera
系统里的景深的概念。
- 如果开启深度测试,近端的画面会遮挡远端,出现正确的深度效果
- 如果不开启,会出现两张图片在抢夺 Z 的现象
static int Init(ESContext *esContext)
{
UserData *userData = esContext->userData;
const char vShaderStr[] =
"#version 300 es \n"
"uniform mat4 u_mvpMatrix; \n"
"layout(location = 0) in vec4 a_position; \n"
"layout(location = 1) in vec2 a_texCoord; \n"
"out vec2 v_texCoord; \n"
"void main() \n"
"{ \n"
" gl_Position = u_mvpMatrix * a_position; \n"
" v_texCoord = a_texCoord; \n"
"} \n";
char fShaderStr[] =
"#version 300 es \n"
"precision mediump float; \n"
"in vec2 v_texCoord; \n"
"layout(location = 0) out vec4 outColor; \n"
"uniform sampler2D s_texture; \n"
"vec4 tempColor; \n"
"void main() \n"
"{ \n"
" tempColor = texture( s_texture, v_texCoord ); \n"
" outColor = vec4(tempColor.r, tempColor.b, tempColor.g, tempColor.a); \n"
"} \n";
userData->programObject = esLoadProgram(vShaderStr, fShaderStr);
userData->samplerLoc = glGetUniformLocation(userData->programObject, "s_texture");
userData->textureIdFront = loadTgaTextures("./Huskey.tga");
userData->textureIdBack = loadTgaTextures("./scene.tga");
userData->mvpLoc = glGetUniformLocation(userData->programObject, "u_mvpMatrix");
userData->angle = 0.0f;
// 启用深度测试
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
return TRUE;
}
static void Draw(ESContext *esContext)
{
UserData *userData = esContext->userData;
GLfloat vertices1[] = {
-0.7f, 0.7f, 0.5f, // 左上
-0.7f, -0.7f, 0.5f, // 左下
0.7f, -0.7f, 0.5f, // 右下
0.7f, 0.7f, 0.5f // 右上
};
GLfloat vertices2[] = {
// 顶点坐标 (x, y, z)
-0.5f, 0.5f, 0.1f,
-0.5f, -0.5f, 0.1f,
0.5f, -0.5f, 0.1f,
0.5f, 0.5f, 0.1f,
};
GLfloat texCoords[] = {
// 纹理坐标 (s, t)
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f,
};
GLushort indices[] = { 0, 1, 2, 0, 2, 3 };
glViewport(0, 0, esContext->width, esContext->height);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(userData->programObject);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), vertices1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), texCoords);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glUniformMatrix4fv(userData->mvpLoc, 1, GL_FALSE, (GLfloat *)&userData->mvpMatrix.m[0][0]);
// Bind the texture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, userData->textureIdFront);
// Set the sampler texture unit to 0
glUniform1i(userData->samplerLoc, 0);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), vertices2);
glEnableVertexAttribArray(0);
glActiveTexture(GL_TEXTURE0);
glUniformMatrix4fv(userData->mvpLoc, 1, GL_FALSE, (GLfloat *)&userData->mvpMatrix.m[0][0]);
glBindTexture(GL_TEXTURE_2D, userData->textureIdBack);
glUniform1i(userData->samplerLoc, 0);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
}
static void Update(ESContext *esContext, float deltaTime)
{
UserData *userData = esContext->userData;
ESMatrix perspective;
ESMatrix orthographic;
ESMatrix modelview;
float aspect;
// Compute a rotation angle based on time to rotate the cube
userData->angle += (deltaTime * 40.0f);
if (userData->angle >= 360.0f)
{
userData->angle -= 360.0f;
}
aspect = (GLfloat)esContext->width / (GLfloat)esContext->height;
// Generate a perspective matrix with a 60 degree FOV
// 如果不需要 Perspective 可以不设置
esMatrixLoadIdentity(&perspective);
esPerspective(&perspective, 45.0f, aspect, 1.0f, 20.0f);
// Generate a model view matrix to rotate/translate the cube
// 沿着 Z 轴负方向平移两个位置
// 沿着 y 轴旋转一定的角度 x y z repsents x y z rotate
esMatrixLoadIdentity(&modelview);
esTranslate(&modelview, 0.0, 0.0, -2.0);
esRotate(&modelview, userData->angle, 0.0, 1.0, 0.0);
esMatrixMultiply(&userData->mvpMatrix, &modelview, &perspective);
}
static void ShutDown(ESContext *esContext)
{
UserData *userData = esContext->userData;
glDeleteTextures(1, &userData->textureIdFront);
glDeleteTextures(1, &userData->textureIdBack);
glDeleteProgram(userData->programObject);
}
实际效果如下:
启动深度测试的效果:
不启用深度测试:
Z-Fight 现象
深度测试中,深度冲突现象需要值得注意。深度冲突(Z-fighting)是指两个平面(或三角形)相互平行且靠近的过于紧密,深度缓冲区不具有足够的精度确定哪一个平面靠前,导致这两个平面的内容不断交替显示,看上去像平面内容争夺顶靠前的位置。
防止深度冲突的方法:
- 不要让物体之间靠得过近,以免它们的三角形面片发生重叠; - 把近平面设置得远一些(越靠近近平面的位置精度越高);
- 牺牲一些性能,使用更高精度的深度值。