# He hit me!

# Or

# Basic lighting

You know what joke they always do when they start teaching lights right? There’s no need for me to say “lights on!” or “let there be light!” ….if I had to choose I’d pick “can has light? Kthxbi”

### Table of contents

## Before we begin

### The three basic lighting channels

* Ambient* is the base color added to all surfaces. In deep space for instance, this color is complete blackness. An analogy on earth would be all light hitting an object which doesn’t originate directly from a light source, and thus is weakened and close to even on all spots. (light reflected from other objects)

* Diffuse* is light hitting an object directly from a light source and spreads evenly through the surfaces, depending on the surface angle towards the source.

The most common algorithm for diffuse is * Lambertian* reflectance, which we will use in our basic lighting shaders. Lambertian uses the dot product between the surface normal and the view direction vector to calculate diffuse.

It produces a smooth and gradual (and somewhat rough, like uncoated wood) lighting texture.

* Specular* is light reflected in a specular angle, creating the “shiny spot” (or strip) that is seen in reflective surfaces. The size and distribution of the specular depending on the “shininess factor” we provide, and the algorithm we use. This highlight is dynamic, and changes location and spread according to the angle from which the user views the object from.

The two most commonly used methods for specular are * Phong* and

*, we will learn them both.*

**Blinn-Phong**### What are normals?

A normal is a vector that points perfectly upwards from the surface (orthogonal). It indicates the direction to which the surface (or a specific point across it) is facing.

### A normalized vector

Is a vector which all of his components run between 0 and 1 in value keeping their original proportions compared to the vector's magnitude. Essentially, they are clamped so that the length of the vector will be 1 (a unit vector).

The magnitude of a 2d vector will be: √( X^{2} + Y^{2}) (Pythagoras)

A vector (2, 1) when normalized will become (2/√5, 1/√5)

### The dot product

The dot product equals the multiplication of two vector’s magnitudes by each other and the cos(α) between them (|A|*|B|*cos(α)) as if they had originated from the same spot.

This function helps us determine the directional relationship between two vectors.

On unit vectors, the result is always between 1 and -1 (since it uses a cos function):

1. If the two vectors are orthogonal to each other, the result is 0

2. If two vectors are in opposite directions the result is -1 (Note, opposite can be facing each other or from each other)

3. If vectors are facing the same directions the result is 1

4. If the angle between two vectors is sharp (acute), the result is between 0 and 1;

5. If the angle between two vectors is blunt (obtuse), the result is between 0 and -1;

### A quick look

Ambient, lambertian diffuse and Phong specular (per vertex) separately

and combined:

(Note, this isn’t a combination of the 3 above, because they combine ugly)

## Gouraud shading (per vertex)

Gouraud shading is the simplest, fastest lighting method that combines ambient, diffuse and specular channels to light an object depending on the angle between the direction of the light source and the vertex’s normal.

For this shader, we will use Lambertian model for diffuse, and Phong reflection model for specular.

### Adding ambient… or the new RED RING OF DEATH

… It can also be square ring of death… Or a tea pot…

As for ambient, it’s not very difficult. Just declare a COLOR and call it ambientColor. Give it whatever color you wish, but you might not want to make it very intense (0.125 is enough).

### Diffuse

Now things will start getting a bit more complicated.

We need to take into consideration a few parameters:

- A desired diffuse color
- The position of the light
- The angle in which the light hits the surface

1 and 2 can be done swiftly:

float4 diffColor : COLOR; float4 lightPos; // something like (200 150 0 0) will suffice

Since we will be setting these variables by hand in FXC, remember to either insert values in the declaration or in the material properties.

### The plan:

In order to calculate the diffuse color of the vertex, we need to:

- Receive the vertex normal
- Calculate the direction in which the light hits the vertex
- Calculate their dot product
- Use the dot product as a factor for the diffuse color
- Pass the combined color (diffuse + ambient) to the pixel shader

**1: adding normal to vertex input **

we need to add a normal to the vertex shader input:

struct vertexIn { float4 position : POSITION; float3 normal : NORMAL; };

