## Sunday, 8 July 2012

### Basic HLSL Lighting Techniques - Episode 6: Environment Maps - Reflection & Refraction

Having played about with the NVIDIA Glass shader and my own (old) attempts at glass I have come up with this post

Refraction
Refraction is the light passing through an object but as they do so they bend. This is the effect we will get with this effect. Luckily HLSL has an intrinsic refraction function (refract) that we can use to get this effect. To get the refracted vector we need to calculate the direction of the viewer in relation to the surface and pass that with the surfaces normal to the refract function along with the degree of refraction we want.

So we need to pass in a world view projection and a world matrix as well as the camera position, oh and for the hell of it a tint for colour.

float4x4 wvp : WorldViewProjection;
float4x4 world : World;

float3 EyePosition : CAMERAPOSITION;
float4 tint = float4(1,1,1,1);And the structures look like this

struct VS_IN
{
float4 Position : POSITION;
float3 Normal : NORMAL;
};
struct VS_OUT
{
float4 Position : POSITION;
float3 Refract: TEXCOORD1;
};
struct PS_OUT
{
float4 Color : COLOR;
};

We also need to pass in the Environment Map we are going to use to make the refraction and include a function to make reading it a little simpler

texture cubeMap;
samplerCUBE CubeMap = sampler_state
{
texture = <cubeMap> ;
};

float4 CubeMapLookup(float3 CubeTexcoord)
{
return texCUBE(CubeMap, CubeTexcoord);
}

So now we have the bits we need to do the job we need a vertex shader to calculate our position and refraction value. In the NVIDIA shader this was done in the pixel shader, but I figured this may give some performance boost, but doing this means that I have had to make sacrifices (not literal ones) later on.

VS_OUT VS_Refraction(VS_IN input)
{
VS_OUT output = (VS_OUT)0;

output.Position = mul(input.Position,wvp);

float3 Normal = mul(normalize(input.Normal), world);
float3 PosWorldr = (mul(input.Position, world));
float3 ViewDir = normalize(PosWorldr - EyePosition);

output.Refract = refract(ViewDir, Normal, .99);

return output;
}

Now all we need to do is get the correct colour from the environment map

PS_OUT PS_Refraction(VS_OUT input)
{
PS_OUT output = (PS_OUT)0;

output.Color = CubeMapLookup(input.Refract) * tint;

return output;
}

And we get an effect like this

A bit hard to see, click the picture for a bigger version.

Reflection

And so onto reflection, again HLSL has another function we can use, reflect, and again we need to pass a view direction (incident vector) and the surface normal.

The shaders parameters are the same as before, the only change is to the structures, and all that is changing is the name of the parameters

struct VS_OUT
{
float4 Position : POSITION;
float3 Reflect: TEXCOORD0;
};

Again a small change in the vertex shader to populate the Reflect parameter with the result of the intrinsic reflect method

output.Reflect = reflect(ViewDirection, Normal);

Then as before, we get the colour from the environment map

output.Color = CubeMapLookup(input.Reflect) * tint;

And we then get this effect

So if we now combine the two effects we get a glass effect, not as nice as the NVIDIA one I grant you, but an effect none the less. Again we have to alter the the output structure

struct VS_OUT
{
float4 Position : POSITION;
float3 Reflect: TEXCOORD0;
float3 Refract: TEXCOORD1;
};

We populate both the parameters as before in the vertex shader

output.Reflect = reflect(ViewDirection, Normal);
output.Refract = refract(ViewDirection, Normal, 0.99);

And then in the pixel shader we get the colours from the environment map, I have taken the liberty of using the same method the NVIDIA shader did and used a lerp.

output.Color = lerp(CubeMapLookup(input.Refract),CubeMapLookup(input.Reflect),.5f) * tint;

And we get this

Now I kind of liked the prism effect you get with the NVIDIA Glass shader and thought I would add it here, so I have created a forth shader RC3DGlass shader.

So I added the RGB array and the refraction index for the RGB array (etas)

half3 etas = { 0.80, 0.82, 0.84 };

// wavelength colors
const half4 colors = {
{ 1, 0, 0, 0 },
{ 0, 1, 0, 0 },
{ 0, 0, 1, 0 },
};

As I mentioned before the view direction and surface normals are calculated in the vertex shader in my version and so I need the vertex shader to output an array of refraction values.

struct VS_OUT
{
half4 Position : POSITION;
half3 Reflect: TEXCOORD0;
half3 RefractRGB: TEXCOORD1;
};

Also I have replaced the in intrinsic refract method with the one in the NVIDIA shader so I can get the nice prism effect. So the vertex shader now looks like this

VS_OUT VS_Glass(VS_IN input)
{
VS_OUT output = (VS_OUT)0;

output.Position = mul(input.Position,wvp);

half3 Normal = mul(normalize(input.Normal), world);
half3 PosWorldr = (mul(input.Position, world));
half3 ViewDirection = normalize(PosWorldr - EyePosition);

output.Reflect = reflect(ViewDirection, Normal);

// transmission
bool fail = false;
for(int i=0; i<3; i++)
output.RefractRGB[i] = refract2(ViewDirection, Normal, etas[i]);

return output;
}

PS_OUT PS_Glass(VS_OUT input)
{
PS_OUT output = (PS_OUT)0;

half4 refract = half4(0,0,0,0);
for(int c=0;c<3;c++)
refract += CubeMapLookup(input.RefractRGB[c]) * colors[c];

output.Color = lerp(refract,CubeMapLookup(input.Reflect),.5) * tint;

return output;
}

As you can see I have had to put a loop in the vertex and the pixel shader which may well take away any perf boot I get from doing the calcs in the vertex shader this also means that the Fresnel term could not be applied, well I tried and could not get a decent effect when calculating in the VS, guess that's why NVIDIA did it in the PS lol

And we finally get this effect

Solution for this post can be down loaded here.