표면 셰이더 조명 예제
버텍스 및 프래그먼트 셰이더 작성

DX11/OpenGL Core 테셀레이션 지원 표면 셰이더

표면 셰이더는 DirectX 11/OpenGL Core GPU 테셀레이션을 다음과 같이 일부 지원합니다.

  • 테셀레이션은 tessellate:FunctionName 모디파이어(modifier)로 나타냅니다. 이 함수는 삼각형 에지와 내부 테셀레이션 팩터를 계산합니다.
  • 테셀레이션이 사용되는 경우 “버텍스 모디파이어”(vertex:FunctionName)가 테셀레이션 후에 도메인 셰이더에 생성된 각 버텍스에 대해 호출됩니다. 여기서 일반적으로 변위 매핑을 합니다.
  • 표면 셰이더는 옵션으로 퐁 테셀레이션(phong tessellation)을 계산하여 변위 매핑 없이도 모델 표면을 매끄럽게 만듭니다.

현재 테셀레이션 지원의 한계:

  • 삼각 도메인만 지원 - 사각형, 등치선 테셀레이션은 지원하지 않습니다.
  • 테셀레이션을 사용하는 경우 셰이더가 셰이더 모델 4.6 타겟으로 자동 컴파일되어 구형 그래픽스 타겟에서 실행되는 것을 막습니다.

버텍스 모디파이어에서 GPU 테셀레이션을 사용하지 않을 때 예제

다음 예제는 테셀레이션을 사용하지 않고 변위 매핑을 하는 표면 셰이더를 보여줍니다. 이 셰이더는 단순히 변위 맵에서 오는 양에 기반하여 버텍스의 노멀을 따라 버텍스를 움직입니다.

    Shader "Tessellation Sample" {
            Properties {
                _MainTex ("Base (RGB)", 2D) = "white" {}
                _DispTex ("Disp Texture", 2D) = "gray" {}
                _NormalMap ("Normalmap", 2D) = "bump" {}
                _Displacement ("Displacement", Range(0, 1.0)) = 0.3
                _Color ("Color", color) = (1,1,1,0)
                _SpecColor ("Spec color", color) = (0.5,0.5,0.5,0.5)
            }
            SubShader {
                Tags { "RenderType"="Opaque" }
                LOD 300
            
                CGPROGRAM
                #pragma surface surf BlinnPhong addshadow fullforwardshadows vertex:disp nolightmap
                #pragma target 4.6

                struct appdata {
                    float4 vertex : POSITION;
                    float4 tangent : TANGENT;
                    float3 normal : NORMAL;
                    float2 texcoord : TEXCOORD0;
                };

                sampler2D _DispTex;
                float _Displacement;

                void disp (inout appdata v)
                {
                    float d = tex2Dlod(_DispTex, float4(v.texcoord.xy,0,0)).r * _Displacement;
                    v.vertex.xyz += v.normal * d;
                }

                struct Input {
                    float2 uv_MainTex;
                };

                sampler2D _MainTex;
                sampler2D _NormalMap;
                fixed4 _Color;

                void surf (Input IN, inout SurfaceOutput o) {
                    half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
                    o.Albedo = c.rgb;
                    o.Specular = 0.2;
                    o.Gloss = 1.0;
                    o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));
                }
                ENDCG
            }
            FallBack "Diffuse"
        }

위 셰이더는 표준에 가깝습니다.

  • disp 버텍스 모디파이어는 변위 맵을 샘플링하고 해당 노멀을 따라 버텍스를 움직입니다.
  • 이 셰이더는 디폴트 appdata_full 대신 커스텀 “버텍스 데이터 입력” 구조(appdata)를 사용합니다. 아직은 필요하지 않지만 테셀레이션에서는 가능한 한 작은 구조를 사용하는 편이 훨씬 효율적입니다.
  • 여기서는 버텍스 데이터에 두 번째 UV 좌표가 없기 때문에 라이트맵을 제외하기 위해 nolightmap 지시자를 추가합니다.

아래 이미지에는 이 셰이더가 적용된 단순한 게임 오브젝트가 나와 있습니다.


고정 크기 테셀레이션

모델의 면들이 화면상에서 거의 같은 크기인 경우 고정된 양의 테셀레이션을 메시에 추가(전체 메시에 대해 동일한 테셀레이션 레벨 추가)하십시오.

