python opengl pygame pyopengl skybox
aquíaquíaquí

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

  • ./images1/ : aquí
  • ./images2/ : aquí
  • ./images3/ : aquí (usando las imágenes de "rayos" en este zip)

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!

  1. 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.
  2. 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!