Tag Archives: cg

Rendering Glow and Soft Edged Object

Often, I have found myself trying to render light shaft, laser, godray and other similar special effect.

A formula I like to use in my pixel shader is

abs(dot(NormalVec, ViewVec)); //NdotV

This would give 1 from the center of the object to 0 at the edge of object. Using this value to apply to alpha blending or multiply it to the color, would give a soft edge to the object.

It is also useful to control how soft the edges of the object are, we could use

float finalBlending = pow(NdotV, softness).

This would make the edge softer the higher the softness value.

For effects that have hard edge and soft center, use 1 – NdotV.

Recently I also have been using

2 * abs(NdotV – 0.5);

to create effects which have hard center and hard edge with a gradual fade off between the two.

Try this with normal mapping to create more interesting effect.

Optimising Shaders

I am using HLSL shader model 3, but this article should apply to other languages and shader models.

It is Good to MAD

One of the most basic optimisation is to use mad operation, which is to multiply 2 values and add a third value to the result.
This is two instructions for the price of one, luckily the compiler is usually smart enough to snap this bargain when they sense it.
It is still useful to look at the compiled asm to combine any exceptions

The Power of 4

The beauty of GPU is most instructions are process in 4 components data block. This will caused any instruction that uses 1 data to cost as much as the same instruction using 4 data.

Eg pow(NdotH, shininess) cost the same as pow(float4(light1NdotH, light2NdotH, light3NdotH, light4NdotH), shininess)

Step operation and comparision operator also work in 4
float4 sampleDepths;
float depthToCompare;

float4 results = step(sampleDepths, depthToCompare);
float4 results = (sampleDepths > depthToCompare);

When 4 become 1

After comparision, it is often desirable to combine the results together to get total number of samples compared correctly.
This is where dotproduct shine.
Remember dot product of 2 float4 is

vec1.x * vec2.x + vec1.y * vec2.y + vec1.z * vec2.z + vec1.w * vec2.w

which is 4 mad instruction for 1 dp4 instruction

float numberOfResultsTrue = dot(results, 1);

Cheap Matrix Inverse

Remember orthonormal matrices Inverse is equal to their Transpose. Tangent, Binormal(Bitangent), Normal are usually in unit length and are perpendicular to each other. Use their transpose to convert any tangent space vector back to object space.

float3x3 matTan = float3x3(T, B, N);
float3x3 matTanInverse = transpose(matTan);
float3 normalFromNormalMap;
float3 worldNormal = mul(matTanInverse, normalFromNormalMap);

Random From UV

RenderMonkey has a very Cool Texture generator, that allow you to generate a texture from HLSL.

Well It is cool until you need to generate a random texture from UV.

Here are some quick code that is prob not mathematically correct, but it work for me.

float4 Texture2DFill( float2 vTexCoord : POSITION,
float2 vTexelSize : PSIZE) : COLOR
float uvFactor = dot(vTexCoord, float2( 1528.4f, 51.3f));
float rand = frac(fmod(uvFactor * 51223.00445, 20)) * 20;
rand *= uvFactor;
rand *= sqrt(rand * 347);
rand = fmod(rand, 2 * 3.1412 * uvFactor);
rand = frac(rand);
return rand;

Playing with NormalMap

Combining tangent space normal map is different from combining normal images. Here are some ways to combine two normal map together. The general idea is the identity normal, float3(0, 0, 1), will use the vertex default normal.


Adding two normals and preserving both details can be done this way.
finalNormal = float3(normal1.xy + normal2.xy, normal1.z * normal2.z);
finalNormal = normalize(finalNormal);


Using lerp. we can easily blend two normal
lerp(normal1, normal2, factor)


To make the normal map look stronger we could do

normal.xy *= strength;

This will cause any slope to bent away from the surface normal making it look steeper.


To compress normal, it is common to copy the X value to the A channel of the image, and calculate the Z value from X and Y
In short it is to take advantage of the uncompressed A channel in DXT5 and to save an additional channel data by calculating Z during runtime.

float3 normal;
normal.xy = tex2D(normalMap, uv).ag;

normal.z = sqrt(1 – normal.x * normal.x – normal.y * normal.y);
which can be optimise to
normal.z = sqrt(1 – dot(normal.xy, normal.xy));

Light Attenuation

Point Light Distance Attenuation

This is the commonly used formula to attenuate light by a constant, linear and quadric factor.

1 / (constantFactor + (linearFactor * disFactor) + (quadricFactor * disFactor * disFactor));

personally I dislike this formula as it is very difficult to isolate light to a particular area only.

 Spherical Light Attenuation

This is something I use more frequently.

saturate(1.0 – smoothstep(lightAttMinRange, lightMaxRange, lightDis));

This will only start attenuate at a specific distance and will have a smooth fall off to the lightMaxRange. It really make lights placement in room simpler.

Spot Light Attenuation

float spot = dot(spotlightDir, lightDir);

float outerAngleFactor = cos(outerAngleFactor / 2);

float innerAngleFactor  = cos(innerAngleFactor / 2);

float attenuation = (spot >= outerAngleFactor) * pow((spot – outerAngleFactor) / (innerAngleFactor – outerAngleFactor), falloff);

if you are using ogre, outerAngleFactor and attenuation  is calculated for you. if you are using lightVec from NdotL, do remember to inverse the direction.