スカルプテッド プリム: 技術的解説

From Second Life Wiki
Jump to navigation Jump to search

はじめに

スカルプテッド プリムとはテクスチャ画像 (スカルプト マップ) から生成される三次元の格子構造です。スカルプト マップの画像データは頂点データに変換され、各ピクセルは各頂点に対応しますが、描画詳細度によってはピクセルの間引きが起こります。(ビューアがどのようにデータを扱うかは後述。) それぞれのピクセル (頂点) 行は自分自身にリンクし、4 つのピクセルからなるブロックのそれぞれで 2 個の三角形を形成します。上端と下端では、各頂点はそれぞれの極にリンクされます。

Sculpted-prim-explanation.png

ピクセル フォーマット

スカルプト マップのαチャネル (何らかの透明度) は現在無視されるので、各ピクセル毎に 24 ビットが割り当てられ、すなわち色ごとでは 8 ビット、0~255 の値が使われます。各色は 3D 空間での軸座標を表現し、赤・緑・青はそれぞれ X・Y・Z 座標に対応します。別のアプリケーションからデータをインポートする場合は、それが必ずしも SL と同様の軸設定をしているとは限らない点に注意してください。そうした場合は、適切な軸変換が必要です。

各色の値は、プリム中心位置 <0,0,0> を基準とした各軸方向の位置情報に対応します。128 未満の値はマイナス方向、128 より大きい値はプラス方向の位置となります。全面が平坦な灰色 <128,128,128> のスカルプト マップは、プリム中心位置の小さな点として描画されるでしょう。

一辺 1m のスカルプテッド プリムについて見ると、0~255 の色値は中心から -0.5m~0.496m の位置情報に対応します。SL の全てのプリムが持つ三次元のサイズ ベクトルに関し、スカルプテッド プリムは通常プリムとほぼ同等の最大描画サイズ (9.961m) を持ちます。39mm 足りない理由は、色値の最大が 255 であり、プラス側の最大描画サイズをきっちり 0.5m にするには 256 まで値が必要だからです。

テクスチャ マッピング

スカルプト マップは UV マップも兼ねます。それは表面テクスチャがどのように格子構造へ貼り付けられるかを表現します。スカルプト マップは、どの頂点が、表面テクスチャ画像のどのピクセルに対応するかをそのまま表現しています。それは実際に立体として描画された時、各頂点の、表面テクスチャ画像における UV 座標を決定するのに使われます。これは従来の通常プリムに比べると、テクスチャ マッピングに関して大きな利点となります。なぜなら Maya のような 3D モデリングソフトでテクスチャ処理を全て済ませてから、その位置マップをエクスポートすると、UV 座標がそのまま SL でも再現されるからです。

ビューアでの描画

以下の分析はビューア v1.22 で行ないました。

注意: ビューア v1.20.14 以降では、32x32, 64x64, 128x128 ピクセルのスカルプト マップをアップロードする際に "ロスのない圧縮を使用" にチェックを入れると、無圧縮の JPEG2000 形式で保存されます。これより大きなサイズの場合は、圧縮ノイズによってプリムの形状が歪んでしまい、リアルな表現も台無しになるでしょう。加えて、アセットサーバは 128x128 より大きなサイズのテクスチャをスカルプトマップとして認識しません。スカルプトウィンドウの上では動作するものであってもです。 (実はウィンドウの中にはテクスチャならなんでも置けるのです。)


重要: スカルプト マップを読み込む際、ビューアのプログラム上の処理という点で言うと、最初の行 (00~F0) は画像の最上行にあたります。列の並びは左から右の順です。

Mesh.png

ビューアは頂点の格子構造を最も詳細に描画する際、(縫い目のタイプが球形ならば) 以下のようにスカルプト マップを処理します。

33 個の頂点からなる一番上の 1 行は極に割り当てられます。
それぞれ 33 頂点からなる 31 行が通常の頂点として採られます。
33 個の頂点からなる一番下の 1 行は極に割り当てられます。

各行において 33 番目の頂点は 1 番目の頂点と同じです。これにより格子面は側面で円筒状に縫い合わされます。 両極はそれぞれスカルプト マップの最上行・最下行の "ピクセル数÷2" の位置にあるピクセルから採られます。(64x64 ピクセルのスカルプト マップならば 33 番目の列、すなわち列番号は 0 から数えるので (32, 0) および (32, 63) のピクセルが該当します。)

例えば描画詳細度が最高の場合、縫い目のタイプが球形のスカルプテッド プリムには 32列x33行の頂点を持つ格子が必要です。スカルプト マップが 64x64 ピクセルならば、頂点は以下のピクセルから採られます:

 (32,0)  (32,0)  (32,0) ...  (32,0)  (32,0)  (32,0)
  (0,2)   (2,2)   (4,2) ...  (60,2)  (62,2)   (0,2)
   .                                       . 
   .                                       .
   .                                       .
 (0,60)  (2,60)  (4,60) ... (60,60) (62,60)  (0,60)
 (0,62)  (2,62)  (4,62) ... (60,62) (62,62)  (0,62)
(32,63) (32,63) (32,63) ... (32,63) (32,63) (32,63)

