]]>Well for me these Shader Effects Series are the best tutorials I found on the web. Clean, concise, well presented and with live demos, what else one could ask?

Ah...we need MORE

]]>There is a slight mistake in specular element calculation. In vertex shader you determine view vector as follows:

vWorldVertex = ModelMatrix * vec4(Vertex * ModelScale, 1.0); vec4 viewVertex = ViewMatrix * vWorldVertex; vViewVec = normalize(-viewVertex.xyz);

This is not correct and will give you Vertex->Camera vectors in View space, which you later use with the vertex normal and light vector in worlds space.

Instead you can transfer uniform camera position into the vertex shader and calculate the correct Vertex-Camera vector in the World space:

... uniform vec3 vec3CameraWorldPos; ... vViewVec = normalize(vec3CameraWorldPos - vWorldVertex);

Another method is to use inverted view matrix on (0,0,0) vector but I believe it would be slower.

Great article btw.

]]>

Start WebGL DemoSorry, it appears you don't have support for WebGL.

In order to run this demo, you must meet the following requirements.

- You are running the latest version of Mozilla Firefox, Google Chrome, or Safari.
- You have a WebGL compatible video card with the latest drivers.
- Your video card is not blacklisted. You can check the current blacklist on Khronos.

Some browsers may require additional configuration in order to get WebGL to run. If you are having problems running this demo, visit the following sites.

- Firefox: How to force-enable blocked graphics features.
- Chrome: How to enable WebGL with a blacklisted graphics card.
- Safari: How to enable WebGL.

<!-- WebGL Canvas -->

<!-- Controls -->

<!-- Object Ambient -->Ambient Colour

<!-- Object Diffuse -->Diffuse Colour

<!-- Object Specular -->Specular Colour

<!-- Object Shininess -->Shininess

<!-- Light Colour -->Light Colour

<!-- Light Attenuation -->Attenuation

<!-- Other Options -->Shading Type

Gouraud

Phong

/// <summary>

/// Light source structure.

/// <summary>

struct LightSource

{

vec3 Position;

vec3 Attenuation;

vec3 Direction;

vec3 Colour;

float OuterCutoff;

float InnerCutoff;

float Exponent;

};/// <summary>

/// Material source structure.

/// <summary>

struct MaterialSource

{

vec3 Ambient;

vec4 Diffuse;

vec3 Specular;

float Shininess;

vec2 TextureOffset;

vec2 TextureScale;

};/// <summary>

/// Attributes.

/// <summary>

attribute vec3 Vertex;

attribute vec2 Uv;

attribute vec3 Normal;/// <summary>

/// Uniform variables.

/// <summary>

uniform mat4 ProjectionMatrix;

uniform mat4 ViewMatrix;

uniform mat4 ModelMatrix;

uniform vec3 ModelScale;uniform int NumLight;

uniform LightSource Light[4];

uniform MaterialSource Material;/// <summary>

/// Gets or sets whether vertex lighting (Gouraud Shading) or

/// fragment lighting (Phong Shading) is used.

/// <summary>

uniform int ShadingType;/// <summary>

/// Varying variables.

/// <summary>

varying vec4 vWorldVertex;

varying vec3 vWorldNormal;

varying vec2 vUv;

varying vec3 vViewVec;

varying vec4 vColour;/// <summary>

/// Vertex shader entry.

/// <summary>

void main ()

