Exemplo n.º 1
0
class OpenGLWidget(QOpenGLWidget, QOpenGLExtraFunctions):

    def __init__(self, fmt: QSurfaceFormat, parent=None, *args, **kwargs):
        QOpenGLWidget.__init__(self, parent, *args, **kwargs)
        QOpenGLExtraFunctions.__init__(self, *args, **kwargs)
        self.width, self.height = 1280, 720

        self.program = QOpenGLShaderProgram()
        self.vbo = QOpenGLBuffer(QOpenGLBuffer.VertexBuffer)
        self.ebo = QOpenGLBuffer(QOpenGLBuffer.IndexBuffer)
        self.vao = QOpenGLVertexArrayObject()

        self.model_loc = None
        self.projection_loc = None
        self.camera_loc = None
        self.attrib_loc = None

        self.shape = Cube()
        self.models = []
        self.model = None
        self.projection = None

        self.camera = Camera()
        self.last_pos = QPoint(self.width / 2.0, self.height / 2.0)

        self.setFormat(fmt)
        self.context = QOpenGLContext(self)
        if not self.context.create():
            raise RuntimeError("Unable to create GL context")

        self.timer = QTimer()
        self.timer.timeout.connect(self.update)
        self.timer.start(1000.0/FPS)

    ### QtGL ###

    def initializeGL(self) -> None:
        self.context.aboutToBeDestroyed.connect(self.cleanup)

        self.initializeOpenGLFunctions()
        self.build_shaders()
        self.create_vbo()
        self.glClearColor(0.2, 0.0, 0.2, 0.5)

    def paintGL(self) -> None:
        self.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
        self.glEnable(gl.GL_DEPTH_TEST)
        self.camera.move()
        self.render()

    def resizeGL(self, width: int, height: int) -> None:
        self.width, self.height = width, height
        self.glViewport(0, 0, width, height)
        self.projection = pyrr.matrix44.create_perspective_projection_matrix(45, width/height, 0.1, 150)

    def render(self) -> None:
        self.program.bind()
        vao_binder = QOpenGLVertexArrayObject.Binder(self.vao)

        # TODO: switch to Qt functions (doesn't work)
        gl.glUniformMatrix4fv(self.projection_loc, 1, gl.GL_FALSE, self.projection)
        view = self.camera.get_view_matrix()
        gl.glUniformMatrix4fv(self.camera_loc, 1, gl.GL_FALSE, view)

        for model in self.models:
            # TODO: move to entity class ?
            rotation = pyrr.matrix44.create_from_axis_rotation(
                pyrr.vector3.create_from_matrix44_translation(model),
                time.time(),
            )
            scale = pyrr.matrix44.create_from_scale(
                pyrr.vector3.create_from_matrix44_translation(model) * 0.1,
            )
            rotation = pyrr.matrix44.multiply(scale, rotation)
            model = pyrr.matrix44.multiply(rotation, model)

            orbit = pyrr.Matrix44.from_y_rotation(-0.1*time.time())
            model = pyrr.matrix44.multiply(model, orbit)
            gl.glUniformMatrix4fv(self.model_loc, 1, gl.GL_FALSE, model)
            self.glDrawElements(gl.GL_TRIANGLES, len(self.shape.indices), gl.GL_UNSIGNED_INT, VoidPtr(0))

        self.program.release()
        vao_binder = None

    ### Helpers ###

    def build_shaders(self) -> None:
        if not self.program.addShaderFromSourceFile(QOpenGLShader.Vertex, "shaders/vertex_shader.glsl"):
            raise FileNotFoundError("Unable to load vertex shader")
        if not self.program.addShaderFromSourceFile(QOpenGLShader.Fragment, "shaders/fragment_shader.glsl"):
            raise FileNotFoundError("Unable to load fragment shader")
        if not self.program.link():
            raise RuntimeError("Unable to link shader program")

    def create_vbo(self) -> None:
        self.program.bind()  # suspicious behaviour ?

        self.vao.create()
        vao_binder = QOpenGLVertexArrayObject.Binder(self.vao)

        self.vbo.create()
        self.vbo.bind()
        self.vbo.allocate(self.shape.vertices, self.shape.vertices.nbytes)

        self.attrib_loc = self.program.attributeLocation("a_position")
        self.model_loc = self.program.uniformLocation("model")
        self.projection_loc = self.program.uniformLocation("projection")
        self.camera_loc = self.program.uniformLocation("camera")

        for i in range(150):
            x, y, z = random.normalvariate(0, 15), random.normalvariate(0, 15), random.normalvariate(0, 15)
            self.models.append(pyrr.matrix44.create_from_translation(pyrr.Vector3([x, y, z])))

        self.ebo.create()
        self.ebo.bind()
        self.ebo.allocate(self.shape.indices, self.shape.indices.nbytes)

        float_size = ctypes.sizeof(ctypes.c_float)  # (4)
        null = VoidPtr(0)
        pointer = VoidPtr(3 * float_size)
        self.glEnableVertexAttribArray(0)
        self.glVertexAttribPointer(
            0, 3, gl.GL_FLOAT, gl.GL_FALSE, 6 * float_size, null
        )
        self.glEnableVertexAttribArray(1)
        self.glVertexAttribPointer(
            1, 3, gl.GL_FLOAT, gl.GL_FALSE, 6 * float_size, pointer
        )
        self.vao.release()
        self.vbo.release()
        self.ebo.release()

        self.program.release()
        vao_binder = None

    ### Events ###

    def keyPressEvent(self, event):
        """W: 0x57, A: 0x41, S: 0x53, D: 0x44, space: 0x20, shift: 0x01000020"""  # TODO: make keymap
        if event.key() == int(0x57):
            self.camera.keyboard_press("FORWARD")
        elif event.key() == int(0x53):
            self.camera.keyboard_press("BACKWARD")
        if event.key() == int(0x41):
            self.camera.keyboard_press("LEFT")
        elif event.key() == int(0x44):
            self.camera.keyboard_press("RIGHT")
        if event.key() == int(0x20):
            self.camera.keyboard_press("UP")
        elif event.key() == int(0x01000020):
            self.camera.keyboard_press("DOWN")

    def keyReleaseEvent(self, event):
        if event.key() == int(0x57):
            self.camera.keyboard_release("FORWARD")
        elif event.key() == int(0x53):
            self.camera.keyboard_release("BACKWARD")
        if event.key() == int(0x41):
            self.camera.keyboard_release("LEFT")
        elif event.key() == int(0x44):
            self.camera.keyboard_release("RIGHT")
        if event.key() == int(0x20):
            self.camera.keyboard_release("UP")
        elif event.key() == int(0x01000020):
            self.camera.keyboard_release("DOWN")

    def mousePressEvent(self, event):
        self.last_pos = QPoint(event.pos())
        event.accept()

    def mouseMoveEvent(self, event):
        dx = event.x() - self.last_pos.x()
        dy = event.y() - self.last_pos.y()

        if event.buttons():
            self.camera.mouse_movement(float(dx), float(dy))

        event.accept()
        self.last_pos = QPoint(event.pos())

    def wheelEvent(self, event):
        if event.type() in (Qt.ScrollBegin, Qt.ScrollEnd):  # FIXME: doesn't seem to work, true divide err
            return

        degrees = event.delta() / 8
        steps = degrees / 15

        event.accept()
        self.camera.scroll_movement(steps)

    ### Slots ###

    @Slot()
    def cleanup(self):
        if not self.makeCurrent():
            raise Exception("Could not make context current")
        self.vbo.destroy()
        self.program.bind()
        self.program.removeAllShaders()
        self.program.release()
        del self.program
        self.program = None
        self.doneCurrent()
