広告
広告

Blender でインテリアマッピング

カテゴリ:blender

インテリアマッピングは板ポリゴンに、奥行きのある仮想の部屋を描画するアルゴリズムだ。

床と天井のみのインテリアマッピング

fig1
P : オブジェクト空間でのシェーディング位置
I : オブジェクト空間での単位視線ベクトル
d : グリッド間距離
hit : 計算によって求める仮想の衝突位置

まず床と天井との衝突位置を計算したあとに、他の面の計算を説明する。以下の計算式から、係数 t がわかれば衝突位置 hit がきまる。P はオブジェクト空間でのシェーディング位置で、I はオブジェクト空間での単位視線ベクトルだ。

hit = P + t * I

インテリアマッピングは固定幅のグリッドで部屋が分割されるので、シェーディング位置の端数を切り捨てることにより衝突位置の高さを計算できる。視点が上向きなら天井、下向きなら床の位置を計算する。計算するコードは以下のようになる。

hit.z = (I.z > 0) ? ceil(P.z/d) * d : (ceil(P.z/d)-1) * d

高さがわかると係数 t がきまり、衝突位置 hit を計算できる。

hit.z = P.z + t * I.z

t = (hit.z - P.z) / I.z

OSL のソースコードは以下のようになる。.blend ファイルの interior-mapping-z.osl でテストできる。

shader InteriorMapping(
    point Position = P,
    vector Incoming = I,
    float d = 1.0,
    output vector texcoord = vector(0,0,0),
)
{
    point p = transform("world", "object", Position);
    vector incoming = -transform("world", "object", Incoming);

    float hit_height = (incoming[2] > 0.0) ? ceil(p[2]/d)*d : (ceil(p[2]/d)-1)*d;
    float t = (hit_height - p[2]) / incoming[2];
    point hit = p + t * incoming;

    texcoord = hit / 8;
}

5面すべてを計算するインテリアマッピング

上記の計算を残りの軸に対しても計算する。係数 t は XYZ 軸の中で最も小さいものを採用する。.blend ファイルの interior-mapping.osl でテストできる。

うまく見えないときはオフセットを調整する。

shader InteriorMapping(
    point Position = P,
    vector Incoming = I,
    float d = 1.0,
    vector offset = vector(0,0,0),
    output vector texcoord = vector(0,0,0),
)
{
    point p = transform("world", "object", Position) + offset;
    vector incoming = -transform("world", "object", Incoming);

    float min_t = 1e10;
    int id; // 0:+X, 1:-X, 2:-Y, 3:+Y, 4:-Z, 5:+Z
    for (int i = 0; i < 3; ++i){
        int is_flip = incoming[i] > 0.0;
        float hit_height = is_flip ? ceil(p[i]/d)*d : (ceil(p[i]/d)-1)*d;
        float t = (hit_height - p[i]) / incoming[i];
        if (t < min_t){
            min_t = t;
            id = i * 2 + is_flip;
        }
    }
    
    point hit = p + min_t * incoming;
    if (0 <= id && id <= 1){   // X
        texcoord = vector(hit[1], hit[2],0) / 8;
    }
    else if(2 <= id && id <= 3){ // Y (Depth)
        texcoord = vector(hit[0], hit[2],0) / 8;
    }
    else{   // Z (Up)
        texcoord = hit / 8;
    }
}

最適化

t = (ceil(P.z/d)*d - P.z) / I.z の ceil(P.z/d)*d - P.z の部分は、切り捨てられた端数を取得しているので -mod(P.z, d) で置き換えられる。つまり t = -mod(P.z, d) / I.z になる。

t = ((ceil(P.z/d)-1)*d - P.z) / I.z は -(mod(P.z, d) + d) / I.z に置き換えられる。

最適化されたコードは以下のようになる。

shader InteriorMapping(
    vector distance = vector(1.0, 1.0, 1.0),
    vector offset = vector(0,0,0),
    output vector texcoord = vector(0,0,0),
)
{
    vector incoming = -transform("world", "object", I);
    point p = transform("world", "object", P) + offset;
    vector mod_p = mod(p + offset, -distance);
    
    float min_t = 1e10;
    int id; // 0:+X, 1:-X, 2:-Y, 3:+Y, 4:-Z, 5:+Z
    for (int i = 0; i < 3; ++i){
        int is_flip = incoming[i] < 0.0;
        float t = -(mod_p[i] + is_flip * distance[i]) / incoming[i];
        if (t < min_t){
            min_t = t;
            id = i * 2 + is_flip;
        }
    }
    
    point hit = p + min_t * incoming;
    int table_x[6] = {1,1,0,0,0,0};
    int table_y[6] = {2,2,2,2,1,1};
    texcoord = vector(hit[table_x[id]], hit[table_y[id]],0) / 8;
}

UV の計算

衝突位置と id とを使って UV 座標を計算する。UV 座標はテクスチャの配置によって計算方法が変わる。今更ながらOSLでこんなものを作っていた。3Dテクスチャ的なやつと視差マッピング的なやつ。に OSL のサンプルがある。

そのほか

オブジェクト空間ではなく UV 座標を使ったインテリアマッピングも可能だ。

OSL では transform("world", "object", Position) のようにしてワールド空間をオブジェクト空間に変換できる。またベクトル変換(Vector Transform)ノードを使うことでベクトルをオブジェクト空間に変換できる。

object space
オブジェクト空間に変換

外部リンク

Interior Mapping A new technique for rendering realistic buildings (pdf)

Interior mapping in Unity

インテリアマッピングの基礎


OSL

Eevee

Simulation of the “Interior Mapping” technique through Nodes で Eevee で実行可能な .blend ファイルがダウンロードできる。


そのほか

関連記事

Blender 記事の目次


広告
広告