{

vWorldVertex = ModelMatrixvec4(VertexModelScale, 1.0);

vec4 viewVertex = ViewMatrixvWorldVertex;viewVertex;

gl_Position = ProjectionMatrix`vUv = Material.TextureOffset + (Uv * Material.TextureScale); vWorldNormal = normalize(mat3(ModelMatrix) * Normal); vViewVec = normalize(-viewVertex.xyz); if ( ShadingType == 0 ) { vColour = vec4(Material.Ambient, 0.0); for (int i = 0; i < 4; ++i) { if ( i >= NumLight ) break; // Calculate diffuse term vec3 lightVec = normalize(Light[i].Position - vWorldVertex.xyz); float l = dot(vWorldNormal, lightVec); if ( l > 0.0 ) { // Calculate spotlight effect float spotlight = 1.0; if ( (Light[i].Direction.x != 0.0) || (Light[i].Direction.y != 0.0) || (Light[i].Direction.z != 0.0) ) { spotlight = max(-dot(lightVec, Light[i].Direction), 0.0); float spotlightFade = clamp((Light[i].OuterCutoff - spotlight) / (Light[i].OuterCutoff - Light[i].InnerCutoff), 0.0, 1.0); spotlight = pow(spotlight * spotlightFade, Light[i].Exponent); } // Calculate specular term vec3 r = -normalize(reflect(lightVec, vWorldNormal)); float s = pow(max(dot(r, vViewVec), 0.0), Material.Shininess); // Calculate attenuation factor float d = distance(vWorldVertex.xyz, Light[i].Position); float a = 1.0 / (Light[i].Attenuation.x + (Light[i].Attenuation.y * d) + (Light[i].Attenuation.z * d * d)); // Add to colour vColour.xyz += ((Material.Diffuse.xyz * l) + (Material.Specular * s)) * Light[i].Colour * a * spotlight; } } vColour.w = Material.Diffuse.w; }`

}

## ifdef GL_ES

`precision highp float;`

## endif

/// <summary>

/// Light source structure.

/// <summary>

struct LightSource

{

vec3 Position;

vec3 Attenuation;

vec3 Direction;

vec3 Colour;

float OuterCutoff;

float InnerCutoff;

float Exponent;

};/// <summary>

/// Material source structure.

/// <summary>

struct MaterialSource

{

vec3 Ambient;

vec4 Diffuse;

vec3 Specular;

float Shininess;

vec2 TextureOffset;

vec2 TextureScale;

};/// <summary>

/// Uniform variables.

/// <summary>

uniform int NumLight;

uniform LightSource Light[4];

uniform MaterialSource Material;/// <summary>

/// Gets or sets whether vertex lighting (Gouraud Shading) or

/// fragment lighting (Phong Shading) is used.

/// <summary>

uniform int ShadingType;/// <summary>

/// Varying variables.

/// <summary>

varying vec4 vWorldVertex;

varying vec3 vWorldNormal;

varying vec2 vUv;

varying vec3 vViewVec;

varying vec4 vColour;/// <summary>

/// Fragment shader entry.

/// <summary>

void main ()

