game101 第二次作业; webgl 实现
使用 THREEJS 作为基础框架,构建各类矩阵,自定义矩阵运算,最终完成
最终效果
在旋转与投影矩阵第二篇中,详细解释了三维变换的原理,详细解释了视图矩阵,规范立方体,投影矩阵,透视投影矩阵参数的概念。三维变换分为缩放变换,平移变换和旋转变换;视图矩阵描述了当前相机的位置和旋转角度得分复原;规范立方体描述了在默认情况下,元素应该处于 [-1. 1]^3 立方体内;投影矩阵描述了将人眼视角转换为标准相机视角,位于原点,lookAt 为 (0, 0, -1),up 为 (0, 1, 0);透视投影参数描述了已知 fov,aspct,near,far 从而推导其他的参数。
理论基础均已经讲解完毕,本章主要讲代码实现,列出过程量
构建透视投影矩阵需要四个参数,fov,aspct,near,far,根据四个参数推导 top,right,bottom,left。将透视相机转换为标准相机需要两步:
代码实现
// 构建一个透视投影矩阵, function perspMatrix(fov, aspect, near, far){ fov = Math.PI*fov/180; const top = Math.tan(fov/2) * Math.abs(near); const right = aspect * top; const bottom = -top; const left = -right; const ortho = new THREE.Matrix4() .scale(new THREE.Vector3(2/(right-left), 2/(top-bottom), 2/(near-far))) .multiply(new THREE.Matrix4().setPosition(-(right+left)/2, -(top+bottom)/2, -(near+far)/2)); const perseToOrtho = new THREE.Matrix4().set( near, 0, 0, 0, 0, near, 0, 0, 0, 0, near+far, -1*near*far, 0, 0,1, 0 ) return ortho.clone().multiply(perseToOrtho); }
设置 fov 为 90,aspect 为 1,near 为 -3,far 为 -9 的结果
const per = perspMatrix(90, 1, -3, -9);
绕任意过原点的轴的旋转变换矩阵理论基础如下,该表示绕过原点的 n 向量逆时针旋转 θ 角度的变换矩阵
绕过任意点的向量如何计算呢?上篇也介绍过,先移动到原点计算再逆移动回去
代码如下
// 绕任意过原点的轴的旋转变换矩阵 function getAnyRotation(axis, angle){ const cosTheta = Math.cos(angle); const matrix1 = new THREE.Matrix3().multiplyScalar(cosTheta); const matrix2 = getSelfMul(axis).multiplyScalar(1-cosTheta); const matrix3 = new THREE.Matrix3(); matrix3.set( 0, -axis.z, axis.y, axis.z, 0, -axis.x, -axis.y, axis.x, 0 ); matrix3.multiplyScalar(Math.sin(angle)); const result = matrix3Add( matrix3Add(matrix1,matrix2), matrix3); return new THREE.Matrix4().setFromMatrix3(result); }
三角形的三个顶点坐标初始值已经固定,若想让其旋转,首要方法就是通过矩阵运算改变顶点坐标,改变模型的位置信息的矩阵叫做模型矩阵,理论基础如下
这里构造一个绕 z 轴逆时针旋转的模型矩阵
// 等价于 makeRotationZ model.makeRotationZ(rotation); function rotateZ(model, rotate){ model.set( Math.cos(rotate), -Math.sin(rotate), 0, 0, Math.sin(rotate), Math.cos(rotate), 0, 0, 0, 0,1, 0, 0, 0, 0, 1 ) }
调用相应函数即可
window.addEventListener('keydown', ev => { if(ev.key === "a" ){ rotation += 0.2; rotateZ(model, rotation) return; } if ( ev.key === 'd'){ rotation -= 0.2; rotateZ(model, rotation); return; } if ( ev.key === 'q'){ angle -= 0.2; uniforms.anyRotate.value = getAnyRotation(vec, angle); return; } if ( ev.key === 'e'){ angle += 0.2; uniforms.anyRotate.value = getAnyRotation(vec, angle); } })
至此完成了 games101 的第二次作业,并以 web 端的形式展示出来,算是做了部分创新,有兴趣的伙伴可以一起探讨,加油