PyOpenGL 透视投影

Posted

技术标签:

【中文标题】PyOpenGL 透视投影【英文标题】:PyOpenGL Perspective Projection 【发布时间】:2017-08-04 19:40:29 【问题描述】:

我对 PyOpenGL 比较陌生,并开始学习转换和模型、视图和投影矩阵。这很好,直到我编译并运行我的代码,发现我的对象突然消失了,老实说,我不知道 OpenGL 是否正在剪切我的对象,或者只是因为某些相机错误而没有显示它,但我相信它有一些东西与在我的代码中实现的投影矩阵有关,因为当我从我的代码中删除投影矩阵并运行我的程序时,一切突然又恢复了工作,除了我没有实现透视投影的事实。无论如何,任何人都会非常感激:D

这是我的 PyOpenGL 代码。

import OpenGL, PIL, pygame, numpy, pyrr, math, sys, os

from OpenGL.GL import *
from PIL import Image
from pyrr import Matrix44, Vector4, Vector3, Quaternion

VERT_DATA = numpy.array([0.5, 0.5, 0.0,
                         0.5, -0.5, 0.0,
                        -0.5, -0.5, 0.0,
                        -0.5, 0.5, 0.0],
                        dtype="float32")

COLOR_DATA = numpy.array([1.0, 0.0, 0.0, 1.0,
                          0.0, 1.0, 0.0, 1.0,
                          0.0, 0.0, 1.0, 1.0,
                          0.0, 1.0, 1.0, 1.0],
                          dtype="float32")

TEXTURE_COORD_DATA = numpy.array([0.5, 0.5,
                                  0.5, -0.5,
                                 -0.5, -0.5,
                                 -0.5, 0.5],
                                 dtype="float32")

INDICES = numpy.array([0, 1, 3,
                       1, 2, 3],
                       dtype="int32")

class GLProgram:
    def __init__(self):
        self.gl_program = glCreateProgram()
        self.mvp_matrix = self.projection()
        self.shaders()
        self.gl_buffers()

    def gl_texture(self, texture_path):
        image = Image.open(texture_path).transpose(Image.FLIP_TOP_BOTTOM)
        image_data = numpy.fromstring(image.tobytes(), numpy.uint8)
        width, height = image.size

        texture = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D, texture)

        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)

        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image_data)
        glGenerateMipmap(GL_TEXTURE_2D)

        return texture

    def gl_buffers(self):
        self.vao = glGenVertexArrays(1)
        glBindVertexArray(self.vao)

        self.pos_vbo = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, self.pos_vbo)
        glBufferData(GL_ARRAY_BUFFER, VERT_DATA, GL_STATIC_DRAW)
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None)
        glEnableVertexAttribArray(0)

        self.text_coord_vbo = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, self.text_coord_vbo)
        glBufferData(GL_ARRAY_BUFFER, TEXTURE_COORD_DATA, GL_STATIC_DRAW)
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, None)
        glEnableVertexAttribArray(1)

        self.pos_ebo = glGenBuffers(1)
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.pos_ebo)
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.pos_ebo)
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDICES, GL_STATIC_DRAW)

        self.brick_texture = self.gl_texture("check.jpg")

    def shaders(self):
        vertex_shader = glCreateShader(GL_VERTEX_SHADER)
        fragment_shader = glCreateShader(GL_FRAGMENT_SHADER)

        with open("VertexShader.vert", "r") as vert_file:
            vert_source = vert_file.read()
        with open("FragmentShader.frag", "r") as frag_file:
            frag_source = frag_file.read()

        glShaderSource(vertex_shader, vert_source)
        glShaderSource(fragment_shader, frag_source)

        glCompileShader(vertex_shader)
        if not glGetShaderiv(vertex_shader, GL_COMPILE_STATUS):
            info_log = glGetShaderInfoLog(vertex_shader)
            print ("Compilation Failure for " + vertex_shader + " shader:\n" + info_log)

        glCompileShader(fragment_shader)
        if not glGetShaderiv(fragment_shader, GL_COMPILE_STATUS):
            info_log = glGetShaderInfoLog(fragment_shader)
            print ("Compilation Failure for " + fragment_shader + " shader:\n" + info_log)

        glAttachShader(self.gl_program, vertex_shader)
        glAttachShader(self.gl_program, fragment_shader)

        glLinkProgram(self.gl_program)

        glDeleteShader(vertex_shader)
        glDeleteShader(fragment_shader)

    def projection(self):
        scale_matrix = pyrr.matrix44.create_from_scale(Vector3([1, 1, 1]))
        rot_matrix = Matrix44.identity()
        trans_matrix = pyrr.matrix44.create_from_translation(Vector3([1, 1, 0]))

        model_matrix = scale_matrix * rot_matrix * trans_matrix
        view_matrix = pyrr.matrix44.create_look_at(numpy.array([4, 3, 3]), numpy.array([1, 1, 0]), numpy.array([0, 1, 0]))
        proj_matrix = pyrr.matrix44.create_perspective_projection_matrix(45.0, 1280/720, 0.1, 1000.0)
        mvp_matrix = proj_matrix * view_matrix * model_matrix

        return mvp_matrix

    def display(self):
        glEnable(GL_DEPTH_TEST)

        glClearColor(0.0, 0.0, 0.0, 1.0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        glUseProgram(self.gl_program)
        glActiveTexture(GL_TEXTURE0)
        glBindTexture(GL_TEXTURE_2D, self.brick_texture)
        texture_uniform = glGetUniformLocation(self.gl_program, "the_texture")
        glUniform1i(texture_uniform, 0)

        trans_uniform = glGetUniformLocation(self.gl_program, "mvp")
        glUniformMatrix4fv(trans_uniform, 1, GL_FALSE, self.mvp_matrix)

        glBindVertexArray(self.vao)

        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, None)
        glUseProgram(0)

