跳转到主要内容
Computer Graphics Sep 24, 2022 2 tags

图形 3.1 深度与模板测试

图形 3.1 深度与模板测试

cover

模板测试(Stencil Test)

简单介绍

  1. 颜色缓冲区
  2. 模板缓冲区,我们会在模板缓冲区中给每个片元分配一个[0,255]之间的数值,8位,默认位0。
  3. 通过自定义的准则,上图展示了,模板缓冲区中片元为0,则不显示颜色缓冲区对应的片元;为1,则显示颜色缓冲区的片元。

理解

模板测试在渲染管线的位置

在片元着色器结束,到帧缓存输出之间,有一个逐片元操作,有很多种测试。

Pixel Ownership Test :控制像素的使用权限。(只能在game窗口和scene窗口显示。其他位置都会被剔除掉)

Scissor Test :裁剪测试。(在game和scene窗口内,自己可以再次定义渲染范围)

Alpha Test :透明度测试。(设置一个透明度阈值,大于这个阈值就通过测试,相反小于就剔除)

Stencil Test :模板测试。

Depth Test :深度测试。

Blending :透明度混合。(实现半透明效果)

Dithering

Logic Op

逻辑上理解模板测试

  1. 模板缓存参考值和读掩码按位与
  2. 比较
  3. 模板缓冲值和读掩码按位与
  • 通过一定条件来判断是对该片元或片元属性执行抛弃操作还是保留操作。
if(referenceValue & readMask comparisonFuction stencilBufferValue &readMask){
    通过像素
}
else{
	剔除像素
}

书面概念上理解模板测试

模板测试:模板缓冲区可以为屏幕上每个像素点保存一个无符号整数值(8位 0~255),这个值的具体意义视程序的具体应用而定

发生在Alpha Test之后,Depth Test之前,也能实现通过测试,就保留像素点,更新颜色缓冲区,相反剔除。

语法

stencil{
	Ref referenceValue 		//给当前片元设置参考值 [0,255]
    ReadMask readMask		//读掩码
    WriteMask writeMask		//写掩码
    Comp comparisonFunction	//比较操作
    Pass stencilOperation	//通过了有什么操作
    Fail stencilOperation	//没有通过有什么操作
    ZFail stencilOperation	//通过了但是深度测试没通过
}

ComparisonFunction

更新值 StencilOperation

模板测试的项目展示

Demo1

Minions Art: https://www.patreon.com/posts/14832618

Demo逻辑

  1. 首先当前的模板缓冲区全为0。
  2. 先渲染的不透明物体(不考虑其他,只考虑这次demo)。
  3. 再渲染遮罩(这个遮罩的作用是修改屏幕的模板缓冲区值,因为不比较就通过测试,然后Pass replace修改模板缓冲区中的值)。
  4. 最后渲染人物物体(通过参考值来判断是否等于模板缓冲区中的值,来判断保留当前片元)。

遮罩Shader

Shader "FX/StencilMask" {
	Properties{

		_ID("Mask ID", Int) = 1
	}
		SubShader{
            //在不透明物体后渲染
			Tags{ "RenderType" = "Opaque" "Queue" = "Geometry+1" }
			ColorMask 0 //颜色遮罩,0就是什么都不输出,也可以选择:RGBA,RGB,R,G,B,A
			ZWrite off	//	关闭深度写入	
			Stencil{
				Ref[_ID]
				Comp always	//默认keep
				Pass replace //默认keep
               	//Fail keep
                //ZFail keep
			}
		Pass{
			CGINCLUDE
		struct appdata {
			float4 vertex : POSITION;
		};
		struct v2f {
			float4 pos : SV_POSITION;
		};

	
		v2f vert(appdata v) {
			v2f o;
			o.pos = UnityObjectToClipPos(v.vertex);
			return o;
		}
		half4 frag(v2f i) : SV_Target{
			return half4(1,1,1,1);
		}
		ENDCG

	}
}
}

物体Shader

Shader "Toon/Lit StencilMask" {
	Properties {
		_Color ("Main Color", Color) = (0.5,0.5,0.5,1)
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_Ramp ("Toon Ramp (RGB)", 2D) = "gray" {} 
		_ID("Mask ID", Int) = 1

	}

	SubShader {
        //在遮罩后渲染
		Tags { "RenderType"="Opaque" "Queue" = "Geometry+2"}
		LOD 200
		
		Stencil {
			Ref [_ID]
			Comp equal
		}

		
CGPROGRAM
#pragma surface surf ToonRamp

sampler2D _Ramp;

// custom lighting function that uses a texture ramp based
// on angle between light direction and normal
#pragma lighting ToonRamp exclude_path:prepass
inline half4 LightingToonRamp (SurfaceOutput s, half3 lightDir, half atten)
{
	#ifndef USING_DIRECTIONAL_LIGHT
	lightDir = normalize(lightDir);
	#endif
	
	half d = dot (s.Normal, lightDir)*0.5 + 0.5;
	half3 ramp = tex2D (_Ramp, float2(d,d)).rgb;
	
	half4 c;
	c.rgb = s.Albedo * _LightColor0.rgb * ramp * (atten * 2);
	c.a = 0;
	return c;
}


sampler2D _MainTex;
float4 _Color;

struct Input {
	float2 uv_MainTex : TEXCOORD0;
};

void surf (Input IN, inout SurfaceOutput o) {
	half4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
	o.Albedo = c.rgb;
	o.Alpha = c.a;
}
ENDCG

	} 

	Fallback "Diffuse"
}

