This tutorial coversprojective texture mapping for projectors, which are particular rendering components of Unity.
It is based onSection “Cookies”. If you haven't read that tutorial yet, you should read it first.
Unity's projectors are somewhat similar to spotlights. In fact, they can be used for similar applications. There is, however, an important technical difference: For spotlights, the shaders of all lit objects have to compute the lighting by the spotlight as discussed inSection “Cookies”. If the shader of an object ignores the spotlight, it just won't be lit by the spotlight. This is different for projectors: Each projector is associated with a material with a shader that is applied to any object in the projector's range. Thus, an object's shader doesn't need to deal with the projector; instead, the projector applies its shader to all objects in its range as an additional render pass in order to achieve certain effects, e.g. adding the light of a projected image or attenuating the color of an object to fake a shadow. In fact, various effects can be achieved by using different blend equations of the projector's shader. (Blend equations are discussed inSection “Transparency”.)
One might even consider projectors as the more “natural” way of implementing lights. However, the interaction between light and materials is usually specific to each material while the single shader of a projector cannot deal with all these differences. This limits the possibilities of projectors to three basic behaviors: adding light to an object, modulating an object's color, or both, adding light and modulating the object's color. We will look at adding light to an object and attenuating an object's colors as an example of modulating them.
In order to create a projector, chooseGameObject > Create Empty from the main menu and then (with the new object still selected)Component > Effects > Projector from the main menu. You have now a projector that can be manipulated similarly to a spotlight. The settings of the projector in theInspector Window are discussed inUnity's manual. Here, the only important setting is the projector'sMaterial, which will be applied to all objects in its range. Thus, we have to create another material and assign a suitable shader to it. This shader usually doesn't have access to the materials of the game objects, which it is applied to; therefore, it doesn't have access to their textures etc. Neither does it have access to any information about light sources. However, it has access to the attributes of the vertices of the game objects and its own shader properties.
A shader to add light to objects could be used to project any image onto other objects, similarly to an overhead projector or a movie projector. Thus, it should use a texture image similar to a cookie for spotlights (seeSection “Cookies”) except that the RGB colors of the texture image should be added to allow for colored projections. We achieve this by setting the fragment color to the RGBA color of the texture image and using the blend equation
Blend One One
which just adds the fragment color to the color in the framebuffer. (Depending on the texture image, it might be better to useBlend SrcAlpha One in order to remove any colors with zero opacity.)
Another difference to the cookies of spotlights is that we should use the Unity-specific uniform matrixunity_Projector to transform positions from object space to projector space instead of the matrix_LightMatrix0. However, coordinates in projector space work very similar to coordinates in light space — except that the resulting and coordinates are in the correct range; thus, we don't have to bother with adding 0.5. Nonetheless, we have to perform the division by the coordinates (as always for projective texture mapping); either by explicitly dividing and by or by usingtex2Dproj.
The lineZWrite Off makes sure that we don't change the depth buffer since we are only adding light to a mesh that has already been rasterized.Offset -1, -1 slightly changes the depth to pretend that we are a bit in front of the mesh to which we are adding light. This helps to make sure that nothing of what we rasterize now is occluded by that mesh. (When you copy and paste the code below, some editors add a space character ("") after the first "-", which creates a syntax error. Just delete that space character.)
Shader"Cg projector shader for adding light"{Properties{_ShadowTex("Projected Image",2D)="white"{}}SubShader{Pass{BlendOneOne// add color of _ShadowTex to the color in the framebufferZWriteOff// don't change depthsOffset-1,-1// avoid depth fighting (should be "Offset -1, -1")CGPROGRAM#pragma vertex vert#pragma fragment frag// User-specified propertiesuniformsampler2D_ShadowTex;// Projector-specific uniformsuniformfloat4x4unity_Projector;// transformation matrix// from object space to projector spacestructvertexInput{float4vertex:POSITION;float3normal:NORMAL;};structvertexOutput{float4pos:SV_POSITION;float4posProj:TEXCOORD0;// position in projector space};vertexOutputvert(vertexInputinput){vertexOutputoutput;output.posProj=mul(unity_Projector,input.vertex);output.pos=UnityObjectToClipPos(input.vertex);returnoutput;}float4frag(vertexOutputinput):COLOR{if(input.posProj.w>0.0)// in front of projector?{returntex2D(_ShadowTex,input.posProj.xy/input.posProj.w);// alternatively: return tex2Dproj(// _ShadowTex, input.posProj);}else// behind projector{returnfloat4(0.0,0.0,0.0,0.0);}}ENDCG}}Fallback"Projector/Light"}
Notice that we have to test whether is positive (i.e. the fragment is in front of the projector, not behind it). Without this test, the projector would also add light to objects behind it. Furthermore, the texture image has to be square and it is usually a good idea to use textures with wrap mode set to clamp.
Just in case you wondered: the shader property for the texture is called_ShadowTex in order to be compatible with the built-in shaders for projectors.
As described inSection “Cookies”, projective texture mapping comes sometimes with an unpleasant side effect: at the edges of the projection, the GPU uses a high mip map level, which can result in a visible border (in particular for texture maps with clamped texture coordinates). The easiest way to avoid this, is to deactivate mip maps for the texture image: find and select the texture image in theProject Window; then in theInspector Window setTexture Type toAdvanced and uncheckGenerate Mip Maps. Don't forget to click theApply button.

The basic steps of creating a projector for modulating colors are the same as above. The only difference is the shader code. The following example adds a drop shadow by attenuating colors, in particular the floor's color. Note that in an actual application, the color of the shadow caster should not be attenuated. This can be achieved by assigning the shadow caster to a particularLayer (in theInspector Window of the game object) and specifying this layer underIgnore Layers in theInspector Window of the projector.
In order to give the shadow a certain shape, we use the alpha component of a texture image to determine how dark the shadow is. (Thus, we can use the cookie textures for lights in the standard assets.) In order to attenuate the color in the framebuffer, we should multiply it with 1 minus alpha (i.e. factor 0 for alpha equals 1). Therefore, the appropriate blend equation is:
Blend Zero OneMinusSrcAlpha
TheZero indicates that we don't add any light. Even if the shadow is too dark, no light should be added; instead, the alpha component should be reduced in the fragment shader, e.g. by multiplying it with a factor less than 1. For an independent modulation of the color components in the framebuffer, we would requireBlend Zero SrcColor orBlend Zero OneMinusSrcColor.
The different blend equation is actually about the only change in the shader code compared to the version for adding light:
Shader"Cg projector shader for drop shadows"{Properties{_ShadowTex("Projected Image",2D)="white"{}}SubShader{Pass{BlendZeroOneMinusSrcAlpha// attenuate color in framebuffer// by 1 minus alpha of _ShadowTexZWriteOff// don't change depthsOffset-1,-1// avoid depth fighting (should be "Offset -1, -1")CGPROGRAM#pragma vertex vert#pragma fragment frag// User-specified propertiesuniformsampler2D_ShadowTex;// Projector-specific uniformsuniformfloat4x4unity_Projector;// transformation matrix// from object space to projector spacestructvertexInput{float4vertex:POSITION;float3normal:NORMAL;};structvertexOutput{float4pos:SV_POSITION;float4posProj:TEXCOORD0;// position in projector space};vertexOutputvert(vertexInputinput){vertexOutputoutput;output.posProj=mul(unity_Projector,input.vertex);output.pos=UnityObjectToClipPos(input.vertex);returnoutput;}float4frag(vertexOutputinput):COLOR{if(input.posProj.w>0.0)// in front of projector?{returntex2D(_ShadowTex,input.posProj.xy/input.posProj.w);// alternatively: return tex2Dproj(// _ShadowTex, input.posProj);}else// behind projector{returnfloat4(0.0,0.0,0.0,0.0);}}ENDCG}}Fallback"Projector/Light"}
Congratulations, this is the end of this tutorial. We have seen:
If you still want to know more