Tutorial #14: Zeichenreihenfolge

Die Bestimmung der sichtbaren Flächen in einer Szene erfolgt normalerweise nach dem Z-Buffer Verfahren (auch Depth Buffer, Tiefenpuffer). Dabei ist die Zeichenreihenfolge der Objekte für das Ergebnis unerheblich. Allerdings funktioniert das Z-Buffer Verfahren nur mit opaken Flächen und nicht mit transparenten Flächen. Transparente, sich überdeckende Flächen müssen sortiert von hinten nach vorne gezeichnet werden, da die Zeichenreihenfolge einen entscheidenden Einfluss auf die resultierende Mischfarbe hat.(1)

Daher ist es oft sinnvoll, zuerst die opaken Geometrien mit Z-Buffer zu zeichnen und danach die transparenten Geometrien, wobei aber nur mehr lesend auf den Z-Buffer zugegriffen wird. Auch für Overlays, HUDs und verschiedene Effekte ist die Zeichenreihenfolge wichtig.

Dieses Tutorial beschreibt in welcher Abfolge das Rendering genau passiert und welche Möglichkeiten es gibt, um die Zeichenreihenfolge zu beeinflussen.

Zu beachten
(1) Es gibt auch Verfahren um transparente Flächen unabhängig von der Zeichenreihenfolge zu zeichnen - siehe z.B. Weighted Blended Order-Independent Transparency.

Render Hierarchie

In folgenden Ebenen kann Einfluss auf die Zeichenreihenfolge des Renderers genommen werden:

  1. Framebuffer/Backbuffer
  2. View
  3. Camera
  4. Layer
  5. Pass
  6. SortOrder/Distance/DepthOrder

Framebuffer/Backbuffer

Zunächst werden alle Graph::FrameBuffer in der Reihenfolge, in der sie im Szenegraphen definiert sind, gerendert. Als letztes wird der Backbuffer gerendert.

View

Für jeden Framebuffer/Backbuffer kann es einen oder mehrere Graph::View-Objekte geben.

Ein View definiert einen Bildausschnitt bzw. eine Region im Fenster (Integer-Koordinaten, Pixel). Der View hat immer die gleiche Größe wie der zugehörige Frambebuffer/Backbuffer. Eine view mask kann dazu verwendet werden, um den Zeichenbereich auf einen kleineren, rechteckigen Bereich einzuschränken ("Scissor Test").

Die Zeichenreihenfolge kann mit dem Attribut depthOrder festgelegt werden. Bei gleicher depthOrder bestimmt die Definitionsreihenfolge auch die Zeichenreihenfolge. Eine höherer depthOrder Wert bewirkt ein späterers rendern d.h. der View wird, innerhalb des Framebuffers, über den anderen Views mit kleineren depthOrder Werten gezeichnet.

Camera

Für jeden View kann es eine oder mehrere Graph::Camera-Objekte geben.

Ein Graph::Camera-Objekt definiert einen sichtbaren Bereich in der virtuellen Welt (Real-Koordinaten).

Die Zeichenreihenfolge kann mit dem Attribut depthOrder festgelegt werden. Bei gleicher depthOrder bestimmt die Definitionsreihenfolge auch die Zeichenreihenfolge. Eine höherer depthOrder Wert bewirkt ein späterers rendern d.h. die Kamera wird, innerhalb des Views, über den anderen Kameras mit kleineren depthOrder Werten gezeichnet.

Layer

Jede Kamera rendert ihre Objekte (zugeordnet über Kind-Knoten oder über Graph::CameraState).

Auf Wunsch kann das Rendern schichtweise mit mehreren Layern erfolgen. Standardmäßig wird alles in den Layer 0 gerendert. Mit einem Graph::LayerState-Knoten und dem Attribut index kann auf einen anderen Layer umgeschaltet werden.

Zu beachten
Achtung! Aus Ressourcengründen sollten Layer immer fortlaufend, beginnend mit 0, benützt werden (also z.B. 0, 1, 2, 3 und nicht 0, 12, 20, 30).

Die Zeichenreihenfolge der Layer wird über den Layer-Index festgelegt, wobei höhere Werte später gerendert werden.

Pass

Das Rendern eines Layers erfolgt in zwei Durchgängen (engl. passes), abhängig vom Graph::Material und dem Attribut objectSortMode.

Im ersten Durchgang (Pass 0) werden alle Objekte gerendert, bei denen das Graph::Material einen objectSortMode von OBJECT_SORT_MODE_BY_MATERIAL hat.

Im zweiten Durchgang (Pass 1) werden alle Objekte gerendert, bei denen das Graph::Material einen objectSortMode von OBJECT_SORT_MODE_BY_DEPTH hat.

Wird das Attribut objectSortMode nicht explizit angegeben, bestimmt der Z-Buffer Mode die Zuordnung:

  • Materialien die den Z-Buffer schreiben (DEPTH_BUFFER_MODE_WRITE_ONLY oder DEPTH_BUFFER_MODE_READ_AND_WRITE) verwenden OBJECT_SORT_MODE_BY_MATERIAL (Pass 0).
  • Alle anderen Materialien verwenden OBJECT_SORT_MODE_BY_DEPTH (Pass 1).

SortOrder, Distanz, DepthOrder

Im Pass 0 bestimmt das Attribut sortOrder des Materials die Zeichenreihenfolge.

Im Pass 1 erfolgt die Sortierung nach:

  1. sortOrder Attribut des Materials.
  2. Tiefensortierung (Bei gleicher sortOder, Sortierung von hinten nach vorn).
  3. depthOrder Attribut der Geometrie (bei gleicher sortorder und Distanz, Sortierung über depthOrder Attribut).

