J'essaie de faire fonctionner une skybox avec OpenGL 3.3 et GLSL version 330.
Je n'ai pas pu trouver de tutoriel OGL skybox complètement moderne n'importe où sur le web, j'ai donc modernisé un ancien (en utilisant à la glVertexAttribPointer()
place de gl_Vertex
pour les sommets, etc.). Cela fonctionne surtout, mais pour 2 détails majeurs:
Les skyboxes ressemblent plus à des triangles de ciel, et les textures sont mal déformées et étirées (elles sont censées être des champs d'étoiles, je reçois des lignes sur fond noir). Je suis sûr à 99% que c'est parce que je n'ai pas porté correctement les anciens tutoriels.
Voici ma classe 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);
}
Fragment Shader:
#version 330 compatibility
uniform samplerCube SkyTexture;
in vec3 Position;
void main()
{
gl_FragColor = textureCube(SkyTexture, Position);
}
Voici un exemple des problèmes. Si quelqu'un pouvait jeter un coup d'œil qui connaît bien GLSL (je l'apprends toujours) ou les skyboxes, j'apprécierais toute aide que vous pourriez apporter. Aussi, bravo si vous pouvez m'apprendre à utiliser des fonctions non obsolètes dans le fragment shader afin que je n'aie pas à utiliser le profil de compatibilité de glsl 330.
EDIT: J'ai immédiatement trouvé le problème avec les textures d'étirement: j'utilisais Position = Vertex.xy
x
plutôt que Position = Vertex.xy
z
dans le vertex shader. Oups. Mais l'erreur de triangle existe toujours.
Réponses:
Bien que cette réponse ne dise pas ce qui ne va pas avec votre approche, elle présente un moyen plus simple de rendre les skyboxes.
Façon traditionnelle (cube texturé)
Un moyen simple de créer des skyboxes est de rendre un cube texturé centré sur la position de la caméra. Chaque face du cube se compose de deux triangles et d'une texture 2D (ou d'une partie d'un atlas). En raison des coordonnées de texture, chaque face nécessite ses propres sommets. Cette approche a des problèmes dans les coutures des faces adjacentes, où les valeurs de texture ne sont pas interpolées correctement.
Cube avec texture cubemap
Comme à la manière traditionnelle, un cube texturé est rendu autour de la caméra. Au lieu d'utiliser six textures 2D, une seule texture cubemap est utilisée. Étant donné que la caméra est centrée à l'intérieur du cube, les coordonnées des sommets sont mappées une à une avec les vecteurs d'échantillonnage de la carte des cubes. Ainsi, les coordonnées de texture ne sont pas nécessaires pour les données de maillage et les sommets peuvent être partagés entre les faces à l'aide du tampon d'index.
Cette approche résout également le problème des coutures lorsque GL_TEXTURE_CUBE_MAP_SEAMLESS est activé.
Une manière plus simple (meilleure)
Lors du rendu d'un cube et de la caméra à l'intérieur, la fenêtre entière est remplie. Jusqu'à cinq faces de la skybox peuvent être partiellement visibles à tout moment. Les triangles des faces du cube sont projetés et découpés dans la fenêtre et les vecteurs d'échantillonnage de la carte du cube sont interpolés entre les sommets. Ce travail est inutile.
Il est possible de remplir un seul quadrilatère remplissant la fenêtre entière et de calculer les vecteurs d'échantillonnage cubemap dans les coins. Étant donné que les vecteurs d'échantillonnage de la carte cubique correspondent aux coordonnées des sommets, ils peuvent être calculés en supprimant la projection des coordonnées de la fenêtre dans l'espace mondial. Ceci est l'opposé de la projection des coordonnées du monde vers la fenêtre d'affichage et peut être obtenu en inversant les matrices. Assurez-vous également de désactiver l'écriture dans le tampon z ou d'écrire une valeur suffisamment éloignée.
Voici le vertex shader qui accomplit cela:
aPosition
est les coordonnées du sommet{-1,-1; 1,-1; 1,1; -1,1}
. Le shader calculeeyeDirection
avec l'inverse de la matrice modèle-vue-projection. Cependant, l'inversion est divisée pour les matrices de projection et du monde vers la caméra. En effet, seule la partie 3x3 de la matrice de la caméra doit être utilisée pour éliminer la position de la caméra. Cela aligne la caméra sur le centre de la skybox. De plus, comme mon appareil photo n'a pas d'échelle ni de cisaillement, l'inversion peut être simplifiée pour la transposition. L'inversion de la matrice de projection est une opération coûteuse et pourrait être précalculée, mais comme ce code est exécuté par le vertex shader généralement seulement quatre fois par image, ce n'est généralement pas un problème.Le shader de fragment effectue simplement une recherche de texture en utilisant le
eyeDirection
vecteur:Notez que pour vous débarrasser du mode de compatibilité, vous devez remplacer
textureCube
par justetexture
et spécifier vous-même la variable de sortie.la source
inverse()