Version: 2017.1
Ejemplos de Iluminación del Surface Shader
Escribiendo Vertex y fragment shaders

Surface Shaders con Teselación DX11 / OpenGL Core

Los Surface Shaders tienen algún soporte para Teselación del GPU para DirectX 11 / OpenGL Core GPU. La idea es:

  • La Teselación es indicada por el modificador tessellate:FunctionName. Esa función computa el borde del triángulo y dentro de los factores de teselación.
  • Cuándo la teselación es utilizada, “vertex modifier” (vertex:FunctionName) es invocada después de la teselación, para cada vértice generado en el sombreador de dominio. Aquí típicamente usted haría displacement mapping.
  • Los Surface Shaders pueden opcionalmente computar phong tessellation para suavizar la superficie del modelo incluso sin displacement mapping.

Limitaciones actuales del soporte de Teselación:

  • Solamente el dominio del triángulo - ninguna teselación quads, ni isoline.
  • Cuando la teselación es utilizada, el shader es automáticamente compilando a un Shader Model 5.0, que significa que solo va a funcionar con DX11.

Ninguna teselación GPU, desplazamiento en el modificador de vértice(Vertex modifier)

Comencemos con un surface shader que no haga ningún displacement mapping sin utilizar teselación. Simplemente mueve vértices a lo largo de sus normales basados en la cantidad que viene del displacement map:

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

El sombreador de arriba es estándar, puntos de interes:

  • El modificador de Vértice(Vertex modifier) disp toma muestras del displacement map y mueve vértices a lo largo de sus normales.
  • Utiliza una estructura personalizada “vertex data input” (appdata) en vez del predeterminado appdata_full. Esto no se necesita todavía, pero es más eficiente para la teselación utilizar la estructura más pequeño posible.
  • Ya que nuestra información del vértice no tiene una segunda coordenada UV, nosotros agregamos la directiva nolightmap para exluir los lightmaps.

He aquí cómo algunos objetos simples se verían con este sombreador:

Cantidad fija de tessellation

Agreguemos una cantidad fija de teselación, i.e. el mismo nivel de teselación para el entero mesh. Este acercamiento es adecuado si la cara de su modelo son del mismo tamaño en la pantalla. Algún script puede luego cambiar el nivel de teselación del código, basado en la distancia de la cámara.

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

La función de teselación, tessFixed en nuestro sombreador, devuelve cuatro factores de teselación como un valor float4 sencillo: factores de árbol para cada borde del triángulo, y un factor para el interior del triángulo. Aquí, nosotros simplemente devolvemos un valor constante que es establecido en las propiedades del material.

Teselación basado en la distancia

Nosotros también podemos cambiar el nivel de teselación basado en las distancias desde la cámara. Por ejemplo, nosotros podemos definir dos valores de distancia; la distancia en la cuál la teselación está en su máximo (digamos, 10 metros), y la distancia hacia qué nivel de teselación gradualmente decrece (digamos, 20 metros).

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

Aquí la función de teselación toma tres parámetros; la información de los vértices de las tres esquinas de los triángulos antes de la teselación. Esto es necesitado para computar los niveles de teselación, que dependen en la posición del vértice ahora. Nosotros incluimos un archivo de ayuda integrado Tessellation.cginc y llamamos la función UnityDistanceBasedTess para hacer todo el trabajo. Esa función computa distancias de cada vértice a la cámara y deriva los factores finales de la teselación.

Teselación basada en la longitud de los bordes.

Teselación puramente basada en la distancia es solamente buena cuando los tamaños del triángulo son bastantes similares. En la imagen de arriba, usted puede ver que los objetos que tienen los triángulos pequeños están muy teselados, mientras los objetos que tienen triángulos grandes no están suficientemente teselados.

En vez, los niveles de teselación pueden ser computados basados en la longitud del borde del triángulo en la pantalla - entre más largo el borde, más grande es el factor de teselación que debería ser aplicado.

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

Aquí nuevamente, nosotros simplemente llamamos la función UnityEdgeLengthBasedTess de Tessellation.cginc para hacer todo el trabajo.

Por razones de rendimiento, es aconsejable llamar la función UnityEdgeLengthBasedTessCull en vez, que hará patch frustum culling. Esto hace que el sombreador sea más costos, pero ahorra mucho trabajo de GPU para partes de meshes que están afuera de la vista de la cámara.

Phong Tessellation

Phong Tessellation modifica las posiciones de las caras divididas para que la superficie resultante siga las normales del mesh un poco. Es una manera efectiva de hacer que los meshes de bajo-poly se vuelvan más suave.

Los Surface Shader de Unity pueden computar teselación Phong automáticamente utilizando la directiva de compilación tessphong:VariableName . He aquí un ejemplo del sombreador:

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

He aquí una comparación entre el sombreador regular (fila arriba) y uno que utiliza la teselación Phon (fila debajo). Usted puede ver eso incluso sin ningún displacement mapping, la superficie se vuelve más redonda.

Ejemplos de Iluminación del Surface Shader
Escribiendo Vertex y fragment shaders