WebGL多模型光照综合实例

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

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

polygons

内容大纲

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

  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(1, 3, 40)),
        coneBuffer = createBufferInfoFromArrays(gl, Cone(1, 3, 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);
    })();
}

总结

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,924评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,902评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,716评论 0 239
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,783评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,166评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,510评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,784评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,476评论 0 196
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,196评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,459评论 2 243
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,978评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,321评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,964评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,046评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,803评论 0 193
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,530评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,420评论 2 265