Pygame + PyOpenGL 中使用不同纹理的天空盒渲染不一致
Inconsistent skybox rendering using different textures in Pygame + PyOpenGL
受我的 incomplete answer to this question, I am implementing a simple skybox in PyOpenGL in accordance with this 教程的启发,根据需要对 OpenGL 2.1/GLSL 120 和 python2.7-isms 进行了细微调整。在大多数情况下,它可以成功运行,但取决于我传递给立方体贴图的六张图像,这些图像最终要么在一对相对的面之间交换,要么随机旋转!下面是这个演示的主要 class:
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))
我使用以下顶点和片段着色器来显示天空盒的立方体贴图:
./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);
}
经过多次尝试,我认为错误出在 pygame 加载天空盒图像时。我测试了三组天空盒图像。每个都有不同的视觉错误和修复它们的技巧,我在上面的代码中已经注意到了这一点。以下是用于测试的三个天空盒的来源(请务必重命名图像,使其包含 right
、left
、top
、bottom
、back
, 或 front
在各自的文件名中)。
这三个天空盒都使用不同的图像格式(分别为 bmp、tga 和 png)。我如何才能在不依赖看似随机的旋转或图像交换的情况下,始终如一地稳健地处理所有这些和未来的图像案例?任何帮助或见解将不胜感激。
更新:
我创建了一个 github repository,您可以在其中测试代码,而无需创建 main.py 和着色器、下载图像以及自己重命名和组织内容。如果您有兴趣对其进行测试,这应该会使代码更容易 运行。
以下是我使用的所有版本:
- python 2.7.12
- pygame 1.9.2b1
- pyopengl 3.1.0(使用 opengl 2.1 和 GLSL 120)
如果您需要任何其他信息,请告诉我!
所以,事实证明,我在渲染天空盒时遇到的所有问题都可以归结为两个原因,none 其中 none 是由于 pygame 加载图像的方式不一致各种文件格式!
- 天空盒图像在立方体两个面之间的接缝连接方式上彼此不一致。这解释了为什么每个天空盒图像测试结果都有不同的问题。按照 this 问题中描述的位于立方体内部的惯例,我翻转并重新保存了绘画中的图像。
然而,仅此还不够。事实证明,OpenGL 在 "cubemap-land" 中对 z 轴的约定是 flipped。这导致正面和背面相互交换。我能想到的最简单的修复方法是在顶点着色器中交换纹理坐标。这是修正后的顶点着色器。
#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);
}
我有问题中提到的 github 中的代码来反映这些变化以及改进相机以手动环顾四周。
这是最终结果的 gif 动画,供所有感兴趣的人使用!
受我的 incomplete answer to this question, I am implementing a simple skybox in PyOpenGL in accordance with this 教程的启发,根据需要对 OpenGL 2.1/GLSL 120 和 python2.7-isms 进行了细微调整。在大多数情况下,它可以成功运行,但取决于我传递给立方体贴图的六张图像,这些图像最终要么在一对相对的面之间交换,要么随机旋转!下面是这个演示的主要 class:
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))
我使用以下顶点和片段着色器来显示天空盒的立方体贴图:
./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);
}
经过多次尝试,我认为错误出在 pygame 加载天空盒图像时。我测试了三组天空盒图像。每个都有不同的视觉错误和修复它们的技巧,我在上面的代码中已经注意到了这一点。以下是用于测试的三个天空盒的来源(请务必重命名图像,使其包含 right
、left
、top
、bottom
、back
, 或 front
在各自的文件名中)。
这三个天空盒都使用不同的图像格式(分别为 bmp、tga 和 png)。我如何才能在不依赖看似随机的旋转或图像交换的情况下,始终如一地稳健地处理所有这些和未来的图像案例?任何帮助或见解将不胜感激。
更新: 我创建了一个 github repository,您可以在其中测试代码,而无需创建 main.py 和着色器、下载图像以及自己重命名和组织内容。如果您有兴趣对其进行测试,这应该会使代码更容易 运行。
以下是我使用的所有版本:
- python 2.7.12
- pygame 1.9.2b1
- pyopengl 3.1.0(使用 opengl 2.1 和 GLSL 120)
如果您需要任何其他信息,请告诉我!
所以,事实证明,我在渲染天空盒时遇到的所有问题都可以归结为两个原因,none 其中 none 是由于 pygame 加载图像的方式不一致各种文件格式!
- 天空盒图像在立方体两个面之间的接缝连接方式上彼此不一致。这解释了为什么每个天空盒图像测试结果都有不同的问题。按照 this 问题中描述的位于立方体内部的惯例,我翻转并重新保存了绘画中的图像。
然而,仅此还不够。事实证明,OpenGL 在 "cubemap-land" 中对 z 轴的约定是 flipped。这导致正面和背面相互交换。我能想到的最简单的修复方法是在顶点着色器中交换纹理坐标。这是修正后的顶点着色器。
#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); }
我有问题中提到的 github 中的代码来反映这些变化以及改进相机以手动环顾四周。
这是最终结果的 gif 动画,供所有感兴趣的人使用!