MAX
MAX

Reputation: 23

Set CullFace to Front and Back in Qt3D - issue with ActiveFrameGraph

I want my textured mesh (non-manifold) to display the texture color on both outside and inside faces (it currently displays as completely white inside) I have found from this 6-year-old post that I should set Cull Face to front and back, which I did, trying to use the guidelines provided (RenderSettings, RenderStateSet). But then, I am stuck at how this all works together, and how it ties to the ActiveFrameGraph.

Below is my code attempt, which still shows inside faces as white (need to set paths to actual files). If I play with ActiveFrameGraph, I either have it crash, or it complains it does not have a SurfaceSelector, leaving me again clueless.

Here is my code, all working except for the CullFace / RenderSettings / RenderStateSet / ActiveFrameGraph part:

from PyQt6.QtWidgets import QMainWindow, QApplication, QWidget, QPushButton, QLabel, QHBoxLayout, QVBoxLayout, QSplitter
from PyQt6.QtGui import QIcon, QPixmap, QPainter, QImage, QMatrix4x4, QQuaternion, QVector3D, QColor, QGuiApplication
from PyQt6.QtCore import QSize, Qt
import sys
from PyQt6.Qt3DCore import QEntity, QTransform, QAspectEngine
from PyQt6 import Qt3DRender
from PyQt6 import QtCore
from PyQt6.Qt3DRender import QCamera, QCameraLens, QRenderAspect, QSceneLoader, QPickingSettings, QFrameGraphNode, QRenderSurfaceSelector
from PyQt6.Qt3DInput import QInputAspect
from PyQt6.Qt3DExtras import QForwardRenderer, QPhongMaterial, QCylinderMesh, QSphereMesh, QTorusMesh, Qt3DWindow, QOrbitCameraController, QDiffuseSpecularMaterial, QTextureMaterial, QNormalDiffuseMapMaterial, QDiffuseMapMaterial 
from PyQt6.Qt3DExtras import QSphereMesh, QPlaneMesh

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.view = Qt3DWindow()
        self.container = QWidget.createWindowContainer(self.view)
        self.setCentralWidget(self.container)
        
 
        # Create & set layouts
        main_layout = QVBoxLayout()
        main_layout.addWidget(self.container)
        main_widget = QWidget()
        main_widget.setLayout(main_layout)
        self.setCentralWidget(main_widget)

        # Define object paths
        filepath1 = "some_path.obj"
        texture_filepath1 = "some_other_path.jpg"

        # Root entity
        self.rootEntity = QEntity()

        # Load mesh
        mesh_object = Qt3DRender.QMesh()
        mesh_object.setSource(QtCore.QUrl.fromLocalFile(filepath1))
        mesh_entity = QEntity(self.rootEntity)
        mesh_entity.addComponent(mesh_object)
 
        # Load/create textures
        # Using QTexture2D class
        mesh_texture_image = Qt3DRender.QTextureImage(self.rootEntity)
        mesh_texture_image.setSource(QtCore.QUrl.fromLocalFile(texture_filepath1))
        mesh_texture_image.setEnabled(True)
        mesh_texture_2D = Qt3DRender.QTexture2D(self.rootEntity)
        mesh_texture_2D.addTextureImage(mesh_texture_image)


        # Create Materials
        # Using QDiffuseMapMaterial
        diffuse_material = QDiffuseMapMaterial(self.rootEntity)
        diffuse_material.setDiffuse(mesh_texture_2D)
        diffuse_material.setShininess(0)

        # Add material components to entity
        mesh_entity.addComponent(diffuse_material)

        # Cull face
        render_settings = self.view.renderSettings()
        activeframegraph = render_settings.activeFrameGraph
        self.render_state_set = Qt3DRender.QRenderStateSet(render_settings)
        self.cull_face = Qt3DRender.QCullFace(self.render_state_set)
        self.cull_face.setMode(Qt3DRender.QCullFace.CullingMode.FrontAndBack)
        self.render_state_set.addRenderState(self.cull_face)
        #render_settings.setActiveFrameGraph(self.render_state_set)
        self.rootEntity.addComponent(render_settings)


        # Camera.
        camera = self.view.camera()
        camera.lens().setPerspectiveProjection(45.0, 16.0 / 9.0, 1, 1000.0)
        camera.setPosition(QVector3D(0.0, 0, 120.0))
        camera.setViewCenter(QVector3D(0.0, 100.0, 0.0))

        # For camera controls.
        camController = QOrbitCameraController(self.rootEntity)
        camController.setLinearSpeed(200.0)
        camController.setLookSpeed(280.0)
        camController.setCamera(camera)
        
        # Light
        light_entity = QEntity(self.rootEntity)
        light = Qt3DRender.QPointLight(light_entity)
        light.setConstantAttenuation(0)
        light_entity.addComponent(light)
        light_transform = QTransform()
        light_transform.setTranslation(QVector3D(350, 100, 200))
        light_entity.addComponent(light_transform)

        light_entity_2 = QEntity(self.rootEntity)
        light_2 = Qt3DRender.QPointLight(light_entity_2)
        light_2.setConstantAttenuation(0)
        light_entity_2.addComponent(light_2)
        light_transform = QTransform()
        light_transform.setTranslation(QVector3D(-350, 100, 200))
        light_entity_2.addComponent(light_transform)

        
        # Object picker
        self.object_picker = Qt3DRender.QObjectPicker(self.rootEntity)
        self.object_picker.pressed.connect(self.on_object_picked)
        self.rootEntity.addComponent(self.object_picker)        
        self.pick_set = self.view.renderSettings().pickingSettings()
        self.pick_set.setPickMethod(Qt3DRender.QPickingSettings.PickMethod.PrimitivePicking)
        

        self.view.setRootEntity(self.rootEntity)


    def on_object_picked(self, pick_event):
        print("Object picked at:", pick_event.worldIntersection().x(), pick_event.worldIntersection().y(), pick_event.worldIntersection().z())
        


