如何更新 A-Frame 中 GLTF 模型的修改顶点?

How to update modified vertices of GLTF model in A-Frame?

我有一个 GLTF 模型,其网格代表低聚水。在 A-Frame 中加载 GLTF 模型后,我试图通过修改 tick() 函数中每个顶点的 y 坐标来为水设置动画。

问题是修改后的顶点没有绘制在屏幕上,只绘制了初始修改。我错过了什么?

为了重现该问题,我制作了一个简单的 GLTF 模型,其中包含一个 4x4 网格中的 32 个三角形的单个网格:

{
    "accessors": [
        {
            "bufferView": 0,
            "byteOffset": 0,
            "componentType": 5123,
            "count": 96,
            "max": [
                24
            ],
            "min": [
                0
            ],
            "type": "SCALAR"
        },
        {
            "bufferView": 1,
            "byteOffset": 0,
            "componentType": 5126,
            "count": 25,
            "max": [
                1.0,
                0.0,
                1.0
            ],
            "min": [
                -1.0,
                0.0,
                -1.0
            ],
            "type": "VEC3"
        },
        {
            "bufferView": 2,
            "byteOffset": 0,
            "componentType": 5126,
            "count": 25,
            "max": [
                0.0,
                1.0,
                0.0
            ],
            "min": [
                0.0,
                1.0,
                0.0
            ],
            "type": "VEC3"
        }
    ],
    "asset": {
        "version": "2.0"
    },
    "bufferViews": [
        {
            "buffer": 0,
            "byteLength": 192,
            "byteOffset": 0,
            "target": 34963
        },
        {
            "buffer": 0,
            "byteLength": 300,
            "byteOffset": 192,
            "target": 34962
        },
        {
            "buffer": 0,
            "byteLength": 300,
            "byteOffset": 492,
            "target": 34962
        }
    ],
    "buffers": [
        {
            "byteLength": 792,
            "uri": "data:application/octet-stream;base64,AAABAAIAAAADAAEAAwAEAAEAAwAFAAQABQAGAAQABQAHAAYABwAIAAYABwAJAAgABgAIAAoABgAKAAsABAAGAAsABAALAAwAAQAEAAwAAQAMAA0AAgABAA0AAgANAA4ADgANAA8ADgAPABAAEAAPABEAEAARABIADwATABEADwAUABMADQAUAA8ADQAMABQADAAVABQADAALABUACwAWABUACwAKABYAFQAWABcAFQAXABgAFAAVABgAFAAYABMAAACAvwAAAAAAAIC/AAAAvwAAAAAAAAC/AAAAvwAAAAAAAIC/AACAvwAAAAAAAAC/AAAAvwAAAAAAAACAAACAvwAAAAAAAAAAAAAAvwAAAAAAAAA/AACAvwAAAAAAAAA/AAAAvwAAAAAAAIA/AACAvwAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAA/AAAAAAAAAAAAAACAAAAAAAAAAAAAAAC/AAAAAAAAAAAAAIC/AAAAPwAAAAAAAAC/AAAAPwAAAAAAAIC/AACAPwAAAAAAAAC/AACAPwAAAAAAAIC/AACAPwAAAAAAAAAAAAAAPwAAAAAAAACAAAAAPwAAAAAAAAA/AAAAPwAAAAAAAIA/AACAPwAAAAAAAIA/AACAPwAAAAAAAAA/AAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAA"
        }
    ],
    "materials": [
        {
            "name": "Water",
            "pbrMetallicRoughness": {
                "baseColorFactor": [
                    0.0,
                    0.46700000762939453,
                    0.7450000047683716,
                    1.0
                ],
                "metallicFactor": 0.0,
                "roughnessFactor": 1.0
            }
        }
    ],
    "meshes": [
        {
            "primitives": [
                {
                    "attributes": {
                        "NORMAL": 2,
                        "POSITION": 1
                    },
                    "indices": 0,
                    "material": 0,
                    "mode": 4
                }
            ]
        }
    ],
    "nodes": [
        {
            "mesh": 0,
            "name": "water"
        }
    ],
    "scene": 0,
    "scenes": [
        {
            "nodes": [
                0
            ]
        }
    ]
}

要在 A 帧中加载 GLTF 模式并设置动画,我使用以下 HTML + JavaScript:

