Implementación de un skybox con GLSL versión 330

14

Estoy tratando de hacer que un skybox funcione con OpenGL 3.3 y GLSL versión 330.

No pude encontrar un tutorial de OGL skybox completamente moderno en ninguna parte de la web, así que modernicé uno más antiguo (usando en glVertexAttribPointer()lugar de gl_Vertexvértices, etc.). Funciona principalmente, pero para 2 detalles principales:

Los skyboxes son más como triángulos de cielo, y las texturas están muy deformadas y estiradas (se supone que son campos de estrellas, obtengo líneas sobre un fondo negro). Estoy 99% seguro de que esto se debe a que no porté los tutoriales antiguos completamente correctamente.

Aquí está mi clase de skybox:

static ShaderProgram* cubeMapShader = nullptr;

static const GLfloat vertices[] = 
{
    1.0f, -1.0f,  1.0f,
    1.0f,  1.0f,  1.0f,
    1.0f,  1.0f, -1.0f,
    -1.0f, -1.0f,  1.0f,
    -1.0f, -1.0f, -1.0f,
    -1.0f,  1.0f, -1.0f,
    -1.0f,  1.0f,  1.0f,
    -1.0f,  1.0f, -1.0f,
    1.0f,  1.0f, -1.0f,
    1.0f,  1.0f,  1.0f,
    -1.0f,  1.0f,  1.0f,
    -1.0f, -1.0f,  1.0f,
    1.0f, -1.0f,  1.0f,
    1.0f, -1.0f, -1.0f,
    -1.0f, -1.0f, -1.0f,
    1.0f, -1.0f,  1.0f,
    -1.0f, -1.0f,  1.0f,
    -1.0f,  1.0f,  1.0f,
    1.0f,  1.0f,  1.0f,
    -1.0f, -1.0f, -1.0f,
    1.0f, -1.0f, -1.0f,
    1.0f,  1.0f, -1.0f,
    -1.0f,  1.0f, -1.0f
};

Skybox::Skybox(const char* xp, const char* xn, const char* yp, const char* yn, const        char* zp, const char* zn)
{
if (cubeMapShader == nullptr)
    cubeMapShader = new ShaderProgram("cubemap.vert", "cubemap.frag");

    texture = SOIL_load_OGL_cubemap(xp, xn, yp, yn, zp, zn, SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_MIPMAPS);

    glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
    glBindTexture(GL_TEXTURE_CUBE_MAP, 0);

    glGenVertexArrays(1, &vaoID);
    glBindVertexArray(vaoID);
    glGenBuffers(1, &vboID);
    glBindBuffer(GL_ARRAY_BUFFER, vboID);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
    glBindVertexArray(0);

    scale = 1.0f;
}

Skybox::~Skybox()
{

}

void Skybox::Render()
{
    ShaderProgram::SetActive(cubeMapShader);
    glDisable(GL_DEPTH_TEST);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
    cubeMapShader->Uniform1i("SkyTexture", 0);
    cubeMapShader->UniformVec3("CameraPosition", Camera::ActiveCameraPosition());
    cubeMapShader->UniformMat4("MVP", 1, GL_FALSE, Camera::GetActiveCamera()->GetProjectionMatrix() * Camera::GetActiveCamera()->GetViewMatrix() * glm::mat4(1.0));
    glBindVertexArray(vaoID);
    glDrawArrays(GL_QUADS, 0, 24);
    glBindVertexArray(0);
    glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
}

Sombreador de vértices:

#version 330 
layout(location = 0) in vec3 Vertex;

uniform vec3 CameraPosition;
uniform mat4 MVP;

out vec3 Position;

void main()
{
    Position = Vertex.xyz;
    gl_Position = MVP * vec4(Vertex.xyz + CameraPosition, 1.0);
}

Fragmento Shader:

#version 330 compatibility

uniform samplerCube SkyTexture;

in vec3 Position;

void main()
{
    gl_FragColor = textureCube(SkyTexture, Position);
}

Aquí hay un ejemplo de los problemas técnicos. Si alguien pudiera ver quién conoce bien GLSL (todavía lo estoy aprendiendo), o skyboxes, agradecería cualquier ayuda que pueda brindar. Además, felicitaciones si me pueden enseñar cómo usar funciones no obsoletas en el fragment shader para que no tenga que usar el perfil de compatibilidad de glsl 330.


EDITAR: Inmediatamente encontré el problema con las texturas de estiramiento: estaba usando en Position = Vertex.xyxlugar de Position = Vertex.xyzen el sombreador de vértices. Ups Pero el error del triángulo todavía existe.

sm81095
fuente
1
Solo necesita 4 vértices (quad de pantalla completa) para representar un skybox con una textura de mapa de cubos. Solo necesita un sombreador de vértices que calcule las coordenadas de textura correctas según la cámara y la proyección.
msell
Podría ser un problema de selección. ¿Has intentado deshabilitar la selección de la cara posterior para intentar ver si obtienes el cuadro completo?
pwny
@pwny, no pensé en eso. Lo intenté y no funcionó, pero puedo ver cómo eso podría haberlo desechado. Gracias por la sugerencia.
sm81095
@msell, he oído hablar de este enfoque, pero no encontré un tutorial en línea para esto, y todavía estoy en el proceso de aprender glsl. Si pudiera proporcionar un ejemplo o un enlace a un ejemplo sobre cómo hacer esto, lo agradecería enormemente.
sm81095

