天天看點

Surface Shader examples 表面着色器示例 - Unity Shader Reference 系列3

Surface Shader examples 表面着色器示例

本文檔主要是對Unity官方手冊的個人了解與總結(其實以翻譯記錄為主:>)

僅作為個人學習使用,不得作為商業用途,歡迎轉載,并請注明出處。

文章中涉及到的操作都是基于Unity2018.2版本

參考連結:https://docs.unity3d.com/Manual/SL-SurfaceShaderExamples.html

The Surface Shaders examples on this page show you how to use the built-in lighting models. For examples on how to implement custom lighting models, see Surface Shader Lighting Examples.

這個頁面上的表面着色器示例向您展示了如何使用内置的光照模型。有關如何實作自定義照明模型的例子,請參閱表面着色器照明示例。

Simple shader example

We’ll start with a very simple Shader and build up on that. Here’s a Shader that sets the surface color to “white”. It uses the built-in Lambert (diffuse) lighting model.

我們将從一個非常簡單的Shader 開始,并在此基礎上進行建構。這是一個表面顔色設定為“白色”的Shader 。它使用内置的朗伯Lambert (漫反射)光照模型。

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"
  }
           

Here’s how it looks like on a model with two Lights set up:

這看起來模型上有兩個燈光的樣子:

Surface Shader examples 表面着色器示例 - Unity Shader Reference 系列3

Texture

An all-white object is quite boring, so let’s add a Texture. We’ll add a Properties block to the Shader, so we get a Texture selector in our Material. Other changes are in bold below.

一個全白的物體是相當無聊的,是以讓我們添加一個紋理。我們将在材質中添加一個屬性塊,這樣我們就可以在材質中得到一個紋理選擇器。其他的變化在下面是粗體。

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"
  }
           
Surface Shader examples 表面着色器示例 - Unity Shader Reference 系列3

Normal mapping

Let’s add some normal mapping:

讓我們添加一些法線貼圖:

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"
  }
           
Surface Shader examples 表面着色器示例 - Unity Shader Reference 系列3

Rim Lighting 邊緣光照

Now, try to add some Rim Lighting to highlight the edges of a GameObject. We’ll add some emissive light based on the angle between surface normal and view direction. For that, we’ll use the built-in viewDir Surface Shader variable.

現在,試着添加一些邊緣光照來突出一個GameObject的邊緣。我們将根據表面法線和視角方向之間的夾角添加一些自發光。為此,我們将使用内置的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"
  }
           
Surface Shader examples 表面着色器示例 - Unity Shader Reference 系列3

Detail Texture 細節紋理

For a different effect, let’s add a Detail Texture that is combined with the base Texture. A Detail Texture usually uses the same UVs but different Tiling in the Material, so we need to use different input UV coordinates.

為了獲得不同的效果,讓我們添加一個與基本紋理相結合的細節紋理。在材質中一個細節紋理通常使用相同的UVs但不同的平鋪,是以我們需要使用不同的輸入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"
  }
           

Using a Texture checker does not always make much practical sense, but in this example it is used to illustrate what happens:

使用紋理檢查器并不總是有很實用的感覺,但在本例中,它用于說明發生了什麼:

Surface Shader examples 表面着色器示例 - Unity Shader Reference 系列3

Detail Texture in Screen Space 螢幕空間的細節紋理

A Detail Texture in screen space does not make practical sense for a soldier head model, but here it is used to illustrate how a built-in screenPos input might be used:

在螢幕空間中,一個細節紋理對于士兵頭部模型來說并沒有很實用的感覺,但是在這裡它被用來說明如何使用内置的screenPos輸入:

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"
  }
           

The normal mapping has been removed from the Shader above, just to make it shorter:

法線貼圖已經從上面的Shader中移除,隻是為了讓它更簡短:

Surface Shader examples 表面着色器示例 - Unity Shader Reference 系列3

Cubemap Reflection 立方體貼圖反射

Here’s a Shader that does cubemapped reflection using built-in worldRefl input. It’s very similar to built-in Reflective/Diffuse Shader:

這是一個使用内置的worldRefl 輸入來做的 cubemap 反射。它非常類似于内置的反射/漫射Shader:

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"
  }
           

Because it assigns the reflection color as Emission, we get a very shiny soldier:

因為它把反射顔色配置設定給自發光,我們得到了一個非常閃亮的士兵:

Surface Shader examples 表面着色器示例 - Unity Shader Reference 系列3

If you want to do reflections that are affected by normal maps, it needs to be slightly more involved: INTERNAL_DATA needs to be added to the Input structure, and WorldReflectionVector function used to compute per-pixel reflection vector after you’ve written the Normal output.

如果您想要進行受法線貼圖影響的反射,它需要稍微多一點的參與:INTERNAL_DATA 需要被添加到輸入結構中,以及在您寫入法線輸出之後可用于計算每個像素反射向量的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"
  }
           

Here’s a normal-mapped shiny soldier:

這是一個法線映射的發光士兵:

Surface Shader examples 表面着色器示例 - Unity Shader Reference 系列3

Slices via World Space Position 通過世界空間位置的切片

Here’s a Shader that “slices” the GameObject by discarding pixels in nearly horizontal rings. It does this by using the clip() Cg/HLSL function based on the world position of a pixel. We’ll use the built-in worldPos Surface Shader variable.

這裡有一個着色器,它通過在幾乎水準的環形上丢棄像素來“分割”遊戲對象。它通過使用 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"
  }
           
