class SquareObject(QObject): def __init__(self, parent=None): super(SquareObject, self).__init__(parent=parent) self.initialize_shader_program() self.initialize_geometry() self.initialize_geometry_on_gpu() self.initialize_texture_on_gpu() def destroy(self): self.vao.release() self.vao.destroy() self.vbo.release() self.vbo.destroy() self.ebo.release() self.ebo.destroy() self.texture0.release() self.texture0.destroy() self.texture1.release() self.texture1.destroy() def initialize_shader_program(self): self.vertex_shader = """ #version 330 core layout (location = 0) in vec3 in_coords; layout (location = 1) in vec3 in_color; layout (location = 2) in vec2 in_tex_coords; uniform mat4 transform; out vec3 out_color; out vec2 out_tex_coords; void main() { gl_Position = transform * vec4(in_coords, 1.0); out_color = in_color; out_tex_coords = vec2(in_tex_coords.x, in_tex_coords.y); } """ self.fragment_shader = """ #version 330 core in vec3 out_color; in vec2 out_tex_coords; out vec4 frag_color; uniform sampler2D texture0; uniform sampler2D texture1; void main() { if (out_tex_coords.x > 0.5) frag_color = texture(texture0, out_tex_coords); else frag_color = texture(texture1, out_tex_coords); //frag_color = mix(texture(texture0, out_tex_coords), // texture(texture1, out_tex_coords), 0.5) // * vec4(out_color, 1.0); } """ self.arg_pos = { 'in_coords': 0, 'in_color': 1, 'in_tex_coords': 2, 'out_color': 0 } self.program = QOpenGLShaderProgram(parent=self.parent()) self.program.addCacheableShaderFromSourceCode(QOpenGLShader.Vertex, self.vertex_shader) self.program.addCacheableShaderFromSourceCode(QOpenGLShader.Fragment, self.fragment_shader) self.program.link() self.program.bind() def initialize_geometry(self): self.vertices = np.array( [ # coords color texture coords [0.5, -0.5, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0], # bottom-right [0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0], # top-right [-0.5, 0.5, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0], # top-let [-0.5, -0.5, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0] # bottom-let ], dtype=np.float32) self.triangles = np.array([[0, 1, 2], [2, 3, 0]], dtype=np.int32) def initialize_geometry_on_gpu(self): self.vao = QOpenGLVertexArrayObject(self.parent()) if not self.vao.create(): raise ValueError('Could not create VAO') self.vao.bind() self.vbo = QOpenGLBuffer(QOpenGLBuffer.VertexBuffer) if not self.vbo.create(): raise ValueError('Could not create VBO') self.vbo.bind() self.vbo.setUsagePattern(QOpenGLBuffer.StaticDraw) vertices_data = self.vertices.tostring() self.vbo.allocate(len(vertices_data)) self.vbo.write(0, vertices_data, len(vertices_data)) self.ebo = QOpenGLBuffer(QOpenGLBuffer.IndexBuffer) if not self.ebo.create(): raise ValueError('Could not create EBO') self.ebo.bind() self.ebo.setUsagePattern(QOpenGLBuffer.StaticDraw) triangles_data = self.triangles.tostring() self.ebo.allocate(len(triangles_data)) self.ebo.write(0, triangles_data, len(triangles_data)) self.program.enableAttributeArray(self.arg_pos['in_coords']) self.program.setAttributeBuffer( self.arg_pos['in_coords'], gl.GL_FLOAT, 0, 3, self.vertices.shape[1] * self.vertices.dtype.itemsize) self.program.enableAttributeArray(self.arg_pos['in_color']) self.program.setAttributeBuffer( self.arg_pos['in_color'], gl.GL_FLOAT, 3 * self.vertices.dtype.itemsize, 3, self.vertices.shape[1] * self.vertices.dtype.itemsize) self.program.enableAttributeArray(self.arg_pos['in_tex_coords']) self.program.setAttributeBuffer( self.arg_pos['in_tex_coords'], gl.GL_FLOAT, 6 * self.vertices.dtype.itemsize, 2, self.vertices.shape[1] * self.vertices.dtype.itemsize) self.vao.release() self.vbo.release() self.ebo.release() def initialize_texture_on_gpu(self): # Texture 0. image0 = QImage(path.join(DATA_DIR, 'ksmall.jpg')).mirrored() self.texture0 = QOpenGLTexture(image0) self.texture0.setMinificationFilter(QOpenGLTexture.LinearMipMapLinear) self.texture0.setMagnificationFilter(QOpenGLTexture.Linear) self.texture0.setWrapMode(QOpenGLTexture.Repeat) self.texture0.bind(0) self.program.setUniformValue(self.program.uniformLocation('texture0'), 0) # Texture 1. image1 = QImage(path.join(DATA_DIR, 'sunflowerField.jpg')).mirrored() self.texture1 = QOpenGLTexture(image1) self.texture1.setMinificationFilter(QOpenGLTexture.LinearMipMapLinear) self.texture1.setMagnificationFilter(QOpenGLTexture.Linear) self.texture1.setWrapMode(QOpenGLTexture.Repeat) self.texture1.bind(1) self.program.setUniformValue(self.program.uniformLocation('texture1'), 1) def render(self, transform): self.program.bind() self.program.setUniformValue('transform', transform) self.vao.bind() gl.glDrawElements(gl.GL_TRIANGLES, self.triangles.size, gl.GL_UNSIGNED_INT, None) self.program.release()
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)