WebGL多模型光照综合实例

作者: jeffzhong | 来源:发表于2018-01-12 20:58 被阅读19次

  原文地址:WebGL多模型光照综合实例
  WebGL是一个非常的接近硬件底层的光栅化API, 从非常类似C/C++风格的API调用方式就可以看出来, 习惯了高级语言的我们会觉得很不友好,觉得特别繁琐. 这个也是很多人觉得WebGL难的原因之一. 如果我们要使用WebGL做一些项目,毫无疑问要么使用Three.js之类的3D库, 要么需要对原生的API进行封装. 这段时间查看了一些WebGL工具库的源代码, 参考封装出了一个简单的工具库,这样往后用WebGL做小项目就方便多了.

  经过前面章节的学习, WebGL的知识点掌握的差不多了, 终于到了做特效和Demo的阶段了,来看一下这节实现的特效:WebGL多物体多光源场景

polygons & lights

内容大纲

   实现图形绕坐标原点旋转, 同时给所有的物体增加环境光, 漫反射, 高光. 其中旋转功能使用矩阵复合变换实现; 光照部分比较复杂,实现了多个光源照射.

  1. 着色器
  2. 模型变换

着色器

顶点着色器

  代码很简单,逐顶点传入坐标,法向量矩阵,模型矩阵,mvp矩阵.

  attribute vec4 a_position;
  attribute vec4 a_normal;
  uniform mat4 u_modelMatrix;
  uniform mat4 u_normalMatrix;
  uniform mat4 u_mvpMatrix;
  varying vec3 v_normal;
  varying vec3 v_position;

  void main() {
    gl_Position = u_mvpMatrix * a_position;
    v_normal=vec3(u_normalMatrix * a_normal);
    v_position= vec3(u_modelMatrix * a_position);
  }

片元着色器

  分别在左前方和右后方添加了平行光源和点光源, 平行光源的高光使用的是宾氏模型, 它的高光过渡效果比较平滑; 点光源的高光使用的是冯氏模型, 它的高光部分比较明亮, 反射的效果比较好.
  最后将两个光源照射产生的漫反射,高光亮度相加,就得到它们的综合光照效果了.

    precision mediump float;
    uniform vec3 u_lightColor;
    uniform vec3 u_lightPosition;
    uniform vec3 u_lightPosition2;
    uniform vec3 u_ambientColor;
    uniform vec3 u_viewPosition;
    uniform vec4 u_color;
    varying vec3 v_normal;
    varying vec3 v_position;

    void main() {
        // 法向量归一化
        vec3 normal = normalize(v_normal);
        // 计算环境光反射颜色
        vec3 ambient = u_ambientColor * u_color.rgb;

        // 第一个光源:平行光
        vec3 lightDirection = normalize(u_lightPosition);
        // 计算法向量和光线的点积
        float cosTheta = max(dot(lightDirection, normal), 0.0);
        // 计算漫反射光的颜色
        vec3 diffuse = u_lightColor * u_color.rgb * cosTheta;
        // 宾氏模型高光
        float shininess =100.0;
        vec3 specularColor =vec3(1.0,1.0,1.0);
        vec3 viewDirection = normalize(u_viewPosition-v_position);
        vec3 halfwayDir = normalize(lightDirection + viewDirection);
        float specularWeighting = pow(max(dot(normal, halfwayDir), 0.0), shininess);
        vec3 specular = specularColor.rgb * specularWeighting * step(cosTheta,0.0);

        // 第二个光源:点光源
        vec3 lightDirection2 = normalize(u_lightPosition2 - v_position.xyz);
        // 计算法向量和光线的点积
        float cosTheta2 = max(dot(lightDirection2, normal), 0.0);
        // 计算漫反射光的颜色
        vec3 diffuse2 = u_lightColor * u_color.rgb * cosTheta2;
        // 冯氏模型高光
        float shininess2 =30.0;
        vec3 specularColor2 =vec3(1.0,1.0,1.0);
        vec3 reflectionDirection = reflect(-lightDirection2, normal);
        float specularWeighting2 = pow(max(dot(reflectionDirection, viewDirection), 0.0), shininess2);
        vec3 specular2 = specularColor2.rgb * specularWeighting2 * step(cosTheta,0.0);
        // 两个光源亮度相加
        gl_FragColor = vec4(diffuse+diffuse2+ambient+specular+specular2,u_color.a); 
    }