def main():
    pygame.init()
    os.environ['SDL_VIDEO_CENTERED'] = '1'
    pygame.display.set_caption("3D Graphics")
    pygame.display.set_mode((1280, 720), pygame.DOUBLEBUF | pygame.OPENGL)
    clock = pygame.time.Clock()
    gl = GLProgram()

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

        clock.tick(60)
        gl.display()
        pygame.display.flip()

if __name__ == "__main__":
    main()

顶点着色器:

#version 330 core

layout (location = 0) in vec3 position;
layout (location = 1) in vec2 text_coord;

out vec2 final_text_coord;

uniform mat4 mvp;

void main() 
    gl_Position = mvp * vec4(position, 1.0);
    final_text_coord = text_coord;

片段着色器:

#version 330 core

in vec2 final_text_coord;

out vec4 frag_color;

uniform sampler2D the_texture;

void main() 
    frag_color = texture(the_texture, final_text_coord);

【问题讨论】:

pyrr 文档说“矩阵以行优先格式排列”,但您使用它们时就好像它们以列优先格式存储一样。 我理解你对矩阵布局的看法,但是当你说“你使用它们就像它们作为列主要存储一样”时你迷失了,请你详细说明一下。 如果 A 是 row-major 并且 B 是相同的矩阵但采用 col-major 格式,则 A = transpose(B)。顺序或矩阵乘法也必须反转。 @18TristonLehmann:您使用glUniformMatrix4fv(..., GL_FALSE) 发送矩阵,按照 GL 的约定,这意味着它需要 16 个浮点数,其中前四个将描述该矩阵的第一列。您也不会通过翻转着色器中的乘法顺序来隐式转置该矩阵,因此您实际应用于这些向量的矩阵将转置为您应该使用的矩阵。 好的,我想我已经成功了,我会尽快发布答案。太感谢了!!!! 【参考方案1】:

