Projective Texture Step by Step

In my research about projection, I came to projective texture. I found a lot of material about projective texture on the web but neither of them tells exactly what steps you need to follow to archieve the desired effect. In this post, I will tell in more detail how this works.
Basically, texture projection is a dynamic texture mapping of an object that is changed according to some point of view like a camera or a spot light. It works like projecting a point using the normal world, view and projection matrix but, in this time, projecting a texture coordinate. Below is the shader that accomplish this effect and we will see line by line how it works. So, let’s go.

First of all, we need the texture that will be projected:
texture gDiffuse;

After that, we need to create a sampler. Here there is a trick. The CLAMP mode needs to be used for all adrress. This is because, sometimes, as we will see, the texture coordinate generated can be greater than 1.0.
sampler2D gDiffuseSampler = sampler_state {
Texture = <gDiffuse>;
MagFilter = Linear;
MinFilter = Linear;
AddressU = CLAMP;
AddressV = CLAMP;
AddressW = CLAMP;
};

Here is view matrix that will do the texture projection.
float4x4 projectorView;

And this is our normal camera matrix, composed by the Word, View and Projection.
float4x4 WorldViewProj;

To facilitate, I created some structs to store the information that will be needed by the shader effect.
struct VSIn
{
float4 position : POSITION0;
float4 texcoord0 : TEXCOORD0;
};

struct VSOut
{
float4 position : POSITION0;
float4 texcoord0 : TEXCOORD0;
};

Here is our main vertex shader. Let’s see how it works:

VSOut mainVS(VSIn input){

First, we create our struct to store the end result computed by the vertex shader.
VSOut output;

In this line, we compute the texture coordinate. We can see here that, to do this, we get the position and multiply by the projectorView, which is a matrix composed of a world, view and a projection but of some other point of view that can be another camera, as is the case of this example. After this multiplication, we said that the position is in homogenius clip space. Points outside the field of view of this projector view will not be rendered, or in other words, will not have texture coordinates.
output.texcoord0 = mul(float4(input.position.xyz, 1.0), projectorView);

Here we compute normally the projected position of this position.
output.position = mul(float4(input.position.xyz, 1.0), WorldViewProj);

And finally return these information
return output;
}

Now let’s see the Fragment Shader.

float4 mainPS(VSOut input) : COLOR {

First of all, we are making a division of all components of texture coordinates by w. It’s is necessary because, as I said before, we are in homogenius clip space. This space is used to do clipping but here we do not need this. So, to get the real position of this vertex we have to make this division. After that we are in normalized clip space. That is, our vertex can go from -1.0 to 1.0. Vertex outside of this range are not showed. Here we have a problem: texture coordinates only can go from 0.0 to 1.0. To solve this, we multiply it by 0.5 and add 0.5. This will do the job.
float4 texcoord = input.texcoord0;
texcoord.x = ((texcoord.x / texcoord.w) * 0.5 ) + 0.5;
texcoord.y = ((texcoord.y / texcoord.w) * -0.5 ) + 0.5;
texcoord.z = ((texcoord.z / texcoord.w) * 0.5 ) + 0.5;
texcoord.w = (texcoord.w / texcoord.w);

Next, we get the texel from the texture normally.
float4 tex = tex2D(gDiffuseSampler, texcoord);

And finally here we return the color. Here there’s another trick. As we are using clamp mode, texture coordinates outside the range from 0.0 to 1.0 will have its color matched to the border of the texture. So, what I did here is make the border of the image transparent, or alfa equals to 0. After that I multiply the texel color from the texture with it’s alpha and added the color of my plane, which in this case is white.
return float4(tex.r * tex.a + (1.0 * (1 – tex.a)),
tex.g + (1.0 * (1 – tex.a)),
tex.b + (1.0 * (1 – tex.a)), 1.0);
}

And is it. Below is an image of this shader applied in a plane using my engine that I’m making in XNA. Here, I used a camera with different field of view. Instead of a plane, it can be applied in whatever object you want.

In this shader, I tried to use the tex2Dproj which takes the texture coordinates in homogeneous coordinate space but it doesn’t work for me. If anyone get it’s working please, let me know.

Any comment or question, don’t hesitate to write me.

Comments 43

Leave a Reply

Your email address will not be published. Required fields are marked *