三维几何变换
一、学习目的
了解几何变换的意义
掌握三维基本几何变换的算法
二、学习内容
在本次试验中,我们实现透视投影和三维几何变换。我们首先定义一个立方体作为我们要进行变换的三维物体。
三、具体代码
(1)算法实现
// 获取Canvas元素
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');// 定义立方体的顶点(8个顶点,每个顶点有x,y,z坐标)
// 定义一个单位立方体,中心在原点
let cube = [[-0.5, -0.5, -0.5], // 0: 左下后[0.5, -0.5, -0.5], // 1: 右下后[0.5, 0.5, -0.5], // 2: 右上后[-0.5, 0.5, -0.5], // 3: 左上后[-0.5, -0.5, 0.5], // 4: 左下前[0.5, -0.5, 0.5], // 5: 右下前[0.5, 0.5, 0.5], // 6: 右上前[-0.5, 0.5, 0.5] // 7: 左上前
];// 定义立方体的边(12条边,每条边连接两个顶点)
const edges = [[0, 1], [1, 2], [2, 3], [3, 0], // 后面[4, 5], [5, 6], [6, 7], [7, 4], // 前面[0, 4], [1, 5], [2, 6], [3, 7] // 连接前后面的边
];// 保存原始立方体顶点
const originalCube = JSON.parse(JSON.stringify(cube));// 变换参数
let scale = 100; // 缩放因子
let offsetX = canvas.width / 2;
let offsetY = canvas.height / 2;
let distance = 5; // 观察点到投影平面的距离// 绘制坐标轴
function drawAxes() {const axisLength = 150;// X轴ctx.beginPath();ctx.strokeStyle = '#e74c3c';ctx.lineWidth = 2;ctx.moveTo(offsetX, offsetY);ctx.lineTo(offsetX + axisLength, offsetY);ctx.stroke();// Y轴ctx.beginPath();ctx.strokeStyle = '#3498db';ctx.moveTo(offsetX, offsetY);ctx.lineTo(offsetX, offsetY - axisLength);ctx.stroke();// Z轴ctx.beginPath();ctx.strokeStyle = '#f1c40f';ctx.moveTo(offsetX, offsetY);ctx.lineTo(offsetX - axisLength * 0.7, offsetY + axisLength * 0.7);ctx.stroke();// 添加轴标签ctx.font = '14px Arial';ctx.fillStyle = '#e74c3c';ctx.fillText("X", offsetX + axisLength + 5, offsetY + 5);ctx.fillStyle = '#3498db';ctx.fillText("Y", offsetX - 15, offsetY - axisLength - 5);ctx.fillStyle = '#f1c40f';ctx.fillText("Z", offsetX - axisLength * 0.7 - 15, offsetY + axisLength * 0.7 + 15);
}// 透视投影函数
function perspectiveProjection(point) {// 简单的透视投影计算let z = point[2] + distance;let factor = distance / z;// 计算投影后的2D坐标let x = point[0] * factor * scale + offsetX;let y = -point[1] * factor * scale + offsetY; // 翻转Y轴以匹配屏幕坐标return [x, y];
}// 绘制立方体
function drawCube() {ctx.clearRect(0, 0, canvas.width, canvas.height);// 绘制坐标轴drawAxes();// 进行投影并绘制立方体的边ctx.beginPath();ctx.strokeStyle = 'rgb(0, 255, 0)'; // 使用RGB格式的绿色ctx.lineWidth = 3;for (let edge of edges) {const [p1Index, p2Index] = edge;const p1Projected = perspectiveProjection(cube[p1Index]);const p2Projected = perspectiveProjection(cube[p2Index]);ctx.moveTo(p1Projected[0], p1Projected[1]);ctx.lineTo(p2Projected[0], p2Projected[1]);}ctx.stroke();
}// 平移变换
function translate(dx, dy, dz) {for (let i = 0; i < cube.length; i++) {cube[i][0] += dx;cube[i][1] += dy;cube[i][2] += dz;}drawCube();
}// 缩放变换
function scaleTransform(sx, sy, sz) {for (let i = 0; i < cube.length; i++) {cube[i][0] *= sx;cube[i][1] *= sy;cube[i][2] *= sz;}drawCube();
}// 旋转矩阵计算
function rotateX(angle) {const cos = Math.cos(angle);const sin = Math.sin(angle);for (let i = 0; i < cube.length; i++) {const y = cube[i][1];const z = cube[i][2];cube[i][1] = y * cos - z * sin;cube[i][2] = y * sin + z * cos;}drawCube();
}function rotateY(angle) {const cos = Math.cos(angle);const sin = Math.sin(angle);for (let i = 0; i < cube.length; i++) {const x = cube[i][0];const z = cube[i][2];cube[i][0] = x * cos + z * sin;cube[i][2] = -x * sin + z * cos;}drawCube();
}function rotateZ(angle) {const cos = Math.cos(angle);const sin = Math.sin(angle);for (let i = 0; i < cube.length; i++) {const x = cube[i][0];const y = cube[i][1];cube[i][0] = x * cos - y * sin;cube[i][1] = x * sin + y * cos;}drawCube();
}// 重置立方体到初始状态
function resetCube() {cube = JSON.parse(JSON.stringify(originalCube));drawCube();
}// 添加按钮事件监听
document.getElementById('rotateX').addEventListener('click', () => rotateX(Math.PI / 18));
document.getElementById('rotateY').addEventListener('click', () => rotateY(Math.PI / 18));
document.getElementById('rotateZ').addEventListener('click', () => rotateZ(Math.PI / 18));
document.getElementById('translate').addEventListener('click', () => translate(0.1, 0, 0));
document.getElementById('scale').addEventListener('click', () => scaleTransform(1.1, 1.1, 1.1));
document.getElementById('reset').addEventListener('click', resetCube);// 初始化 - 绘制一个旋转了的立方体以便更好地观察
rotateX(Math.PI / 6);
rotateY(Math.PI / 4);
scaleTransform(1.5, 1.5, 1.5);
drawCube();
(2)前端HTML页面
<!DOCTYPE html>
<html>
<head><title>三维几何变换与透视投影</title><style>body {display: flex;flex-direction: column;align-items: center;background-color: #f5f5f5;font-family: Arial, sans-serif;padding: 20px;margin: 0;min-height: 100vh;}h1 {color: #2c3e50;margin-bottom: 30px;text-align: center;}canvas {border: 2px solid #34495e;border-radius: 8px;background-color: white;box-shadow: 0 4px 8px rgba(0,0,0,0.1);margin-bottom: 20px;}.controls {display: flex;gap: 10px;flex-wrap: wrap;justify-content: center;max-width: 600px;padding: 15px;background-color: white;border-radius: 8px;box-shadow: 0 2px 4px rgba(0,0,0,0.05);}button {padding: 12px 24px;border: none;border-radius: 5px;background-color: #2ecc71;color: white;cursor: pointer;transition: all 0.3s ease;font-size: 14px;font-weight: bold;text-transform: uppercase;letter-spacing: 1px;}button:hover {background-color: #27ae60;transform: translateY(-2px);box-shadow: 0 2px 4px rgba(0,0,0,0.1);}button#reset {background-color: #e74c3c;}button#reset:hover {background-color: #c0392b;}.container {max-width: 800px;width: 100%;margin: 0 auto;padding: 20px;}</style>
</head>
<body><div class="container"><h1>三维几何变换演示</h1><canvas id="canvas" width="600" height="400"></canvas><div class="controls"><button id="rotateX">绕X轴旋转</button><button id="rotateY">绕Y轴旋转</button><button id="rotateZ">绕Z轴旋转</button><button id="translate">平移</button><button id="scale">缩放</button><button id="reset">重置</button></div></div><script src="3dTransform.js"></script>
</body>
</html>
四、运行结果
五、项目简介
# 3D几何变换演示
## 项目概述
这是一个基于Canvas的3D几何变换演示项目,展示了基本的3D变换操作:旋转、平移和缩放。项目使用JavaScript实现,通过透视投影将3D立方体渲染到2D画布上。
## 主要功能
- **旋转**:支持绕X轴、Y轴、Z轴旋转
- **平移**:沿X轴方向移动立方体
- **缩放**:均匀缩放立方体大小
- **重置**:恢复立方体到初始状态
## 代码结构
- `cube`数组定义了立方体的8个顶点坐标
- `edges`数组定义了连接顶点的12条边
- 主要变换函数:
- `rotateX/Y/Z()` - 旋转
- `translate()` - 平移
- `scaleTransform()` - 缩放
- `resetCube()` - 重置
## 使用方法
1. 确保HTML文件中包含对应按钮
2. 点击按钮触发相应变换:
- 旋转X/Y/Z - 绕对应轴旋转
- 平移 - 沿X轴移动
- 缩放 - 均匀放大
- 重置 - 恢复初始状态
3. 初始状态立方体会自动旋转一定角度以便观察
## 技术要点
- 使用透视投影将3D坐标转换为2D屏幕坐标
- 实时重绘立方体实现动画效果
- 保留原始顶点数据用于重置功能