# Lighting rendering — simulating lighting effect with canvas

Time：2020-9-22

## illumination

We can see objects because light shines on them and then reflects back into our eyes. There are many influencing factors: the position of the observer, the position of the light source, the color of the light, the color of the surface of the object, the material and the roughness, etc. In the future, we will explore in detail how to simulate the material of an object. In this article, we will only discuss the light source.

## Parallel light source

The scale of the sun is very large relative to the earth, so it can be considered that the light from the sun is parallel, that is, the sun is a parallel light source.

It is very simple to simulate the illumination of a parallel light source. When the light is perpendicular to the plane, that is, the light direction is at a 90 degree angle to the plane, the illumination is the strongest. If the angle of illumination keeps increasing (or the angle between the light and the plane becomes smaller and smaller), the illumination will also become weaker. When the light direction is completely parallel to the plane, no light can shine on the plane, and the light intensity becomes 0.

It can be concluded that the illumination of the parallel light is related to two directions: the direction of the light and the direction of the illuminated plane.

We use a vector perpendicular to the plane to describe the orientation of the plane. In graphics, this vector is generally called “normal vector”.

We can calculate the change of light intensity by the “dot multiplication” of vectors.

Dot multiplication, also known as scalar product, is a binary operation that takes two vectors on a real number R and returns a real scalar. The operation rule of point multiplication is very simple, just sum the product of the corresponding coordinates of two vectors. Here we calculate the three-dimensional vector, we use the array to represent the vector, write a simple method to calculate the point multiplication:

``````/**
*Point multiplication
*@ param {array < number >} V1 vector v1
*@ param {array < number >} V2 vector V2
*@ return {number} multiplication result
*/
function dot( v1, v2 ) {
return v1[ 0 ] * v2[ 0 ] + v1[ 1 ] * v2[ 1 ] + v1[ 2 ] * v2[ 2 ];
}``````

There are several important vector operations we will also use, here we define in advance, in order to reduce the space, here omit the specific implementation, code can see the final instance source code.

``````/**
*Convert vector to unit vector
* @param {Array<number>} v
*@ return {array < number >} unit vector
*/
function normalize( v ) { /* ... */ }

/**
*Subtracting two vectors
* @param {Array<number>} v1
* @param {Array<number>} v2
* @return {Array<number>}
*/
function sub( v1, v2 ) { /* ... */ }

/**
*Calculate the inverse vector of a vector
* @param {Array<number>} v
* @return {Array<number>}
*/
function negate( v ) { /* ... */ }``````

We assume that the top left corner of the page is the origin o, the right direction is the positive direction of X axis, the lower direction is the positive direction of Y axis, and the outward direction of vertical screen is the positive direction of Z axis. We can define a plane with a width of 500 and a height of 500

``````var plane = {
Center: [250, 250, 0], // coordinates of plane center point
Width: 500, // width
Height: 500, // high
Normal: [0, 0, 1], // orientation, that is, normal vector
Color: {R: 255, G: 0, B: 0} // the color is red
}``````

For directional light, we only need to care about its direction and color. We can define a parallel light source as follows:

``````var directionalLight = {
Direction: [0, 0, - 1], // from the outside of the screen to the screen vertically
Color: {R: 255, G: 255, B: 255} // the color is pure white
}``````

The light of the parallel light is parallel, so the effect of the parallel light on each position of the plane is the same. In other words, the whole plane should be the same color.
According to the above rule (the light intensity is equal to the ray inverse vectorDot multiplicationWe can calculate the color:

``````// ...
var reverseLightDirection = negate(  directionalLight.direction  ）; // calculate the opposite direction vector of the parallel light
var intensity = dot( reverseLightDirection,  plane.normal  ）; // computes the dot product of two vectors

//Calculate the color when there is light
var color = {
r: intensity * plane.color.r + intensity * directionalLight.r,
g: intensity * plane.color.g + intensity * directionalLight.g,
b: intensity * plane.color.b + intensity * directionalLight.g,
}

var canvas = document.getElementById( 'canvas' );
var ctx = canvas.getElementById( '2d' );
ctx.rect( plane.center[ 0 ], plane.center[ 1 ], plane.width, plane.height );
ctx.fillStyle = 'rgb(' + color.r + ',' + color.g + ',' + color.b ')';
ctx.fill();``````

I wrote an example where you can adjust the direction of light to see the effect of lighting in different directions.
Online running example ## Point source

In daily life, point light source is more common, incandescent lamp and desk lamp can be considered as point light source.

First, we define a point light source. For a point light source, we only need to care about its position and color

``````var pointLight = {
Position: [250, 250, 100], // the light source is located 100 above the center of the plane
Color: {R: 255, G: 255, B: 255} // the color is pure white
}``````

The calculation rule of light intensity remains unchanged: the light intensity is equal to the light ray’s inverse direction vector point times the plane normal vector. But the light from a point source is emitted from a point, and when they shine on the plane, all the rays are in different directions. So we have to calculate the intensity of all pixels on the plane one by one.

Here we need to use the putimagedata provided by canvas. This method can directly fill in the pixel color value of an area to draw. The code is as follows:

``````// ...
var imageData =  ctx.createImageData (500, 500); // create an imagedata to save pixel data

for ( var x = 0; x < imageData.width; x++ ) {
for ( var y = 0; y < imageData.height; y++ ) {
var index = y *  imageData.width  +X; // the index of the currently calculated pixel

var point = [ x, y, 0 ];
var normal = [ 0, 0, 1 ];

var reverseLightDirection = normalize( sub(  pointLight.position , point)); // the inverse vector of the ray direction

var light = dot( reverseLightDirection, normal );

imageData.data[ index * 4 ] = pointLight.color.r * intensity + plane.color.r * intensity;
imageData.data[ index * 4 + 1 ] = pointLight.color.g * intensity + plane.color.g * intensity;
imageData.data[ index * 4 + 2 ] = pointLight.color.b * intensity + plane.color.b * intensity;
imageData.data[ index * 4 + 3 ] = 255;
}
}

ctx.putImageData( imageData, 100, 100 );``````

So you can see the result: I wrote a more complicated example. You can use the mouse to move the light source and slide the wheel to change the light source height
Online running example Dynamic graph looks like there are many circles, but in fact, it doesn’t. You can play it by yourself

For a`500*500`We need to calculate all the points on the plane one by one. We need to cycle`500*500=250000`This is actually very inefficient. And in the rendering of complex scenes, there will not be only one light source, but also projection and other calculations, so the amount of calculation will be very large.

At a lower level, this is because every calculation is made byCPUThe CPU can only perform serial calculation. It can only complete one calculation before starting the next calculation, so it is very slow.

This kind of complex rendering is actually more suitable for webgl, because each calculation is actually independent, and webgl can use itGPUParallel computing capability of,meanwhileTo calculate the light intensity at all points. One`500*500`In theory, it only takes one calculation time, and the improvement is very large.

This article also wants to introduce webgl through this simple lighting calculation. In the following articles, I will use webgl to achieve this effect again. Lighting effect of webgl rendering

This is the end of the article.

I plan to write a series of articles on front-end graphics rendering, which will cover the common front-end graphics rendering technologies: canvas, SVG and webgl. Through this series of articles, we hope that readers can have a better understanding of the front-end graphics rendering interface, image processing, graphics basic knowledge. Hope to share at the same time, but also consolidate and review their own knowledge, and common progress.