チュートリアル > SVG ポリゴン レンダラーの開発 - 第 1 部 |
次に、3 部で構成されるチュートリアル シリーズのうち、Vector_Map テクスチャ マップとともに MAXScript を使用する方法を説明する第 1 部を示します。MAXScript はレンダリングされるシーン オブジェクトの面を表すポリゴン エンティティを含むディスクに SVG XML ファイルを書き込むために使用されます。SVG ファイルは、任意の Web ブラウザにロードするか、 renderMap() 関数を使用してビットマップにレンダリングすることができます。MAXScript を使用してSVG ファイルを作成する方法の詳細については、「Vector_Map テクスチャ マップと MAXScript」のトピックを参照してください。
このチュートリアルの第 1 部では、単にすべてのジオメトリ オブジェクトのカメラに面する三角面をすべて出力します。
チュートリアルの第 2 部では、ポリゴンを奥行きでソートし、面が不正確に重ならないようにします。
チュートリアルの第 3 部では、マテリアル カラーのサポートを追加し、ライティングを実装します。
Vector_Map テクスチャ マップと MAXScript
(
次のコードがすべてそれ自身のローカル スコープ内で評価されるように、ブラケットを開きます。
fn ColorToHex col = ( local theComponents = #(bit.intAsHex col.r, bit.intAsHex col.g, bit.intAsHex col.b) local theValue = "#" for i in theComponents do theValue += (if i.count == 1 then "0" else "") + i theValue )
この関数は引数として MAXScript カラー値をとります。
その後、カラー値の赤、緑および青のコンポーネントがそれぞれ 16 進値に変換された行列を作成します(これらは bit.intAsHex() メソッドによって文字列として返されます)。
theValue と呼ばれる文字列変数が、Web HTML および XML ファイル内で使用される 16 進カラーの接頭辞である「#」に初期化されます。
その後、For Loop は、配列内の変換済みコンポーネント内で繰り返され、文字列変数にアキュムレートします。文字列が単一の文字を持つ場合、先頭のゼロが含まれます。
ループは値がすべて 16 進で生成されます。たとえば Red は「#ff0000」になります。
local st = timestamp()
これを使用して、SVG を出力して生成される Vector_Map をスクリプトの終わりのイメージにレンダリングするのにかかった時間を計算します。
local theFileName = (getDir #userscripts + "\\PolygonRendering1.svg") local theSVGfile = createFile theFileName
SVG ファイルのためのファイル名を定義します。これは UserScripts フォルダに格納されます。
その後、新しい ASCII テキスト ファイルを作成するためにこのファイル名を使用します。そこに SVG XML 定義を出力します。
format "<svg xmlns=\"http://www.w3.org/2000/svg\"\n" to:theSVGfile format "\t\txmlns:xlink=\"http://www.w3.org/1999/xlink\">\n" to:theSVGfile
すべての SVG ファイルは XML の内容について記述するヘッダーで始まらなければなりません。
この標準情報を、新規作成されたテキスト ファイルにフォーマットします。
local theViewTM = viewport.getTM() theViewTM.row4 = [0,0,0]
現在のビューポートのビュー変換行列が必要です。ただし、方向(ビューアを向く、またはビューアを向かない)をチェックするため、面法線をビュー空間に変換する部分は使用しません。
このため、平行移動部分を含んでいる .row4 をゼロにします。
local theViewTM2 = viewport.getTM()
local theViewSize = getViewSize() local theViewScale = getViewSize() theViewScale.x /= 1024.0 theViewScale.y /= 1024.0
Vertex_Texture の 1024x1024 キャンバス スペースを変換するために、ビューポート サイズおよびビューポート スケールを取得する必要があります。
ビュー サイズには、レンダリングしたい実際のピクセル解像度が含まれます。
ビュー スケールには、Vertex_Texture からレンダー出力サイズに変換するためのスケール係数が含まれます。
local theStrokeThickness = 3
ソリッド塗り潰しカラーにはシェーディングがないため、エッジを表示状態にすることは重要です。そこで、ストローク厚さを 3 として定義するための変数を設定します。
gw.setTransform (matrix3 1)
頂点をワールド空間からビュー空間に変換するためのグラフィックス ウィンドウ(gw)関数を使用します。そのため、gw 変換行列が ID 行列に設定されることを確認する必要があります。
for o in Geometry where not o.isHiddenInVpt and classof o != TargetObject do (
この For Loop にはすべての GeometryClass オブジェクトが含まれ、何らかの理由で非表示にならず、有効なメッシュをもたない TargetObject クラスのオブジェクトのみがフィルタされます。
local theStrokeColor = white local theFillColor = o.wirecolor
レンダリングは 2 つの色を使用します - アウトラインのための白(ストローク カラー)および塗り潰しカラーのためのオブジェクト カラー(ワイヤフレーム カラー)です。
local theMesh = snapshotAsMesh o
現在のオブジェクトのメッシュのワールド状態を、変数内に取り込みます。
これによってそのフェースをループし、頂点と法線データにアクセスすることができます。
for f = 1 to theMesh.numfaces do (
この For Loop は、現在のメッシュのすべてのフェースを処理します。
local theNormal = normalize (getFaceNormal theMesh f)
これはすでに正規化されているはずですが、あらためて normalize() を適用して確実に正規化するのにそれほど手間はかかりません。
if (theNormal*theViewTM).z > 0 do (
法線がカメラを指している面のみを描画することを確認します(バックフェース カリングともいいます)。
そのためには、最初にワールド空間に存在していた法線に、ビュー変換行列(平行移動部分が 0 に設定されている)を乗算します。これは法線をビュー空間に変換します。
この時点では、ビューがその -Z 軸に沿ってシーンを見ているため、マイナスの Z 値はビューの方向に平行(ビューアから遠ざかる)となり、プラスの Z 値は「ビューアの方向を向く」ことを意味しています。
深さでのソートがない(これについては第 2 部で追加します)ため、天球体のエッジにはまだいくつかの問題があります。
テストを if(theNormal*theViewTM).z < 0 do に反転すると、正面をカリングして、背面のみを描画します。
このテストを実行しないと、メッシュのすべての面が描画されてしまい、まだその他のソートを実行していないために、結果は非常に煩雑なものになります。
local theFace = getFace theMesh f
local v1 = gw.transPoint (getVert theMesh theFace.x) local v2 = gw.transPoint (getVert theMesh theFace.y) local v3 = gw.transPoint (getVert theMesh theFace.z)
これで、面の 3 つの頂点をそれぞれ取得し、現在のビュー空間に変換することができます。
v1.x /= theViewScale.x v1.y /= theViewScale.y v2.x /= theViewScale.x v2.y /= theViewScale.y v3.x /= theViewScale.x v3.y /= theViewScale.y
ただし、SVG がその専用のキャンバスを現在のビューに再マップするためには、画面座標を 0 ~ 1023 の範囲で指定しなければならないため、変換された頂点の X および Y コンポーネントを、スクリプトの最初で計算したビュー スケール係数で除算しなければなりません。このように、X がビューポートの右の境界にあるピクセルは 1023 にマップし、ビューポートの中心にあるピクセルは、そのビューポート ピクセル座標に関係なく、SVG キャンバスの中心に正確に配置されます。
format "\t<polygon points='%,% %,% %,%' \n" v1.x v1.y v2.x v2.y v3.x v3.y to:theSVGfile format "\tstyle='stroke:%; fill:%; stroke-width:%'/>\n" (ColorToHex theStrokeColor) (ColorToHex theFillColor) theStrokeThickness to:theSVGfile
これで、3 つの変換された頂点の X と Y 座標を指定することにより、SVG ポリゴン定義を出力することができます。
また、定義したストローク カラー、オブジェクトのカラーに設定された塗り潰しカラー、および現在 3 に設定されているストロークの厚さを使用するためのポリゴンのスタイルを定義します。
)--end if normal positive )--end f loop )--end o loop
IF テストの本文および 2 つのネスト ループ(現在のオブジェクトのすべての面をループするものと、すべてのジオメトリ オブジェクトをループするもの)を閉じることができます。
format "</svg>\n" to:theSVGfile close theSVGfile
また、閉じタグで SVG XML 本文を終了し、テキスト ファイルを閉じることもできます。
local theSVGMap = VectorMap vectorFile:theFileName alphasource:0 local theBitmap = bitmap theViewSize.x theViewSize.y renderMap theSVGMap into:theBitmap filter:true display theBitmap
これで、出力先のテキスト ファイル名を使用し、アルファ チャネルを有効にして、新しい VectorMap を構築することができます。
現在のビューポートの正確なピクセル サイズに基づいたサイズを持つ定義済みのビットマップに出力します。
続いて、フィルタを有効にして VectorMap をそのビットマップにレンダリングし、生成されるビットマップ値を最終的に表示することができます。
format "Render Time: % sec.\n" ((timestamp()-st)/1000.0)
スクリプトを終了する前に、SVG を保存しマップをレンダリングするためにかかった時間を出力することができます。
timestamp() はミリ秒単位であるため、秒単位で生成するには 1000.0 で除算しなければなりません。
)
パース ビューポートを使用していくつかのジオメトリ プリミティブをレンダリングしてみましょう。
結果からわかるとおり、ティーポットを構成する 4 つの要素、すなわちボディ、ふた、ハンドル、口には厳密なソートの問題があります。口の一部はボディの上に描画されます。
複数のオブジェクトを個別にレンダリングするときは、カメラの位置と生成の順序に応じて、同様の問題が発生します。
次の例では、円柱がティーポットに続いて 2 番目に作成され、実際にティーポットの後ろに配置される球は 3 番目に作成されました。
チュートリアルの第 2 部では、単一のオブジェクト内、および複数のオブジェクト間で不正な重なりが発生しないようにポリゴンをソートし、上のような問題を修正します。