Stanford dragon#
References
The classic scan - 871k triangles - read with VTK and path traced. The model is the reason to reach for Cinematic rendering here: the scales and the coiled tail are all concavity, so the shadow under the belly, the light the floor throws back into the flank, and the sheen running along the spine come out of the simulation rather than from a screen-space approximation.
Two details do most of the work. The scan is authored Y-up while K3D is Z-up, so it is rotated in VTK rather than through the mesh’s model matrix - that keeps the bounds honest, and the camera and the floor below are derived from them. And the floor is not decoration: it is where the bounced light comes from, and without it a path traced model floats in the environment and reads flatter than it is.
Both images are the same scene at 256 samples: the gallery thumbnail accumulates
them once, when these pages are built, the plot below in your browser.
tone_mapping='aces' matters at this budget - bounced light between the gold
and the floor genuinely exceeds 1.0, and without a curve it clips.
import zipfile
import numpy as np
import vtk
import k3d
from k3d.helpers import download
filename = download('https://casual-effects.com/g3d/data10/research/model/dragon/dragon.zip')
with zipfile.ZipFile(filename) as archive:
archive.extract('dragon.obj')
reader = vtk.vtkOBJReader()
reader.SetFileName('dragon.obj')
triangles = vtk.vtkTriangleFilter()
triangles.SetInputConnection(reader.GetOutputPort())
to_z_up = vtk.vtkTransform()
to_z_up.RotateX(90)
upright = vtk.vtkTransformPolyDataFilter()
upright.SetTransform(to_z_up)
upright.SetInputConnection(triangles.GetOutputPort())
upright.Update()
dragon = upright.GetOutput()
bounds = np.array(dragon.GetBounds()).reshape(3, 2)
centre = bounds.mean(axis=1)
size = float((bounds[:, 1] - bounds[:, 0]).max())
plot = k3d.plot(renderer='cinematic',
environment='venice_sunset',
tone_mapping='aces',
grid_visible=False,
camera_auto_fit=False,
background_color=0x2A2C30,
cinematic_samples=256,
cinematic_bounces=6)
plot += k3d.vtk_poly_data(dragon,
color=0xC8A15A,
flat_shading=False,
roughness=0.2,
metalness=0.7,
compression_level=5,
name='dragon')
floor_z = float(bounds[2, 0])
span = 0.9 * size
plot += k3d.mesh(np.array([[centre[0] - span, centre[1] - span, floor_z],
[centre[0] + span, centre[1] - span, floor_z],
[centre[0] + span, centre[1] + span, floor_z],
[centre[0] - span, centre[1] + span, floor_z]], np.float32),
np.array([[0, 1, 2], [0, 2, 3]], np.uint32),
color=0x9AA0A6,
roughness=0.2,
metalness=0.7,
name='floor')
eye = centre + np.array([1.02, 0.245, 0.38]) * size
plot.camera = [*eye, *centre, 0, 0, 1]
plot.display()
What the parameters above do, and the same dragon under six different
environments, are on the Cinematic rendering page. The same model under advanced,
and the rest of the walkthrough, is in examples/stanford_dragon.ipynb.