読者です 読者をやめる 読者になる 読者になる

デジタル・デザイン・ラボラトリーな日々

アラフィフプログラマーが数学と物理を基礎からやり直す

3Dを基礎から勉強する テクスチャマッピング

前回の「3Dを基礎から勉強する アニメーションの表示」から半年が経ってしまいました。

今回はテクスチャマッピングとなります。2Dでは「テクスチャマッピングを理解してみる」でやっているのですが、3Dだとどうなのか?ってことですね。

理解するには一番単純な立方体でテクスチャはサイコロ画像にしてみます。
3DモデリングツールであるBlenderで立方体にサイコロのテクスチャを付けたのをThree.js用にエクスポートしてました。エクスポートされたデータは、Three.jsのバージョン r69でformatVersion 3.1と今となっては少し古いですが、理解する上では問題ないのでこのままにします。

下記がエクスポートされたデータの頂点情報(vertices)と面位置情報(faces)と UV座標情報(uvs)です。

"vertices" : [
0.787166,-0.849721,-0.917893,
0.787166,-0.84972,1.08211,
-1.21283,-0.84972,1.08211,
-1.21283,-0.849721,-0.917893,
0.787167,1.15028,-0.917892,
0.787166,1.15028,1.08211,
-1.21283,1.15028,1.08211,
-1.21283,1.15028,-0.917893],

"faces"    : [
42,1,2,3,0,0,1,2,0,1,2,
42,7,6,5,0,3,4,5,3,4,5,
42,0,4,5,0,6,7,5,6,7,5,
42,1,5,6,0,0,5,8,0,5,4,
42,6,7,3,0,9,10,2,4,3,2,
42,0,3,7,0,6,11,12,6,2,3,
42,0,1,3,0,6,0,2,6,0,2,
42,4,7,5,0,7,3,5,7,3,5,
42,1,0,5,0,0,6,5,0,6,5,
42,2,1,6,0,13,0,8,1,0,4,
42,2,6,3,0,1,9,2,1,4,2,
42,4,0,7,0,7,6,12,7,6,3],

"uvs"      : [[
0.4999999701976776,0.3787218928337097,
0.7488495111465454,0.3787217438220978,
0.7488495707511902,0.6275712847709656,
0.0023009898141026497,0.6275716423988342,
0.0023009302094578743,0.37872204184532166,
0.2511504590511322,0.3787219524383545,
0.5000000596046448,0.6275714635848999,
0.25115060806274414,0.6275714635848999,
0.2511502206325531,0.12987245619297028,
0.997698962688446,0.3787217438220978,
0.997698962688446,0.6275712847709656,
0.5000002384185791,0.8764209747314453,
0.2511507272720337,0.8764211535453796,
0.49999985098838806,0.1298723667860031]]

f:id:Yaju3D:20141102013653j:plainf:id:Yaju3D:20141101234115j:plain
頂点数は8個、面数は12(6x2)個、UV座標数は14個あります。
下図はテスト用のテクスチャ画像です、サイコロの重複しない頂点数が14個となります。
f:id:Yaju3D:20150705180235p:plain

// UVデータ
this.hasUvs = false;
if (modelObject.uvs !== undefined) {
    this.hasUvs = true;
    this.modelData.uvs = new Array();
    for (i = 0; i < modelObject.uvs[0].length; i += 2) {
        var u = modelObject.uvs[0][i + 0] % 1.0;
        var v = modelObject.uvs[0][i + 1] % 1.0;
        u = (u < 0) ? 1 + u : u;
        v = (v < 0) ? v * -1 : 1 - v;

        this.modelData.uvs.push(new UV(u, v));
    }
}

v方向(縦)は上下が逆になっているため、1 - vとしています。
また、他データによってはマイナス値や1を超える値があったため、値が0~1になるように調整しています。

