3D レンダリング

要約:
  • Renderer3dPrimitive、および render-primitive は、イミーディエイト モードの 3D レンダリングの実行に使用される主要ツールです。
  • Renderer3dGraphic は、Renderer3d および描画面へのアクセスを提供する Graphic オブジェクトです。
  • スポットライト、方向ライト、およびポイント ライトを使用して、3D オブジェクトをライティングすることができます。

3D レンダリングの概要

Curl® 言語は、Renderer3d クラスを通じて、Primitiverender-primitive マクロを組み合わせて、3D イミーディエイト モード レンダリングを提供します。Drawable クラスは、レンダリングが行われる領域を示します。Renderer3dGraphic は、画面上のグラフィック階層に Drawable を追加するための便利なメカニズムを提供します。
注意: Renderer2dGraphic は、DrawableGraphic からプロパティ DrawableGraphic.occlusion-performance-mode を継承します。 このプロパティーはパフォーマンスおよびレンダリング精度に影響します。 詳細は、SceneGraphicocclusion-performance-mode を参照してください。
高レベルの保持モード 3D レンダリングの場合は、シーン パッケージ CURL.GRAPHICS.SCENE を使用します。シーンは、Renderer3dGraphic の専門的なサブクラスである SceneGraphic など、スペースで 3D オブジェクトを操作するためのツールを提供します。「シーン」を参照してください。
描画面およびレンダラーにアクセスする方法はいくつかあります。次に要約を示します。
以下のセクションでは、これらのアプローチについて詳しく説明します。

Renderer3dGraphic からの Renderer3d の取得

Renderer3dGraphic は、Drawable および Renderer3d へのアクセスを提供し、また、レンダリングされたイメージの画面への配置も扱います。Renderer3dGraphic は、Graphic のサブクラスであり、グラフィック階層に配置可能であること、グラフィック オプション設定に対する応答、およびイベント ハンドリングなどのグラフィック属性を継承します。
再ペイント ハンドラを記述し、それを Renderer3dGraphic コンストラクタに提供する必要があります。このプロシージャでは、目的のレンダリングを実行します。再ペイント ハンドラ コードは、関連付けられた Drawable にレンダリングします。この再ペイント ハンドラは、Graphic の再ペイントが必要になるたびに呼び出されるコールバック プロシージャです。
再ペイント コールバック プロシージャは、Graphic のサイズが変更されるときなど、再ペイントが要求されたときに常に自動的に呼び出されます。Renderer3dGraphic.update-drawable を呼び出すと、再ペイント処理をトリガできます。これによってコールバック プロシージャが呼び出され、画面に Drawable をレンダリングするために必要な更新が行われます。
アニメーション アプリケーションは、連続的に画面を再ペイントするために、update-drawable を呼び出します。Renderer3dGraphic を使用してアニメーションのコードを書く方法の 1 つは、Timerupdate-drawable の呼び出しを配置する方法です。TimerEvent が起動されるたびに、update-drawable が呼び出され、それが再ペイント ハンドラを呼び出します。
次の例では、primitive-proc と呼ばれる簡単な再ペイント ハンドラを定義しています。このプロシージャには、3 つの引数が渡されます。引数 rgRenderer3dGraphicrenRenderer3d、そして area は更新が必要な領域を示す RectangleSet です。area が NULL の場合、Drawable 全体を更新する必要があります。
この例は、render-primitive を使用します。これについては、「Primitiverender-primitive を使用したレンダリング」で詳しく説明します。

