# GLSL Programming/Unity/Projectors

This tutorial covers projective texture mapping for projectors, which are particular rendering components of Unity.

It is based on Section “Cookies”. If you haven't read that tutorial yet, you should read it first.

## Contents

### Unity's ProjectorsEdit

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.

### Projectors for Adding LightEdit

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 (see Section “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 use `Blend 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 matrix `_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 ${\displaystyle x}$  and ${\displaystyle y}$  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 ${\displaystyle w}$  coordinates (as always for projective texture mapping); either by explicitly dividing ${\displaystyle x}$  and ${\displaystyle y}$  by ${\displaystyle w}$  or by using `texture2DProj`:

```Shader "GLSL projector shader for adding light" {
Properties {
_ShadowTex ("Projected Image", 2D) = "white" {}
}
Pass {
Blend One One
// add color of _ShadowTex to the color in the framebuffer

GLSLPROGRAM

// User-specified properties

// Projector-specific uniforms
uniform mat4 _Projector; // transformation matrix
// from object space to projector space

varying vec4 positionInProjSpace;
// position of the vertex (and fragment) in projector space

#ifdef VERTEX

void main()
{
positionInProjSpace = _Projector * gl_Vertex;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

#endif

#ifdef FRAGMENT

void main()
{
if (positionInProjSpace.w > 0.0) // in front of projector?
{
gl_FragColor = texture2D(_ShadowTex ,
vec2(positionInProjSpace) / positionInProjSpace.w);
// alternatively: gl_FragColor = texture2DProj(
}
else // behind projector
{
gl_FragColor = vec4(0.0);
}
}

#endif

ENDGLSL
}
}
// The definition of a fallback shader should be commented out
// during development:
// Fallback "Projector/Light"
}
```

Notice that we have to test whether ${\displaystyle w}$  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.

A cartoon character with a drop shadow.

### Projectors for Modulating ColorsEdit

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 particular Layer (in the Inspector View of the game object) and specifying this layer under Ignore Layers in the Inspector View 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`

The `Zero` 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 require `Blend Zero SrcColor` or `Blend Zero OneMinusSrcColor`.

The different blend equation is actually about the only change in the shader code compared to the version for adding light:

```Shader "GLSL projector shader for drop shadows" {
Properties {
_ShadowTex ("Shadow Shape", 2D) = "white" {}
}
Pass {
Blend Zero OneMinusSrcAlpha // attenuate color in framebuffer
// by 1 minus alpha of _ShadowTex

GLSLPROGRAM

// User-specified properties

// Projector-specific uniforms
uniform mat4 _Projector; // transformation matrix
// from object space to projector space

varying vec4 positionInProjSpace;
// position of the vertex (and fragment) in projector space

#ifdef VERTEX

void main()
{
positionInProjSpace = _Projector * gl_Vertex;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

#endif

#ifdef FRAGMENT

void main()
{
if (positionInProjSpace.w > 0.0) // in front of projector?
{
gl_FragColor = texture2D(_ShadowTex ,
vec2(positionInProjSpace) / positionInProjSpace.w);
// alternatively: gl_FragColor = texture2DProj(
}
else // behind projector
{
gl_FragColor = vec4(0.0);
}
}

#endif

ENDGLSL
}
}
// The definition of a fallback shader should be commented out
// during development:
// Fallback "Projector/Light"
}
```

### SummaryEdit

Congratulations, this is the end of this tutorial. We have seen:

• How Unity's projectors work.
• How to implement a shader for a projector to add light to objects.
• How to implement a shader for a projector to attenuate objects' colors.