WebGL Easy Tutorial (X): Light

1 Overview

In the previous tutorial, "WebGL Easy Tutorial (IX): integrated example: Terrain Rendering" , the realization of a rendering of terrain. In this tutorial, give this terrain combined with light, make it more real, three-dimensional feeling stronger.

2. Principle

2.1. Light type

In reality, even a pure white object, you can easily identify the outline of the object. In fact, since the difference in the dark to produce light of its three-dimensional. Similar to reality, WebGL There are three basic types of light:

  1. Point light source: a spot light emitted to the surroundings, such as light bulbs, flame. Defines a point light source requires a light source position, direction and color of the light. Depending on the position of the irradiation point, the direction of light is different.
  2. Parallel light: parallel light can be emitted as a light source at infinity, such as sunlight. Because of the special position far from the light source, it can be said that the light reaches the parallel when illuminated objects. Only need a direction and color can be defined.
  3. Ambient light: ambient light is indirect light, that means that the light source, through a variety of other objects emission and exposure to light on the surface of the object. For example, at night to open the refrigerator door, the kitchen light produced. After multiple reflections because the intensity is very small gap has been no need to accurately calculate the light intensity. It is generally believed that ambient light is uniformly irradiated onto the surface of an object, only one defined color.

as the picture shows:
image

2.2 reflective type

Since the final color of the object color is displayed due to reflection of light, it is determined by factors of two parts: the type of incident light and the surface of the object. Including color information of incident light and the incident light direction, and the information of the object surface comprising a substrate and a colored reflection characteristic. There are environmental reflection (enviroment / ambient reflection) and diffuse reflectance (diffuse reflection) according to two types of light rays reflected by the object by:

2.2.1. Environmental reflector (enviroment / ambient reflection)

Reflection for ambient light environment is concerned, the reflection in the environment, the ambient light illuminating the object is uniform in all aspects, equal intensity, that is, the direction can be reflected in the opposite direction of the incident light. That is just about the color of the final object with incident light color and the base color. It can be defined ambient reflected color:
\ [<ambient reflected color> = <color incident> × <substrate surface color> \ tag {1} \]
Note that in the formulas, this means a multiplication color vector the component-wise multiplication.

2.2.2. Diffuse reflectance (diffuse reflection)

Diffuse light is directed parallel to the point light source terms. Junior believed time had physical contact with specular and diffuse reflection. If the surface as smooth as a mirror, then the light will be reflected over a certain angle, it is the glare from a visual reflection effect; if the surface is uneven, the reflected light will be at a fixed angle is not emitted. In reality, most of the surface is rough, so to see a variety of objects. as the picture shows:
image

Diffuse reflection, the reflected light color in addition to color, the color of the substrate surface, and the incident surface of the object normal vector of the incident light depends on the angle of incidence formed. So that an incident angle of θ, the color of diffusely reflected light may be calculated according to:
\ [<diffuse color> = <color incident> × <substrate surface color> × cosθ \ tag {2}
\] incident angle [theta] can be calculated by the vector dot product:
\ [<ray direction> - <normal direction> = | ray direction | * | normal direction | * cosθ \]
If the light direction normal are normalized, then vector mold (length) in the case 1, there are:
\ [<diffuse color> = <color incident> × <substrate surface color> × (<ray direction> - <normal direction>) \]
Note, here, "light direction" refers to the fact that the incident direction in the opposite direction, i.e. the direction from the light incident point to point, as shown:
image

2.2.3 Integrated

When the ambient diffuse and reflected exist, both together, will give the final color of the object to be observed:
\ [<reflected color surface> = <diffuse color> + <ambient reflected color> \ tag {3} \]

3. Examples

3.1. Specific code

Improved tutorial on the JS code is as follows:

// 顶点着色器程序
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' + //位置
  'attribute vec4 a_Color;\n' + //颜色
  'attribute vec4 a_Normal;\n' + //法向量
  'uniform mat4 u_MvpMatrix;\n' +
  'varying vec4 v_Color;\n' +
  'varying vec4 v_Normal;\n' +
  'void main() {\n' +
  '  gl_Position = u_MvpMatrix * a_Position;\n' + //设置顶点的坐标
  '  v_Color = a_Color;\n' +
  '  v_Normal = a_Normal;\n' +
  '}\n';