模板测试总结

  • 使用模板缓冲区最重要的两个值:当前模板缓冲值(stencilBufferValue)和模板参考值(referenceValue)。
  • 模板测试主要就是对这两个值使用特定的比较操作:Never,Always,Less,LEqual,Greater,GEqual,Equal。
  • **模板测试之后要对模板缓冲区的值(stencilBufferValue)进行更新操作,**更新操作包括:Keep,Zero,Replace,IncrSat,DecrSat,Invert等等。
  • 模板测试之后可以根据结果对模板缓冲区做不同的更新操作,比如模板测试成功操作Pass,模板测试失败操作Fail,深度测试失败操作ZFail,还有正对正面和背面精确更新操作PassBack,PassFront,FailBack等等。

深度测试(Z Test)

用来判断物体的先后顺序。

理解

深度测试在渲染管线的位置

逻辑上理解深度测试

if(ZWrite On && (currentDepthValue ComparisonFunction DepthBufferValue)){
	写入深度
}else{
	忽略深度
}
if(currentDepthValue ComparsionFunction DepthBufferValue){
	写入颜色缓冲区
}else{
	不写入颜色缓冲区
}

书面概念上理解深度测试

深度测试 :针对当前对象在屏幕上(更准确的说是frame buffer对应的像素点,将对象自身的深度值与当前该像素点缓存的深度值比较,如果通过了,本对象在该像素点才会将颜色写入颜色缓冲区,否则不会写入颜色缓冲区。

发展上理解深度测试

画家算法

从远及近的画(渲染)物体,如果近处的物体很大,遮蔽了后面的物体,这就会导致后面的物体是无用渲染,Overdraw。

Z-buffer

通过深度缓冲区控制渲染顺序,控制Z-buffer对深度的存储:Z Test,Z Write

深度缓冲区(Z-Buffer)

深度缓冲就像颜色缓冲(储存所有的片段颜色:视觉输出)一样,在每个片段中储存了信息,并且(通常)和颜色缓冲区有着一样的宽度和高度。深度缓冲区是由窗口系统自动创建的,它会以16、24或32位float的形式储存它的深度值。在大部分的系统中,深度缓冲的精度都是24位的

Z-buffer 中储存的是当前的深度信息,对于每个像素存储一个深度值。

通过Z Write 和 Z Test 来调用 Z-Buffer,实现想要的渲染结果。

Z Write

深度写入包括两种状态:ZWrite On 与 ZWrite Off

当我们开启深度写入的时候,物体被渲染时针对物体在屏幕(更准确地说是frame buffer)上每个像素的深度都写入到深度缓冲区;反之,如果是ZWrite Off,那么物体的深度就不会写入深度缓冲区。但是,物体是否会写入深度,除了ZWrite这个状态之外,更重要的是需要深度测试通过,也就是ZTest通过,如果ZTest都没通过,那么也就不会写入深度了。

ZTest分为通过和不通过两种情况,ZWrite分为开启和关闭两种情况的四种情况:

  • 深度测试通过,深度写入开启:写入深度缓冲区,写入颜色缓冲区。
  • 深度测试通过,深度写入关闭:不写深度缓冲区,写入颜色缓冲区。
  • 深度测试失败,深度写入开启:不写深度缓冲区,不写颜色缓冲区。
  • 深度测试失败,深度写入关闭:不写深度缓冲区,不写颜色缓冲区。

Z Test比较操作

默认是ZWrite On 和 ZTest LEqual,深度缓冲区的值一开始是无限大的。

渲染队列

Unity中内置的几种渲染队列,按照渲染顺序,从先到后进行排序,队列数越小,越先渲染,队列数越大,越后渲染。

  • **Background(1000) :**最早被渲染的物体的队列。
  • **Geometry(2000) :**不透明物体的渲染队列。大多数物体都应该使用该队列进行渲染,也是Unity Shader中默认的渲染队列。
  • **AlphaTest(2450) :**有透明通道,需要进行Alpha Test的物体的队列,比在Geometry中更有效。
  • Transparent(3000) : 半透物体的渲染队列。一般是不写深度的物体,Alpha Blend等的在该队列渲染。
  • **Overlay(4000) :**最后被渲染的物体的队列,一般是覆盖效果,比如镜头光晕,屏幕贴片之类的。

Unity中设置渲染队列

//默认是Geometry
Tags{
	"Queue" = "Transparent"
}
  • 不透明物体的渲染顺序:从前往后。
  • 透明物体的渲染顺序:从后往前。(OverDraw) 可以在Shader的Inspector面板查看相关属性

深度测试的项目展示

Demo1

Related Articles

继续阅读