Source: github.com/buglas/webg…

Popularly speaking, shading is the way of showing the shading or color of objects with lines or color blocks when painting.

  • Line color

  • Color piece color

In computer graphics, coloring is the process of applying different materials to different objects.

We have to color the model in the computer, it is very difficult to restore the reality.

We can only make the model closer to reality.

There are many ways to color a model to make it close to reality.

Next, I’ll show you a method called blinn-phong coloring.

Blinn-phong is a reflection model, not strictly physical, but a simulation of physical phenomena, so let’s not take reality too seriously.

Let’s first look at the conditions that we need to consider when we want to color an object.

  • Coloring point

    • Normal normal
    • Color diffuse coefficient
    • Gloss shininess
  • The light source

    • Position of light source
    • Intensity of light source
  • Distance r from the coloring point to the light source

  • The direction from the coloring point to the light source l

  • The direction from the coloring point to the viewpoint v

Next, let’s first consider the relationship between the brightness of the shading point and the above conditions.

1- The Angle between the ray and the normal of the coloring point

Under the same light source, the Angle between the incident light and the normal line of the colored point will affect the amount of light received by the colored point.

In the figure above, if the first shader can receive 6 rays, when the shader is rotated 45 degrees, it can receive only 4 rays.

Therefore, we will use the cosine value of the Angle between the incident ray L and the normal line n of the coloring point to represent the degree of light exposure of the coloring point.

Cosine theta = l. nCopy the code

Explain to me where the above equation comes from.

According to the dot product formula:

L. n = cosine theta * | | | | l * nCopy the code

So:

Cosine theta = l. n / | | | | l * nCopy the code

Because: l,n is a unit vector

So:

Cosine theta = l n/a 1 * 1 cosine theta = l. nCopy the code

2- Attenuation of light

Light attenuates as it travels.

The farther away the coloring point is from the light source, the less light energy the coloring point receives.

There are many factors that affect light attenuation in the real world, such as weather conditions and air quality.

We can forget about the graphics here, and we can use a simple formula to simulate the decay of light rays.

Known:

I is the intensity of the light source

R is the distance from the coloring point to the light source

Seek: light intensity at the colored point

Solution:

Intensity = I/r squaredCopy the code

Note:

Some light, such as sunlight entering a room through a window, can be ignored.

The idea is that light direction and light attenuation can be ignored as long as they vary very little within a certain range, thus improving rendering speed.

Next, we comb through the above known conditions and formulas to find the diffuse reflection of the coloring point.

3 – diffuse

3-1- Diffuse reflection formula

The calculation formula of diffuse reflection is as follows:

Ld = kd (I/r squared) * Max (0, n, l)Copy the code
  • Ld-diffusely Reflected light
  • Kd-diffuse coefficient, color
  • I/r²- The intensity of the light at the colored point
  • Max (0,n· L)- The amount of light received by the shading point

Note: Diffuse reflection has nothing to do with line of sight V, this is because diffuse reflection is reflected in all directions.

Next, let’s use this formula to add some sunshine to the WebGL world.

3-2- Diffuse reflection example

Known:

  • A sphere

    • Diffuse reflection coefficient u_Kd
  • The sphere was illuminated by the sun

  • Characteristics of sunlight:

    • Parallel light
    • The light direction is u_LightDir
    • Light intensity is 1 and attenuation is ignored

Seek: diffuse reflection of sphere

Solution:

1. The shader

<script id="vs" type="x-shader/x-vertex"> attribute vec4 a_Position; attribute vec3 a_Normal; uniform mat4 u_ModelMatrix; uniform mat4 u_PvMatrix; varying vec3 v_Normal; void main(){ gl_Position = u_PvMatrix*u_ModelMatrix*a_Position; v_Normal=a_Normal; } </script> <script id="fs" type="x-shader/x-fragment"> precision mediump float; uniform vec3 u_Kd; uniform vec3 u_LightDir; varying vec3 v_Normal; Void main () {vec3 diffuse = Max u_Kd * (0.0, dot (u_LightDir v_Normal)); Gl_FragColor = vec4 (diffuse, 1.0); } </script>Copy the code

2. Declare what you know

