web-dev-qa-db-ja.com

THREE.jsはUV座標を生成します

THREE.js OBJローダーを使用してシーンにモデルをインポートする作業をしています。

MeshNormalMaterialを割り当てると、ジオメトリがうまく表示されるため、ジオメトリを正常にインポートできることを知っています。ただし、UV座標を必要とするものを使用すると、エラーが発生します。

[.WebGLRenderingContext]GL ERROR :GL_INVALID_OPERATION : glDrawElements: attempt to access out of range vertices in attribute 1 

これは、ロードされたOBJにUV座標がないためです。しかし、必要なテクスチャ座標を生成する方法があるかどうか疑問に思っていました。私が試してみました

material.needsUpdate = true;
geometry.uvsNeedUpdate = true;
geometry.buffersNeedUpdate = true;

...しかし役に立たない。

Three.jsを使用してUVテクスチャを自動的に生成する方法はありますか、それとも自分で座標を割り当てる必要がありますか?

28
Cabbibo

私の知る限り、UVを自動的に計算する方法はありません。

自分で計算する必要があります。プレーンのUVを計算するのは非常に簡単です。このサイトでは、次の方法について説明します。 テクスチャ座標の計算

複雑な形状の場合、どうすればいいかわかりません。たぶん、あなたは平らな表面を検出することができました。

[〜#〜] edit [〜#〜]

平面のサンプルコード(x, y, z) どこ z = 0

geometry.computeBoundingBox();

var max = geometry.boundingBox.max,
    min = geometry.boundingBox.min;
var offset = new THREE.Vector2(0 - min.x, 0 - min.y);
var range = new THREE.Vector2(max.x - min.x, max.y - min.y);
var faces = geometry.faces;

geometry.faceVertexUvs[0] = [];

for (var i = 0; i < faces.length ; i++) {

    var v1 = geometry.vertices[faces[i].a], 
        v2 = geometry.vertices[faces[i].b], 
        v3 = geometry.vertices[faces[i].c];

    geometry.faceVertexUvs[0].Push([
        new THREE.Vector2((v1.x + offset.x)/range.x ,(v1.y + offset.y)/range.y),
        new THREE.Vector2((v2.x + offset.x)/range.x ,(v2.y + offset.y)/range.y),
        new THREE.Vector2((v3.x + offset.x)/range.x ,(v3.y + offset.y)/range.y)
    ]);
}
geometry.uvsNeedUpdate = true;
41
Sayris

ここでの他の回答は非常に役立ちましたが、ほとんどが平らな表面を持つ形状のすべての側面に繰り返しパターンテクスチャを適用するという要件に完全には適合しませんでした。問題は、xおよびy成分のみをuおよびvとして使用すると、垂直表面に奇妙なストレッチテクスチャが生じることです。

以下の私のソリューションでは、サーフェス法線を使用して、uとvにマッピングする2つのコンポーネント(x、y、z)を選択します。まだかなり粗雑ですが、非常にうまく機能します。

function assignUVs(geometry) {

    geometry.faceVertexUvs[0] = [];

    geometry.faces.forEach(function(face) {

        var components = ['x', 'y', 'z'].sort(function(a, b) {
            return Math.abs(face.normal[a]) > Math.abs(face.normal[b]);
        });

        var v1 = geometry.vertices[face.a];
        var v2 = geometry.vertices[face.b];
        var v3 = geometry.vertices[face.c];

        geometry.faceVertexUvs[0].Push([
            new THREE.Vector2(v1[components[0]], v1[components[1]]),
            new THREE.Vector2(v2[components[0]], v2[components[1]]),
            new THREE.Vector2(v3[components[0]], v3[components[1]])
        ]);

    });

    geometry.uvsNeedUpdate = true;
}

この関数は、UVをオブジェクトのサイズに正規化しません。これは、同じシーン内の異なるサイズのオブジェクトに同じテクスチャを適用する場合に効果的です。ただし、ワールド座標系のサイズに応じて、おそらくテクスチャを拡大縮小して繰り返す必要があります。

texture.repeat.set(0.1, 0.1);
texture.wrapS = texture.wrapT = THREE.MirroredRepeatWrapping;
14
Tamlyn

ここでの答えは素晴らしく、私を大いに助けてくれました。 1つだけ:頂点を更新する場合は、UVを再割り当てせずに、設定します(スコープは私のジオメトリです)。

scope.updateUVs = (copy=true) => {

    scope.computeBoundingBox();

    var max     = scope.boundingBox.max;
    var min     = scope.boundingBox.min;

    var offset  = new THREE.Vector2(0 - min.x, 0 - min.y);
    var range   = new THREE.Vector2(max.x - min.x, max.y - min.y);

    if (!copy) {
        scope.faceVertexUvs[0] = [];
    }
    var faces = scope.faces;

    for (i = 0; i < scope.faces.length ; i++) {

      var v1 = scope.vertices[faces[i].a];
      var v2 = scope.vertices[faces[i].b];
      var v3 = scope.vertices[faces[i].c];

      var uv0 = new THREE.Vector2( ( v1.x + offset.x ) / range.x , ( v1.y + offset.y ) / range.y );
      var uv1 = new THREE.Vector2( ( v2.x + offset.x ) / range.x , ( v2.y + offset.y ) / range.y );
      var uv2 = new THREE.Vector2( ( v3.x + offset.x ) / range.x , ( v3.y + offset.y ) / range.y );

      if (copy) {
          var uvs =scope.faceVertexUvs[0][i];
          uvs[0].copy(uv0);
          uvs[1].copy(uv1);
          uvs[2].copy(uv2);
      } else {
          scope.faceVertexUvs[0].Push([uv0, uv1, uv2]);
      }
    }

    scope.uvsNeedUpdate = true;

}
4
Jörg Viola

これは、球面マッピング(ヨー、ピッチ座標)で機能する一般的なバージョンです。例を参照してください here 、(loadSuzanneを参照)関数):

