[UnityShader]渲染队列、ZWrite和ZTest

本文深入讲解深度缓存、深度测试的概念及其在Unity中的应用。详细分析了ZWrite和ZTest参数如何影响渲染结果,并通过实验验证不同的配置下物体遮挡的表现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考链接:

http://blog.csdn.net/zhuyingqingfen/article/details/18979547

http://blog.csdn.net/lysc_forever/article/details/13614449

http://m.blog.csdn.net/blog/yxriyin/39081843


(1)什么是深度?

 深度其实就是该像素点在3d世界中距离摄像机的距离。离摄像机越远,则深度值(Z值)越大。


(2)什么是深度缓存?

深度缓存中存储着准备要绘制在屏幕上的像素点的深度值。如果启用了深度缓冲区,在绘制每个像素之前,OpenGL会把该像素的深度值和深度缓存的深度值进行比较。如果新像素深度值<深度缓存深度值,则新像素值会取代原先的;反之,新像素值被遮挡,其颜色值和深度将被丢弃。(深度主要起的是比较的作用)


(3)什么是深度测试?

在深度测试中,默认情况是将要绘制的新像素的z值与深度缓冲区中对应位置的z值进行比较,如果比深度缓存中的值小,那么用新像素的颜色值更新深度缓存中对应像素的颜色值


(4)为什么需要深度?

在不使用深度测试的时候,如果我们先绘制一个距离较近的物体,再绘制距离较远的物体,则距离远的物体因为后绘制,会把距离近的物体覆盖掉,这样的效果并不是我们所希望的。而有了深度缓冲以后,绘制物体的顺序就不那么重要了,都能按照远近(Z值)正常显示,这很关键。



那么,在unity中,如果知道了渲染队列,ZWrite,ZTest,如何确定哪个物体先显示呢?

首先,unity先将渲染队列中较前的进行渲染,然后再执行ZWrite,ZTest

ZWrite可以取的值为:On/Off,默认值为On,代表是否要将像素的深度写入深度缓存中(同时还要看ZTest是否通过)。

ZTest可以取的值为:Greater/GEqual/Less/LEqual/Equal/NotEqual/Always/Never/Off,默认值为LEqual,代表通过比较深度来更改颜色缓存的值。例如当取默认值的情况下,如果将要绘制的新像素的z值小于等于深度缓存中的值,则将用新像素的颜色值更新深度缓存中对应像素的颜色值。需要注意的是,当ZTest取值为Off时,表示的是关闭深度测试,等价于取值为Always,而不是Never!Always指的是直接将当前像素颜色(不是深度)写进颜色缓冲区中;而Never指的是不要将当前像素颜色写进颜色缓冲区中,相当于消失。



///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

那么,重点来了:

1.当ZWrite为On时,ZTest通过时,该像素的深度才能成功写入深度缓存,同时因为ZTest通过了,该像素的颜色值也会写入颜色缓存。

2.当ZWrite为On时,ZTest不通过时,该像素的深度不能成功写入深度缓存,同时因为ZTest不通过,该像素的颜色值不会写入颜色缓存。

3.当ZWrite为Off时,ZTest通过时,该像素的深度不能成功写入深度缓存,同时因为ZTest通过了,该像素的颜色值会写入颜色缓存。

4.当ZWrite为Off时,ZTest不通过时,该像素的深度不能成功写入深度缓存,同时因为ZTest不通过,该像素的颜色值不会写入颜色缓存。


可以看到,像素的深度能否成功写入深度缓存,条件是ZWrite为On,ZTest通过;

写入深度缓存的作用就是为ZTest的比较做准备。

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



因为ZWrite默认值为On,ZTest默认值为LEqual,所以这很好地解释了为什么在unity中,距离相机近的东西会阻挡住距离相机远的东西。如果我们先绘制一个距离较近的物体,再绘制距离较远的物体,则距离远的物体因为后绘制,会把距离近的物体覆盖掉,这时我们可以通过修改ZWrite和ZTest来改变物体的遮挡关系!


测试环境(蓝色方块距离相机较近,注意这个不是Game视图):




测试的Shader代码(两个方块的shader代码起始都是一样的,测试时修改的是测试区里的代码):

Shader "Custom/ZTest" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
/////////////////////////////////////////测试区
		Tags{ "Queue" = "Geometry" }
	    //ZWrite Off
		//ZTest Off
