WebGL使用点光源照明(WebGL进阶06)

Posted 点燃火柴

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WebGL使用点光源照明(WebGL进阶06)相关的知识,希望对你有一定的参考价值。

1. demo效果

在这里插入图片描述

上图效果为原点处有点光源照射的效果

2. 点光源介绍

现实生活中最常见的光照,就是灯泡照明,灯泡发出的光从灯泡开始向四面八方散射出去,实际上,灯泡发出的光会随着距离变大而慢慢衰减。距离越远,到达的光就越少。在使用平行光的时候需要知道平行光的方向,但是点光源不需要方向,只要确定它的位置就可以了,虽然点光源没有方向属性,但是我们可以用点光源的坐标减去顶点的坐标就可以得到点光源照射的方向,从而计算出照射的物体表面的光

点光源方向计算:点光源方向 = 点光源坐标 - 顶点坐标

由于点光源照射需要计算从光源到顶点的向量,所以计算量会高于平行光照明的计算量

3. 实现要点

3.1 片元着色器中计算点光源

在片元着色器中接收点光源位置uLightPosition和物体顶点坐标vPosition,二者相减得到点光源的方向,接下来的计算就与平行光照射一样了,具体实现参照以下代码

//片元着色器
var FSHADER_SOURCE = `
  #ifdef GL_ES
    precision mediump float; // 设置float类型为中精度
  #endif
  uniform mat4 uInvMatrix;//模型坐标变换矩阵的逆矩阵
  uniform vec3 uLightPosition;//点光源位置
  uniform vec4 uAmbientLightColor;//环境光颜色
  uniform vec3 uEyeDirection;//视点方向
  varying vec3 vPosition;//接收顶点
  varying vec3 vNormal; //接收法线信息
  varying vec4 vColor; //接收颜色信息
  void main(){
    vec3  lightVec  = uLightPosition - vPosition;
    vec3 invLight = normalize(uInvMatrix*vec4(lightVec,0.0)).xyz;
    vec3 invEye = normalize(uInvMatrix*vec4(uEyeDirection,0.0)).xyz;
    vec3 halfLE = normalize(invLight + invEye);//半程向量
    float diffuse = clamp(dot(vNormal,invLight),0.0,1.0);//将结果限定在0.0~1.0内
    float specular = pow(clamp(dot(vNormal, halfLE), 0.0, 1.0), 50.0);//计算高光
    //颜色 = 顶点颜色 * 漫反射光 + 反射光 + 环境光
    vec4 destColor = vColor*vec4(vec3(diffuse),1.0) + vec4(vec3(specular), 1.0)+uAmbientLightColor;
    gl_FragColor = destColor;//将计算的颜色信息赋值给内置变量gl_FragColor
  }
`

3.2 准备绘制物体的数据

demo中绘制了两个物体,一个甜圈圈一个球体,在绘制前首先准备绘制它们各自所需的顶点、法线、颜色和绘制索引信息

//获取顶点位置、法线、颜色的存储地址
var attLocations = {
  position: gl.getAttribLocation(prg, 'position'),
  normal: gl.getAttribLocation(prg, 'normal'),
  color: gl.getAttribLocation(prg, 'color'),
}

//每个顶点属性的大小(分量数)
var attStrides = {
  position: 3,
  normal: 3,
  color: 4,
}

// 生成绘制甜圈圈的信息
var torusData = torus(50, 50, 1.0, 3.0);

// 存放甜圈圈顶点、法线、颜色的VBO
var tVbos = {
  position: create_vbo(torusData[0]),
  normal: create_vbo(torusData[1]),
  color: create_vbo(torusData[2]),
}

//存放甜圈圈绘制索引
var tIndex = torusData[3];

// 生成绘制球体的信息
var sphereData = sphere(64, 64, 2.0, [0.25, 0.25, 0.75, 1.0]);

// 存放球体顶点、法线、颜色的VBO
var sVbos = {
  position: create_vbo(sphereData[0]),
  normal: create_vbo(sphereData[1]),
  color: create_vbo(sphereData[2]),
}
//存放球体绘制索引
var sIndex = sphereData[3];

3.3 绘制物体

上一步中准备了绘制物体所需的信息,这里为了实现物体旋转,需要在tick函数中分别绘制甜圈圈和球体,以绘制甜圈圈为例说明一下,在绘制函数中绘制前先要设置VBO和IBO,然后要重新计算模型矩阵和坐标变换的逆矩阵并重新向着色器中传值模型视图投影矩阵uMvpMatrix、模型矩阵uModelMatrix、模型坐标变换矩阵的逆矩阵uInvMatrix,绘制球体也是同样的流程

