Cg Programming/Unity/Displacement Maps

From Wikibooks, open books for an open world
Jump to navigation Jump to search
Irregular shapes like this asteroid can be created using a spherical mesh with vertices that are moved by a displacement map.

This tutorial introduces displacement maps as an example of an application of texture lookups in vertex shaders.

This tutorial requires some knowledge about texture mapping as described in Section “Textured Spheres”.

Texture Lookups in Vertex Shaders[edit | edit source]

Most tutorials in this wikibook use texture lookups only in fragment shaders because texture lookups in vertex shaders are a Shader Model 3.0 feature, i.e., it is not supported by all GPUs (see Unity's description of platform-specific rendering differences).

However, displacement mapping requires a lookup of a texture value for each vertex, which is used to displace, i.e., move, each vertex to a new position. For technical reasons, we cannot use the standard tex2d command. Instead we have to use the command tex2dlod with a four-dimensional vector specifying the texture coordinates where the 4th component is 0. (This makes sure that we always read the value from the finest mipmap level and we don't request an automatic computation of the mipmap level, which is impossible in a vertex shader.) The command could look like this:

      float4 dispTexColor = tex2Dlod(_DisplacementTex, float4(i.texcoord.xy, 0.0, 0.0));

where i.texcoord.xy are the two texture coordinates.

Displacement of Vertices[edit | edit source]

The example below converts the resulting color dispTexColor to a gray value and scales it with a user-specified uniform _MaxDisplacement:

      float displacement = dot(float3(0.21, 0.72, 0.07), dispTexColor.rgb) * _MaxDisplacement;

This displacement value is then used to move each vertex along its surface normal vector, i.e., we add the vertex position in object coordinates (i.vertex in the example) to the surface normal vector (i.normal) multiplied with the displacement value.

      float4 newVertexPos = i.vertex + float4(i.normal * displacement, 0.0);

Note that i.normal is a three-dimensional vector; thus, we have to append a 0.0 coordinate to form a four-dimensional vector before we can add it to the vertex position.

Shader Code[edit | edit source]

The rest of the code applies the standard vertex transformations and colors the surface with a unlit texture map. Make sure to use it on meshes with relatively many vertices - otherwise the displaced surface will appear quite "blocky."

Shader "Vertex Displacement" {
   Properties {
      _MainTex ("Main Texture", 2D) = "white" {}
      _DisplacementTex ("Displacement Texture", 2D) = "white" {}
      _MaxDisplacement ("Max Displacement", Float) = 1.0
   }
   SubShader {
      Pass {    
         CGPROGRAM

         #pragma vertex vert
         #pragma fragment frag
   
         uniform sampler2D _MainTex;
         uniform sampler2D _DisplacementTex;
         uniform float _MaxDisplacement;
   
         struct vertexInput {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
            float4 texcoord : TEXCOORD0;
         };
   
         struct vertexOutput {
            float4 position : SV_POSITION;
            float4 texcoord : TEXCOORD0;
         };
   
         vertexOutput vert(vertexInput i) {
            vertexOutput o;
            
            // get color from displacement map, and convert to float from 0 to _MaxDisplacement
            float4 dispTexColor = tex2Dlod(_DisplacementTex, float4(i.texcoord.xy, 0.0, 0.0));
            float displacement = dot(float3(0.21, 0.72, 0.07), dispTexColor.rgb) * _MaxDisplacement;
            
            // displace vertices along surface normal vector
            float4 newVertexPos = i.vertex + float4(i.normal * displacement, 0.0);   

            // output data            
            o.position = UnityObjectToClipPos(newVertexPos); 
            o.texcoord = i.texcoord;
            return o;
   
         }
   
         float4 frag(vertexOutput i) : COLOR
         {
            return tex2D(_MainTex, i.texcoord.xy);
         }
   
         ENDCG
      }
   }
}

Note that edges of the surface mesh might not match up if the colors at the edges of the texture image do not match or if the surface normal vectors of corresponding vertices do not match.

Summary[edit | edit source]

Congratulations, you have reached the end of this tutorial. You saw:

  • How use texture mapping in vertex shaders.
  • How to move vertex positions along surface normal vectors.

Further reading[edit | edit source]

If you still want to learn more

< Cg Programming/Unity

Unless stated otherwise, all example source code on this page is granted to the public domain.