python - Reconstrucción inconfundible del cielo raso utilizando diferentes texturas en Pygame+PyOpenGL
skybox (1)
Motivado por mi respuesta incompleta a esta pregunta, estoy implementando un skybox simple en PyOpenGL de acuerdo con este tutorial, haciendo pequeños ajustes necesarios para OpenGL 2.1 / GLSL 120 y python2.7-isms. En su mayor parte, funciona con éxito, pero según las seis imágenes que paso a mi mapa de cubos, ¡ las imágenes acaban intercambiándose entre un solo par de caras opuestas o se rotan aleatoriamente ! A continuación se muestra la clase principal de esta demostración:
import pygame
import sys
import time
import glob
import numpy as np
from ctypes import *
from OpenGL.GL import *
from OpenGL.GL import shaders
from OpenGL.GLU import *
def load_shaders(vert_url, frag_url):
vert_str = "/n".join(open(vert_url).readlines())
frag_str = "/n".join(open(frag_url).readlines())
vert_shader = shaders.compileShader(vert_str, GL_VERTEX_SHADER)
frag_shader = shaders.compileShader(frag_str, GL_FRAGMENT_SHADER)
program = shaders.compileProgram(vert_shader, frag_shader)
return program
def load_cubemap(folder_url):
tex_id = glGenTextures(1)
face_order = ["right", "left", "top", "bottom", "back", "front"]
"""
#hack that fixes issues for ./images1/
face_order = ["right", "left", "top", "bottom", "front", "back"]
"""
face_urls = sorted(glob.glob(folder_url + "*"))
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_CUBE_MAP, tex_id)
for i, face in enumerate(face_order):
face_url = [face_url for face_url in face_urls if face in face_url.lower()][0]
face_image = pygame.image.load(face_url).convert()
"""
#hack that fixes issues for ./images2/
if face == "bottom":
face_image = pygame.transform.rotate(face_image, 270)
if face == "top":
face_image = pygame.transform.rotate(face_image, 90)
"""
"""
#hack that fixes issues for ./images3/
if face == "bottom" or face == "top":
face_image = pygame.transform.rotate(face_image, 180)
"""
face_surface = pygame.image.tostring(face_image, ''RGB'')
face_width, face_height = face_image.get_size()
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, face_width, face_height, 0, GL_RGB, GL_UNSIGNED_BYTE, face_surface)
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_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)
return tex_id
def render():
global width, height, program
global rotation, cubemap
glEnable(GL_DEPTH_TEST)
glEnable(GL_TEXTURE_2D)
glEnable(GL_TEXTURE_CUBE_MAP)
skybox_right = [1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1]
skybox_left = [-1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1]
skybox_top = [-1, 1, -1, 1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, 1, -1, 1, -1]
skybox_bottom = [-1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1]
skybox_back = [-1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1]
skybox_front = [-1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1]
skybox_vertices = np.array([skybox_right, skybox_left, skybox_top, skybox_bottom, skybox_back, skybox_front], dtype=np.float32).flatten()
skybox_vbo = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, skybox_vbo)
glBufferData(GL_ARRAY_BUFFER, skybox_vertices.nbytes, skybox_vertices, GL_STATIC_DRAW)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glClear(GL_COLOR_BUFFER_BIT)
glClear(GL_DEPTH_BUFFER_BIT)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(60, float(width)/height, 0.1, 1000)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
#glRotate(rotation, 0, 1, 0)#spin around y axis
#glRotate(rotation, 1, 0, 0)#spin around x axis
glRotate(rotation, 1, 1, 1)#rotate around x, y, and z axes
glUseProgram(program)
glDepthMask(GL_FALSE)
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap)
glEnableClientState(GL_VERTEX_ARRAY)
glBindBuffer(GL_ARRAY_BUFFER, skybox_vbo)
glVertexPointer(3, GL_FLOAT, 0, None)
glDrawArrays(GL_TRIANGLES, 0, 36)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glDisableClientState(GL_VERTEX_ARRAY)
glBindTexture(GL_TEXTURE_CUBE_MAP, 0)
glDepthMask(GL_TRUE)
glUseProgram(0)
pygame.display.flip()
if __name__ == "__main__":
title = "Skybox"
target_fps = 60
(width, height) = (800, 600)
flags = pygame.DOUBLEBUF|pygame.OPENGL
screen = pygame.display.set_mode((width, height), flags)
prev_time = time.time()
rotation = 0
cubemap = load_cubemap("./images1/")#front and back images appear swapped
#cubemap = load_cubemap("./images2/")#top and bottom images appear rotated by 90 and 270 degrees respectively
#cubemap = load_cubemap("./images3/")#top and bottom images appear rotated by 180 degrees
program = load_shaders("./shaders/skybox.vert", "./shaders/skybox.frag")
pause = False
while True:
#Handle the events
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
pause = not pause
#Do computations and render stuff on screen
if not pause:
rotation += 1
render()
#Handle timing code for desired FPS
curr_time = time.time()
diff = curr_time - prev_time
delay = max(1.0/target_fps - diff, 0)
time.sleep(delay)
fps = 1.0/(delay + diff)
prev_time = curr_time
pygame.display.set_caption("{0}: {1:.2f}".format(title, fps))
Uso los siguientes sombreadores de vértices y fragmentos para mostrar los mapas de cubo para el cuadro de cielo:
./shaders/skybox.vert
#version 120
varying vec3 tex_coords;
void main()
{
gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;
tex_coords = vec3(gl_Vertex);
}
./shaders/skybox.frag
#version 120
varying vec3 tex_coords;
uniform samplerCube skybox;
void main()
{
gl_FragColor = textureCube(skybox, tex_coords);
}
Creo que después de jugar mucho, el error está en la carga de las imágenes del Skybox de pygame. He probado tres juegos de imágenes de Skybox. Cada uno tiene un error visual diferente y hack para arreglarlos, lo cual he notado en el código anterior. Aquí están las fuentes de los tres palcos para pruebas (asegúrese de cambiar el nombre de las imágenes para que incluyan right
, left
, top
, bottom
, back
o front
en sus respectivos nombres de archivo).
Todos estos tres skybox usan formatos de imagen diferentes (bmp, tga y png, respectivamente). ¿Cómo puedo manejar de manera consistente todos estos casos de imágenes futuras y de manera robusta sin depender de rotaciones aparentemente aleatorias o intercambios de imágenes? Cualquier ayuda o idea sería muy apreciada.
Actualización: he creado un repositorio github donde puedes probar el código sin tener que crear un main.py y sombreadores, descargar las imágenes, cambiar el nombre y organizar los contenidos tú mismo. Esto debería hacer que el código sea mucho más fácil de ejecutar en caso de que esté interesado en probarlo.
Aquí están las versiones de todo lo que estoy usando:
- Python 2.7.12
- pygame 1.9.2b1
- pyopengl 3.1.0 (utilizando OpenGL 2.1 y GLSL 120)
¡Hazme saber si necesitas cualquier otra información!
Entonces, resultó que todos los problemas que estaba teniendo al renderizar el skybox podían reducirse a dos causas, ¡ninguna de las cuales se debía a inconsistencias en cómo el pygame carga imágenes de varios formatos de archivo!
- Las imágenes de la caja del cielo eran inconsistentes entre sí en cómo se unían las costuras entre las dos caras de los cubos. Esto explica por qué cada resultado de la prueba de imagen del palco tiene problemas diferentes. Siguiendo la convención de estar dentro del cubo, describa en esta pregunta, volteé y volví a guardar las imágenes en pintura.
Sin embargo, eso solo no era suficiente. Resulta que la convención OpenGL para el eje z en "cubemap-land" está volteada . Esto causó que las caras delantera y trasera se intercambiaran entre sí. La solución más simple que pude encontrar es cambiar las coordenadas de la textura en el sombreador de vértices. Aquí está el sombreador de vértices corregido.
#version 120 varying vec3 tex_coords; void main() { gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex; tex_coords = vec3(gl_Vertex) * vec3(1, 1, -1); }
Tengo el código en el github mencionado en la pregunta para reflejar estos cambios, así como mejorar la cámara para buscar manualmente.
¡Aquí hay un gif animado del resultado final para cualquiera que esté interesado!