function draw_torus() {
  // 设置甜圈圈VBO
  set_attribute(tVbos, attLocations, attStrides);

  // 创建IBO
  var sIbo = create_ibo(tIndex);

  // IBO绑定
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, sIbo);

  modelMatrix.set(new Matrix4());
  modelMatrix.translate(tx, -ty, -tz); //平移
  g_MvpMatrix.multiply(modelMatrix) //相乘模型变换矩阵

  //计算模型坐标变换矩阵的逆矩阵
  invMatrix.setInverseOf(modelMatrix)

  //向着色器传值模型视图投影矩阵uMvpMatrix、模型坐标变换矩阵的逆矩阵uInvMatrix
  gl.uniformMatrix4fv(uniformLocations.uMvpMatrix, false, g_MvpMatrix.elements);
  gl.uniformMatrix4fv(uniformLocations.uModelMatrix, false, modelMatrix.elements);
  gl.uniformMatrix4fv(uniformLocations.uInvMatrix, false, invMatrix.elements);

  //绘图
  gl.drawElements(gl.TRIANGLES, tIndex.length, gl.UNSIGNED_SHORT, 0);

}

function draw_sphere() {
  set_attribute(sVbos, attLocations, attStrides);

  var ibo = create_ibo(sIndex);
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);

  modelMatrix.set(new Matrix4());
  modelMatrix.translate(-2 * tx, 2 * ty, 2 * tz); //平移

  g_MvpMatrix.multiply(modelMatrix); //相乘模型变换矩阵

  //计算模型坐标变换矩阵的逆矩阵
  invMatrix.setInverseOf(modelMatrix);

  //向着色器传值模型视图投影矩阵uMvpMatrix、模型矩阵uModelMatrix、模型坐标变换矩阵的逆矩阵uInvMatrix
  gl.uniformMatrix4fv(uniformLocations.uMvpMatrix, false, g_MvpMatrix.elements);
  gl.uniformMatrix4fv(uniformLocations.uModelMatrix, false, modelMatrix.elements);
  gl.uniformMatrix4fv(uniformLocations.uInvMatrix, false, invMatrix.elements);

  gl.drawElements(gl.TRIANGLES, sIndex.length, gl.UNSIGNED_SHORT, 0);
}

4. demo代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title></title>
</head>