Über das Graph::Camera-Attribut depthSortMode kann die Methode der Tiefensortierung festgelegt werden (z.B. keine Sortierung, Z-Value, Distanz - siehe IEnums::DepthSortMode).

Zu beachten
Das depthOrder Attribut des Eltern-Knoten wird an Kind-Knoten weitergegeben. Die depthOrder des Kind-Knoten entspricht dann der Summe aus depthOrder des Eltern-Knotens und der angegebenen depthOrder des Kind-Knotens. Soll ein Kind-Knoten hinter einem Eltern-Knoten gerendert werden, so kann beim Kind-Knoten ein negativer depthOrder Wert verwendet werden.

Automatische Gruppierung

Objekte, bei denen die Zeichenreihenfolge in einem Pass nicht eindeutig bestimmt ist,
werden vom Renderer nach folgenden Kriterien zusammengefasst:

  • Shader-Program-Switch-Minimierung
  • Textur-Switch-Minimierung
  • Gruppierung bei gleicher Beleuchtung

Das Zusammenfassen erfolgt automatisch, kann von außen nicht beeinflusst werden und ist nicht deterministisch.

Beispiel

Als einfaches Beispiel zeigen wir die Verwendung mehrerer Views und Kameras und zeichnen einige Objekte mit unterschiedlichen Materialien.

Wir verwenden eine background_camera um den Bildschirm und den Z-Buffer zu löschen, sowie eine main_camera zum Zeichnen des Hintergrunds.

Zu beachten
Die background_camera hat keine Kind-Knoten und muss daher mittels CameraState aktiviert werden. Anderenfalls würde die Kamera vom Renderer übersprungen werden.
<View id="main_view"
    leftMaskCoord="0"
    topMaskCoord="0"
    rightMaskCoord="0"
    bottomMaskCoord="0"
/>

<Camera id="background_camera"
    viewId="main_view"
    projectionType="ORTHOGRAPHIC"
    unitSizeX="1"
    unitSizeY="1"
    colorClearValue="000000aah"
    clearColorBuffer="true"
    clearDepthBuffer="true"
    depthOrder="0"
/>
<CameraState
    cameraId="background_camera"
/>

<Camera id="main_camera"
    viewId="main_view"
    projectionType="ORTHOGRAPHIC"
    unitSizeX="1"
    unitSizeY="1"
    clearColorBuffer="no"
    clearDepthBuffer="no"
    depthOrder="10"
/>

<CameraTransform
    cameraId="main_camera"
    posX="0" posY="0" posZ="512"
/>

<CameraState
    cameraId="main_camera"
/>

Die main_camera verwenden wir um eine Textur großflächig anzuzeigen.

    <Instance graphResourceId="package_main:graph_camera"/>
    <PlaneGeometry materialSlot="4" textureSlot="1" scaleFactorX="10000" scaleFactorY="1080"/>

Zusätzlich definieren wir einen view_left und eine perspektivische Kamera camera_left. Den Graph::View schränken wir mit dem Attribut rightMaskCoord auf die linke Bildschirmhälfte ein. Das Zentrum der Graph::Camera verschieben wir ebenfalls in die Mitte der linken Hälfte.

<?xml version="1.0" ?>

<Graph>

    <View id="view_left"
        leftMaskCoord="0"
        topMaskCoord="0"
        rightMaskCoord="-640"
        bottomMaskCoord="0"
    />

    <Camera id="camera_left"
        centerX="-0.5"
        viewId="view_left"
        fieldOfViewX="2"
        clearColorBuffer="no"
        clearDepthBuffer="no"
        depthOrder="2"
    />

    <CameraTransform 
        cameraId="camera_left" posX="0" posZ="600"
    />

    <CameraState
        cameraId="camera_left"
    />
</Graph>

Für die rechte Bildschirmhälfte definieren wir einen view_right und eine orthographische Kamera camera_right. Auch hier schränken wird den Graph::View ein und verschieben das Zentrum der Graph::Camera.

<?xml version="1.0" ?>

<Graph>

    <View id="view_right"
        leftMaskCoord="640"
        topMaskCoord="0"
        rightMaskCoord="0"
        bottomMaskCoord="0"
    />

    <Camera id="camera_right"
        projectionType="ORTHOGRAPHIC"
        centerX="0.5"
        viewId="view_right"
        unitSizeX="1"
        unitSizeY="1"
        clearColorBuffer="no"
        clearDepthBuffer="no"
        depthOrder="2"
    />

    <Transform angleX="-90d">
        <CameraTransform 
            cameraId="camera_right" posZ="500"
        />
    </Transform>

    <CameraState
        cameraId="camera_right"
    />
</Graph>

Mit den beiden Kameras rendern wir die gleiche Szene in unterschiedlichen Ansichten.

<Instance graphResourceId="package_main:graph_camera_left"/>
    <Reference targetId="myScene"/>

<Instance graphResourceId="package_main:graph_camera_right"/>
    <Reference targetId="myScene"/>

Als Ergebnis erhalten wir ein Bild, das mit vier Kameras gerendert wurde.

  • background_camera löscht den Fensterinhalt
  • main_camera füllt den Hintergrund mit einem blauen Bild
  • camera_left rendert die Szene in einer perspektivischen Ansicht
  • camera_right rendert die Szene orthographisch aus der Vogelperspektive.

Damit die opaken und transparenten Flächen richtig gerendert werden, wird für opakes Material depthBufferMode="READ_AND_WRITE" und für transparentes Material depthBufferMode="READ_ONLY" verwendet.

tut0114_render_order.png
Render Order


Copyright © 2011-2024 Spraylight GmbH.