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:
- Framebuffer/Backbuffer
- View
- Camera
- Layer
- Pass
- 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_ONLYoderDEPTH_BUFFER_MODE_READ_AND_WRITE) verwendenOBJECT_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:
sortOrderAttribut desMaterials.- Tiefensortierung (Bei gleicher
sortOder, Sortierung von hinten nach vorn). depthOrderAttribut derGeometrie(bei gleichersortorderund Distanz, Sortierung überdepthOrderAttribut).
Ü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
depthOrderAttribut des Eltern-Knoten wird an Kind-Knoten weitergegeben. DiedepthOrderdes Kind-Knoten entspricht dann der Summe ausdepthOrderdes Eltern-Knotens und der angegebenendepthOrderdes Kind-Knotens. Soll ein Kind-Knoten hinter einem Eltern-Knoten gerendert werden, so kann beim Kind-Knoten ein negativerdepthOrderWert 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_camerahat keine Kind-Knoten und muss daher mittelsCameraStateaktiviert 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_cameralöscht den Fensterinhaltmain_camerafüllt den Hintergrund mit einem blauen Bildcamera_leftrendert die Szene in einer perspektivischen Ansichtcamera_rightrendert 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.