# Point Light Tutorial

## Per Vertex Shader

## Introduction

This article discusses the vertex and fragment shaders
for the Point Light
example.
The *vertex* shader provides most of the
functionality for the Point Light
example.
The Fragment Shader Point Light
and Face Mapped Cube
examples process lighting primarily in the *fragment* shader.
Yet all three examples follow the same pattern.

This tutorial explains the steps which generate point lighting. This article briefly discusses the trade off between processing in the vertex shader, versus the fragment shader.

Seven Thunder Software didn't create the algorithm for the shaders presented here. Light algorithms are based on observations recorded as far back as 10 AD, with detail provided by others along the way.

## Tradeoff Between Speed and Detail

Vertex shaders usually run less often than fragment shaders.
Processing in the vertex shader should provide slightly more
speed. However vertex shaders often produce less detail
than fragment shaders.
The Point Light
example processes lighting in the vertex shader.
The
cubes in the *Per Fragment* Point Light
and
*Per Vertex* Point Light
examples appear nearly identical except for color.
More complex models processed in the fragment shader
usually show more detail than models processed in the vertex shader.
For example compare the capsule
with lighting processed in the
vertex shader
to the capsule with lighting processed in the
fragment shader, above.

The fragment shader point light functionality is nearly identical to the vertex shader point light functionality. However most of the processing within the vertex point light shader was moved to the fragment shader. The vertex shader then passes varyings for the modified position and normal through to the fragment shader. In other words both types of point light shaders follow the same steps. However the fragment shader accomplishes those steps in the fragment shader. The vertex shader accomplishes those steps in the vertex shader.

## Model and Normal Matrices

The model matrix represents rotation and translation for cube vertices in the example projects. The normal matrix represents relative rotation and translation for normals associated with vertices in the cube.

The JavaScript for this example uploads an
attribute with normal coordinates per vertex.
Each frame of animation uploads a *normal matrix*.
The normal matrix is a 3 x 3 matrix derived from
the inverted and transposed *model matrix*.

That's a complicated way of saying the normal matrix represents a reduced model matrix. The 3 x 3 normal matrix contains the first three entries of the first three rows of the 4 x 4 model matrix.

The following two listings show a sample 4 x 4 model matrix, followed by the transposed and inverted 3 x 3 normal matrix.

### Model Matrix

0.993,0.095,-0.06,0 0,0.528,0.849,0 0.112,-0.844,0.524,0 0,4.246,-7.641,1

### Normal Matrix

0.993,0.095,-0.06 -0.001,0.528,0.849 0.112,-0.844,0.524

## Vertex Shader

### Varyings

The vertex shader sends
two varyings out for use
within the fragment shader.
Varying `v_tex_coord0`

simply receives texel
coordinates from
attribute `a_tex_coord0`

.
Varying `v_lightweighting`

is the focus of this tutorial.
The vertex shader
assigns a value to `v_lightweighting`

representing the *amount of light color* to
apply for each vertex.

varying vec3 v_lightweighting; varying vec2 v_tex_coord0;

### Attributes

Attribute input to the vertex shader
include `vec4`

attributes for vertex coordinates,
associated `vec3`

normal coordinates,
as well as texel attributes. The texel attributes
are simply passed through a varying to
the fragment shader.
The following listing includes attribute
declarations for vertex coordinates,
normals and texels.

attribute vec4 a_position; attribute vec3 a_normal; attribute vec2 a_tex_coord0;

### Constants

Constants within the vertex shader
include a `vec3`

representing ambient
light,
a `vec3`

representing
the light vector,
and a `vec3`

representing
the light color.
The following listing
shows the constant declarations.

const vec3 c_ambient = vec3( 0.2, 0.2, 0.2 ); const vec3 c_light_location = vec3( -0.5, -0.5, 1.0 ); const vec3 c_light_color = vec3( 0.8, 0.8, 0.8 );

### Uniforms

Uniform input to the vertex shader
include a 4 x 4 model view matrix `um4_matrix`

,
3 x 3 normal matrix `um3_nmatrix`

,
and a perspective projection
matrix `um4_pmatrix`

.
The model view matrix
`mat4 um4_matrix`

