回到目录
大家好,我是阿赵。这里用一个传统的描边例子来说明一下,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 100
Pass
{
Cull Back
HLSLPROGRAM
#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_END
v2f 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 texture
half4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDHLSL
}
Pass
{
Cull Front
HLSLPROGRAM
#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_END
v2f 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 100
Pass
{
Tags{ "LightMode" = "LightweightForward" }
Cull Back
HLSLPROGRAM
#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_END
v2f 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 texture
half4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDHLSL
}
Pass
{
Tags{ "LightMode" = "SRPDefaultUnlit" }
Cull Front
HLSLPROGRAM
#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_END
v2f 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 100
Pass
{
Cull Front
HLSLPROGRAM
#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_END
v2f 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,用于显示描边。