<html>
    <head>
        <script src="https://aframe.io/releases/0.9.2/aframe.min.js"></script>
    </head>
    <body>
        <script>
            AFRAME.registerComponent('aniwater', {
                init: function() {
                    const data = this.data;

                    this.setupWater();
                    this.el.addEventListener('object3dset', this.setupWater.bind(this));
                },
                tick: function() {
                    const mesh = this.el.getObject3D('mesh');

                    if (!mesh) {
                        return;
                    }

                    mesh.traverse(function (node) {
                        // water is child node
                        if (node.name === "water") {
                            var vertexCount = node.geometry.attributes.position.count;
                            var vertices = node.geometry.attributes.position.array;

                            // update y of vertex
                            for (var i = 0; i < vertexCount; i++) {
                                var vy = vertices[i * 3 + 1];

                                // get the data associated to it
                                var vprops = this.waves[i];

                                // update the position of the vertex
                                vy = vprops.y + Math.sin(vprops.ang) * vprops.amp;

                                // increment the angle for the next frame
                                vprops.ang += vprops.speed;

                                vertices[i * 3 + 1] = vy;
                            }

                            // compute normals
                            node.geometry.computeFaceNormals();
                            node.geometry.computeVertexNormals();

                            // this does not work?
                            node.geometry.verticesNeedUpdate=true;
                        }
                    });
                },
                setupWater: function() {
                    const mesh = this.el.getObject3D('mesh');

                    if (!mesh) {
                        return;
                    }

                    mesh.traverse(function (node) {
                        // water is child node
                        if (node.name === "water") {
                            // array with wave information
                            this.waves = [];

                            var vertexCount = node.geometry.attributes.position.count;
                            var vertices = node.geometry.attributes.position.array;

                            // create an array to store new data associated to each vertex
                            for (var i = 0; i < vertexCount; i++) {
                                // get y position of each vertex
                                var vy = vertices[i * 3 + 1];

                                // store some data associated to it
                                this.waves.push({
                                    y: vy,
                                    // random start angle
                                    ang: Math.random() * Math.PI * 2,
                                    // random amplitude
                                    amp: 0.5 + Math.random() * 0.15,
                                    // random speed
                                    speed:0.016 + Math.random() * 0.032
                                 });
                            }
                        }
                    });
                }
            });
        </script>

        <a-scene>
            <a-assets>
                <a-asset-item id="water_asset" src="water.gltf">
            </a-assets>
            <a-gltf-model id="water" src="#water_asset" position="0 0 -4" aniwater></a-gltf-model>
            <a-sky color="#ECECEC"></a-sky>
        </a-scene>
    </body>
</html>

使用资产将 GLTF 模型加载到 a-gltf-model 中,并附加名为 aniwater 的自定义组件。

自定义组件基于 aframes-extra 存储库 (https://github.com/donmccurdy/aframe-extras/blob/master/src/misc/cube-env-map.js) 中的 cube-env-map.js 并定义了 3 个函数。

setupWater() 函数创建一个数组,其中包含有关每个顶点的信息,以便可以为水设置动画。这是目前基于此处找到的代码:https://tympanus.net/codrops/2016/04/26/the-aviator-animating-basic-3d-scene-threejs/

init() 函数在加载模型时将 setupWater() 函数附加到模型。这似乎工作正常。

顶点的实际修改是在函数 tick() 中完成的。对于每个顶点,更新 y 坐标并计算 y 的新值。在此之后,法线被重新计算并且 verticesNeedUpdate 标志被设置为 true 以指示更改。 这似乎不起作用。

我认为问题出在这样一个事实,即 GLTF 模型的顶点没有存储在 geometry.vertices 中,而是存储在 [=56 中=] 因为三角形是顶点的索引。这让我怀疑 verticesNeedUpdate 标志仅适用于 geometry.vertices,但我不确定。

位置数组中的顶点似乎确实更新了,因为我可以看到网格上的阴影变化是由更新的法线引起的。

如何让A-Frame在绘制水网格时使用更新后的顶点?

根据 docs, and this anwser:

当使用缓冲区几何体(并且模型是这样加载的)时,您需要在更改的属性对象上设置 needsUpdate 标志:

// Vertices change
var vertices = node.geometry.attributes.position.array;
// .... updates ...

// nofitication
node.geometry.attributes.position.needsUpdate = true

故障 here.