Exemplo n.º 2
0
class RenderWindow(QWindow):
    def __init__(self, format):
        super(RenderWindow, self).__init__()
        self.setSurfaceType(QWindow.OpenGLSurface)
        self.setFormat(format)
        self.context = QOpenGLContext(self)
        self.context.setFormat(self.requestedFormat())
        if not self.context.create():
            raise Exception("Unable to create GL context")
        self.program = None
        self.timer = None
        self.angle = 0

    def initGl(self):
        self.program = QOpenGLShaderProgram(self)
        self.vao = QOpenGLVertexArrayObject()
        self.vbo = QOpenGLBuffer()

        format = self.context.format()
        useNewStyleShader = format.profile() == QSurfaceFormat.CoreProfile
        # Try to handle 3.0 & 3.1 that do not have the core/compatibility profile
        # concept 3.2+ has. This may still fail since version 150 (3.2) is
        # specified in the sources but it's worth a try.
        if (format.renderableType() == QSurfaceFormat.OpenGL and format.majorVersion() == 3
            and format.minorVersion() <= 1):
            useNewStyleShader = not format.testOption(QSurfaceFormat.DeprecatedFunctions)

        vertexShader = vertexShaderSource if useNewStyleShader else vertexShaderSource110
        fragmentShader = fragmentShaderSource if useNewStyleShader else fragmentShaderSource110
        if not self.program.addShaderFromSourceCode(QOpenGLShader.Vertex, vertexShader):
            raise Exception("Vertex shader could not be added: {} ({})".format(self.program.log(), vertexShader))
        if not self.program.addShaderFromSourceCode(QOpenGLShader.Fragment, fragmentShader):
            raise Exception("Fragment shader could not be added: {} ({})".format(self.program.log(), fragmentShader))
        if not self.program.link():
            raise Exception("Could not link shaders: {}".format(self.program.log()))

        self.posAttr = self.program.attributeLocation("posAttr")
        self.colAttr = self.program.attributeLocation("colAttr")
        self.matrixUniform = self.program.uniformLocation("matrix")

        self.vbo.create()
        self.vbo.bind()
        self.verticesData = vertices.tobytes()
        self.colorsData = colors.tobytes()
        verticesSize = 4 * vertices.size
        colorsSize = 4 * colors.size
        self.vbo.allocate(VoidPtr(self.verticesData), verticesSize + colorsSize)
        self.vbo.write(verticesSize, VoidPtr(self.colorsData), colorsSize)
        self.vbo.release()

        vaoBinder = QOpenGLVertexArrayObject.Binder(self.vao)
        if self.vao.isCreated(): # have VAO support, use it
            self.setupVertexAttribs()

    def setupVertexAttribs(self):
        self.vbo.bind()
        self.program.setAttributeBuffer(self.posAttr, GL.GL_FLOAT, 0, 2)
        self.program.setAttributeBuffer(self.colAttr, GL.GL_FLOAT, 4 * vertices.size, 3)
        self.program.enableAttributeArray(self.posAttr)
        self.program.enableAttributeArray(self.colAttr)
        self.vbo.release()

    def exposeEvent(self, event):
        if self.isExposed():
            self.render()
            if self.timer is None:
                self.timer = QTimer(self)
                self.timer.timeout.connect(self.slotTimer)
            if not self.timer.isActive():
                self.timer.start(10)
        else:
            if self.timer and self.timer.isActive():
                self.timer.stop()

    def render(self):
        if not self.context.makeCurrent(self):
            raise Exception("makeCurrent() failed")
        functions = self.context.functions()
        if self.program is None:
            functions.glEnable(GL.GL_DEPTH_TEST)
            functions.glClearColor(0, 0, 0, 1)
            self.initGl()

        retinaScale = self.devicePixelRatio()
        functions.glViewport(0, 0, self.width() * retinaScale,
                             self.height() * retinaScale)
        functions.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)

        self.program.bind()
        matrix = QMatrix4x4()
        matrix.perspective(60, 4 / 3, 0.1, 100)
        matrix.translate(0, 0, -2)
        matrix.rotate(self.angle, 0, 1, 0)
        self.program.setUniformValue(self.matrixUniform, matrix)

        if self.vao.isCreated():
            self.vao.bind()
        else: # no VAO support, set the vertex attribute arrays now
            self.setupVertexAttribs()

        functions.glDrawArrays(GL.GL_TRIANGLES, 0, 3)

        self.vao.release()
        self.program.release()

        # swapInterval is 1 by default which means that swapBuffers() will (hopefully) block
        # and wait for vsync.
        self.context.swapBuffers(self)
        self.context.doneCurrent()

    def slotTimer(self):
        self.render()
        self.angle += 1

    def glInfo(self):
        if not self.context.makeCurrent(self):
            raise Exception("makeCurrent() failed")
        functions = self.context.functions()
        text = """Vendor: {}\nRenderer: {}\nVersion: {}\nShading language: {}
\nContext Format: {}\n\nSurface Format: {}""".format(
               functions.glGetString(GL.GL_VENDOR), functions.glGetString(GL.GL_RENDERER),
               functions.glGetString(GL.GL_VERSION),
               functions.glGetString(GL.GL_SHADING_LANGUAGE_VERSION),
               print_surface_format(self.context.format()),
               print_surface_format(self.format()))
        self.context.doneCurrent()
        return text