Surface Shader examples 表面着色器示例 - Unity Shader Reference 系列3

Normal Extrusion with Vertex Modifier 頂點調節器的法線擠壓

It is possible to use a “vertex modifier” function that will modify the incoming vertex data in the vertex Shader

. This can be used for things like procedural animation and extrusion along normals. Surface Shader compilation directive vertex:functionName is used for that, with a function that takes inout appdata_full parameter.

可以使用一個“頂點調節器”函數來修改頂點着色器中的傳入頂點資料。這可以用于像程式動畫和沿着法線擠壓的東西。表面着色器編譯指令vertex:functionName 是用來做這個的,它的函數接受inout appdatafull參數。

Here’s a Shader that moves vertices along their normals by the amount specified in the Material:

這是一個Shader ,它通過材質中指定的數量沿着法線來移動頂點:

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"
  }
           

Moving vertices along their normals makes a fat soldier:

沿着法線移動頂點位置渲染成一個胖士兵:

Surface Shader examples 表面着色器示例 - Unity Shader Reference 系列3

Custom data computed per-vertex 每個頂點的自定義資料

Using a vertex modifier function, it is also possible to compute custom data in a vertex Shader, which then will be passed to the Surface Shader function per-pixel. The same compilation directive vertex:functionName is used, but the function should take two parameters: inout appdata_full and out Input. You can fill in any Input member that is not a built-in value there.

使用頂點調節器函數,也可以在頂點着色器中計算自定義資料,然後将其傳遞給每個像素的表面着色器函數。同樣的編譯指令vertex:functionName,但是函數應該有兩個參數:inout appdatafull和out Input。您可以填寫任何不是内置值的輸入成員。

Note: Custom Input members used in this way must not have names beginning with ‘uv’ or they won’t work properly.

注意:以這種方式使用的自定義輸入成員不能以“uv”開頭,否則他們無法正常工作。

The example below defines a custom float3 customColor member, which is computed in a vertex function:

下面的例子定義了一個定制的float3 customColor成員,它是在一個頂點函數中計算的:

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"
  }
           

In this example customColor is set to the absolute value of the normal:

在本例中,customColor被設定為法線的絕對值:

Surface Shader examples 表面着色器示例 - Unity Shader Reference 系列3

More practical uses could be computing any per-vertex data that is not provided by built-in Input variables; or optimizing Shader computations. For example, it’s possible to compute Rim lighting at the GameObject’s vertices, instead of doing that in the Surface Shader per-pixel.

更實際的用途可能是計算每個頂點資料,而這些資料不是由内置的輸入變量提供的;或者優化着色計算。例如,在GameObject的頂點上計算邊緣光照是可能的,而不是在每個像素的表面着色。

Final Color Modifier 最終顔色調節器

It is possible to use a “final color modifier” function that will modify the final color computed by the Shader. The Surface Shader compilation directive finalcolor:functionName is used for this, with a function that takes Input IN, SurfaceOutput o, inout fixed4 color parameters.

可以使用“最終顔色調節器”函數來修改由着色器計算的最終顔色。表面着色器編譯指令finalcolor:functionName用于此目的,它的功能包括輸入Input IN、輸出SurfaceOutput o、inout fixed4 color參數。

Here’s a simple Shader that applies tint to the final color. This is different from just applying tint to the surface Albedo color: this tint will also affect any color that comes from Lightmaps, Light Probes and similar extra sources.

這裡有一個簡單的着色器,它對最終的顔色進行着色。這不同于隻對表面的反射率(Albedo )顔色進行着色:這種色調也會影響來自光照圖、光照探測器和類似的額外來源的任何顔色。

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"
  }
           
Surface Shader examples 表面着色器示例 - Unity Shader Reference 系列3

Custom Fog with Final Color Modifier 自定義霧的最終顔色調節器

A common case for using final color modifier (see above) would be implementing completely custom Fog in forward rendering. Fog needs to affect the final computed pixel Shader color, which is exactly what the finalcolor modifier does.

使用最終顔色調節器(見上文)的常見情況是在正向渲染中實作完全自定義的霧。霧需要影響最終計算的像素着色顔色,這正是finalcolor修飾符所做的。

Here’s a Shader that applies fog tint based on the distance from screen center. This combines the vertex modifier with the custom vertex data (fog) and the final color modifier. When used in the forward rendering additive pass, the Fog needs to fade to black. This example handles that and performs a check for UNITY_PASS_FORWARDADD.

這是一個根據螢幕中心的距離應用霧色的着色器。這将頂點調節器與自定義頂點資料(霧)和最終的顔色調節器結合在一起。當使用前向渲染additive 通道時,霧需要逐漸變黑。這個例子将處理并對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"
  }
           
Surface Shader examples 表面着色器示例 - Unity Shader Reference 系列3

Linear Fog 線性霧

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 貼花

Decals are commonly used to add details to Materials at run time (for example, bullet impacts). They are especially useful in deferred rendering, because they alter the GBuffer before it is lit, therefore saving on performance.

貼花通常用于在運作時向材質添加細節(例如,子彈的影響)。它們在延遲渲染中特别有用,因為它們在上色之前改變了GBuffer,是以節省了性能。

In a typical scenario, Decals should be rendered after the opaque objects and should not be shadow casters, as seen in the ShaderLab “Tags” in the example below.

在一個典型的場景中,Decals應該在不透明的對象之後渲染,而不應該有陰影投射,就像下面例子中的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
    }
}