{

if ( ShadingType == 1 )

{

// vWorldNormal is interpolated when passed into the fragment shader.

// We need to renormalize the vector so that it stays at unit length.

vec3 normal = normalize(vWorldNormal);`vec3 colour = Material.Ambient; for (int i = 0; i < 4; ++i) { if ( i >= NumLight ) break; // Calculate diffuse term vec3 lightVec = normalize(Light[i].Position - vWorldVertex.xyz); float l = dot(normal, lightVec); if ( l > 0.0 ) { // Calculate spotlight effect float spotlight = 1.0; if ( (Light[i].Direction.x != 0.0) || (Light[i].Direction.y != 0.0) || (Light[i].Direction.z != 0.0) ) { spotlight = max(-dot(lightVec, Light[i].Direction), 0.0); float spotlightFade = clamp((Light[i].OuterCutoff - spotlight) / (Light[i].OuterCutoff - Light[i].InnerCutoff), 0.0, 1.0); spotlight = pow(spotlight * spotlightFade, Light[i].Exponent); } // Calculate specular term vec3 r = -normalize(reflect(lightVec, normal)); float s = pow(max(dot(r, vViewVec), 0.0), Material.Shininess); // Calculate attenuation factor float d = distance(vWorldVertex.xyz, Light[i].Position); float a = 1.0 / (Light[i].Attenuation.x + (Light[i].Attenuation.y * d) + (Light[i].Attenuation.z * d * d)); // Add to colour colour += ((Material.Diffuse.xyz * l) + (Material.Specular * s)) * Light[i].Colour * a * spotlight; } } gl_FragColor = clamp(vec4(colour, Material.Diffuse.w), 0.0, 1.0); } else { // Set colour gl_FragColor = vColour; }`

}

## Lighting Basics

## Abstract

This shader demonstrates the basic lighting formula using ambient, diffuse and

specular reflection, light attenuation, and spotlight effects. This article will describe

the basics behind lighting and cover the differences between Gouraud and Phong shading

techniques.## Diffuse Reflection

We see objects because objects reflect some of the light energy they receive. When

light strikes a surface, it reflects some of that energy back into the environment. This

is illustrated in the following diagram.

A mathematician by the name Johann Lambert in 1760 discovered this property

of diffuse reflection and deduced the following formula.

^{[1]}Where

is the unit normal vector of the surface

is the

unit direction vector from a point on the surface to the light source.is the intensity of the light source.

is the calculated intensity of the diffusely reflected light.

If you assume your light intensity remains at a constant of 1.0, then the

formula reduces to a simple dot product between the surface normal and the direction

vector from the surface to the light source. The dot product between two vectors will

produce a positive value if both vectors are within 90 degrees of each other, 0 if the

vectors are perpendicular to each other, or a negative value if both vectors are greater

than 90 degrees apart. If both vectors are of unit length (ie: normalized), then the dot

product will return a value -1.0 <= I_{D}<= 1.0.

If the value is less than or equal to 0, then that part of the object is shaded. If the

value is greater than 0, then that part of the object is illuminated based on the diffuse

intensity value calculated for that particular point. This is defined in the following

formula.

Where

is the red, green, and

blue diffuse colour of the objectis your Lambert diffuse

intensity value.is the final diffuse

colour.If you have multiple light sources, you simply sum the diffuse reflection

contributions from each light source and clamp the result to your colour depth. Shaders

use floating point values, so you would clamp the result between 0.0 and 1.0.## Improving Diffusion Reflection

Constant diffuse factor vs bump map

The diffuse reflection formula discussed in this article and demonstrated

in the WebGL demo use a constant diffuse value. This produces a smooth surface that lacks

any texture such as cracks, bumps, pores, scratches, etc. In real life, all surfaces

exhibit irregularities causing light to reflect at different angles. This is illustrated

in the following diagram.

This can be represented using a high number of displaced polygons, but that

comes with a computationally expensive process. A more efficient way to improve diffuse

reflection quality is to use bump maps. Bump maps are image files that contain the

perturbed surface normal vectors encoded in RGB space. With a bump map, you calculate the

diffuse intensity value for the surface normal as discussed above in addition to

calculating the diffuse intensity value of the perturbed normal stored in the bump map.

You then combine (multiply) the two in order to produce the final intensity value. The

following demonstrates what a bump map in RGB space looks like and what the final

lighting calculations would produce.

From left to right: Displacement map, bump map conversion, final lighting

resultThis topic goes outside the scope of this article, but it is mentioned to

give you insight how to improve the visual quality of diffuse reflection. Topics you can

investigate include dot 3 bump mapping (aka normal mapping) and the more advanced

parallax bump mapping.## Specular Reflection

Specular reflection is what gives you that shinny look that you often see

on billiard balls, leather sofas, or metal surfaces. It can be really shinny and give off

a bright glare or it can be really dull and lower the contrast of a particular object.

There are many empirical formulae for calculating specular reflection, but this shader

focuses on the more popular Phong reflection model. The Phong reflection model was

developed by Bui Tuong Phong at the University of Utah in 1973^{[2]}. It is

calculated using the following formula.

Where

is the unit reflection

vector calculated from the surface normal and the light direction vector.is the unit light

direction vectoris the unit surface

normal vectoris the unit view

direction vectoris the shininess term. A

high value produces a smaller glare while a low value produces a larger glare.is the calculated intensity of the specular reflected light.

Unlike diffuse reflection where light reflects at multiple angles, specular

lighting only reflects at one angle, which is relative to the viewer and light direction

vectors. The dot product of these two unit vectors will produce a value 0.0 <=

I_{S}<= 1.0. From this we deduce that a specular highlight will be at its

strongest when the reflected light vector is aligned with the view direction vector and

at its weakest when the reflected light vector is greater than or equal to 90 degrees

from the viewer. The formula to apply the intensity value to the specular colour is

defined below.

Where

is the red, green, and

blue specular colour of the objectis your calculated

specular intensity value.is the final specular

colour.As with diffuse reflection, if you have multiple light sources you simply

sum the specular reflection contributions from each light source and clamp the result to

your colour depth.## Improving Specular Reflection

Using a constant specular colour throughout an object does not always work

out well. For example, the human body doesn't shine equally across all body parts. A bald

head is more likely to give off a polished shine than your arms or hands. A sweaty body

is will also give off more shine compared to a dry body. In order to define these areas

and treat them differently, you need to create and supply your shader with a specular

map. This map will control the specular intensities that are permitted for a particular

pixel on your object. The following example demonstrates the rendering differences with

and without a specular map.

From left to right: Face without specular map, specular map, face with

specular mapIn addition to specular maps, normal maps also help bring out specular

reflections. The bumpiness of the material will alter the direction light reflects off

the surface, giving the following result.

Specular reflection due to bump mapping

This is a common technique applied to rendering oceans. To reduce the

polygon count, the waves of an ocean are rendered using low and high frequency normal

maps. The specular highlights produced by these normal maps produces a more realistic

body of water. This topic goes outside the scope of this article, but it is mentioned to

give you insight how to improve the visual quality of specular reflection.## Ambient Lighting

Sphere rendered without ambient lighting and with ambient lighting.

When light is reflected off a surface, that light can be further reflected

by other objects in the area, which then other objects reflect that light and so on until

the energy dissipates. This is known as ambient lighting (or background lighting) and it

plays a crucial role in generating realistic imagery. The most basic way to represent

ambient lighting is to illuminate the object as a whole with a constant value, but this

can create less than realistic images. When combined with smart texturing to cover up the

obvious constant ambient factor, it can sometimes be enough to improve the image

quality.Since ambient is additional lighting from the environment, the ambient

colour is added to the result of the lighting equation. This produces the following

formula.Where

is the red, green, and

blue ambient colour value to add.is the remainder of the

lighting formula that will sum diffuse, specular, attenuation, and spotlightsis the final colour of

the pixel.## Improving Ambient Lighting

Diffuse reflection uses bump maps to improve quality and specular

reflection uses specular maps. Ambient quality can be improved in a similar way by using

ambient occlusion maps. Ambient occlusion is an empirical technique for determining how

much ambient lighting a pixel on an object receives based on its view of the outside

world. These maps even include the amount of shadowing received due to light sources

being occluded by other objects.Image on the left rendered without AO and with AO on the right.

Ambient occlusion maps are calculated based on the assumption objects

remain stationary. If an object is transformed, then the calculated ambient values are no

longer valid. One way around that is to use screen space ambient occlusion (SSAO), which

is a real-time technique applied in a fragment shader to approximate ambient occlusion

values based on the recorded depth values. It requires a fair amount of processing power

to calculate however.This topic goes outside the scope of this article, but it is mentioned to

give you insight how to improve the visual quality of ambient lighting.## Attenuation

Light dissipates over the distance it travels and more so when it bumps

into other particles such as dust, walls, gases, etc. This visual trait can be simulated

by multiplying the calculated light intensity value by an attenuation factor that will

reduce the intensity of light based on the distance it travelled from the light source to

the surface of the object. This is represented using the following formula.Where

is an attenuation

constant.is the calculated linear

attenuation and is the linear constant

used in that calculation.is the calculated

quadratic attenuation andis the quadratic

constant used in that calculation.is the distance between

the surface of the object and the light source.is the calculated

attenuation factor that will multiply the light intensity value.Each attenuation factor has a certain effect on luminosity. The constant

attenuation factor doesn't dim the light over distance, but instead adjusts its

intensity. The linear attenuation constant dims the light linearly over distance, which

is approximate to a light source that emits in a vacuum. The quadratic constant dims the

light more per distance travelled, which is typical in an environment where light

collides with many particles. This is often used to simulate weak light sources such as

flashlights and lanterns.## Spotlights

Spotlights extend the lighting equation by cutting off the light source

after a certain angle. This is shown in the formula below.

Where

is the unit light

direction vector.is the unit direction

vector from the light source to the vertex.is the spotlight

exponent, which increases or decreases the spotlight's brightness factor.is the calculated angle

between the light source and the vertex.

This is similar to the diffuse reflection formula except you are examining

the situation from the light's point of view instead of the vertex point of view. If the

vertex lies within the cone of influence, it will be lit, otherwise it will be shaded. In

addition to the above, sometimes you want a sharp edge and other times you want a nice

falloff. The effect is illustrated below.

Spotlight without falloff on the left and with falloff on the right

A falloff can be calculated by factoring an inner ring and an outer ring

in the spotlight's cone of influence. This is illustrated below.

The area inside the inner ring is fully lit, whereas the falloff area will

dim the luminosity as you approach the outer ring. This can be added to the spotlight

formula.

Where

is the outer cutoff

angle, represented in radians. Ex: cos(45.0 * PI / 180.0).is the inner cutoff

angle, which is less than or equal to the outer cutoff angle.is the calculated

spotlight angle from the previous equation.is the calculated

spotlight intensity clamped to the range 0.0 and 1.0.

## Putting it all Together

We've covered diffuse reflection, specular reflection, ambient lighting,

attenuation, and spotlights. When combining all these elements, we get the final lighting

formula.

Where

is the ambient

component.is the diffuse

component.is the specular

component.is the light colour.

is the attenuation

factor.is the spotlight

factor.is the final colour for

that pixel.## Shading Techniques

There are two ways to apply the lighting formula.

## Gouraud Shading

Gouraud shading is an interpolation technique whereby the colour values

calculated at the vertices of a polygon are linearly interpolated to fill the rest of the

polygon. This is computationally inexpensive as lighting calculations are performed at

the vertices rather than at each pixel, which is the case with Phong shading. There is

some quality loss when using a low-polygon model however. Both diffuse and specular

reflections can appear blocky due to interpolating between so few polygons. An example of

this is shown below.The first image demonstrates specular highlights with Gouraud shading. The

second image demonstrates specular highlights with Phong shading. Since there are more

pixels than polygons when rendering the image, the linearly interpolated values

calculated in the vertex shader display quality loss. If however you have an object with

a sufficiently high polygon count, typically a 1:1 polygon per pixel ratio, then you

effectively eliminate the problem.Here is the same object rendered with Gouraud shading, but with a

sufficiently higher polygon count. In this particular case, the performance benefits of

Gouraud shading are eliminated by the fact that more memory and vertex processing are

required to produce this level of quality.## Phong Shading

Phong shading, not to be confused with the Phong reflection model, is an

interpolation technique whereby the surface normal is interpolated over the polygon for

purposes of shading the object. By interpolating normals instead of colour values, as is

the case with Gouraud shading, you end up with a higher quality image at the expense of

additional computations per pixel. One thing to note is that linearly interpolating

normals will not guarantee unit length throughout. You must renormalize the normal vector

in order to restore this property. Failing to do so will result in improper lighting

intensities when interpolating normals with large angular differences, such as the vertex

normals at each corner in a cube.## References

1. Wikipedia Editors (2012-02-10). "Lambertian reflectance".

Wikipedia. Retrieved 2012-02-11.2. Wikipedia Editors (2011-11-27). "Phong shading" . Wikipedia. Retrieved 2012-02-11.

The source code for this project is made freely available for download. The ZIP package below contains both the HTML and JavaScript files to replicate this WebGL demo.

The source code utilizes the Nutty Open WebGL Framework, which is an open sourced, simplified version of the closed source Nutty WebGL Framework. It is released under a modified MIT license, so you are free to use if for personal and commercial purposes.

Download Source