Example #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()
Example #2
0
class Viewport(View, QOpenGLWidget):
    def __init__(self,
                 scene,
                 updateFpsDisplay,
                 renderSettings=Rendersettings(),
                 parent=None):
        View.__init__(self, scene)
        QOpenGLWidget.__init__(self, parent)
        self.renderImage = QImage()
        self.renderSettings = renderSettings
        self.updateFpsDisplay = updateFpsDisplay
        self.shaderProgram = QOpenGLShaderProgram()
        self.viewPortShaderProgram = QOpenGLShaderProgram()
        self.lightShaderProgram = QOpenGLShaderProgram()
        self.eyeLoc = QVector3D(0.0, 0.0, 5.0)
        self.pressLoc = QVector2D()
        self.isRendering = False
        self.viewportPlane = ViewportPlane()
        self.viewportTexture = None
        self.timer = QTimer()
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(self.checkRender)
        self.scene.registerView(self, [
            UpdateType.MATERIAL_CHANGE, UpdateType.MATERIAL_CREATE,
            UpdateType.MATERIAL_DELETE, UpdateType.OBJECT_CREATE,
            UpdateType.OBJECT_DELETE, UpdateType.OBJECT_TRANSFORM,
            UpdateType.CAMERA_CHANGE, UpdateType.LIGHT_CHANGE,
            UpdateType.SCENE_LOAD
        ])
        self.timestamps = None
        self.fpsWindow = 10
        self.renderer = None
        self.renderStartTime = None

    def initializeGL(self):
        self.glFunctions = self.context().functions()
        self.glFunctions.glEnable(GL.GL_DEPTH_TEST)
        self.glFunctions.glEnable(GL.GL_CULL_FACE)
        self.glFunctions.glClearColor(0.15, 0.25, 0.3, 1.0)

        vertSuccess = self.shaderProgram.addShaderFromSourceFile(
            QOpenGLShader.Vertex, "editor/ui/shaders/vertex.vert")
        fragSuccess = self.shaderProgram.addShaderFromSourceFile(
            QOpenGLShader.Fragment, "editor/ui/shaders/fragment.frag")

        print("Vertex shader compilation {}".format(
            "successful" if vertSuccess else "failed"))
        print("Fragment shader compilation {}".format(
            "successful" if fragSuccess else "failed"))
        print("Program linking {}".format(
            "successful" if self.shaderProgram.link() else "failed"))

        vertSuccess = self.viewPortShaderProgram.addShaderFromSourceFile(
            QOpenGLShader.Vertex, "editor/ui/shaders/viewportvertex.vert")
        fragSuccess = self.viewPortShaderProgram.addShaderFromSourceFile(
            QOpenGLShader.Fragment, "editor/ui/shaders/viewportfragment.frag")

        print("Viewport vertex shader compilation {}".format(
            "successful" if vertSuccess else "failed"))
        print("Viewport fragment shader compilation {}".format(
            "successful" if fragSuccess else "failed"))
        print("Viewport program linking {}".format(
            "successful" if self.viewPortShaderProgram.link() else "failed"))

        vertSuccess = self.lightShaderProgram.addShaderFromSourceFile(
            QOpenGLShader.Vertex, "editor/ui/shaders/vertex.vert")
        fragSuccess = self.lightShaderProgram.addShaderFromSourceFile(
            QOpenGLShader.Fragment, "editor/ui/shaders/lightfragment.frag")

        print("Light vertex shader compilation {}".format(
            "successful" if vertSuccess else "failed"))
        print("Light fragment shader compilation {}".format(
            "successful" if fragSuccess else "failed"))
        print("Light program linking {}".format(
            "successful" if self.viewPortShaderProgram.link() else "failed"))

        self.shaderProgram.release()
        self.viewPortShaderProgram.release()

        # Call all drawable Objects initGL
        for c in drawableClasses:
            if isinstance(c, ViewportPlane):
                c.initGL(self.viewPortShaderProgram)
            else:
                c.initGL(self.shaderProgram)

    def resizeGL(self, w, h):
        self.scene.getCamera().setAspect(w, h)
        if self.viewportTexture and self.context().isValid():
            self.viewportTexture.destroy()
        if self.context().isValid():
            self.renderImage = QImage(w, h, QImage.Format_RGB32)
            self.renderImage.fill(Qt.black)
            self.viewportTexture = QOpenGLTexture(QOpenGLTexture.Target2D)
            self.viewportTexture.setSize(w, h)
            self.viewportTexture.setBorderColor(0, 0, 0, 255)
            self.viewportTexture.setWrapMode(QOpenGLTexture.ClampToBorder)
            self.viewportTexture.setMinificationFilter(QOpenGLTexture.Nearest)
            self.viewportTexture.setMagnificationFilter(QOpenGLTexture.Nearest)
            self.viewportTexture.setData(self.renderImage,
                                         QOpenGLTexture.DontGenerateMipMaps)
            self.updateRenderResolution(w, h)

    def paintGL(self):
        self.glFunctions.glClear(GL.GL_COLOR_BUFFER_BIT
                                 | GL.GL_DEPTH_BUFFER_BIT)
        if not self.isRendering:
            self.shaderProgram.bind()
            self.shaderProgram.setUniformValue(
                "eyePos",
                self.scene.getCamera().getTranslation())
            self.shaderProgram.release()

            for obj in self.scene.getObjectIterator():
                if isinstance(obj, Light):
                    obj.draw(self.lightShaderProgram, self.glFunctions)
                else:
                    obj.draw(self.shaderProgram, self.glFunctions)
        else:
            self.viewportTexture.bind()
            self.viewportPlane.draw(self.viewPortShaderProgram,
                                    self.glFunctions)
            self.viewportTexture.release()

    def mousePressEvent(self, e):
        self.pressLoc = QVector2D(e.localPos())
        super().mousePressEvent(e)

    def mouseMoveEvent(self, e):
        if e.buttons() == Qt.LeftButton:
            currPos = QVector2D(e.localPos())
            diff = currPos - self.pressLoc
            diff *= 0.5
            self.pressLoc = currPos
            self.scene.getCamera().rotate(diff.x(), diff.y())

    def wheelEvent(self, e):
        if e.modifiers() == Qt.ShiftModifier:
            self.scene.getCamera().zoom(-e.angleDelta().y() / 45.0)
        else:
            self.scene.getCamera().track(-e.angleDelta().y() / 600.0)

    def notify(self, updateType):
        if updateType == UpdateType.SCENE_LOAD:
            self.scene.getCamera().setAspect(self.width(), self.height())
        if self.isRendering:
            if updateType == UpdateType.CAMERA_CHANGE:
                self.cameraToRenderer()
            elif updateType in [
                    UpdateType.OBJECT_TRANSFORM, UpdateType.OBJECT_CREATE,
                    UpdateType.OBJECT_DELETE, UpdateType.MATERIAL_CREATE,
                    UpdateType.MATERIAL_DELETE, UpdateType.MATERIAL_CHANGE,
                    UpdateType.LIGHT_CHANGE
            ]:
                self.renderer.stop()
                self.renderer.clearMaterials()
                self.renderer.clearObjects()
                for mat in self.scene.getMaterialIterator():
                    self.materialToRenderer(mat)
                for obj in self.scene.getObjectIterator():
                    self.sceneObjectToRenderer(obj)
                self.cameraToRenderer()
                self.renderer.start()
            self.timestamps = [[time.time(), 0]]
            self.renderStartTime = time.time()

        self.update()

    def startRender(self):
        if self.isRendering:
            return
        self.isRendering = True
        self.renderer = PyRenderer(self.width(), self.height())
        self.renderer.setRenderSettings(self.renderSettings)
        self.timestamps = [[time.time(), 0]]
        self.cameraToRenderer()
        self.update()
        for mat in self.scene.getMaterialIterator():
            self.materialToRenderer(mat)
        for obj in self.scene.getObjectIterator():
            self.sceneObjectToRenderer(obj)
        self.renderer.start()
        self.timer.setInterval(20)
        self.timer.start()
        self.renderStartTime = time.time()

    def stopRender(self):
        if not self.isRendering:
            return
        self.timer.stop()
        self.isRendering = False
        self.renderer = None
        self.resizeGL(self.width(), self.height())
        self.update()
        self.updateFpsDisplay('')

    def updateRenderResolution(self, width, height):
        if not self.isRendering:
            return
        self.renderer.setResolution(width, height)
        self.timestamps = [[time.time(), 0]]

    def checkRender(self):
        if not self.isRendering:
            return
        imageArray, numIterations = self.renderer.getData(
            self.width(), self.height())
        if numIterations < 0 or imageArray.shape != (self.height(),
                                                     self.width()):
            self.timer.start()
            return
        if self.viewportTexture and self.context().isValid():
            self.viewportTexture.destroy()
        if self.context().isValid():
            self.renderImage = QImage(self.width(), self.height(),
                                      QImage.Format_RGB32)
            copyArray = np.ndarray(shape=(self.height(), self.width()),
                                   dtype=np.uint32,
                                   buffer=self.renderImage.bits())
            copyArray[0:, 0:] = imageArray[0:, 0:]
            self.viewportTexture = QOpenGLTexture(QOpenGLTexture.Target2D)
            self.viewportTexture.setSize(self.width(), self.height())
            self.viewportTexture.setBorderColor(0, 0, 0, 255)
            self.viewportTexture.setWrapMode(QOpenGLTexture.ClampToBorder)
            self.viewportTexture.setMinificationFilter(QOpenGLTexture.Nearest)
            self.viewportTexture.setMagnificationFilter(QOpenGLTexture.Nearest)
            self.viewportTexture.setData(self.renderImage,
                                         QOpenGLTexture.DontGenerateMipMaps)
            if len(self.timestamps) == self.fpsWindow:
                self.timestamps.pop()
            self.timestamps.insert(0, [time.time(), numIterations])
            diffs = [(self.timestamps[i][1] - self.timestamps[i + 1][1]) /
                     (self.timestamps[i][0] - self.timestamps[i + 1][0])
                     for i in range(len(self.timestamps) - 1)]
            secs = int(time.time() - self.renderStartTime)
            self.updateFpsDisplay(
                '{: 4.1f} iterations/sec     {:02.0f}:{:02.0f} min    {:7d} iterations    {:4d} x {:4d} pixel'
                .format(
                    sum(diffs) / len(diffs), secs // 60, secs % 60,
                    numIterations, self.width(), self.height()))
        self.update()
        self.timer.start()

    def closeViewport(self):
        self.stopRender()
        if self.viewportTexture and self.context().isValid():
            self.viewportTexture.destroy()

    def cameraToRenderer(self):
        cam = self.scene.getCamera()
        worldPos = cam.getTranslation()
        targetPos = self.scene.getFocusObject().getTranslation()
        self.renderer.updateCamera(
            [worldPos.x(), worldPos.y(),
             worldPos.z()],
            [targetPos.x(), targetPos.y(),
             targetPos.z()], cam.fovY, cam.fStop, cam.focusDistance,
            cam.stratificationLevel)

    def sceneObjectToRenderer(self, sceneObject):
        objType = None
        materialId = None
        lightIntensity = 0.0
        lightColor = [0.0, 0.0, 0.0]
        isVisible = True
        if isinstance(sceneObject, Camera):
            objType = SceneObjectType.CAMERA
            materialId = -1
        if isinstance(sceneObject, Sphere):
            objType = SceneObjectType.SPHERE
            materialId = sceneObject.material.id
        if isinstance(sceneObject, Cube):
            objType = SceneObjectType.CUBE
            materialId = sceneObject.material.id
        if isinstance(sceneObject, Plane):
            objType = SceneObjectType.PLANE
            materialId = sceneObject.material.id
        if isinstance(sceneObject, Disc):
            objType = SceneObjectType.DISC
            materialId = sceneObject.material.id
        if isinstance(sceneObject, Light):
            objType = sceneObject.getEmitterShape()
            materialId = sceneObject.material.id
            lightIntensity = sceneObject.getIntensity()
            colVec = sceneObject.getColor()
            lightColor = [colVec.x(), colVec.y(), colVec.z()]
            isVisible = sceneObject.getVisibility()

        matrixArray = sceneObject.calculateModel().transposed().copyDataTo()
        self.renderer.addObject(sceneObject.id, materialId, objType,
                                matrixArray, lightIntensity, lightColor,
                                sceneObject.area(), isVisible)

    def materialToRenderer(self, material):
        if type(material) == DiffuseMaterial:
            col = material.getEditorColor()
            self.renderer.addDiffuseMaterial(
                material.id, [col.x(), col.y(), col.z()])
        if type(material) == SpecularMaterial:
            reflectionColor = material.getReflectionColor()
            transmissionColor = material.getTransmissionColor()
            self.renderer.addSpecularMaterial(material.id, [
                reflectionColor.x(),
                reflectionColor.y(),
                reflectionColor.z()
            ], [
                transmissionColor.x(),
                transmissionColor.y(),
                transmissionColor.z()
            ], material.getIOR())

    def setRenderSettings(self, renderSettings):
        self.renderSettings = renderSettings
        if self.isRendering:
            self.renderer.setRenderSettings(self.renderSettings)

    def getRenderSettings(self):
        return self.renderSettings

    def saveImage(self):
        if not self.isRendering:
            return

        saveImage = self.renderImage.mirrored(horizontal=True)
        fileTuple = QFileDialog.getSaveFileName(
            self,
            'Save destination',
            dir="./images",
            filter='Images (*.png *.jpg);;All files (*)')
        if fileTuple[0]:
            filename = fileTuple[0]
            if '.' not in filename:
                filename += '.png'
            saveImage.save(filename, quality=100)