class Application(QMainWindow):
    def __init__(self):
        super().__init__()
        #
        view3d = View3D()
        self.setCentralWidget(view3d)
        self.show()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.setGeometry(100, 100, 800, 600)
    window.setWindowTitle("3D Object Viewer")
    window.show()

    sys.exit(app.exec())

Upvotes: 0

Views: 73

Answers (1)

Sakthi
Sakthi

Reputation: 11

By using setActiveFramegraph method, you are creating a new framegraph for your application. When creating a new framegraph, you have to follow some rules, one of which is to set a surface on which your scene can be rendered. Before, Qt3D probably used default framegraph with view as the surface, now, you have to specify it manually in your new framegraph.

To do that, create a new Qt3DRender.QRenderSurfaceSelector object. Set view as it's surface using setSurface method and add viewport, camera selector and the renderStateSet. Framegraph is traversed depth-first. I have not used pyside before but below code should give you some idea on creating a new framegraph with QCullFace set.

self.render_surface_selector = Qt3DRender.QRenderSurfaceSelector
self.render_surface_selector.setSurface(self.view)
self.viewport = Qt3DRender.QViewport(self.render_surface_selector)
self.viewport.setNormalizedRect(QRectF(0, 0, 1.0, 1.0)); 
self.cam_controller = Qt3DRender.QCameraSelector(self.viewport)
self.cam_controller.setCamera(self.view.camera)
self.render_state_set = Qt3DRender.QRenderStateSet(self.cam_controller)
self.cull_face = Qt3DRender.QCullFace
self.cull_face.setMode(Qt3DRender.QCullFace.CullingMode.FrontAndBack)
self.render_state_set.addRenderState(self.cull_face)

self.view.setActiveFrameGraph(self.render_surface_selector)

Note the parent-child relationship between each framegraph nodes. However, to render both sides, don't you want to disable culling entirely? Currently, Qt3D probably has culling enabled for back-side. If this is the case, you may need to add this line instead.

self.cull_face.setMode(QCullFace.NoCulling)

This will disable culling globally, resulting in rendering of both sides of all faces. Cheers.

Upvotes: 0

Related Questions