<body>
  <!--通过canvas标签创建一个800px*800px大小的画布-->
  <canvas id="webgl" width="800" height="800"></canvas>
  <script type="text/javascript" src="./lib/cuon-matrix.js"></script>
  <script>
    //顶点着色器
    var VSHADER_SOURCE = `
      attribute vec3 position; //顶点位置信息
      attribute vec4 color; //颜色
      attribute vec3 normal; //法线
      uniform mat4 uMvpMatrix; //模型视图投影矩阵
      uniform mat4 uModelMatrix; //模型矩阵
      varying vec3 vPosition;//向片元着色器传值顶点信息
      varying vec3 vNormal; //向片元着色器传值法线信息
      varying vec4 vColor; //向片元着色器传值颜色信息
      void main(){
        vPosition = (uModelMatrix * vec4(position, 1.0)).xyz;
        vNormal = normal;
        vColor = color;
        gl_Position = uMvpMatrix*vec4(position,1.0); //将模型视图投影矩阵与顶点坐标相乘赋值给顶点着色器内置变量gl_Position
      }
      `
    //片元着色器
    var FSHADER_SOURCE = `
      #ifdef GL_ES
        precision mediump float; // 设置float类型为中精度
      #endif
      uniform mat4 uInvMatrix;//模型坐标变换矩阵的逆矩阵
      uniform vec3 uLightPosition;//点光源位置
      uniform vec4 uAmbientLightColor;//环境光颜色
      uniform vec3 uEyeDirection;//视点方向
      varying vec3 vPosition;//接收顶点
      varying vec3 vNormal; //接收法线信息
      varying vec4 vColor; //接收颜色信息
      void main(){
        vec3  lightVec  = uLightPosition - vPosition;
        vec3 invLight = normalize(uInvMatrix*vec4(lightVec,0.0)).xyz;
        vec3 invEye = normalize(uInvMatrix*vec4(uEyeDirection,0.0)).xyz;
        vec3 halfLE = normalize(invLight + invEye);//半程向量
        float diffuse = clamp(dot(vNormal,invLight),0.0,1.0);//将结果限定在0.0~1.0内
        float specular = pow(clamp(dot(vNormal, halfLE), 0.0, 1.0), 50.0);//计算高光
        //颜色 = 顶点颜色 * 漫反射光 + 反射光 + 环境光
        vec4 destColor = vColor*vec4(vec3(diffuse),1.0) + vec4(vec3(specular), 1.0)+uAmbientLightColor;
        gl_FragColor = destColor;//将计算的颜色信息赋值给内置变量gl_FragColor
      }
    `

    onload = function () {

      //通过getElementById()方法获取canvas画布
      var canvas = document.getElementById('webgl');

      //通过方法getContext()获取WebGL上下文
      var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');


      //创建程序对象
      var prg = createProgram(VSHADER_SOURCE, FSHADER_SOURCE);

      //获取uniform变量模型视图投影矩阵、模型矩阵、模型坐标变换矩阵的逆矩阵、点光源位置、环境光颜色、视角方向
      var uniformLocations = {
        uMvpMatrix: gl.getUniformLocation(prg, 'uMvpMatrix'),
        uModelMatrix: gl.getUniformLocation(prg, 'uModelMatrix'),
        uInvMatrix: gl.getUniformLocation(prg, 'uInvMatrix'),
        uLightPosition: gl.getUniformLocation(prg, 'uLightPosition'),
        uAmbientLightColor: gl.getUniformLocation(prg, 'uAmbientLightColor'),
        uEyeDirection: gl.getUniformLocation(prg, 'uEyeDirection'),

      }
      //给顶点着色器uniform变量uLightDirection-点光源位置传值[0.0, 0.0, 0.0]
      gl.uniform3fv(uniformLocations.uLightPosition, [0.0, 0.0, 0.0]);

      //给顶点着色器uniform变量uAmbientLightColor- 环境光颜色传值(0.2, 0.1, 0.2, 1.0)
      gl.uniform4f(uniformLocations.uAmbientLightColor, 0.2, 0.1, 0.2, 1.0);

      //给顶点着色器uniform变量uEyeDirection-视点方向传值[0.0, 0.0, 20.0]
      gl.uniform3fv(uniformLocations.uEyeDirection, [0.0, 0.0, 20.0]);


      var g_MvpMatrix = new Matrix4(); //模型视图投影矩阵 
      var viewProjMatrix = new Matrix4(); //创建视图投影矩阵
      var modelMatrix = new Matrix4(); //创建模型矩阵
      var invMatrix = new Matrix4(); //创建模型矩阵

      viewProjMatrix.setPerspective(45.0, canvas.width / canvas.height, 1.0, 100.0);
      viewProjMatrix.lookAt(-30.0, -30.0, 30.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

      gl.enable(gl.DEPTH_TEST); //开启隐藏面消除
      gl.depthFunc(gl.LEQUAL); //如果传入值小于或等于深度缓冲区值,则通过
      gl.enable(gl.CULL_FACE); //激活多边形正反面剔除


      //获取顶点位置、法线、颜色的存储地址
      var attLocations = {
        position: gl.getAttribLocation(prg, 'position'),
        normal: gl.getAttribLocation(prg, 'normal'),
        color: gl.getAttribLocation(prg, 'color'),
      }

      //每个顶点属性的大小(分量数)
      var attStrides = {
        position: 3,
        normal: 3,
        color: 4,
      }

      // 生成绘制甜圈圈的信息
      var torusData = torus(50, 50, 1.0, 3.0);

      // 存放甜圈圈顶点、法线、颜色的VBO
      var tVbos = {
        position: create_vbo(torusData[0]),
        normal: create_vbo(torusData[1]),
        color: create_vbo(torusData[2]),
      }

      //存放甜圈圈绘制索引
      var tIndex = torusData[3];

      // 生成绘制球体的信息
      var sphereData = sphere(64, 64, 2.0, [0.25, 0.25, 0.75, 1.0]);

      // 存放球体顶点、法线、颜色的VBO
      var sVbos = {
        position: create_vbo(sphereData[0]),
        normal: create_vbo(sphereData[1]),
        color: create_vbo(sphereData[2]),
      }
      //存放球体绘制索引
      var sIndex = sphereData[3];

      var count = 0;
      var rad, tx, ty, tz;

      (function tick() {

        // gl初始化
        gl.clearColor(0.0, 0.0, 0.0, 1.0); //指定调用 clear() 方法时使用的颜色值
        gl.clearDepth(1.0); //设置深度清除值
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); //清空颜色和深度缓冲区


        //计算模型视图投影矩阵 
        g_MvpMatrix.set(viewProjMatrix); //设置视图投影矩阵 


        count++;

        rad = (count % 360) * Math.PI / 180;
        tx = Math.cos(rad) * 3.5;
        ty = Math.sin(rad) * 3.5;
        tz = Math.sin(rad) * 3.5;


        draw_torus(); //绘制甜圈圈

        draw_sphere(); //绘制球体

        gl.flush();

        requestAnimationFrame(tick)

      })();



      function draw_torus() {
        // 设置甜圈圈VBO
        set_attribute(tVbos, attLocations, attStrides);

        // 创建IBO
        var sIbo = create_ibo(tIndex);

        // IBO绑定
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, sIbo);

        modelMatrix.set(new Matrix4());
        modelMatrix.translate(tx, -ty, -tz); //平移
        g_MvpMatrix.multiply(modelMatrix) //相乘模型变换矩阵

        //计算模型坐标变换矩阵的逆矩阵
        invMatrix.setInverseOf(modelMatrix)

        //向着色器传值模型视图投影矩阵uMvpMatrix、模型矩阵uModelMatrix、模型坐标变换矩阵的逆矩阵uInvMatrix
        gl.uniformMatrix4fv(uniformLocations.uMvpMatrix, false, g_MvpMatrix.elements);
        gl.uniformMatrix4fv(uniformLocations.uModelMatrix, false, modelMatrix.elements);
        gl.uniformMatrix4fv(uniformLocations.uInvMatrix, false, invMatrix.elements);

        //绘图
        gl.drawElements(gl.TRIANGLES, tIndex.length, gl.UNSIGNED_SHORT, 0);

      }

      function draw_sphere() {
        set_attribute(sVbos, attLocations, attStrides);

        var ibo = create_ibo(sIndex);
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);

        modelMatrix.set(new Matrix4());
        modelMatrix.translate(-2 * tx, 2 * ty, 2 * tz); //平移

        g_MvpMatrix.multiply(modelMatrix); //相乘模型变换矩阵

        //计算模型坐标变换矩阵的逆矩阵
        invMatrix.setInverseOf(modelMatrix);

        //向着色器传值模型视图投影矩阵uMvpMatrix、模型矩阵uModelMatrix、模型坐标变换矩阵的逆矩阵uInvMatrix
        gl.uniformMatrix4fv(uniformLocations.uMvpMatrix, false, g_MvpMatrix.elements);
        gl.uniformMatrix4fv(uniformLocations.uModelMatrix, false, modelMatrix.elements);
        gl.uniformMatrix4fv(uniformLocations.uInvMatrix, false, invMatrix.elements);

        gl.drawElements(gl.TRIANGLES, sIndex.length, gl.UNSIGNED_SHORT, 0);
      }

      //创建程序对象
      function createProgram(vshader, fshader) {

        //创建顶点着色器对象
        var vertexShader = loadShader(gl.VERTEX_SHADER, vshader);
        //创建片元着色器对象
        var fragmentShader = loadShader(gl.FRAGMENT_SHADER, fshader);

        if (!vertexShader || !fragmentShader) {
          return null
        }

        //创建程序对象program
        var program = gl.createProgram();
        if (!gl.createProgram()) {
          return null
        }

        //分配顶点着色器和片元着色器到program
        gl.attachShader(program, vertexShader);
        gl.attachShader(program, fragmentShader);
        //链接program
        gl.linkProgram(program);

        //检查程序对象是否连接成功
        var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
        if (!linked) {
          var error = gl.getProgramInfoLog(program);
          console.log('程序对象连接失败: ' + error);
          gl.deleteProgram(program);
          gl.deleteShader(fragmentShader);
          gl.deleteShader(vertexShader);
          return null
        }

        //使用program
        gl.useProgram(program);

        gl.program = program;
        //返回程序program对象
        以上是关于WebGL使用点光源照明(WebGL进阶06)的主要内容,如果未能解决你的问题,请参考以下文章

WebGL三维模型实现Phong着色(WebGL进阶05)

三维模型反射光照射实现物体表面高光实现(WebGL进阶04)

三维模型平行光照射实现(WebGL进阶02)

webgl之五彩光源

WebGL入门与进阶3

WebGL简易教程:包围球与投影