縫い目のタイプがその他のプリム (トーラス、平面、シリンダー) でも、頂点の結合が無いという点を除けば、大体同様です。トーラス タイプは描画詳細度が最高の状態で 32列x32行の頂点が必要で、それはスカルプト マップの各軸 0, 2, 4, ...., 60, 62 のピクセルから採られます。平面タイプは 33列x33行の頂点が必要で、各軸 0, 2, 4, ...., 60, 62 のピクセルから採られます。シリンダー タイプは 32列x33行の頂点が必要で、水平方向ではトーラス タイプの、垂直方向では平面タイプの方法に従います。

スカルプト マップには 64x64 ピクセルの画像が推奨されます。(32x32 ピクセルは公式には対応しておらず、また予期せぬ結果をもたらします。) 理屈上、32x32 ピクセルの場合も (殆ど) 同等の結果が得られるのですが、今のところスカルプト マップの最後の行が頂点としても、(球形タイプの場合) 極座標を採るための行としても扱われません。極座標はもう一つ手前の行から設定されるため、最後の2行が丸ごと極に割り当てられます。いずれにせよ、球形タイプの場合は北極から南極まで33個の頂点があるため、32x32 ピクセルのスカルプト マップが描画に充分な情報を持っているとは言えません。

描画詳細度が落ちると、平面タイプの場合 17 点あるいは 9 点が採られます。トーラス タイプの場合 16 点あるいは 8 点が採られます。これらは上記と同様の手順で採られますが、ピクセル間隔が違います。例えば 0, 2, 4, ..., 60, 62, 63 から 33 点採る代わりに、0, 4, 8, ..., 56, 60, 63 から 17 点を採ります。いかなる描画詳細度 (LOD, Level of Detail) でも描画されるべき重要な頂点のピクセルは、必ずこれらの位置に設定してください。

最終結果の品質は JPEG 圧縮によっても左右されます。生物的な形状を作るならば圧縮することで良い結果を得られますが、そうでないならば 64x64 ピクセルのテクスチャを無圧縮で使ってください。(アップロード時に "ロスの無い圧縮を使用" のチェックを入れる。)

作例

sculptedpreview で表示した林檎のスカルプテッド プリム Sculpted-preview-03.png

sculptedpreview で表示した S 字のスカルプテッド プリム。スカルプト マップを表面テクスチャとしても適用しています。 Sculpted-preview-04.png

以下の C++ のプログラムは "testsculpture.tga" を出力します。生成されたスカルプト マップは、縫い目のタイプ、及びどのピクセルが描画に使われるかの理解に役立つでしょう。この画像をスカルプト マップおよび表面テクスチャとして適用し、ワイヤーフレーム表示で縫い目タイプを切り替えて見ましょう。

<lsl>

 // Test program for sculptures -- Aleric Inglewood, Feb 2009.
 #include <cmath>
 #include <stdint.h>
 #include <sys/types.h>
 #include <cstring>
 #include <algorithm>
 #include <fstream>
 // TGA header definition.
 #pragma pack(push,1) // Pack structure byte aligned
 struct tga_header
 {
   uint8_t   id_length;              // Image id field length.
   uint8_t   colour_map_type;        // Colour map type.
   uint8_t   image_type;             // Image type.
   uint16_t  colour_map_index;       // First entry index.
   uint16_t  colour_map_length;      // Colour map length.
   uint8_t   colour_map_entry_size;  // Colour map entry size.
   uint16_t  x_origin;               // x origin of image.
   uint16_t  y_origin;               // u origin of image.
   uint16_t  image_width;            // Image width.
   uint16_t  image_height;           // Image height.
   uint8_t   pixel_depth;            // Pixel depth.
   uint8_t   image_desc;             // Image descriptor.
 };
 int main()
 {
   int const width = 64;
   int const height = 64;
   int const bpp = 24;
   float const scale = 255.0f;
   size_t filesize = sizeof(tga_header) + width * height * bpp / 8;
   uint8_t* tga_buf = new uint8_t [filesize];
   tga_header tga;
   std::memset(&tga, 0, sizeof(tga_header));
   tga.pixel_depth = bpp;
   tga.image_width  = width;
   tga.image_height = height;
   tga.image_type = 2;   // Uncompressed.
   tga.image_desc = 8;   // 8 bits per component.
   std::memcpy(tga_buf, &tga, sizeof(tga_header));
   uint8_t* out = tga_buf + sizeof(tga_header);
   for (int y = 0; y < height; ++y)
   {
     for (int x = 0; x < width; ++x)
     {	
       double r, g, b;
       r = (double)x / (width - 1);
       g = (double)y / (height - 1);
       b = 1.0;
       // None of these points are taken into account in a sculptie.
       if (x % 2 == 1)
         b = 0.5;
       if (y % 2 == 1)
         b = 0.5;
       // Except those (depending on the stitching type).
       if (x == width - 1)
         b = 0.98;             // plane.
       if (y == height - 1)
         b = 0.98;             // plane and cylinder.
       // In the spherical stitching, these two are the poles.
       if (x == 32)
       {
         if (y == 0 || y == 63)
         b = 0.7;
       }
       // TGA format writes BGR ...
       *out++ = (uint8_t)(round(b * scale));
       *out++ = (uint8_t)(round(g * scale));
       *out++ = (uint8_t)(round(r * scale));
     }
   }
   std::ofstream file;
   file.open("testsculpture.tga");
   file.write((char*)tga_buf, filesize);
   file.close();
   delete [] tga_buf;
 }

</lsl>