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

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

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

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

このチュートリアルの第 3 部では、フラット シェーディングおよびブリン鏡面反射光ハイライトを使用して、マテリアル カラーのサポートを追加し、ライティングを実装します。

関連トピック:

Vector_Map テクスチャ マップ

Vector_Map テクスチャ マップと MAXScript

数値 - ビット メソッド

qsort メソッド

renderMap メソッド

自然言語

配列の値を、カメラ空間の Z 深度である、6 番目の配列要素によってソートする関数を定義します。

描画するすべてのポリゴン データを収集し、事前にソートするための配列を導入します。

Z-深度値を計算し、必要なデータをすべて新しい配列に収集します。

qsort() および新しいソート関数を使用して新しい配列をソートします。

ソートされた配列をループし、背面から前面に描画します。

スクリプト:

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

    fn compareFN v1 v2=
    (
        if v1[6] < v2[6] then -1 else 1
    )

    local st = timestamp()
    local theFileName = (getDir #userscripts + "\\PolygonRendering.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 drawArray = #()        

    local theStrokeThickness = 1

    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

                local theFaceCenter = meshop.getFaceCenter theMesh f
                local theZDepth = (theFaceCenter*theViewTM2).z
                append drawArray #(v1, v2, v3, (ColorToHex theStrokeColor), (ColorToHex theLitFillColor), theZDepth)


                        )--end if normal positive
        )--end f loop
    )--end o loop

    qsort drawArray compareFN

    for d in drawArray do
    (
        format "\t<polygon points='%,%  %,%  %,%' \n" d[1].x d[1].y d[2].x d[2].y d[3].x d[3].y to:theSVGfile
        format "\tstyle='stroke:%; fill:%; stroke-width:%'/>\n" d[4] d[5] theStrokeThickness to:theSVGfile
    )

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

ステップごとの解説

*Italic* のコードは第 1 部と同じです。Bold のコードは新規部分です。

(
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
)
fn compareFN v1 v2=
(
    if v1[6] < v2[6] then -1 else 1
)

この関数は、ソート配列の 2 つの要素を比較する qsort() メソッドによって使用されます。

配列の 6 番目の要素内の各ポリゴンの Z-深度値を格納します。それにより、この関数は 2 つの配列要素を取得し、最初の要素が 2 番目の要素よりも離れているか近いかによって -1 または 1 を返します。

local st = timestamp()
local theFileName = (getDir #userscripts + "\\PolygonRendering.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 drawArray = #()

この配列にはポリゴンが入力されます。これには、ビュー空間に変換された頂点、ストロークと塗り潰しに使用する色、さらにソートのための Z-深度値が含まれます。

local theStrokeThickness = 1

ストロークを 1 に設定し、天球体のアウトラインに見られた前回の問題をより詳しく調査できるようにします。

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
            local theFaceCenter = meshop.getFaceCenter theMesh f
            local theZDepth = (theFaceCenter*theViewTM2).z

面の中心からビューアへの距離によってソートします。そのために、まずワールド空間内の面の中心の位置を取得し、次に、それをビュー空間に変換します。

Z 座標はマイナスの値になり、ビューアからの点の距離が長くなるほど値は低くなります。

これを、qsort() によって呼び出されるソート関数内で使用します。

            append drawArray #(v1, v2, v3, (ColorToHex theStrokeColor), (ColorToHex theLitFillColor), theZDepth)

SVG ファイルに直接出力する代わりに、各ポリゴンを drawArray 変数に収集します。これには、画面空間の 3 つの頂点、ポリゴンのストロークと塗り潰しの 16 進の色、および上で計算した Z-深度値が含まれます。

        )--end if normal positive
    )--end f loop
)--end o loop
qsort drawArray compareFN

これで、6 番目の要素(Z-深度値)に格納された距離に基づいて drawArray をソートすることができます。

Z-深度の小さい(大きなマイナスの数)ポリゴンを最初に描画し、カメラから見て背面にある要素から前面にある要素の順序で描画する「ペインタ アルゴリズム」を実装します。

for d in drawArray do
(
    format "\t<polygon points='%,%  %,%  %,%' \n" d[1].x d[1].y d[2].x d[2].y d[3].x d[3].y to:theSVGfile
    format "\tstyle='stroke:%; fill:%; stroke-width:%'/>\n" d[4] d[5] theStrokeThickness to:theSVGfile
)

この For ループはソートされたポリゴン データを SVG ファイルに出力します。

このコードは、前のバージョンのコードによく似ていますが、これを 2 番目のループで実行し、ソートされた配列から値を読み取ります。

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

スクリプトを使用してジオメトリをレンダリングする

第 1 部と同じ例でスクリプトへの変更をテストし、違いを確認してみましょう。

結果からわかるとおり、天球体のエッジの問題は新しいソート描画アプローチによって解決されました。

これは、「一定カラー + エッジ面」描画モードを使用してビューポートに表示される結果と非常に高い精度で一致します。

[ストロークの厚さ](Stroke Thickness)を 2 に増やし、第 1 部で使用したティーポット プリミティブをレンダリングします。

ビューポートをリサイズし、結果を確認してみます。

第 1 部での問題がある設定をレンダリングすると、今度は正しい結果が生成されます。

ただし、背面をカリングしているので、ティーポットの本体とふたの間のギャップを通して球を見ることができます。

背面のカリング ラインを再マーキングすることによって

-- if (theNormal*theViewTM).z > 0 do

カリングせずにすべてのオブジェクトのすべてのポリゴンをレンダリングすることができます。レンダリングには約 2 倍の時間がかかりますが、球は適切に隠されます。

チュートリアルの第 3 部では、マテリアル カラーおよびライティング サポートをスクリプトに追加して、拡散反射光シェーディングおよびブリン鏡面反射光ハイライトによるファセット レンダリングを生成します。