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 등의 반환값을 갖는 형태로 작성된다.

참고 자료

It's only fair to share...Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedIn

The author

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

댓글 남기기