Cg Programming/Unity/Reflecting Surfaces

This tutorial introduces reflection mapping (and cube maps to implement it).

Reflections in a soap bubble.

It's the first in a small series of tutorials about environment mapping using cube maps in Unity. The tutorial is based on the per-pixel lighting described in Section “Smooth Specular Highlights” and on the concept of texture mapping, which was introduced in Section “Textured Spheres”.

A skybox is a (infinitely) large box surrounding the whole scene. Here a reflected camera ray (i.e. view ray) hits one of the textured faces of the skybox.

Reflection Mapping with a Skybox

edit

The illustration to the left depicts the concept of reflection mapping with a static skybox: a view ray is reflected at a point on the surface of an object and the reflected ray is intersected with the skybox to determine the color of the corresponding pixel. The skybox is just a large cube with textured faces surrounding the whole scene. It should be noted that skyboxes are usually static and don't include any dynamic objects of the scene. However, “skyboxes” for reflection mapping are often rendered to include the scene from a certain point of view. This is, however, beyond the scope of this tutorial.

Moreover, this tutorial covers only the computation of the reflection, it doesn't cover the rendering of the skybox, which is discussed in Section “Skyboxes”. For the reflection of a skybox in an object, we have to render the object and reflect the rays from the camera to the surface points at the surface normal vectors. The mathematics of this reflection is the same as for the reflection of a light ray at a surface normal vector, which was discussed in Section “Specular Highlights”.

Once we have the reflected ray, its intersection with a large skybox has to be computed. This computation actually becomes easier if the skybox is infinitely large: in that case the position of the surface point doesn't matter at all since its distance from the origin of the coordinate system is infinitely small compared to the size of the skybox; thus, only the direction of the reflected ray matters but not its position. Therefore, we can actually also think of a ray that starts in the center of a small skybox instead of a ray that starts somewhere in an infinitely large skybox. (If you are not familiar with this idea, you probably need a bit of time to accept it.) Depending on the direction of the reflected ray, it will intersect one of the six faces of the textured skybox. We could compute, which face is intersected and where the face is intersected and then do a texture lookup (see Section “Textured Spheres”) in the texture image for the specific face. However, Cg offers cube maps, which support exactly this kind of texture lookups in the six faces of a cube using a direction vector. Thus, all we need to do, is to provide a cube map for the environment as a shader property and use the texCUBE instruction with the reflected direction to get the color at the corresponding position in the cube map.

Cube Maps

edit

A cube map shader property called _Cube can be defined this way in a Unity shader:

   Properties {
      _Cube ("Reflection Map", Cube) = "" {}
   }

The corresponding uniform variable is defined this way in a Cg shader:

            uniform samplerCUBE _Cube;

To create a cube map, select Create > Legacy > Cubemap in the Project Window. Then you have to specify six texture images for the faces of the cube in the Inspector Window. Free examples of such textures can be found in Unity's Asset Store (select Windows > Asset Store and search for "skyboxes"). Furthermore, you should check MipMaps in the Inspector Window for the cube map; this should produce considerably better visual results for reflection mapping. Alternatively, you can import cubemap textures (which include the images for the six faces in one image file) by setting the Texture Type to Cubemap in the Inspector Window.

The vertex shader has to compute the view direction viewDir and the normal direction normalDir as discussed in Section “Specular Highlights”. To reflect the view direction in the fragment shader, we can use the Cg function reflect as also discussed in Section “Specular Highlights”:

            float3 reflectedDir = 
               reflect(input.viewDir, normalize(input.normalDir));

And to perform the texture lookup in the cube map and store the resulting color in the fragment color, we simply use:

            return texCUBE(_Cube, reflectedDir);

That's about it.

Complete Shader Code

edit

The shader code then becomes:

Shader "Cg shader with reflection map" {
   Properties {
      _Cube("Reflection Map", Cube) = "" {}
   }
   SubShader {
      Pass {   
         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag

         #include "UnityCG.cginc"

         // User-specified uniforms
         uniform samplerCUBE _Cube;

         struct vertexInput {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float3 normalDir : TEXCOORD0;
            float3 viewDir : TEXCOORD1;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            float4x4 modelMatrix = unity_ObjectToWorld;
            float4x4 modelMatrixInverse = unity_WorldToObject; 
 
            output.viewDir = mul(modelMatrix, input.vertex).xyz 
               - _WorldSpaceCameraPos;
            output.normalDir = normalize(
               mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
            output.pos = UnityObjectToClipPos(input.vertex);
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR
         {
            float3 reflectedDir = 
               reflect(input.viewDir, normalize(input.normalDir));
            return texCUBE(_Cube, reflectedDir);
         }
 
         ENDCG
      }
   }
}

Summary

edit

Congratulations! You have reached the end of the first tutorial on environment maps. We have seen:

  • How to compute the reflection of a skybox in an object.
  • How to generate cube maps in Unity and how to use them for reflection mapping.

Further reading

edit

If you still want to know more

< Cg Programming/Unity

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