Three.js:如何在负缩放后翻转法线

8

我已经克隆并翻转了一个对象,使用了负比例尺,导致了我的单面面孔被反转。我的问题是,如何翻转法线呢?

我不想使用material.side = THREE.DoubleSide,原因是:1)它不能正常工作(一些阴影从内部绘制),2)我想尽可能地保持性能,所以DoubleSide对我来说不是一个选择。

这就是我的对象被翻转的样子。

mesh.scale.x = - scale_width;

提前感谢!


我也看了你对这个问题的回答,我在想除了负缩放,我还有什么其他选择来“翻转”我的对象呢?也许你可以给我一个比负缩放更好的解决方案?我的任务是构建一个对称的对象:一个虚拟艺术画廊中的图像框架。所以我有一个框架边的几何形状,它们甚至可以在形状上有所变化。每个框架边都有两个45度的角,圆形或尖锐的,随意选择。所以我创建一个角并克隆它,然后我进行负缩放,以获得完全翻转。有没有什么建议可以在不使用负缩放的情况下处理它? - Markus Siebeneicher
5个回答

8
我建议不要使用负缩放,有很多原因可以解释,可以参考这个链接:Transforming vertex normals in three.js 你可以使用反演矩阵来应用于几何体,像下面这样:
geometry.scale( - 1, 1, 1 );

如链接中所解释的,这样做的一个后果是几何面将不再具有逆时针的绕序,而是顺时针的。

您可以手动遍历您的几何体并翻转每个面的绕序。如果您没有应用纹理并且没有使用UV,则这可能适用于您。如果要对几何体进行纹理处理,则还需要校正UV。

实际上,three.js 中的一个几何反转实用程序将是一个不错的补充。目前,该库不支持您想要做的操作。

three.js r.72


谢谢解释,这让我很有信心考虑实现这个功能 :) - Markus Siebeneicher

6

我把这个放在这里。我在某个地方找到了flipNormals并为BufferGeometry进行了翻译。

翻转法线,翻转UV,反向面绕组

适用于索引BufferGeometry的版本

function flipBufferGeometryNormalsIndexed(geometry) {
    const index = geometry.index.array
    for (let i = 0, il = index.length / 3; i < il; i++) {
        let x = index[i * 3]
        index[i * 3] = index[i * 3 + 2]
        index[i * 3 + 2] = x
    }
    geometry.index.needsUpdate = true
}

非索引BufferGeometry版本

export function flipBufferGeometryNormals(geometry) {
    const tempXYZ = [0, 0, 0];

    // flip normals
    for (let i = 0; i < geometry.attributes.normal.array.length / 9; i++) {
        // cache a coordinates
        tempXYZ[0] = geometry.attributes.normal.array[i * 9];
        tempXYZ[1] = geometry.attributes.normal.array[i * 9 + 1];
        tempXYZ[2] = geometry.attributes.normal.array[i * 9 + 2];

        // overwrite a with c
        geometry.attributes.normal.array[i * 9] =
            geometry.attributes.normal.array[i * 9 + 6];
        geometry.attributes.normal.array[i * 9 + 1] =
            geometry.attributes.normal.array[i * 9 + 7];
        geometry.attributes.normal.array[i * 9 + 2] =
            geometry.attributes.normal.array[i * 9 + 8];

        // overwrite c with stored a values
        geometry.attributes.normal.array[i * 9 + 6] = tempXYZ[0];
        geometry.attributes.normal.array[i * 9 + 7] = tempXYZ[1];
        geometry.attributes.normal.array[i * 9 + 8] = tempXYZ[2];
    }

    // change face winding order
    for (let i = 0; i < geometry.attributes.position.array.length / 9; i++) {
        // cache a coordinates
        tempXYZ[0] = geometry.attributes.position.array[i * 9];
        tempXYZ[1] = geometry.attributes.position.array[i * 9 + 1];
        tempXYZ[2] = geometry.attributes.position.array[i * 9 + 2];

        // overwrite a with c
        geometry.attributes.position.array[i * 9] =
            geometry.attributes.position.array[i * 9 + 6];
        geometry.attributes.position.array[i * 9 + 1] =
            geometry.attributes.position.array[i * 9 + 7];
        geometry.attributes.position.array[i * 9 + 2] =
            geometry.attributes.position.array[i * 9 + 8];

        // overwrite c with stored a values
        geometry.attributes.position.array[i * 9 + 6] = tempXYZ[0];
        geometry.attributes.position.array[i * 9 + 7] = tempXYZ[1];
        geometry.attributes.position.array[i * 9 + 8] = tempXYZ[2];
    }

    // flip UV coordinates
    for (let i = 0; i < geometry.attributes.uv.array.length / 6; i++) {
        // cache a coordinates
        tempXYZ[0] = geometry.attributes.uv.array[i * 6];
        tempXYZ[1] = geometry.attributes.uv.array[i * 6 + 1];

        // overwrite a with c
        geometry.attributes.uv.array[i * 6] =
            geometry.attributes.uv.array[i * 6 + 4];
        geometry.attributes.uv.array[i * 6 + 1] =
            geometry.attributes.uv.array[i * 6 + 5];

        // overwrite c with stored a values
        geometry.attributes.uv.array[i * 6 + 4] = tempXYZ[0];
        geometry.attributes.uv.array[i * 6 + 5] = tempXYZ[1];
    }

    geometry.attributes.normal.needsUpdate = true;
    geometry.attributes.position.needsUpdate = true;
    geometry.attributes.uv.needsUpdate = true;
}

