Estou tentando fazer com que um skybox funcione com o OpenGL 3.3 e GLSL versão 330.
Não consegui encontrar um tutorial do OGL skybox completamente moderno em nenhum lugar da Web; portanto, modernizei um mais antigo (usando em glVertexAttribPointer()
vez de gl_Vertex
vértices etc.). Está funcionando principalmente, mas com dois detalhes principais:
As caixas de céu são mais parecidas com triângulos do céu, e as texturas são muito distorcidas e esticadas (deveriam ser campos de estrelas, fico com linhas em fundo preto). Tenho 99% de certeza de que isso ocorre porque eu não mudei os tutoriais antigos completamente corretamente.
Aqui está a minha aula do 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);
}
Vertex Shader:
#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);
}
Shader do fragmento:
#version 330 compatibility
uniform samplerCube SkyTexture;
in vec3 Position;
void main()
{
gl_FragColor = textureCube(SkyTexture, Position);
}
Aqui está um exemplo das falhas. Se alguém puder dar uma olhada que conhece bem o GLSL (ainda estou aprendendo) ou caixas de sky, agradeceria qualquer ajuda que você pudesse dar. Além disso, parabéns, se você puder me ensinar como usar funções não obsoletas no shader de fragmentos, para que eu não precise usar o perfil de compatibilidade do glsl 330.
Edição: Encontrou imediatamente o problema com as texturas de alongamento: eu estava usando em Position = Vertex.xy
x
vez de Position = Vertex.xy
z
no shader de vértice. Opa Mas o erro do triângulo ainda existe.
Respostas:
Embora essa resposta não diga o que há de errado com sua abordagem, ela apresenta uma maneira mais simples de renderizar caixas de correio.
Maneira tradicional (cubo texturizado)
Uma maneira simples de criar skyboxes é renderizar um cubo texturizado centralizado na posição da câmera. Cada face do cubo consiste em dois triângulos e uma textura 2D (ou parte de um atlas). Devido às coordenadas da textura, cada face requer vértices próprios. Essa abordagem apresenta problemas nas costuras das faces adjacentes, onde os valores da textura não são interpolados adequadamente.
Cubo com textura de mapa de cubo
Como na maneira tradicional, um cubo texturizado é renderizado em torno da câmera. Em vez de usar seis texturas 2D, é usada uma única textura de mapa de cubo. Como a câmera está centralizada dentro do cubo, as coordenadas do vértice são mapeadas uma a uma com os vetores de amostragem do mapa do cubo. Portanto, as coordenadas de textura não são necessárias para os dados da malha e os vértices podem ser compartilhados entre as faces usando o buffer de índice.
Essa abordagem também corrige o problema de costuras quando GL_TEXTURE_CUBE_MAP_SEAMLESS está ativado.
Maneira mais simples (melhor)
Ao renderizar um cubo e a câmera ficar dentro dele, toda a janela de exibição é preenchida. Até cinco faces da caixa do céu podem ser parcialmente visíveis a qualquer momento. Os triângulos das faces do cubo são projetados e cortados na viewport e os vetores de amostragem do mapa do cubo são interpolados entre os vértices. Este trabalho é desnecessário.
É possível preencher um único quad preenchendo toda a viewport e calcular os vetores de amostragem do mapa do cubo nos cantos. Como os vetores de amostragem do mapa do cubo correspondem às coordenadas do vértice, eles podem ser calculados desprojetando as coordenadas da viewport para o espaço do mundo. É o oposto de projetar coordenadas mundiais para a viewport e pode ser conseguido invertendo as matrizes. Além disso, certifique-se de desabilitar a gravação do buffer z ou gravar um valor suficientemente longe.
Abaixo está o vertex shader que realiza isso:
aPosition
são as coordenadas do vértice{-1,-1; 1,-1; 1,1; -1,1}
. O sombreador calculaeyeDirection
com o inverso da matriz modelo-vista-projeção. No entanto, a inversão é dividida para matrizes de projeção e do mundo para a câmera. Isso ocorre porque apenas a parte 3x3 da matriz da câmera deve ser usada para eliminar a posição da câmera. Isso alinha a câmera ao centro da caixa do céu. Além disso, como minha câmera não possui redimensionamento ou cisalhamento, a inversão pode ser simplificada para transposição. A inversão da matriz de projeção é uma operação dispendiosa e pode ser pré-calculada, mas como esse código é executado pelo shader de vértice normalmente apenas quatro vezes por quadro, geralmente não é um problema.O shader de fragmento simplesmente executa uma pesquisa de textura usando o
eyeDirection
vetor:Observe que, para se livrar do modo de compatibilidade, você precisa substituir
textureCube
por justtexture
e especificar a variável de saída por conta própria.fonte
inverse()