**2: calculate the light direction:**

In order to do so, we need to subtract vertex’s world position from the light’s world position.

We also normalize the light direction, because its true magnitude doesn’t matter, and that way we can use it as a direction.

//vertex world position float3 worldpos = mul(input.position, world_m); //normalized direction vector float3 lightDir = normalize( lightPos - worldpos );

**3: dot product**

HLSL provides a function to calculate the dot product of two vectors:

//vertex world position float dotNL = dot ( lightDir, input.normal );

**4: make a diffuse color factor**

//this value will later be multiplied by diffCollor to create a color vector Float diff = saturate(dotNL);

Note that I used a function called * saturate*. This function clamps the value to between 0 and 1 (this is not equivalent to abs()! Values below 0 will become 0 and above 1 will become 1)

I did that because I don’t want my values to turn negative or exceed 1, because I’m accumulating color values. A negative dot product will sabotage ambient color by negating it where it doesn’t have any effect (such the opposite side of the light)

**5: The state of things**

At the moment, our program should look something like this:

float4x4 worldViewProj_m : WorldViewProjection; float4x4 world_m : World; // make sure you fill these three values float4 lightPos; float4 ambientColor : COLOR; float4 diffColor : COLOR; struct vertexIn { float4 position : POSITION; float3 normal : NORMAL; }; struct vertexOut { float4 position : POSITION; float4 finColor : COLOR; }; struct pixelIn { float4 finColor : COLOR; }; vertexOut mainVS(vertexIn input) { vertexOut output = (vertexOut)0; output.position = mul(input.position, worldViewProj_m); float3 worldpos = mul(input.position, world_m); float3 lightDir = normalize( lightPos - worldpos ); float dotNL = dot ( lightDir, input.normal ); float diff = saturate(dotNL); // combine the two lights output. finColor = diff * diffColor + ambientColor; return output; } float4 mainPS( pixelIn input ) : COLOR { return input.lightColor; }

Although you should be seeing sharp edged vertices, FXC seem to soften these. But you can see a somewhat murky line of vertices along the transition line to the * dark side*.

## Specular

How do we calculate specular according to phong? We need to take into consideration these few things:

- The specular reflection angle of the light along the vertex.
- The angle of the camera

### Specular reflection

Phong model provides the simplest, most strait forward algorithm for specular reflection.

It goes like this:

(Theory only, actual code will come later):

R = (2 * dotNL * N) – L

R : specular reflection vector;

dotNL : dot product of vertex normal and light direction vectors;

N : vertex normal vector;

L : light direction vector;

### Line by line

Let’s see why and how, and don’t worry – it’s all pictures! Even a hamster can catch up!

**Step 1: dot**

Let's use two simple 2d vectors, separated by 45 degrees

As mentioned, the dot product is cos(45)

**Step 2: midway vector**

Our midway vector value is now: (0, 2*cos(45) )

(Do not confuse this with half-way vector used in Blinn)

**Step 3: the reflection vector**

To achieve a reflection vector, we will have to add a negative L vector (or subtract L, however you see it)

The R vector is our reflection vector, and it equals ( -cos(45), cos(45) )

Its values are opposite to L along X.

### Calculating the view

The second part of creating specular is to calculate the factor of the view angle (after we calculated the directional vector from the vertex to the camera) on the specular distribution depending on the angle between the reflection ray and the direction to the view, and according to a predefined factor we choose in advance.

In order to do that, we take the dot product of the direction of the view and the reflection vector, and pow() it by desired “shininess”.

V – The view’s direction

dotVR – Dot product of V and R

## The actual code

So, now we know what to do, let’s do it!

First thing we have to do, is to add a global variable that will automatically track the camera position (when we export to ogre we will have one provided from ogre, don’t worry)

float4 cameraPos : CameraPosition;

Secondly, we need to calculate the direction from the vertex to the camera:

(again, since it’s a directional vector, we normalize it)

float3 viewDir = normalize( cameraPos - worldpos );

Now we calculate the reflection vector:

float3 ref = (input.normal * 2 * dotNL) - lightDir;