function assignUVs(geometry) {

    geometry.faceVertexUvs[0] = [];

    geometry.faces.forEach(function(face) {

        var uvs = [];
        var ids = [ 'a', 'b', 'c'];
        for( var i = 0; i < ids.length; i++ ) {
            var vertex = geometry.vertices[ face[ ids[ i ] ] ].clone();

            var n = vertex.normalize();
            var yaw = .5 - Math.atan( n.z, - n.x ) / ( 2.0 * Math.PI );
            var pitch = .5 - Math.asin( n.y ) / Math.PI;

            var u = yaw,
                v = pitch;
            uvs.Push( new THREE.Vector2( u, v ) );
        }
        geometry.faceVertexUvs[ 0 ].Push( uvs );
    });

    geometry.uvsNeedUpdate = true;
}

Box UVマッピングは、あらゆる種類のthree.jsコンフィギュレーターでおそらく最も有用なものです。- https://jsfiddle.net/mmalex/pcjbysn1/

このソリューションは、インデックス付きおよびインデックスなしの両方のバッファジオメトリで、面ごとに機能します。

three.js UV mapping box

使用例:

//build some mesh
var bufferGeometry = new THREE.BufferGeometry().fromGeometry(new THREE.DodecahedronGeometry(2.5, 0));
let material = new THREE.MeshPhongMaterial({
    color: 0x10f0f0,
    map: new THREE.TextureLoader().load('http://mbnsay.com/rayys/images/1K_UV_checker.jpg')
});

//find out the dimensions, to let texture size 100% fit without stretching
bufferGeometry.computeBoundingBox();
let bboxSize = bufferGeometry.boundingBox.getSize();
let uvMapSize = Math.min(bboxSize.x, bboxSize.y, bboxSize.z);

//calculate UV coordinates, if uv attribute is not present, it will be added
applyBoxUV(bufferGeometry, new THREE.Matrix4().getInverse(cube.matrix), uvMapSize);

//let three.js know
bufferGeometry.attributes.uv.needsUpdate = true;

この例は、次のapplyBoxUVの実装に基づいています

