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

Posted 点燃火柴

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了三维模型反射光照射实现物体表面高光实现(WebGL进阶04)相关的知识,希望对你有一定的参考价值。

1. demo效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 反射光介绍

反射光分为漫反射和镜面反射,平行光属于慢反射。通常反射光指的是镜面反射光又称高光反射,指由光源直接经物体表面反射入眼睛的光线。镜面反射使物体看上去更有光泽
下图是反射光模型,光源发出的光照射到物体上并被反射,当反射光和视线正对时,在一定的范围内光大多数的光会反射到眼睛里,这时从物体表面反射的光在这个范围内,就会产生高光现象

在这里插入图片描述

但是如果上述模型计算光反射,需要大量的计算才能完成。可以通过使用入射光向量和视线向量的半程向量来获得近似于反射光的结果的方法。

首先要根据入射光向量和视线向量来计算半程向量。然后通过半程向量与物体表面法向量的点乘来计算反射光的强度。

3. 实现要点

3.1 反射光计算

计算反射光与计算平行光的漫反射有点类似,首先获取光源的逆向量和视线的逆向量,然后将二者相加得到半程向量。之后使用这个半程向量和物体表面法线点乘计算光线强度,随后利用指数运算进行处理,它可以将弱光转换为较弱的光,强光则保持原样。使高光反射效果更加逼真。
物体表面最终颜色的计算公式:颜色 = 顶点颜色 * 漫反射光 + 反射光 + 环境光

//顶点着色器
var VSHADER_SOURCE = `
  attribute vec3 position; //顶点位置信息
  attribute vec4 color; //颜色
  attribute vec3 normal; //法线
  uniform mat4 uMvpMatrix; //模型视图投影矩阵
  uniform mat4 uInvMatrix;//模型坐标变换矩阵的逆矩阵
  uniform vec3 uLightDirection;//平行光方向
  uniform vec3 uDirectLightColor;//平行光颜色
  uniform vec4 uAmbientLightColor;//环境光颜色
  uniform vec3 uEyeDirection;//视点方向
  varying vec4 vColor; //向片元着色器传值颜色信息
  void main(){
    vec3 invLight = normalize(uInvMatrix*vec4(uLightDirection,0.0)).xyz;
    vec3 invEye = normalize(uInvMatrix*vec4(uEyeDirection,0.0)).xyz;
    vec3 halfLE = normalize(invLight + invEye);//半程向量
    float diffuse = clamp(dot(normal,invLight),0.0,1.0);//将结果限定在0.0~1.0内
    float specular = pow(clamp(dot(normal, halfLE), 0.0, 1.0), 50.0);//计算高光
    //颜色 = 顶点颜色 * 漫反射光 + 反射光 + 环境光
    vColor = color*vec4(vec3(uDirectLightColor),1.0)*vec4(vec3(diffuse),1.0) + vec4(vec3(specular), 1.0)+uAmbientLightColor;
    gl_Position = uMvpMatrix*vec4(position,1.0); //将模型视图投影矩阵与顶点坐标相乘赋值给顶点着色器内置变量gl_Position
  }
  `

3.2 向着色器传值

与之前一样,首先获取顶点着色器中变量的存储地址,然后通过这个地址向对应的变量中传值

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

}
//给顶点着色器uniform变量uLightDirection-平行光方向传值[-0.5, 0.5, 0.5]
gl.uniform3fv(uniformLocations.uLightDirection, [-0.5, 0.5, 0.5]);

