チュートリアル - SVG ポリゴン レンダラーの開発 - 第 1 部

チュートリアル > SVG ポリゴン レンダラーの開発 - 第 1 部

次に、3 部で構成されるチュートリアル シリーズのうち、Vector_Map テクスチャ マップとともに MAXScript を使用する方法を説明する第 1 部を示します。MAXScript はレンダリングされるシーン オブジェクトの面を表すポリゴン エンティティを含むディスクに SVG XML ファイルを書き込むために使用されます。SVG ファイルは、任意の Web ブラウザにロードするか、 renderMap() 関数を使用してビットマップにレンダリングすることができます。MAXScript を使用してSVG ファイルを作成する方法の詳細については、「Vector_Map テクスチャ マップと MAXScript」のトピックを参照してください。

このチュートリアルの第 1 部では、単にすべてのジオメトリ オブジェクトのカメラに面する三角面をすべて出力します。

チュートリアルの第 2 部では、ポリゴンを奥行きでソートし、面が不正確に重ならないようにします。

チュートリアルの第 3 部では、マテリアル カラーのサポートを追加し、ライティングを実装します。

関連トピック:

Vector_Map テクスチャ マップ

Vector_Map テクスチャ マップと MAXScript

数値 - ビット メソッド

renderMap メソッド

自然言語

MAXScript カラーを 16 進値 (Web フォーマット) に変換する関数を定義します。

新しい SVG テキスト ファイルを作成し、そのヘッダーを出力します。

ビュー変換およびサイズに関する情報を収集します。

すべての可視のジオメトリ シーン オブジェクトをループします

ワールド空間 TriMesh を取得し、その面をループします。

イメージ スペース内の各面の 3 つの頂点の位置を計算して、SVG に出力します。

SVG を閉じ、新しい Vector_Map インスタンスを作成して、表示するビットマップにレンダリングします。

スクリプト:

(
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
)

local st = timestamp()
local theFileName = (getDir #userscripts + "\\PolygonRendering3.svg")
local theSVGfile = createFile theFileName
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

local theViewTM =  viewport.getTM()
theViewTM.row4 = [0,0,0]

local theViewTM2 = viewport.getTM()
local theViewSize = getViewSize()
local theViewScale = getViewSize()
theViewScale.x /= 1024.0
theViewScale.y /= 1024.0
	
local theStrokeThickness = 3
	
gw.setTransform (matrix3 1)	
for o in Geometry where not o.isHiddenInVpt and classof o != TargetObject do
(
	local theStrokeColor = white
	local theFillColor = o.wirecolor	
	
	local theMesh = snapshotAsMesh o
	for f = 1 to theMesh.numfaces do
	(
		local theNormal = normalize (getFaceNormal theMesh f)
		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)
			
			v1.x /= theViewScale.x 
			v1.y /= theViewScale.y 
			v2.x /= theViewScale.x 
			v2.y /= theViewScale.y
			v3.x /= theViewScale.x
			v3.y /= theViewScale.y
			
			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			
		)--end if normal positive
	)--end f loop
)--end o loop

format "</svg>\n" to:theSVGfile
close theSVGfile
local theSVGMap = VectorMap vectorFile:theFileName alphasource:0
local theBitmap = bitmap theViewSize.x theViewSize.y
renderMap theSVGMap into:theBitmap filter:true
display theBitmap
format "Render Time: % sec.\n" ((timestamp()-st)/1000.0)
)

ステップごとの解説

(

次のコードがすべてそれ自身のローカル スコープ内で評価されるように、ブラケットを開きます。

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」になります。

変数 theValue は関数の結果として返されます。

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 部では、単一のオブジェクト内、および複数のオブジェクト間で不正な重なりが発生しないようにポリゴンをソートし、上のような問題を修正します。