提问人:RalkB 提问时间:4/29/2023 最后编辑:RalkB 更新时间:5/2/2023 访问量:56
在 TypeScript 中为 3D 引擎计算三角形法线时出错
Error calculating triangle normals in TypeScript for a 3d engine
问:
我正在尝试使用TypeScript计算三角形的法线,但我得到了意想不到的结果。以下是所用顶点和三角形的详细信息:
let vertices: Vertex[] = [
{ x: 0, y: 0, z: 0 },
{ x: 1, y: 0, z: 0 },
{ x: 1, y: 1, z: 0 },
{ x: 0, y: 1, z: 0 },
{ x: 0, y: 0, z: 1 },
{ x: 1, y: 0, z: 1 },
{ x: 1, y: 1, z: 1 },
{ x: 0, y: 1, z: 1 },
];
const triangles: Triangle[] = [
{ v1: 0, v2: 1, v3: 2 }, { v1: 0, v2: 2, v3: 3 }, // Front face
{ v1: 4, v2: 5, v3: 6 }, { v1: 4, v2: 6, v3: 7 }, // Back face
{ v1: 0, v2: 1, v3: 5 }, { v1: 0, v2: 5, v3: 4 }, // Bottom face
{ v1: 2, v2: 3, v3: 7 }, { v1: 2, v2: 7, v3: 6 }, // Top face
{ v1: 0, v2: 3, v3: 7 }, { v1: 0, v2: 7, v3: 4 }, // Left face
{ v1: 1, v2: 2, v3: 6 }, { v1: 1, v2: 6, v3: 5 } // Right face
];
我还对顶点应用了一些旋转和平移,这些旋转和平移工作正常。但是,当我尝试使用该函数计算三角形的法线时,结果似乎不正确。calculateNormal()
以下是轮换和平移的实现:
const angle = 1;
const rotationX = rotationMatrixX(angle);
const rotationY = rotationMatrixY(angle);
vertices = vertices.map((vertex) => {
let [x, y, z, w] = multiplyMatrixVector(rotationX, [
vertex.x,
vertex.y,
vertex.z,
1,
]);
return {x, y, z};
})
vertices = vertices.map((vertex) => {
let [x, y, z, w] = multiplyMatrixVector(rotationY, [
vertex.x,
vertex.y,
vertex.z,
1,
]);
return {x, y, z};
})
const translatedVertices = transformVertices(vertices, translationMatrix);
const culledTriangles = [];
const normals = [];
for (let i = 0; i < triangles.length; i++) {
const { v1, v2, v3 } = triangles[i];
const p1 = translatedVertices[v1];
const p2 = translatedVertices[v2];
const p3 = translatedVertices[v3];
const normal = calculateNormal(p1, p2, p3);
if (normal.z > 0) {
culledTriangles.push(triangles[i]);
normals.push(normal);
}
}
// Transform and project the rotated vertices
const transformedVertices = transformVertices(translatedVertices, perspectiveProjectionMatrix());
const scaledVertices = scaleAndNormalizeVertices(transformedVertices);
// Draw the rotated cube
drawWireframe(ctx, scaledVertices, culledTriangles, normals);
以下是该函数的实现:calculateNormal()
function calculateNormal(a: Vertex, b: Vertex, c: Vertex): Vertex {
const line1: Vertex = {
x: b.x - a.x,
y: b.y - a.y,
z: b.z - a.z,
};
const line2: Vertex = {
x: c.x - a.x,
y: c.y - a.y,
z: c.z - a.z,
};
const normal: Vertex = {
x: line1.y * line2.z - line1.z * line2.y,
y: line1.z * line2.x - line1.x * line2.z,
z: line1.x * line2.y - line1.y * line2.x,
};
const length = Math.sqrt(normal.x ** 2 + normal.y ** 2 + normal.z ** 2);
normal.x /= length;
normal.y /= length;
normal.z /= length;
return normal;
}
下面是 drawWireframe 函数:
/**
* Draw the wireframe of a 3D object
* @param ctx - The 2D rendering context of the canvas.
* @param vertices - The array of vertices of the 3D object.
* @param triangles - The array of triangles of the 3D object.
*/
export function drawWireframe(ctx: CanvasRenderingContext2D, vertices: Vertex[], triangles: Triangle[], normals: Vertex[]) {
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, config.canvas.width, config.canvas.height);
ctx.strokeStyle = "#ffffff";
// Add a scale factor to normals to make them more visible
const normalScale = 50;
for (let i = 0; i < triangles.length; i++) {
const { v1, v2, v3 } = triangles[i];
const p1 = vertices[v1];
const p2 = vertices[v2];
const p3 = vertices[v3];
// Desenhar o triângulo
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.lineTo(p3.x, p3.y);
ctx.closePath();
ctx.stroke();
// Calculate the center point of the triangle
const centerX = (p1.x + p2.x + p3.x) / 3;
const centerY = (p1.y + p2.y + p3.y) / 3;
// Obtain the corresponding normal and scale
const normal = normals[i];
const endX = centerX + normal.x * normalScale;
const endY = centerY + normal.y * normalScale;
// draw the normal
ctx.strokeStyle = "#ff0000";
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.lineTo(endX, endY);
ctx.stroke();
ctx.strokeStyle = "#ffffff";
}
};
我已经验证了每个三角形的函数是否被正确调用,但生成的法线似乎不正确。calculateNormal()
下面是立方体的示例(图中的红线表示法线的方向):立方体
我将不胜感激任何关于我可能做错了什么以及如何解决它的建议或建议。
答:
0赞
RalkB
5/2/2023
#1
我通过更改三角形的顶点顺序并将 normal.x 乘以 -1 解决了这个问题。
顶点变化:
const triangles: Triangle[] = [
{ v1: 0, v2: 2, v3: 1 }, { v1: 0, v2: 3, v3: 2 },
{ v1: 4, v2: 5, v3: 6 }, { v1: 4, v2: 6, v3: 7 },
{ v1: 0, v2: 1, v3: 5 }, { v1: 0, v2: 5, v3: 4 },
{ v1: 2, v2: 3, v3: 7 }, { v1: 2, v2: 7, v3: 6 },
{ v1: 0, v2: 7, v3: 3 }, { v1: 0, v2: 4, v3: 7 },
{ v1: 1, v2: 2, v3: 6 }, { v1: 1, v2: 6, v3: 5 }
];
使用这种排序,所有三角形都指向外,使所有法线也指向。
乘以 -1:
function calculateNormal(a: Vertex, b: Vertex, c: Vertex): Vertex {
const line1: Vertex = {
x: b.x - a.x,
y: b.y - a.y,
z: b.z - a.z,
};
const line2: Vertex = {
x: c.x - a.x,
y: c.y - a.y,
z: c.z - a.z,
};
const normal: Vertex = {
x: line1.y * line2.z - line1.z * line2.y,
y: line1.z * line2.x - line1.x * line2.z,
z: line1.x * line2.y - line1.y * line2.x,
};
const length = Math.sqrt(normal.x ** 2 + normal.y ** 2 + normal.z ** 2);
normal.x /= length;
normal.y /= length;
normal.z /= length;
normal.x *= -1; // multiply by -1
return normal;
}
由于我没有得到的某种原因,当我将 x 乘以 -1 时,法线会不断平行且正确地绘制。删除该乘法似乎不会影响背面剔除,因为验证仅使用 z 参数。
评论