//给顶点着色器uniform变量uDirectLightColor- 平行光颜色传值(1.0,1.0,1.0)
gl.uniform3f(uniformLocations.uDirectLightColor, 1.0, 1.0, 1.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]);

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 uInvMatrix;//模型坐标变换矩阵的逆矩阵
      uniform vec3 uLightDirection;//平行光方向
      uniform vec3 uDirectLightColor;//平行光颜色
      uniform vec4 uAmbientLightColor;//环境光颜色
      uniform vec3 uEyeDirection;//视点方向
      varying vec4 vColor; //向片元着色器传值颜色信息
      void main(){
        vec3 invLight = normalize(uInvMatrix*vec4(uLightDirection,0.0)).xyz;
        vec3 invEye = normalize(uInvMatrix*vec4(uEyeDirection,0.0)).xyz;
        vec3 halfLE = normalize(invLight + invEye);//半程向量
        float diffuse = clamp(dot(normal,invLight),0.0,1.0);//将结果限定在0.0~1.0内
        float specular = pow(clamp(dot(normal, halfLE), 0.0, 1.0), 50.0);//计算高光
        //颜色 = 顶点颜色 * 漫反射光 + 反射光 + 环境光
        vColor = color*vec4(vec3(uDirectLightColor),1.0)*vec4(vec3(diffuse),1.0) + vec4(vec3(specular), 1.0)+uAmbientLightColor;
        gl_Position = uMvpMatrix*vec4(position,1.0); //将模型视图投影矩阵与顶点坐标相乘赋值给顶点着色器内置变量gl_Position
      }
      `

    //片元着色器
    var FSHADER_SOURCE = `
      #ifdef GL_ES
       precision mediump float; // 设置float类型为中精度
      #endif
      varying vec4 vColor; //接收顶点着色器传送的颜色信息
      void main(){
       gl_FragColor = vColor; //将接收的颜色信息赋值给内置变量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);

      //获取顶点位置、法线、颜色的存储地址
      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, 3.0, 8.0);

      var position = torusData[0];
      var normal = torusData[1];
      var color = torusData[2];
      var index = torusData[3];

      // 创建存放顶点、法线、颜色的VBO
      var vbos = {
        position: create_vbo(position),
        normal: create_vbo(normal),
        color: create_vbo(color),
      }

      // 设置VBO
      set_attribute(vbos, attLocations, attStrides);

      // 创建IBO
      var ibo = create_ibo(index);

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


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

      }
      //给顶点着色器uniform变量uLightDirection-平行光方向传值[-0.5, 0.5, 0.5]
      gl.uniform3fv(uniformLocations.uLightDirection, [-0.5, 0.5, 0.5]);

      //给顶点着色器uniform变量uDirectLightColor- 平行光颜色传值(1.0,1.0,1.0)
      gl.uniform3f(uniformLocations.uDirectLightColor, 1.0, 1.0, 1.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 currentAngle = [0.0, 0.0]; //当前旋转的角度[x-axis, y-axis]
      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(0.0, 20.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 rotateSpeed = 0.1; // 自动旋转速度
      (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); //设置视图投影矩阵 

        modelMatrix.setRotate(currentAngle[0], 1.0, 0.0, 0.0); //沿X轴旋转设置矩阵
        modelMatrix.rotate(currentAngle[1], 0.0, 1.0, 0.0); //沿Y轴旋转设置矩阵

        rotateSpeed += 0.3;
        modelMatrix.rotate(rotateSpeed, 0.0, 1.0, 1.0); //更新旋转矩阵

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

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

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


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

        gl.flush();

        requestAnimationFrame(tick)

      })();

      initEventHandlers(canvas, currentAngle) //注册鼠标事件

      //创建程序对象
      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对象
        return program
      }

      function loadShader(type, source) {
        // 创建顶点着色器对象
        var shader = gl.createShader(type);
        if (shader == null) {
          console.log('创建着色器失败');
          return null
        }

        // 引入着色器源代码
        gl.shaderSource(shader, source);

        // 编译着色器
        gl.compileShader(shader);

        // 检查顶是否编译成功
        var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
        if (!compiled) {
          var error = gl.getShaderInfoLog(shader);
          console.log('编译着色器失败: ' + error);
          gl.deleteShader(shader);
          return null
        }

        return shader
      }


      function initEventHandlers(canvas, currentAngle) {
        var dragging = false; //默认鼠标拖动不旋转物体
        var lastX = -1,
          lastY = -1; //鼠标最后的位置

        canvas.onmousedown = function (ev) { //注册鼠标按下事件
          var x = ev.clientX,
            y = ev.clientY;

          //鼠标在物体上开始拖动
          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.onmouseup = function (ev) {
          dragging = false;
        }

        canvas.onmousemove = function (ev) { //注册鼠标移动事件
          var x = ev.clientX,
            y = ev.clientY;
          if (dragging) {
            var factor = 100 / canvas.height; //旋转因子
            var dx = factor * (x - lastX);
            var dy = factor * (y - lastY);
            //沿Y轴的旋转角度控制在-90到90度之间
            currentAngle[0] = Math.max(Math.min(currentAngle[0] + dy, 90.0), -90.0);
            currentAngle[1] = currentAngle[1] + dx;
          }
          lastX = x;
          lastY = y;
        }
      }

      // 创建VBO
      function create_vbo(data) {
        //创建缓冲区对象
        var vbo = gl.createBuffer();

        //绑定缓冲区到ARRAY_BUFFER
        gl.bindBuffer(gl.ARRAY_BUFFER, vbo);

        //将数据写入缓冲区对象
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);

        //解绑缓冲区
        gl.bindBuffer(gl.ARRAY_BUFFER, null);

        return vbo
      }


      // 向VBO写入数据
      function set_attribute(vbo, attLocation, attStride) {

        for (var key in vbo) {
          //绑定缓冲区到ARRAY_BUFFER
          gl.bindBuffer(gl.ARRAY_BUFFER, vbo[key]);

          //分配缓存区到指定地址
          gl.vertexAttribPointer(attLocation[key], attStride[key], gl.FLOAT, false, 0, 0);

          //开启缓冲区
          gl.enableVertexAttribArray(attLocation[key]);
        }
      }

      // 创建IBO
      function create_ibo(data) {
        //创建缓冲区对象
        var ibo = gl.createBuffer();

        //绑定缓冲区到ELEMENT_ARRAY_BUFFER
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);

        //将数据写入缓冲区对象
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int16Array(data), gl.STATIC_DRAW三维模型环境光照射实现(WebGL进阶03)

UnityShader镜面反射计算与反射光向量推导

shader编程-RayMarching与SDF结合开始三维探索(WebGL-Shader开发基础07)

shader编程-RayMarching与SDF结合开始三维探索(WebGL-Shader开发基础07)

视觉高级篇24 # 如何模拟光照让3D场景更逼真?(下)

视觉高级篇24 # 如何模拟光照让3D场景更逼真?(下)