Перспективная проекция PyOpenGL

Я относительно новичок в 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);
}

person 18 Triston Lehmann    schedule 04.08.2017    source источник
comment
В документах pyrr говорится, что матрицы располагаются в формате строк, но вы используете их так, как если бы они хранились в формате столбцов.   -  person derhass    schedule 06.08.2017
comment
Я понимаю, что вы говорите о расположении матриц, но вы потеряли меня, когда сказали, что используете их так, как будто они хранятся в виде основных столбцов, не могли бы вы уточнить.   -  person 18 Triston Lehmann    schedule 06.08.2017
comment
Если A является основным по строкам, а B является той же матрицей, но в формате основного столбца, то A = transpose(B). Порядок или умножение матриц также должны быть инвертированы.   -  person Ripi2    schedule 06.08.2017
comment
@ 18TristonLehmann: вы отправляете матрицу с glUniformMatrix4fv(..., GL_FALSE), что по соглашению GL означает, что ожидается 16 чисел с плавающей запятой, где первые четыре будут описывать первый столбец этой матрицы. Вы также не транспонируете эту матрицу неявно, меняя порядок умножения в шейдере, поэтому матрицы, которые вы фактически применяете к этим векторам, транспонируются в то, что вы должны использовать.   -  person derhass    schedule 06.08.2017
comment
Хорошо, я думаю, что у меня все получилось, я опубликую ответ как можно скорее. Большое спасибо!!!!   -  person 18 Triston Lehmann    schedule 06.08.2017


Ответы (1)


Было несколько проблем с вашим кодом:

  1. Умножение матриц. Умножение двух двумерных массивов numpy вместе приводит к покомпонентному продукту, а не к матричному умножению. Использование numpy.matmul решит эту проблему.

  2. Условные обозначения матрицы не были ясны.

    Как указано в документации для 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_translate2 и gl_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]))

который отличается отрицательным знаком для поворота (и компенсирует дополнительные transpose, которые вы делаете в gl_transaltion).

Результат отличается от того, что вы получили в своем опубликованном ответе, но это связано с тем, что матрица вашей модели была нарушена в этом ответе из-за неправильной функции умножения (которая особенно удалила часть перевода и исказила вращение в некоторую операцию сдвига ).

Результат, который я получаю как с gl_translate2, так и с gl_translate3: скриншот, показывающий искаженный в перспективе квадрат и это выглядит очень правдоподобно для указанных параметров.

person derhass    schedule 06.08.2017
comment
Я не уверен, чем gl_translate3 теперь отличается от варианта, который я предложил в чате ранее, но мы, вероятно, что-то упустили. Также обратите внимание, что вы можете избавиться от этого последнего numpy.transpose(m), просто установив аргумент transpose glUniformMatrix в GL_TRUE. - person derhass; 07.08.2017
comment
Таким образом, решение здесь состоит в том, чтобы всегда использовать numpy.matmul с матрицами pyrr вместо звездочки. Таким образом, матрица перевода, поворота и масштабирования также должна быть умножена с использованием matmul для правильной работы. - person Piotr Majek; 26.04.2021