原文:
Hull Outlines
到目前为止,我们基本上是一个着色器只会执行一次将模型绘制到屏幕上。实际上在一个着色器中是允许对一个模型绘制多次。比如说我们接下来的轮廓实现方案就需要对模型绘制多次。首先按往常一样渲染一遍模型,然后将模型顶点沿着法线方向移动一点,然后再次进行绘制,而这第二次绘制的模型会出现在上一次绘制的边缘处,也就是我们想得到的轮廓。
为了能够更好的理解本文,建议你先了解什么是表面着色器,以及无光照着色器。
Outlines for Unlit Shaders
沿用之前无光照着色器脚本,我们只需要将其中的Pass
复制一遍就可以。现在有两个完全相同的Pass
,所以即便是绘制两遍,最终的结果也是一样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| Pass{ CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert #pragma fragment frag
sampler2D _MainTex; float4 _MainTex_ST;
fixed4 _Color;
struct appdata{ float4 vertex : POSITION; float2 uv : TEXCOORD0; };
struct v2f{ float4 position : SV_POSITION; float2 uv : TEXCOORD0; };
v2f vert(appdata v){ v2f o; o.position = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; }
fixed4 frag(v2f i) : SV_TARGET{ fixed4 col = tex2D(_MainTex, i.uv); col *= _Color; return col; }
ENDCG }
|
然后我们需要对上面这个Pass
的变量进行修改,因为轮廓不需要纹理,只需要轮廓颜色、轮廓宽度,所以我们删除纹理变量,然后增加轮廓颜色、和轮廓宽度变量,并且在Properties
块中添加这两个属性。
1 2 3 4 5 6 7 8
| Properties{ _OutlineColor ("Outline Color", Color) = (0, 0, 0, 1) _OutlineThickness ("Outline Thickness", Range(0,.1)) = 0.03
_Color ("Tint", Color) = (0, 0, 0, 1) _MainTex ("Texture", 2D) = "white" {} }
|
1 2 3 4
| fixed4 _OutlineColor;
float _OutlineThickness;
|
接下来是修改片段着色器,直接返回我们的轮廓颜色。
1 2 3 4
| fixed4 frag(v2f i) : SV_TARGET{ return _OutlineColor; }
|
因为我们没有使用纹理,所以与纹理相关的UV变量也不需要,所以可以将其从那些结构体中删除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| struct appdata{ float4 vertex : POSITION; };
struct v2f{ float4 position : SV_POSITION; };
v2f vert(appdata v){ v2f o; o.position = UnityObjectToClipPos(position); return o; }
|
上图是修改后的显示效果,我们的物体最终显示为轮廓色,这是因为我们第二个Pass
将第一个Pass
渲染的图完全覆盖了。我们接下来处理这个问题。
为了保证我们的第二个Pass
超出第一个Pass
的显示范围,从而形成轮廓。我们需要将模型的顶点沿着其法向量的方向偏移。因此我们需要在模型网格数据中传入法向量,
1 2 3 4 5
| struct appdata{ float4 vertex : POSITION; float3 normal : NORMAL; };
|
1 2 3 4 5 6 7 8 9 10 11 12
| v2f vert(appdata v){ v2f o; float3 normal = normalize(v.normal); float3 outlineOffset = normal * _OutlineThickness; float3 position = v.vertex + outlineOffset; o.position = UnityObjectToClipPos(position);
return o; }
|
现在可以可以通过_OutlineThinckness
来控制边缘的宽度,但是我们第一个Pass
渲染的画面还是被遮挡了。为了修复这个问题,我们将第二个Pass
改为正面剔除。这样可以保证第二个Pass
渲染的画面永远在第一个Pass
之后。
上图就是我们得到的轮廓了。
Source
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
| Shader "Tutorial/19_InvertedHull/Unlit"{ Properties{ _OutlineColor ("Outline Color", Color) = (0, 0, 0, 1) _OutlineThickness ("Outline Thickness", Range(0,.1)) = 0.03
_Color ("Tint", Color) = (0, 0, 0, 1) _MainTex ("Texture", 2D) = "white" {} }
SubShader{ Tags{ "RenderType"="Opaque" "Queue"="Geometry"}
Pass{ CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert #pragma fragment frag
sampler2D _MainTex; float4 _MainTex_ST;
fixed4 _Color;
struct appdata{ float4 vertex : POSITION; float2 uv : TEXCOORD0; };
struct v2f{ float4 position : SV_POSITION; float2 uv : TEXCOORD0; };
v2f vert(appdata v){ v2f o; o.position = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; }
fixed4 frag(v2f i) : SV_TARGET{ fixed4 col = tex2D(_MainTex, i.uv); col *= _Color; return col; }
ENDCG }
Pass{ Cull front
CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert #pragma fragment frag
fixed4 _OutlineColor; float _OutlineThickness;
struct appdata{ float4 vertex : POSITION; float3 normal : NORMAL; };
struct v2f{ float4 position : SV_POSITION; };
v2f vert(appdata v){ v2f o; float3 normal = normalize(v.normal); float3 outlineOffset = normal * _OutlineThickness; float3 position = v.vertex + outlineOffset; o.position = UnityObjectToClipPos(position);
return o; }
fixed4 frag(v2f i) : SV_TARGET{ return _OutlineColor; }
ENDCG } }
FallBack "Standard" }
|
Outlines with Surface Shaders
前面是在普通的顶点、片段着色其中应用轮廓效果,在表面着色器中其实也一样。对于表面着色器,Unity会自动生成部分代码,但是不会改动我们写入的代码,因此我们可以直接将前面的第二个轮廓Pass
直接复制过来,并且可以实现同样的轮廓效果。
Source
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| Shader "Tutorial/020_InvertedHull/Surface" { Properties { _Color ("Tint", Color) = (0, 0, 0, 1) _MainTex ("Texture", 2D) = "white" {} _Smoothness ("Smoothness", Range(0, 1)) = 0 _Metallic ("Metalness", Range(0, 1)) = 0 [HDR] _Emission ("Emission", color) = (0,0,0)
_OutlineColor ("Outline Color", Color) = (0, 0, 0, 1) _OutlineThickness ("Outline Thickness", Range(0,1)) = 0.1 } SubShader { Tags{ "RenderType"="Opaque" "Queue"="Geometry"}
CGPROGRAM #pragma surface surf Standard fullforwardshadows #pragma target 3.0
sampler2D _MainTex; fixed4 _Color;
half _Smoothness; half _Metallic; half3 _Emission;
struct Input { float2 uv_MainTex; };
void surf (Input i, inout SurfaceOutputStandard o) { fixed4 col = tex2D(_MainTex, i.uv_MainTex); col *= _Color; o.Albedo = col.rgb; o.Metallic = _Metallic; o.Smoothness = _Smoothness; o.Emission = _Emission; } ENDCG
Pass{ Cull Front
CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert #pragma fragment frag
fixed4 _OutlineColor; float _OutlineThickness;
struct appdata{ float4 vertex : POSITION; float4 normal : NORMAL; };
struct v2f{ float4 position : SV_POSITION; };
v2f vert(appdata v){ v2f o; o.position = UnityObjectToClipPos(v.vertex + normalize(v.normal) * _OutlineThickness); return o; }
fixed4 frag(v2f i) : SV_TARGET{ return _OutlineColor; }
ENDCG } } FallBack "Standard" }
|
本篇轮廓实现方案和上一篇后处理轮廓方案的区别在于,本文所有的作色器是应用到个体模型上,所以可以根据需要选择哪些模型显示轮廓,并且还可以调节轮廓的宽度,整体的表现效果也有很大的差异。如果说哪个方案好,我觉得我们应该掌握这两种轮廓方案,然后根据实际情况进行选择。
希望你能通过本篇,了解如何在一个着色器中使用多个Pass
,并且知道如何利用它们来实现轮廓效果。
你可以在以下链接找到源码:
https://github.com/ronja-tutorials/ShaderTutorials/blob/master/Assets/020_Inverted_Hull/UnlitOutlines.shader
https://github.com/ronja-tutorials/ShaderTutorials/blob/master/Assets/020_Inverted_Hull/SurfaceOutlines.shader
希望你能喜欢这个教程哦!如果你想支持我,可以关注我的推特,或者通过ko-fi、或patreon给两小钱。总之,各位大爷,走过路过不要错过,有钱的捧个钱场,没钱的捧个人场:-)!!!