跳到主要内容

1 篇博文 含有标签「stencil」

查看所有标签

Stencil用法

· 阅读需 6 分钟
苏明才
Vulkan驱动填坑狮

opengl的stencil称为模板测试,和深度测试一样,是Opengl中常用的技术。概念就不解释了,直接演示用法。我们实现对一个立方体盒子进行高亮描边。

stencil用法

stencil最简单的用法是使用下面三个函数:

  • glStencilMask(GLuint value): 位遮罩, 0x00设置模板缓冲不可写入;value=0xFF设置可以写入。
  • glStencilFunc(GLenum func, GLint ref, GLuint mask): 用ref值与存储的模板值比较,func为比较方法。
  • glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass): 模板值的写入策略

简单来说,glStencilMask是开关,开了才可以写入模板值;glStencilFunc是写入模板值的前提条件;glStencilOp是模板值的具体写入方法。

示例代码

普通流程

我们先准备好了画一个立方体盒子的代码:

GLuint boxProgramID;

void display( GLFWwindow* window )
{
glClearColor( 0, 0, 0.4, 0 );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// 设置好坐标变化的mvp
glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float) 5 / (float)5, 0.1f, 100.0f);
glm::mat4 view = glm::lookAt(glm::vec3(1,2,3), glm::vec3(0,0,0), glm::vec3(0,1,0));
glm::mat4 model = glm::mat4(1.0f);
glm::mat4 mvp = projection * view * model;

// 画立方体
glUseProgram(boxProgramID);
GLuint mvpId = glGetUniformLocation(boxProgramID, "MVP");
glUniformMatrix4fv(mvpId, 1, GL_FALSE, &mvp[0][0]);
glDrawArrays(GL_TRIANGLES, 0, 36);
}

void prepare() {
boxProgramID = LoadShaders( "data/shader/03.MVP.vs", "data/shader/03.MVP.fs" );

// 立方体的顶点坐标,纹理坐标
const GLfloat g_vertex_buffer_data[] = {
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,

-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,

-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,

0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,

-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,

-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};

// VAO、VBO的数据
GLuint VAO1;
GLuint VBO1;
glGenVertexArrays(1, &VAO1);
glBindVertexArray(VAO1);
glGenBuffers(1, &VBO1);
glBindBuffer(GL_ARRAY_BUFFER, VBO1);
glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (void*)0);

// 纹理数据
GLuint texture;
glGenTextures(1, &texture);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);

// 环绕方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
// 过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// 加载图片数据
int width, height, channels;
stbi_set_flip_vertically_on_load(true);
unsigned char* data = stbi_load("data/img/test.jpg", &width, &height, &channels, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
stbi_image_free(data);
// 纹理坐标
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (void*)(3 * sizeof(GLfloat)));

glEnable(GL_DEPTH_TEST);
}

int main( int argc, char** argv )
{
glfwInit();
GLFWwindow* window = glfwCreateWindow( 512, 512, "OpenGL_Tutorial", NULL, NULL );
glfwMakeContextCurrent( window );
gladLoadGLLoader( (GLADloadproc)glfwGetProcAddress );
glfwSwapInterval( 1 );

glfwSetKeyCallback( window, keyboard );
glfwSetCursorPosCallback( window, motion );
glfwSetMouseButtonCallback( window, mouse );

prepare();

while (!glfwWindowShouldClose(window)) {
display(window);
glfwPollEvents();
glfwSwapBuffers(window);
}

glfwTerminate();
return 0;
}

显示效果如图:

1639578757898

使用模板测试功能

加入模板测试功能,流程描述:

  1. 开启模板测试
  2. 清除所有模板值
  3. 画一个立方体盒子,将盒子区域的模板值设置可写,值为1
  4. 画一个稍大点的盒子(盒子放大1.05倍),不贴图,fs里直接设置颜色固定值,进行模板测试,模板测试条件为模板值 != 1,设置模板值不可写。

具体代码:

void display( GLFWwindow* window )
{
// 1.开启stencil功能
glEnable(GL_STENCIL_TEST);
// 设置模板值可写,下面的clear GL_STENCIL_BUFFER_BIT操作才能执行
glStencilMask(0xFF);
// 设置模板测试成功的写入方式为替换,即测试成功,使用glStencilFunc中的ref值写入模板值
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glClearColor( 0, 0, 0.4, 0 );
// 2.清除深度、模板值
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

// 3.画立方体
glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float) 5 / (float)5, 0.1f, 100.0f);
glm::mat4 view = glm::lookAt(glm::vec3(1,2,3), glm::vec3(0,0,0), glm::vec3(0,1,0));
glm::mat4 model = glm::mat4(1.0f);
glm::mat4 mvp = projection * view * model;

// 设置模板值的写入策略,GL_ALWAYS表示无条件写入
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glUseProgram(boxProgramID);
GLuint mvpId = glGetUniformLocation(boxProgramID, "MVP");
glUniformMatrix4fv(mvpId, 1, GL_FALSE, &mvp[0][0]);
glDrawArrays(GL_TRIANGLES, 0, 36);

// 4.画描边
model = glm::mat4(1.0f);
// 放大盒子为原来的1.05倍
model = glm::scale(model, glm::vec3(1.05, 1.05, 1.05));
mvp = projection * view * model;

// 进行模板测试,只有不等于1的模板值才测试成功
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
// 设置模板值不可写,此处无需更新模板值
glStencilMask(0x00);
glUseProgram(stencilProgramID);
mvpId = glGetUniformLocation(stencilProgramID, "MVP");
glUniformMatrix4fv(mvpId, 1, GL_FALSE, &mvp[0][0]);
glDrawArrays(GL_TRIANGLES, 0, 36);

}

现在的效果:

1639579682967