// 片元着色器程序
var FSHADER_SOURCE =
  'precision mediump float;\n' +
  'uniform vec3 u_DiffuseLight;\n' + // 漫反射光颜色
  'uniform vec3 u_LightDirection;\n' + // 漫反射光的方向
  'uniform vec3 u_AmbientLight;\n' + // 环境光颜色
  'varying vec4 v_Color;\n' +
  'varying vec4 v_Normal;\n' +
  'void main() {\n' +
  //对法向量归一化
  '  vec3 normal = normalize(v_Normal.xyz);\n' +
  //计算光线向量与法向量的点积
  '  float nDotL = max(dot(u_LightDirection, normal), 0.0);\n' +
  //计算漫发射光的颜色 
  '  vec3 diffuse = u_DiffuseLight * v_Color.rgb * nDotL;\n' +
  //计算环境光的颜色
  '  vec3 ambient = u_AmbientLight * v_Color.rgb;\n' +
  '  gl_FragColor = vec4(diffuse+ambient, v_Color.a);\n' +
  '}\n';

//定义一个矩形体:混合构造函数原型模式
function Cuboid(minX, maxX, minY, maxY, minZ, maxZ) {
  this.minX = minX;
  this.maxX = maxX;
  this.minY = minY;
  this.maxY = maxY;
  this.minZ = minZ;
  this.maxZ = maxZ;
}

Cuboid.prototype = {
  constructor: Cuboid,
  CenterX: function () {
    return (this.minX + this.maxX) / 2.0;
  },
  CenterY: function () {
    return (this.minY + this.maxY) / 2.0;
  },
  CenterZ: function () {
    return (this.minZ + this.maxZ) / 2.0;
  },
  LengthX: function () {
    return (this.maxX - this.minX);
  },
  LengthY: function () {
    return (this.maxY - this.minY);
  }
}

//定义DEM
function Terrain() {}
Terrain.prototype = {
  constructor: Terrain,
  setWH: function (col, row) {
    this.col = col;
    this.row = row;
  }
}

var currentAngle = [0.0, 0.0]; // 绕X轴Y轴的旋转角度 ([x-axis, y-axis])
var curScale = 1.0; //当前的缩放比例

function main() {
  var demFile = document.getElementById('demFile');
  if (!demFile) {
    console.log("Failed to get demFile element!");
    return;
  }

  demFile.addEventListener("change", function (event) {
    //判断浏览器是否支持FileReader接口
    if (typeof FileReader == 'undefined') {
      console.log("你的浏览器不支持FileReader接口!");
      return;
    }

    var input = event.target;
    var reader = new FileReader();
    reader.onload = function () {
      if (reader.result) {

        //读取
        var terrain = new Terrain();
        if (!readDEMFile(reader.result, terrain)) {
          console.log("文件格式有误,不能读取该文件!");
        }

        //绘制
        onDraw(gl, canvas, terrain);
      }
    }

    reader.readAsText(input.files[0]);
  });

  // 获取 <canvas> 元素
  var canvas = document.getElementById('webgl');

  // 获取WebGL渲染上下文
  var gl = getWebGLContext(canvas);
  if (!gl) {
    console.log('Failed to get the rendering context for WebGL');
    return;
  }

  // 初始化着色器
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed to intialize shaders.');
    return;
  }

  // 指定清空<canvas>的颜色
  gl.clearColor(0.0, 0.0, 0.0, 1.0);

  // 开启深度测试
  gl.enable(gl.DEPTH_TEST);

  //清空颜色和深度缓冲区
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
}

//绘制函数
function onDraw(gl, canvas, terrain) {
  // 设置顶点位置
  var n = initVertexBuffers(gl, terrain);
  if (n < 0) {
    console.log('Failed to set the positions of the vertices');
    return;
  }

  //注册鼠标事件
  initEventHandlers(canvas);

  //设置灯光
  setLight(gl);

  //绘制函数
  var tick = function () {
    //设置MVP矩阵
    setMVPMatrix(gl, canvas, terrain.cuboid);

    //清空颜色和深度缓冲区
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    //绘制矩形体
    gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_SHORT, 0);

    //请求浏览器调用tick
    requestAnimationFrame(tick);
  };

  //开始绘制
  tick();
}

//设置灯光
function setLight(gl) {
  var u_AmbientLight = gl.getUniformLocation(gl.program, 'u_AmbientLight');
  var u_DiffuseLight = gl.getUniformLocation(gl.program, 'u_DiffuseLight');
  var u_LightDirection = gl.getUniformLocation(gl.program, 'u_LightDirection');
  if (!u_DiffuseLight || !u_LightDirection || !u_AmbientLight) {
    console.log('Failed to get the storage location');
    return;
  }

  //设置漫反射光
  gl.uniform3f(u_DiffuseLight, 1.0, 1.0, 1.0);

  // 设置光线方向(世界坐标系下的)
  var solarAltitude = 45.0;
  var solarAzimuth = 315.0;
  var fAltitude = solarAltitude * Math.PI / 180; //光源高度角
  var fAzimuth = solarAzimuth * Math.PI / 180; //光源方位角

  var arrayvectorX = Math.cos(fAltitude) * Math.cos(fAzimuth);
  var arrayvectorY = Math.cos(fAltitude) * Math.sin(fAzimuth);
  var arrayvectorZ = Math.sin(fAltitude);
  
  var lightDirection = new Vector3([arrayvectorX, arrayvectorY, arrayvectorZ]);
  lightDirection.normalize(); // Normalize
  gl.uniform3fv(u_LightDirection, lightDirection.elements);

  //设置环境光
  gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);
}

