type
Post
status
Published
date
Nov 4, 2018
slug
summary
WebGL 对于前端同学来说是一个非常值得学习的方向,没错,它不是一个必须要掌握的技能,但是非常值得学习,为什么呢?
tags
从入门到放弃
category
技术分享
icon
password
WebGL 对于前端同学来说是一个非常值得学习的方向,没错,它不是一个必须要掌握的技能,但是非常值得学习,为什么呢?
可能大多数同学们首先想到的是 WebGL 可以做出牛 X 的效果。但是仅仅为了这点,我建议学习 Three.js 就够了,它是基于 WebGL 封装的三维图形库,无需了解底层的实现原理,稍微有点空间知识就能上手,用于生产时我也建议使用 Three.js。但是这就好像“我们学了 React 难道就不需要会 Dom 操作了吗”一样。了解 WebGL 不仅仅是为了做特效这么简单,我认为它值得学习有以下四点原因:
1. 学习了 WebGL 相当于学习了 OpenGL。
许多同学应该知道,WebGL 是 OpenGL 的 JavaScript 实现。那么 OpenGL 是什么,OpenGL 是 Open Graphics Library 的简称,即“开放式图形库”,用来做 2D、3D 矢量图形的光栅化渲染。同样的图形库还有 DirectX(爱玩游戏的同学肯定知道)、Vulkan(近几年新出现的图形库,王者荣耀就出过 Vulkan 版)。即便是以上这些图形库在 API 上都是很相似的,WebGL 和 OpenGL 的 API 更是一模一样了,只是语言不同:
WebGL | OpenGL |
constant | gl.VERTEX_SHADER |
function | gl.drawElements |
2. 了解 GPU
“图形库”说白了它就是操作 GPU 的 API,我们平时的写代码都算是 CPU 编程,用了 OpenGL 或者 WebGL 后我们就可以做 GPU 编程。在学习 WebGL 的过程中,我们不仅需要使用 JavaScript 调用 WebGL 提供的 API,我们还将学习一门新的语言 GLSL(Graphics Library Shader Language),也就是着色器语言。为什么要使用两种语言?因为就像刚刚说的,在 WebGL 程序中,既有在 CPU 执行的部分,也有在 GPU 执行的部分,JavaScript 调用 WebGL 的部分属于 CPU 编程,而 GLSL 可以直接通过 WebGL 传给 GPU 执行,即 GPU 编程,这其中的一些细节后文会讲到。
3. 入门图形学
使用 WebGL 不同于使用 three.js,后者只要有一些空间想象能力就能使用,但是前者却需要你掌握一些基本的图形学知识:比如渲染管线、着色器、纹理、光照等。了解了图形学以后,你对游戏引擎、电影特效也会有更深的理解。
4. 大厂、大佬布道
百度 ECharts 3D、阿里 G3D、蚂蚁金服 AntV 里都能看到 WebGL 的身影;winter 老师也在 08 年 GMTC 大会上对图形学、WebGL 做过分享。
既然学习它有这么多好处,那就让我们开始吧,但在正式使用 webgl 之前让我先学习些基础知识。
1 图形学相关知识
1.1 渲染管线
我们需要了解的第一个知识点叫渲染管线。什么叫管线?其英文叫 pipeline,我们知道他有流水线的意思,其实渲染管线就是渲染的流水线,我们可以理解成渲染的流程。而渲染又是啥?就是要将我们定义的三维模型数据变成二维图像,最终呈现在屏幕上。渲染的流程具体说来有三个阶段:
- 应用程序阶段(Application Stage)
- 几何阶段(Geomguanetry Stage)
- 光栅化阶段(Rasterizer Stage)
其中应用程序阶段就是我们开发者控制的阶段,即我们写代码的阶段,我们可以定义模型、相机、光照、纹理、动画等。几何阶段也有开发者可控制(可编程)的部分,但是几何阶段需要硬件支持,也就是 GPU 支持,可以理解为:应用程序阶段还是 CPU 编程,几何阶段是 GPU 编程。那中间一定有个衔接点,一会儿会讲到。
再说到几何阶段和光栅化阶段,这两个阶段需要硬件支持也就是 GPU,所以这两个阶段也被称为 GPU 渲染管线。这两个阶段我们先看下面两张图:
其中:
- 绿色的阶段都是完全可编程的(GLSL)。
- 黄色的阶段可配置,但不可编程(使用 WebGL API 提供的常量)。
- 蓝色的阶段完全固定。
首先我们需要知道,我们使用代码定义的模型大概是什么样的,其实不管再复杂的模型都是由一个个小的三角形构成的,这样小的三角形叫做图元,渲染一个复杂模型实则就是渲染许多个三维空间中三角形。那么我们就以渲染一个三角形来看上面的过程。
1)顶点着色器
三角形有三个顶点,这三个顶点包含着一些信息,最重要的就是顶点位置,三维空间的 3 个顶点一定能确定唯一的三角形(这里严谨点会在一条直线上,那也是能确定唯一线段的),但 4 个顶点就不一定能确定唯一图元了,这也解释了为什么用三角形做图元,所以顶点着色器,就是接收顶点数据,并渲染。
2)图元着色器
我们刚才说了,三角形就是图元,其实这样说不准确,在 OpenGL 和 DirectX 中,我们可以事先用多个三角形拼接成一个自定义的最小图元,这样可以减少图元遍历次数,当然这不是所有模型都适用,你需要自己事先找寻规律设置好图元,所以图元着色器并不是个必要的步骤,而且更重要的是 webgl 中还不支持图元着色器。
3)裁剪
我们最终的渲染结果会呈现在 canvas 画布上,始终是要有个大小的,那么模型超出画布的部分就会被裁减,减少不必要的渲染。
4)屏幕映射
即三维空间映射到屏幕的过程,后面还会提到。
以上都是几何阶段,你会发现顶点位置在这个阶段是要素,顶点位置决定了顶点着色器的输入输出,顶点位置决定了一个模型会不会被裁减,顶点位置决定了映射到屏幕的样子。接下来就是光栅化阶段,光栅化其实就是像素化的意思,因为最终投影的屏幕时像素点构成的,所以要将连续的矢量图转化成离散的位图,图 2 其实看得很明白。
5)三角形设定和遍历
这里的三角形指的就是上面说的图元,这里需要遍历,为了接下来的像素着色。
6)像素着色器
我们之前只保存了,顶点位置信息,其实到了这步我们还需要顶点颜色信息才能将整个三角形带颜色地渲染出来,为什么只需要顶点颜色就行了,因为这里计算机做了一个插值操作,插值其实是个计算机很常见的操作,比如我们在做动画时有个词叫补间动画,我们只需要定义关键帧,定义动画类型,中间流畅的动画过程自动就生成了,其实就是计算机帮你做了。这里我们定义了顶点颜色,整个三角形的颜色就自动生成了,那如果你说这个插值不是你想要的结果?不好意思,你应该去定义个小一点的图元去做更精细化的控制。
7)合并阶段
就是把所有模型的渲染合并,因为这其中可能有半透明的模型,所以这一部分也是有一定渲染工作要做
以上就是渲染管线了,即渲染的流程了,这其中不清楚的部分其实就是模型是怎么投影到屏幕的,三维是怎么转化成二维的
1.2 模型、视图、投影(MVP)
一图胜千言
在之前分享过程中,我还可以比划比划,这里大家只能看上图自行理解。首先概念先清楚:
- 模型,即我们定义的三维空间中的物体
- 模型变换,即该物体自己的变换,比如旋转、移动、缩放
- 视图,即摄像机决定的所看到的空间
- 视图变换,即摄像机的变化导致的所看到的空间变化
- 投影,这个简单,其实就分两种:正交投影和透视投影
- 投影变化,这个也简单,只能在这两种投影间变换,一般就不变
但是在代码层面是怎么做的呢?这里需要复习下线性代数知识,理解下矩阵的空间意义,其实三维空间里变换可以视为“乘一个矩阵”。
// 使用 minMatrix 各种矩阵的生成和初始化 var mMatrix = m.identity(m.create()); // 模型变换矩阵 var vMatrix = m.identity(m.create()); // 视图变换矩阵 var pMatrix = m.identity(m.create()); // 投影变换矩阵 var mvpMatrix = m.identity(m.create()); // 最终的坐标变换矩阵 m.lookAt([0.0, 0.0, 5.0], [0, 0, 0], [0, 1, 0], vMatrix); // 视图变换矩阵初始化 m.perspective(45, c.width / c.height, 0.1, 100, pMatrix); // 投影变换矩阵初始化 // 各个矩阵相乘的顺序使用示例 m.multiply(pMatrix, vMatrix, tmpMatrix); // p和v相乘 m.multiply(mvpMatrix, mMatrix, mvpMatrix); // 然后和m相乘
2 WebGL
前面我们了解了图形学的一些基础知识,下面我们正式开始讲 webgl,其实编写 webgl 程序就是我们之前说的应用程序阶段,就是我们使用 javascript 调 webgl API 的过程。这个过程大致分为下面几步:
- 从 HTML 中获取 canvas 对象
- 从 canvas 中获取 WebGL 的 context
- 编译着色器
- 准备模型数据
- 顶点缓存对象(VBO)生成和通知
- 索引缓存对象(IBO)生成和通知
- 生成 mvp 变换矩阵
- 发出绘图命令
- 更新 canvas 并渲染
这其中难点:
- 着色器:GLSL
- MVP 矩阵:线性代数、第三方库
- VBO、IBO:数据从内存存到显存以供 GPU 使用
2.1 着色器
那我们来看下第一个难点–着色器(shader),着色器的作用之前已经讲过了,我们来看下代码长什么样:
<script id="vs" type="x-shader/x-vertex"> attribute vec3 position; attribute vec4 color; uniform mat4 mvpMatrix; varying vec4 vColor; void main(void){ vColor = color; gl_Position = mvpMatrix * vec4(position, 1.0); } </script> <script id="fs" type="x-shader/x-fragment"> precision mediump float; varying vec4 vColor; void main(void){ gl_FragColor = vColor; } </script>
- GLSL,类 C 语言
- 顶点着色器、片段着色器
- Attribute: 每个顶点不同的属性
- Uniform: 顶点相同的属性
- Varying: 顶点着色器与片段着色器相互传递的属性
- Precision: 精度(mediump:精确度中)
- 对三维图形变换需要对它作用四维矩阵(https://zhuanlan.zhihu.com/p/20514920)
- 齐次坐标变换
2.2 变换矩阵
这个也是前面提到过的了,主要看下下面两个方法。
m.lookAt([0.0, 0.0, 5.0], [0, 0, 0], [0, 1, 0], vMatrix); // 视图变换矩阵初始化 m.perspective(45, c.width / c.height, 0.1, 100, pMatrix); // 投影变换矩阵初始化
lookAt
是生成视图矩阵的方法,有 4 个参数,第一个参数是相机位置,这里在第二个参数是看向哪个点,第三个参数是相机摆向,这个怎么理解呢,比如把我们人看成相机,我们正常站着,我们的摆向就是头朝天花板;我们躺在,我们摆向就是头朝墙;我们倒立着,我们摆向就是头朝地,第四个参数用来保存计算结果的矩阵。perspective
是生成透视投影矩阵的方法,我们前面知道投影分为透视投影和正交投影,perspective 的意思就是透视,有 5 个参数,第一个参数是视角,第二个参数是屏幕的宽高比例,第三个参数 是近截面的位置,第四个参数远界面位置,最后一个参数 用来保存计算结果的矩阵。2.3 缓存对象
我们之前提到过,我们用 javascript 调用 webgl API 时,属于 CPU 编程,中间定义的变量存在内存中,而最终需要 GPU 渲染,我们需要将变量存到显存中,缓存对象可以理解为做这件事的东西。
- 顶点属性、VBO、顶点属性元素数一一对应
- 顶点可以有许多属性,如位置、颜色、法线等(其中位置是必须的,其他可选)
- 顶点的每个属性都要定义属性的数据,由于是一维数组所以需要用顶点属性元素数标识,如:顶点位置属性元素数是 3(xyz)顶点、顶点颜色属性元素数是 4(rgba)
- 顶点的每个属性都要定义 VBO,CPU 编程与 GPU 编程的衔接
2.4 实践
一个简单 demo,转动的彩色三角形。
- index.html
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Demo</title> <script src="./script/minMatrix.js" type="text/javascript"></script> <script src="./script/script.js" type="text/javascript"></script> <script id="vs" type="x-shader/x-vertex"> attribute vec3 position; attribute vec4 color; uniform mat4 mvpMatrix; varying vec4 vColor; void main(void){ vColor = color; gl_Position = mvpMatrix * vec4(position, 1.0); } </script> <script id="fs" type="x-shader/x-fragment"> precision mediump float; varying vec4 vColor; void main(void){ gl_FragColor = vColor; } </script> </head> <body> <canvas id="canvas" width="500" height="300"></canvas> </body> </html>
• script/script.js
onload = function() { // 获取canvas元素 var c = document.getElementById("canvas"); c.width = 500; c.height = 300; // 获取webgl上下文 var gl = c.getContext("webgl"); // 生成顶点和片段着色器 var v_shader = create_shader("vs"); var f_shader = create_shader("fs"); // 生成和链接程序对象 var prg = create_program(v_shader, f_shader); // attributeLocation 的获取,并存到到数组 var attLocation = new Array(2); attLocation[0] = gl.getAttribLocation(prg, "position"); attLocation[1] = gl.getAttribLocation(prg, "color"); // 将 attribute 的元素数存储到数组中 var attStride = [3, 4]; // 存储顶点属性的数组 var position = [0.0, 1.0, 0.0, 1.0, 0.0, 0.0, -1.0, 0.0, 0.0]; var color = [1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0]; // VBO的生成 var pos_vbo = create_vbo(position); var col_vbo = create_vbo(color); // 注册VBO set_attribute([pos_vbo, col_vbo], attLocation, attStride); // uniformLocation 的获取 var uniLocation = gl.getUniformLocation(prg, "mvpMatrix"); // 使用 minMatrix 的矩阵相关处理 // 生成 matIV 实例对象 var m = new matIV(); // 生成和初始化各种矩阵 var mMatrix = m.identity(m.create()); // 模型变化矩阵 var vMatrix = m.identity(m.create()); // 视图变化矩阵 var pMatrix = m.identity(m.create()); // 投影矩阵 var tmpMatrix = m.identity(m.create()); // vp temp矩阵 var mvpMatrix = m.identity(m.create()); // mvp 矩阵 // 视图×投影变换矩阵 m.lookAt([0.0, 0.0, 5.0], [0, 0, 0], [0, 1, 0], vMatrix); m.perspective(45, c.width / c.height, 0.1, 100, pMatrix); m.multiply(pMatrix, vMatrix, tmpMatrix); // 计数器声明 var count = 0; // 渲染循环 setInterval(function() { // canvas 初始化 gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clearDepth(1.0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // 递增计数器 count++; // 根据计数器计算弧度 var rad = ((count % 360) * Math.PI) / 180; // 1° = π/180°,1rad= 180°/π。 // 一周是 360 度,也是 2π 弧度,即 360°=2π. // 围绕Y轴旋转 m.identity(mMatrix); m.translate(mMatrix, [0.0, 0.0, 0.0], mMatrix); m.rotate(mMatrix, rad, [0, 1, 0], mMatrix); // 完成并渲染模型,坐标变换矩阵 m.multiply(tmpMatrix, mMatrix, mvpMatrix); gl.uniformMatrix4fv(uniLocation, false, mvpMatrix); gl.drawArrays(gl.TRIANGLES, 0, 3); // 重绘上下文 gl.flush(); }, 1000 / 30); // 用于生成着色器的函数 function create_shader(id) { // 存储着色器的变量 var shader; // 从HTML获取对script标记的引用 var scriptElement = document.getElementById(id); // script标签不存在时删除 if (!scriptElement) { return; } // 检查script标记的type属性 switch (scriptElement.type) { // 顶点着色器 case "x-shader/x-vertex": shader = gl.createShader(gl.VERTEX_SHADER); console.log(gl.VERTEX_SHADER); break; // 片段着色器 case "x-shader/x-fragment": shader = gl.createShader(gl.FRAGMENT_SHADER); break; default: return; } // 将代码指定给生成的着色器 gl.shaderSource(shader, scriptElement.text); // 编译着色器 gl.compileShader(shader); // 检查着色器是否正确编译 if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { // 如果成功,则返回着色器并退出 return shader; } else { // 失败时发出错误日志警报 alert(gl.getShaderInfoLog(shader)); } } // 生成程序对象并链接着色器的函数 function create_program(vs, fs) { // 生成程序对象 var program = gl.createProgram(); // 将着色器指定给程序对象 gl.attachShader(program, vs); gl.attachShader(program, fs); // 链接着色器 gl.linkProgram(program); // 检查着色器链接是否正确 if (gl.getProgramParameter(program, gl.LINK_STATUS)) { // 在成功后启用程序对象 gl.useProgram(program); // 返回程序对象并退出 return program; } else { // 失败时发出错误日志警报 alert(gl.getProgramInfoLog(program)); } } // 生成 VBO 的函数 function create_vbo(data) { // 生成缓冲区对象 var vbo = gl.createBuffer(); // 绑定缓冲区 gl.bindBuffer(gl.ARRAY_BUFFER, vbo); // 将数据放入缓冲区 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW); // 禁用缓冲区绑定 gl.bindBuffer(gl.ARRAY_BUFFER, null); // 返回生成的 VBO return vbo; } // 用于绑定和注册 VBO 的函数 function set_attribute(vbo, attL, attS) { // 处理作为参数接收的数组 for (var i in vbo) { // 绑定缓冲区 gl.bindBuffer(gl.ARRAY_BUFFER, vbo[i]); // 启用 attributeLocation gl.enableVertexAttribArray(attL[i]); // 通知 attributeLocation 并注册 gl.vertexAttribPointer(attL[i], attS[i], gl.FLOAT, false, 0, 0); } } };
• script/minMatrix.js
function matIV() { this.create = function() { return new Float32Array(16); }; this.identity = function(dest) { dest[0] = 1; dest[1] = 0; dest[2] = 0; dest[3] = 0; dest[4] = 0; dest[5] = 1; dest[6] = 0; dest[7] = 0; dest[8] = 0; dest[9] = 0; dest[10] = 1; dest[11] = 0; dest[12] = 0; dest[13] = 0; dest[14] = 0; dest[15] = 1; return dest; }; this.multiply = function(mat1, mat2, dest) { var a = mat1[0], b = mat1[1], c = mat1[2], d = mat1[3], e = mat1[4], f = mat1[5], g = mat1[6], h = mat1[7], i = mat1[8], j = mat1[9], k = mat1[10], l = mat1[11], m = mat1[12], n = mat1[13], o = mat1[14], p = mat1[15], A = mat2[0], B = mat2[1], C = mat2[2], D = mat2[3], E = mat2[4], F = mat2[5], G = mat2[6], H = mat2[7], I = mat2[8], J = mat2[9], K = mat2[10], L = mat2[11], M = mat2[12], N = mat2[13], O = mat2[14], P = mat2[15]; dest[0] = A * a + B * e + C * i + D * m; dest[1] = A * b + B * f + C * j + D * n; dest[2] = A * c + B * g + C * k + D * o; dest[3] = A * d + B * h + C * l + D * p; dest[4] = E * a + F * e + G * i + H * m; dest[5] = E * b + F * f + G * j + H * n; dest[6] = E * c + F * g + G * k + H * o; dest[7] = E * d + F * h + G * l + H * p; dest[8] = I * a + J * e + K * i + L * m; dest[9] = I * b + J * f + K * j + L * n; dest[10] = I * c + J * g + K * k + L * o; dest[11] = I * d + J * h + K * l + L * p; dest[12] = M * a + N * e + O * i + P * m; dest[13] = M * b + N * f + O * j + P * n; dest[14] = M * c + N * g + O * k + P * o; dest[15] = M * d + N * h + O * l + P * p; return dest; }; this.scale = function(mat, vec, dest) { dest[0] = mat[0] * vec[0]; dest[1] = mat[1] * vec[0]; dest[2] = mat[2] * vec[0]; dest[3] = mat[3] * vec[0]; dest[4] = mat[4] * vec[1]; dest[5] = mat[5] * vec[1]; dest[6] = mat[6] * vec[1]; dest[7] = mat[7] * vec[1]; dest[8] = mat[8] * vec[2]; dest[9] = mat[9] * vec[2]; dest[10] = mat[10] * vec[2]; dest[11] = mat[11] * vec[2]; dest[12] = mat[12]; dest[13] = mat[13]; dest[14] = mat[14]; dest[15] = mat[15]; return dest; }; this.translate = function(mat, vec, dest) { dest[0] = mat[0]; dest[1] = mat[1]; dest[2] = mat[2]; dest[3] = mat[3]; dest[4] = mat[4]; dest[5] = mat[5]; dest[6] = mat[6]; dest[7] = mat[7]; dest[8] = mat[8]; dest[9] = mat[9]; dest[10] = mat[10]; dest[11] = mat[11]; dest[12] = mat[0] * vec[0] + mat[4] * vec[1] + mat[8] * vec[2] + mat[12]; dest[13] = mat[1] * vec[0] + mat[5] * vec[1] + mat[9] * vec[2] + mat[13]; dest[14] = mat[2] * vec[0] + mat[6] * vec[1] + mat[10] * vec[2] + mat[14]; dest[15] = mat[3] * vec[0] + mat[7] * vec[1] + mat[11] * vec[2] + mat[15]; return dest; }; this.rotate = function(mat, angle, axis, dest) { var sq = Math.sqrt( axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2] ); if (!sq) { return null; } var a = axis[0], b = axis[1], c = axis[2]; if (sq != 1) { sq = 1 / sq; a *= sq; b *= sq; c *= sq; } var d = Math.sin(angle), e = Math.cos(angle), f = 1 - e, g = mat[0], h = mat[1], i = mat[2], j = mat[3], k = mat[4], l = mat[5], m = mat[6], n = mat[7], o = mat[8], p = mat[9], q = mat[10], r = mat[11], s = a * a * f + e, t = b * a * f + c * d, u = c * a * f - b * d, v = a * b * f - c * d, w = b * b * f + e, x = c * b * f + a * d, y = a * c * f + b * d, z = b * c * f - a * d, A = c * c * f + e; if (angle) { if (mat != dest) { dest[12] = mat[12]; dest[13] = mat[13]; dest[14] = mat[14]; dest[15] = mat[15]; } } else { dest = mat; } dest[0] = g * s + k * t + o * u; dest[1] = h * s + l * t + p * u; dest[2] = i * s + m * t + q * u; dest[3] = j * s + n * t + r * u; dest[4] = g * v + k * w + o * x; dest[5] = h * v + l * w + p * x; dest[6] = i * v + m * w + q * x; dest[7] = j * v + n * w + r * x; dest[8] = g * y + k * z + o * A; dest[9] = h * y + l * z + p * A; dest[10] = i * y + m * z + q * A; dest[11] = j * y + n * z + r * A; return dest; }; this.lookAt = function(eye, center, up, dest) { var eyeX = eye[0], eyeY = eye[1], eyeZ = eye[2], upX = up[0], upY = up[1], upZ = up[2], centerX = center[0], centerY = center[1], centerZ = center[2]; if (eyeX == centerX && eyeY == centerY && eyeZ == centerZ) { return this.identity(dest); } var x0, x1, x2, y0, y1, y2, z0, z1, z2, l; z0 = eyeX - center[0]; z1 = eyeY - center[1]; z2 = eyeZ - center[2]; l = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2); z0 *= l; z1 *= l; z2 *= l; x0 = upY * z2 - upZ * z1; x1 = upZ * z0 - upX * z2; x2 = upX * z1 - upY * z0; l = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2); if (!l) { x0 = 0; x1 = 0; x2 = 0; } else { l = 1 / l; x0 *= l; x1 *= l; x2 *= l; } y0 = z1 * x2 - z2 * x1; y1 = z2 * x0 - z0 * x2; y2 = z0 * x1 - z1 * x0; l = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2); if (!l) { y0 = 0; y1 = 0; y2 = 0; } else { l = 1 / l; y0 *= l; y1 *= l; y2 *= l; } dest[0] = x0; dest[1] = y0; dest[2] = z0; dest[3] = 0; dest[4] = x1; dest[5] = y1; dest[6] = z1; dest[7] = 0; dest[8] = x2; dest[9] = y2; dest[10] = z2; dest[11] = 0; dest[12] = -(x0 * eyeX + x1 * eyeY + x2 * eyeZ); dest[13] = -(y0 * eyeX + y1 * eyeY + y2 * eyeZ); dest[14] = -(z0 * eyeX + z1 * eyeY + z2 * eyeZ); dest[15] = 1; return dest; }; this.perspective = function(fovy, aspect, near, far, dest) { var t = near * Math.tan((fovy * Math.PI) / 360); var r = t * aspect; var a = r * 2, b = t * 2, c = far - near; dest[0] = (near * 2) / a; dest[1] = 0; dest[2] = 0; dest[3] = 0; dest[4] = 0; dest[5] = (near * 2) / b; dest[6] = 0; dest[7] = 0; dest[8] = 0; dest[9] = 0; dest[10] = -(far + near) / c; dest[11] = -1; dest[12] = 0; dest[13] = 0; dest[14] = -(far * near * 2) / c; dest[15] = 0; return dest; }; this.transpose = function(mat, dest) { dest[0] = mat[0]; dest[1] = mat[4]; dest[2] = mat[8]; dest[3] = mat[12]; dest[4] = mat[1]; dest[5] = mat[5]; dest[6] = mat[9]; dest[7] = mat[13]; dest[8] = mat[2]; dest[9] = mat[6]; dest[10] = mat[10]; dest[11] = mat[14]; dest[12] = mat[3]; dest[13] = mat[7]; dest[14] = mat[11]; dest[15] = mat[15]; return dest; }; this.inverse = function(mat, dest) { var a = mat[0], b = mat[1], c = mat[2], d = mat[3], e = mat[4], f = mat[5], g = mat[6], h = mat[7], i = mat[8], j = mat[9], k = mat[10], l = mat[11], m = mat[12], n = mat[13], o = mat[14], p = mat[15], q = a * f - b * e, r = a * g - c * e, s = a * h - d * e, t = b * g - c * f, u = b * h - d * f, v = c * h - d * g, w = i * n - j * m, x = i * o - k * m, y = i * p - l * m, z = j * o - k * n, A = j * p - l * n, B = k * p - l * o, ivd = 1 / (q * B - r * A + s * z + t * y - u * x + v * w); dest[0] = (f * B - g * A + h * z) * ivd; dest[1] = (-b * B + c * A - d * z) * ivd; dest[2] = (n * v - o * u + p * t) * ivd; dest[3] = (-j * v + k * u - l * t) * ivd; dest[4] = (-e * B + g * y - h * x) * ivd; dest[5] = (a * B - c * y + d * x) * ivd; dest[6] = (-m * v + o * s - p * r) * ivd; dest[7] = (i * v - k * s + l * r) * ivd; dest[8] = (e * A - f * y + h * w) * ivd; dest[9] = (-a * A + b * y - d * w) * ivd; dest[10] = (m * u - n * s + p * q) * ivd; dest[11] = (-i * u + j * s - l * q) * ivd; dest[12] = (-e * z + f * x - g * w) * ivd; dest[13] = (a * z - b * x + c * w) * ivd; dest[14] = (-m * t + n * r - o * q) * ivd; dest[15] = (i * t - j * r + k * q) * ivd; return dest; }; }
代码有注释就不展开讲了
3 参考
- 作者:Wave52
- 链接:https://vercel.wuchengran.com/article/a07a259e-76d5-4721-b3bd-defed5b23292
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章