这一节我们来介绍透视投影,那什么是透视投影呢?和我们说的正射投影有什么不同呢?在现实生活中距离我们越远的物体就看起来越小,这就是透视投影,就是在z轴方向上,距离我们越远的物体,在表现上来说就越小,这一节我们先写一个简单的透视投影,然后讨论一下透视矩阵,以及透视矩阵的生成过程,最后用透视矩阵实现E的展示。
一个简单的透视算法
我们可以这样想,一个最简单的透视算法就是用一个点的X,Y坐标除以Z坐标就行了,这就能实现到我们距离越远,距离越小的效果。我们在第5章的基础上修改顶点着色器,如下
ini
float z_half = a_position.z * 0.005;
vec3 t_position = vec3(a_position.x/z_half,a_position.y/z_half,a_position.z);
// vec4(a_position.xy / z, z, 1.0);
vec3 position = (matrix_translate * matrix_rotateX * matrix_rotateY * matrix_rotateZ * matrix_scale * vec4(t_position, 1)).xyz;
至于为什么要乘以0.005,那是由于我们的z值有点大,并且这个算法不准确,取了一个经验值。效果如下:
可以较为明显的看到3D的透视效果。感兴趣的同学可以自行调节参数再试试其他的。很明显我们的透视算法并不好,太简单了。见到到就不太正确,下面我们介绍真能整的透视变换。
透视矩阵的生成
关于什么是透视,首先要上图,一图胜千言。

这两张图来源于计算机图形学这本书。我们要有一个感性的认识就行了。什么是感性的认识呢,从数学上来讲就是要知道相关系数,看图说话,首先在z轴方向的大小肯定和z轴距离有关,其次和上下夹角ɑ有关,再次和左右∂夹角有关,所以生成矩阵函数的肯定是这样的 Z = f(z,ɑ,∂);具体到底怎么计算的,其实不在我们的讨论范围,我们又不是搞数学的,我们你需要的是结果。结果如下:
以上内容来源于deepseek。然后感性分析一下,嗯应该是对的。然后我们在代码里面根据这个信息,构造透视矩阵。
使用透视矩阵
然后将透视矩阵放到我们以前的代码中。主要代码:
ini
/**
* 构造透视投影矩阵(列主序)
* @param {number} fov - 垂直视野角(弧度制,如 Math.PI/4)
* @param {number} aspect - 宽高比(width / height)
* @param {number} near - 近裁剪面距离(必须 > 0)
* @param {number} far - 远裁剪面距离(必须 > near)
* @returns {Float32Array} 4x4 透视矩阵(列主序)
*/
function createPerspectiveMatrix(fov, aspect, near, far) {
// 1. 计算垂直方向的缩放因子(基于视野角)
const f = 1.0 / Math.tan(fov / 2); // 相当于 1/tan(fov/2)
// 2. 计算深度范围倒数(用于非线性深度映射)
const rangeInv = 1.0 / (near - far);
// 3. 初始化列主序矩阵(16元素 Float32Array)
const matrix = new Float32Array(16);
// 第一列
matrix[0] = f / aspect; // x 缩放(受宽高比影响)
matrix[1] = 0;
matrix[2] = 0;
matrix[3] = 0;
// 第二列
matrix[4] = 0;
matrix[5] = f; // y 缩放
matrix[6] = 0;
matrix[7] = 0;
// 第三列(深度处理)
matrix[8] = 0;
matrix[9] = 0;
matrix[10] = (far + near) * rangeInv; // 非线性深度映射
matrix[11] = -1; // 透视除法关键
// 第四列
matrix[12] = 0;
matrix[13] = 0;
matrix[14] = 2 * far * near * rangeInv; // 深度平移
matrix[15] = 0;
return matrix;
}
效果如下:
代码地址: gitee.com/feng-lianxi...
总结
有了透视矩阵我们就可以建立摄像机的概念了,这样你就会对three.js的摄像机场景画布,有更清晰的概念。下一节我们就介绍摄像机。