Programmable Shader Example: A Cg Program Bump Mapped Material
In this example, you will use a Cg Program shader with a combination of vertex shaders, pixel shaders, 2D textures, cubic textures, and texture coordinate generation nodes to create realtime bump mapping on a sphere.
About Textures
Except for the normal map, all of the textures used in this example are available from the Autodesk Softimage Samples database that is installed with Softimage. You can find this database in the \Data\XSI_SAMPLES subdirectory of the installation path.
What is Realtime Bump Mapping?
Realtime bump mapping is essentially per-pixel lighting using a normal map. The goal is to create a pixel shader that takes the terms of the lighting equation and applies the equation on a per-pixel basis. In order to do so, you will need the following data:
• The normal at the pixel. This is provided by a normal map, which is set by an OGL Texture node.
• The color at the pixel. This is provided by a color texture map, which is also set by an OGL Texture node.
• The light direction at the pixel. This is provided in one of the texture coordinates, which is set by the vertex shader.
• The half vector at the pixel. This is also provided in one of the texture coordinates, which is also set by the vertex shader.
The final lighting equation is as follows:
Diffuse = normal . light direction
Specular = (normal . half vector) ^ some exponent
Final color = (Diffuse * color) + specular
Since the data required by the pixel shader is not provided by the fixed function pipeline, you need to create a vertex shader that will provide it. The vertex shader will be responsible for:
• Generating the position of the vertex in screen space.
• Passing through the texture coordinates required for the normal and color maps.
• Computing the light direction and storing it in one of the texture coordinates.
• Computing the half vector and storing it in one of the texture coordinates.
Normalizing the Light Direction and Half Vector
The vertex shader previously described provides the necessary data for the pixel shader, but there’s one problem: the light direction and the half vector will come in the pixel shader unnormalized, meaning that they won’t have unit length. This will cause artifacts in the final lighting equation.
There are two ways to solve this problem:
• Normalize the incoming light direction and half vector in the pixel shader. This consumes more instructions but is more accurate.
or
• Use a special cubic texture map called a normalization map. This consumes only one pixel shader instruction, but it also consumes one texture target, and is less accurate.
For this example, you are going to use a normalization map in order for the example to work with all the supported profiles.
Now make with the bump mapping!
To prepare the scene
1. From any 3D view’s display type menu, choose Realtime Shaders > OpenGL. This allows you to view the realtime shader effect you’re about to create.
2. The effect you’re about to create only works with a single point light. If necessary, choose Get > Primitive > Light > Point from the Render toolbar to get a point light.
Position the point light as desired and then delete all of the other lights in the scene.
3. Choose Get > Primitive > Sphere from any toolbar.
4. With the sphere selected, choose Get > Material > Phong from the Render toolbar.
5. Press 7 to open the object’s render tree.
6. Select and delete the Phong node.
To create the normal map
The bump map algorithm in this example uses normal maps in tangent space. This means that the sphere must provide tangent information. In Softimage, you can store tangents in a color at vertices (CAV) property.
7. To generate the tangents and normal map, use the Ultimapper tool, as described in Transferring Surface Attribute Maps (Ultimapper) [Texturing].
Once you’ve created the normal map using the Ultimapper, the sphere should look something like this:

For more information about creating, using, and painting on CAV properties, see Vertex Colors [Texturing].
To draw the object
8. From the render tree menu, choose Nodes > RealTime > OpenGL > More... and select the OGL Draw node from the browser window.
9. Connect the OGL Draw node to the Material node’s Realtime port.

To create the vertex shader setup
10. From the render tree menu, choose Nodes > Realtime > Cg > Cg Program.
11. Double-click the Cg Program node to open its property editor and do the following:
- Set the Profile to use the ARB, NV30 or NV20 vertex program profile.
- Enter the following code in the Cg Program window:
struct app2vertex
{
float4 position : POSITION;
float4 normal : NORMAL;
float4 color : COLOR0;
float4 uv0 : TEXCOORD0;
float4 uv1 : TEXCOORD1;
float4 uv2 : TEXCOORD2;
float4 uv3 : TEXCOORD3;
};
struct v2f
{
float4 hpos : HPOS;
float4 col0 : COL0;
float4 uv0 : TEXCOORD0;
float4 uv1 : TEXCOORD1;
float4 uv2 : TEXCOORD2;
float4 uv3 : TEXCOORD3;
};
v2f main
(
app2vertex IN,
uniform float4x4 simodelviewproj,
uniform float4x4 ModelViewI,
uniform float4 sieyepos,
uniform float4x4 simodel,
uniform float4 silightposition_0,
uniform float4 Params
)
{
v2f OUT;
// transform in homogenous space
OUT.hpos = mul(simodelviewproj, IN.position);
// create the basis
float4 normal = IN.normal;
float4 tangent = (IN.color * 2) - 1;
float4 binormal = normal.yzxw * tangent.zxyw;
binormal = (-tangent.yzxw * normal.zxyw) + binormal;
normal = normalize(normal);
tangent = normalize(tangent);
binormal = normalize(binormal);
// compute the light direction per vertex
float4 lDir = mul(ModelViewI, silightposition_0);
lDir = lDir - IN.position;
// transform the light direction in tangent space
float4 lDirT;
lDirT.x = dot( tangent.xyz, lDir.xyz);
lDirT.y = dot(binormal.xyz, lDir.xyz);
lDirT.z = dot(normal.xyz, lDir.xyz);
lDirT = normalize(lDirT);
OUT.uv0 = lDirT;
// transform the camera pos in world space
float4 gPos = mul(simodel, IN.position);
float4 EyeToVert = sieyepos - gPos;
EyeToVert = mul(ModelViewI, EyeToVert);
float3 EyeToVertT;
EyeToVertT.x = dot( tangent.xyz, EyeToVert.xyz);
EyeToVertT.y = dot(binormal.xyz,EyeToVert.xyz);
EyeToVertT.z = dot(normal.xyz, EyeToVert.xyz);
EyeToVertT = normalize(EyeToVertT);
// and compute the halfangle
float3 HalfAngle = normalize(EyeToVertT + lDirT);
// now transform in tangent space
OUT.uv1.xyz = HalfAngle;
// passthru the UVs
OUT.uv2 = IN.uv2;
OUT.uv3 = IN.uv3;
return OUT;
}
Without going in too much detail, here are a few observations about how the Cg program shader uses this code:
- The shader transforms the vertex position from model space to screen space.
- The shader computes the vertex basis (the tangent space). The light direction and the half angle will be expressed in tangent space because the normal map is in tangent space as well.
- The light direction is set in the first texture coordinate of the output.
- The half vector is set in the second texture coordinate of the output.
- The normal map and diffuse map texture coordinates are passed through in the third and fourth texture coordinates of the output.
12. Once you’ve written the code, you can set the Build option to Compile and Execute.
The code only needs one uniform parameter that is not a Autodesk Softimage semantic. This parameter is the inverse model matrix, which you will provide using a Cg Matrix node.
13. From the render tree menu, choose Nodes > RealTime > Cg > More... and select the Cg Matrix node from the browser window.
14. Connect the Cg Matrix node to the OGL Draw node’s previous port, then connect the Cg Program node to the Cg Matrix node’s previous port.
The render tree should now look something like this:

