Inconsistent skybox rendering using different text

2019-09-12 02:46发布

Motivated by my incomplete answer to this question, I am implementing a simple skybox in PyOpenGL in accordance with this tutorial, making minor tweaks as needed for OpenGL 2.1/GLSL 120 and python2.7-isms. For the most part, it works successfully, but depending on what six images I pass to my cubemap, the images either end up swapped between a single pair of opposite faces or are randomly rotated! Below is the main class of this demo:

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

I use the following vertex and fragment shaders for displaying the cubemaps for the skybox:

./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);
}

I believe after much playing around that the error is in pygame's loading of the skybox images. I have tested three sets of skybox images. Each one has a different visual error and hack to fix them, which I have noted in the above code. Here are the sources for the three skyboxes for testing (be sure to rename the images so that they include right, left, top, bottom, back, or front in their respective file names).

  • ./images1/: here
  • ./images2/: here
  • ./images3/: here (using the "rays" images in this zip)

All of these three skyboxes use different image formats (bmp, tga, and png respectively). How can I consistently handle all of these and future image cases robustly without relying on seemingly random rotations or image swaps? Any help or insight would be greatly appreciated.

Update: I have created a github repository where you can test out the code without having to create a main.py and shaders, download the images, and rename and organize the contents yourself. This should make the code a lot easier to run in case you are interested in testing it out.

Here are the versions of everything that I am using:

  • python 2.7.12
  • pygame 1.9.2b1
  • pyopengl 3.1.0 (using opengl 2.1 and GLSL 120)

Let me know if you need any other information!

1条回答
家丑人穷心不美
2楼-- · 2019-09-12 03:12

So, it turns out that all of the issues that I was having rendering the skybox could be boiled down to two causes, none of which were due to inconsistencies in how pygame loads images of various file formats!

  1. The skybox images were inconsistent with one another in how the seams between two faces of the cubes were attached. This explained why each skybox image test result had different issues. Following the convention of being inside the cube describe in this question, I flipped and resaved the images in paint.
  2. That alone was not enough, however. It turns out that the OpenGL convention for the z-axis in "cubemap-land" is flipped. This caused the front and back faces to be swapped with one another. The simplest fix that I could come up with is swapping the texture coordinates in the vertex shader. Here is the corrected vertex shader.

    #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);
    }
    

I have the code in the github mentioned in the question to reflect these changes as well as improve the camera for manually looking around.

Here is an animated gif of the final result for anyone who is interested!

Animated GIF image

查看更多
登录 后发表回答