Flipping Face Normal ThreeJS 但不在地板上
Flipping Face Normal ThreeJS but not on floor
我尝试根据平面图建造一个房屋发电机。生成网格效果很好,但现在我需要翻转某些面上的法线。
buildRoomMeshFromPoints(planeScalar, heightScalar){
var pointsAsVector2 = []
this.points.map(e => {
pointsAsVector2.push(new THREE.Vector2(e.x * planeScalar, e.y * planeScalar))
})
var shape = new THREE.Shape();
shape.moveTo(pointsAsVector2[0].x, pointsAsVector2[0].y)
pointsAsVector2.shift()
pointsAsVector2.forEach(e => shape.lineTo(e.x, e.y))
const extrusionSettings = {
steps: 2,
depth: heightScalar,
bevelEnabled: false
};
var roomGeometry = new THREE.ExtrudeGeometry( shape, extrusionSettings );
var materialFront = new THREE.MeshBasicMaterial( { color: 0xffff00 } );
var materialSide = new THREE.MeshBasicMaterial( { color: 0xff8800 } );
var materialArray = [ materialFront, materialSide ];
var roomMaterial = new THREE.MeshFaceMaterial(materialArray);
var room = new THREE.Mesh(roomGeometry, roomMaterial);
room.position.set(0,0,0);
room.rotation.set(THREE.MathUtils.degToRad(-90),0,0)
return room;
}
这是根据二维点集合生成房屋的代码。为了让墙壁看透,我想改变所有墙壁和屋顶的法线。
我的方法是将每个面法线角度与向上矢量 (THREE.Vector3(0,1,0)) 进行比较,如果角度大于 0.0xx,则翻转它。我根本不知道如何翻转它们:)
感谢您的帮助!
你好帕斯卡
用最简单的术语来说,“翻转”或找到法线(或任何)向量的负数是对其每个分量取反的问题。因此,如果您的法向量 n
是一个 THREE.Vector3
实例,那么它的负数是 n.multiplyScalar(-1)
,或者如果它在 [ x, y, z ]
形式的数组中,那么它的负数是 [ -1 * x, -1 * y, -1 * z ]
.
不过,翻转法线向量并不能完成您想要完成的所有工作。 Three.js(以及许多其他引擎和渲染器)中的法线与正在渲染的三角形的 边 的概念是分开的和不同的。因此,如果您 仅 翻转矢量,Three.js 将继续渲染构成网格外部的三角形正面;不过,这些面孔会显得更暗,因为它们的反射方向完全错误。
对于每个三角形,你需要两者 (a) 翻转其顶点的法线;和 (b) 渲染该三角形的背面 或 反转三角形的正面。
要渲染三角形的背面,您可以设置.side
property of your material to THREE.BackSide
。 (我没有测试过这个,它可能有其他影响;除其他外,您可能会遇到代码库的其他部分,这些部分必须专门编写,并着眼于渲染背面的事实.)
一个更稳健的解决方案是让三角形本身面向另一个方向。
ExtrudeGeometry
是 BufferGeometry
的工厂,顶点位置存储在生成几何体的 .attributes.position.array
属性 中的平面数组中。您可以将数组中的每第 3-5 个元素与每第 6-9 个元素交换以反转三角形的缠绕顺序,从而改变 Three.js 认为是正面的一侧。因此,定义为 (0, 0, 0), (1, 0, 1), (1, 1, 1) 并在数组中表示为 [ 0, 0, 0, 1, 0, 1, 1, 1, 1 ]
的三角形变为 (0, 0, 0), ( 1, 1, 1), (1, 0, 1) 和 [ 0, 0, 0, 1, 1, 1, 1, 0, 1 ]
。 (换句话说,ABC 变成 ACB。)
要在代码中完成此操作,需要类似以下内容。
/**
* @param { import("THREE").BufferGeometry } geom
* @return { undefined }
*/
flipSides = (geom) => {
const positions = geom.getAttribute("position");
const normals = geom.getAttribute("normal");
const newNormals = Array.from(normals.array);
for (let attrName of ["position", "normal", "uv"]) {
// for (let i = 0; i < positions.count; i += 3) {
// ExtrudeGeometry generates a non-indexed BufferGeometry. To flip
// the faces, we must reverse the winding order, i.e., for each triangle
// ABC, we must change it to ACB. We must do this for the position,
// normal, and uv buffers.
const attr = geom.getAttribute(attrName);
let newArr = Array.from(attr.array)
const sz = attr.itemSize;
for (let i = 0; i < attr.count; i++) {
const offset = sz * 3 * i;
// i is the index of the first of three vertices of a triangle.
// Sample the buffer for the second and third vertices, which
// we'll swap.
const tempB = newArr.slice(
offset + sz,
offset + 2 * sz
);
const tempC = newArr.slice(
offset + 2 * sz,
offset + 3 * sz
);
newArr.splice(offset + sz, sz, ...tempC);
newArr.splice(offset + 2 * sz, sz, ...tempB);
}
// If we're working on the normals buffer, we also need to reverse
// the normals. Since reversing a vector simply entails a
// scalar-vector multiplication by -1, and since the array is
// flat, we can do this with one map() operation.
if (attrName == "normal") {
newArr = newArr.map((e) => e * -1);
}
// Replace the position buffer with our new array
geom.setAttribute(
attrName,
new THREE.BufferAttribute(
Float32Array.from(newArr),
sz
));
attr.needsUpdate = true;
}
我已经在 CodePen 上发布了这种方法的 demonstration。
我尝试根据平面图建造一个房屋发电机。生成网格效果很好,但现在我需要翻转某些面上的法线。
buildRoomMeshFromPoints(planeScalar, heightScalar){
var pointsAsVector2 = []
this.points.map(e => {
pointsAsVector2.push(new THREE.Vector2(e.x * planeScalar, e.y * planeScalar))
})
var shape = new THREE.Shape();
shape.moveTo(pointsAsVector2[0].x, pointsAsVector2[0].y)
pointsAsVector2.shift()
pointsAsVector2.forEach(e => shape.lineTo(e.x, e.y))
const extrusionSettings = {
steps: 2,
depth: heightScalar,
bevelEnabled: false
};
var roomGeometry = new THREE.ExtrudeGeometry( shape, extrusionSettings );
var materialFront = new THREE.MeshBasicMaterial( { color: 0xffff00 } );
var materialSide = new THREE.MeshBasicMaterial( { color: 0xff8800 } );
var materialArray = [ materialFront, materialSide ];
var roomMaterial = new THREE.MeshFaceMaterial(materialArray);
var room = new THREE.Mesh(roomGeometry, roomMaterial);
room.position.set(0,0,0);
room.rotation.set(THREE.MathUtils.degToRad(-90),0,0)
return room;
}
这是根据二维点集合生成房屋的代码。为了让墙壁看透,我想改变所有墙壁和屋顶的法线。
我的方法是将每个面法线角度与向上矢量 (THREE.Vector3(0,1,0)) 进行比较,如果角度大于 0.0xx,则翻转它。我根本不知道如何翻转它们:)
感谢您的帮助!
你好帕斯卡
用最简单的术语来说,“翻转”或找到法线(或任何)向量的负数是对其每个分量取反的问题。因此,如果您的法向量 n
是一个 THREE.Vector3
实例,那么它的负数是 n.multiplyScalar(-1)
,或者如果它在 [ x, y, z ]
形式的数组中,那么它的负数是 [ -1 * x, -1 * y, -1 * z ]
.
不过,翻转法线向量并不能完成您想要完成的所有工作。 Three.js(以及许多其他引擎和渲染器)中的法线与正在渲染的三角形的 边 的概念是分开的和不同的。因此,如果您 仅 翻转矢量,Three.js 将继续渲染构成网格外部的三角形正面;不过,这些面孔会显得更暗,因为它们的反射方向完全错误。
对于每个三角形,你需要两者 (a) 翻转其顶点的法线;和 (b) 渲染该三角形的背面 或 反转三角形的正面。
要渲染三角形的背面,您可以设置.side
property of your material to THREE.BackSide
。 (我没有测试过这个,它可能有其他影响;除其他外,您可能会遇到代码库的其他部分,这些部分必须专门编写,并着眼于渲染背面的事实.)
一个更稳健的解决方案是让三角形本身面向另一个方向。
ExtrudeGeometry
是 BufferGeometry
的工厂,顶点位置存储在生成几何体的 .attributes.position.array
属性 中的平面数组中。您可以将数组中的每第 3-5 个元素与每第 6-9 个元素交换以反转三角形的缠绕顺序,从而改变 Three.js 认为是正面的一侧。因此,定义为 (0, 0, 0), (1, 0, 1), (1, 1, 1) 并在数组中表示为 [ 0, 0, 0, 1, 0, 1, 1, 1, 1 ]
的三角形变为 (0, 0, 0), ( 1, 1, 1), (1, 0, 1) 和 [ 0, 0, 0, 1, 1, 1, 1, 0, 1 ]
。 (换句话说,ABC 变成 ACB。)
要在代码中完成此操作,需要类似以下内容。
/**
* @param { import("THREE").BufferGeometry } geom
* @return { undefined }
*/
flipSides = (geom) => {
const positions = geom.getAttribute("position");
const normals = geom.getAttribute("normal");
const newNormals = Array.from(normals.array);
for (let attrName of ["position", "normal", "uv"]) {
// for (let i = 0; i < positions.count; i += 3) {
// ExtrudeGeometry generates a non-indexed BufferGeometry. To flip
// the faces, we must reverse the winding order, i.e., for each triangle
// ABC, we must change it to ACB. We must do this for the position,
// normal, and uv buffers.
const attr = geom.getAttribute(attrName);
let newArr = Array.from(attr.array)
const sz = attr.itemSize;
for (let i = 0; i < attr.count; i++) {
const offset = sz * 3 * i;
// i is the index of the first of three vertices of a triangle.
// Sample the buffer for the second and third vertices, which
// we'll swap.
const tempB = newArr.slice(
offset + sz,
offset + 2 * sz
);
const tempC = newArr.slice(
offset + 2 * sz,
offset + 3 * sz
);
newArr.splice(offset + sz, sz, ...tempC);
newArr.splice(offset + 2 * sz, sz, ...tempB);
}
// If we're working on the normals buffer, we also need to reverse
// the normals. Since reversing a vector simply entails a
// scalar-vector multiplication by -1, and since the array is
// flat, we can do this with one map() operation.
if (attrName == "normal") {
newArr = newArr.map((e) => e * -1);
}
// Replace the position buffer with our new array
geom.setAttribute(
attrName,
new THREE.BufferAttribute(
Float32Array.from(newArr),
sz
));
attr.needsUpdate = true;
}
我已经在 CodePen 上发布了这种方法的 demonstration。