您的代码存在几个问题:

    矩阵乘法。将两个二维 numpy 数组相乘会产生一个分量乘积,而不是矩阵乘法。使用numpy.matmul 可以解决这个问题。

    矩阵约定不明确。

    正如pyrr 的文档所述,矩阵在内存中按行优先排列,这与 GL 的默认约定相反。此外,pyrr 创建了转换为标准 GL 约定的矩阵。有人可能认为这两件事都会自行抵消,这只是在一定程度上是正确的。每当您对这些矩阵实际执行一些其他操作(例如乘法)时,它就会中断,然后它将使用本机约定,并搞砸了。

我确实修改了您的代码,提供了两种不同的解决方案。由于您在问题和答案之间稍微更改了代码,因此我最终得到了两个版本的狂野组合。我还禁用了纹理的东西,因为我缺少一些库(和图像文件)。我相应地修改了片段着色器,但这并不重要。

import OpenGL, PIL, pygame, numpy, pyrr, math, sys, os

from OpenGL.GL import *
from PIL import Image
from pyrr import Matrix44, Vector4, Vector3, Quaternion

VERT_DATA = numpy.array([0.5, 0.5, 0.0,
                         0.5, -0.5, 0.0,
                        -0.5, -0.5, 0.0,
                        -0.5, 0.5, 0.0],
                        dtype="float32")

COLOR_DATA = numpy.array([1.0, 0.0, 0.0, 1.0,
                          0.0, 1.0, 0.0, 1.0,
                          0.0, 0.0, 1.0, 1.0,
                          0.0, 1.0, 1.0, 1.0],
                          dtype="float32")

TEXTURE_COORD_DATA = numpy.array([0.5, 0.5,
                                  0.5, -0.5,
                                 -0.5, -0.5,
                                 -0.5, 0.5],
                                 dtype="float32")

INDICES = numpy.array([0, 1, 3,
                       1, 2, 3],
                       dtype="int32")

WINDOW_WIDTH=1280
WINDOW_HEIGHT=720

