Scissor Regions与Stencil Buffer

Number of views 139

在某些情况下,我们可能想要只渲染屏幕的一部分,屏蔽掉当前颜色缓冲区的其他部分。在OpenGL中有两种方法可以做到这一点,Scissor Regions和Stencil Buffer。本文将解释如何在OpenGL应用程序中使用这两种方法。

发生在片元着色器后的渲染管线执行操作按照顺序通常为:

$$
Alpha test → Stencil Test → \
Depth Test → Color Blending → FBO
$$

抛开Pixelownershiptest,ScissorTest与StencilTest在流程前方,这很容易想象,好不容易通过了深度测试,结果被裁切了,任谁都是不同意的。而AlphaTest在Stencil Test前方,也不难理解,因为有时需要对特定的Alpha值进行模板测试,需要注意的是AlphaTest开启后会影响earlyZ的启用,开启AlphaTest在Opengl中是通过glEnable(GL_ALPHA_TEST)并配合glAlphaFunc(GL_GREATER, 0.5)进行操作,这与在片元中直接通过类似判断if(gl_FragColor.a < 0.5) discard是等价的。ColorBlend在输出FBO前也是能够理解的,因为只有片元的所有测试都通过了,此时进行颜色混合才是合理的。

Scissor Regions

在渲染时,Scissor Regions与Stencil Buffer都可用来屏蔽BackBuffer的部分区域,其中比较简单的是Scissor Regions(裁剪区域)。Scissor Regions是缓冲区的一个矩形区域,在这个区域内,渲染过程通常会把Scissor Regions以外的内容忽略(丢弃)。只保留Scissor Regions区域内的内容并把该区域内容写入缓冲区,因此要想在Scissor Regions区域外写入颜色或深度缓冲区的内容只能修改或增加Scissor Regions的位置与大小。

与其他OpenGL的状态启用类似,当使用glEnable启用特定状态进行渲染时,使用GL_SCISSOR__TEST符号常量对Scissor Regions进行裁剪测试。一次只能有一个裁剪区域,裁剪区域所覆盖的区域是由OpenGL函数glScissor设置的。它需要设置4个参数:裁剪区域x和y的起始位置坐标,以及该区域的尺寸大小(宽高)。与glViewport一样,这些参数是在屏幕空间中,因此是以像素为单位度量的。值得注意的是,裁剪测试也会影响清屏的过程(glClear):如果启用了裁剪测试,那么它只会清除裁剪区域内的内容。

Stencil Buffer

另一种屏蔽BackBuffer特定区域的方法是通过StencilBuffer(模板缓冲),它与深度或颜色缓冲区一样,也是缓冲区的一种,其大小就是屏幕大小。StencilBuffer与Scissor Regions(裁剪区域)通过设定矩形区域的方式不同,StencilBuffer可以通过绘制几何图形进行模板测试,如:可以在模板缓冲区中绘制一个圆,这样就只渲染圆中的片元,这对于第一人称射击类的游戏来讲是很基础的功能(如狙击镜效果等)。

在模板缓冲中,通常每个模板值(Stencil Value)是8位的(即1字节,相当于uint8_t类型大小(0~255))。所以每个片元一共有256种不同的模板值。所以模板值是可改变的,改变的方式可以通过递增、递减、覆盖或对现有模板值执行布尔运算进行,与深度缓冲一样,我们可以对包含模板值的片元决定是否绘制还是对其丢弃,也可以叠加多个对象到同一个模板缓冲中,并进行后续操作。

可以使用GL_STENCIL_TEST符号常量来启用对模板缓冲的测试和写入,并使用glStencilFunc和glStencilOp这两个OpenGL函数来做进一步控制。其中glStencilFunc控制如何对模板缓冲进行测试,它有三个参数: func、ref和mask。模板测试的工作原理与深度测试相同——同样可以检查现有值是否大于、小于或等于某个值,以及通过func参数设置的其它比较方式(GL_NEVER,GL_LESS,GL_LEQUAL,GL_EQUAL,GL_GREATER,GL_NOTEQUAL,GL_GEQUAL,GL_ALWAYS)。深度测试使用的值是相机空间的z坐标,但模板测试使用的是ref参数提供的值。将(ref参数值 & mask和 已在模板缓冲中的ref值 & mask)进行比较。这允许我们对多个并发测试使用一个模板缓冲。下面是一些使用glStencilFunc的例子。

glStencilFunc(GL_ALWAYS, 1, 0):如果我们启用模板缓冲,并使用该模板函数来确定哪些内容进入模板缓冲,模板测试将始终通过(由于GL_ALWAYS操作)。