对于旧的几何样式
export function flipNormals(geometry) {
    let temp = 0;
    let face;

    // flip every vertex normal in geometry by multiplying normal by -1
    for (let i = 0; i < geometry.faces.length; i++) {
        face = geometry.faces[i];
        face.normal.x = -1 * face.normal.x;
        face.normal.y = -1 * face.normal.y;
        face.normal.z = -1 * face.normal.z;
    }

    // change face winding order
    for (let i = 0; i < geometry.faces.length; i++) {
        const face = geometry.faces[i];
        temp = face.a;
        face.a = face.c;
        face.c = temp;
    }

    // flip UV coordinates
    const faceVertexUvs = geometry.faceVertexUvs[0];
    for (let i = 0; i < faceVertexUvs.length; i++) {
        temp = faceVertexUvs[i][0];
        faceVertexUvs[i][0] = faceVertexUvs[i][2];
        faceVertexUvs[i][2] = temp;
    }

    geometry.verticesNeedUpdate = true;
    geometry.normalsNeedUpdate = true;

    geometry.computeFaceNormals();
    geometry.computeVertexNormals();
    geometry.computeBoundingSphere();
}

你好,Pawel。旧的几何样式可以工作,但是新的BufferGeometry函数没有成功。格式可能改变了吗?你能否再次检查并提供一个可行的示例? - trusktr
嗯,也许格式改变了,而且文档也不是很好。这里有一个没有负比例的演示:https://codepen.io/trusktr/pen/ZEvVpra。现在这里是X轴为-1比例的相同演示:https://codepen.io/trusktr/pen/rNpoMJJ。最后,这里是尝试应用上述函数,但它完全破坏了立方体:https://codepen.io/trusktr/pen/gOoZwKp。 - trusktr
@trusktr 这个函数只适用于非索引几何体。我添加了另一个版本,适用于索引几何体,而且更简单。 - Pawel
太棒了,谢谢!这是带有索引函数工作的演示:https://codepen.io/trusktr/pen/ZErQZWg。这暗示了一个相关的问题:我们如何检测和区分使用索引与不使用索引的几何,以便调用正确的函数? - trusktr
1
  1. 太好了!
  2. if (geometry.index) {}
- Pawel

3

这个问题已经两年了,但是如果有人路过的话,请注意。以下是一种非破坏性的方式:

您可以进入“脏点/法线”模式,并手动翻转法线:

mesh.geometry.dynamic = true
mesh.geometry.__dirtyVertices = true;
mesh.geometry.__dirtyNormals = true;

mesh.flipSided = true;

//flip every vertex normal in mesh by multiplying normal by -1
for(var i = 0; i<mesh.geometry.faces.length; i++) {
    mesh.geometry.faces[i].normal.x = -1*mesh.geometry.faces[i].normal.x;
    mesh.geometry.faces[i].normal.y = -1*mesh.geometry.faces[i].normal.y;
    mesh.geometry.faces[i].normal.z = -1*mesh.geometry.faces[i].normal.z;
}

mesh.geometry.computeVertexNormals();
mesh.geometry.computeFaceNormals();

+1 @WestLangley,我建议您永远不要使用负缩放。


有趣。但请注意,.flipSided已不再是THREE.js的一部分。请参见此处u/WestLangley的答案:http://stackoverflow.com/questions/19673913/three-js-flipsided-property。 - steveOw
geometry.__dirtyVertices和geometry.__dirtyNormals也已经被弃用。请参阅https://github.com/mrdoob/three.js/wiki/Updates。 - Reimund

3

已经修复!!

three.js r89版本开始,当一个对象的缩放值为负数时object.scale.x = -1 ,法线也会翻转(参见:#12787支持反射矩阵。

(但是我必须升级到r91版本来解决我的法线问题。)


1

如果您有一个已索引的BufferGeometry,只需按以下方式重新排序索引:

    let temp;
    for ( let i = 0; i < geometry.index.array.length; i += 3 ) {
      // swap the first and third values
      temp = geometry.index.array[ i ];
      geometry.index.array[ i ] = geometry.index.array[ i + 2 ];
      geometry.index.array[ i + 2 ] = temp;
    }


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接