Tuesday, 13 December 2011

Wizard's Laboratory

This year I made a minigame involving wizards, and thought I'd make a graphic for the title screen, which would be in a point-and-click style. The idea was to make a scene reminiscent of Myst. I made the scene in Blender, it's mostly original except for couple of models I took from archive3d, and textures and materials from various sources.

Here was the first mockup:


And the final result:


Friday, 2 December 2011

Chromatic Dispersion

When white light passes through a prism, the different frequency components of the light refract at different angles, creating a rainbow effect. Real rainbows you see in the sky are caused by sunlight being dispersed into separate colours by moisture in the air, which has the same net effect as a prism.

A prism.

A simple way to imitate this effect in a shader is to create three refracted rays, one for each component (red, green and blue) of a fragment. Using slightly different indices of refraction for each ray creates an effect like dispersion. The difference is that in reality the frequency spectrum is continuous.

This example builds on a refraction shader discussed in the OpenGL 4.0 Shading Language Cookbook by David Wolff.

The result:
Chromatic dispersion with reflective component.
Vertex shader:
 #version 400  
 layout (location = 0) in vec3 VertexPosition;  
 layout (location = 1) in vec3 VertexNormal;  
 layout (location = 2) in vec2 VertexTexCoord;  
 out vec3 ReflectDir;  
 out vec3 RefractDirR;  
 out vec3 RefractDirG;  
 out vec3 RefractDirB;  
 struct MaterialInfo  
 {  
   float Eta;       // Index of refraction  
   float ReflectionFactor; // Percentage of reflected light  
 };  
 uniform MaterialInfo Material;  
 uniform bool DrawSkyBox;  
 uniform vec3 WorldCameraPosition;  
 uniform mat4 ModelViewMatrix;  
 uniform mat4 ModelMatrix;  
 uniform mat3 NormalMatrix;  
 uniform mat4 ProjectionMatrix;  
 uniform mat4 MVP;  
 void main()  
 {  
   if( DrawSkyBox )  
   {  
     ReflectDir = VertexPosition;  
   }  
   else  
   {  
     vec3 worldPos = vec3( ModelMatrix * vec4(VertexPosition,1.0) );  
     vec3 worldNorm = vec3(ModelMatrix * vec4(VertexNormal, 0.0));  
     vec3 worldView = normalize( WorldCameraPosition - worldPos );  
     //calculate reflection direction  
     ReflectDir = reflect(-worldView, worldNorm );  
     //calculate a slightly different colour direction for each colour component  
     RefractDirR = refract(-worldView, worldNorm, Material.Eta );  
     RefractDirG = refract(-worldView, worldNorm, Material.Eta + 0.008);  
     RefractDirB = refract(-worldView, worldNorm, Material.Eta + 0.016);  
   }  
   gl_Position = MVP * vec4(VertexPosition,1.0);  
 }  

Fragment shader:

 #version 400  
 in vec3 ReflectDir;  
 in vec3 RefractDirR;  
 in vec3 RefractDirG;  
 in vec3 RefractDirB;  
 uniform samplerCube CubeMapTex;  
 uniform bool DrawSkyBox;  
 struct MaterialInfo  
 {  
   float Eta;       // Index of refraction  
   float ReflectionFactor; // Percentage of reflected light  
 };  
 uniform MaterialInfo Material;  
 layout( location = 0 ) out vec4 FragColor;  
 void main()  
 {  
   // Access the cube map texture  
   vec4 reflectColor = texture(CubeMapTex, ReflectDir);  
   // Look up the separate colour components from the cubemap  
   vec4 refractColor;  
   refractColor.x = texture(CubeMapTex, RefractDirR).r;  
   refractColor.y = texture(CubeMapTex, RefractDirG).g;  
   refractColor.z = texture(CubeMapTex, RefractDirB).b;  
   if( DrawSkyBox )  
   {  
     FragColor = reflectColor;  
   }  
   else  
   {  
     FragColor = mix(refractColor, reflectColor, Material.ReflectionFactor);  
   }  
 }