例: Renderer3dGraphic からの Renderer3d の取得
{import * from CURL.GRAPHICS.RENDERER3D}
||define a simple repaint handler procedure
{define-proc {primitive-proc
                 rg:Renderer3dGraphic,
                 ren:Renderer3d,
                 area:#RectangleSet}:void
    let d:Drawable={non-null rg.drawable}
    {render-primitive
        p:Primitive,
        type=Primitive.triangles
        on ren do
        {p.color3 1.0, 0, 0} || red
        {p.vertex2 0in, 1in}
        {p.vertex2 1in, 0in}
        {p.vertex2 1in, 2in}

        {p.color3 0, 1.0, 0} || green
        {p.vertex2 1in, 0in}
        {p.vertex2 1in, 2in}
        {p.vertex2 2in, 1in}
    }
}
||create a Renderer3dGraphic,
||passing that procedure as repaint-handler
{Renderer3dGraphic repaint-handler=primitive-proc}
       

オフスクリーン描画の作成

次の例では、Drawable.create-offscreen から Drawable を取得し、その Drawable からレンダラーを取得します。結果は、さまざまな方法で画面に表示できます。この例では、Renderer2d.render-drawable を使用します。
注意: 次のコードでは、Drawable.forced-redraw-handlerを設定します。 forced-redraw-handlerプロシージャは、Drawable が内容が失われたために再描画されなくてはない場合に呼び出されます。 Drawable が内容が失われてしまう理由は、スクリーンセーバーのアクティベーション等を含め、原因は複数ある可能性があります。 もし、コードがこのようなシチュエーションに対応していない場合は、 アプレットはダイアログを提示し、Drawable が内容を失ったことをユーザーに通知します。
Drawableが、Applet が存続する間に常に使用される場合を除いて、 Drawableは、必要が無くなった時に直ぐ消去される必要があります。 Renderer2dGraphicRenderer3dGraphicSceneGraphic は自動的にこれを対処しますが、 Drawable を直接作成した場合は、自分で消去を実行する必要があります。 Drawable.destroyを使用して、 Drawables を削除することができます。 以下の例は、グラフィカル階層に挿入する場合のみに Drawables を作成します。 そして、グラフィカル階層から削除する時に消去します。 このプロセスによって Drawable が必要とされてない時に存在しないようにすることが出来ます。

例: オフスクリーン描画の作成
{import * from CURL.GRAPHICS.STANDARD}
{import * from CURL.GRAPHICS.RENDERER3D}
{import * from CURL.GUI.STANDARD}

{define-class public final MyFill {inherits Fill}

  field public drawable:#Drawable
  field public renderer:#Renderer3d

  {constructor public {default ...}
    {construct-super
        {on AttachEvent do
            || Create Drawable.
            {assert self.drawable == null}
            set (self.renderer, self.drawable) = {Renderer3d.create-offscreen 2in, 2in}

            || The following code is necessary because Drawables
            || can lose their contents for a number of reasons,
            || including the activation of a screensaver. If this
            || situation is not handled this way, the applet pops
            || up a dialog notifying the user, which is usually
            || not desirable.  All that is necessary is to repaint
            || the entire drawable.
            set self.drawable.forced-redraw-handler =
                {proc {}:void
                    {self.draw-into-offscreen-drawable}
                }

            {self.draw-into-offscreen-drawable}
            
        },
        {on DetachEvent do
            || Destroy Drawable.
            {self.drawable.destroy}
            set self.drawable = null
            set self.renderer = null
        },
        ...
    }


  }

  {method public final {draw r2d:Renderer2d}:void
    let grect:GRect = {self.layout.get-bounds}

    let width:Distance = grect.rextent + grect.lextent
    let height:Distance = grect.ascent + grect.descent

    || Translate by -width/2 and -height/2 because Fill's
    || internal idea of its origin, for purposes of drawing,
    || is in its center.
    {with-render-properties translation =
        {Distance2d -width/2, -height/2}
        on r2d do
        {r2d.render-drawable
            0m, 0m, width, height, {non-null self.drawable}
        }
    }
  }

  {method private {draw-into-offscreen-drawable}:void

    let r:Renderer3d = {non-null self.renderer}

    {render-primitive p:Primitive, type = "quads" on r do
        || red
        {p.color3 1.0, 0.0, 0.0}
        {p.vertex2 .5in, .5in}

        || green
        {p.color3 0.0, 1.0, 0.0}
        {p.vertex2 .5in, 1.5in}

        || blue
        {p.color3 0.0, 0.0, 1.0}
        {p.vertex2 1.5in, 1.5in}

        || yellow - oh yeah
        {p.color3 1.0, 1.0, 0.0}
        {p.vertex2 1.5in, .5in}

    }
  }
}


{let myfill:MyFill = {MyFill width = 2in, height = 2in}}
{value myfill}

SceneObject のサブクラス化

次の例では、PolygonSet のサブクラスを作成して球をレンダリングします。PolygonSet は、SceneObject のサブクラスです。
注意: このアプローチは、SCENE パッケージを使用する場合で下位レベルのレンダリングを実行する必要があるときに限り有効です。

例: SceneObject のサブクラス化
{import * from CURL.GRAPHICS.SCENE}

{define-proc public
    {sphere-vertices-normals hres:int, vres:int}:
    ({Array-of FloatDistance3d}, {Array-of FloatDirection3d})
    let vertices:{Array-of FloatDistance3d} =
        {{Array-of FloatDistance3d}.from-size
            (hres + 1) * (vres + 1),
            {FloatDistance3d 0f(m), 0f(m), 0f(m)}
        }
    let normals:{Array-of FloatDirection3d} =
        {{Array-of FloatDirection3d}.from-size
            (hres + 1) * (vres + 1),
            {FloatDirection3d 0, 0, 0}
        }
    let n:int = 0
    {for i:int = 0 to hres do
        {for j:int = 0 to vres do
            let azimuth:FloatAngle = -180f(deg) + i * 360f(deg)/hres
            let elevation:FloatAngle =  -90f(deg) + j * 180f(deg)/vres
            set vertices[n] =
                {FloatDistance3d
                    ({cos azimuth} * {cos elevation}) * 1f(in),
                    ({sin azimuth} * {cos elevation}) * 1f(in),
                    ({sin elevation}) * 1f(in)
                }
            set normals[n] = vertices[n]/1f(in)
            {inc n}
        }
    }
    {return vertices, normals}
}
{define-proc public
    {mesh-faces hres:int, vres:int}:#{Array-of #{Array-of int}}
    let faces:#{Array-of #{Array-of int}} =
        {{Array-of #{Array-of int}}.from-size hres * vres, null}
    let n:int = 0
    {for i:int = 0 to hres - 1 do
        {for j:int = 0 to vres - 1 do
            set faces[n] =
                {new {Array-of int},
                    i       * (vres + 1) + j,
                    (i + 1) * (vres + 1) + j,
                    (i + 1) * (vres + 1) + j + 1,
                    i       * (vres + 1) + j + 1
                }
            {inc n}
        }
    }
    {return faces}
}
{define-class public Sphere {inherits PolygonSet}

  field hres:int
  field vres:int
  {constructor public {default
                          hres:int = 20,
                          vres:int = 10,
                          ...}
    {construct-super ... }
    set self.hres = hres
    set self.vres = vres
    set (self.vertices, self.normals) =
        {sphere-vertices-normals hres, vres}
    set self.faces = {mesh-faces hres, vres}
  }
  {method open public
    {paint renderer:Renderer3d,
        viewport-width:Distance, viewport-height:Distance}:void
    {with
        renderer.cull-face-enabled? = true
     do
        {super.paint renderer, viewport-width, viewport-height}
    }

  }
  {method open public
    {get-local-bounding-box
        check-visibility?:bool = false
    }:(min-xyz:Distance3d,
       max-xyz:Distance3d,
       valid-bounds?:bool)
    {return
        {Distance3d -1in, -1in, -1in},
        {Distance3d 1in, 1in, 1in},
        not check-visibility? or self.bounding-box-or-object-visible?
    }
  }
}
{value
    let scene:Scene = {Scene}
    let camera:Camera = scene.camera
    let camera-target:Distance3d = {Distance3d 0ft, 0ft, 0ft}
    let camera-position:Distance3d = {Distance3d 2in, -1in, 2in}
    let camera-direction:Direction3d =
        {(camera-target - camera-position).direction}
    let up:Direction3d = {Direction3d 0, 0, 1}
    {camera.set-orientation-and-position
        camera-direction, up, position = camera-position}
    set camera.projection = Projection.perspective
    set camera.near-clipping-plane = 1in
    set camera.far-clipping-plane = 20ft
    set camera.field-of-view = 90degrees
    let dir-light-color:Color = {Palette.get-magenta} ||magenta
    let dir-light:DirectionalLight =
        {DirectionalLight
            direction =  {Direction3d -1, 0, 0},
            diffuse-color = dir-light-color,
            specular-color = dir-light-color
        }
    {scene.add-object dir-light}
    let sphere:Sphere =
        {Sphere
            lighting-enabled? = true,
            hres = 50, vres = 25,
            fill-pattern = {FillPattern.get-white}
        }
    {scene.add-object sphere}
    let scene-graphic:SceneGraphic =
        {SceneGraphic
            scene,
            width=2in,
            height=2in,
            background = {FillPattern.get-black}
        }
    scene-graphic
}

mutate-fill-pattern の使用

次の例は、mutate-fill-pattern を使用した Renderer3d の取得を示しています。これは、FillPattern に描画する方法を提供する効果があります。この場合、Drawable とそれに関連付けられた Renderer3d を使用します。mutate-fill-pattern を使用して、Renderer2d または Pixmap を取得することもできます。

例: mutate-fill-pattern の使用
{import * from CURL.GRAPHICS.RENDERER3D}
{value
    let fp:FillPattern =
        {FillPattern.from-url {url "curl://install/docs/default/images/adria.jpg"}}

    {mutate-fill-pattern renderer:Renderer3d on fp do
        {render-primitive p:Primitive, type="triangles"
            on renderer do
            {p.color3 1.0, 0, 0} || red
            {p.vertex2 0in, 1in}
            {p.vertex2 1in, 0in}
            {p.vertex2 1in, 2in}

            {p.color3 0, 1.0, 0} || green
            {p.vertex2 1in, 0in}
            {p.vertex2 1in, 2in}
            {p.vertex2 2in, 1in}
        }
    }
    || display the FillPattern in a Frame
    {Frame width=2in, height=2in, background=fp}
}