Note
Go to the end to download the full example code
Light EffectsΒΆ
Lighting effect demonstration examples with adjustable parameters
import math
from PySide6 import QtWidgets, QtGui, QtCore
from wgpu.gui.qt import WgpuWidget
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 = WgpuWidget(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=[], index=None
):
checkbox = QtWidgets.QCheckBox(name)
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.Quaternion().set_from_euler(la.Euler(0.01, 0.02))
self.mesh.rotation.multiply(rot)
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
# light1.world.x = math.cos(t) * math.cos(3*t) * scale
# light1.world.y = math.cos(3*t) * math.sin(t) * scale
# light1.world.z = math.sin(3*t) * scale
renderer.render(scene, camera)
renderer.request_draw()
renderer.request_draw(animate)
if __name__ == "__main__":
app = QtWidgets.QApplication([])
window = LightViewer()
window.show()
app.exec()