15. Double-click the Cg Matrix node to open its property editor and set the properties as shown in the following image:

To create the pixel shader setup
16. From the render tree menu, choose Nodes > Realtime Cg > Cg Program.
17. Double-click the new Cg Program node to open its property editor and do the following:
- Set the Profile to use the ARB, NV30, or NV20 fragment (pixel) program profile.
- Enter the following code in the Cg Program window:
fragout main
(
float4 col0 : COLOR0,
float4 uv0 : TEXCOORD0,
float4 uv1 : TEXCOORD1,
float4 uv2 : TEXCOORD2,
float4 uv3 : TEXCOORD3,
uniform samplerCUBE tex0,
uniform samplerCUBE tex1,
uniform sampler2D tex2,
uniform sampler2D tex3
)
{
fragout OUT;
float4 t0 = texCUBE(tex0, uv0);
float4 t1 = texCUBE(tex1, uv1);
float4 t2 = tex2D(tex2, uv2.xy);
float4 t3 = tex2D(tex3, uv3.xy);
t0.y = 1 - t0.y;
t0 = (t0 -.5) * 2 ;
t1.y = 1 - t1.y;
t1 = (t1 -.5) * 2;
t2 = (t2 -.5) * 2;
float4 diffuse = dot(t0.xyz, t2.xyz);
float4 specular = dot(t1.xyz, t2.xyz);
// self shadowing
specular = specular * diffuse;
specular = specular * specular * specular;
OUT.col = (diffuse * t3) + specular;
return OUT;
}
Without going in too much detail, here are a few observations about how the Cg program shader uses this code:
- The normalization map is set in target 0 and 1.
- The normal map is set in target 2.
- The color texture map is set in target 3.
- Normals stored as colors are halved and biased: you need to unpack these normals properly, like this : t0 = (t0 -.5) * 2
- The specular power is hard coded and cannot be changed.
- Normalizing a vector with a normalization map is easy. Simply sample the normalization map with U, V, and W coordinates and the returned color will be the normalized vector of (U,V,W).
18. Once you’ve written the code, you can set the Build option to Compile and Execute.
19. In the render tree, connect the new Cg Program node to the OGL Draw node’s previous input. This disconnects the currently connected Cg Matrix node.
20. Connect the Cg Matrix node to the new Cg Program node’s previous input.
The render tree should look something like this:

To set up the required textures
Earlier, you saw that the pixel shader code requires four textures. In the following steps, you’ll set up these textures:
21. From the render tree’s Nodes > RealTime > OpenGL > More... menu, get two of each of the following shaders:
- OGL Cubic Texture
- OGL Texture
- OGL Texture Transform
22. Connect the shaders together as follows:
- Connect the first OGL Cubic Texture node to the first OGL Texture Transform node’s previous port.
- Connect the first OGL Texture Transform node to the second OGL Cubic Texture node’s previous port.
- Connect the second OGL Cubic Texture node to the second OGL Texture Transform node’s previous port.
- Connect the second OGL Texture Transform node to the first OGL Texture node’s previous port.
- Connect the first OGL Texture node to the second OGL Texture node’s previous port.
- Connect the second OGL Texture node to the previous port of the Cg Program shader that contains the vertex shader program.
The render tree should now look something like this:

23. Set the properties of the OGL Cubic Texture shaders as follows:
- Set the first OGL Cubic Texture shader’s target to 0.
- Set the second OGL Cubic Texture shader’s target to 1.
- Set the texture images for each face of the cubic projection. Both shaders should use the same six images.
![]()
|
The images used in this example are available from the samples database that is installed with Softimage. You can find this database in the \Data\XSI_SAMPLES subdirectory of the installation path. |
24. Set the properties of the OGL Texture shaders as follows:
- Set the first OGL Texture shader’s target to 2. Set its texture to the normal map image.
- Set the second OGL Texture shader’s target to 3. Set its texture to the surface color map that you wish to use. The texture used in this example is also available from the samples database.
The final effect should look like this.

Autodesk Softimage v.7.5