glStencilFunc(GL_GREATER, 1, ~0):在这种情况下,ref值(本例中为1)和模板缓冲中的现有ref值将同时与掩码值~0并操作,该掩码值将执行按位NOT操作。这将把值0反转为1,意味着掩码将保持ref值不受影响(1与1并操作结果为1)。func参数值为GL_GREATER将只允许值大于现有的模板值(也就是(ref参数值 & mask > 已在模板缓冲中的ref值 & mask))才让其测试通过,所以在这种情况下,如果现有模板ref值为0,该模板测试函数就会测试通过(1大于0 ),否则模板函数就会失败。

glStencilFunc(GLEQUAL, 255, 8):如果模板缓冲区现有值是1,这个GL_EQUAL的测试将失败——因为255 & 8=8显然不会等于1 & 8=0,但是如果模板缓冲区现有ref值是8,那么测试就会通过了。

当模板测试通过或失败时后续的操作将由glStencilOp函数决定。该函数接受3个参数,分别控制当模板测试失败时、模板测试通过但随后片元未能通过深度测试时、以及当模板测试和深度测试都通过(或模板测试通过而深度测试被禁用)时发生的情况。然后,我们可以保持、重置、增加或替换当前的模板缓冲区的值。甚至可以不需要实际更新它的内容,以便再次复用该模板缓冲区。

glStencilOp(GL_ZERO, GL_KEEP, GL_KEEP):如果模板测试失败,将设置该模板位置的值为零。如果模板测试通过了,即使深度测试失败了,也将保持现有模板值不做更改。

glStencilOp(GL KEEP, GL KEEP, GL REPLACE) : 如果模板测试失败,或者模板测试通过但随后片元未能通过深度测试时,就保持原有的模板值不做改变。但是,如果模板和深度测试都通过了,则模板缓冲中的值将被glStencilFunc的当前ref值替换。

glStencilOp(GL_KEEP, GLREPLACE, GL_KEEP): 如果模板测试通过,但深度测试失败,模板缓冲区中的值将被替换。这可以用来确定一个物体与另一个物体相交的位置,因为物体A与物体B相交的部分将无法通过深度测试。

下面看代码:

void Renderer::RenderScene() {
        // 清除缓冲区
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER);
	// 启用裁剪测试
        if (usingScissors) {
		glEnable(GL_SCISSOR_TEST);
                // 确定裁剪区域
		glScissor(static_cast<float>(width) / 2.5f, static_cast<float>(height / 2.5f),
			static_cast<float>(width) / 5.0f, static_cast<float>(height / 5.0f));
	}
	glUseProgram(currentShader->GetProgram());
	UpdateShaderMatrices();
	glUniform1i(glGetUniformLocation(currentShader->GetProgram(), "diffuseTex"), 0);
	glActiveTexture(GL_TEXTURE0);
	// 启用模板测试
        if (usingStencil) {
		glEnable(GL_STENCIL_TEST);
                // 禁止颜色缓冲区的写入
		glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
		// 不管目标模板缓冲区的颜色值如何,始终测试通过
                glStencilFunc(GL_ALWAYS, 2, ~0);
                // 无论模板测试与深度测试结果如何,都将替换目标模板缓冲区的值
		glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);

		quad->Draw();
                // 开启颜色缓冲区写入
		glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
		// 当目标模板值为2时,则模板测试通过
                glStencilFunc(GL_EQUAL, 2, ~0);
                // 无论模板测试与深度测试的结果如何,目标模板值都将保持结果不变
		glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
	}
	triangle->Draw();

	glUseProgram(0);
	glDisable(GL_SCISSOR_TEST);
	glDisable(GL_STENCIL_TEST);
	SwapBuffers();
}

在上面的代码中,通过usingScissors变量开启裁剪测试,以及通过usingStencil开启模板测试,这两个变量默认为false,当不开启裁剪与模板测试时的画面如下:

裁剪测试的代码比较简单,通过glEnable(GL_SCISSOR_TEST)开启裁剪测试并通过

glScissor(static_cast<float>(width) / 2.5f, static_cast<float>(height / 2.5f),
			static_cast<float>(width) / 5.0f, static_cast<float>(height / 5.0f))

上述代码确定裁剪位置及大小,当开启裁剪测试时的画面如下:

当只开启模板测试时,我们引入了glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GLFALSE),值为GL_FALSE时代表各颜色通道不对颜色缓冲区进行更改,此时的运行画面如下:

之所以画面呈现的是方格形屏蔽,是因为我们在绘制四边形(quad->Draw())使用的图片如下:

并通过片元着色器对图片透明度部分进行提前丢弃:

uniform sampler2D diffuseTex;

in Vertex {
	vec2 texCoord;
} IN;

out vec4 gl_FragColor;

void main(void){
	vec4 value = texture(diffuseTex, IN.texCoord).rgba;
	if(value.a == 0.0) {
		discard;
	}
	gl_FragColor = value;
}

如果注释掉glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GLFALSE)这部分代码,那它的画面如下:

所以在设置模板值得过程中,必须对颜色缓冲区得写入进行限制。

0 Answers