Exemplo n.º 3
0
class RenderWindow(QWindow):
    def __init__(self, format):
        super(RenderWindow, self).__init__()
        self.setSurfaceType(QWindow.OpenGLSurface)
        self.setFormat(format)
        self.context = QOpenGLContext(self)
        self.context.setFormat(self.requestedFormat())
        if not self.context.create():
            raise Exception("Unable to create GL context")
        self.program = None
        self.timer = None
        self.angle = 0

    def initGl(self):
        self.program = QOpenGLShaderProgram(self)
        self.vao = QOpenGLVertexArrayObject()
        self.vbo = QOpenGLBuffer()

        format = self.context.format()
        useNewStyleShader = format.profile() == QSurfaceFormat.CoreProfile
        # Try to handle 3.0 & 3.1 that do not have the core/compatibility profile
        # concept 3.2+ has. This may still fail since version 150 (3.2) is
        # specified in the sources but it's worth a try.
        if (format.renderableType() == QSurfaceFormat.OpenGL and format.majorVersion() == 3
            and format.minorVersion() <= 1):
            useNewStyleShader = not format.testOption(QSurfaceFormat.DeprecatedFunctions)

        vertexShader = vertexShaderSource if useNewStyleShader else vertexShaderSource110
        fragmentShader = fragmentShaderSource if useNewStyleShader else fragmentShaderSource110
        if not self.program.addShaderFromSourceCode(QOpenGLShader.Vertex, vertexShader):
            raise Exception("Vertex shader could not be added: {} ({})".format(self.program.log(), vertexShader))
        if not self.program.addShaderFromSourceCode(QOpenGLShader.Fragment, fragmentShader):
            raise Exception("Fragment shader could not be added: {} ({})".format(self.program.log(), fragmentShader))
        if not self.program.link():
            raise Exception("Could not link shaders: {}".format(self.program.log()))

        self.posAttr = self.program.attributeLocation("posAttr")
        self.colAttr = self.program.attributeLocation("colAttr")
        self.matrixUniform = self.program.uniformLocation("matrix")

        self.vbo.create()
        self.vbo.bind()
        self.verticesData = vertices.tobytes()
        self.colorsData = colors.tobytes()
        verticesSize = 4 * vertices.size
        colorsSize = 4 * colors.size
        self.vbo.allocate(VoidPtr(self.verticesData), verticesSize + colorsSize)
        self.vbo.write(verticesSize, VoidPtr(self.colorsData), colorsSize)
        self.vbo.release()

        vaoBinder = QOpenGLVertexArrayObject.Binder(self.vao)
        if self.vao.isCreated(): # have VAO support, use it
            self.setupVertexAttribs()

    def setupVertexAttribs(self):
        self.vbo.bind()
        self.program.setAttributeBuffer(self.posAttr, GL.GL_FLOAT, 0, 2)
        self.program.setAttributeBuffer(self.colAttr, GL.GL_FLOAT, 4 * vertices.size, 3)
        self.program.enableAttributeArray(self.posAttr)
        self.program.enableAttributeArray(self.colAttr)
        self.vbo.release()

    def exposeEvent(self, event):
        if self.isExposed():
            self.render()
            if self.timer is None:
                self.timer = QTimer(self)
                self.timer.timeout.connect(self.slotTimer)
                self.timer.start(10)

    def render(self):
        if not self.context.makeCurrent(self):
            raise Exception("makeCurrent() failed")
        functions = self.context.functions()
        if self.program is None:
            functions.glEnable(GL.GL_DEPTH_TEST)
            functions.glClearColor(0, 0, 0, 1)
            self.initGl()

        functions.glViewport(0, 0, self.width(), self.height())
        functions.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)

        self.program.bind()
        matrix = QMatrix4x4()
        matrix.perspective(60, 4 / 3, 0.1, 100)
        matrix.translate(0, 0, -2)
        matrix.rotate(self.angle, 0, 1, 0)
        self.program.setUniformValue(self.matrixUniform, matrix)

        if self.vao.isCreated():
            self.vao.bind()
        else: # no VAO support, set the vertex attribute arrays now
            self.setupVertexAttribs()

        functions.glDrawArrays(GL.GL_TRIANGLES, 0, 3)

        self.vao.release()
        self.program.release()

        # swapInterval is 1 by default which means that swapBuffers() will (hopefully) block
        # and wait for vsync.
        self.context.swapBuffers(self)
        self.context.doneCurrent()

    def slotTimer(self):
        self.render()
        self.angle += 1

    def glInfo(self):
        if not self.context.makeCurrent(self):
            raise Exception("makeCurrent() failed")
        functions = self.context.functions()
        text = "Vendor: {}\nRenderer: {}\nVersion: {}\nShading language: {}".format(
               functions.glGetString(GL.GL_VENDOR), functions.glGetString(GL.GL_RENDERER),
               functions.glGetString(GL.GL_VERSION),
               functions.glGetString(GL.GL_SHADING_LANGUAGE_VERSION))
        self.context.doneCurrent()
        return text