c++ opengl 3d shader shadow-mapping

c++ - ¿Punteros en OpenGL sombra cubemapping?



3d shader (1)

Fondo

Estoy trabajando en un juego en 3D usando C ++ y OpenGL moderno (3.3). Ahora estoy trabajando en la iluminación y la representación de sombras, y he implementado con éxito el mapeo de sombras direccional. Después de leer los requisitos para el juego, decidí que iba a necesitar un mapa de sombra de luz de puntos. Después de investigar un poco, descubrí que para hacer un mapeo de sombras omnidireccional haré algo similar al mapeo de sombras direccional, pero con un mapa de cubos.

No tengo conocimiento previo de cubemaps, pero mi comprensión de ellos es que un mapa de cubos son seis texturas, que se adjuntan perfectamente. Miré a mi alrededor pero desafortunadamente luché por encontrar un "tutorial" definitivo sobre el tema para OpenGL moderno. Primero busco tutoriales que lo expliquen de principio a fin porque me costó mucho aprender de fragmentos de código fuente o solo de conceptos, pero lo intenté.

Entendimientos actuales

Aquí está mi comprensión general de la idea, menos los tecnicismos. Por favor corrigeme.

  • Para cada punto de luz, se configura un framebuffer, como un mapa de sombras direccional
  • Luego se genera una única textura de mapa de cubos y se vincula con glBindTexture(GL_TEXTURE_CUBE_MAP, shadowmap) .
  • El mapa de cubos está configurado con los siguientes atributos:

    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);

(Esto también es similar a un mapa de sombras direccional)

  • Ahora glTexImage2D() se glTexImage2D() seis veces, una para cada cara. Lo hago así:

    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);

  • La textura se adjunta al framebuffer con una llamada a

    glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, shadowmap, 0);

  • Cuando la escena se va a representar, se procesa en dos pasos, como el mapeo de sombras direccionales.

  • En primer lugar, el marcador de sombra se vincula, la ventana gráfica se ajusta al tamaño del mapa de sombras (1024 por 1024 en este caso).
  • El sacrificio se establece en las caras frontales con glCullFace(GL_FRONT)
  • El programa de sombreado activo se cambia a los sombreadores de sombra de vértice y fragmento del que proporcionaré las fuentes de más abajo
  • Se calculan las matrices de vista de luz para las seis vistas. Lo hago creando un vector de las matrices glm :: mat4''s y push_back() , como esto:

    // 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); } }

  • El framebuffer predeterminado está enlazado y la escena se dibuja normalmente.

Problema

Ahora, a los shaders. Aquí es donde mi entendimiento se seca. No estoy completamente seguro de lo que debo hacer, mi investigación parece entrar en conflicto entre sí, porque es para diferentes versiones. Terminé suavemente copiando y pegando código de fuentes aleatorias, y esperando que lograra algo más que una pantalla negra. Sé que esto es terrible, pero no parece haber definiciones claras sobre qué hacer. ¿En qué espacios trabajo? ¿Necesito un sombreador de sombras por separado, como el que usé en la iluminación de puntos direccionales? ¿Qué demonios utilizo como tipo para un mapa de cubos de sombras? samplerCube? samplerCubeShadow? ¿Cómo muestro correctamente dicho cubemap? Espero que alguien pueda aclararlo y brindarme una buena explicación. Mi comprensión actual de la parte del sombreador es: - Cuando la escena se representa en el mapa de cubos, el sombreado de vértice simplemente toma el uniforme de depthMVP que calculé en mi código C ++ y transforma los vértices de entrada por ellos. - El fragmento de sombreado del pase cubemap simplemente asigna el valor de salida única a gl_FragCoord.z . (Esta parte no ha cambiado desde cuando implementé el mapeo de sombras direccional. Supuse que sería lo mismo para el mapa de cubos porque los sombreadores ni siquiera interactúan con el mapa de cubos: OpenGL simplemente presenta la salida de ellos al mapa de cubos, ¿verdad? Porque es un framebuffer?)

  • El sombreado de vértices para la reproducción normal no se modifica.
  • En el fragmento de sombreado para la reproducción normal, la posición del vértice se transforma en el espacio de la luz con la proyección de la luz y la matriz de vista.
  • De alguna manera se utiliza en la búsqueda de texturas de mapa de página. ???
  • Una vez que se ha recuperado la profundidad utilizando medios mágicos, se compara con la distancia de la luz al vértice, de forma muy similar a la asignación de sombras direccional. Si es menos, ese punto debe estar sombreado, y viceversa.

No es mucho de un entendimiento. Me quedo en blanco en cuanto a cómo se transforman los vértices y cómo se usan para buscar el mapa de cubos, así que voy a pegar la fuente de mis sombreadores, con la esperanza de que la gente pueda aclarar esto. Tenga en cuenta que gran parte de este código es copiar y pegar a ciegas, no he alterado nada para no poner en peligro ningún entendimiento.