class GLProgram:
    def __init__(self):
        self.gl_program = glCreateProgram()
        self.mvp_matrix = self.projection()
        self.shaders()
        self.gl_buffers()
        self.cube_model_matrix, self.cube_view_matrix, self.cube_proj_matrix = self.gl_translate(Vector3([1.0, 1.0, 1.0]), 45.0, Vector3([0.5, 0.5, 0.5]))
        self.cube_mvp = self.gl_translate3(Vector3([1.0, 1.0, 1.0]), -45.0, Vector3([0.5, 0.5, 0.5]))

    def gl_texture(self, texture_path):
        return 0

    def gl_buffers(self):
        self.vao = glGenVertexArrays(1)
        glBindVertexArray(self.vao)

        self.pos_vbo = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, self.pos_vbo)
        glBufferData(GL_ARRAY_BUFFER, VERT_DATA, GL_STATIC_DRAW)
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None)
        glEnableVertexAttribArray(0)

        self.text_coord_vbo = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, self.text_coord_vbo)
        glBufferData(GL_ARRAY_BUFFER, TEXTURE_COORD_DATA, GL_STATIC_DRAW)
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, None)
        glEnableVertexAttribArray(1)

        self.pos_ebo = glGenBuffers(1)
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.pos_ebo)
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.pos_ebo)
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDICES, GL_STATIC_DRAW)

        self.brick_texture = self.gl_texture("check.jpg")

    def shaders(self):
        vertex_shader = glCreateShader(GL_VERTEX_SHADER)
        fragment_shader = glCreateShader(GL_FRAGMENT_SHADER)

        with open("VertexShader.vert", "r") as vert_file:
            vert_source = vert_file.read()
        with open("FragmentShader.frag", "r") as frag_file:
            frag_source = frag_file.read()

        glShaderSource(vertex_shader, vert_source)
        glShaderSource(fragment_shader, frag_source)

        glCompileShader(vertex_shader)
        if not glGetShaderiv(vertex_shader, GL_COMPILE_STATUS):
            info_log = glGetShaderInfoLog(vertex_shader)
            print ("Compilation Failure for " + vertex_shader + " shader:\n" + info_log)

        glCompileShader(fragment_shader)
        if not glGetShaderiv(fragment_shader, GL_COMPILE_STATUS):
            info_log = glGetShaderInfoLog(fragment_shader)
            print ("Compilation Failure for " + fragment_shader + " shader:\n" + info_log)

        glAttachShader(self.gl_program, vertex_shader)
        glAttachShader(self.gl_program, fragment_shader)

        glLinkProgram(self.gl_program)

        glDeleteShader(vertex_shader)
        glDeleteShader(fragment_shader)

    def projection(self):
        scale_matrix = pyrr.matrix44.create_from_scale(Vector3([1, 1, 1]))
        rot_matrix = Matrix44.identity()
        trans_matrix = pyrr.matrix44.create_from_translation(Vector3([1, 1, 0]))

        model_matrix = scale_matrix * rot_matrix * trans_matrix
        view_matrix = pyrr.matrix44.create_look_at(numpy.array([4, 3, 3]), numpy.array([1, 1, 0]), numpy.array([0, 1, 0]))
        proj_matrix = pyrr.matrix44.create_perspective_projection_matrix(45.0, 1280/720, 0.1, 1000.0)
        mvp_matrix = proj_matrix * view_matrix * model_matrix

        return mvp_matrix
    def gl_translate(self, translation, rotation, scale):
        trans_matrix = pyrr.matrix44.create_from_translation(translation)
        rot_matrix = numpy.transpose(pyrr.matrix44.create_from_y_rotation(rotation))
        scale_matrix = numpy.transpose(pyrr.matrix44.create_from_scale(scale))

        model_matrix = scale_matrix * rot_matrix * trans_matrix
        view_matrix = pyrr.matrix44.create_look_at(numpy.array([2.0, 2.0, 3.0], dtype="float32"),
            numpy.array([0.0, 0.0, 0.0], dtype="float32"),
            numpy.array([0.0, 1.0, 0.0], dtype="float32"))
        proj_matrix = pyrr.matrix44.create_perspective_projection(45.0, WINDOW_WIDTH/WINDOW_HEIGHT, 0.1, 200.0)

        return model_matrix, view_matrix, proj_matrix

    def gl_translate2(self, translation, rotation, scale):
        trans_matrix = pyrr.matrix44.create_from_translation(translation)
        rot_matrix = pyrr.matrix44.create_from_y_rotation(rotation)
        scale_matrix = pyrr.matrix44.create_from_scale(scale)

        model_matrix = numpy.matmul(numpy.matmul(scale_matrix,rot_matrix),trans_matrix)
        view_matrix = pyrr.matrix44.create_look_at(numpy.array([2.0, 2.0, 3.0], dtype="float32"),
            numpy.array([0.0, 0.0, 0.0], dtype="float32"),
            numpy.array([0.0, 1.0, 0.0], dtype="float32"))
        proj_matrix = pyrr.matrix44.create_perspective_projection(45.0, WINDOW_WIDTH/WINDOW_HEIGHT, 0.1, 200.0)
        m = numpy.matmul(numpy.matmul(model_matrix,view_matrix),proj_matrix) 

        return m
    def gl_translate3(self, translation, rotation, scale):
        trans_matrix = numpy.transpose(pyrr.matrix44.create_from_translation(translation))
        rot_matrix = numpy.transpose(pyrr.matrix44.create_from_y_rotation(rotation))
        scale_matrix = numpy.transpose(pyrr.matrix44.create_from_scale(scale))

        model_matrix = numpy.matmul(numpy.matmul(trans_matrix,rot_matrix),scale_matrix)
        view_matrix = numpy.transpose(pyrr.matrix44.create_look_at(numpy.array([2.0, 2.0, 3.0], dtype="float32"),
            numpy.array([0.0, 0.0, 0.0], dtype="float32"),
            numpy.array([0.0, 1.0, 0.0], dtype="float32")))
        proj_matrix = numpy.transpose(pyrr.matrix44.create_perspective_projection(45.0, WINDOW_WIDTH/WINDOW_HEIGHT, 0.1, 200.0))
        m = numpy.matmul(numpy.matmul(proj_matrix,view_matrix),model_matrix) 

        return numpy.transpose(m)

    def display(self):
        glEnable(GL_DEPTH_TEST)

        glClearColor(0.0, 0.0, 0.0, 1.0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        glUseProgram(self.gl_program)
        glActiveTexture(GL_TEXTURE0)
        glBindTexture(GL_TEXTURE_2D, self.brick_texture)
        texture_uniform = glGetUniformLocation(self.gl_program, "the_texture")
        glUniform1i(texture_uniform, 0)

        trans_uniform = glGetUniformLocation(self.gl_program, "mvp")
        glUniformMatrix4fv(trans_uniform, 1, GL_FALSE, self.cube_mvp)
        #model_location = glGetUniformLocation(self.gl_program, "model")
        #glUniformMatrix4fv(model_location, 1, GL_FALSE, self.cube_model_matrix)
        #view_location = glGetUniformLocation(self.gl_program, "view")
        #glUniformMatrix4fv(view_location, 1, GL_FALSE, self.cube_view_matrix)
        #proj_location = glGetUniformLocation(self.gl_program, "proj")
        #glUniformMatrix4fv(proj_location, 1, GL_FALSE, self.cube_proj_matrix)
        glBindVertexArray(self.vao)

        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, None)
        glUseProgram(0)

