XNA UK User Group
A helping hand for bedroom coders throughout the land.
Basic HLSL Lighting Techniques - Episode 6: Environment Maps - Reflection & Refraction

Blogs

RandomChaos

Syndication

http://www.youtube.com/v/_gSZ8CA2t70 <p><a href="http://www.youtube.com/v/_gSZ8CA2t70">http://www.youtube.com/v/_gSZ8CA2t70</a></p>

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

Last Minute Addition

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[3] = {
    { 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[3]: 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;
}

And finally the pixel shader

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.


Posted Tue, Jan 6 2009 9:43 PM by Nemo Krad

Comments

GameDevKicks.com wrote Episode 6: Environment Maps - Reflection
on Wed, Jan 7 2009 2:47 PM

You've been kicked (a good thing) - Trackback from GameDevKicks.com

clair wrote re: Basic HLSL Lighting Techniques - Episode 6: Environment Maps - Reflection & Refraction
on Thu, Feb 12 2009 12:56 PM

hiiyaa cwl site wuu2 ??? xXxXxXxX

Mushroom wrote re: Basic HLSL Lighting Techniques - Episode 6: Environment Maps - Reflection & Refraction
on Sat, Jun 20 2009 7:23 AM

I've been wondering how to do this for a good while now, thanks for putting up this tutorial ;D

I think I'm becoming a regular here~

Nemo Krad wrote re: Basic HLSL Lighting Techniques - Episode 6: Environment Maps - Reflection & Refraction
on Sat, Jun 20 2009 7:48 AM

Good, glad you found it useful :) and there is nothing wrong with being a regular, welcome :D

Krusbert wrote re: Basic HLSL Lighting Techniques - Episode 6: Environment Maps - Reflection & Refraction
on Wed, Sep 9 2009 12:13 PM

The best tutorial on this particular subject I have seen!

Something about actually creating the cubemap would be useful as well :)

Nemo Krad wrote re: Basic HLSL Lighting Techniques - Episode 6: Environment Maps - Reflection & Refraction
on Wed, Sep 16 2009 9:51 PM

Well, you have at least a couple choices there.

1. Create the scene and take 6 images to a CubeTexture and then save that to file.

2. In your favourite art package, create the 6 cube surfaces then using the DXTex.exe app that comes with the SDK create the cube map.

Bajeo wrote re: Basic HLSL Lighting Techniques - Episode 6: Environment Maps - Reflection & Refraction
on Wed, Dec 16 2009 10:40 PM

Hey there very gd tutorial :D

One thing i am wondering

How do you send the cube map to the shader, do you pass it as a single texture?

I am trying to add this effect to Vertex texture displaced water.

Nemo Krad wrote re: Basic HLSL Lighting Techniques - Episode 6: Environment Maps - Reflection & Refraction
on Wed, Dec 16 2009 11:21 PM

@Bajeo well yes, if you download the sample you will see. But yes I pass a cubemap to the shader.

Azizajalal wrote re: Basic HLSL Lighting Techniques - Episode 6: Environment Maps - Reflection & Refraction
on Mon, Apr 26 2010 11:12 AM

Very nice effect, the best tutorial on this particular subject I have seen! Something about actually creating the cubemap would be useful as well.