/////////////////////////////////////////测试区
		CGPROGRAM
		#pragma surface surf Lambert

		sampler2D _MainTex;

		struct Input {
			float2 uv_MainTex;
		};

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


渲染顺序:先蓝色方块再白色方块(以下简称蓝,白)

注意ZWrite默认值为On,ZTest默认值为LEqual,没有渲染物体时,深度缓存中的深度可以理解为无限大

1.

/////////////////////////////////////////蓝色方块测试区
		Tags{ "Queue" = "Geometry+200" }
	    ZWrite Off
		//ZTest Off
/////////////////////////////////////////蓝色方块测试区
/////////////////////////////////////////白色方块测试区
		Tags{ "Queue" = "Geometry+300" }
		//ZWrite Off
	    //ZTest Off
/////////////////////////////////////////白色方块测试区

结果:白在前面

分析:蓝没有将像素写进深度缓存中,ZTest通过了,颜色缓存中存放了蓝的颜色值

白将像素写进深度缓存中,ZTest通过了,颜色缓存的值变为白的,所以显示白


2.

/////////////////////////////////////////蓝色方块测试区
		Tags{ "Queue" = "Geometry+200" }
	    //ZWrite Off
		//ZTest Off
/////////////////////////////////////////蓝色方块测试区
/////////////////////////////////////////白色方块测试区
		Tags{ "Queue" = "Geometry+300" }
		//ZWrite Off
	    //ZTest Off
/////////////////////////////////////////白色方块测试区

结果:蓝在前面

分析:蓝将像素写进深度缓存中,ZTest通过了,颜色缓存中存放了蓝的颜色值

而白的像素深度大于蓝的,既没有成功将像素写进深度缓存,同时ZTest不通过,像素被舍弃,所以显示蓝


3.

/////////////////////////////////////////蓝色方块测试区
		Tags{ "Queue" = "Geometry+200" }
	    ZWrite Off
		ZTest Off
/////////////////////////////////////////蓝色方块测试区
/////////////////////////////////////////白色方块测试区
		Tags{ "Queue" = "Geometry+300" }
		//ZWrite Off
	    //ZTest Off
/////////////////////////////////////////白色方块测试区

结果:白在前面

分析:蓝没有将像素写进深度缓存中,ZTest通过了,颜色缓存中存放了蓝的颜色值

白将像素写进深度缓存中,ZTest通过了,颜色缓存中存放为白的颜色值


4.

/////////////////////////////////////////蓝色方块测试区
		Tags{ "Queue" = "Geometry+200" }
	    //ZWrite Off
		//ZTest Off
/////////////////////////////////////////蓝色方块测试区
/////////////////////////////////////////白色方块测试区
		Tags{ "Queue" = "Geometry+300" }
		//ZWrite Off
	    ZTest Off
/////////////////////////////////////////白色方块测试区

结果:白在前面

分析:蓝将像素写进了深度缓存中

白将像素写进了深度缓存中,ZTest通过了,白将颜色缓存中的蓝的像素颜色值替换了。


5.

/////////////////////////////////////////蓝色方块测试区
		Tags{ "Queue" = "Geometry+200" }
	    //ZWrite Off
		//ZTest Off
/////////////////////////////////////////蓝色方块测试区
/////////////////////////////////////////白色方块测试区
		Tags{ "Queue" = "Geometry+300" }
		ZWrite Off
	    ZTest Off
/////////////////////////////////////////白色方块测试区

结果:白在前面

分析:蓝将像素写进了深度缓存中

白没能将像素写进了深度缓存中(ZWrite为off),但ZTest通过了,此时颜色缓存的值变为白的,但是深度缓存的值是蓝的


6.

/////////////////////////////////////////蓝色方块测试区
		Tags{ "Queue" = "Geometry+200" }
	    //ZWrite Off
		ZTest Off
/////////////////////////////////////////蓝色方块测试区
/////////////////////////////////////////白色方块测试区
		Tags{ "Queue" = "Geometry+300" }
		//ZWrite Off
	    ZTest Off
/////////////////////////////////////////白色方块测试区

结果:白在前面

分析:蓝将像素写进了深度缓存中

白将像素写进了深度缓存中,ZTest通过了,此时颜色缓存的值变为白的


7.

/////////////////////////////////////////蓝色方块测试区
		Tags{ "Queue" = "Geometry+200" }
	    ZWrite Off
		//ZTest Off
