Light Effects

Lighting effect demonstration examples with adjustable parameters

import math

from PySide6 import QtWidgets, QtGui, QtCore
from rendercanvas.qt import QRenderWidget
import pygfx as gfx
import pylinalg as la


class LightViewer(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Light_viewer")
        self.resize(800, 600)
        self.wgpu_widget = QRenderWidget(max_fps=60)

        main_layout = QtWidgets.QHBoxLayout()
        main_layout.addWidget(self.wgpu_widget, 1)
        main_layout.addSpacing(10)

        self.btn_layout = QtWidgets.QVBoxLayout()
        main_layout.addLayout(self.btn_layout)

        self.setLayout(main_layout)

        self.init_scene()
        self.init_gui()

    def init_gui(self):
        self.mesh_flat_checkbox = self.create_checkbox(
            "Flat Shading", self.mesh.material, "flat_shading"
        )

        self.mesh_wireframe_checkbox = self.create_checkbox(
            "Wireframe", self.mesh.material, "wireframe"
        )

        self.mesh_side_checkbox = self.create_checkbox(
            "Back Side",
            None,
            None,
            lambda c: setattr(self.mesh.material, "side", "back" if c else "front"),
        )

        self.mesh_rotate_checkbox = self.create_checkbox("Auto Rotate")

        self.mesh_color_btn = self.create_color_btn(
            "Diffuse", self.mesh.material, "color"
        )

        self.mesh_specular_btn = self.create_color_btn(
            "Specular", self.mesh.material, "specular"
        )

        self.mesh_emissive_btn = self.create_color_btn(
            "Emissive", self.mesh.material, "emissive"
        )

        self.create_slider("Shininess", 1, 100, self.mesh.material, "shininess")

        self.add_split()

        self.point_light1_move = self.create_checkbox("Auto Move")

        self.point_light1_checkbox = self.create_checkbox(
            "Point Light 1",
            self.point_light1,
            "visible",
            toggle=[
                self.create_color_btn(
                    "Color",
                    self.point_light1,
                    "color",
                ),
                self.create_slider(
                    "Intensity", 0, 5, self.point_light1, "intensity", step=0.01
                ),
                self.point_light1_move,
            ],
            index=self.btn_layout.indexOf(self.point_light1_move),
        )

        self.add_split()

        self.point_light2_move = self.create_checkbox("Auto Move")
        self.point_light2_checkbox = self.create_checkbox(
            "Point Light 2",
            self.point_light2,
            "visible",
            toggle=[
                self.create_color_btn(
                    "Color",
                    self.point_light2,
                    "color",
                ),
                self.create_slider(
                    "Intensity", 0, 5, self.point_light2, "intensity", step=0.01
                ),
                self.point_light2_move,
            ],
            index=self.btn_layout.indexOf(self.point_light2_move),
        )

        self.add_split()

        self.directional_light_checkbox = self.create_checkbox(
            "Directional Light",
            self.directional_light,
            "visible",
            index=self.btn_layout.count(),
            toggle=[
                self.create_color_btn(
                    "Color",
                    self.directional_light,
                    "color",
                ),
                self.create_slider(
                    "Intensity", 0, 5, self.directional_light, "intensity", step=0.01
                ),
            ],
        )

        self.add_split()

        self.ambient_light_checkbox = self.create_checkbox(
            "Ambient Light",
            self.ambient_light,
            "visible",
            index=self.btn_layout.count(),
            toggle=[
                self.create_color_btn("Color", self.ambient_light, "color"),
                self.create_slider(
                    "Intensity", 0, 5, self.ambient_light, "intensity", step=0.01
                ),
            ],
        )

        self.btn_layout.addStretch(1)

    def add_split(self):
        self.btn_layout.addSpacing(5)
        self.btn_layout.addWidget(QtWidgets.QLabel("-----------------------"))
        self.btn_layout.addSpacing(5)

    def create_color_btn(self, name, target, property, callback=None):
        layout = QtWidgets.QHBoxLayout()

        layout.addWidget(QtWidgets.QLabel(name))

        color_btn = QtWidgets.QPushButton()
        color_btn.setStyleSheet("background-color: %s" % getattr(target, property).hex)

        def set_color():
            color = QtWidgets.QColorDialog.getColor(
                QtGui.QColor(getattr(target, property).hex)
            )
            if color.isValid():
                color_btn.setStyleSheet("background-color: %s" % color.name())
                setattr(target, property, color.name())
                if callback:
                    callback(color.name())

        color_btn.clicked.connect(set_color)

        layout.addWidget(color_btn)
        self.btn_layout.addLayout(layout)
        return color_btn

    def create_checkbox(
        self, name, target=None, property=None, callback=None, toggle=None, index=None
    ):
        checkbox = QtWidgets.QCheckBox(name)
        if not toggle:
            toggle = []
        if target and property:
            checkbox.setChecked(bool(getattr(target, property)))

        def set_property(*args):
            if target and property:
                setattr(target, property, checkbox.isChecked())
            for e in toggle:
                e.setEnabled(checkbox.isChecked())
            if callback:
                callback(checkbox.isChecked())

        checkbox.toggled.connect(set_property)

        set_property()
        if index is not None:
            self.btn_layout.insertWidget(index, checkbox)
        else:
            self.btn_layout.addWidget(checkbox)
        return checkbox

    def create_slider(self, name, min, max, target, property, step=1, callback=None):
        layout = QtWidgets.QHBoxLayout()
        layout.addWidget(QtWidgets.QLabel(name))
        slide = QtWidgets.QSlider(QtCore.Qt.Horizontal)
        slide.setMinimum(min / step)
        slide.setMaximum(max / step)
        # slide.setSingleStep(step)
        val = getattr(target, property)
        slide.setValue(val / step)

        if isinstance(step, float):
            val_label = QtWidgets.QLabel(f"{float(val):3.2f}")
        else:
            val_label = QtWidgets.QLabel(f"{int(val):03d}")

        layout.addWidget(val_label)

        def set_value(value):
            value = value * step
            if isinstance(step, float):
                val_label.setText(f"{float(value):3.2f}")
            else:
                val_label.setText(f"{int(value):03d}")
            setattr(target, property, value)
            if callback:
                callback(value)

        slide.valueChanged.connect(set_value)

        layout.addWidget(slide)

        self.btn_layout.addLayout(layout)
        return slide

    def init_scene(self):
        renderer = gfx.renderers.WgpuRenderer(self.wgpu_widget)
        scene = gfx.Scene()
        self.scene = scene

        self.mesh = gfx.Mesh(
            # gfx.box_geometry(20, 20, 20),
            gfx.torus_knot_geometry(10, 3, 128, 32),
            material=gfx.MeshPhongMaterial(color="#00aaff"),
        )

        # mesh.rotation.set_from_euler(la.Euler(math.pi / 6, math.pi / 6))
        scene.add(self.mesh)

        # Point Light1
        point_light1 = gfx.PointLight("#ffffff")
        self.point_light1 = point_light1
        point_light1.world.x = 25
        point_light1.world.y = 20

        point_light1.add(gfx.PointLightHelper())
        scene.add(point_light1)

        # Point Light2
        point_light2 = gfx.PointLight("#80ff80")
        self.point_light2 = point_light2
        point_light2.visible = False
        point_light2.world.x = -25
        point_light2.world.y = 20

        point_light2.add(gfx.PointLightHelper())
        scene.add(point_light2)

        # Directional light
        directional_light = gfx.DirectionalLight("#ffff00")
        self.directional_light = directional_light
        directional_light.visible = False
        directional_light.world.x = -25
        directional_light.world.y = 20

        directional_light.add(gfx.DirectionalLightHelper(5))
        scene.add(directional_light)

        # Ambient light
        self.ambient_light = gfx.AmbientLight()
        scene.add(self.ambient_light)

        camera = gfx.PerspectiveCamera(70, 16 / 9)
        camera.world.z = 50
        camera.show_pos((0, 0, 0))

        gfx.OrbitController(camera, register_events=renderer)

        t1 = 0
        t2 = 0
        scale = 30

        point_light1.world.x = math.sin(t1 + math.pi / 3) * scale
        point_light1.world.y = math.sin(t1 + 1) * 5 + 15
        point_light1.world.z = math.cos(t1 + math.pi / 3) * scale

        point_light2.world.x = math.sin(t2 - math.pi / 3) * scale
        point_light2.world.y = math.sin(t2 + 2) * 5 + 15
        point_light2.world.z = math.cos(t2 - math.pi / 3) * scale

        def animate():
            if self.mesh_rotate_checkbox.isChecked():
                rot = la.quat_from_euler((0.01, 0.02, 0.0))
                self.mesh.local.rotation = la.quat_mul(rot, self.mesh.local.rotation)

            nonlocal t1, t2, scale

            if self.point_light1_move.isChecked() and self.point_light1.visible:
                t1 += 0.01
                point_light1.world.x = math.sin(t1 + math.pi / 3) * scale
                point_light1.world.y = math.sin(t1 + 1) * 5 + 15
                point_light1.world.z = math.cos(t1 + math.pi / 3) * scale

            if self.point_light2_move.isChecked() and self.point_light2.visible:
                t2 += 0.02
                point_light2.world.x = math.sin(t2 - math.pi / 3) * scale
                point_light2.world.y = math.sin(t2 + 2) * 5 + 15
                point_light2.world.z = math.cos(t2 - math.pi / 3) * scale

            renderer.render(scene, camera)
            renderer.request_draw()

        renderer.request_draw(animate)


if __name__ == "__main__":
    app = QtWidgets.QApplication([])
    window = LightViewer()
    window.show()
    app.exec()

Gallery generated by Sphinx-Gallery

Interactive example

Try this example in your browser using Pyodide. Might not work with all examples and all devices. Check the output and your browser’s console for details.