Zeiger auf modernes OpenGL Shadow Cubemapping?

Hintergrund

Ich arbeite an einem 3D-Spiel mit C ++ und modernem OpenGL (3.3). Ich arbeite jetzt an der Licht- und Schattenwiedergabe und habe die gerichtete Schattenzuordnung erfolgreich implementiert. Nachdem ich die Anforderungen für das Spiel durchgelesen hatte, entschied ich, dass ich Punktlichtschatten-Mapping benötigen würde. Nach einigen Recherchen stellte ich fest, dass ich für die omnidirektionale Schattenzuordnung etwas Ähnliches wie für die gerichtete Schattenzuordnung tun werde, jedoch stattdessen mit einer Cubemap.

Ich habe keine Vorkenntnisse über Cubemaps, verstehe sie jedoch so, dass eine Cubemap aus sechs nahtlos verbundenen Texturen besteht. Ich habe mich ein bisschen umgesehen, aber leider hatte ich Mühe, ein definitives "Tutorial" zum Thema für modernes OpenGL zu finden. Ich suche zuerst nach Tutorials, die es von Anfang bis Ende erklären, weil ich ernsthaft Mühe hatte, aus Ausschnitten des Quellcodes oder nur aus Konzepten zu lernen, aber ich habe es versucht.

Aktuelles Verständnis

Hier ist mein allgemeines Verständnis der Idee, abzüglich der technischen Details. Bitte korrigiere mich.

Für jedes Punktlicht wird ein Framebuffer eingerichtet, ähnlich wie beim gerichteten ShadowmappingEine einzelne Cubemap-Textur wird dann generiert und mit gebundenglBindTexture(GL_TEXTURE_CUBE_MAP, shadowmap).

Die Cubemap wird mit folgenden Attributen eingerichtet:

glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

(Dies ähnelt auch der gerichteten Schattenzuordnung.)

JetztglTexImage2D() wird sechsmal durchlaufen, einmal für jedes Gesicht. Ich mache das so:

 for (int face = 0; face < 6; face++) // Fill each face of the shadow cubemap
     glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_DEPTH_COMPONENT32F , 1024, 1024, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);

Die Textur wird mit einem Aufruf von an den Framebuffer angehängt

glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, shadowmap, 0);

Wenn die Szene gerendert werden soll, wird sie in zwei Durchgängen gerendert, wie bei der gerichteten Schattenzuordnung.

Zunächst wird der Schatten-Framebuffer gebunden, das Ansichtsfenster wird an die Größe der Schattenkarte angepasst (in diesem Fall 1024 mal 1024).Das Keulen wird mit auf die Vorderseite eingestelltglCullFace(GL_FRONT)Das aktive Shader-Programm wird auf die Vertex- und Fragment-Shaders umgeschaltet, die ich später zur Verfügung stellen werde

Die Lichtansichtsmatrizen für alle sechs Ansichten werden berechnet. Dazu erstelle ich einen Vektor aus glm :: mat4's undpush_back() die Matrizen, wie folgt:

// Create the six view matrices for all six sides
for (int i = 0; i < renderedObjects.size(); i++) // Iterate through all rendered objects
{
    renderedObjects[i]->bindBuffers(); // Bind buffers for rendering with it

    glm::mat4 depthModelMatrix = renderedObjects[i]->getModelMatrix(); // Set up model matrix

    for (int i = 0; i < 6; i++) // Draw for each side of the light
    {
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, shadowmap, 0);
        glClear(GL_DEPTH_BUFFER_BIT); // Clear depth buffer

        // Send MVP for shadow map
        glm::mat4 depthMVP = depthProjectionMatrix * depthViewMatrices[i] * depthModelMatrix;
        glUniformMatrix4fv(glGetUniformLocation(shadowMappingProgram, "depthMVP"), 1, GL_FALSE, glm::value_ptr(depthMVP));

        glUniformMatrix4fv(glGetUniformLocation(shadowMappingProgram, "lightViewMatrix"), 1, GL_FALSE, glm::value_ptr(depthViewMatrices[i]));
        glUniformMatrix4fv(glGetUniformLocation(shadowMappingProgram, "lightProjectionMatrix"), 1, GL_FALSE, glm::value_ptr(depthProjectionMatrix));
        glDrawElements(renderedObjects[i]->getDrawType(), renderedObjects[i]->getElementSize(), GL_UNSIGNED_INT, 0);
    }
}

Der Standard-Framebuffer ist gebunden und die Szene wird normal gezeichnet.

Problem

Nun zu den Shadern. Dies ist, wo mein Verständnis trocken läuft. Ich bin völlig unsicher, was ich tun soll, meine Forschung scheint sich zu widersprechen, weil es für verschiedene Versionen ist. Am Ende kopierte und fügte ich Code aus zufälligen Quellen ein und hoffte, dass er etwas anderes als einen schwarzen Bildschirm hervorbringen würde. Ich weiß, dass dies schrecklich ist, aber es scheint keine klaren Definitionen zu geben, was zu tun ist. In welchen Räumen arbeite ich? Benötige ich überhaupt einen separaten Schatten-Shader, wie ich ihn bei direktionaler Punktbeleuchtung verwendet habe? Was zum Teufel verwende ich als Typ für eine Schatten-Cubemap? samplerCube? samplerCubeShadow? Wie kann ich die Cubemap richtig testen? Ich hoffe, dass jemand es für mich klären und eine nette Erklärung liefern kann. Mein derzeitiges Verständnis des Shader-Teils ist: - Wenn die Szene in die Cubemap gerendert wird, nimmt der Vertex-Shader einfach die in meinem C ++ - Code berechnete Depth MVP-Uniform und transformiert die Eingabe-Vertices durch sie. - Der Fragment-Shader des Cubemap-Durchlaufs weist den Single-Out-Wert einfach dem zugl_FragCoord.z. (Dieser Teil hat sich gegenüber der Implementierung der gerichteten Schattenzuordnung nicht geändert. Ich habe angenommen, dass dies beim Cubemapping der Fall ist, da die Shader nicht einmal mit der Cubemap interagieren. OpenGL gibt die Ausgabe einfach auf der Cubemap wieder Bildspeicher, Framebuffer?)