// Const lightDir = new Vector3(0.5, 1, 1). Normalize () const u_Kd = [0.7, 0.7, 0.7] // Sphere const sphere = new SphereGeometry(0.5, 6, 4) Vertices} = sphere.getAttribute('position') const {array: Normals} = sphere.getAttribute('normal') // Vertex index collection const {array: indexes} = sphere.indexCopy the code

The sphere is created using the SphereGeometry object of three.js, from which we can directly extract the vertex set, normal set and vertex index of the sphere, and then use it for our own use.

And then I’ll show you a way to draw a sphere in polar coordinates, but let’s start with lights.

2. Drawing

// scene const scene = new scene ({gl}) // registerProgram object scene.registerProgram(' blinn-phong ', {program: createProgram( gl, document.getElementById('vs').innerText, document.getElementById('fs').innerText ), attributeNames: ['a_Position', 'a_Normal'], uniformNames: [ 'u_PvMatrix', 'u_ModelMatrix', 'u_Kd', 'u_LightDir' ] } ) const mat = new Mat({ program: 'Blinn-Phong', data: { u_PvMatrix: { value: orbit.getPvMatrix().elements, type: 'uniformMatrix4fv', }, u_ModelMatrix: { value: new Matrix4().elements, type: 'uniformMatrix4fv', }, u_LightDir: { value: [...lightDir], type: 'uniform3fv', }, u_Kd: { value: u_Kd, type: 'uniform3fv', }, } }) const geo = new Geo({ data: { a_Position: { array: vertices, size: 3 }, a_Normal: { array: normals, size: 3 }, }, index: { array: indexes } }) const obj = new Obj3D({ geo, mat }) scene.add(obj) scene.draw()Copy the code

3. Fine tune Scene object with vertex index drawing method.

Change the previous gl.unsigned_byte to gl.unsigned_short.

gl.drawElements(gl[mode],count, gl.UNSIGNED_SHORT,0)
Copy the code

Explain why you’re doing this.

We used data type gl.unsigned_byte for vertex index plots, as in:

gl.drawElements(gl.TRIANGLES,3,gl.UNSIGNED_BYTE,0)
Copy the code

This data type can only create a set of vertex indexes using the Uint8Array object as follows:

