回到目录

大家好,我是阿赵。这里用一个传统的描边例子来说明一下,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,用于显示描边。

查看原文