OpenGL Tutorial (Version 3.3) – volumetrische Nebelwolken, transparente Objekte (Download)

Das Rendern von transparenten Objekten führt in komplexen Szenen nicht selten zu Darstellungsfehlern (Objekte hinter einem transparenten Objekt sind unsichtbar, z-Fighting Probleme bei der Partikeldarstellung). Zwar schafft das so genannte Back To Front Order Rendering ein wenig Abhilfe, da weiter von der Kamera entfernt liegende Objekte zuerst gerendert werden, jedoch ist die Festlegung der richtigen Darstellungsreihenfolge mit einigem zusätzlichen Rechenaufwand verbunden, und darüber hinaus ist das Verfahren wirkungslos gegen die häufig in Partikelsystemen auftretenden z-Fighting Probleme.

Die optimale Lösung wäre die Darstellung der transparenten Objekte in einem zweiten Render-Durchgang, nach dem bereits alle nicht transparenten (opaquen) gerendert worden sind. Zur Vermeidung von z-Fighting Problemen müssen die transparenten Objekte mit deaktiviertem Tiefenpuffer gerendert werden. Damit die durch die opaquen Objekte verdeckten Bereiche nicht dargestellt werden, muss darüber hinaus ein Tiefenvergleich mit den Tiefenwerten dieser Objekte durchgeführt werden.


Zu Demonstrationszwecken haben wir das vorangegangene Programmbeispiel um vier volumetrische Nebelwolken erweitert, die wir um den Planeten herum positioniert haben. Gerendert werden die einzelnen Nebelpartikel dieser Wolken mit deaktivierten Tiefenpuffer in einem zweiten Durchgang nach der Darstellung des Planeten.
Wie bereits erwähnt, besteht der eigentliche Trick bei der Partikeldarstellung im Tiefenvergleich zwischen den Tiefenwerten der Erde mit denen der Partikel.
Im Vertex Shader (VolumetricNebula.vert) werden zunächst die World-Space-Vertexpositionen an den Fragment Shader weitergeleitet:

gl_Position    = matWorldViewProjection*gs_Vertex;
gs_TexCoord[0] = gs_MultiTexCoord0;

gs_TexCoord[1].x = gl_Position.x;
gs_TexCoord[1].y = gl_Position.y;
gs_TexCoord[1].z = gl_Position.z;


Der eigentliche Tiefenvergleich findet dann im Fragment Shader (VolumetricNebula.frag) statt:

// World-Space-Vertexposition in den Screen Space transformieren:
float tempFloat = 1.0/gl_Position.z;

vec2 ProjectedTexCoord = vec2(0.5*gs_TexCoord[1].x*tempFloat + 0.5,
                              0.5*gs_TexCoord[1].y*tempFloat + 0.5);

// Tiefenwerte aus dem Renderdurchgang mit den opaquen Objekten auslesen.
float CameraDepth1 = texture2D(CameraDepthTexture, ProjectedTexCoord).w;
float CameraDepth2 = texture2D(CameraDepthTexture, ProjectedTexCoord+
                               vec2(0.0005, 0.0005)).w;

float CameraDepth3 = texture2D(CameraDepthTexture, ProjectedTexCoord-
                               vec2(0.0005, 0.0005)).w;
float CameraDepth4 = texture2D(CameraDepthTexture, ProjectedTexCoord+
                               vec2(-0.0005, 0.0005)).w;
float CameraDepth5 = texture2D(CameraDepthTexture, ProjectedTexCoord-
                               vec2(-0.0005, 0.0005)).w;
     


float MinCameraDepth = CameraDepth1;
 

MinCameraDepth = min(MinCameraDepth, CameraDepth2);
MinCameraDepth = min(MinCameraDepth, CameraDepth3);
MinCameraDepth = min(MinCameraDepth, CameraDepth4);
MinCameraDepth = min(MinCameraDepth, CameraDepth5);
 

if(MinCameraDepth < 0.0)
    MinCameraDepth = 100000.0;
   
 
float MaxCameraDepth = CameraDepth1;

MaxCameraDepth = max(MaxCameraDepth, CameraDepth2);
MaxCameraDepth = max(MaxCameraDepth, CameraDepth3);
MaxCameraDepth = max(MaxCameraDepth, CameraDepth4);
MaxCameraDepth = max(MaxCameraDepth, CameraDepth5);
 

if(MaxCameraDepth < 0.0)
    MaxCameraDepth = 100000.0;
 

float diff = abs(MaxCameraDepth-MinCameraDepth);  

// Tiefenvergleich durchführen:
if(diff > 0.5)
    gs_FragColor = Brightness*NebulaColor;
else 

{
    if(MinCameraDepth < gs_TexCoord[1].z-0.125)
        discard;
    else
        gs_FragColor = Brightness*NebulaColor;

}
 
Hinweis:
Für die Ausführung dieses Programmbeispiels muss der Treiber Ihrer Grafikkarte die OpenGL Version 3.3 unterstützen.

Visual C++ 2010: DemoWithOpenGL2010_Tut15