Index: {array: New Uint8Array([... )}Copy the code

However, one drawback of the Uint8Array is that its data can only be in the range of [0,255], which means that the number of vertices in a model cannot exceed 256.

Therefore, we need to expand the value range of vertex indexes, such as using Uint16Array to build vertex indexes.

Uint16Array data ranges from 0 to 65535, which is sufficient for general models.

In three.js, the Uint16Array is used to index its vertices.

Using the Uint16Array, the data type in the drawElements() method needs to change to gl.unsigned_short.

Note:

At present, my WebGL shelf can also do deep encapsulation, such as three. Js built-in several program objects and geometry objects, when needed directly call.

Because I’m currently focusing on graphics, I won’t do depth encapsulation anymore, as long as it’s enough.

Although the WebGL framework is more difficult to use than Three.js, it is at least easier to use than native handwriting, and it can help you understand the underlying principles better.

The effect is as follows:

The current sphere looks like a plaster sphere, because that’s what diffuse reflection does, and the reflected light goes in all directions fairly evenly, with no highlights.

Now let’s think about a problem, I want to turn this plaster ball into a plastic ball, what should I do?

At this point, we need specular reflection.

4- Specular reflection

4-1- Specular reflection formula

The difference between plastic and gypsum depends on its surface is smoother, more close to mirror surface, can have obvious highlight existence.

Let’s use specular reflection to think about when our eyes can see the highlights of an object.

In the figure above, direction R is the specular reflection direction of ray I on the surface of the object, and R and L are symmetric based on normal n.

When v is close to R, a highlight is visible.

Therefore, Phone proposed a method to judge whether the eyes can see highlights by the included Angle of Angle < V,R>.

However, to find the reflection vector R of L based on ray L and normal n requires a considerable amount of calculation.

So Blinn has since improved on the Phone solution with a simpler Blinn-Phone material.

Next, let’s look at the design ideas for blinn-phone materials.

In the figure above, vector h is an angular bisector of Angle <v,v+ L >.

From observation, we can know:

The Angle <h,n> and the Angle < V,R> are proportional with the change of line of sight.

In other words, the closer v is to the specular reflection R, the closer the angular bisector H is to the normal n.

Blinn-phone calculates specular reflection as follows:

H = (v + l)/Ls + l | | v = ks * (I/r squared) * pow (Max h. (0, n), p)Copy the code
  • H: Angular bisector of Angle
    ,>
  • | | v + l (v + l) in length
  • Ls: Specularly reflected light
  • Ks: Specularly coefficient
  • Max (0, n, h) : cos < < n > h,
  • Pow (Max (0,n·h),p) : cos Angle

    p
    ,n>

So let me explain what cos Angle <h,n> p means.

If you just use cos Angle <h,n> to calculate the speculations, you’ll get a larger speculations, and the speculations that we usually see are usually smaller.

So we can power cosine of Angle <h,n>.

4-1- Examples of specular reflection

Now, let’s do a mirror reflection based on that sphere.

Known:

  • A sphere

    • Diffuse reflection coefficient u_Kd
    • Specular reflection coefficient u_Ks
  • The sphere was illuminated by the sun

  • Characteristics of sunlight:

    • Parallel light
    • The light direction is u_LightDir
    • Light intensity is 1 and attenuation is ignored
  • Viewpoint position: u_Eye

Seek: specular reflection of the sphere

Solution:

1. Declare what you know.

// Const LightDir = new Vector3(0.5, 1, 1). Normalize () const u_Kd = [0.7, 0.7, 0.7] // Const u_Ks = [0.3, 0.3, 0.3]Copy the code

2. Register program objects.

scene.registerProgram(
  'Blinn-Phong',
  {
    program: createProgram(
      gl,
      document.getElementById('vs').innerText,
      document.getElementById('fs').innerText
    ),
    attributeNames: ['a_Position', 'a_Normal'],
    uniformNames: [
      'u_PvMatrix', 'u_ModelMatrix', 'u_Kd', 'u_LightDir',
      'u_Ks', 'u_Eye'
    ]
  }
)
Copy the code

3. Add the new UNIFORM variable to the material.

const mat = new Mat({
  program: 'Blinn-Phong',
  data: {
    u_PvMatrix: {
      value: orbit.getPvMatrix().elements,
      type: 'uniformMatrix4fv',
    },
    u_ModelMatrix: {
      value: new Matrix4().elements,
      type: 'uniformMatrix4fv',
    },
    u_LightDir: {
      value: Object.values(LightDir),
      type: 'uniform3fv',
    },
    u_Kd: {
      value: u_Kd,
      type: 'uniform3fv',
    },
    u_Ks: {
      value: u_Ks,
      type: 'uniform3fv',
    },
    u_Eye: {
      value: Object.values(camera.position),
      type: 'uniform3fv',
    },
  },
  mode: 'TRIANGLES',
})
Copy the code

4. Update viewpoint during continuous rendering

! (function render() { orbit.getPvMatrix() scene.setUniform('u_Eye', { value: Object.values(camera.position) }) scene.draw() requestAnimationFrame(render) })()Copy the code

5. Calculate specular reflection in shader.

<script id="vs" type="x-shader/x-vertex"> attribute vec4 a_Position; attribute vec3 a_Normal; uniform mat4 u_ModelMatrix; uniform mat4 u_PvMatrix; varying vec3 v_Normal; varying vec3 v_Position; void main(){ gl_Position = u_PvMatrix*u_ModelMatrix*a_Position; v_Normal=a_Normal; v_Position=vec3(a_Position); } </script> <script id="fs" type="x-shader/x-fragment"> precision mediump float; uniform vec3 u_Kd; uniform vec3 u_Ks; uniform vec3 u_LightDir; uniform vec3 u_Eye; varying vec3 v_Normal; varying vec3 v_Position; Vec3 eyeDir=normalize(u_eye-v_position); vec3 eyeDir=normalize(u_eye-v_position); Vec3 el=eyeDir+u_LightDir; Vec3 h=el/length(el); vec3 h=el/length(el); // Diffuse =u_Kd* Max (0.0,dot(v_Normal,u_LightDir)); // Specular =u_Ks*pow(Max (0.0,dot(v_Normal,h)), 64.0); // Diffuse +specular; Gl_FragColor = vec4 (l, 1.0); } </script>Copy the code

The effect is as follows:

5- Ambient reflection

5-1- Formula for ambient reflex

La=ka*Ia
Copy the code
  • La: Reflected ambient light
  • Ka: Ambient light coefficient
  • Ia: Ambient light intensity

5-2- Example of ambient reflection

Known:

  • A sphere

    • Diffuse reflection coefficient u_Kd
    • Specular reflection coefficient u_Ks
  • The sphere was illuminated by the sun

  • Characteristics of sunlight:

    • Parallel light
    • The light direction is u_LightDir
    • Light intensity is 1 and attenuation is ignored
  • View position: camera. Position

  • Ambient light coefficient: u_Ka

  • Ambient light intensity: 1

Seek: ambient reflection of sphere

Solution:

1. Declare what you know.

// Const LightDir = new Vector3(0.5, 1, 1). Normalize () const u_Kd = [0.7, 0.7, 0.7] // Const u_Ks = [0.2, 0.2, 0.2] // Ambient light const u_Ka = [0.2, 0.2, 0.2]Copy the code

2. Register program objects.

scene.registerProgram(
  'Blinn-Phong',
  {
    program: createProgram(
      gl,
      document.getElementById('vs').innerText,
      document.getElementById('fs').innerText
    ),
    attributeNames: ['a_Position', 'a_Normal'],
    uniformNames: [
      'u_PvMatrix', 'u_ModelMatrix', 'u_Kd', 'u_LightDir',
      'u_Ks', 'u_Eye', 'u_Ka'
    ]
  }
)
Copy the code

3. Add the new UNIFORM variable to the material.

const mat = new Mat({
  program: 'Blinn-Phong',
  data: {
    u_PvMatrix: {
      value: orbit.getPvMatrix().elements,
      type: 'uniformMatrix4fv',
    },
    u_ModelMatrix: {
      value: new Matrix4().elements,
      type: 'uniformMatrix4fv',
    },
    u_LightDir: {
      value: Object.values(LightDir),
      type: 'uniform3fv',
    },
    u_Kd: {
      value: u_Kd,
      type: 'uniform3fv',
    },
    u_Ks: {
      value: u_Ks,
      type: 'uniform3fv',
    },
    u_Ka: {
      value: u_Ka,
      type: 'uniform3fv',
    },
    u_Eye: {
      value: Object.values(camera.position),
      type: 'uniform3fv',
    },
  },
  mode: 'TRIANGLES',
})
Copy the code

5. Calculate ambient reflection in shaders.

<script id="vs" type="x-shader/x-vertex"> attribute vec4 a_Position; attribute vec3 a_Normal; uniform mat4 u_ModelMatrix; uniform mat4 u_PvMatrix; varying vec3 v_Normal; varying vec3 v_Position; void main(){ gl_Position = u_PvMatrix*u_ModelMatrix*a_Position; v_Normal=a_Normal; v_Position=vec3(a_Position); } </script> <script id="fs" type="x-shader/x-fragment"> precision mediump float; uniform vec3 u_Kd; uniform vec3 u_Ks; uniform vec3 u_Ka; uniform vec3 u_LightDir; uniform vec3 u_Eye; varying vec3 v_Normal; varying vec3 v_Position; Vec3 eyeDir=normalize(u_eye-v_position); vec3 eyeDir=normalize(u_eye-v_position); Vec3 el=eyeDir+u_LightDir; Vec3 h=el/length(el); vec3 h=el/length(el); // Diffuse =u_Kd* Max (0.0,dot(v_Normal,u_LightDir)); Vec3 specular=u_Ks*pow(Max (0.0,dot(v_Normal,h)), 64.0); // Diffuse +specular+u_Ka; Gl_FragColor = vec4 (l, 1.0); } </script>Copy the code

The effect is as follows:

6- Blinn-phong reflection model

Our previous method of adding Ambient light, Diffuse Diffuse, and Specular to the Diffuse is called blinn-Phong reflection model:

L = La + Ld + = ka * Ia Ls L + kd (I/r squared) * Max (0, n, L) + ks * (I/r squared) * pow (Max h. (0, n), p)Copy the code

From the above example, we can see:

  • Diffuse reflection gives an object a sense of motion;
  • Specular reflection can make objects light up in front of our eyes;
  • Ambient reflection can make objects look more delicate.

Now that you have a little bit of knowledge about coloring, let’s talk about something we skipped before – coloring frequency.