模型变换

  js代码部分使用工具库封装了原生WebGL的很多细节, 现在写起代码来要愉快得多了, 感觉和写canvas差不了太多😂. 这里重点介绍模型变换部分, 其他部分代码的细节之前章节已经介绍过了,所以不再详述.
  首先初始化着色器,并创建program对象; 这里使用了多种图形(圆球,立方体,圆柱体,圆锥体), 所以分别为它们创建缓冲区, 缓冲区数据主要包括顶点,法向量,索引.
  接着创建200个图形对象, 给每个对象赋予 随机x/y轴角速度, 随机初始点, 随机颜色.
  最后终于到了动画的环节了, 绕原点旋转可以分解为x轴旋转, y轴旋转和位移. 要注意的是矩阵复合变换相乘的顺序, 也就是左乘和右乘是有区别的, 学过线性代数的应该都有印象. 这里要实现图形先位移z,然后再旋转, 那么对应的复合变换矩阵就是这样

<模型矩阵> = <绕x轴旋转矩阵> * <绕y轴旋转矩阵> * <位移矩阵>

  模型矩阵与视图投影矩阵相乘得出mvp矩阵, 对模型矩阵逆转置后还可以求出逆转置矩阵.
  将矩阵和变量的值传递给着色器, 输出对应的图形缓冲区,然后根据图形对象依次绘制图形, 最后调用requestAnimationFrame递归执行动画函数.

    var canvas=document.getElementById('canvas'),
        gl=get3DContext(canvas,true);

    function main() {
        if (!gl) {
            console.log('Failed to get the rendering context for WebGL');
            return;
        }

    var program = createProgramInfo(gl, ['vs', 'fs']),
        sphereBuffer = createBufferInfoFromArrays(gl, Sphere(50)),
        cubeBuffer = createBufferInfoFromArrays(gl, Cube()),
        cylinderBuffer = createBufferInfoFromArrays(gl, Cylinder(0.5, 2, 40)),
        coneBuffer = createBufferInfoFromArrays(gl, Cone(0.5, 2, 40)),
        buffers = [sphereBuffer, cubeBuffer, cylinderBuffer, coneBuffer];

        for(var i=0;i<NUM;i++){
            Polygons.push({
                xRotation: Random(-60,60),
                yRotation: Random(-60,60),
                xAngle:0,
                yAngle:0,
                x:Random(-15,15),
                y:Random(-15,15),
                z:Random(3,20),
                color:Color.rgbToWebgl(Color.hslToRgb(RandomHsl()))
            });
        }

        gl.clearColor(0.1,0.1,0.1,1);
        gl.enable(gl.DEPTH_TEST);
        gl.viewport(0, 0, canvas.width, canvas.height);//设置绘图区域

        gl.useProgram(program.program);

        var modelMatrix = new Matrix4(),
            normalMatrix = new Matrix4(),
            mvpMatrix = new Matrix4(),
            last = Date.now();

        
        (function animate(){
            var now = new Date(),
                elapsed = now - last;

            last = now;

            // ...

            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

            setUniforms(program,{
                u_lightColor: [1,1,1],
                u_lightPosition: LIGHT_POS, 
                u_lightPosition2: LIGHT_POS2, 
                u_ambientColor:[0.4,0.4,0.4],
                u_viewPosition:[eyeX,eyeY,eyeZ]
            });

            var buffer;
            Polygons.forEach((polygon,i)=>{
                polygon.xAngle+=polygon.xRotation * elapsed / 1000;
                polygon.yAngle+=polygon.yRotation * elapsed / 1000;
                polygon.xAngle%=360;
                polygon.yAngle%=360;

                // 模型矩阵
                modelMatrix.setRotate(polygon.xAngle, 1, 0, 0);
                modelMatrix.rotate(polygon.yAngle, 0, 1, 0);
                modelMatrix.translate(0,0,polygon.z);
                // modelMatrix.translate(polygon.x,polygon.y,polygon.z);

                // 每次重置mvp矩阵
                mvpMatrix.setPerspective(45, canvas.width / canvas.height, 1.0, 200.0);
                mvpMatrix.lookAt(eyeX,eyeY,eyeZ, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
                mvpMatrix.multiply(modelMatrix);

                // 逆转置矩阵
                normalMatrix.setInverseOf(modelMatrix);
                normalMatrix.transpose();
                buffer = buffers[i % buffers.length];
                setBuffersAndAttributes(gl,program,buffer);
                setUniforms(program,{
                    u_color:polygon.color,
                    u_modelMatrix: modelMatrix.elements,
                    u_normalMatrix: normalMatrix.elements,
                    u_mvpMatrix: mvpMatrix.elements
                });

                gl.drawElements(gl.TRIANGLES, buffer.numElements, buffer.indexType, 0);
            });

            requestAnimationFrame(animate);
        }());
    }

总结

  使用工具类省略了很多繁琐无聊的部分,不用再去扣语法细节.比如获取变量地址, 赋值, 创建缓冲区. 我们可以把精力都集中到业务逻辑方面.

相关文章

网友评论

    本文标题:WebGL多模型光照综合实例

    本文链接:https://www.haomeiwen.com/subject/vbyfoxtx.html