// UV情報
var uv0: UV = (this.hasUvs) ? this.modelData.uvs[this.modelData.faces[i + 5]] : new UV(0, 0);
var uv1: UV = (this.hasUvs) ? this.modelData.uvs[this.modelData.faces[i + 6]] : new UV(0, 0);
var uv2: UV = (this.hasUvs) ? this.modelData.uvs[this.modelData.faces[i + 7]] : new UV(0, 0);

JSON Model format 3
"faces"では、区切りが42(0x2A)になっており、bitに変換すると「00101010」です。
0: 0 = triangle (3 indices), 1 = quad (4 indices)
1: 0 = no face material, 1 = face material (1 index)
2: 0 = no face uvs, 1 = face uvs (1 index)
3: 0 = no face vertex uvs, 1 = face vertex uvs (3 indices or 4 indices)
4: 0 = no face normal, 1 = face normal (1 index)
5: 0 = no face vertex normals, 1 = face vertex normals (3 indices or 4 indices)
6: 0 = no face color, 1 = face color (1 index)
7: 0 = no face vertex colors, 1 = face vertex colors (3 indices or 4 indices)

0ビットは「0」なので三角形ポリゴンです。
区切りの先頭データはType(42=00101010)となり、例 42,v0,v1,v2,0,fv0,fv1,fv2,nv0,nv1,nv2 として11個単位でデータを区切ります。その中で5,6,7の索引値(fv0,fv1,fv2)が三角形ポリゴンとテクスチャを関連付ける値となります。

// テクスチャの描画
var vertex_list = [pt1.x, pt1.y, pt2.x, pt2.y, pt3.x, pt3.y];
var uv_list = [face.coords[0].u, face.coords[0].v, face.coords[1].u, face.coords[1].v, face.coords[2].u, face.coords[2].v];
this.drawTriangle(g, this.texture, vertex_list, uv_list);

テクスチャ描画の基本部分は2Dの時と同じです。参照:テクスチャマッピングを理解してみる

// 三角形テクスチャ描画
drawTriangle(g, img, vertex_list, uv_list) {
    var _Ax = vertex_list[2] - vertex_list[0];
    var _Ay = vertex_list[3] - vertex_list[1];
    var _Bx = vertex_list[4] - vertex_list[0];
    var _By = vertex_list[5] - vertex_list[1];

    var Ax = (uv_list[2] - uv_list[0]) * img.width;
    var Ay = (uv_list[3] - uv_list[1]) * img.height;
    var Bx = (uv_list[4] - uv_list[0]) * img.width;
    var By = (uv_list[5] - uv_list[1]) * img.height;

    //逆行列を求める
    var m = new M22();
    m._11 = Ax;
    m._12 = Ay;
    m._21 = Bx;
    m._22 = By;
    var mi = m.getInvert();
    if (!mi) return;

    //マトリックス変換値を求める
    var a, b, c, d;
    a = mi._11 * _Ax + mi._12 * _Bx;
    c = mi._21 * _Ax + mi._22 * _Bx;

    b = mi._11 * _Ay + mi._12 * _By;
    d = mi._21 * _Ay + mi._22 * _By;

    g.save();

    g.beginPath();
    g.moveTo(vertex_list[0], vertex_list[1]);
    g.lineTo(vertex_list[2], vertex_list[3]);
    g.lineTo(vertex_list[4], vertex_list[5]);
    g.clip();	//三角形に切り取る

    var e = vertex_list[0] - (a * uv_list[0] * img.width + c * uv_list[1] * img.height);
    var f = vertex_list[1] - (b * uv_list[0] * img.width + d * uv_list[1] * img.height);
    g.transform(a, b, c, d, e, f);
    g.drawImage(img, 0, 0);
    g.restore();
}

立方体にサイコロのテクスチャマッピングをした結果となります。
f:id:Yaju3D:20150706000232j:plain
サイコロ画像を外した場合のテクスチャマッピングをした結果となります。
f:id:Yaju3D:20150706001015j:plain
複雑なロボット(頂点数:18449、面数:23179、UV座標数:12287)も同じプログラムで表示することが出来ます。但し今のままでは描画が遅すぎます。
f:id:Yaju3D:20150706001908j:plain