represents rotation and translation
per frame.
The normal matrix
`mat3 um3_nmatrix`

*also* represents
rotation and translation per frame.
However multiply the 3 x 3 normal matrix
with a `vec3`

normal
and the 4 x 4 model view matrix with
`vec4`

vertex coordinates.

Determine the location of the current vertex modified by translation and rotation. The following line multiplies the model view matrix with the vertex coordinate.

vec4 v4_model_position = um4_matrix * a_position;

Determine the direction of the normal modified by translation and rotation. The following line multiplies the normal matrix with the normal attribute.

vec3 v3_normal = um3_nmatrix * a_normal;

### Vectors

A GLSL `vec3`

represents three floating point values.
Developers can use `vec3`

in a number of ways.
For example shaders might
access the values of a `vec3`

as vertex coordinates with X,Y,Z values,
color channels with R,G,B values, or as a
vector.
A vector is a signed
displacement.
Vectors represent
*direction* and magnitude or length.
For example a `vec3`

with the following three values `1.0,3.0,2.0`

,
represent a displacement of one unit
on the X axis, three units on the Y axis,
and two units on the Z axis.
Apply the Pythagorean theorem to determine the magnitude.
The vector symbol is a line with an arrow at one end.
Vectors point in specific directions.
The arrow represents the direction of the vector.

*First* the vertex shader declares a `vec4`

which represents the transformed position
of the current vertex.
This article previously displayed the following line which assigns
the rotated or translated vertex coordinate
to `vec4 v4_model_position`

.
The shader needs `v4_model_position`

for the next step.

vec4 v4_model_position = um4_matrix * a_position;

*Second*
the vertex shader calculates
vector `v3_subtraction_vector`

as the *difference between* the transformed vertex vector
and the constant light direction vector.
The shader
subtracts transformed vertex coordinates
from the light direction
vector.
Subtract
`v4_model_position`

from `c_direction_light`

.

vec3 v3_subtraction_vector = normalize( c_direction_light - v4_model_position.xyz );

Imagine `c_light_direction`

and `v4_model_position`

touch at some point and form two edges of a triangle.
Vector `v3_subtraction_vector`

*forms a triangle*
of three edges.
Now the shader has a vector
which represents the difference
between the current vertex position
and the angle of the light.
In other words the vector describes the relationship
between the vertex and the light.

The built-in function
`normalize()`

is applied
to the result.
The `normalize()`

function
returns a vector with magnitude of one.
A vector with magnitude or length
of one is called a unit vector.
The direction of the normalized
vector remains the same.

Now find the relationship
between the normal and `v3_subtraction_vector`

.
Use the dot product to determine
how much light to apply to this fragment.

### Dot Product

*Third* take the dot product
of the transformed normal
and the subtraction vector
`v3_subtraction_vector`

.
The dot product indicates the
amount of similarity between
the normal and the subtraction vector.

With two unit vectors
dot product returns values between
negative one and positive one.
If the dot product returns *zero*,
then two vectors are perpendicular.
If the dot product returns a value
*greater than zero*,
the two vectors point about
the same direction.
Values greater than zero
indicate an acute angle.
A dot product of one
indicates two vectors are parallel.
Values less than zero
indicate an obtuse angle.

The vertex shader uses
dot product to determine how
much light color the fragment
shader will mix with
the sampler color.
If the dot product returns zero,
the vectors are perpendicular,
*apply zero brightness* from
the light color to the
current fragment.
If the dot product returns
a value *greater than zero*,
the two vectors point
about the same direction.
Multiply the value
returned by the dot
product and the
light color, then
*apply that value to the fragment color*.

The final result provides gradual shading across each surface, taking into account the vertex position, normal and light direction. The following listing demonstrates taking the dot product between the transformed normal and the subtraction vector.

dot( v3_normal, v3_subtraction_vector )

Call the built-in function
`max()`

to return only non negative numbers.
Assign the result to the floating
point number
`f_light_weighting`

.

float f_light_weighting = max( dot( normalize(v3_normal), v3_subtraction_vector ), 0.0 );

## Point Light Diagram

Two vector operations determine the amount of light color to apply to
a fragment. Subtract the vertex vector from the light vector.
The result is labeled Subtraction Vector

