GLSL Programming/Unity/Shading in World Space

From Wikibooks, open books for an open world
Jump to: navigation, search
Some chameleons are able to change their color according to the world around them.

This tutorial introduces uniform variables. It is based on Section “Minimal Shader”, Section “RGB Cube”, and Section “Debugging of Shaders”.

In this tutorial we will look at a shader that changes the fragment color depending on its position in the world. The concept is not too complicated; however, there are extremely important applications, e.g. shading with lights and environment maps. We will also have a look at shaders in the real world; i.e., what is necessary to enable non-programmers to use your shaders?

Transforming from Object to World Space[edit]

As mentioned in Section “Debugging of Shaders”, the attribute gl_Vertex specifies object coordinates, i.e. coordinates in the local object (or model) space of a mesh. The object space (or object coordinate system) is specific to each game object; however, all game objects are transformed into one common coordinate system — the world space.

If a game object is put directly into the world space, the object-to-world transformation is specified by the Transform component of the game object. To see it, select the object in the Scene View or the Hierarchy View and then find the Transform component in the Inspector View. There are parameters for “Position”, “Rotation” and “Scale” in the Transform component, which specify how vertices are transformed from object coordinates to world coordinates. (If a game object is part of a group of objects, which is shown in the Hierarchy View by means of indentation, then the Transform component only specifies the transformation from object coordinates of a game object to the object coordinates of the parent. In this case, the actual object-to-world transformation is given by the combination of the transformation of a object with the transformations of its parent, grandparent, etc.) The transformations of vertices by translations, rotations and scalings, as well as the combination of transformations and their representation as 4×4 matrices are discussed in Section “Vertex Transformations”.

Back to our example: the transformation from object space to world space is put into a 4×4 matrix, which is also known as “model matrix” (since this transformation is also known as “model transformation”). This matrix is available in the uniform variable _Object2World, which is defined and used in the following shader:

Shader "GLSL shading in world space" {
   SubShader {
      Pass {
         GLSLPROGRAM
 
         uniform mat4 _Object2World; 
            // definition of a Unity-specific uniform variable 
 
         varying vec4 position_in_world_space;
 
         #ifdef VERTEX
 
         void main()
         {
            position_in_world_space = _Object2World * gl_Vertex;
               // transformation of gl_Vertex from object coordinates 
               // to world coordinates;
 
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
 
         #endif
 
         #ifdef FRAGMENT
 
         void main()
         {
            float dist = distance(position_in_world_space, 
               vec4(0.0, 0.0, 0.0, 1.0));
               // computes the distance between the fragment position 
               // and the origin (the 4th coordinate should always be 
               // 1 for points).
 
            if (dist < 5.0)
            {
               gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); 
                  // color near origin
            }
            else
            {
               gl_FragColor = vec4(0.3, 0.3, 0.3, 1.0); 
                  // color far from origin
            }
         }
 
         #endif
 
         ENDGLSL
      }
   }
}