다음 예제 스크립트는 고정된 양의 테셀레이션을 적용합니다.

    Shader "Tessellation Sample" {
            Properties {
                _Tess ("Tessellation", Range(1,32)) = 4
                _MainTex ("Base (RGB)", 2D) = "white" {}
                _DispTex ("Disp Texture", 2D) = "gray" {}
                _NormalMap ("Normalmap", 2D) = "bump" {}
                _Displacement ("Displacement", Range(0, 1.0)) = 0.3
                _Color ("Color", color) = (1,1,1,0)
                _SpecColor ("Spec color", color) = (0.5,0.5,0.5,0.5)
            }
            SubShader {
                Tags { "RenderType"="Opaque" }
                LOD 300
            
                CGPROGRAM
                #pragma surface surf BlinnPhong addshadow fullforwardshadows vertex:disp tessellate:tessFixed nolightmap
                #pragma target 4.6

                struct appdata {
                    float4 vertex : POSITION;
                    float4 tangent : TANGENT;
                    float3 normal : NORMAL;
                    float2 texcoord : TEXCOORD0;
                };

                float _Tess;

                float4 tessFixed()
                {
                    return _Tess;
                }

                sampler2D _DispTex;
                float _Displacement;

                void disp (inout appdata v)
                {
                    float d = tex2Dlod(_DispTex, float4(v.texcoord.xy,0,0)).r * _Displacement;
                    v.vertex.xyz += v.normal * d;
                }

                struct Input {
                    float2 uv_MainTex;
                };

                sampler2D _MainTex;
                sampler2D _NormalMap;
                fixed4 _Color;

                void surf (Input IN, inout SurfaceOutput o) {
                    half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
                    o.Albedo = c.rgb;
                    o.Specular = 0.2;
                    o.Gloss = 1.0;
                    o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));
                }
                ENDCG
            }
            FallBack "Diffuse"
        }

위 예제에서 tessFixed 테셀레이션 함수는 네 개의 테셀레이션 팩터(삼각형 모서리 각각에 대한 팩터 3개, 삼각형 내부에 대한 팩터 1개)를 단일 float4 값으로 반환합니다.

예제에서는 머티리얼 프로퍼티에 설정된 상수 값을 반환합니다.


거리 기반 테셀레이션

카메라로부터의 거리에 기반하여 테셀레이션 레벨을 변경할 수도 있습니다. 예를 들어 다음과 같이 두 개의 값을 정의할 수 있습니다.

  • 테셀레이션 레벨이 최대일 때의 거리(예: 10m)입니다.
  • 테셀레이션 레벨이 점진적으로 감소할 때의 거리(예: 20m)입니다.
    Shader "Tessellation Sample" {
            Properties {
                _Tess ("Tessellation", Range(1,32)) = 4
                _MainTex ("Base (RGB)", 2D) = "white" {}
                _DispTex ("Disp Texture", 2D) = "gray" {}
                _NormalMap ("Normalmap", 2D) = "bump" {}
                _Displacement ("Displacement", Range(0, 1.0)) = 0.3
                _Color ("Color", color) = (1,1,1,0)
                _SpecColor ("Spec color", color) = (0.5,0.5,0.5,0.5)
            }
            SubShader {
                Tags { "RenderType"="Opaque" }
                LOD 300
            
                CGPROGRAM
                #pragma surface surf BlinnPhong addshadow fullforwardshadows vertex:disp tessellate:tessDistance nolightmap
                #pragma target 4.6
                #include "Tessellation.cginc"

                struct appdata {
                    float4 vertex : POSITION;
                    float4 tangent : TANGENT;
                    float3 normal : NORMAL;
                    float2 texcoord : TEXCOORD0;
                };

                float _Tess;

                float4 tessDistance (appdata v0, appdata v1, appdata v2) {
                    float minDist = 10.0;
                    float maxDist = 25.0;
                    return UnityDistanceBasedTess(v0.vertex, v1.vertex, v2.vertex, minDist, maxDist, _Tess);
                }

                sampler2D _DispTex;
                float _Displacement;

                void disp (inout appdata v)
                {
                    float d = tex2Dlod(_DispTex, float4(v.texcoord.xy,0,0)).r * _Displacement;
                    v.vertex.xyz += v.normal * d;
                }

                struct Input {
                    float2 uv_MainTex;
                };

                sampler2D _MainTex;
                sampler2D _NormalMap;
                fixed4 _Color;

                void surf (Input IN, inout SurfaceOutput o) {
                    half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
                    o.Albedo = c.rgb;
                    o.Specular = 0.2;
                    o.Gloss = 1.0;
                    o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));
                }
                ENDCG
            }
            FallBack "Diffuse"
        }

여기에서 테셀레이션 함수는 테셀레이션 전 세 개의 삼각형 꼭짓점에 대한 버텍스 데이터를 세 개의 파라미터로 받습니다.

Unity는 이를 통해 버텍스 포지션에 따라 달라지는 테셀레이션 레벨을 계산합니다.

예제에는 빌트인 헬퍼 파일 Tessellation.cginc가 포함되어 있고 이 파일에서 UnityDistanceBasedTess 함수를 호출하여 모든 작업을 수행합니다. 이 함수는 각 버텍스에서 카메라까지의 거리를 계산하고 최종 테셀레이션 팩터를 파생합니다.


모서리 길이 기반 테셀레이션

순수 거리 기반 테셀레이션은 삼각형 크기가 상당히 유사한 경우에만 효과적입니다. 위 이미지에서 작은 삼각형이 포함된 게임 오브젝트에는 테셀레이션이 지나치게 적용되었고 큰 삼각형이 포함된 게임 오브젝트에는 테셀레이션이 충분히 적용되지 않았습니다.