function _applyBoxUV(geom, transformMatrix, bbox, bbox_max_size) {

    let coords = [];
    coords.length = 2 * geom.attributes.position.array.length / 3;

    // geom.removeAttribute('uv');
    if (geom.attributes.uv === undefined) {
        geom.addAttribute('uv', new THREE.Float32BufferAttribute(coords, 2));
    }

    //maps 3 verts of 1 face on the better side of the cube
    //side of the cube can be XY, XZ or YZ
    let makeUVs = function(v0, v1, v2) {

        //pre-rotate the model so that cube sides match world axis
        v0.applyMatrix4(transformMatrix);
        v1.applyMatrix4(transformMatrix);
        v2.applyMatrix4(transformMatrix);

        //get normal of the face, to know into which cube side it maps better
        let n = new THREE.Vector3();
        n.crossVectors(v1.clone().sub(v0), v1.clone().sub(v2)).normalize();

        n.x = Math.abs(n.x);
        n.y = Math.abs(n.y);
        n.z = Math.abs(n.z);

        let uv0 = new THREE.Vector2();
        let uv1 = new THREE.Vector2();
        let uv2 = new THREE.Vector2();
        // xz mapping
        if (n.y > n.x && n.y > n.z) {
            uv0.x = (v0.x - bbox.min.x) / bbox_max_size;
            uv0.y = (bbox.max.z - v0.z) / bbox_max_size;

            uv1.x = (v1.x - bbox.min.x) / bbox_max_size;
            uv1.y = (bbox.max.z - v1.z) / bbox_max_size;

            uv2.x = (v2.x - bbox.min.x) / bbox_max_size;
            uv2.y = (bbox.max.z - v2.z) / bbox_max_size;
        } else
        if (n.x > n.y && n.x > n.z) {
            uv0.x = (v0.z - bbox.min.z) / bbox_max_size;
            uv0.y = (v0.y - bbox.min.y) / bbox_max_size;

            uv1.x = (v1.z - bbox.min.z) / bbox_max_size;
            uv1.y = (v1.y - bbox.min.y) / bbox_max_size;

            uv2.x = (v2.z - bbox.min.z) / bbox_max_size;
            uv2.y = (v2.y - bbox.min.y) / bbox_max_size;
        } else
        if (n.z > n.y && n.z > n.x) {
            uv0.x = (v0.x - bbox.min.x) / bbox_max_size;
            uv0.y = (v0.y - bbox.min.y) / bbox_max_size;

            uv1.x = (v1.x - bbox.min.x) / bbox_max_size;
            uv1.y = (v1.y - bbox.min.y) / bbox_max_size;

            uv2.x = (v2.x - bbox.min.x) / bbox_max_size;
            uv2.y = (v2.y - bbox.min.y) / bbox_max_size;
        }

        return {
            uv0: uv0,
            uv1: uv1,
            uv2: uv2
        };
    };

    if (geom.index) { // is it indexed buffer geometry?
        for (let vi = 0; vi < geom.index.array.length; vi += 3) {
            let idx0 = geom.index.array[vi];
            let idx1 = geom.index.array[vi + 1];
            let idx2 = geom.index.array[vi + 2];

            let vx0 = geom.attributes.position.array[3 * idx0];
            let vy0 = geom.attributes.position.array[3 * idx0 + 1];
            let vz0 = geom.attributes.position.array[3 * idx0 + 2];

            let vx1 = geom.attributes.position.array[3 * idx1];
            let vy1 = geom.attributes.position.array[3 * idx1 + 1];
            let vz1 = geom.attributes.position.array[3 * idx1 + 2];

            let vx2 = geom.attributes.position.array[3 * idx2];
            let vy2 = geom.attributes.position.array[3 * idx2 + 1];
            let vz2 = geom.attributes.position.array[3 * idx2 + 2];

            let v0 = new THREE.Vector3(vx0, vy0, vz0);
            let v1 = new THREE.Vector3(vx1, vy1, vz1);
            let v2 = new THREE.Vector3(vx2, vy2, vz2);

            let uvs = makeUVs(v0, v1, v2, coords);

            coords[2 * idx0] = uvs.uv0.x;
            coords[2 * idx0 + 1] = uvs.uv0.y;

            coords[2 * idx1] = uvs.uv1.x;
            coords[2 * idx1 + 1] = uvs.uv1.y;

            coords[2 * idx2] = uvs.uv2.x;
            coords[2 * idx2 + 1] = uvs.uv2.y;
        }
    } else {
        for (let vi = 0; vi < geom.attributes.position.array.length; vi += 9) {
            let vx0 = geom.attributes.position.array[vi];
            let vy0 = geom.attributes.position.array[vi + 1];
            let vz0 = geom.attributes.position.array[vi + 2];

            let vx1 = geom.attributes.position.array[vi + 3];
            let vy1 = geom.attributes.position.array[vi + 4];
            let vz1 = geom.attributes.position.array[vi + 5];

            let vx2 = geom.attributes.position.array[vi + 6];
            let vy2 = geom.attributes.position.array[vi + 7];
            let vz2 = geom.attributes.position.array[vi + 8];

            let v0 = new THREE.Vector3(vx0, vy0, vz0);
            let v1 = new THREE.Vector3(vx1, vy1, vz1);
            let v2 = new THREE.Vector3(vx2, vy2, vz2);

            let uvs = makeUVs(v0, v1, v2, coords);

            let idx0 = vi / 3;
            let idx1 = idx0 + 1;
            let idx2 = idx0 + 2;

            coords[2 * idx0] = uvs.uv0.x;
            coords[2 * idx0 + 1] = uvs.uv0.y;

            coords[2 * idx1] = uvs.uv1.x;
            coords[2 * idx1 + 1] = uvs.uv1.y;

            coords[2 * idx2] = uvs.uv2.x;
            coords[2 * idx2 + 1] = uvs.uv2.y;
        }
    }

    geom.attributes.uv.array = new Float32Array(coords);
}

function applyBoxUV(bufferGeometry, transformMatrix, boxSize) {

    if (transformMatrix === undefined) {
        transformMatrix = new THREE.Matrix4();
    }

    if (boxSize === undefined) {
        let geom = bufferGeometry;
        geom.computeBoundingBox();
        let bbox = geom.boundingBox;

        let bbox_size_x = bbox.max.x - bbox.min.x;
        let bbox_size_z = bbox.max.z - bbox.min.z;
        let bbox_size_y = bbox.max.y - bbox.min.y;

        boxSize = Math.max(bbox_size_x, bbox_size_y, bbox_size_z);
    }

    let uvBbox = new THREE.Box3(new THREE.Vector3(-boxSize / 2, -boxSize / 2, -boxSize / 2), new THREE.Vector3(boxSize / 2, boxSize / 2, boxSize / 2));

    _applyBoxUV(bufferGeometry, transformMatrix, uvBbox, boxSize);

}
1