一、学习目的
了解几何变换的意义
掌握三维基本几何变换的算法
二、学习内容
在本次试验中,我们实现透视投影和三维几何变换。我们首先定义一个立方体作为我们要进行变换的三维物体。
三、具体代码
(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屏幕坐标
- 实时重绘立方体实现动画效果
- 保留原始顶点数据用于重置功能