Sombreador de vértice de sombra:

#version 150 in vec3 position; uniform mat4 depthMVP; void main() { gl_Position = depthMVP * vec4(position, 1); }

Sombreador de fragmento de sombra:

#version 150 out float fragmentDepth; void main() { fragmentDepth = gl_FragCoord.z; }

Sombreador de vértices estándar:

#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); }

Sombreador de fragmentos estándar:

#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); }

No puedo recordar de dónde provino el código de función ComputerShadowFactor y VectorToDepthValue, porque lo estaba investigando en mi computadora portátil, que no puedo encontrar en este momento, pero este es el resultado de esos shaders:

Es un pequeño cuadrado de espacio no sombreado rodeado de espacios sombreados.

Obviamente me estoy equivocando mucho aquí, probablemente centrado en mis sombreadores, debido a la falta de conocimiento sobre el tema porque me resulta difícil aprender de cualquier cosa que no sean tutoriales, y lo siento mucho. Estoy en una pérdida, sería maravilloso si alguien pudiera aclarar esto con una explicación clara sobre lo que estoy haciendo mal, por qué está mal, cómo puedo solucionarlo y quizás incluso algún código. Creo que el problema puede ser porque estoy trabajando en espacios equivocados.


Espero dar una respuesta a algunas de sus preguntas, pero primero se requieren algunas definiciones:

¿Qué es un cubemap?

Es un mapa de un vector de dirección a un par de [cara, 2d coordenadas en esa cara], obtenido al proyectar el vector de dirección en un cubo hipotético.

¿Qué es una textura de mapa de cubos OpenGL?

Es un conjunto de seis "imágenes".

¿Qué es un muestreador de mapa de mapas GLSL?

Es una muestra primitiva a partir de la cual se puede realizar un muestreo de mapa de cubos. Esto significa que se muestrea utilizando un vector de dirección en lugar de las coordenadas de textura habituales. El hardware luego proyecta el vector de dirección en un cubo hipotético y usa el par resultante [cara, coordenada de textura 2d] para muestrear la "imagen" correcta en la posición 2d derecha.

¿Qué es un muestreador de sombras GLSL?

Es una primitiva de muestra que está delimitada a una textura que contiene valores de profundidad del espacio NDC y, cuando se muestrea utilizando las funciones de muestreo específicas de la sombra, devuelve una "comparación" entre la profundidad del espacio NDC (en el mismo espacio del mapa de la sombra, obviamente) y la profundidad del espacio NDC almacenada dentro de la textura acotada. La profundidad para comparar contra se especifica como un elemento adicional en las coordenadas de textura al llamar a la función de muestreo. Tenga en cuenta que los muestreadores de sombras se proporcionan para facilitar el uso y la velocidad, pero siempre es posible hacer la comparación "manualmente" en el sombreador.

Ahora, para tus preguntas:

OpenGL simplemente se [...] traduce al mapa de cubos, ¿verdad?

No, OpenGL se procesa en un conjunto de objetivos en el framebuffer delimitado actualmente.

En el caso de cubemaps, la forma habitual de renderizar en ellos es:

  • para crearlos y adjuntar cada una de sus seis "imágenes" al mismo framebuffer ( en diferentes puntos de conexión, obviamente )
  • para habilitar solo uno de los objetivos a la vez ( por lo tanto, renderiza en cada cara de cubemap individualmente )
  • para representar lo que desea en la cara de mapa de cubos ( posiblemente utilizando matrices de "vista" y "proyección" específicas de cara )

Mapas de sombras de luz puntual

Además de todo lo que se dice acerca de los cubemaps, hay una serie de problemas al usarlos para implementar el mapeo de sombras en la luz puntual, por lo que la comparación de la profundidad del hardware rara vez se usa.

En cambio, lo que es una práctica común es la siguiente:

  • en lugar de escribir la profundidad del espacio NDC, escriba la distancia radial desde el punto de luz
  • al consultar el mapa de la sombra ( ver código de ejemplo en la parte inferior ):
    • no utilice comparaciones de profundidad de hardware (use samplerCube en lugar de samplerCubeShadow)
    • transformar el punto a probar en el "espacio del cubo" (que no incluye proyección en absoluto)
    • use el vector "espacio-cubo" como dirección de búsqueda para muestrear el mapa de cubos
    • compare la distancia radial muestreada desde el mapa de cubos con la distancia radial del punto probado

Código de muestra

// sample radial distance from the cubemap float radial_dist = texture(my_cubemap, cube_space_vector).x; // compare against test point radial distance bool shadowed = length(cube_space_vector) > radial_dist;