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.
Lighting rendering -- simulating lighting effect with canvas

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
Lighting rendering -- simulating lighting effect with canvas

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:

Lighting rendering -- simulating lighting effect with canvas

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

Lighting rendering -- simulating lighting effect with canvas

Dynamic graph looks like there are many circles, but in fact, it doesn’t. You can play it by yourself

Advantages of webgl

For a500*500We need to calculate all the points on the plane one by one. We need to cycle500*500=250000This 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. One500*500In 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 rendering -- simulating lighting effect with canvas
Lighting effect of webgl rendering

About my blog

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.

Series blog address: https://github.com/hujiulong/…

If you can help you, welcome star, so that you can keep track of your blog updates.