回到目录
大家好,我是阿赵。这里用一个传统的描边例子来说明一下,URP下怎么使用多Pass和Features。
一、传统多Pass描边
最常用的制作描边方法,就是写多一个Cull Front的Pass,然后通过法线方向扩展顶点,模拟描边的效果。
如果用HLSL来写,代码会是这样的:
Shader "azhao/UnlitOutlineBase"
{Properties{_MainTex ("Texture", 2D) = "white" {}_outlineLen("outlineLen",float) = 0.02}SubShader{Tags { "RenderType"="Opaque" }LOD 100Pass{Cull BackHLSLPROGRAM#pragma vertex vert#pragma fragment frag#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/core.hlsl"struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;};struct v2f{float2 uv : TEXCOORD0;float4 pos : SV_POSITION;};CBUFFER_START(UnityPerMaterial)sampler2D _MainTex;float4 _MainTex_ST;CBUFFER_ENDv2f vert (appdata v){v2f o;VertexPositionInputs vertexInput = GetVertexPositionInputs(v.vertex.xyz);o.pos = vertexInput.positionCS;o.uv = TRANSFORM_TEX(v.uv, _MainTex);return o;}half4 frag (v2f i) : SV_Target{// sample the texturehalf4 col = tex2D(_MainTex, i.uv);return col;}ENDHLSL}Pass{Cull FrontHLSLPROGRAM#pragma vertex vert#pragma fragment frag#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/core.hlsl"struct appdata{float4 vertex : POSITION;float3 normal :NORMAL;};struct v2f{float4 pos : SV_POSITION;};CBUFFER_START(UnityPerMaterial)float _outlineLen;CBUFFER_ENDv2f vert(appdata v){v2f o;float4 pos = mul(UNITY_MATRIX_MV, v.vertex);float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);normal.z = -0.5;pos = pos + float4(normalize(normal), 0) * _outlineLen;o.pos = mul(UNITY_MATRIX_P, pos);return o;}half4 frag(v2f i) : SV_Target{return half4(0,0,0,1);}ENDHLSL}}
}
在非URP的情况下,这个Shader可以正常的显示描边:
二、在URP下的多Pass情况
接下来,我们把项目改成URP渲染,具体方法可以参考之前的文章
Unity里URP项目的介绍和创建
切换完成后,会发现刚才那个shader不能正常显示描边了
这是因为,URP本身不鼓励在一个Shader里面写多Pass,所以如果不指定LightMode的情况下,只有第一个Pass生效,其他的Pass都不会生效。
知道了原因之后,那么给Pass加上Tags,应该就能解决这个问题。
Shader "azhao/UnlitOutlineMulPass"
{Properties{_MainTex ("Texture", 2D) = "white" {}_outlineLen("outlineLen",float) = 0.2}SubShader{Tags { "RenderType"="Opaque" }LOD 100Pass{Tags{ "LightMode" = "LightweightForward" }Cull BackHLSLPROGRAM#pragma vertex vert#pragma fragment frag#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/core.hlsl"struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;};struct v2f{float2 uv : TEXCOORD0;float4 pos : SV_POSITION;};CBUFFER_START(UnityPerMaterial)sampler2D _MainTex;float4 _MainTex_ST;CBUFFER_ENDv2f vert (appdata v){v2f o;VertexPositionInputs vertexInput = GetVertexPositionInputs(v.vertex.xyz);o.pos = vertexInput.positionCS;o.uv = TRANSFORM_TEX(v.uv, _MainTex);return o;}half4 frag (v2f i) : SV_Target{// sample the texturehalf4 col = tex2D(_MainTex, i.uv);return col;}ENDHLSL}Pass{Tags{ "LightMode" = "SRPDefaultUnlit" }Cull FrontHLSLPROGRAM#pragma vertex vert#pragma fragment frag#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/core.hlsl"struct appdata{float4 vertex : POSITION;float3 normal :NORMAL;};struct v2f{float4 pos : SV_POSITION;};CBUFFER_START(UnityPerMaterial)float _outlineLen;CBUFFER_ENDv2f vert(appdata v){v2f o;float4 pos = mul(UNITY_MATRIX_MV, v.vertex);float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);normal.z = -0.5;pos = pos + float4(normalize(normal), 0) * _outlineLen;o.pos = mul(UNITY_MATRIX_P, pos);return o;}half4 frag(v2f i) : SV_Target{return half4(0,0,0,1);}ENDHLSL}}
}
可以看到,在第一个Pass加的是
Tags{ "LightMode" = "LightweightForward" }
而在第二个Pass加的是
Tags{ "LightMode" = "SRPDefaultUnlit" }
从URP的代码看
m_ShaderTagIdList.Add(new ShaderTagId("UniversalForward"));m_ShaderTagIdList.Add(new ShaderTagId("LightweightForward"));m_ShaderTagIdList.Add(new ShaderTagId("SRPDefaultUnlit"));
应该来说,用UniversalForward、LightweightForward或者SRPDefaultUnlit都是可以的。
加完Tags之后,刚才那个Shader在URP渲染管线下,也能正常的渲染出描边了。
三、使用URP的Features
之前说过,URP本身并不鼓励在Shader里面写多Pass。这是因为URP的中心思想都是尽量用同一个Pass渲染尽量多的东西。所以其实我们通过Tags来强制开启多Pass,本身也不太符合URP的思想。
为了解决这个问题,URP提供了一个叫做Features的功能。
接下来我们就来用一下这个功能。
回到URP的Asset_Renderer文件的设置项,增加一个叫做outline的Feature
在新建的Feature里面,指定了需要渲染的Layer,我这里新建了一个叫做outline的Layer。然后把刚才用于渲染带描边的小球的多个Pass的材质球拖到Overrides里面,然后指定Pass Index为1。
指定材质球并指定Pass这一步,其实是可以在一个多Pass的Shader里面指定用哪一个Pass作为这个Feature的渲染。
如果我们不用之前的Shader,单纯写一个只有一个描边Pass的Shader,也是可以的,比如
Shader "azhao/UnlitOutlineOnly"
{Properties{_MainTex ("Texture", 2D) = "white" {}_outlineLen("outlineLen",float) = 0.2}SubShader{Tags { "RenderType"="Opaque" }LOD 100Pass{Cull FrontHLSLPROGRAM#pragma vertex vert#pragma fragment frag#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/core.hlsl"struct appdata{float4 vertex : POSITION;float3 normal :NORMAL;};struct v2f{float4 pos : SV_POSITION;};CBUFFER_START(UnityPerMaterial)float _outlineLen;CBUFFER_ENDv2f vert(appdata v){v2f o;float4 pos = mul(UNITY_MATRIX_MV, v.vertex);float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);normal.z = -0.5;pos = pos + float4(normalize(normal), 0) * _outlineLen;o.pos = mul(UNITY_MATRIX_P, pos);return o;}half4 frag(v2f i) : SV_Target{return half4(0,0,0,1);}ENDHLSL}}
}
那么Feature里面的设置就是
然后,回到小球身上,随便给一个默认的材质球给小球就行了,然后把小球的Layer设置成刚才新建的outline。这个时候
可以看到,小球是使用了默认的材质去渲染,只是额外叠加了一个描边的效果。
这样的处理就很符合URP的思想了,指定一个层,不管这个层原来的Shader是怎样显示的,都统一额外的用一个Pass,用于显示描边。