Version: 2020.3
표면 셰이더 및 렌더링 경로
표면 셰이더의 커스텀 조명 모델

표면 셰이더 예제

이 페이지의 표면 셰이더 예제는 빌트인 조명 모델을 사용하는 방법을 보여줍니다. 커스텀 조명 모델을 구현하는 방법에 대한 예제는 표면 셰이더 조명 예제를 참조하십시오.

In the Built-in Render Pipelne, Surface Shaders are a streamlined way of writing shaders that interact with lighting.

렌더 파이프라인 호환성

Feature name 빌트인 렌더 파이프라인 유니버설 렌더 파이프라인(URP) 고해상도 렌더 파이프라인(HDRP) Custom SRP
Surface Shaders 지원 No

For a streamlined way of creating Shader objects in URP, see Shader Graph.
No

For a streamlined way of creating Shader objects in HDRP, see Shader Graph.
지원 안 함

Simple shader example

우선 아주 간단한 셰이더에서 시작해서 점점 발전시켜 보겠습니다. 아래의 셰이더는 표면 컬러를 “흰색”으로 설정합니다. 이 셰이더는 빌트인 램버트(디퓨즈) 조명 모델을 사용합니다.

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

다음은 두 개의 광원이 설정된 모델에서 셰이더가 어떻게 보이는지를 나타냅니다.

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.

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

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

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

다른 효과를 추가하기 위해 베이스 텍스처와 결합된 디테일 텍스처를 추가합시다. 디테일 텍스처는 동일한 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"
    }

텍스처 체커 사용이 늘 적합한 건 아니지만 이 예제에서는 무슨 일이 일어나는지 보기 위해서 사용하겠습니다.

Detail Texture in Screen Space

스크린 공간의 디테일 텍스처에 대해 알아보겠습니다. 이 텍스처는 병사의 머리 모델에는 별로 적합하지 않지만 여기선 빌트인 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"
    }

더 간단하게 만들기 위해 위 셰이더에서 노멀 매핑을 제거했습니다.

Cubemap Reflection

다음은 빌트인 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_DATAInput 구조에 추가되어야 하고 Normal 출력을 사용한 후에 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"
    }

다음은 노멀맵 처리된 빛나는 병사 모델입니다.

Slices via World Space Position

다음 셰이더는 수평 방향의 고리 형태로 픽셀을 제거하여 게임 오브젝트를 “슬라이스”합니다. 셰이더는 픽셀의 월드 포지션에 기반한 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 with Vertex Modifier

버텍스 셰이더에 입력된 버텍스 데이터를 수정하는 “버텍스 모디파이어(vertex modifier)” 기능을 사용할 수 있습니다. 이는 순차적 애니메이션 노멀을 따라가는 익스트루전(extrusion) 등에 사용할 수 있습니다. 이를 위해 표면 셰이더 컴파일 지시자 vertex:functionNameinout 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"
    }

노멀을 따라 버텍스를 움직이면 병사의 얼굴이 부풉니다.

Custom data computed per-vertex

버텍스 모디파이어 기능을 사용하면 버텍스 셰이더에서 커스텀 데이터를 계산할 수 있으며 그 후 이를 픽셀마다 표면 셰이더 함수로 넘길 수 있습니다. 컴파일 지시자 vertex:functionName를 동일하게 사용하지만 이 때 함수는 inout appdata_fullout Input이라는 두 개의 파라미터를 받습니다. 입력의 멤버 중 빌트인 값이 없는 어느 것을 채워도 됩니다.

참고: 이 방식으로 사용되는 커스텀 입력 멤버는 절대로 ’uv’로 시작하는 이름을 사용해서는 안 되며 만약 그럴 경우 제대로 동작하지 않습니다.

아래 예제에서는 커스텀 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"
    }

이 예제에서 customColor는 노멀의 절대값으로 설정됩니다.

더 실용적인 사용법의 예로 빌트인 입력 변수에서 제공되지 않는 버텍스당 데이터를 연산이나 셰이더 연산 최적화 등이 있습니다. 예를 들어 표면 셰이더에서 픽셀당으로 계산하는 대신 게임 오브젝트의 버텍스에서 림 조명을 연산할 수 있습니다.

Final Color Modifier

셰이더가 계산한 최종 컬러를 수정하는 “최종 컬러 모디파이어(final color modifier)” 기능을 사용할 수 있습니다. 이를 위해 표면 셰이더 컴파일 지시자 finalcolor:functionNameInput IN, SurfaceOutput o, inout fixed4 color를 파라미터로 받는 함수를 사용합니다.

다음은 최종 컬러에 틴트를 적용하는 간단한 셰이더입니다. 이는 표면 알베도 컬러에만 틴트를 적용하는 것과는 다릅니다. 이 틴트는 라이트맵, 라이트 프로브 및 유사한 별도 소스에서 나오는 모든 컬러에 영향을 줍니다.

  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

최종 컬러 모디파이어(위 참조)의 일반적인 용도는 포워드 렌더링에서 완전한 커스텀 안개를 구현합니다. 안개는 최종 계산된 픽셀 셰이더 컬러에 영향을 주어야 하며, 이 기능이 바로 finalcolor 모디파이어의 기능입니다.

아래 셰이더는 화면 중앙으로부터의 거리에 기반하여 안개 틴트를 적용합니다. 이는 커스텀 버텍스 데이터(fog)가 있는 버텍스 모디파이어와 최종 컬러 모디파이어를 결합합니다. 포워드 렌더링 추가 패스에서 사용되면 안개는 검은색으로 페이드되어야 하며 아래 예제에서는 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"
    }

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

데칼은 흔히 런타임에서 머티리얼에 세부 사항을 추가하기 위해 사용합니다(예: 총알 임팩트). 특히 디퍼드 렌더링에서 훌륭한 툴로 사용되는데 조명 받기 전에 GBuffer를 변경하여 성능을 유지하기 때문입니다.

일반적인 시나리오에서 데칼은 불투명한 오브젝트 다음에 렌더링되며 아래 예제의 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
    }
}
표면 셰이더 및 렌더링 경로
표면 셰이더의 커스텀 조명 모델
Copyright © 2020 Unity Technologies
优美缔软件(上海)有限公司 版权所有
"Unity"、Unity 徽标及其他 Unity 商标是 Unity Technologies 或其附属机构在美国及其他地区的商标或注册商标。其他名称或品牌是其各自所有者的商标。
公安部备案号:
31010902002961