//读取DEM函数
function readDEMFile(result, terrain) {
  var stringlines = result.split("\n");
  if (!stringlines || stringlines.length <= 0) {
    return false;
  }

  //读取头信息
  var subline = stringlines[0].split("\t");
  if (subline.length != 6) {
    return false;
  }
  var col = parseInt(subline[4]); //DEM宽
  var row = parseInt(subline[5]); //DEM高
  var verticeNum = col * row;
  if (verticeNum + 1 > stringlines.length) {
    return false;
  }
  terrain.setWH(col, row);

  //读取点信息
  var ci = 0;
  var pSize = 9;
  terrain.verticesColors = new Float32Array(verticeNum * pSize);
  for (var i = 1; i < stringlines.length; i++) {
    if (!stringlines[i]) {
      continue;
    }

    var subline = stringlines[i].split(',');
    if (subline.length != pSize) {
      continue;
    }

    for (var j = 0; j < pSize; j++) {
      terrain.verticesColors[ci] = parseFloat(subline[j]);
      ci++;
    }
  }

  if (ci !== verticeNum * pSize) {
    return false;
  }

  //包围盒
  var minX = terrain.verticesColors[0];
  var maxX = terrain.verticesColors[0];
  var minY = terrain.verticesColors[1];
  var maxY = terrain.verticesColors[1];
  var minZ = terrain.verticesColors[2];
  var maxZ = terrain.verticesColors[2];
  for (var i = 0; i < verticeNum; i++) {
    minX = Math.min(minX, terrain.verticesColors[i * pSize]);
    maxX = Math.max(maxX, terrain.verticesColors[i * pSize]);
    minY = Math.min(minY, terrain.verticesColors[i * pSize + 1]);
    maxY = Math.max(maxY, terrain.verticesColors[i * pSize + 1]);
    minZ = Math.min(minZ, terrain.verticesColors[i * pSize + 2]);
    maxZ = Math.max(maxZ, terrain.verticesColors[i * pSize + 2]);
  }

  terrain.cuboid = new Cuboid(minX, maxX, minY, maxY, minZ, maxZ);

  return true;
}


//注册鼠标事件
function initEventHandlers(canvas) {
  var dragging = false; // Dragging or not
  var lastX = -1,
    lastY = -1; // Last position of the mouse

  //鼠标按下
  canvas.onmousedown = function (ev) {
    var x = ev.clientX;
    var y = ev.clientY;
    // Start dragging if a moue is in <canvas>
    var rect = ev.target.getBoundingClientRect();
    if (rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom) {
      lastX = x;
      lastY = y;
      dragging = true;
    }
  };

  //鼠标离开时
  canvas.onmouseleave = function (ev) {
    dragging = false;
  };

  //鼠标释放
  canvas.onmouseup = function (ev) {
    dragging = false;
  };

  //鼠标移动
  canvas.onmousemove = function (ev) {
    var x = ev.clientX;
    var y = ev.clientY;
    if (dragging) {
      var factor = 100 / canvas.height; // The rotation ratio
      var dx = factor * (x - lastX);
      var dy = factor * (y - lastY);
      currentAngle[0] = currentAngle[0] + dy;
      currentAngle[1] = currentAngle[1] + dx;
    }
    lastX = x, lastY = y;
  };

  //鼠标缩放
  canvas.onmousewheel = function (event) {
    if (event.wheelDelta > 0) {
      curScale = curScale * 1.1;
    } else {
      curScale = curScale * 0.9;
    }
  };
}