이런 문제를 해결하기 위한 한 가지 방법은 화면상의 삼각형 모서리 길이에 기반하여 테셀레이션 레벨을 계산하는 것입니다. Unity는 더 큰 테셀레이션 팩터를 더 긴 모서리에 적용합니다.

    Shader "Tessellation Sample" {
            Properties {
                _EdgeLength ("Edge length", Range(2,50)) = 15
                _MainTex ("Base (RGB)", 2D) = "white" {}
                _DispTex ("Disp Texture", 2D) = "gray" {}
                _NormalMap ("Normalmap", 2D) = "bump" {}
                _Displacement ("Displacement", Range(0, 1.0)) = 0.3
                _Color ("Color", color) = (1,1,1,0)
                _SpecColor ("Spec color", color) = (0.5,0.5,0.5,0.5)
            }
            SubShader {
                Tags { "RenderType"="Opaque" }
                LOD 300
            
                CGPROGRAM
                #pragma surface surf BlinnPhong addshadow fullforwardshadows vertex:disp tessellate:tessEdge nolightmap
                #pragma target 4.6
                #include "Tessellation.cginc"

                struct appdata {
                    float4 vertex : POSITION;
                    float4 tangent : TANGENT;
                    float3 normal : NORMAL;
                    float2 texcoord : TEXCOORD0;
                };

                float _EdgeLength;

                float4 tessEdge (appdata v0, appdata v1, appdata v2)
                {
                    return UnityEdgeLengthBasedTess (v0.vertex, v1.vertex, v2.vertex, _EdgeLength);
                }

                sampler2D _DispTex;
                float _Displacement;

                void disp (inout appdata v)
                {
                    float d = tex2Dlod(_DispTex, float4(v.texcoord.xy,0,0)).r * _Displacement;
                    v.vertex.xyz += v.normal * d;
                }

                struct Input {
                    float2 uv_MainTex;
                };

                sampler2D _MainTex;
                sampler2D _NormalMap;
                fixed4 _Color;

                void surf (Input IN, inout SurfaceOutput o) {
                    half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
                    o.Albedo = c.rgb;
                    o.Specular = 0.2;
                    o.Gloss = 1.0;
                    o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));
                }
                ENDCG
            }
            FallBack "Diffuse"
        }

이 예제에서는 Tessellation.cginc 에서 UnityEdgeLengthBasedTess 함수를 호출하여 모든 작업을 수행합니다.

성능 향상을 위해 패치 절두체 컬링을 수행하는 UnityEdgeLengthBasedTessCull 함수를 대신 호출하십시오. 이렇게 하면 셰이더가 성능을 좀 더 소모하지만 카메라 뷰를 벗어나는 메시 부분에 대한 GPU 작업을 상당히 줄일 수 있습니다.


퐁 테셀레이션

퐁 테셀레이션은 세부 분할된 면의 포지션을 수정하여 그 결과로 얻어진 표면이 메시 노멀을 다소 따르도록 해 줍니다. 이 방식은 폴리곤이 적은 메시를 더 매끄럽게 만드는 데 상당히 효과적인 방법입니다.

Unity의 표면 셰이더는 tessphong:VariableName 컴파일 지시자를 사용하여 퐁 테셀레이션을 자동으로 계산할 수 있습니다. 아래는 예제 셰이더입니다.

    Shader "Phong Tessellation" {
            Properties {
                _EdgeLength ("Edge length", Range(2,50)) = 5
                _Phong ("Phong Strengh", Range(0,1)) = 0.5
                _MainTex ("Base (RGB)", 2D) = "white" {}
                _Color ("Color", color) = (1,1,1,0)
            }
            SubShader {
                Tags { "RenderType"="Opaque" }
                LOD 300
            
                CGPROGRAM
                #pragma surface surf Lambert vertex:dispNone tessellate:tessEdge tessphong:_Phong nolightmap
                #include "Tessellation.cginc"

                struct appdata {
                    float4 vertex : POSITION;
                    float3 normal : NORMAL;
                    float2 texcoord : TEXCOORD0;
                };

                void dispNone (inout appdata v) { }

                float _Phong;
                float _EdgeLength;

                float4 tessEdge (appdata v0, appdata v1, appdata v2)
                {
                    return UnityEdgeLengthBasedTess (v0.vertex, v1.vertex, v2.vertex, _EdgeLength);
                }

                struct Input {
                    float2 uv_MainTex;
                };

                fixed4 _Color;
                sampler2D _MainTex;

                void surf (Input IN, inout SurfaceOutput o) {
                    half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
                    o.Albedo = c.rgb;
                    o.Alpha = c.a;
                }

                ENDCG
            }
            FallBack "Diffuse"
        }

다음은 일반 셰이더(상단 행)와 퐁 테셀레이션을 사용한 셰이더(하단 행)의 차이를 비교합니다. 변위 매핑이 없어도 표면이 더 둥글게 변한 것을 볼 수 있습니다.


  • 2018–03–20 편집 리뷰를 거쳐 페이지 수정됨
  • 2018.1에서 금속용 테셀레이션(Tessellation for Metal) 추가됨
표면 셰이더 조명 예제
버텍스 및 프래그먼트 셰이더 작성