Der Vertex-Shader für das normale Rendering bleibt unverändert.Im Fragment-Shader für das normale Rendern wird die Scheitelpunktposition mit der Projektions- und Ansichtsmatrix des Lichts in den Lichtraum transformiert.Das wird irgendwie in der Cubemap-Textur-Suche verwendet. ???Sobald die Tiefe mit magischen Mitteln ermittelt wurde, wird sie mit der Entfernung des Lichts zum Scheitelpunkt verglichen, ähnlich wie bei der gerichteten Schattenabbildung. Wenn es weniger ist, muss dieser Punkt abgeschattet werden und umgekehrt.

Es ist kein großes Verständnis. Ich verstehe nicht, wie die Scheitelpunkte transformiert und zum Nachschlagen der Cubemap verwendet werden, also füge ich die Quelle für meine Shader ein, in der Hoffnung, dass die Leute dies klären können. Bitte beachten Sie, dass ein Großteil dieses Codes blind kopiert und eingefügt wird. Ich habe nichts geändert, um das Verständnis nicht zu gefährden.

Shadow Vertex Shader:
#version 150

in vec3 position;

uniform mat4 depthMVP;

void main()
{
    gl_Position = depthMVP * vec4(position, 1);
}
Shadow Fragment Shader:
#version 150

out float fragmentDepth;

void main()
{
    fragmentDepth = gl_FragCoord.z;
}
Standard Vertex Shader:
#version 150

in vec3 position;
in vec3 normal;
in vec2 texcoord;

uniform mat3 modelInverseTranspose;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;

out vec3 fragnormal;
out vec3 fragnormaldirection;
out vec2 fragtexcoord;
out vec4 fragposition;
out vec4 fragshadowcoord;

void main()
{
    fragposition = modelMatrix * vec4(position, 1.0);
    fragtexcoord = texcoord;
    fragnormaldirection = normalize(modelInverseTranspose * normal);
    fragnormal = normalize(normal);
    fragshadowcoord = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);


    gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
}
Standard-Fragment-Shader:
#version 150

out vec4 outColour;

in vec3 fragnormaldirection;
in vec2 fragtexcoord;
in vec3 fragnormal;
in vec4 fragposition;
in vec4 fragshadowcoord;

uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrixInversed;

uniform mat4 lightViewMatrix;
uniform mat4 lightProjectionMatrix;

uniform sampler2D tex;
uniform samplerCubeShadow shadowmap;

float VectorToDepthValue(vec3 Vec)
{
    vec3 AbsVec = abs(Vec);
    float LocalZcomp = max(AbsVec.x, max(AbsVec.y, AbsVec.z));

    const float f = 2048.0;
    const float n = 1.0;
    float NormZComp = (f+n) / (f-n) - (2*f*n)/(f-n)/LocalZcomp;
    return (NormZComp + 1.0) * 0.5;
}

float ComputeShadowFactor(samplerCubeShadow ShadowCubeMap, vec3 VertToLightWS)
{   
    float ShadowVec = texture(ShadowCubeMap, vec4(VertToLightWS, 1.0));
    if (ShadowVec + 0.0001 > VectorToDepthValue(VertToLightWS)) // To avoid self shadowing, I guess
        return 1.0;

    return 0.7;
}

void main()
{
    vec3 light_position = vec3(0.0, 0.0, 0.0);
    vec3 VertToLightWS = light_position - fragposition.xyz;
    outColour = texture(tex, fragtexcoord) * ComputeShadowFactor(shadowmap, VertToLightWS);
}

Ich kann mich nicht erinnern, woher der ComputerShadowFactor- und der VectorToDepthValue-Funktionscode stammten, da ich ihn auf meinem Laptop recherchiert habe, auf den ich momentan nicht zugreifen kann, aber das ist das Ergebnis dieser Shader:

Es ist ein kleines Quadrat mit nicht abgeschattetem Raum, das von einem schattigen Raum umgeben ist.

Ich mache hier offensichtlich viel falsch, wahrscheinlich im Hinblick auf meine Shader, weil ich zu wenig über das Thema weiß, weil es mir schwer fällt, von etwas anderem als Tutorials zu lernen, und das tut mir sehr leid. Ich bin ratlos, es wäre wunderbar, wenn jemand eine klare Erklärung darüber abgeben könnte, was ich falsch mache, warum es falsch ist, wie ich es reparieren kann und vielleicht sogar einen Code. Ich denke, das Problem könnte sein, dass ich an den falschen Stellen arbeite.

Antworten auf die Frage(1)

Ihre Antwort auf die Frage