//设置MVP矩阵
function setMVPMatrix(gl, canvas, cuboid) {
  // Get the storage location of u_MvpMatrix
  var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
  if (!u_MvpMatrix) {
    console.log('Failed to get the storage location of u_MvpMatrix');
    return;
  }

  //模型矩阵
  var modelMatrix = new Matrix4();
  modelMatrix.scale(curScale, curScale, curScale);
  modelMatrix.rotate(currentAngle[0], 1.0, 0.0, 0.0); // Rotation around x-axis 
  modelMatrix.rotate(currentAngle[1], 0.0, 1.0, 0.0); // Rotation around y-axis 
  modelMatrix.translate(-cuboid.CenterX(), -cuboid.CenterY(), -cuboid.CenterZ());

  //投影矩阵
  var fovy = 60;
  var near = 1;
  var projMatrix = new Matrix4();
  projMatrix.setPerspective(fovy, canvas.width / canvas.height, 1, 10000);

  //计算lookAt()函数初始视点的高度
  var angle = fovy / 2 * Math.PI / 180.0;
  var eyeHight = (cuboid.LengthY() * 1.2) / 2.0 / angle;

  //视图矩阵  
  var viewMatrix = new Matrix4(); // View matrix   
  viewMatrix.lookAt(0, 0, eyeHight, 0, 0, 0, 0, 1, 0);

  //MVP矩阵
  var mvpMatrix = new Matrix4();
  mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);

  //将MVP矩阵传输到着色器的uniform变量u_MvpMatrix
  gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
}

//
function initVertexBuffers(gl, terrain) {
  //DEM的一个网格是由两个三角形组成的
  //      0------1            1
  //      |                   |
  //      |                   |
  //      col       col------col+1    
  var col = terrain.col;
  var row = terrain.row;

  var indices = new Uint16Array((row - 1) * (col - 1) * 6);
  var ci = 0;
  for (var yi = 0; yi < row - 1; yi++) {
    //for (var yi = 0; yi < 10; yi++) {
    for (var xi = 0; xi < col - 1; xi++) {
      indices[ci * 6] = yi * col + xi;
      indices[ci * 6 + 1] = (yi + 1) * col + xi;
      indices[ci * 6 + 2] = yi * col + xi + 1;
      indices[ci * 6 + 3] = (yi + 1) * col + xi;
      indices[ci * 6 + 4] = (yi + 1) * col + xi + 1;
      indices[ci * 6 + 5] = yi * col + xi + 1;
      ci++;
    }
  }

  //
  var verticesColors = terrain.verticesColors;
  var FSIZE = verticesColors.BYTES_PER_ELEMENT; //数组中每个元素的字节数

  // 创建缓冲区对象
  var vertexColorBuffer = gl.createBuffer();
  var indexBuffer = gl.createBuffer();
  if (!vertexColorBuffer || !indexBuffer) {
    console.log('Failed to create the buffer object');
    return -1;
  }

  // 将缓冲区对象绑定到目标
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);
  // 向缓冲区对象写入数据
  gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);

  //获取着色器中attribute变量a_Position的地址 
  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position');
    return -1;
  }
  // 将缓冲区对象分配给a_Position变量
  gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 9, 0);

  // 连接a_Position变量与分配给它的缓冲区对象
  gl.enableVertexAttribArray(a_Position);

  //获取着色器中attribute变量a_Color的地址 
  var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
  if (a_Color < 0) {
    console.log('Failed to get the storage location of a_Color');
    return -1;
  }
  // 将缓冲区对象分配给a_Color变量
  gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 9, FSIZE * 3);
  // 连接a_Color变量与分配给它的缓冲区对象
  gl.enableVertexAttribArray(a_Color);

  // 向缓冲区对象分配a_Normal变量,传入的这个变量要在着色器使用才行
  var a_Normal = gl.getAttribLocation(gl.program, 'a_Normal');
  if (a_Normal < 0) {
    console.log('Failed to get the storage location of a_Normal');
    return -1;
  }
  gl.vertexAttribPointer(a_Normal, 3, gl.FLOAT, false, FSIZE * 9, FSIZE * 6);
  //开启a_Normal变量
  gl.enableVertexAttribArray(a_Normal);

  // 将顶点索引写入到缓冲区对象
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

  return indices.length;
}

3.2. Detailed changes

3.2.1. Setting sunshine

The main change is to add a function of illumination provided setLight drawing functions in the onDraw () () in:

//绘制函数
function onDraw(gl, canvas, terrain) {
  //...

  //注册鼠标事件
  initEventHandlers(canvas);

  //设置灯光
  setLight(gl);

  //绘制函数
  var tick = function () {
    //...
  };

  //开始绘制
  tick();
}

Expand this particular function, the code can be seen mainly to the ambient light passed shader color u_AmbientLight, diffuse color u_DiffuseLight, diffuse reflection direction u_LightDirection these three parameters. Ambient light is reflected by the color of the object according to the other, the intensity of the ambient light is weak, is set to (0.2,0.2,0.2). Here with the diffuse color to simulate sunlight, it can be set to the strongest (1.0,1.0,1.0):