def main():
    pygame.init()
    os.environ['SDL_VIDEO_CENTERED'] = '1'
    pygame.display.set_caption("3D Graphics")
    pygame.display.set_mode((1280, 720), pygame.DOUBLEBUF | pygame.OPENGL)
    clock = pygame.time.Clock()
    gl = GLProgram()

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

        clock.tick(60)
        gl.display()
        pygame.display.flip()

if __name__ == "__main__":
    main()

使用此顶点着色器:

#version 330 core

layout (location = 0) in vec3 position;
layout (location = 1) in vec2 text_coord;

out vec2 final_text_coord;

uniform mat4 mvp;

void main() 
    gl_Position = mvp * vec4(position, 1.0);
    final_text_coord = text_coord;

还有这个片段着色器:

#version 330 core

in vec2 final_text_coord;

out vec4 frag_color;

uniform sampler2D the_texture;

void main() 
    frag_color = vec4(1,0,0,1);

我专门添加了方法gl_translate2gl_translate3。两者都产生相同的矩阵,2 变体只是使用 pyrr 库的本机乘法顺序约定,gl_translate3 使用 GL 约定。

我还创建了具有不同参数的矩阵

self.cube_mvp = self.gl_translate3(Vector3([1.0, 1.0, 1.0]), -45.0, Vector3([0.5, 0.5, 0.5]))

这在旋转的负号上有所不同(并补偿了您在 gl_transaltion 中所做的额外 transpose)。

结果与您在发布的答案中得到的不同,但这是由于错误的乘法函数(尤其是删除了平移部分并将旋转扭曲为一些剪切操作)。

gl_translate2gl_translate3 得到的结果是: 对于指定的参数,这看起来很合理。

【讨论】:

我不确定gl_translate3 现在与我之前在聊天中提出的变体有何不同,但我们可能错过了一些细节。另请注意,您只需将glUniformMatrixtranspose 参数设置为GL_TRUE,即可摆脱最后的numpy.transpose(m) 所以这里的解决方案是始终使用带有 pyrr 矩阵的 numpy.matmul 而不是星号。所以平移、旋转和缩放矩阵也应该使用matmul相乘才能正常工作

以上是关于PyOpenGL 透视投影的主要内容,如果未能解决你的问题,请参考以下文章

Shadowmap 适用于正射投影,但不适用于透视投影

计算机图形学-MVP变换之投影(Projection)变换

SharpGL之透视投影和摄像机

如何构建透视投影矩阵(无 API)

Android应用开发 OpenGL ES -- 透视投影 和 正交投影

相机矩阵(Camera Matrix)