in the following
diagram.
Call the `dot()`

product function, to find the
amount of similarity between the Subtraction Vector

and the normal vector.
If the angle between vectors is acute, then apply light color.
The more similar the vectors are, the more light color applies for this vertex.
If the vectors are perpendicular or obtuse, then apply zero light color.

*Last* the vertex shader determines the amount
of light color to send out for
the fragment shader.
Multiply the light color
by the result of the dot product.
Add in the ambient light.
The sum equals the amount
of light to apply for this vertex.
Assign the result to
the varying `v_lightweighting`

.
The GPU interpolates values
then sends them on to the fragment
shader through the varying
with the same name; `v_lightweighting`

.

v_lightweighting = c_ambient + c_light_color * f_light_weighting;

The vertex shader also
multiplies the modified vertex coordinate
by the perspective projection matrix.
Last the built in variable
`gl_Position`

receives the modified vertex coordinate.
The listing for the entire vertex shader follows.

attribute vec4 a_position; attribute vec2 a_tex_coord0; varying vec2 v_tex_coord0; attribute vec4 a_normal; // Translated or // rotated normal matrix. uniform mat3 um3_nmatrix; // Translated or // rotated model matrix: uniform mat4 um4_matrix; // PP matrix. uniform mat4 um4_pmatrix; const vec3 c_ambient = vec3( 0.2, 0.2, 0.2 ); const vec3 c_light_location = vec3( -0.5, -0.5, 1.0 ); const vec3 c_light_color = vec3( 0.8, 0.8, 0.8 ); varying vec3 v_lightweighting; void main(void) { // The position of the // vertex after rotation and translation. vec4 v4_model_position = um4_matrix * a_position; // Determine the direction of the normal // relative to the current // rotation and translation. vec3 v3_normal = um3_nmatrix * a_normal; // Find the vector // which represents the // difference between // the light vector // and the position // of the current vertex. // The vector from // the vertex coordinate // to the light direction. vec3 v3_subtraction_vector = normalize ( c_light_location - v4_model_position.xyz ); // The amount of light. // Dot product of the normal // direction and light direction. // max() restricts result // to non negative numbers. float f_light_weighting = max( dot( v3_normal, v3_subtraction_vector ), 0.0 ); // Varying output. v_tex_coord0 = a_tex_coord0; // Assign the amount // of light to apply // to this vertex, // to the varying: v_lightweighting = c_ambient + c_light_color * f_light_weighting; gl_Position = um4_pmatrix * v4_model_position; }

## Fragment Shader

Very little happens in the fragment shader compared to the vertex shader

Varying output from the
vertex shader become input
for the fragment shader,
after the GPU interpolates values.
The fragment shader receives
the `vec2`

varying
`v_tex_coord0`

as texel coordinates to sample the
texture.
The fragment shader receives
the `vec3`

varying
`v_lightweighting`

which represents the amount
of light to apply to this fragment.

The fragment shader samples
a texture.
The fragment shader multiplies the RGB values
of the sample, by the varying `v_lightweighting`

.
Maintain the sample's original alpha value.

gl_FragColor = vec4( color0.rgb * v_lightweighting, color0.a );

The entire fragment shader source code follows.

precision mediump float; // Texel. varying vec2 v_tex_coord0; // The amount of // light to apply // to this fragment. varying vec3 v_lightweighting; uniform sampler2D u_sampler0; void main(void) { // Sample the texture. vec4 v4_color0 = texture2D( u_sampler0, v_tex_coord0 ); // Multiply the // sample color's RGB // values by the amount // of light to apply. gl_FragColor = vec4( v4_color0.rgb * v_lightweighting, v4_color0.a ); }

## Summary

This article discussed the vertex and fragment shaders
for the Point Light
example.
The *vertex* shader provides most of the
functionality for the Point Light
example.
The Fragment Shader Point Light
and Face Mapped Cube
examples process lighting primarily in the *fragment* shader.
Yet all three examples follow the same pattern.

This tutorial explained the steps which generate point lighting. This article briefly discussed the trade off between processing in the vertex shader, versus the fragment shader.

Have fun and love learning! See more WebGL lighting examples.