1、深度测试
深度测试,根据片段的深度值来判断片段能否被渲染,如果一个物体有遮挡,那么我们只希望绘制我们可以看得到的片段,离我们比较远被遮挡的片段是不希望被绘制出来的。
在屏幕空间中,物体表面上的任意点的坐标 z 值代表一个深度值,深度值的范围是 [0, 1],其中值为 0 代表在近平面上,为 1 代表在远平面上,也就是说深度值越小则离观察者是越近的。
深度测试发生在模板测试之后,当深度测试被启用的时候,就会将一个片段的深度值与对应位置的深度缓冲值比较,如果这个深度测试失败了,片段也就将会被丢弃。
深度测试默认是被禁用的,可以使用 GL_DEPTH_TEST 来启用它。
glEnable(GL_DEPTH_TEST);
默认情况下,当启用深度测试时,一个片段如果通过了深度测试,也会更新对应的深度缓冲值,但是是否更新深度缓冲值也是可以配置的,可以通过 glDepthMask 进行配置。
glDepthMask(GL_FALSE);
GL_FALSE 表示禁止写入,GL_TRUE 表示可以写入,默认情况下深度缓冲区是可以写入的。
如果我们创建了一块深度缓冲区,但是没有设置默认值,那么它的值可能就会是随机的(一般启用了深度测试,会自动将这个值设置为 1.0),所以我们在用到深度测试的时候,可以使用 glClearDepthf 设置深度缓冲默认值。
glClearDepthf(1.0);
glClera(GL_DEPTH_BUFFER_BIT);
深度测试函数
当启用了深度测试时,我们通过 glDepthFunc 深度测试函数来设置深度比较条件,让当前深度值和深度缓冲区对应位置深度值进行比较,来判断是否应该丢弃该片段。
void glDepthFunc(GLenum func);
深度测试函数接受下面表格中的比较运算符:
参数 | 含义 |
GL_ALWAYS | 永远通过深度测试 |
GL_NEVER | 永远不通过深度测试 |
GL_LESS | 在片段深度值小于缓冲的深度值时通过测试 |
GL_EQUAL | 在片段深度值等于缓冲区的深度值时通过测试 |
GL_LEQUAL | 在片段深度值小于等于缓冲区的深度值时通过测试 |
GL_GREATER | 在片段深度值大于缓冲区的深度值时通过测试 |
GL_NOTEQUAL | 在片段深度值不等于缓冲区的深度值时通过测试 |
GL_GEQUAL | 在片段深度值大于等于缓冲区的深度值时通过测试 |
默认情况下使用的深度函数是 GL_LESS,它将会丢弃深度值大于等于当前深度缓冲值的所有片段。
那么一段深度测试的代码就可以写成下面这样
struct PixelAttri {
uint16_t x{};
uint16_t y{};
uint32_t z{};
uint8_t r{};
uint8_t g{};
uint8_t b{};
uint8_t a{};
bool front_face{};
};
bool DepthTest(const PixelAttri& pixel) {
bool depth_pass = true;
uint32_t buffer_z = FrameBuffer::GetInstance().GetDepth(pixel.x, pixel.y);
switch(depth_config_.func) {
case GL_NEVER:
depth_pass = false;
break;
case GL_LESS:
depth_pass = (pixel.z < buffer_z);
break;
case GL_EQUAL:
depth_pass = (pixel.z == buffer_z);
break;
case GL_LEQUAL:
depth_pass = (pixel.z <= buffer_z);
break;
case GL_GREATER:
depth_pass = (pixel.z > buffer_z);
break;
case GL_NOTEQUAL:
depth_pass = (pixel.z != buffer_z);
break;
case GL_GEQUAL:
depth_pass = (pixel.z >= buffer_z);
break;
case GL_ALWAYS:
depth_pass = true;
break;
default:
break;
}
return depth_pass;
}
int main(void) {
bool depth_pass = DepthTest(pixel);
if (depth_pass && depth_mask) {
FrameBuffer::GetInstance().SetDepth(pixel.x, pixel.y, pixel.z);
}
return 0;
}
一般情况下,深度缓冲区都是 24 位的,所以这里用了 uint32_t 表示,在进入深度测试之前都会将深度值从浮点转成整型的,比如 1.0 会转成 2^24 - 1
2、模板测试
当片段着色器处理完一个片段后,模板测试就会开始执行,和深度测试一样,它也会丢弃片段。接下来,被保留的片段会进入深度测试。模板测试也是根据一个缓冲来进行的,它叫做模板缓冲。一般模板缓冲和深度缓冲共用一个(其中模板值占 8 位,深度值占 24 位)。
一个模板缓冲中,通常每个模板值都是 8 位的,所以每一个像素一共能有 256 种不同的模板值。我们可以将这些模板值设置为我们想要的值,然后当某一个片段有模板值的时候,我们既可以选择丢弃或者是保留这个片段了,一个模板缓冲简单例子就像下面这样:
模板缓冲首先会被清除为0,之后在模板缓冲中使用1填充了一个空心矩形。场景中的片段将会只在片段的模板值为 1 的时候会被渲染(其它的都被丢弃了)。
模板缓冲操作允许我们在渲染片段时将模板缓冲设定为一个特定的值。通过在渲染时修改模板缓冲的内容,我们写入了模板缓冲。在同一个(或者接下来的)渲染迭代中,我们可以读取这些值,来决定丢弃还是保留某个片段。使用模板测试的步骤一般如下:
1. 启用模板缓冲写入。
2. 渲染物体,更新模板缓冲的内容。
3. 禁用模板缓冲的写入。
4. 渲染其它物体,这次会根据模板缓冲的内容丢弃特定判断。
默认情况下模板测试也是关闭的,我们可以通过启用GL_STENCIL_TEST来开启模板测试
glEnable(GL_STENCIL_TEST);
和深度缓冲区一样,我们需要在每次绘制之前清除模板缓冲区
glClearStencil(0);
glClera(GL_STENCIL_BUFFER_BIT);
和深度测试的 glDepthMask 函数一样,模板缓冲也有一个类似的函数。glStencilMask 允许我们设置一个位掩码(Bitmask),它会与将要写入缓冲的模板值进行与(AND)运算。默认情况下设置的位掩码所有位都为 1,不影响输出,但如果我们将它设置为 0x00,写入缓冲的所有模板值最后都会变成0。这与深度测试中的glDepthMask(GL_FALSE)是等价的。
glStencilMask(0xFF); // 每一位写入模板缓冲时都保持原样
glStencilMask(0x00); // 每一位在写入模板缓冲时都会变成0(禁用写入)
大部分情况下都是使用 0x00 或者 0xff 作为掩码值。
和深度测试一样,我们对模板缓冲应该通过还是失败,以及它应该如何影响模板缓冲,也是有一定控制的。一共有两个函数能够用来配置模板测试,分别是 glStencilFunc 和 glStencilOp。
模板测试控制函数
可以通过 glStencilFunc 来设置模板测试的判断条件
void glStencilFunc(GLenum func, GLint ref, GLuint mask);
func:甚至模板测试函数比较值,取值和深度缓冲比较值一样。
ref:设置模板测试的参考值。模板缓冲的内容将会与这个值进行比较。
mask:设置一个掩码,它将会与参考值和储存的模板值在测试比较之前进行与(AND)运算。初始情况下都为 1。
模板行为设置函数
可以通过 glStencilOp 来设置模板测试通过/失败后的一些行为,来控制怎么更新模板缓冲区
void glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass);
sfail:模板测试失败采取的行为。
dpfail:模板测试通过,但深度测试失败采取的行为。
dppass:模板测试和深度测试都通过时采取的行为。
每个选项都可以选用以下的一种行为:
行为 | 描述 |
GL_KEEP | 保持当前储存的模板值 |
GL_ZERO | 将模板值设置为 0 |
GL_REPLACE | 将模板值设置为 glStencilFunc 函数设置的 ref 值 |
GL_INCR | 如果模板值小于最大值则将模板值加 1 |
GL_INCR_WRAP | 与GL_INCR一样,但如果模板值超过了最大值则归零 |
GL_DECR | 如果模板值大于最小值则将模板值减 1 |
GL_DECR_WRAP | 与GL_DECR一样,但如果模板值小于 0 则将其设置为最大值 |
GL_INVERT | 按位翻转当前的模板缓冲值 |
默认情况下 glStencilOp 的设置为
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
模板测试代码实现
// 模板测试
bool StencilTest(const PixelAttri& pixel) {
bool stencil_pass = true;
int stencil_func = pixel.front_face ? stencil_config_.front_stencil_func : stencil_config_.back_stencil_func;;
int stencil_func_ref = pixel.front_face ? stencil_config_.front_stencil_func_ref : stencil_config_.back_stencil_func_ref;
int stencil_func_mask = pixel.front_face ? stencil_config_.front_stencil_func_mask : stencil_config_.back_stencil_func_mask;
int stencil = static_cast<int>(FrameBuffer::GetInstance().GetStencil(pixel.x, pixel.y));
stencil_func_ref = stencil_func_ref & stencil_func_mask;
stencil = stencil & stencil_func_mask;
switch (stencil_func) {
case GL_NEVER:
stencil_pass = false;
break;
case GL_LESS:
stencil_pass = stencil_func_ref < stencil;
break;
case GL_LEQUAL:
stencil_pass = stencil_func_ref <= stencil;
break;
case GL_GREATER:
stencil_pass = stencil_func_ref > stencil;
break;
case GL_GEQUAL:
stencil_pass = stencil_func_ref >= stencil;
break;
case GL_EQUAL:
stencil_pass = stencil_func_ref == stencil;
break;
case GL_NOTEQUAL:
stencil_pass = stencil_func_ref != stencil;
break;
case GL_ALWAYS:
stencil_pass = true;
break;
default:
break;
}
return stencil_pass;
}
// 更新模板缓冲区
void UpdateStencilBuffer(GLenum op_code, const PixelAttri& pixel) {
uint8_t stencil = FrameBuffer::GetInstance().GetStencil(pixel.x, pixel.y);
switch (op_code) {
case GL_KEEP:
break;
case GL_ZERO:
FrameBuffer::GetInstance().SetStencil(pixel.x, pixel.y, 0);
break;
case GL_REPLACE: {
int ref = pixel.front_face ? stencil_config_.front_stencil_func_ref : stencil_config_.back_stencil_func_ref;
FrameBuffer::GetInstance().SetStencil(pixel.x, pixel.y, ref);
break;
}
case GL_INCR: {
if (stencil < 0xff) {
stencil = stencil + 1;
}
FrameBuffer::GetInstance().SetStencil(pixel.x, pixel.y, stencil);
break;
}
case GL_INCR_WRAP: {
if (stencil < 0xff) {
stencil = stencil + 1;
} else {
stencil = 0;
}
FrameBuffer::GetInstance().SetStencil(pixel.x, pixel.y, stencil);
break;
}
case GL_DECR: {
if (stencil > 0) {
stencil = stencil - 1;
}
FrameBuffer::GetInstance().SetStencil(pixel.x, pixel.y, stencil);
break;
}
case GL_DECR_WRAP: {
if (stencil > 0) {
stencil = stencil - 1;
} else {
stencil = 0xff;
}
FrameBuffer::GetInstance().SetStencil(pixel.x, pixel.y, stencil);
break;
}
case GL_INVERT: {
stencil = (uint8_t)~stencil;
FrameBuffer::GetInstance().SetStencil(pixel.x, pixel.y, stencil);
break;
}
default:
break;
}
}
3、混合
混合通常是实现物体透明度的一种技术,透明度就是说一个物体不是纯色的,它的颜色是物体本身的颜色和它背后其它物体的颜色不同强度的结合。透明的物体可以是完全透明的(让所有的颜色穿过),或者是半透明的(它让颜色通过,同时也会显示自身的颜色)。一个物体的透明度是通过它颜色的 alpha 值来决定的。Alpha 颜色值是颜色向量的第四个分量。
当 alpha 值为 1.0 表示是不透明的,当 alpha 值为 0.0 表示是完全透明的,而当 alpha 值为 0.5 时。物体的颜色有 50% 是来自物体自身的颜色,50% 来自物体背后的颜色。
默认情况下,混合也是关闭的,我们可以通过 GL_BLEND 来启用
glEnable(GL_BLEND);
那么我们应该怎么混合呢,我们可以通过 glBlendEquation 来设置我们的混合方程
void glBlendEquation(GLenum mode);
参数 mode 指定了源颜色和目标颜色的组合方式,必须是 GL_FUNC_ADD、GL_FUNC_SUBTRACT 或GL_FUNC_REVERSE_SUBTRACT。
参数 | 计算方式 | 说明 |
GL_FUNC_ADD | result = src_color * src_factor + dst_color * dst_factor | src_color:源颜色向量 src_factor:源因子值 dst_color :目标颜色向量 dst_factor:目标因子值 |
GL_FUNC_SUBTRACT | result = src_color * src_factor - dst_color * dst_factor | |
GL_FUNC_REVERSE_SUBTRACT | result = dst_color * dst_factor - src_color * src_factor |
混合方程计算过程中需要用到源因子值和目标因子值,我们可以通过 glBlendFunc 来设置这两个因子值
glBlendFunc(GLenum sfactor, GLenum dfactor)
sfactor 和 dfactor 可以设置的参数如下
选项 | 值 |
GL_ZERO | 因子等于 0 |
GL_ONE | 因子等于 1 |
GL_SRC_COLOR | 因子等于源颜色向量 src_color |
GL_ONE_MINUS_SRC_COLOR | 因子等于 1 - src_color |
GL_DST_COLOR | 因子等于目标颜色向量 dst_color |
GL_ONE_MINUS_DST_COLOR | 因子等于 1 - dst_color |
GL_SRC_ALPHA | 因子等于源颜色向量 alpha 分量 |
GL_ONE_MINUS_SRC_ALPHA | 因子等于 1 - 源颜色向量 alpha 分量 |
GL_DST_ALPHA | 因子等于目标颜色向量 alpha 分量 |
GL_ONE_MINUS_DST_ALPHA | 因子等于 1 - 目标颜色向量 alpha 分量 |
GL_CONSTANT_COLOR | 因子等于常数颜色向量 |
GL_ONE_MINUS_CONSTANT_COLOR | 因子等于 1 - 常数颜色向量 |
GL_CONSTANT_ALPHA | 因子等于常数颜色向量 alpha 分量 |
GL_ONE_MINUS_CONSTANT_ALPHA | 因子等于 1 - 常数颜色向量 alpha 分量 |
上面提到了常数颜色向量,这个常数颜色向量是通过 glBlendColor 来设置的
void glBlendColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);
当我们设置混合方程和混合因子值得时候,可以将 alpha 和 rgb 设置得不一样,它们是通过下面两个函数实现
void glBlendEquationSeparate(GLenum modeRGB, GLenum modeAlpha);
void glBlendFuncSeparate(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha);
混合的代码实现为
Pixel DoBlend(const PixelAttri& pixel) {
uint32_t buffer_pixel = FrameBuffer::GetInstance().GetPixel(pixel.x, pixel.y);
Pixel src;
src.r = pixel.r;
src.g = pixel.g;
src.b = pixel.b;
src.a = pixel.a;
Pixel dst;
dst.r = buffer_pixel & 0xff;
dst.g = (buffer_pixel & 0xff00) >> 8;
dst.b = (buffer_pixel & 0xff0000) >> 16;
dst.a = (buffer_pixel & 0xff000000) >> 24;
std::vector<float> src_rgb_factor = CalcBlendFactorRGB(blend_config_.blend_func_src_rgb, src, dst);
float src_alpha_factor = CalcBlendFactorAlpha(blend_config_.blend_func_src_alpha, src, dst);
std::vector<float> dst_rgb_factor = CalcBlendFactorRGB(blend_config_.blend_func_dst_rgb, src, dst);
float dst_alpha_factor = CalcBlendFactorAlpha(blend_config_.blend_func_dst_alpha, src, dst);
Pixel result;
result.r = CalcBlendEquation(blend_config_.blend_equation_rgb, src.r, src_rgb_factor[0], dst.r, dst_rgb_factor[0]);
result.g = CalcBlendEquation(blend_config_.blend_equation_rgb, src.g, src_rgb_factor[1], dst.g, dst_rgb_factor[1]);
result.b = CalcBlendEquation(blend_config_.blend_equation_rgb, src.b, src_rgb_factor[2], dst.b, dst_rgb_factor[2]);
result.a = CalcBlendEquation(blend_config_.blend_equation_alpha, src.a, src_alpha_factor, dst.a, dst_alpha_factor);
return result;
}
std::vector<float> CalcBlendFactorRGB(int func, const Pixel& src, const Pixel& dst) {
std::vector<float> result(3, 1.0f);
switch (func) {
case GL_ZERO:
result[0] = 0.0f;
result[1] = 0.0f;
result[2] = 0.0f;
break;
case GL_ONE:
result[0] = 1.0f;
result[1] = 1.0f;
result[2] = 1.0f;
break;
case GL_SRC_COLOR:
result[0] = src.r * 1.0f / 255;
result[1] = src.g * 1.0f / 255;
result[2] = src.g * 1.0f / 255;
break;
case GL_ONE_MINUS_SRC_COLOR:
result[0] = 1 - src.r * 1.0f / 255;
result[1] = 1 - src.g * 1.0f / 255;
result[2] = 1 - src.g * 1.0f / 255;
break;
case GL_DST_COLOR:
result[0] = dst.r * 1.0f / 255;
result[1] = dst.g * 1.0f / 255;
result[2] = dst.g * 1.0f / 255;
break;
case GL_ONE_MINUS_DST_COLOR:
result[0] = 1 - dst.r * 1.0f / 255;
result[1] = 1 - dst.g * 1.0f / 255;
result[2] = 1 - dst.g * 1.0f / 255;
break;
case GL_SRC_ALPHA:
result[0] = src.a * 1.0 / 255;
result[1] = src.a * 1.0 / 255;
result[2] = src.a * 1.0 / 255;
break;
case GL_ONE_MINUS_SRC_ALPHA:
result[0] = 1 - src.a * 1.0 / 255;
result[1] = 1 - src.a * 1.0 / 255;
result[2] = 1 - src.a * 1.0 / 255;
break;
case GL_DST_ALPHA:
result[0] = dst.a * 1.0 / 255;
result[1] = dst.a * 1.0 / 255;
result[2] = dst.a * 1.0 / 255;
break;
case GL_ONE_MINUS_DST_ALPHA:
result[0] = 1 - dst.a * 1.0 / 255;
result[1] = 1 - dst.a * 1.0 / 255;
result[2] = 1 - dst.a * 1.0 / 255;
break;
case GL_CONSTANT_COLOR:
result[0] = blend_config_.blend_color.r;
result[1] = blend_config_.blend_color.g;
result[2] = blend_config_.blend_color.b;
break;
case GL_ONE_MINUS_CONSTANT_COLOR:
result[0] = 1 - blend_config_.blend_color.r;
result[1] = 1 - blend_config_.blend_color.g;
result[2] = 1 - blend_config_.blend_color.b;
break;
case GL_CONSTANT_ALPHA:
result[0] = blend_config_.blend_color.a;
result[1] = blend_config_.blend_color.a;
result[2] = blend_config_.blend_color.a;
break;
case GL_ONE_MINUS_CONSTANT_ALPHA:
result[0] = 1 - blend_config_.blend_color.a;
result[1] = 1 - blend_config_.blend_color.a;
result[2] = 1 - blend_config_.blend_color.a;
break;
default:
break;
}
return result;
}
float CalcBlendFactorAlpha(int func, const Pixel& src, const Pixel& dst) {
float factor = 0.0f;
switch (func) {
case GL_ZERO:
factor = 0.0f;
break;
case GL_ONE:
factor = 1.0f;
break;
case GL_SRC_COLOR:
break;
case GL_ONE_MINUS_SRC_COLOR:
break;
case GL_DST_COLOR:
break;
case GL_ONE_MINUS_DST_COLOR:
break;
case GL_SRC_ALPHA:
factor = src.a * 1.0 / 255;
break;
case GL_ONE_MINUS_SRC_ALPHA:
factor = 1 - (src.a * 1.0 / 255);
break;
case GL_DST_ALPHA:
factor = dst.a * 1.0 / 255;
break;
case GL_ONE_MINUS_DST_ALPHA:
factor = 1 - (dst.a * 1.0 / 255);
break;
case GL_CONSTANT_COLOR:
break;
case GL_ONE_MINUS_CONSTANT_COLOR:
break;
case GL_CONSTANT_ALPHA:
factor = blend_config_.blend_color.a;
break;
case GL_ONE_MINUS_CONSTANT_ALPHA:
factor = 1 - blend_config_.blend_color.a;
break;
default:
break;
}
return factor;
}
uint8_t CalcBlendEquation(int func, uint8_t src, float src_factor, uint8_t dst, float dst_factor) {
uint8_t result = 0;
switch (func) {
case GL_FUNC_ADD:
result = src * src_factor + dst * dst_factor;
break;
case GL_FUNC_SUBTRACT:
result = src * src_factor - dst * dst_factor;
break;
case GL_FUNC_REVERSE_SUBTRACT:
result = dst * dst_factor - src * src_factor;
break;
default:
break;
}
return result;
}