效果
原理
结合透视矩阵和视觉矩阵进行绘制
知识点
01透视矩阵
透视矩阵将视图空间中的坐标转换为裁剪空间中的坐标,使得更远的物体看起来更小。
function perspectiveMatrix(fov, aspect, near, far) {const f = 1.0 / Math.tan(fov / 2);const nf = 1 / (near - far);return new Float32Array([f / aspect, 0, 0, 0,0, f, 0, 0,0, 0, (far + near) * nf, -1,0, 0, (2 * far * near) * nf, 0]);
}
参数解释
函数 perspectiveMatrix
创建并返回一个透视投影矩阵,用于3D图形的透视投影。以下是每个参数的详细说明:
-
fov
(Field of View, 视野):- 视野角度,即观察者在垂直方向上能够看到的视角。通常以弧度表示,角度单位可以通过
Math.PI / 180
转换为弧度。 - 例如,
45 * Math.PI / 180
表示45度的视角。
- 视野角度,即观察者在垂直方向上能够看到的视角。通常以弧度表示,角度单位可以通过
-
aspect
(Aspect Ratio, 纵横比):- 视图的宽高比,即视图的宽度除以高度。它决定了横向和纵向的缩放比例。
- 例如,如果视口的宽度是800像素,高度是600像素,则
aspect
= 800 / 600 = 4 / 3。
-
near
(Near Clipping Plane, 近裁剪平面):- 近裁剪平面的距离,即从观察者到最近可见物体的距离。该值必须大于0。
- 例如,设置为0.1表示从0.1单位距离开始可见物体。
-
far
(Far Clipping Plane, 远裁剪平面):- 远裁剪平面的距离,即从观察者到最远可见物体的距离。该值必须大于
near
值。 - 例如,设置为100.0表示到100单位距离结束可见物体。
- 远裁剪平面的距离,即从观察者到最远可见物体的距离。该值必须大于
02视图矩阵
视图矩阵将世界空间中的坐标转换为视图空间中的坐标,表示相机的视点。
function lookAtMatrix(eye, center, up) {const zAxis = normalize(subtractVectors(eye, center));const xAxis = normalize(cross(up, zAxis));const yAxis = normalize(cross(zAxis, xAxis));return new Float32Array([xAxis[0], yAxis[0], zAxis[0], 0,xAxis[1], yAxis[1], zAxis[1], 0,xAxis[2], yAxis[2], zAxis[2], 0,-dot(xAxis, eye), -dot(yAxis, eye), -dot(zAxis, eye), 1]);
}function normalize(v) {const length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);return [v[0] / length, v[1] / length, v[2] / length];
}function subtractVectors(a, b) {return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
}function cross(a, b) {return [a[1] * b[2] - a[2] * b[1],a[2] * b[0] - a[0] * b[2],a[0] * b[1] - a[1] * b[0]];
}function dot(a, b) {return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
}
参数解释
函数 lookAtMatrix
用于生成一个视图矩阵,用于将场景从世界空间转换到观察者空间。它通常用于实现相机的视图转换,使得相机看向特定的方向。这个函数的参数如下:
-
eye
:- 相机的位置,表示观察者所在的点。
- 例如
[1, 1, 1]
表示相机位于 (1, 1, 1) 的位置。
-
center
:- 视点,表示相机所看向的目标点。
- 例如
[0, 0, 0]
表示相机看向 (0, 0, 0) 的位置。
-
up
:- 上方向向量,表示哪一个方向是相机的“上”方向。通常为
[0, 1, 0]
,表示正Y方向。
- 上方向向量,表示哪一个方向是相机的“上”方向。通常为
函数 lookAtMatrix 用于生成一个视图矩阵,用于将场景从世界空间转换到观察者空间。它通常用于实现相机的视图转换,使得相机看向特定的方向。这个函数的参数如下:eye:相机的位置,表示观察者所在的点。
例如 [1, 1, 1] 表示相机位于 (1, 1, 1) 的位置。
center:视点,表示相机所看向的目标点。
例如 [0, 0, 0] 表示相机看向 (0, 0, 0) 的位置。
up:上方向向量,表示哪一个方向是相机的“上”方向。通常为 [0, 1, 0],表示正Y方向。
逻辑
设置透视矩阵和视觉矩阵
代码
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>立方体绘制</title><style>* {margin: 0;padding: 0;}canvas {margin: 50px auto 0;display: block;background: yellow;}</style><script src="test.js"></script></head><body><canvas id="canvas" width="400" height="400"></canvas><script>// 获取const canvas = document.getElementById('canvas')const gl = canvas.getContext('webgl')// 定义片源着色器和顶点着色器const vsSource = `attribute vec4 apos;attribute vec4 acolor;varying vec4 vcolor;uniform mat4 u_projectionMatrix;uniform mat4 u_viewMatrix;void main() {gl_Position =u_projectionMatrix * u_viewMatrix * apos;vcolor = acolor;}`const fsSource = `precision mediump float;varying vec4 vcolor;void main() {gl_FragColor = vcolor;}`// 初始化webglconst program = initShader(gl, vsSource, fsSource)gl.useProgram(program)// 获取 uniform 位置const u_projectionMatrixLocation = gl.getUniformLocation(program, 'u_projectionMatrix')const u_viewMatrixLocation = gl.getUniformLocation(program, 'u_viewMatrix')// 创建透视矩阵和视图矩阵const fov = Math.PI / 4 // 45度视角const aspect = canvas.width / canvas.heightconst near = 0.1const far = 10.0// lookAtMatrix 函数通过计算相机的位置、目标点和上方向,生成一个视图矩阵const projectionMatrix = perspectiveMatrix(fov, aspect, near, far)const viewMatrix = lookAtMatrix([3, 1, 7], [0, 0, 0], [0, 1, 0])function lookAtMatrix(eye, center, up) {const zAxis = normalize(subtractVectors(eye, center))const xAxis = normalize(cross(up, zAxis))const yAxis = normalize(cross(zAxis, xAxis))return new Float32Array([xAxis[0],yAxis[0],zAxis[0],0,xAxis[1],yAxis[1],zAxis[1],0,xAxis[2],yAxis[2],zAxis[2],0,-dot(xAxis, eye),-dot(yAxis, eye),-dot(zAxis, eye),1])}function normalize(v) {const length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])return [v[0] / length, v[1] / length, v[2] / length]}function subtractVectors(a, b) {return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]}function cross(a, b) {return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]}function dot(a, b) {return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]}function perspectiveMatrix(fov, aspect, near, far) {const f = 1.0 / Math.tan(fov / 2)const nf = 1 / (near - far)return new Float32Array([f / aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, (far + near) * nf, -1, 0, 0, 2 * far * near * nf, 0])}// 设置投影矩阵和视图矩阵gl.uniformMatrix4fv(u_projectionMatrixLocation, false, projectionMatrix)gl.uniformMatrix4fv(u_viewMatrixLocation, false, viewMatrix)// 创建缓存区function initBuffer(gl, program) {const aposLocation = gl.getAttribLocation(program, 'apos')const acolorLocation = gl.getAttribLocation(program, 'acolor')const verticesColors = new Float32Array([// 前面 (红色)-0.5,-0.5,0.5,1,0,0,1, // 左下0.5,-0.5,0.5,1,0,0,1, // 右下0.5,0.5,0.5,1,0,0,1, // 右上-0.5,0.5,0.5,1,0,0,1, // 左上// 后面 (绿色)-0.5,0.5,-0.5,0,1,0,1, // 左上-0.5,-0.5,-0.5,0,1,0,1, // 左下0.5,-0.5,-0.5,0,1,0,1, // 右下0.5,0.5,-0.5,0,1,0,1, // 右上// 左面 (蓝色)-0.5,-0.5,-0.5,0,0,1,1, // 后下-0.5,0.5,-0.5,0,0,1,1, // 后上-0.5,0.5,0.5,0,0,1,1, // 前上-0.5,-0.5,0.5,0,0,1,1, // 前下// 右面 (洋红色)0.5,-0.5,-0.5,1,0,1,1, // 后下0.5,0.5,-0.5,1,0,1,1, // 后上0.5,0.5,0.5,1,0,1,1, // 前上0.5,-0.5,0.5,1,0,1,1, // 前下// 顶面 (黄色)-0.5,0.5,0.5,1,1,0,1, // 前左0.5,0.5,0.5,1,1,0,1, // 前右0.5,0.5,-0.5,1,1,0,1, // 后右-0.5,0.5,-0.5,1,1,0,1, // 后左// 底面 (青色)-0.5,-0.5,0.5,0,1,1,1, // 前左0.5,-0.5,0.5,0,1,1,1, // 前右0.5,-0.5,-0.5,0,1,1,1, // 后右-0.5,-0.5,-0.5,0,1,1,1 // 后左])const indices = new Uint16Array([// 前面0, 1, 2, 0, 2, 3,// 后面4, 5, 6, 4, 6, 7,// 左面8, 9, 10, 8, 10, 11,// 右面12, 13, 14, 12, 14, 15,// 顶面16, 17, 18, 16, 18, 19,// 底面20, 21, 22, 20, 22, 23])const vertexColorBuffer = gl.createBuffer()gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer)gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW)const indexBuffer = gl.createBuffer()gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer)gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW)const FSIZE = verticesColors.BYTES_PER_ELEMENT// 启用顶点属性并指向顶点缓冲区中的数据gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, FSIZE * 7, 0)gl.enableVertexAttribArray(aposLocation)gl.vertexAttribPointer(acolorLocation, 4, gl.FLOAT, false, FSIZE * 7, FSIZE * 3)gl.enableVertexAttribArray(acolorLocation)}initBuffer(gl, program)// 启用深度测试// 清除缓冲区时同时清除颜色缓冲区和深度缓冲区:gl.enable(gl.DEPTH_TEST)gl.clearColor(0, 0, 0, 1)gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0)</script></body>
</html>