//设置灯光
function setLight(gl) {
  var u_AmbientLight = gl.getUniformLocation(gl.program, 'u_AmbientLight');
  var u_DiffuseLight = gl.getUniformLocation(gl.program, 'u_DiffuseLight');
  var u_LightDirection = gl.getUniformLocation(gl.program, 'u_LightDirection');
  if (!u_DiffuseLight || !u_LightDirection || !u_AmbientLight) {
    console.log('Failed to get the storage location');
    return;
  }

  //设置漫反射光
  gl.uniform3f(u_DiffuseLight, 1.0, 1.0, 1.0);

  //...

  gl.uniform3fv(u_LightDirection, lightDirection.elements);

  //设置环境光
  gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);
}

As mentioned earlier, the sun light is a parallel light, so only need to set the direction of the line. Calculate the direction and the two geographical parameters solarAltitude sun elevation angle and azimuth of the sun solarAzimuth related. Can temporarily do not have to focus on their specific projections details (can be found in my other blog post is achieved by OSG sunshine simulation models II and IV), only need to know the direction of the diffuse reflection here is not arbitrarily designated, is based on actual parameters calculated.

function setLight(gl) {
{
  //...

  // 设置光线方向(世界坐标系下的)
  var solarAltitude = 45.0;
  var solarAzimuth = 315.0;
  var fAltitude = solarAltitude * Math.PI / 180; //光源高度角
  var fAzimuth = solarAzimuth * Math.PI / 180; //光源方位角

  var arrayvectorX = Math.cos(fAltitude) * Math.cos(fAzimuth);
  var arrayvectorY = Math.cos(fAltitude) * Math.sin(fAzimuth);
  var arrayvectorZ = Math.sin(fAltitude);
  
  var lightDirection = new Vector3([arrayvectorX, arrayvectorY, arrayvectorZ]);
  lightDirection.normalize(); // Normalize

  //...
}

3.2.2. Shader lighting settings

Here the vertex shader and the incoming light is not used parameters, but the vertex buffer and the target color value to be saved as a value varying variables, buffers for incoming fragments:

// 顶点着色器程序
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' + //位置
  'attribute vec4 a_Color;\n' + //颜色
  'attribute vec4 a_Normal;\n' + //法向量
  'uniform mat4 u_MvpMatrix;\n' +
  'varying vec4 v_Color;\n' +
  'varying vec4 v_Normal;\n' +
  'void main() {\n' +
  '  gl_Position = u_MvpMatrix * a_Position;\n' + //设置顶点的坐标
  '  v_Color = a_Color;\n' +
  '  v_Normal = a_Normal;\n' +
  '}\n';

In the tile buffer, passed to the tile buffer and the color value of the normal values ​​have been interpolated value becomes the base color of each fragment and method. The normalized normal vector, the dot product to do with the incoming direction of the diffuse reflection, diffuse reflection the angle of incidence obtained. Diffuse reflection angle of incidence of the incoming light intensity and the diffuse reflection sheet element base color, (2) is calculated according to the equation the diffuse color. Fragments substrate color with the incoming ambient color, (1) is calculated according to the formula ambient reflected light color. According to equation (3) both added to obtain the final color of the fragment displayed.

// 片元着色器程序
var FSHADER_SOURCE =
  'precision mediump float;\n' +
  'uniform vec3 u_DiffuseLight;\n' + // 漫反射光颜色
  'uniform vec3 u_LightDirection;\n' + // 漫反射光的方向
  'uniform vec3 u_AmbientLight;\n' + // 环境光颜色
  'varying vec4 v_Color;\n' +
  'varying vec4 v_Normal;\n' +
  'void main() {\n' +
  //对法向量归一化
  '  vec3 normal = normalize(v_Normal.xyz);\n' +
  //计算光线向量与法向量的点积
  '  float nDotL = max(dot(u_LightDirection, normal), 0.0);\n' +
  //计算漫发射光的颜色 
  '  vec3 diffuse = u_DiffuseLight * v_Color.rgb * nDotL;\n' +
  //计算环境光的颜色
  '  vec3 ambient = u_AmbientLight * v_Color.rgb;\n' +
  '  gl_FragColor = vec4(diffuse+ambient, v_Color.a);\n' +
  '}\n';

4. Results

The final results of the browser displays the following:
image
image

Compared to the previous tutorial renderings, we can significantly enhance the three-dimensional found, the case can clearly see the undulating terrain.

5. Reference

Originally part of the code and illustrations from "WebGL Programming Guide," the source link: address . Follow-up will continue to update the contents of this shared directory.

Guess you like

Origin www.cnblogs.com/charlee44/p/11668014.html