( dotNL and lightDir had already been calculated, you didn’t forget right? It's not like you fell asleep by now or something)

Lastly, we calculate the dotVR (view and reflection dot), calculate the specular light, and add the product to the output color:

float dotRV = dot(ref, viewDir); float spec = pow(saturate(dotRV), [your specular factor here] ); output.finColor = diff * diffColor + spec * specColor + ambientColor;

Note that I used saturate on dotRV as well. If I wouldn’t have, we would have a dark spot that mirrors the specular in position and behavior swallowing ambient light in the back of the object.

It’s kind of cool though (the ambient devouring blackness), try it.

## The state of things

By now, two program looks something like this:

float4 specColor : COLOR; … vertexOut mainVS(vertexIn input) { vertexOut output = (vertexOut )0; output.position = mul(input.position, worldViewProj_m); float3 worldpos = mul(input.position, world_m); float3 lightDir = normalize( lightPos - worldpos ); float3 viewDir = normalize( cameraPos - worldpos ); float dotNL = dot ( lightDir, input.normal ); float diff = saturate(dotNL); float3 ref = (input.normal * 2 * dotNL) - lightDir; float dotRV = dot(ref, viewDir); float spec = pow(saturate(dotRV),15); output.finColor = diff * diffColor + spec*specColor + ambientColor; return output; } float4 mainPS( pixelIn input ) : COLOR { return input.finColor; }

Our first per vertex shader is now complete!

You can try and add some other factors, such as light intensity or whatever…

The result you should be getting. I divided the diffuse by 3 to make the specular show how ugly it is when using per vertex.

With higher shiny number you can see the effect of per vertex on specular better.

I know, it’s ugly, its supposed to be.

## ‘Exporting’ to OGRE

Unlike our earlier shaders, this shader actually has substance. This means you have to be more acute with your exporting skillz.

Make sure that:

- You provide all needed externals to each program that need them (globals in FXC)
- Remember to swap mul() components.

### About lights

When retrieving lights from OGRE via the scripts, you need to add an index – this tells ogre which light to choose. The index run from 0 to n, when 0 is the closest to the object and n is the farthest.

Such as:

param_named_auto lightPos light_position 0

When you finish, your files should look something like these:

The HLSL file:

struct vertexIn { float4 position : POSITION; float3 normal : NORMAL; }; struct vertexOut { float4 position : POSITION; float4 finColor : COLOR; }; struct pixelIn { float4 finColor : COLOR; }; vertexOut mainVS( vertexIn input, uniform float4x4 worldViewProj_m, uniform float4x4 world_m, uniform float4 cameraPos, uniform float4 lightPos, uniform float4 ambientColor, uniform float4 diffColor, uniform float4 specColor, uniform float shiny ) { vertexOut output = (vertexOut)0; output.position = mul(worldViewProj_m, input.position); float3 worldpos = mul(world_m, input.position); float3 lightDir= normalize( lightPos - worldpos ); float3 viewDir = normalize( cameraPos - worldpos ); float dotNL = dot ( lightDir, input.normal ); float diff = saturate(dotNL); float3 ref = (input.normal * 2 * dotNL) - lightDir; float dotRV = dot(ref, viewDir); float spec = pow(saturate(dotRV), shiny); output.finColor = diff* diffColor + spec*specColor + ambientColor; return output; } float4 mainPS( pixelIn input ) : COLOR { return input.finColor; }

The material script

vertex_program vv hlsl { source balrg.hlsl entry_point mainVS target vs_1_1 default_params { param_named_auto worldViewProj_m worldViewProj_matrix param_named_auto world_m world_matrix param_named_auto cameraPos camera_position param_named_auto lightPos light_position 0 param_named_auto ambientColor ambient_light_colour 0 param_named_auto diffColor light_diffuse_colour 0 param_named_auto specColor light_specular_colour 0 param_named shiny float 25.0 } } fragment_program pp hlsl { source balrg.hlsl entry_point mainPS target ps_2_0 } material textry { technique { pass { vertex_program_ref vv { } fragment_program_ref pp { } } } }