在计算机图形学中,摄像机模型是构建虚拟场景视觉效果的关键部分,它模拟了真实世界中摄像机的工作原理,决定了场景如何被投影到二维屏幕上。接下来,我们将深入探讨计算机图形学摄像机模型,并通过 JavaScript 代码进行实现。
一、摄像机模型基础概念
摄像机模型主要涉及几个核心概念:
- 摄像机位置:在三维场景中,摄像机所在的具体坐标位置,它决定了从哪里观察场景。
- 观察方向:摄像机朝向的方向,它确定了摄像机能够看到场景的具体区域。
- 上方向:用于定义摄像机的 “上方”,避免在渲染时出现画面颠倒等问题。
- 投影方式:分为正交投影和透视投影。正交投影不会产生近大远小的效果,常用于工程制图等场景;透视投影则模拟人眼观察物体的方式,物体越远看起来越小,这是在游戏、影视特效等场景中常用的投影方式 。
二、使用 JavaScript 实现摄像机模型
我们借助 JavaScript 和 WebGL 库来实现摄像机模型,首先需要搭建基础的 WebGL 场景。
1. 初始化 WebGL 场景
// 获取canvas元素
const canvas = document.getElementById('glCanvas');
// 获取WebGL绘图上下文
const gl = canvas.getContext('webgl');
if (!gl) {
alert('浏览器不支持WebGL');
}
// 设置视口大小
gl.viewport(0, 0, canvas.width, canvas.height);
2. 定义摄像机参数
我们创建一个Camera类来管理摄像机的相关参数。
class Camera {
constructor() {
// 摄像机位置
this.position = [0, 0, 5];
// 观察目标点
this.target = [0, 0, 0];
// 上方向
this.up = [0, 1, 0];
// 视角(角度制)
this.fov = 45;
// 近裁剪平面距离
this.near = 0.1;
// 远裁剪平面距离
this.far = 100.0;
}
}
3. 计算视图矩阵
视图矩阵用于将场景从世界坐标系转换到摄像机坐标系,它描述了摄像机的位置、观察方向和上方向。我们可以使用lookAt函数来计算视图矩阵。
function mat4() {
return new Float32Array(16).fill(0);
}
function lookAt(eye, center, up, out = mat4()) {
const zAxis = [0, 0, 0];
const xAxis = [0, 0, 0];
const yAxis = [0, 0, 0];
// 计算z轴
zAxis[0] = eye[0] - center[0];
zAxis[1] = eye[1] - center[1];
zAxis[2] = eye[2] - center[2];
// 归一化z轴
const len = Math.sqrt(zAxis[0] * zAxis[0] + zAxis[1] * zAxis[1] + zAxis[2] * zAxis[2]);
zAxis[0] /= len;
zAxis[1] /= len;
zAxis[2] /= len;
// 计算x轴
xAxis[0] = up[1] * zAxis[2] - up[2] * zAxis[1];
xAxis[1] = up[2] * zAxis[0] - up[0] * zAxis[2];
xAxis[2] = up[0] * zAxis[1] - up[1] * zAxis[0];
// 归一化x轴
const xLen = Math.sqrt(xAxis[0] * xAxis[0] + xAxis[1] * xAxis[1] + xAxis[2] * xAxis[2]);
xAxis[0] /= xLen;
xAxis[1] /= xLen;
xAxis[2] /= xLen;
// 计算y轴
yAxis[0] = zAxis[1] * xAxis[2] - zAxis[2] * xAxis[1];
yAxis[1] = zAxis[2] * xAxis[0] - zAxis[0] * xAxis[2];
yAxis[2] = zAxis[0] * xAxis[1] - zAxis[1] * xAxis[0];
// 构建视图矩阵
out[0] = xAxis[0];
out[1] = yAxis[0];
out[2] = zAxis[0];
out[3] = 0;
out[4] = xAxis[1];
out[5] = yAxis[1];
out[6] = zAxis[1];
out[7] = 0;
out[8] = xAxis[2];
out[9] = yAxis[2];
out[10] = zAxis[2];
out[11] = 0;
out[12] = -xAxis[0] * eye[0] - xAxis[1] * eye[1] - xAxis[2] * eye[2];
out[13] = -yAxis[0] * eye[0] - yAxis[1] * eye[1] - yAxis[2] * eye[2];
out[14] = -zAxis[0] * eye[0] - zAxis[1] * eye[1] - zAxis[2] * eye[2];
out[15] = 1;
return out;
}
4. 计算投影矩阵
这里我们以透视投影矩阵为例,透视投影矩阵根据摄像机的视角、纵横比、近裁剪平面和远裁剪平面来构建。
function perspective(fov, aspect, near, far, out = mat4()) {
const yMax = near * Math.tan((fov * Math.PI) / 360);
const yMin = -yMax;
const xMin = yMin * aspect;
const xMax = yMax * aspect;
out[0] = 2 * near / (xMax - xMin);
out[5] = 2 * near / (yMax - yMin);
out[8] = (xMax + xMin) / (xMax - xMin);
out[9] = (yMax + yMin) / (yMax - yMin);
out[10] = -(far + near) / (far - near);
out[11] = -1;
out[14] = -2 * far * near / (far - near);
out[15] = 0;
return out;
}
5. 在渲染中使用摄像机矩阵
在渲染循环中,我们需要将视图矩阵和投影矩阵传递给着色器,以便正确渲染场景。
const camera = new Camera();
const viewMatrix = lookAt(camera.position, camera.target, camera.up);
const projectionMatrix = perspective(camera.fov, canvas.width / canvas.height, camera.near, camera.far);
// 假设已经有顶点着色器和片元着色器程序,并且获取了相关的uniform变量位置
const viewMatrixLocation = gl.getUniformLocation(program, 'u_viewMatrix');
const projectionMatrixLocation = gl.getUniformLocation(program, 'u_projectionMatrix');
// 在渲染循环中更新矩阵
function render() {
gl.uniformMatrix4fv(viewMatrixLocation, false, viewMatrix);
gl.uniformMatrix4fv(projectionMatrixLocation, false, projectionMatrix);
// 执行其他渲染操作,如绘制几何体等
gl.drawArrays(gl.TRIANGLES, 0, numVertices);
requestAnimationFrame(render);
}
render();
三、摄像机的交互控制
为了让摄像机更加灵活,我们可以添加交互控制,比如通过鼠标拖动实现摄像机的旋转,通过鼠标滚轮实现摄像机的缩放(改变摄像机位置)。
1. 鼠标拖动旋转摄像机
let lastX = 0;
let lastY = 0;
let isDragging = false;
canvas.addEventListener('mousedown', (e) => {
isDragging = true;
lastX = e.clientX;
lastY = e.clientY;
});
canvas.addEventListener('mousemove', (e) => {
if (isDragging) {
const xoffset = e.clientX - lastX;
const yoffset = e.clientY - lastY;
lastX = e.clientX;
lastY = e.clientY;
// 这里简单地根据鼠标偏移量更新摄像机的观察方向
// 实际应用中需要更复杂的计算,如使用四元数等
const cameraDir = [
camera.position[0] - camera.target[0],
camera.position[1] - camera.target[1],
camera.position[2] - camera.target[2]
];
// 计算旋转后的方向
// 省略具体旋转计算代码
camera.position[0] = camera.target[0] + cameraDir[0];
camera.position[1] = camera.target[1] + cameraDir[1];
camera.position[2] = camera.target[2] + cameraDir[2];
// 更新视图矩阵
const viewMatrix = lookAt(camera.position, camera.target, camera.up);
const viewMatrixLocation = gl.getUniformLocation(program, 'u_viewMatrix');
gl.uniformMatrix4fv(viewMatrixLocation, false, viewMatrix);
}
});
canvas.addEventListener('mouseup', () => {
isDragging = false;
});
2. 鼠标滚轮缩放摄像机
canvas.addEventListener('wheel', (e) => {
const speed = 0.1;
camera.position[2] -= e.deltaY * speed;
if (camera.position[2] < camera.near) {
camera.position[2] = camera.near;
}
if (camera.position[2] > camera.far) {
camera.position[2] = camera.far;
}
// 更新视图矩阵
const viewMatrix = lookAt(camera.position, camera.target, camera.up);
const viewMatrixLocation = gl.getUniformLocation(program, 'u_viewMatrix');
gl.uniformMatrix4fv(viewMatrixLocation, false, viewMatrix);
});
通过以上步骤,我们就完成了一个基础的计算机图形学摄像机模型的实现与交互控制。在实际项目中,还可以进一步优化和扩展,比如添加更多的摄像机模式、更复杂的交互逻辑等。
上述文章涵盖了摄像机模型的核心知识与代码实现。若你觉得某些部分需要补充,或想更换代码语言,欢迎随时和我说。