서피스 쉐이더 작성
표면 쉐이더에서 사용자 정의 라이팅 모델

표면 쉐이더의 예

Surface Shaders](SL-SurfaceShaders.html)의 예입니다. 다음은 내장 라이팅 모델 사용에 집중하고 있습니다. 사용자 정의 라이팅 모델의 구현 방법은 Surface Shader Lighting Examples에 있습니다.

Simple

아주 간단한 쉐이더를 작성 해 볼것입니다. 다음은 표면의 색상을 단순히 흰색 지정하는 쉐이더입니다. 이것은 내장 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"
  }

다음은 두 개의 lights가 설정된 모델에서의 모습입니다.

텍스처

새하얀 오브젝트는 꽤 시시하니까 텍스처를 추가합시다. Properties block을 이 쉐이더에 추가하여, 메테리얼에서 텍스처 셀렉터를 가져옵니다. 기타 변경 사항은 아래에 굵게 표시되어 있습니다.

  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)

이번에는 림 라이팅을 추가하여 오브젝트의 연결을 강조합니다. 표면의 법선과 뷰 방향 사이의 각도에 따라 방사광을 추가합니다. 그것을 위해 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"
  }

Detail Texture

다른 효과를 주기 위해, 기본 텍스처와 결합하는 세부 텍스처(Detail Texture)를 추가합시다. 세부 텍스처는 같은 UV를 사용하지만 일반적으로 메테리얼에서의 타일링이 다르기 때문에 다른 입력 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"
  }

검사기 텍스처를 사용해도 별로 의미는 없지만, 무슨 일이 일어나는지 알 수 있습니다.

화면 공간의 세부 텍스처

화면 공간에서의 세부 텍스처는 어떻게 됩니까? 병사의 머리 모델에는 별로 의미가 없지만, 내장 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"
  }

단축하기 위하여 위 쉐이더에서 법선 매핑을 삭제했습니다.

큐브 맵 반사

다음은 내장 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으로 반사 색상을 할당하기 때문에 매우 빛나는 병사를 얻을 수 있습니다.

법선 맵의 영향을 받는 반사를 실행하고 싶은 경우, 약간 관여시킬 필요가 있습니다. 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"
  }

이것은 법선 매핑된 빛나고 있는 병사입니다 :

월드 공간상의 위치를 ​​통한 슬라이스

다음은 수평에 가까운 고리 안에 픽셀을 파기함으로써 오브젝트를 슬라이스 하는 쉐이더입니다. 이것은 픽셀 월드 위치에 기초하여 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"
  }

Normal Extrusion 과 Vertex Modifier

정점 쉐이더에서 들어오는 정점 데이터를 수정하는 vertex modifier 함수를 사용할 수 있습니다. 이것은 절차적 애니메이션과 법선을 따라 돌출하는 등에 사용할 수 있습니다. 따라서 표면 쉐이더 컴파일 지시문 vertex:functionName이 사용되지만, 이것은 inout appdata_full 파라미터를 취하는 함수입니다.

다음은, 메테리얼에서 지정한 양만큼 법선을 따라 정점을 움직이는 쉐이더입니다.

  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:functionName이 사용되지만, 이 함수는 inout appdata_full out Input의 두 개의 파라미터를 취해야 합니다. 여기에서 기본 값이 아닌 입력 멤버를 기입할 수 있습니다.

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

아래의 예에서는 사용자 정의 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:

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 object’s vertices, instead of doing that in the surface shader per-pixel.

Final Color Modifier

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

Here’s a simple shader that applies tint to final color. This is different from just applying tint to surface Albedo color: this tint will also affect any color that came from lightmaps, light probes and similar extra sources.

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

Custom Fog with Final Color Modifier

Common use case for 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.

Here’s a shader that applies fog tint based on distance from screen center. This combines both the vertex modifier with custom vertex data (fog) and final color modifier. When used in forward rendering additive pass, Fog needs to fade to black color, and this example handles that as well with a check for 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 = mul (UNITY_MATRIX_MVP, 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"
  }

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(mul (UNITY_MATRIX_MV, 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"
}

데칼(Decal)

Decal are commonly used to add details to materials at runtime (bullets impacts are for example). They are a great tool especially in deferred rendering as they alter the GBuffer before it is lit, thus saving on performance.

In a typical scenario Decal should probably be rendered after the opaque objects and should not be a shadow caster as seen in the shaderlab “Tags” in the example below.

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
  } 
서피스 쉐이더 작성
표면 쉐이더에서 사용자 정의 라이팅 모델