Respuestas:

29

Si bien esta respuesta no dice qué hay de malo en su enfoque, presenta una forma más sencilla de renderizar skyboxes.

Forma tradicional (cubo texturizado)

Una forma sencilla de crear skyboxes es renderizar un cubo texturizado centrado en la posición de la cámara. Cada cara del cubo consta de dos triángulos y una textura 2D (o parte de un atlas). Debido a las coordenadas de textura, cada cara requiere vértices propios. Este enfoque tiene problemas en las costuras de las caras adyacentes, donde los valores de textura no se interpolan correctamente.

Cubo con textura cubemap

Como en la forma tradicional, se representa un cubo texturizado alrededor de la cámara. En lugar de usar seis texturas 2D, se usa una sola textura de mapa de cubos. Debido a que la cámara está centrada dentro del cubo, las coordenadas del vértice se asignan una a una con los vectores de muestreo del mapa de cubos. Por lo tanto, las coordenadas de textura no son necesarias para los datos de malla y los vértices se pueden compartir entre caras mediante el uso de un búfer de índice.

Este enfoque también corrige el problema de las costuras cuando GL_TEXTURE_CUBE_MAP_SEAMLESS está habilitado.

Forma más simple (mejor)

Al renderizar un cubo y la cámara se encuentra dentro de él, se llena toda la ventana gráfica. Hasta cinco caras del skybox pueden ser parcialmente visibles en cualquier momento. Los triángulos de las caras de los cubos se proyectan y recortan en la ventana gráfica y los vectores de muestreo del mapa de cubos se interpolan entre los vértices. Este trabajo es innecesario.

Es posible llenar un solo quad llenando toda la ventana gráfica y calcular los vectores de muestreo del mapa de cubos en las esquinas. Dado que los vectores de muestreo del mapa de cubos coinciden con las coordenadas del vértice, se pueden calcular desproyectando las coordenadas de la ventana gráfica al espacio mundial. Esto es lo contrario de proyectar coordenadas mundiales a la ventana gráfica y puede lograrse invirtiendo las matrices. También asegúrese de deshabilitar la escritura del búfer z o escribir un valor lo suficientemente lejos.

A continuación se muestra el sombreador de vértices que logra esto:

#version 330
uniform mat4 uProjectionMatrix;
uniform mat4 uWorldToCameraMatrix;

in vec4 aPosition;

smooth out vec3 eyeDirection;

void main() {
    mat4 inverseProjection = inverse(uProjectionMatrix);
    mat3 inverseModelview = transpose(mat3(uWorldToCameraMatrix));
    vec3 unprojected = (inverseProjection * aPosition).xyz;
    eyeDirection = inverseModelview * unprojected;

    gl_Position = aPosition;
} 

aPositionson las coordenadas del vértice {-1,-1; 1,-1; 1,1; -1,1}. El sombreador calcula eyeDirectioncon el inverso de la matriz de modelo-vista-proyección. Sin embargo, la inversión se divide para la proyección y las matrices de mundo a cámara. Esto se debe a que solo se debe usar la parte 3x3 de la matriz de la cámara para eliminar la posición de la cámara. Esto alinea la cámara al centro del skybox. Además, como mi cámara no tiene escala ni cizallamiento, la inversión se puede simplificar a la transposición. La inversión de la matriz de proyección es una operación costosa y podría calcularse previamente, pero como este código es ejecutado por el sombreador de vértices, generalmente solo cuatro veces por cuadro, generalmente no es un problema.

El sombreador de fragmentos simplemente realiza una búsqueda de textura usando el eyeDirectionvector:

#version 330
uniform samplerCube uTexture;

smooth in vec3 eyeDirection;

out vec4 fragmentColor;

void main() {
    fragmentColor = texture(uTexture, eyeDirection);
}

Tenga en cuenta que para deshacerse del modo de compatibilidad, debe reemplazar textureCubecon just texturey especificar la variable de salida usted mismo.

msell
fuente
Creo que también debería mencionar que la inversión de la matriz es un proceso costoso, por lo que se realiza mejor en el código del lado del cliente.
akaltar
1
Para los 4 verts de un quad de pantalla completa, no creo que debamos preocuparnos mucho por el costo de la inversión (especialmente porque la GPU que lo hace 4 veces probablemente será más rápido que la CPU que lo hace una vez).
Maximus Minimus
1
Solo una nota útil para la gente, GLSL ES 1.0 (utilizado para GL ES 2.0) no se implementainverse()
Steven Lu
¿Es uWorldToCameraMatrix el MVP del objeto de transformación de la cámara?
Sidar
@Sidar No, es solo la matriz ModelView, la proyección es independiente.
msell