Unity/ 셰이더/ Surface Shader 작성법

Surface Shader

Vertex-Fragment Shader보다 좀 더 높은 수준에서 작성되는 셰이더. Vertex-Fragment 에서 번거롭고 복잡하게 작성해야 하는 것 –라이팅 계산 등– 들을 프로그램 내부에서 자동으로 처리하고, 프로그래머는 직관적인 속성들만 제어하면 되도록 정의되어 있다. –물론 그 자동으로 처리되는 부분을 커스터마이징 하고자 한다면 할 수 있다.

Surface Shader는 유니티에서 주로 사용하는 셰이더이다. 처음 셰이더 파일을 만들면 Default 로 들어가 있는 코드가 서피스 셰이더로 작성되어 있음.

파이프라인


http://www.alanzucconi.com/2015/06/17/surface-shaders-in-unity3d/

Surface Shader에서는 주로 Surface Function을 이용하여 재질을 제어하는 코드를 작성하지만, 필요하다면 Vertex를 제어하거나 Lighting을 제어하는 코드도 만들 수 있다. 위 이미지는 그 3가지 프로세스의 순서를 나타내고 있다.

Unity에서의 작성법

SubShader {
	CGPROGRAM
	#pragma surface surf Lambert
	
	struct Input {
		float4 color : COLOR;
	};

	void surf (Input IN, inout SurfaceOutput o) {
		o.Albedo = 1;
	}
	ENDCG 
}

가장 기본적인 형태의 Surface Shader 코드 형태. 재질에 대한 속성만 정의해주면 효과가 적용되기 때문에 Vertex-Fragment Shader에 비해 매우 간결한 구성을 갖고 있다.

Vertex-Fragment와 달리 pass가 없는게 특징인데, 만일 여러개의 pass를 그리고 싶다면 CGPROGRAM-ENDCG 자체를 여러번 작성하면 된다.

#pragma 다음에 surface가 나오면 Surface Shader를 사용한다는 의미가 된다. surface 다음에 나오는 surf는 Surface Function의 이름이 된다. 자신의 마음대로 써도 무방하지만 일반적으로는 surf 를 사용한다. 마치 vertex function의 이름은 vert, fragment function의 이름은 frag라고 쓰는 것과 비슷함.

surface 함수에서 받을 인자는 구조체 형식으로 선언한다. 위 코드의 Input 구조체에서 필요한 값들을 정의해 두면 surface 함수에서 해당 값을 받아 사용할 수 있다.

vertex-fragment 함수가 최종 결과값을 return 하는 것과 달리 surface 함수는 inout을 이용하여 값을 참조 형식으로 사용한다.

#pragma 맨 마지막에 써 있는 Lambert는 현재 Surface Shader에서 사용할 라이팅 방식을 의미한다. 미리 정의되어 있는 라이팅을 사용한다면 라이팅과 관련한 별다른 코드를 작성하지 않고도 라이팅 효과를 얻을 수 있다. 커스텀 라이팅을 사용할 경우 Labert 대신 커스텀 라이팅을 처리할 함수의 이름을 쓰면 된다.

SubShader {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf Lambert vertex:vert

      sampler2D _MainTex;
      float _Amount;

      struct Input {
          float2 uv_MainTex;
      };

      void vert (inout appdata_full v) {
          v.vertex.xyz += v.normal * _Amount;
      }

      void surf (Input IN, inout SurfaceOutput o) {
          o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
      }
      ENDCG
}

Surface Shader에서 Vertex를 제어해야 하는 경우 #pragma 부분에 vertex를 제어하는 함수를 선언하면 된다. vertex는 버텍스 제어를 한다는 의미고 : 뒤에 사용하는 이름이 vertex function의 이름이 된다.

Vertex Shader와 달리 Suraface Shader의 vertex 함수는 최종 결과값을 inout을 이용하여 참조 형식으로 사용한다.

vertex 함수에서 받는 appdata_full은 유니티 내부에 정의되어 있는 형식이다.

SubShader {
    Tags { "RenderType"="Opaque" }
    LOD 200
    
    CGPROGRAM
    #pragma surface surf Phong
    
    sampler2D _MainTex;
    float4 _MainTint;

    struct Input 
    {
        float2 uv_MainTex;
    };

    void surf (Input IN, inout SurfaceOutput o) 
    {
        half4 c = tex2D (_MainTex, IN.uv_MainTex) * _MainTint;
        o.Albedo = c.rgb;
        o.Alpha = c.a;
    }
    
    inline fixed4 LightingPhong (SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten)
    {
        // 라이팅 처리
        return c;
    }
    ENDCG
}

커스텀 라이팅을 사용하려면 #pragma 영역에 사용하고자 하는 커스텀 라이팅 함수의 이름을 쓰면 된다. 그런데 여기에 쓴 커스텀 라이팅 함수의 이름이 바로 라이팅 함수의 이름이 되지는 않고 ‘Lighting+(라이팅함수이름)’ 처럼 앞에 Lighting이 붙는 형식으로 함수 이름이 정해진다.

Surface Shader 파이프라인 상 라이팅은 가장 마지막에 그려지기 때문에 surface 함수에서 사용한 SurfaceOutput을 받아 처리하게 된다. surface 함수와 vertex 함수와 달리 float4, half4, fixed4 등의 반환값을 갖는 형태로 작성된다.

참고 자료

[ssba]

The author

지성을 추구하는 디자이너/ suyeongpark@abyne.com

댓글 남기기