Shaders#
The octarine.shaders module contains the custom materials and post-processing passes powering some of Octarine's features - see Effects & Shading for an introduction. Requires pygfx>=0.17.
Note that you will rarely have to touch these directly: they are typically employed via the respective octarine.Viewer methods (e.g. add_sparse_volume, set_silhouette or set_depth_of_field).
octarine.shaders.SparseVolume #
A sparse volume defined by brick-packed voxel data.
| PARAMETER | DESCRIPTION |
|---|---|
packed | TYPE: |
material | TYPE: |
octarine.shaders.SparseVolumeMaterial #
Material for rendering a SparseVolume.
In addition to the properties of pygfx.VolumeBasicMaterial (clim, map, gamma, interpolation, opacity), this material has a render_mode:
- "mip": maximum intensity projection (like
pygfx.VolumeMipMaterial) - "density": front-to-back emission/absorption for a cloud-like look;
densitysets the extinction per voxel - "iso" (alias "surface"): shaded isosurface at
threshold
| PARAMETER | DESCRIPTION |
|---|---|
render_mode | TYPE: |
step_size | TYPE: |
threshold | TYPE: |
density | TYPE: |
gradient_delta | TYPE: |
smoothing | TYPE: |
shininess | TYPE: |
emissive | TYPE: |
density property writable #
Extinction per voxel at the top of clim ("density" mode).
emissive property writable #
Color the isosurface emits regardless of lighting.
gradient_delta property writable #
Half-width (in voxels) of the normal's central differences.
render_mode property writable #
The render mode: "mip", "density" or "iso".
shininess property writable #
Size of the specular highlight on the isosurface.
smoothing property writable #
Width (in voxels) of the extra filter applied to the normal.
step_size property writable #
Ray-march step (in voxels) inside occupied bricks.
threshold property writable #
Isosurface level, as a fraction of clim ("iso" mode).
octarine.shaders.pack_sparse_voxels(voxels, values=None, clim=None, brick_size=16, max_atlas_dim=2048, fill_ratio_warn=0.005) #
Pack sparse (N, 3) voxel coordinates into a brick map.
| PARAMETER | DESCRIPTION |
|---|---|
voxels | TYPE: |
values | TYPE: |
clim | TYPE: |
brick_size | TYPE: |
max_atlas_dim | TYPE: |
fill_ratio_warn | TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
PackedBricks | |
octarine.shaders.BitmaskVolume #
A binary sparse volume defined by bitmask-packed voxel runs.
| PARAMETER | DESCRIPTION |
|---|---|
packed | TYPE: |
material | TYPE: |
octarine.shaders.BitmaskVolumeMaterial #
Material for rendering a BitmaskVolume.
The data is binary occupancy, so there is nothing to map onto a colormap: the volume is drawn in a single color.
| PARAMETER | DESCRIPTION |
|---|---|
render_mode | TYPE: |
color | TYPE: |
step_size | TYPE: |
threshold | TYPE: |
density | TYPE: |
gradient_delta | TYPE: |
smoothing | TYPE: |
shininess | TYPE: |
emissive | TYPE: |
color property writable #
Color of the volume.
density property writable #
Extinction per voxel ("density" mode).
emissive property writable #
Color the isosurface emits regardless of lighting.
gradient_delta property writable #
Half-width (in voxels) of the normal's central differences.
render_mode property writable #
The render mode: "mip", "density" or "iso".
shininess property writable #
Size of the specular highlight on the isosurface.
smoothing property writable #
Width (in voxels) of the extra filter applied to the normal.
step_size property writable #
Ray-march step (in voxels) inside occupied bricks.
threshold property writable #
Isosurface level ("iso" mode).
octarine.shaders.pack_voxel_runs(runs, brick_size=16, max_buffer_bytes=128 * 1024 ** 2) #
Pack (N, 4) run-length-encoded voxels into a bitmask brick map.
| PARAMETER | DESCRIPTION |
|---|---|
runs | TYPE: |
brick_size | TYPE: |
max_buffer_bytes | TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
PackedBitmask | |
octarine.shaders.runs_from_voxels(voxels) #
Convert (N, 3) voxel coordinates to (N, 4) runs along x.
| PARAMETER | DESCRIPTION |
|---|---|
voxels | TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
(M, 4) int64 array | Runs as (x, y, z, length), sorted by (z, y, x). |
octarine.shaders.TubeVisual #
A tube surface defined by per-node radial profile coefficients.
| PARAMETER | DESCRIPTION |
|---|---|
coefs | TYPE: |
edges | TYPE: |
material | TYPE: |
colors | TYPE: |
octarine.shaders.TubeMaterial #
Material for rendering a TubeVisual.
In addition to the properties of pygfx.MeshPhongMaterial, this material has the two angular level-of-detail knobs:
n_theta: number of angular samples around the tube (>= 3). This is both a uniform and the draw-call size; changing it neither re-uploads nor reallocates the coefficients.k_max: number of harmonics evaluated for the surface position. Clamped in the shader to the number actually present in the buffer; 0 renders circular tubes of radius a0.k_normal: number of harmonics evaluated for the normal, clamped tok_max. Deliberately separate and usually much lower - dr/dtheta weights harmonic k by k, so the harmonics that still sharpen the silhouette already make the shading look like sandpaper (and dark where the normal tilts past the view direction). 1 is the default, 0 the smooth-tube floor.
color_mode may be "uniform" (the material's color) or "vertex" (per-node RGBA from geometry.colors).
octarine.shaders.decimate_edges(edges, n_nodes, step) #
Thin an edge list step-fold along its unbranched runs.
Nodes of degree != 2 - branch points and tips - are always kept, so the topology is untouched and no arm can go missing; only the interior of each run is thinned. Striding the node array instead would silently reconnect unrelated branches.
Note that this leaves the surviving nodes' frames describing a centreline that no longer exists: the stored tangent is the local direction at full resolution, and the chord it is now swept along spans step times as much of the centreline's jitter. Follow with align_frames(..., retangent=True) or the decimated tube's rings sit further off their sweep than the undecimated ones did.
| PARAMETER | DESCRIPTION |
|---|---|
edges | TYPE: |
n_nodes | TYPE: |
step | TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
(E', 2) int32 array | |
octarine.shaders.align_frames(coefs, edges, retangent=True) #
Rebuild the per-node frame so that it is consistent along edges.
Two things about the frames as they arrive show up as jagged geometry, and this fixes both:
- The stored theta = 0 reference is rotation-minimizing only within an unbranched run. The chain restarts at every branch point, so an edge spanning a junction connects theta = 0 on one ring to an unrelated angle on the other and the quad between them is twisted. The runs also drift against a true rotation-minimizing frame in the interior. One chain is propagated over the whole tree here instead.
- The stored tangent is the centreline's local direction. For a skeleton traced from voxels that sits well off the chord the surface is actually swept along, which tilts the ring relative to its own sweep and is what makes consecutive rings punch through each other;
decimate_edgesmakes it worse, not better, because the chord then spans more of the jitter.retangentre-derives the tangent from the edge list.
The profile is rotated to follow the new frame, which is exact (see rotate_profile), so no shape is lost to the realignment itself. Re-tangenting does move the surface - the profile was measured in the old cross-section plane and is applied in a slightly tilted one - but that error is second order in the tilt while the punch-through it removes is first order.
| PARAMETER | DESCRIPTION |
|---|---|
coefs | TYPE: |
edges | TYPE: |
retangent | TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
(M, 8 + 2K) float32 array | |
octarine.shaders.rotate_profile(coefs, psi) #
Rotate each node's profile by psi (M,) radians about its own tangent.
Exact, and the reason the frame can be rebuilt without losing any shape: substituting theta -> theta + psi into
r(theta) = a0 + sum_k [a_k cos(k theta) + b_k sin(k theta)]
and re-collecting gives
a'_k = a_k cos(k psi) + b_k sin(k psi)
b'_k = b_k cos(k psi) - a_k sin(k psi)
i.e. each harmonic rotates by k psi, and a0 is untouched.
| PARAMETER | DESCRIPTION |
|---|---|
coefs | TYPE: |
psi | TYPE: |
octarine.shaders.split_runs(edges, n_nodes) #
Split an edge list into its unbranched runs.
Each run is a list of node indices [start, ...interior..., end] whose ends have degree != 2 - branch points and tips - and whose interior is all degree 2. Every edge belongs to exactly one run. A closed loop, which has no node of degree != 2 to start from, is broken open at an arbitrary node and comes back with run[0] == run[-1].
| PARAMETER | DESCRIPTION |
|---|---|
edges | TYPE: |
n_nodes | TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
list of list of int | |
octarine.shaders.SilhouetteMeshMaterial #
A Phong mesh material with a Neuroglancer-style silhouette effect.
In addition to the properties of pygfx.MeshPhongMaterial, this material has a silhouette exponent: fragments are multiplied by pow(1 - |dot(normal, view_dir)|, silhouette), making face-on regions transparent and emphasizing edges/creases. 0 disables the effect; typical values are 1-8.
Note that for the transparency to composite sensibly the material should use a transparent alpha_mode (octarine defaults to "weighted_blend").
silhouette property writable #
Silhouette exponent; 0 disables the effect.
octarine.shaders.SubsurfaceMeshMaterial #
A Phong mesh material with subsurface scattering (translucency).
In addition to the properties of pygfx.MeshPhongMaterial (and the silhouette exponent of SilhouetteMeshMaterial, which is off by default here), this material scatters light through the surface.
| PARAMETER | DESCRIPTION |
|---|---|
subsurface | TYPE: |
scatter_color | |
thickness | TYPE: |
distortion | TYPE: |
falloff | TYPE: |
wrap | TYPE: |
glow | TYPE: |
Notes
The transmission term deliberately ignores shadows: light that scatters through an object is exactly the light that did not reach the surface directly, so attenuating it by a shadow lookup would cancel the effect.
distortion property writable #
How far transmitted light bends along the normal, in [0, 1].
falloff property writable #
Exponent of the transmission lobe; higher tightens the glow.
glow property writable #
View-independent floor added to the transmission.
scatter_color property writable #
The color light picks up while travelling through the material.
subsurface property writable #
Master strength of the scattering; 0 disables the effect.
thickness property writable #
How much material the light has to cross, in [0, 1].
wrap property writable #
How far diffuse light bleeds past the terminator, in [0, 1].
octarine.shaders.MatcapMeshMaterial #
A mesh material shaded by a matcap instead of by the scene's lights.
The surface normal in view space looks up a color in the matcap image (a picture of a shaded sphere), which becomes the color of the fragment. The scene's lights, shadows and ambient occlusion play no part in it.
In addition to the properties of pygfx.MeshBasicMaterial:
| PARAMETER | DESCRIPTION |
|---|---|
matcap | TYPE: |
tint | TYPE: |
kwargs | TYPE: |
octarine.shaders.make_matcap(preset='pearl', *, size=256, environment=None, **overrides) #
Render a matcap image by lighting a virtual sphere.
| PARAMETER | DESCRIPTION |
|---|---|
preset | TYPE: |
size | TYPE: |
environment | |
**overrides | DEFAULT: |
| RETURNS | DESCRIPTION |
|---|---|
(size, size, 4) float32 array | Linear (physical) RGBA. Highlights may well exceed 1 - the image is high dynamic range, like the environments it is lit with. |
octarine.shaders.matcap_texture(matcap='pearl', *, size=256, **kwargs) #
Build a matcap texture, ready to be assigned to MatcapMeshMaterial.
| PARAMETER | DESCRIPTION |
|---|---|
matcap | TYPE: |
size | TYPE: |
**kwargs | DEFAULT: |
| RETURNS | DESCRIPTION |
|---|---|
pygfx.TextureMap | |
octarine.shaders.GradientBackgroundMaterial #
A radial gradient background.
The color ramp runs from color_inner at the center, through color_mid half-way out, to color_outer at (and beyond) radius. Distances are measured in units of the canvas width, so the gradient stays circular whatever the window's aspect ratio.
| PARAMETER | DESCRIPTION |
|---|---|
colors | TYPE: |
center | TYPE: |
radius | TYPE: |
falloff | TYPE: |
vignette | TYPE: |
kwargs | TYPE: |
center property writable #
Center of the gradient; (0, 0) is the top left corner.
color_inner property writable #
Color at the center of the gradient.
color_mid property writable #
Color half-way between the center and radius.
color_outer property writable #
Color at (and beyond) radius.
colors property writable #
The gradient's (inner, mid, outer) colors.
falloff property writable #
Shape of the radial ramp; > 1 keeps the core bright.
radius property writable #
Radius of the gradient as a fraction of the canvas width.
vignette property writable #
Strength (0-1) of the darkening towards the corners.
from_preset(preset, **kwargs) classmethod #
Create a material from one of the BACKGROUND_PRESETS.
| PARAMETER | DESCRIPTION |
|---|---|
preset | TYPE: |
kwargs | TYPE: |
set_colors(*colors) #
Set the gradient's colors.
Accepts three colors (inner, mid, outer) or two (inner, outer), in which case the mid stop is the average of the two. Colors can be passed as separate arguments or as a single sequence.
octarine.shaders.FlexPointsMaterial #
A marker points material with flexible size/edge-width spaces.
In addition to the properties of pygfx.PointsMarkerMaterial, this material supports:
edge_size_space: coordinate space foredge_width("screen", "world" or "model"). If None (default),edge_widthuses the same space assize(i.e.size_space), matching stock pygfx behavior.min_size/max_size: clamp the final on-screen point size, in logical pixels. Useful withsize_space="world"to keep far-away points visible ("world-sized but at least N pixels"). The clamp is applied after the size has been converted to screen pixels, so it also works with per-vertex sizes. If both are set,max_sizewins.min_edge_width: a floor for the final on-screen edge width, in logical pixels. Only applies whenedge_width> 0 (i.e. it does not conjure up an edge that was disabled).
edge_size_space property writable #
The coordinate space in which the edge width is expressed.
One of "screen", "world" or "model" (see :obj:pygfx.utils.enums.CoordSpace), or None to use the same space as the point size (size_space).
max_size property writable #
Maximum on-screen point size in logical pixels (None = no maximum).
min_edge_width property writable #
Minimum on-screen edge width in logical pixels (0 = no minimum).
min_size property writable #
Minimum on-screen point size in logical pixels (0 = no minimum).
octarine.shaders.DepthOfFieldPass #
A depth-of-field (focal blur) post-processing pass.
| PARAMETER | DESCRIPTION |
|---|---|
camera | TYPE: |
focus | TYPE: |
aperture | TYPE: |
max_radius | TYPE: |
num_taps | TYPE: |
smooth | TYPE: |
snap_radius | TYPE: |
aperture property writable #
Blur strength (px of blur radius at 100% relative defocus).
focus property writable #
Focal distance in world units; None means autofocus.
max_radius property writable #
Upper limit for the blur radius in physical pixels.
num_taps property writable #
Number of samples in the blur kernel.
smooth property writable #
Approximate autofocus re-focus time in seconds; 0 = instant.
snap_radius property writable #
Autofocus search radius around the view center in px; 0 = off.
get_focus_position(renderer) #
Return the world position of the current focal point.
With a fixed focus this is the point at the focus distance along the view axis; with autofocus (focus=None) it is the surface point at the center of the view, read back from the depth buffer of the last rendered frame.
| PARAMETER | DESCRIPTION |
|---|---|
renderer | TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
(3,) array | None | None if the position cannot be determined - i.e. with autofocus when nothing has been rendered yet or there is no object under the center of the view. |
octarine.shaders.NormalizedDepthPass #
Render normalized depth as shades of grey.
Unlike pygfx's DepthPass (which renders the raw - highly non-linear - depth buffer), this linearizes depth to view-space distance and stretches it over the depth range actually covered by the geometry on screen: the nearest visible surface renders black, the farthest light grey, and the (empty) background stays white.
| PARAMETER | DESCRIPTION |
|---|---|
camera | TYPE: |
overlay | TYPE: |
strength | TYPE: |
octarine.shaders.AmbientOcclusionPass #
A screen-space ambient occlusion (SSAO) post-processing pass.
Estimates how much of the hemisphere above each pixel is blocked by nearby geometry and darkens the image accordingly, which brings out creases, cavities and the contact points between objects.
| PARAMETER | DESCRIPTION |
|---|---|
camera | TYPE: |
radius | TYPE: |
intensity | TYPE: |
bias | TYPE: |
samples | TYPE: |
power | TYPE: |
blur | TYPE: |
debug | TYPE: |
bias property writable #
Occluders nearer than this fraction of radius are ignored.
blur property writable #
Radius of the bilateral blur in pixels; 0 disables it.
debug property writable #
Whether to render the occlusion itself instead of the scene.
intensity property writable #
Strength of the darkening (0 = off, 1 = black where fully occluded).
power property writable #
Exponent applied to the occlusion.
radius property writable #
How far to look for occluders, in world units.
samples property writable #
Number of hemisphere samples per pixel.
octarine.shaders.OutlinePass #
An outline (edge detection) post-processing pass.
Finds silhouettes and creases in the depth buffer and draws them in color.
| PARAMETER | DESCRIPTION |
|---|---|
camera | TYPE: |
color | TYPE: |
thickness | TYPE: |
depth_threshold | TYPE: |
normal_threshold | TYPE: |
debug | TYPE: |
color property writable #
Color of the outline; its alpha is the strength of the effect.
debug property writable #
Whether to render the edges themselves instead of the scene.
depth_threshold property writable #
Relative step in depth that counts as a separate object.
normal_threshold property writable #
How sharp a fold counts as a crease, as 1 - cos(angle); 0 = off.
thickness property writable #
Width of the outline in physical pixels.
octarine.shaders.ToneMappingPass #
A tone mapping (and exposure) post-processing pass.
Compresses the rendered high dynamic range image into the range the display can show, so that bright regions roll off smoothly instead of clipping to flat white.
| PARAMETER | DESCRIPTION |
|---|---|
mode | TYPE: |
exposure | TYPE: |
white_point | TYPE: |
exposure property writable #
Scales the image before the curve is applied.
mode property writable #
The tone mapping curve; one of TONEMAP_MODES.
stops property writable #
The exposure expressed in stops; 0 leaves the image unchanged.
white_point property writable #
The input value that maps to white ("reinhard" and "filmic" only).
octarine.shaders.procedural_env_map(preset='studio', *, resolution=128, rotation=0.0, **overrides) #
Build an environment cube map from the analytic model.
| PARAMETER | DESCRIPTION |
|---|---|
preset | TYPE: |
resolution | TYPE: |
rotation | TYPE: |
**overrides | DEFAULT: |
| RETURNS | DESCRIPTION |
|---|---|
pygfx.TextureMap | A mipmapped |
octarine.shaders.environment_radiance(directions, preset='studio', *, rotation=0.0, **overrides) #
Evaluate the analytic environment model in the given directions.
This is what procedural_env_map bakes into a cube map; it is exposed separately because the matcap generator lights its sphere with the same model (see octarine.shaders.matcap).
| PARAMETER | DESCRIPTION |
|---|---|
directions | TYPE: |
preset | TYPE: |
rotation | TYPE: |
**overrides | DEFAULT: |
| RETURNS | DESCRIPTION |
|---|---|
(..., 3) array | Linear (physical) radiance. Values well above 1 are normal - that is what a light source looks like. |
octarine.shaders.cube_directions(size) #
World-space direction of every texel of a cube map.
| PARAMETER | DESCRIPTION |
|---|---|
size | TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
(6, size, size, 3) array | Unit vectors, one per texel, in the face order wgpu expects (+X, -X, +Y, -Y, +Z, -Z). |
Notes
Cube maps are specified in a left-handed coordinate system while pygfx is right-handed, so pygfx negates the x component when it samples one (both for the skybox background and for env_map lookups). The directions returned here have that flip applied already, i.e. they are the world direction that will end up sampling the corresponding texel.