正如上图可以看到,模板测试就是给设置一个区域,让其显示出来,其他的则不会显示出来。
模板测试和深度测试真的很相似。
下面是打开模板测试和是否开启掩码的代码。
glEnable(GL_STENCIL_TEST);//开启模板测试
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);//清理缓冲区
glStencilMask(0xFF); // 每一位写入模板缓冲时都保持原样
glStencilMask(0x00); // 每一位在写入模板缓冲时都会变成0(禁用写入)
鼓励你们测试一下,因为我各种姿势都试过了,发现加不加这些代码,我的肉眼都没办法分辨出差别。可能要后面的来控制吧!
接下来就是两个函数了:
glStencilFunc(GLenum func, GLint ref, GLuint mask)一共包含三个参数:
func:设置模板测试函数(Stencil Test Function)。这个测试函数将会应用到已储存的模板值上和glStencilFunc函数的ref值上。可用的选项有:GL_NEVER、GL_LESS、GL_LEQUAL、GL_GREATER、GL_GEQUAL、GL_EQUAL、GL_NOTEQUAL和GL_ALWAYS。它们的语义和深度缓冲的函数类似。
ref:设置了模板测试的参考值(Reference Value)。模板缓冲的内容将会与这个值进行比较。
mask:设置一个掩码,它将会与参考值和储存的模板值在测试比较它们之前进行与(AND)运算。初始情况下所有位都为1。
glStencilFunc(GL_EQUAL, 0, 0xFF);//对,你没有看错,这里就是等于0,不是等于1,它不是应该等于1吗?
//可是我让他等于1时,通过模板测试,给我的就只是一个背景板,而且不等于,小于返回的也是背景板,
//剩下的我就不尝试了,如果有兴趣的话,你可以自己尝试一下。
但是glStencilFunc仅仅描述了OpenGL应该对模板缓冲内容做什么,而不是我们应该如何更新缓冲。这就需要glStencilOp这个函数了。
glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)一共包含三个选项,我们能够设定每个选项应该采取的行为:
sfail:模板测试失败时采取的行为。
dpfail:模板测试通过,但深度测试失败时采取的行为。
dppass:模板测试和深度测试都通过时采取的行为。
这里我只记录一些我自己尝试的组合
没有设置glStencilFunc(GL_LESS, 1, 0xFF)时:
glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);//1号图
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);//1
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);//1
glStencilOp(GL_INCR, GL_INCR, GL_INCR);//1
glStencilOp(GL_INCR_WRAP, GL_INCR_WRAP, GL_INCR_WRAP);//1
glStencilOp(GL_DECR, GL_DECR, GL_DECR);//1
glStencilOp(GL_DECR_WRAP, GL_DECR_WRAP, GL_DECR_WRAP);//1
glStencilOp(GL_INVERT, GL_INVERT, GL_INVERT);//1
下面是glStencilFunc(GL_LESS, 1, 0xFF);
glStencilOp(GL_DECR_WRAP, GL_DECR_WRAP, GL_DECR_WRAP);//2
glStencilOp(GL_INVERT, GL_INVERT, GL_INVERT);//2
下面是glStencilFunc(GL_LESS, 0, 0xFF);
glStencilOp(GL_DECR_WRAP, GL_DECR_WRAP, GL_DECR_WRAP);//2
glStencilOp(GL_INVERT, GL_INVERT, GL_INVERT);//2
下面是glStencilFunc(GL_EQUAL, 0, 0xFF);
glStencilOp(GL_INVERT, GL_INVERT, GL_INVERT);//3
其余的如果你感兴趣的,可以自己去测试。我就懒得测试了。
然后我们通过模板测试,开始完成策略游戏中的物体描边
新开一个LightFrag.Frag
#version 330 core
out vec4 FragColor;
void main()
{
FragColor=vec4(1.0f,0.0f,0.0f,1.0f);
}
然后实例化两个不同的着色器
Shader* myShader = new Shader("vertexSource.vert", "fragmentSource.frag");
Shader* lightShader = new Shader("vertexSource.vert", "LightFrag.Frag");
加载两张贴图
unsigned int texbufferA;//贴图缓冲区ID
texbufferA=LoadImageToGpu("container2.png",GL_RGBA,GL_RGBA, Shader::DIFFUSE);
unsigned int texbufferB;//贴图缓冲区ID
texbufferB = LoadImageToGpu("container2_specular.png", GL_RGBA, GL_RGBA, 2);
开启模板测试
glEnable(GL_STENCIL_TEST);
然后是循环中
while (!glfwWindowShouldClose(window))
{ // 检查事件,调用相应的回调函数,如下文的glfwInput函数
//Process Input
ProcessInput(window);
//Clear screen
glClearColor(0.3f, 0.3f, 0.3f, 1.0f);//渲染颜色到后台缓冲
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);//清除前台缓冲
viewMat = myCamera->GetViewMatrix();//刷新视角
for (size_t i = 1; i <= 10; i++)
{
glm::mat4 modelMat;//模型矩阵
modelMat = glm::translate(modelMat, cubePositions[i]);
modelMat = glm::rotate(modelMat, glm::radians(i*10.0f), glm::vec3(0, 1.0f, 0));
glEnable(GL_DEPTH_TEST);//开启深度测试
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);//只有模板与深度有一个失败,就保持,都成功了,就换成ref值
glStencilMask(0xFF);//设置模板可以写入
glStencilFunc(GL_ALWAYS, 1, 0xFF);//模板测试总是通过,并将深度测试通过的模板值改为1
//绘制中间的
myShader->Use();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texbufferA);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texbufferB);
glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat));
glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));
glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));
glUniform3f(glGetUniformLocation(myShader->ID, "objColor"), 1.0f, 1.0f, 1.0f);
glUniform3f(glGetUniformLocation(myShader->ID, "ambientColor"), 0.1f, 0.1f, 0.1f);
glUniform3f(glGetUniformLocation(myShader->ID, "CameraPos"), myCamera->Position.x, myCamera->Position.y, myCamera->Position.z);
myShader->SetUniform1i("material.diffuse", Shader::DIFFUSE);
myShader->SetUniform1i("material.specular", Shader::SPECULAR);
myShader->SetUniform1f("material.shininess", 32);
mesh.DrawVertexArray(myShader);
//绘制边框
glStencilFunc(GL_NOTEQUAL,1, 0xFF);//不等于1的模板值可以通过,
glStencilMask(0x00);//不支持写入,似乎可以注释掉,不知道注释之后有什么坏处
glDisable(GL_DEPTH_TEST);//关掉深度测试
lightShader->Use();
modelMat = glm::scale(modelMat, glm::vec3(1.1f, 1.1f, 1.1f));//设置模型为原来代码块的1.1倍
glUniformMatrix4fv(glGetUniformLocation(lightShader->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat));
glUniformMatrix4fv(glGetUniformLocation(lightShader->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));
glUniformMatrix4fv(glGetUniformLocation(lightShader->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));
mesh.DrawVertexArray(lightShader);//绘画
glStencilMask(0xFF);//如果上面的0x00注释掉了,这个也可以注释掉,效果好像没什么区别
glEnable(GL_DEPTH_TEST);
//model.Draw(myShader);
glBindVertexArray(0);
}
其中的DrawVertexArray函数,是我将Mesh中的测试画正方体的部分封装了出来。
效果图: