チュートリアル - 頂点レンダラーの開発

チュートリアル > 頂点レンダラーの開発

このチュートリアルでは、3 次元空間のオブジェクトを 2 次元のイメージに変換するという、レンダリングの基本を説明します。これを簡単で高速 (リアルタイム) に行うため、スクリプトでは頂点だけを描画します。イメージはビューポートが更新されるたびに再描画します。 つまり、ビューの変更、パン、ズーム、軌道周回を行うと、頂点レンダラーはそれをすぐに反映します。

関連トピック:

アクティブ ビューポート情報、タイプ、および変換へのアクセス

全体の流れ:

macroScript を作成します。

レンダラーが使用可能と使用不可能のどちらであるかを記憶するフラグ変数を定義します。

レンダリング サイズと、フロント バッファおよびバック バッファのイメージを入れるローカル変数を定義します。

ビューポートを再描画するときに呼び出す関数を定義します。

この関数では次のことを行います。

  • バック バッファをフロント バッファにコピーします。

  • すべてのジオメトリ オブジェクトを調べ、そのメッシュを取得します。

  • メッシュのすべての頂点を取得し、現在のビューの変換行列を使用してそれらをイメージ座標に変換します。

  • 頂点ごとに 1 ピクセルをフロント バッファに描画します。

  • 完了したら、イメージを表示します。

isChecked ハンドラからのフラグを返します。

実行すると、

  • フラグを反転します。

  • レンダリング サイズの変数およびビットマップを再定義します。

  • フラグが true の場合はビューポート再描画のコールバック関数を登録し、false の場合は登録を解除します。

スクリプト:

macroScript VertexRender category:"HowTo"
(
local VertexRendererEnabled = false
local screen_width, screen_height, back_vfb, front_vfb
fn VertexRendererFunction =
(
st = timestamp()
copy back_vfb front_vfb
for o in geometry where classof o != TargetObject do 
(
theMesh = snapshotAsMesh o
dot_color = #(o.wirecolor)
theVertCount = theMesh.numverts
for v = 1 to theVertCount do
( 
thePos = (getVert theMesh v)* viewport.getTM()
screen_origin = mapScreenToView [0,0] (thePos.z) [screen_width,screen_height]
end_screen = mapScreenToView [screen_width,screen_height] (thePos.z) [screen_width,screen_height]
world_size = screen_origin-end_screen
x_aspect = screen_width/(abs world_size.x)
y_aspect = screen_height/(abs world_size.y)
screen_coords = point2 (x_aspect*(thePos.x-screen_origin.x)) (-(y_aspect*(thePos.y-screen_origin.y)))
setPixels front_vfb screen_coords dot_color
)--end v loop
)--end o loop
display front_vfb 
et = timestamp()
pushPrompt ("VertRender "+(1000.0/(et-st)) as string +" fps")
)--end fn
 
on isChecked return VertexRendererEnabled
 
on execute do
(
VertexRendererEnabled =not VertexRendererEnabled
 
if VertexRendererEnabled then
(
screen_width=RenderWidth
screen_height=RenderHeight
back_vfb = bitmap screen_width screen_height
front_vfb = bitmap screen_width screen_height
registerRedrawViewsCallback VertexRendererFunction
RedrawViews() 
) 
else
(
unRegisterRedrawViewsCallback VertexRendererFunction 
close front_vfb 
)
)--end on execute
)--end script

ステップごとの解説

macroScript VertexRender category:"HowTo"
(

まず VertexRender という名前の MacroScript を定義することから始めます。 これは[HowTo]カテゴリに表示されます。この macroScript は、 on execute do on is Checked do というイベント ハンドラを持つ高度なものです。

localVertexRendererEnabled= false

レンダラーがアクティブになっているかどうかを示すフラグを保持するローカル変数を定義します。この変数は最初は false に設定されます。

local screen_width,screen_height, back_vfb,front_vfb

イメージの幅と高さ、およびフロント バッファとバック バッファという 2 つのビットマップ (詳細は後述) を保持するローカル変数も定義します。

fn VertexRendererFunction =
(

これは、レンダラーが使用可能状態になってビューポートが更新されるときに実行される関数です。

st = timestamp()

レンダリング スピードについて情報を出力するために、timestamp 関数を使用して現在のシステム時間を取得します。

copy back_vfb front_vfb

バック バッファをフロント バッファにコピーし、こうして前のイメージをクリアします。バック バッファにバックグラウンド イメージを入れ、この関数でその上に描画することもできます。ここでは、バック バッファのイメージは黒に設定されています。

for o in geometry where classof o != TargetObject do
(

シーンのジオメトリ オブジェクトでライトやカメラなどのターゲット オブジェクトではないものすべてについて、ループを繰り返し実行します。このとき、オブジェクトが非表示であるかどうかについてはチェックしないことに注意してください。この点については、練習を兼ねてコードに追加してもかまいません。 ここでは、非表示のものも含め、すべてのオブジェクトを描画します。

theMesh = snapshotAsMesh o

オブジェクトをメッシュとしてメモリ内にスナップショットを取ります。これには、オブジェクトに適用されたすべてのモディファイヤとスペース ワープも含まれます。

dot_color = #(o.wirecolor)

頂点をペイントするために使用する配列を定義し、描画にワイヤフレーム カラーを使用します。

theVertCount = theMesh.numverts

処理を高速化するため、メッシュ内の頂点の読み込みは 1 回にします。頂点のループを制限するには、次の値を使用します。

for v = 1 to theVertCount do
( 

変数 v は 1 から頂点の数までをカウントします。

thePos = (getVert theMesh v)* viewport.getTM()

v 番目の頂点の位置はビューポートの変換行列によって乗算される必要があります(カメラ ビューポートの場合、viewport.getTM() ではカメラの変換行列の逆行列が返されます。パース ビューポートまたは正投影ビューポートの場合はすでに逆行列になっています)。この行列で頂点位置を乗算すると、3 次元空間の座標がカメラ空間に変換されます。

screen_origin = mapScreenToView [0,0] (thePos.z) [screen_width,screen_height]

ここで、レンダリングするイメージが現在の頂点の奥行きで配置されたときのピクセル サイズに相当する投影面のサイズが必要になります。これを計算するには、この面の 2 つの終点(左上と右下)をカメラ空間の 3 次元ポイントとして得る必要があります。

thePos はすでにカメラ座標にあるため、.Z 座標はポイントの Z 深度になります。 mapScreenToView の戻り値は、投影面の左上隅に対応するカメラ空間の 3D ポイントです。

end_screen = mapScreenToView [screen_width,screen_height] (thePos.z) [screen_width,screen_height]

投影面の右下隅に関しても同じことを行います。

ここで行ったこととその理由を説明すると次のようになります。

ご存知のとおり、カメラではカメラ位置を頂点とする円錐 (角錐台) ができます。投影面をカメラの Z 軸に沿って「移動」させると、投影面が「目」から離れるほどワールド単位での大きさは大きくなりますが、最終的なイメージ内のピクセル数は変わりません。

スクリーンショットには、カメラと 2 つの投影面 (赤の X) が示されています。また、カメラから離れるほど、投影面が拡大していくのもわかります。

最も離れた投影面は、カメラ空間でのティーポットの位置の奥行きにちょうど位置します。

この投影面の左上隅の赤の球は、先に計算した screen_origin の座標に対応します。このイメージを作成するために、 screen_origin の値が計算され、次に MAXScript の次のコード行が呼び出され、

sphere pos:(screen_origin*$Camera01.transform)

そのポイントを表す球がワールド座標内に作成されます( screen_origin は最初はカメラ空間にあり、カメラの変換行列で乗算することにより、ワールド座標に変換されます)。

青色の球も同様で、 end_screen の座標を表しています。

ビューをレンダリングすると、カメラからは次のように見えることになります。 赤と青色の球がちょうど予想どおりの場所に現れることがわかります。

world_size = screen_origin-end_screen

これで投影面の 2 つの終点が判明したため、投影面の幅と高さをワールド単位で計算できます。投影面のピクセル単位のサイズもわかっているので、ピクセルとワールド単位の間の関係も割り出すことができます。

x_aspect = screen_width/(abs world_size.x)
y_aspect = screen_height/(abs world_size.y)

投影面のサイズを使用して、アスペクトの値を計算します。 これは基本的に、X 軸および Y 軸に沿ったワールド単位 1 に対するピクセル数になります。

screen_coords= point2 (x_aspect*(thePos.x-screen_origin.x)) (-(y_aspect*(thePos.y-screen_origin.y)))

アスペクトの値を使用して、頂点位置の X と Y の値を実際のピクセル位置に変換します。Y 座標が反転されていることと、X の位置が screen_origin でオフセットされていることに注意してください。 カメラの軸 (イメージの中心) が座標[0,0]にあるのに対し、MAXScript でのイメージの位置が左上隅にあるためです。

setPixels front_vfbscreen_coordsdot_color

フロント バッファの現在の頂点位置にピクセルを描画します。 ワイヤフレーム カラーを使用して先に定義した、カラー配列を使用します。

)--end v loop
)--end o loop
display front_vfb 

すべてのオブジェクトのすべての頂点の処理が完了したら、表示を更新します。

et = timestamp()

時間を止め、1 回の描画にかかった時間を確認します。

pushPrompt ("VertRender " +(1000.0/(et-st)) as string + " fps")

その結果はステータス バーに出力されます。

)--end fn
on isChecked returnVertexRendererEnabled

この macroScript をツールバー、メニュー、またはクアッドメニューに置くとき、そのチェック状態は最初に定義したローカル変数に格納されたブール値によって決まります。

on execute do (

ユーザがボタンを押すか、メニュー項目を選択すると、

VertexRendererEnabled = not VertexRendererEnabled

変数の状態が逆になります。 true であれば false になり、false であれば true になります。

if VertexRendererEnabled then
(

ここでスクリプトが起動された場合、

screen_width=RenderWidth
screen_height=RenderHeight

[シーンをレンダリング](Render Scene)ダイアログ ボックスの現在のサイズ値を読み込みます。これらの値を固定値 (320 と 240 など) で置き換え、レンダラーの設定とは関係なく決まったサイズにすることもできます。

back_vfb = bitmap screen_width screen_height
front_vfb = bitmap screen_width screen_height

これらのサイズ値を使用して 2 つのビットマップを定義します。1 つ目のビットマップは、描画の前にイメージをクリアするために使用するバック バッファです。また、ここで openBitmap の呼び出しを使用し、ディスクからバックグラウンド イメージをロードして上に描画することもできます。

registerRedrawViewsCallback VertexRendererFunction

先にビューポート再描画のコールバック関数として定義した関数を登録します。この関数は 3ds Max のビューポートの再描画のたびに呼び出され、レンダリングされたイメージを更新します。

VertexRendererFunction() 

表示を初期化するには、この関数を 1 回だけ呼び出します。

) 
else
(
unRegisterRedrawViewsCallback VertexRendererFunction 

レンダラーがすでに開いている場合、ここで閉じます。さらに更新が行われないようにコールバック関数の登録を解除します。

close front_vfb 

イメージを閉じて表示を終了します。

)
)--end on execute
)--end script

関連事項