目录:Unity Shader - 知识点目录(先占位,后续持续更新) 原文:Surface Shader examples 版本:2019.1
Surface Shader examples表面着色器的例子
在此页面的Surface Shaders的例子,都将向你展示如果使用内置的光照模型。例如,如何实现自定义的光照模型,查看Surface Shader Lighting Examples。
Simple shader example简单的着色器例子
我们将从一个非常简单的着色器开始,并以此为基础。此着色器只有一个表面白色颜色的效果。使用的光照模型是:Lambert(diffuse)。
Shader "Example/Diffuse Simple" { SubShader { Tags { "RenderType" = "Opaque" } CGPROGRAM #pragma surface surf Lambert struct Input { float4 color : COLOR; }; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = 1; } ENDCG } Fallback "Diffuse" }
这是一个模型与两个灯的样子:
全白色的对象看起来无趣,所以我们添加一个纹理。我们将给Shader添加Properties块,因此我们的Material Inspector将会显示一个名为Texture纹理选择器。
Shader "Example/Diffuse Texture" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType" = "Opaque" } CGPROGRAM #pragma surface surf Lambert struct Input { float2 uv_MainTex; }; sampler2D _MainTex; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; } ENDCG } Fallback "Diffuse" }
法线映射
现在我们再添加法线映射:
Shader "Example/Diffuse Bump" { Properties { _MainTex ("Texture", 2D) = "white" {} _BumpMap ("Bumpmap", 2D) = "bump" {} } SubShader { Tags { "RenderType" = "Opaque" } CGPROGRAM #pragma surface surf Lambert struct Input { float2 uv_MainTex; float2 uv_BumpMap; }; sampler2D _MainTex; sampler2D _BumpMap; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap)); } ENDCG } Fallback "Diffuse" }Rim Lighting
边缘光照
现在我们再添加一些Rim Lighting(边缘光照)来照亮GameObject的边缘。我们将基于表面的法线方向与视角方向的夹角关系添加一些发光效果。为此,将使用Surface Shader内置变量:viewDir。
Shader "Example/Rim" { Properties { _MainTex ("Texture", 2D) = "white" {} _BumpMap ("Bumpmap", 2D) = "bump" {} _RimColor ("Rim Color", Color) = (0.26,0.19,0.16,0.0) _RimPower ("Rim Power", Range(0.5,8.0)) = 3.0 } SubShader { Tags { "RenderType" = "Opaque" } CGPROGRAM #pragma surface surf Lambert struct Input { float2 uv_MainTex; float2 uv_BumpMap; float3 viewDir; }; sampler2D _MainTex; sampler2D _BumpMap; float4 _RimColor; float _RimPower; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap)); half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal)); o.Emission = _RimColor.rgb * pow (rim, _RimPower); } ENDCG } Fallback "Diffuse" }
细节纹理
与基础纹理一起组合使用添加一个细节纹理。此细节纹理使用不一样的uv tiling,因为我们需要使用另一个不同的Input uv坐标变量。
Shader "Example/Detail" { Properties { _MainTex ("Texture", 2D) = "white" {} _BumpMap ("Bumpmap", 2D) = "bump" {} _Detail ("Detail", 2D) = "gray" {} } SubShader { Tags { "RenderType" = "Opaque" } CGPROGRAM #pragma surface surf Lambert struct Input { float2 uv_MainTex; float2 uv_BumpMap; float2 uv_Detail; }; sampler2D _MainTex; sampler2D _BumpMap; sampler2D _Detail; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; o.Albedo *= tex2D (_Detail, IN.uv_Detail).rgb * 2; o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap)); } ENDCG } Fallback "Diffuse" }
在空间坐标下的细节纹理
我们将之前的uv_Detail换成screenPos,然后再surf里头处理屏幕坐标的screenUV生成。
Shader "Example/ScreenPos" { Properties { _MainTex ("Texture", 2D) = "white" {} _Detail ("Detail", 2D) = "gray" {} } SubShader { Tags { "RenderType" = "Opaque" } CGPROGRAM #pragma surface surf Lambert struct Input { float2 uv_MainTex; float4 screenPos; }; sampler2D _MainTex; sampler2D _Detail; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; float2 screenUV = IN.screenPos.xy / IN.screenPos.w; screenUV *= float2(8,6); o.Albedo *= tex2D (_Detail, screenUV).rgb * 2; } ENDCG } Fallback "Diffuse" }
上面的Shader相比之前的Shader删除了法线映射和Emissive,让它更精简一些:
立方体贴图反射
在此使用内置worldRefl变量类处理立方体贴图反射。内置的反射处理相当简单:
Shader "Example/WorldRefl" { Properties { _MainTex ("Texture", 2D) = "white" {} _Cube ("Cubemap", CUBE) = "" {} } SubShader { Tags { "RenderType" = "Opaque" } CGPROGRAM #pragma surface surf Lambert struct Input { float2 uv_MainTex; float3 worldRefl; }; sampler2D _MainTex; samplerCUBE _Cube; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb * 0.5; o.Emission = texCUBE (_Cube, IN.worldRefl).rgb; } ENDCG } Fallback "Diffuse" }
因为反射的颜色我们设置到Emission,因此表面颜色反射出环境光似的相当光滑:
如果你想法线贴图作用到反射效果,那么需要添加:INTERVAL_DATA到Input结构体,并在逐像素中,在你处理了法线后,使用WorldReflectionVector函数计算反射向量。
Shader "Example/WorldRefl Normalmap" { Properties { _MainTex ("Texture", 2D) = "white" {} _BumpMap ("Bumpmap", 2D) = "bump" {} _Cube ("Cubemap", CUBE) = "" {} } SubShader { Tags { "RenderType" = "Opaque" } CGPROGRAM #pragma surface surf Lambert struct Input { float2 uv_MainTex; float2 uv_BumpMap; float3 worldRefl; INTERNAL_DATA }; sampler2D _MainTex; sampler2D _BumpMap; samplerCUBE _Cube; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb * 0.5; o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap)); o.Emission = texCUBE (_Cube, WorldReflectionVector (IN, o.Normal)).rgb; } ENDCG } Fallback "Diffuse" }
添加了法线映射后:
以世界坐标来切片
此着色器对GameObject处理了类似水平切片的像素剔除。基于逐像素世界坐标使用clip()的CG/HLSL函数处理。我们用到变量表面着色器内置worldPos变量。
Shader "Example/Slices" { Properties { _MainTex ("Texture", 2D) = "white" {} _BumpMap ("Bumpmap", 2D) = "bump" {} } SubShader { Tags { "RenderType" = "Opaque" } Cull Off CGPROGRAM #pragma surface surf Lambert struct Input { float2 uv_MainTex; float2 uv_BumpMap; float3 worldPos; }; sampler2D _MainTex; sampler2D _BumpMap; void surf (Input IN, inout SurfaceOutput o) { clip (frac((IN.worldPos.y+IN.worldPos.z*0.1) * 5) - 0.5); o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap)); } ENDCG } Fallback "Diffuse" }
法线方向的顶点向外挤出
可在Vertex Shader处理,使用"Vertex Modifier"(顶点修改器)来修改来自输入的顶点数据。这可以实现程序化动画,沿着法线方向挤出的动画。使用到Surface Shader的编译指令:vertex:functionName,和一个函数参数inout appdata_full。
着色器沿着法线方向乘以amount系数来位移顶点:
Shader "Example/Normal Extrusion" { Properties { _MainTex ("Texture", 2D) = "white" {} _Amount ("Extrusion Amount", Range(-1,1)) = 0.5 } SubShader { Tags { "RenderType" = "Opaque" } CGPROGRAM #pragma surface surf Lambert vertex:vert struct Input { float2 uv_MainTex; }; float _Amount; void vert (inout appdata_full v) { v.vertex.xyz += v.normal * _Amount; } sampler2D _MainTex; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; } ENDCG } Fallback "Diffuse" }
自定义的逐顶点计算用的数据
使用顶点修改器对的功能,这可以在vertex shader中计算自定义数据,然后传到到Surface Shader的逐像素函数中。一样是使用vertex:functionName指令,但函数应该带有两个参数:inout appdata_full和out Input。你可以填入任意的非内置变量在这两个各结构体重。
注意:自定义的Input成员使用这种方式必须不能以"uv"开头,否则无法整体执行。
下面的例子中定义了一个float3 customColor成员,用于vertex函数中计算用:
Shader "Example/Custom Vertex Data" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType" = "Opaque" } CGPROGRAM #pragma surface surf Lambert vertex:vert struct Input { float2 uv_MainTex; float3 customColor; }; void vert (inout appdata_full v, out Input o) { UNITY_INITIALIZE_OUTPUT(Input,o); o.customColor = abs(v.normal); } sampler2D _MainTex; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; o.Albedo *= IN.customColor; } ENDCG } Fallback "Diffuse" }
customColor赋值为法线normal的绝对值:
最适合用于计算不带内置Input变量的顶点数据
;这可以优化Shader的计算能力。例如,尽可能在顶点中计算边缘光照,而不是在逐像素函数中处理。
最终颜色的修改器
尽可能的使用Shader的"final color modifier"函数来修改最终颜色。为此编译指令finalcolor:funcitonName,带上参数:Input IN, SurfaceOutput o, inout fixed4 color。
这有个在final color中应用色调的简单shader。这个不同于仅仅应用色调到Albedo颜色重:也应用影响Lightmaps,Light Probes类似的额外光源颜色。
Shader "Example/Tint Final Color" { Properties { _MainTex ("Texture", 2D) = "white" {} _ColorTint ("Tint", Color) = (1.0, 0.6, 0.6, 1.0) } SubShader { Tags { "RenderType" = "Opaque" } CGPROGRAM #pragma surface surf Lambert finalcolor:mycolor struct Input { float2 uv_MainTex; }; fixed4 _ColorTint; void mycolor (Input IN, SurfaceOutput o, inout fixed4 color) { color *= _ColorTint; } sampler2D _MainTex; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; } ENDCG } Fallback "Diffuse" }
自定义雾化和最终颜色修改器
一个常见的使用final color的修改的情况(像上面的例子的)将实现完整的forward rendering(正向渲染)的自定义雾化。雾化会影响逐像素Shader的最终颜色的计算,这正是finalcolor修改器需要处理的。
此例子是基于屏幕中间点的距离来应用雾化色调的。结合使用vertex modifier和自定义数据(fog数据)和final color modifier。当使用正向渲染的叠加pass,雾化需要淡化到黑色。这个例子中处理处理这个功能外还执行检查UNITY_PASS_FORWARDADD宏定义。
Shader "Example/Fog via Final Color" { Properties { _MainTex ("Texture", 2D) = "white" {} _FogColor ("Fog Color", Color) = (0.3, 0.4, 0.7, 1.0) } SubShader { Tags { "RenderType" = "Opaque" } CGPROGRAM #pragma surface surf Lambert finalcolor:mycolor vertex:myvert struct Input { float2 uv_MainTex; half fog; }; void myvert (inout appdata_full v, out Input data) { UNITY_INITIALIZE_OUTPUT(Input,data); float4 hpos = UnityObjectToClipPos(v.vertex); hpos.xy/=hpos.w; data.fog = min (1, dot (hpos.xy, hpos.xy)*0.5); } fixed4 _FogColor; void mycolor (Input IN, SurfaceOutput o, inout fixed4 color) { fixed3 fogColor = _FogColor.rgb; #ifdef UNITY_PASS_FORWARDADD fogColor = 0; #endif color.rgb = lerp (color.rgb, fogColor, IN.fog); } sampler2D _MainTex; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; } ENDCG } Fallback "Diffuse" }
线性雾化
Shader "Example/Linear Fog" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf Lambert finalcolor:mycolor vertex:myvert #pragma multi_compile_fog sampler2D _MainTex; uniform half4 unity_FogStart; uniform half4 unity_FogEnd; struct Input { float2 uv_MainTex; half fog; }; void myvert (inout appdata_full v, out Input data) { UNITY_INITIALIZE_OUTPUT(Input,data); float pos = length(UnityObjectToViewPos(v.vertex).xyz); float diff = unity_FogEnd.x - unity_FogStart.x; float invDiff = 1.0f / diff; data.fog = clamp ((unity_FogEnd.x - pos) * invDiff, 0.0, 1.0); } void mycolor (Input IN, SurfaceOutput o, inout fixed4 color) { #ifdef UNITY_PASS_FORWARDADD UNITY_APPLY_FOG_COLOR(IN.fog, color, float4(0,0,0,0)); #else UNITY_APPLY_FOG_COLOR(IN.fog, color, unity_FogColor); #endif } 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" }Decals
贴花效果
贴花效果通常用于添加细节贴图实现(如:弹孔)。尤其在延迟渲染啊中很有用,因为它们会在光照计算前修改GBUffer,因为节省了性能消耗。
一个典型的场景,贴花效果应该不透明对象渲染后(并且不能是投影处理的Pass)渲染,查看ShaderLab中的"Tags"属性。
Shader "Example/Decal" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" "Queue"="Geometry+1" "ForceNoShadowCasting"="True" } LOD 200 Offset -1, -1 CGPROGRAM #pragma surface surf Lambert decal:blend 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 } }