/////////////////////////////////////////蓝色方块测试区
/////////////////////////////////////////白色方块测试区
		Tags{ "Queue" = "Geometry+300" }
		ZWrite Off
	    //ZTest Off
/////////////////////////////////////////白色方块测试区

结果:白在前面

分析:白、蓝都没有将像素写入深度缓存中,所以深度缓存中的深度值为无穷大,最后因为白中ZTest默认值的原因,所以显示白


8.

/////////////////////////////////////////蓝色方块测试区
		Tags{ "Queue" = "Geometry+200" }
	    ZWrite Off
		ZTest Off
/////////////////////////////////////////蓝色方块测试区
/////////////////////////////////////////白色方块测试区
		Tags{ "Queue" = "Geometry+300" }
		ZWrite Off
	    ZTest Off
/////////////////////////////////////////白色方块测试区

结果:白在前面

分析:与7类似

<think>我们参考用户的问题引用的内容,特别是引用[2]中提到: “Skybox渲染顺序为2500.5, 先渲染了立方体再渲染skybox由于没有写入深度值所以会被Skybox替换。如果把RenderQueue设置为>2500即可比如:2501。或者zwrite off改为on” 用户希望自定义Shader与天空盒处于同一层级,以避免UI被其他模型遮挡。在URP中,天空盒的渲染队列是2500(实际上引用中说是2500.5,但我们在设置时使用整数即可,通常设置为2500以上)。 解决方案: 1. 设置自定义Shader的渲染队列(Render Queue)为2501(或大于2500的数值),这样它会在天空盒之后渲染,从而避免被天空盒覆盖。 2. 同时,根据需求调整深度写入(ZWrite深度测试(ZTest)。如果希望该Shader绘制的物体不被其他物体遮挡(即始终在最前面),可以关闭深度写入(ZWrite Off),并设置深度测试为Always(总是通过)。 具体步骤: 方法一:通过Shader代码设置 在自定义Shader的Properties块中,可以添加一个属性来控制渲染队列,然后在SubShader中设置Tags。 示例Shader代码(基于URP的Unlit Shader模板修改): ```hlsl Shader "Custom/MySkyboxLevelShader" { Properties { _MainTex ("Texture", 2D) = "white" {} _Color ("Color", Color) = (1,1,1,1) } SubShader { Tags { "RenderType"="Opaque" "Queue"="Overlay+1" // 或者直接指定为2501 } // 注意:在URP中,我们通常使用"RenderPipeline"="UniversalPipeline"标签,但设置队列用"Queue"标签 Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; }; struct Varyings { float2 uv : TEXCOORD0; float4 positionHCS : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; half4 _Color; Varyings vert(Attributes IN) { Varyings OUT; OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz); OUT.uv = TRANSFORM_TEX(IN.uv, _MainTex); return OUT; } half4 frag(Varyings IN) : SV_Target { half4 col = tex2D(_MainTex, IN.uv) * _Color; return col; } ENDHLSL } } } ``` 在上面的Shader中,我们通过Tags中的"Queue"="Overlay+1"来设置渲染队列。Overlay是Unity内置的渲染队列,值为3000,所以Overlay+1就是3001。但是,如果我们想设置为2501,可以直接写"Queue"="2501"。 方法二:在材质球中设置 我们也可以不在Shader中写死队列,而在材质球中动态设置: 1. 创建材质球,选择自定义Shader。 2. 在Inspector面板中,找到材质球的渲染队列(Render Queue)输入框,输入2501。 注意:如果Shader中已经设置了队列,那么材质球会覆盖Shader的设置。 方法三:通过脚本设置材质球的渲染队列 如果需要动态设置,可以在脚本中修改: ```csharp material.renderQueue = 2501; ``` 关于深度测试深度写入的调整: 如果希望这个物体始终绘制在最前面(不被其他物体遮挡,但可能遮挡其他物体),我们可以设置深度测试为Always,并关闭深度写入(避免影响后面物体的深度测试): 在Pass中添加: ```hlsl ZWrite Off ZTest Always ``` 这样,无论物体在什么位置,都会绘制在最前面(因为深度测试总是通过)。 但是,根据引用[2]中的情况,如果我们只是希望避免被天空盒遮挡,那么设置队列大于2500(比如2501)即可,因为天空盒的队列是2500,这样我们会在天空盒之后渲染。同时,如果这个物体是半透明的,可能还需要考虑混合模式(Blend)。 总结步骤: 1. 创建自定义Shader,设置渲染队列为2501(或大于2500的值)。 2. 如果需要该物体始终在最前面(包括在其他非透明物体前面),可以设置ZTest AlwaysZWrite Off。但注意,这样可能会造成该物体遮挡它后面的所有物体(包括UI),所以请根据需求调整。 3. 将该Shader应用到材质,材质应用到物体上。 注意:在URP中,天空盒的渲染队列是2500,背景(Background)队列是1000,几何体(Geometry)是2000,透明(Transparent)是3000,叠加(Overlay)是4000。因此,设置为2501的物体会在天空盒之后,几何体之前渲染。如果希望物体在透明物体之后,可以设置更高的队列。 另外,如果这个自定义Shader用于UI,那么通常UI的渲染队列是Transparent(3000)以上,所以不会受到天空盒的影响。但用户提到的是“使其与天空盒处于同一层级”,所以设置2501是合适的。 引用[4]中提到,World Space模式下UI被看作场景中的一个平面物体。如果用户使用的是World Space模式,那么通过设置渲染队列也可以解决遮挡问题。 最后,我们生成相关问题: 1. 在Unity URP中,如何调整天空盒的渲染顺序? 2. 如何为不同的渲染队列设置不同的渲染状态(如深度测试、混合模式)? 3. 在URP中,如何自定义后处理效果并控制其渲染顺序? 注意:用户的问题是关于自定义Shader的渲染层级,所以我们围绕这个主题生成问题。</think>在Unity URP中设置自定义Shader与天空盒(Skybox)处于同一渲染层级,需要理解URP的渲染队列机制并正确配置Shader参数。以下是详细解决方案: ### 解决方案:设置自定义Shader与天空盒同级渲染 #### 1. 理解URP渲染队列 - 天空盒(Skybox)的默认渲染队列值为 **2500**(实际为2500.5)[^2] - 标准渲染队列值: - Background: 1000 - Geometry: 2000 - AlphaTest: 2450 **Skybox: 2500** - Transparent: 3000 - Overlay: 4000 - 要使对象与天空盒同级渲染,需设置渲染队列为 **2500-2501** #### 2. 在Shader中设置渲染队列 在Shader的SubShader块中添加渲染队列标签: ```hlsl SubShader { Tags { "Queue" = "Background+1" // 2501 "RenderType" = "Opaque" } // ... 其他Shader代码 ... } ``` 或直接指定数值: ```hlsl Tags { "Queue" = "2501" // 明确指定队列值 } ``` #### 3. 配置深度测试与写入 为避免渲染冲突,需配置深度测试规则[^2]: ```hlsl Pass { ZWrite Off // 关闭深度写入 ZTest Always // 始终通过深度测试 // ... HLSL/GLSL代码 ... } ``` #### 4. 完整Shader配置示例 ```hlsl Shader "Custom/SkyboxLevelShader" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "Queue" = "2501" // 与天空盒同级 "RenderType" = "Opaque" } Pass { ZWrite Off ZTest Always HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; }; struct Varyings { float4 positionHCS : SV_POSITION; float2 uv : TEXCOORD0; }; sampler2D _MainTex; Varyings vert(Attributes IN) { Varyings OUT; OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz); OUT.uv = IN.uv; return OUT; } half4 frag(Varyings IN) : SV_Target { return tex2D(_MainTex, IN.uv); } ENDHLSL } } } ``` #### 5. 材质设置验证 1. 创建使用此Shader的材质 2. 在Inspector中确认渲染队列显示为 **2501** 3. 如果未显示正确值,可手动设置: ```csharp // 脚本动态设置 material.renderQueue = 2501; ``` #### 6. 渲染顺序验证 - 渲染顺序:Geometry(2000) → Skybox(2500) → 自定义Shader(2501) - 此时自定义Shader对象会渲染在天空盒之上,但不会遮挡UI元素(UI通常在3000+队列) ### 关键原理说明 1. **渲染队列决定绘制顺序**:数值越大越晚渲染 2. **深度测试(ZTest)**:`Always`确保对象始终可见 3. **深度写入(ZWrite)**:关闭避免影响后续渲染 4. **天空盒特殊性**:天空盒在2500渲染后,会清除深度缓冲区[^2],因此2501队列的对象需要特殊处理 > **注意**:如果使用半透明效果,需额外配置混合模式(Blend),但天空盒层级通常用于不透明物体。
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值