gpt4 book ai didi

javascript - 三.js/jsCAD : how to smooth a mesh normals for an outline effect?

转载 作者:行者123 更新时间:2023-12-04 07:24:21 25 4
gpt4 key购买 nike

我正在尝试使用以下方法在某些 3D 网格上创建轮廓效果:

  • 绘制原始初始着色网格
  • 平滑网格法线
  • 按照法线向外插入顶点
  • 仅使用纯色轮廓绘制背面

  • 除了平滑部分,我一切都在工作。需要平滑网格以创建一个漂亮的连续外部轮廓(否则 Angular 会出现不规则)。
    I've allready asked how to smooth a mesh normals .平滑过程如下:
      let geometry = new THREE.BoxGeometry();
    geometry.deleteAttribute('normal');
    geometry.deleteAttribute('uv');
    geometry = THREE.BufferGeometryUtils.mergeVertices(geometry);
    geometry.computeVertexNormals();
    尽管这对于简单定义良好的几何图形很有效,但它会在更复杂的模型上创建工件。我相信这是由于丢弃了原始法线。 computeVertexNormals()然后从里到外解释一些面孔。
    知道如何解决这个问题吗?
    在平滑法线之前
    before smoothing normals
    平滑法线后
    after smoothing normals

    旁注:
    1/我无法修改原始网格拓扑,因为它们是在运行时使用 jscad 生成的。
    2/我试图写一个算法来解决这个问题,但没有取得很大的成功。我的推理如下:
    让我们假设 computeVertexNormals()根据组成它的顶点的顺序将三 Angular 形解释为面朝上或面朝下。我将找到顶点法线反转的三 Angular 形,并交换两个三 Angular 形顶点来翻转它。如果 computeVertexNormals() 之后法线的点积,则顶点法线反转及之前 computeVertexNormals()是否定的。
    这是代码:
    const fixNormals: (bufferGeometry: THREE.BufferGeometry) => THREE.BufferGeometry
    = (bufferGeometry) => {
    // this function is only made for non indexed buffer geometry
    if (bufferGeometry.index)
    return bufferGeometry;
    const positionAttribute = bufferGeometry.getAttribute('position');
    if (positionAttribute == undefined)
    return bufferGeometry;
    let oldNormalAttribute = bufferGeometry.getAttribute('normal').clone();
    if (oldNormalAttribute === undefined)
    return bufferGeometry;

    bufferGeometry.deleteAttribute('normal');
    bufferGeometry.deleteAttribute('uv');
    bufferGeometry.computeVertexNormals();

    let normalAttribute = bufferGeometry.getAttribute('normal');
    if (normalAttribute === undefined) {
    console.error("bufferGeometry.computeVertexNormals() resulted in empty normals")
    return bufferGeometry;
    }

    const pA = new THREE.Vector3(),
    pB = new THREE.Vector3(),
    pC = new THREE.Vector3();
    const onA = new THREE.Vector3(),
    onB = new THREE.Vector3(),
    onC = new THREE.Vector3();
    const nA = new THREE.Vector3(),
    nB = new THREE.Vector3(),
    nC = new THREE.Vector3();

    for (let i = 0, il = positionAttribute.count; i < il; i += 3) {
    pA.fromBufferAttribute(positionAttribute, i + 0);
    pB.fromBufferAttribute(positionAttribute, i + 1);
    pC.fromBufferAttribute(positionAttribute, i + 2);

    onA.fromBufferAttribute(oldNormalAttribute, i + 0);
    onB.fromBufferAttribute(oldNormalAttribute, i + 1);
    onC.fromBufferAttribute(oldNormalAttribute, i + 2);

    nA.fromBufferAttribute(normalAttribute, i + 0);
    nB.fromBufferAttribute(normalAttribute, i + 1);
    nC.fromBufferAttribute(normalAttribute, i + 2);

    // new normals for this triangle are inverted,
    // need to switch 2 vertices order to keep right face up
    if (onA.dot(nA) < 0 && onB.dot(nB) < 0 && onC.dot(nC) < 0) {
    positionAttribute.setXYZ(i + 0, pB.x, pB.y, pB.z)
    positionAttribute.setXYZ(i + 1, pA.x, pA.y, pA.z)
    }
    }

    bufferGeometry.deleteAttribute('normal');
    bufferGeometry.deleteAttribute('uv');
    bufferGeometry.computeVertexNormals();

    return bufferGeometry;
    }

    最佳答案

    THREE执行 computeVertexNormals通过使用面的缠绕顺序来确定法线。如果一个面是使用左手缠绕定义的,那么你会得到指向错误方向的法线。这也应该会影响照明,除非您不使用着色 Material 。
    也就是说,您还可以看到计算出的法线在人脸之间不同的情况。为了避免这种情况,您需要跟踪给定顶点位置的所有法线(不仅仅是单个顶点,因为该位置可以在缓冲区中的其他地方复制)。
    第 1 步:创建顶点贴图
    这里的想法是您想要创建一个实际顶点位置到您定义的一些属性的映射,这些属性将在以后使用。拥有索引和组会影响此处理,但最终您希望创建一个 Map 并分配其 key作为顶点值的序列化版本。例如,顶点值 x = 1.23, y = 1.45, z = 1.67可能成为关键1.23|1.45|1.67 .这很重要,因为您需要能够引用顶点位置,并关闭两个新的 Vector3 s 实际上会映射到两个不同的键。反正...
    您需要保存的数据是每个顶点的法线,以及最终平滑后的法线。因此,您将推送到每个键的值是一个对象:

    let vertMap = new Map()

    // while looping through your vertices...
    if( vertMap.has( serializedVertex ) ){
    vertMap.get( serializedVert ).array.push( vector3Normal );
    }
    else{
    vertMap.set( serializedVertex, {
    normals: [ vector3Normal ],
    final: new Vector3()
    } );
    }
    第 2 步:平滑法线
    现在您拥有每个顶点位置的所有法线,迭代 vertMap ,平均 normals数组并将最终值分配给 final每个条目的属性。
    vertMap.forEach( ( entry, key, map ) => {

    let unifiedNormal = entry.normals.reduce( ( acc, normal ) => {
    acc.add( normal );
    return acc;
    }, new Vector3() );

    unifiedNormal.x /= entry.normals.length;
    unifiedNormal.y /= entry.normals.length;
    unifiedNormal.z /= entry.normals.length;
    unifiedNormal.normalize();

    entry.final.copy( unifiedNormal );

    } );
    第 3 步:重新分配法线
    现在循环返回您的顶点( position 属性)。由于顶点和法线是成对的,您可以使用当前顶点的索引来访问其关联法线的索引。通过再次序列化当前顶点并使用该值来引用当前顶点来引用 vertMap .分配 final 的值属性到关联的法线。
    let pos = geometry.attributes.position.array;
    let nor = geometry.attributes.normal.array;

    for( let i = 0, l = pos.length; i < l; i += 3 ){
    //serializedVertex = however you chose to do it during vertex mapping
    let newNor = vertMap.get( serializedVertex ).final;
    nor[ i ] = newNor.x;
    nor[ i + 1 ] = newNor.y;
    nor[ i + 2 ] = newNor.z;
    }
    geometry.attributes.normal.needsUpdate = true;
    警告
    可能有更优雅或 API 驱动的方法来执行我上面描述的操作。我提供的代码并不意味着高效或完整,而只是让您了解如何解决问题。

    关于javascript - 三.js/jsCAD : how to smooth a mesh normals for an outline effect?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68299142/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com