Note that this shader makes sure that the definition of the uniform is included in both the vertex and the fragment shader (although this particular fragment shader doesn't need it). This is similar to the definition of varyings discussed in Section “RGB Cube”.

Usually, an OpenGL application has to set the value of uniform variables; however, Unity takes care of always setting the correct value of predefined uniform variables such as _Object2World; thus, we don't have to worry about it.

This shader transforms the vertex position to world space and gives it to the fragment shader in a varying. For the fragment shader the varying variable contains the interpolated position of the fragment in world coordinates. Based on the distance of this position to the origin of the world coordinate system, one of two colors is set. Thus, if you move an object with this shader around in the editor it will turn green near the origin of the world coordinate system. Farther away from the origin it will turn dark grey.

More Unity-Specific Uniforms[edit]

There are, in fact, several predefined uniform variables similar to _Object2World. Here is a short list (including _Object2World), which appears in the shader codes of several tutorials:

      // The following built-in uniforms (except _LightColor0 and 
      // _LightMatrix0) are also defined in "UnityCG.glslinc", 
      // i.e. one could #include "UnityCG.glslinc" 
      uniform vec4 _Time, _SinTime, _CosTime; // time values from Unity
      uniform vec4 _ProjectionParams;
         // x = 1 or -1 (-1 if projection is flipped)
         // y = near plane; z = far plane; w = 1/far plane
      uniform vec4 _ScreenParams; 
         // x = width; y = height; z = 1 + 1/width; w = 1 + 1/height
      uniform vec4 unity_Scale; // w = 1/scale; see _World2Object
      uniform vec3 _WorldSpaceCameraPos;
      uniform mat4 _Object2World; // model matrix
      uniform mat4 _World2Object; // inverse model matrix 
         // (all but the bottom-right element have to be scaled 
         // with unity_Scale.w if scaling is important) 
      uniform vec4 _LightPositionRange; // xyz = pos, w = 1/range
      uniform vec4 _WorldSpaceLightPos0; 
         // position or direction of light source
      uniform vec4 _LightColor0; // color of light source 
      uniform mat4 _LightMatrix0; // matrix to light space

As the comments suggest, instead of defining all these uniforms (except _LightColor0 and _LightMatrix0), you could also include the file UnityCG.glslinc. However, for some unknown reason _LightColor0 and _LightMatrix0 are not included in this file; thus, we have to define them separately:

      #include "UnityCG.glslinc"
      uniform vec4 _LightColor0; 
      uniform mat4 _LightMatrix0;

Unity does not always update all of these uniforms. In particular, _WorldSpaceLightPos0, _LightColor0, and _LightMatrix0 are only set correctly for shader passes that are tagged appropriately, e.g. with Tags {"LightMode" = "ForwardBase"} as the first line in the Pass {...} block; see also Section “Diffuse Reflection”.

More OpenGL-Specific Uniforms[edit]

Another class of built-in uniforms are defined for the OpenGL compability profile, for example the mat4 matrix gl_ModelViewProjectionMatrix, which is equivalent to the matrix product gl_ProjectionMatrix * gl_ModelViewMatrix of two other built-in uniforms. The corresponding transformations are described in detail in Section “Vertex Transformations”.

As you can see in the shader above, these uniforms don't have to be defined; they are always available in GLSL shaders in Unity. If you had to define them, the definitions would look like this:

      uniform mat4 gl_ModelViewMatrix;
      uniform mat4 gl_ProjectionMatrix;
      uniform mat4 gl_ModelViewProjectionMatrix;
      uniform mat4 gl_TextureMatrix[gl_MaxTextureCoords];
      uniform mat3 gl_NormalMatrix; 
         // transpose of the inverse of gl_ModelViewMatrix
      uniform mat4 gl_ModelViewMatrixInverse;
      uniform mat4 gl_ProjectionMatrixInverse;
      uniform mat4 gl_ModelViewProjectionMatrixInverse;
      uniform mat4 gl_TextureMatrixInverse[gl_MaxTextureCoords];
      uniform mat4 gl_ModelViewMatrixTranspose;
      uniform mat4 gl_ProjectionMatrixTranspose;
      uniform mat4 gl_ModelViewProjectionMatrixTranspose;
      uniform mat4 gl_TextureMatrixTranspose[gl_MaxTextureCoords];
      uniform mat4 gl_ModelViewMatrixInverseTranspose;
      uniform mat4 gl_ProjectionMatrixInverseTranspose;
      uniform mat4 gl_ModelViewProjectionMatrixInverseTranspose;
      uniform mat4 gl_TextureMatrixInverseTranspose[gl_MaxTextureCoords];
 
      struct gl_LightModelParameters { vec4 ambient; };
      uniform gl_LightModelParameters gl_LightModel;
      ...

In fact, the compability profile of OpenGL defines even more uniforms; see Chapter 7 of the “OpenGL Shading Language 4.10.6 Specification” available at Khronos' OpenGL page. Unity supports many of them but not all.

Some of these uniforms are arrays, e.g gl_TextureMatrix. In fact, an array of matrices gl_TextureMatrix[0], gl_TextureMatrix[1], ..., gl_TextureMatrix[gl_MaxTextureCoords - 1] is available, where gl_MaxTextureCoords is a built-in integer.

Computing the View Matrix[edit]

Traditionally, it is customary to do many computations in view space, which is just a rotated and translated version of world space (see Section “Vertex Transformations” for the details). Therefore, OpenGL offers only the product of the model matrix \mathrm{M}_{\text{object}\to\text{world}} and the view matrix \mathrm{M}_{\text{world}\to\text{view}}, i.e. the model-view matrix \mathrm{M}_{\text{object}\to\text{view}}, which is available in the uniform gl_ModelViewMatrix. The view matrix is not available. Unity also doesn't provide it.

However, _Object2World is just the model matrix and _World2Object is the inverse model matrix. (Except that all but the bottom-right element have to be scaled by untiy_Scale.w.) Thus, we can easily compute the view matrix. The mathematics looks like this:

\mathrm{M}_{\text{object}\to\text{view}} = \mathrm{M}_{\text{world}\to\text{view}} \mathrm{M}_{\text{object}\to\text{world}}\,   \Rightarrow \mathrm{M}_{\text{world}\to\text{view}} = \mathrm{M}_{\text{object}\to\text{view}} \mathrm{M}_{\text{object}\to\text{world}}^{-1}

In other words, the view matrix is the product of the model-view matrix and the inverse model matrix (which is _World2Object * unity_Scale.w except for the bottom-right element, which is 1). Assuming that we have defined the uniforms _World2Object and unity_Scale, we can compute the view matrix this way in GLSL:

      mat4 modelMatrixInverse = _World2Object * unity_Scale.w;
      modelMatrixInverse[3][3] = 1.0; 
      mat4 viewMatrix = gl_ModelViewMatrix * modelMatrixInverse;

User-Specified Uniforms: Shader Properties[edit]

There is one more important type of uniform variables: uniforms that can be set by the user. Actually, these are called shader properties in Unity. You can think of them as parameters of the shader. A shader without parameters is usually used only by its programmer because even the smallest necessary change requires some programming. On the other hand, a shader using parameters with descriptive names can be used by other people, even non-programmers, e.g. CG artists. Imagine you are in a game development team and a CG artist asks you to adapt your shader for each of 100 design iterations. It should be obvious that a few parameters, which even a CG artist can play with, might save you a lot of time. Also, imagine you want to sell your shader: parameters will often dramatically increase the value of your shader.

Since the description of shader properties in Unity's ShaderLab reference is quite OK, here is only an example, how to use shader properties in our example. We first declare the properties and then define uniforms of the same names and corresponding types.

Shader "GLSL shading in world space" {
   Properties {
      _Point ("a point in world space", Vector) = (0., 0., 0., 1.0)
      _DistanceNear ("threshold distance", Float) = 5.0
      _ColorNear ("color near to point", Color) = (0.0, 1.0, 0.0, 1.0)
      _ColorFar ("color far from point", Color) = (0.3, 0.3, 0.3, 1.0)
   }
 
   SubShader {
      Pass {
         GLSLPROGRAM
 
         // uniforms corresponding to properties
         uniform vec4 _Point;
         uniform float _DistanceNear;
         uniform vec4 _ColorNear;
         uniform vec4 _ColorFar;
 
         #include "UnityCG.glslinc" 
            // defines _Object2World and _World2Object
 
         varying vec4 position_in_world_space;
 
         #ifdef VERTEX
 
         void main()
         {
            mat4 modelMatrix = _Object2World;
 
            position_in_world_space = modelMatrix * gl_Vertex;
 
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; 
         }
 
         #endif
 
         #ifdef FRAGMENT
 
         void main()
         {
            float dist= distance(position_in_world_space, _Point);
 
            if (dist < _DistanceNear)
            {
               gl_FragColor = _ColorNear;
            }
            else
            {
               gl_FragColor = _ColorFar;
            }
         }
 
         #endif
 
         ENDGLSL
      }
   }
}

With these parameters, a non-programmer can modify the effect of our shader. This is nice; however, the properties of the shader (and in fact uniforms in general) can also be set by scripts! For example, a JavaScript attached to the game object that is using the shader can set the properties with these lines:

   renderer.sharedMaterial.SetVector("_Point", 
      Vector4(1.0, 0.0, 0.0, 1.0));
   renderer.sharedMaterial.SetFloat("_DistanceNear", 
      10.0);
   renderer.sharedMaterial.SetColor("_ColorNear", 
      Color(1.0, 0.0, 0.0));
   renderer.sharedMaterial.SetColor("_ColorFar", 
      Color(1.0, 1.0, 1.0));

Use sharedMaterial if you want to change the parameters for all objects that use this material and just material if you want to change the parameters only for one object. With scripting you could, for example, set the _Point to the position of another object (i.e. the position of its Transform component). In this way, you can specify a point just by moving another object around in the editor. In order to write such a script, select Create > JavaScript in the Project View and copy & paste this code:

@script ExecuteInEditMode() // make sure to run in edit mode
 
var other : GameObject; // another user-specified object
 
function Update () // this function is called for every frame
{
   if (null != other) // has the user specified an object?
   {
      renderer.sharedMaterial.SetVector("_Point", 
         other.transform.position); // set the shader property 
         // _Point to the position of the other object
   }
}

Then, you should attach the script to the object with the shader and drag & drop another object to the other variable of the script in the Inspector View.

Summary[edit]

Congratulations, you made it! (In case you wonder: yes, I'm also talking to myself here. ;) We discussed:

  • How to transform a vertex into world coordinates.
  • The most important Unity-specific uniforms that are supported by Unity.
  • The most important OpenGL-specific uniforms that are supported by Unity.
  • How to make a shader more useful and valuable by adding shader properties.

Further Reading[edit]

If you want to know more


< GLSL Programming/Unity

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