luv

Workshop wiki

flame-shaders.lisp

luft/render/flame-shaders.lisp

system luft/renderer · 34 definitions · on GitHub

in-package#:luft.render

Animated torch-flame substrate

A torch remains a sparse semantic attachment, while its rendered body and flame share one frame realized against the final surface. The GPU boundary is deliberately plain: three Vec4 rows containing origin/seed, normal/flags, and tangent/scale. Its CPU functions are scalar references for the shader field and integral rather than a second retained representation.

eval-when
:compile-toplevel:load-toplevel:execute
defconstant+torch-flame-instance-row-count+3
defconstant+torch-flame-instance-scalar-count+12
defconstant+torch-flame-sample-count+9
defconstant+torch-flame-wick-offset+0.5f0
defconstant+torch-flame-length+0.42f0
defconstant+torch-flame-proxy-radius+0.38f0
defconstant+torch-flame-wall-bend+0.055f0
defconstant+torch-flame-extinction+6.4f0

The authored material supplies hue and baseline HDR strength. Heat is a dimensionless scalar response, not a second hidden rgb palette.

defconstant+torch-flame-cool-radiance-scale+1.0f0
defconstant+torch-flame-heat-radiance-gain+2.0f0
defconstant+torch-flame-frame-tolerance+2.0f-4
defconstant+torch-flame-maximum-flags+#.
1-
ash124

The one exact 24-bit float lane is shared by both renderers. Keeping the material and sampled light beside the realized frame makes flame and body publication one immutable transaction instead of two index-coupled sidecars.

defconstant+torch-body-assembly-bit-count+12
defconstant+torch-body-light-bit-count+12
defconstant+torch-body-vertex-row-count+2
defconstant+torch-body-vertex-scalar-count+8
defconstant+torch-body-side-count+8
deftypetorch-flame-instance-data
'
simple-arraysingle-float
12
defunpack-torch-body-frame-flags
assembly-idpacked-voxel-light

Pack a 12-bit material assembly and RGB4 voxel light into frame FLAGS.

check-typeassembly-id
unsigned-byte12
check-typepacked-voxel-light
unsigned-byte12
logiorassembly-id
ashpacked-voxel-light+torch-body-assembly-bit-count+
defununpack-torch-body-frame-flags
flags

Return the assembly id and packed RGB4 voxel light encoded by flags.

unless
and
realpflags
=flags
floorflags
<=0flags+torch-flame-maximum-flags+
error"Torch body flags are not an exact 24-bit nonnegative integer: ~S."flags
let
flags
floorflags
values
ldb
byte+torch-body-assembly-bit-count+0
flags
ldb
byte+torch-body-light-bit-count++torch-body-assembly-bit-count+
flags
defun%torch-flame-finite-single-float-p
value
and
=valuevalue
<=
absvalue
most-positive-single-float
defunvalidate-torch-flame-frame
data&optional
offset0

Validate one three-Vec4 torch frame in data starting at offset.

The normal and tangent must already be unit length and mutually orthogonal. FLAGS is an exactly represented nonnegative integer in the normal row's W lane, SEED is in [0,1), and scale is strictly positive. Return data.

check-typedata
arraysingle-float
check-typeoffset
integer0*
unless
<=
+offset+torch-flame-instance-scalar-count+
lengthdata
error"Torch frame at ~D exceeds a ~D-scalar array."offset
lengthdata
loopforindexfromoffsetbelow
+offset+torch-flame-instance-scalar-count+
forvalue=
arefdataindex
unlessdo
error"Torch frame scalar ~D is not finite: ~S."indexvalue
let*
seed
arefdata
+offset3
nx
arefdata
+offset4
ny
arefdata
+offset5
nz
arefdata
+offset6
flags
arefdata
+offset7
tx
arefdata
+offset8
ty
arefdata
+offset9
tz
arefdata
+offset10
scale
arefdata
+offset11
normal-length-squared
+
*nxnx
*nyny
*nznz
tangent-length-squared
+
*txtx
*tyty
*tztz
normal-tangent-dot
+
*nxtx
*nyty
*nztz
unless
and
<=0.0f0seed
<seed1.0f0
error"Torch frame seed must be in [0,1), not ~S."seed
unless
<=
abs
-normal-length-squared1.0f0
+torch-flame-frame-tolerance+
error"Torch frame normal is not unit length: (~S ~S ~S)."nxnynz
unless
<=
abs
-tangent-length-squared1.0f0
+torch-flame-frame-tolerance+
error"Torch frame tangent is not unit length: (~S ~S ~S)."txtytz
unless
<=
absnormal-tangent-dot
+torch-flame-frame-tolerance+
error"Torch frame normal and tangent are not orthogonal: ~S."normal-tangent-dot
unless
and
<=0.0f0flags
coerce+torch-flame-maximum-flags+'single-float
=flags
floorflags
error"Torch frame flags are not an exact 24-bit nonnegative integer: ~S."flags
unless
pluspscale
error"Torch frame scale must be positive, not ~S."scale
data
defunpack-torch-flame-frame
origin-xorigin-yorigin-zseednormal-xnormal-ynormal-zflagstangent-xtangent-ytangent-zscale

Pack and validate one arbitrary realized torch frame as three Vec4 rows.

let
data
make-array+torch-flame-instance-scalar-count+:element-type'single-float:initial-contents
mapcar
lambda
value
coercevalue'single-float
listorigin-xorigin-yorigin-zseednormal-xnormal-ynormal-zflagstangent-xtangent-ytangent-zscale
data
defununpack-torch-flame-frame
data&optional
offset0

Return origin, seed, normal, flags, tangent, and scale for one frame.

values-list
loopforindexfromoffsetbelow
+offset+torch-flame-instance-scalar-count+
collect
arefdataindex
defun%torch-flame-reference-seed
tick-xtick-ytick-z
let
value
*
sin
+
*tick-x0.01731
*tick-y0.01173
*tick-z0.02357
43758.5453
-value
floorvalue
defuntorch-flame-face-seed
face

Return face's stable animation seed without realizing a surface frame.

unless
error"A torch seed needs an oriented face site, not ~S."face
let
ticks
loopforcoordinateinforaxis-numberbelow3collect
+
*8coordinate
if
logbitpaxis-number
40
%torch-flame-reference-seed
firstticks
secondticks
thirdticks
defun%torch-flame-authored-hdr-radiance

Return the flame material's authored linear HDR radiance as three values.

destructuring-bind
redgreenblue
material-kind-base-tone*torch-flame-material*
let
strength
material-kind-surface-emission*torch-flame-material*
unless
and
realpstrength
not
minuspstrength
every
lambda
channel
and
realpchannel
not
minuspchannel
listredgreenblue
error"Torch flame radiance must be nonnegative, not tone ~S at ~S."
listredgreenblue
strength
values
*redstrength
*greenstrength
*bluestrength
defuntorch-flame-effect-uniform-data
time

Return one float32 Vec4 holding current time and authored linear HDR rgb.

The rgb lanes are the flame material's base tone times its surface emission. The caller owns the clock; this interface never consults wall time, so captures and CPU/GPU comparisons can replay the flame effect exactly.

check-typetimereal
multiple-value-bind
redgreenblue
make-array4:element-type'single-float:initial-contents
mapcar
lambda
value
coercevalue'single-float
listtimeredgreenblue
defun%torch-flame-clamp
valuelowhigh
maxlow
minhighvalue
defun%torch-flame-smoothstep
lowhighvalue
let
amount
%torch-flame-clamp
/
-valuelow
-highlow
0.01.0
*amountamount
-3.0
*2.0amount
defun%torch-flame-fract
value
-value
floorvalue
defun%torch-flame-reference-field
instancepoint-xpoint-ypoint-ztime
let*
origin-x
arefinstance0
origin-y
arefinstance1
origin-z
arefinstance2
seed
arefinstance3
nx
arefinstance4
ny
arefinstance5
nz
arefinstance6
tx
arefinstance8
ty
arefinstance9
tz
arefinstance10
scale
arefinstance11

B = N x T, hence T x B = N for the validated orthonormal pair.

bx
-
*nytz
*nzty
by
-
*nztx
*nxtz
bz
-
*nxty
*nytx

Project world up into the actual surface tangent plane. Its length continuously replaces the old axis-specific wallness switch.

gravity-x
*
-nz
nx
gravity-y
*
-nz
ny
gravity-z
-1.0
*nznz
gravity-strength
sqrt
max0.0
+
*gravity-xgravity-x
*gravity-ygravity-y
*gravity-zgravity-z
gravity-divisor
maxgravity-strength1.0e-6
gravity-x
/gravity-xgravity-divisor
gravity-y
/gravity-ygravity-divisor
gravity-z
/gravity-zgravity-divisor
wick-distance
*+torch-flame-wick-offset+scale
flame-length
*+torch-flame-length+scale
wick-x
+origin-x
*nxwick-distance
wick-y
+origin-y
*nywick-distance
wick-z
+origin-z
*nzwick-distance
qx
-point-xwick-x
qy
-point-ywick-y
qz
-point-zwick-z
axial
/
+
*qxnx
*qyny
*qznz
flame-length
height-squared
sway-u
*scale0.105height-squared
sin
+
*time5.1
*seed19.7
sway-v
*scale0.075height-squared
sin
+
*time6.7
*seed31.1
gravity-bend
*scale+torch-flame-wall-bend+gravity-strengthheight-squared
center-x
+wick-x
*nxflame-lengthheight
*txsway-u
*bxsway-v
*gravity-xgravity-bend
center-y
+wick-y
*nyflame-lengthheight
*tysway-u
*bysway-v
*gravity-ygravity-bend
center-z
+wick-z
*nzflame-lengthheight
*tzsway-u
*bzsway-v
*gravity-zgravity-bend
rx
-point-xcenter-x
ry
-point-ycenter-y
rz
-point-zcenter-z
radial
sqrt
+
*rxrx
*ryry
*rzrz
radius
*scale
+0.055
*0.13bulge
signed-distance
-radialradius
begin
end
-1.0
inside
-1.0
%torch-flame-smoothstep-0.0250.035signed-distance
wave
+0.5
*0.5
sin
+
*point-x17.1
*point-y13.7
*point-z19.3
*time-8.1
*seed23.9
fine
+0.5
*0.5
sin
+
*point-x-31.7
*point-y27.3
*point-z23.1
*time11.3
*seed7.7
density
*beginendinside
+0.68
*0.22wave
*0.10fine
centrality
%torch-flame-clamp
/
-signed-distance
maxradius0.001
0.01.0
heat
%torch-flame-clamp
+0.20
*0.95centrality
*-0.32height
0.01.0
valuessigned-distancedensityheat
defuntorch-flame-reference-signed-distance
instancepoint-xpoint-ypoint-ztime

Evaluate the scalar flame envelope used by the GPU at one world point.

nth-value0
%torch-flame-reference-fieldinstancepoint-xpoint-ypoint-ztime
defuntorch-flame-reference-density
instancepoint-xpoint-ypoint-ztime

Evaluate the animated scalar extinction density at one world point.

nth-value1
%torch-flame-reference-fieldinstancepoint-xpoint-ypoint-ztime
defuntorch-flame-reference-integrate-ray
instanceorigin-xorigin-yorigin-zray-xray-yray-ztime&keymaximum-path-length

Return the fixed-sample premultiplied HDR rgba flame integral along RAY.

maximum-path-length, when supplied, clips the proxy chord to the visible distance in front of opaque scene depth. It is clamped to the canonical proxy chord exactly as the fragment shader does, so zero is a deterministic empty integral and callers can compare full, partial, and fully occluded rays.

let*
ray-length
sqrt
+
*ray-xray-x
*ray-yray-y
*ray-zray-z
when
zeropray-length
error"A torch-flame reference ray must have nonzero length."
let*
ray-x
/ray-xray-length
ray-y
/ray-yray-length
ray-z
/ray-zray-length
full-path-length
*2.0+torch-flame-proxy-radius+
arefinstance11
path-length
ifmaximum-path-length
%torch-flame-clampmaximum-path-length0.0full-path-length
full-path-length
step-length
/path-length+torch-flame-sample-count+
red0.0
green0.0
blue0.0
alpha0.0
multiple-value-bind
authored-redauthored-greenauthored-blue
dotimes
sample+torch-flame-sample-count+
let
travel
*step-length
multiple-value-bind
distancedensityheat
%torch-flame-reference-fieldinstance
+origin-x
*ray-xtravel
+origin-y
*ray-ytravel
+origin-z
*ray-ztravel
time
declare
ignoredistance
let*
sample-alpha
-1.0
exp
-
*density+torch-flame-extinction+step-length
transmittance
-1.0alpha
weight
*transmittancesample-alpha
radiance-scale
++torch-flame-cool-radiance-scale+
*heat+torch-flame-heat-radiance-gain+
incfred
*weightauthored-redradiance-scale
incfgreen
*weightauthored-greenradiance-scale
incfblue
*weightauthored-blueradiance-scale
incfalphaweight
valuesredgreenbluealpha

Canonical torch body

This is deliberately an expanded triangle stream rather than another semantic mesh. Each vertex is two Vec4 rows: local position plus its barycentric selector, then the flat local normal plus a boundary-edge mask. The attachment frame is the only world-space placement representation.

defun%torch-body-point
vector
coerce
*radius
'single-float
coerce
*radius
'single-float
coerceheight'single-float
defun%torch-body-emit-triangle
datapoint-apoint-bpoint-cexpected-normal
let*
ab-x
-
arefpoint-b0
arefpoint-a0
ab-y
-
arefpoint-b1
arefpoint-a1
ab-z
-
arefpoint-b2
arefpoint-a2
ac-x
-
arefpoint-c0
arefpoint-a0
ac-y
-
arefpoint-c1
arefpoint-a1
ac-z
-
arefpoint-c2
arefpoint-a2
normal-x
-
*ab-yac-z
*ab-zac-y
normal-y
-
*ab-zac-x
*ab-xac-z
normal-z
-
*ab-xac-y
*ab-yac-x
facing
+
*normal-x
arefexpected-normal0
*normal-y
arefexpected-normal1
*normal-z
arefexpected-normal2
when
minuspfacing
rotatefpoint-bpoint-c
setfnormal-x
-normal-x
normal-y
-normal-y
normal-z
-normal-z
let
normal-length
sqrt
+
*normal-xnormal-x
*normal-ynormal-y
*normal-znormal-z
when
zeropnormal-length
error"Degenerate canonical torch-body triangle: ~S ~S ~S."point-apoint-bpoint-c
setfnormal-x
/normal-xnormal-length
normal-y
/normal-ynormal-length
normal-z
/normal-znormal-length
loopforpointin
listpoint-apoint-bpoint-c
forbarycentric-indexfrom0do
vector-push-extend
arefpoint0
data
vector-push-extend
arefpoint1
data
vector-push-extend
arefpoint2
data
vector-push-extend
coercebarycentric-index'single-float
data
vector-push-extend
coercenormal-x'single-float
data
vector-push-extend
coercenormal-y'single-float
data
vector-push-extend
coercenormal-z'single-float
data

Faceting comes from the actual face normals. Internal triangle diagonals are not semantic construction edges.

vector-push-extend0.0f0data
defun%make-torch-body-vertex-data
let
data
make-array128:element-type'single-float:adjustablet:fill-pointer0
bottom-radius0.145f0
socket-height0.16f0
socket-radius0.078f0
shaft-height0.50f0
shaft-radius0.055f0
bottom-centre
0.0f00.0f00.0f0
top-centre
0.0f00.0f00.50f0
labels
angle
index
*2.0f0
coercepi'single-float
/
modindex+torch-body-side-count+
+torch-body-side-count+
ring-point
radiusheightindex
radial-normal
indexaxial
let
middle
angle
+index0.5f0
vector
coerce
cosmiddle
'single-float
coerce
sinmiddle
'single-float
coerceaxial'single-float
emit-frustum-side
indexlower-radiuslower-heightupper-radiusupper-height
let*
next
1+index
lower-a
ring-pointlower-radiuslower-heightindex
lower-b
ring-pointlower-radiuslower-heightnext
upper-a
ring-pointupper-radiusupper-heightindex
upper-b
ring-pointupper-radiusupper-heightnext
axial
/
-lower-radiusupper-radius
-upper-heightlower-height
expected
radial-normalindexaxial
%torch-body-emit-triangledatalower-alower-bupper-bexpected
%torch-body-emit-triangledatalower-aupper-bupper-aexpected
dotimes
side+torch-body-side-count+
let
bottom-a
ring-pointbottom-radius0.0f0side
bottom-b
ring-pointbottom-radius0.0f0
%torch-body-emit-triangledatabottom-centrebottom-bbottom-a
0.0f00.0f0-1.0f0
emit-frustum-sidesidebottom-radius0.0f0socket-radiussocket-height
emit-frustum-sidesidesocket-radiussocket-heightshaft-radiusshaft-height
let
top-a
ring-pointshaft-radiusshaft-heightside
top-b
ring-pointshaft-radiusshaft-height
%torch-body-emit-triangledatatop-centretop-atop-b
0.0f00.0f01.0f0
make-array
lengthdata
:element-type'single-float:initial-contentsdata
defuntorch-body-vertex-count

Return the canonical expanded triangle vertex count for one torch body.

/+torch-body-vertex-scalar-count+
defuntorch-body-vertex-data

Return a fresh float32 copy of the canonical two-Vec4-per-vertex body.

defuntorch-body-reference-vertex
framevertex-index

Transform one canonical body vertex through arbitrary realized frame.

Return world position, normalized world normal, barycentric selector, and boundary-edge mask. This is the scalar oracle for both body vertex shaders.

check-typevertex-index
integer0*
unless
error"Torch body vertex ~D exceeds the ~D-vertex canonical body."vertex-index
let*
offset
*vertex-index+torch-body-vertex-scalar-count+
local-x
local-y
local-z
barycentric-index
local-normal-x
local-normal-y
local-normal-z
boundary-edge-mask
origin-x
arefframe0
origin-y
arefframe1
origin-z
arefframe2
normal-x
arefframe4
normal-y
arefframe5
normal-z
arefframe6
tangent-x
arefframe8
tangent-y
arefframe9
tangent-z
arefframe10

B=NxT, so local X/Y/Z map to a right-handed T/B/N frame.

bitangent-x
-
*normal-ytangent-z
*normal-ztangent-y
bitangent-y
-
*normal-ztangent-x
*normal-xtangent-z
bitangent-z
-
*normal-xtangent-y
*normal-ytangent-x
world-x
+origin-x
*scale
+
*local-xtangent-x
*local-ybitangent-x
*local-znormal-x
world-y
+origin-y
*scale
+
*local-xtangent-y
*local-ybitangent-y
*local-znormal-y
world-z
+origin-z
*scale
+
*local-xtangent-z
*local-ybitangent-z
*local-znormal-z
world-normal-x
+
*local-normal-xtangent-x
*local-normal-ybitangent-x
*local-normal-znormal-x
world-normal-y
+
*local-normal-xtangent-y
*local-normal-ybitangent-y
*local-normal-znormal-y
world-normal-z
+
*local-normal-xtangent-z
*local-normal-ybitangent-z
*local-normal-znormal-z
world-normal-length
sqrt
+
*world-normal-xworld-normal-x
*world-normal-yworld-normal-y
*world-normal-zworld-normal-z
valuesworld-xworld-yworld-z
/world-normal-xworld-normal-length
/world-normal-yworld-normal-length
/world-normal-zworld-normal-length
barycentric-indexboundary-edge-mask
in-package#:luft.render.shaders
define-shader-functiontorch-frame-bitangent
normaltangent
"Derive B=NxT, so T/B/N is right-handed for every realized frame."
vec3
-
*
swizzlenormal:y
swizzletangent:z
*
swizzlenormal:z
swizzletangent:y
-
*
swizzlenormal:z
swizzletangent:x
*
swizzlenormal:x
swizzletangent:z
-
*
swizzlenormal:x
swizzletangent:y
*
swizzlenormal:y
swizzletangent:x
define-shader-functiontorch-frame-world-position
local-positionoriginnormaltangentscale
let*
bitangent
+origin
*scale
+
*tangent
swizzlelocal-position:x
*bitangent
swizzlelocal-position:y
*normal
swizzlelocal-position:z
define-shader-functiontorch-frame-world-normal
local-normalnormaltangent
let*
bitangent
normalize
+
*tangent
swizzlelocal-normal:x
*bitangent
swizzlelocal-normal:y
*normal
swizzlelocal-normal:z
define-live-shadertorch-body-vertex-specification
:stage:vertex:inputs
vertex-index:uint:built-in:vertex-index
instance-index:uint:built-in:instance-index

This interface intentionally matches mesh-fragment-specification exactly, including interpolation qualifiers.

:outputs
clip-position:vec4:built-in:position
world-position-output:vec3:location0
mesh-normal-output:vec3:location1:interpolation:flat
assembly-output:float:location2:interpolation:flat
barycentric-output:vec3:location3
current-clip-output:vec4:location4
previous-clip-output:vec4:location5
shadow-sample-output:vec3:location6
boundary-edge-mask-output:uint:location7:interpolation:flat
ambient-occlusion-output:float:location8:interpolation:flat
voxel-light-output:vec3:location9
:resources
torch-frames:storage-buffer:binding0:element:vec4
torch-body-vertices:storage-buffer:binding1:element:vec4
camera-state:uniform-block:binding2:members
camera-position:vec4
camera-right:vec4
camera-up:vec4
camera-forward:vec4
camera-projection:vec4
render-parameters:vec4
previous-camera-position:vec4
previous-camera-right:vec4
previous-camera-up:vec4
previous-camera-forward:vec4
previous-camera-projection:vec4
temporal-parameters:vec4
inspection-parameters:vec4
character-parameters:vec4
sun-vector:vec4
sun-color-vector:vec4
sky-color-vector:vec4
ground-color-vector:vec4
shadow-row-x:vec4
shadow-row-y:vec4
shadow-row-z:vec4
shadow-row-w:vec4
shadow-control:vec4
let*
frame-base
*instance-index
uint#.luft.render::+torch-flame-instance-row-count+
origin-row
buffer-elementtorch-framesframe-base
frame-normal-row
buffer-elementtorch-frames
+frame-base
uint1.0
tangent-row
buffer-elementtorch-frames
+frame-base
uint2.0
origin
swizzleorigin-row:xyz
normal
swizzleframe-normal-row:xyz
tangent
swizzletangent-row:xyz
scale
swizzletangent-row:w
frame-flags
uint
swizzleframe-normal-row:w
assembly-id
float
ldb
byte#.luft.render::+torch-body-assembly-bit-count+0
frame-flags
packed-light
ldb
byte#.luft.render::+torch-body-light-bit-count+#.luft.render::+torch-body-assembly-bit-count+
frame-flags
voxel-light
/
vec3
float
ldb
byte40
packed-light
float
ldb
byte44
packed-light
float
ldb
byte48
packed-light
15.0
vertex-base
*vertex-index
uint#.luft.render::+torch-body-vertex-row-count+
local-position-row
buffer-elementtorch-body-verticesvertex-base
local-normal-row
buffer-elementtorch-body-vertices
+vertex-base
uint1.0
local-position
swizzlelocal-position-row:xyz
local-normal
swizzlelocal-normal-row:xyz
world-position
torch-frame-world-positionlocal-positionoriginnormaltangentscale
world-normal
torch-frame-world-normallocal-normalnormaltangent
barycentric-index
uint
swizzlelocal-position-row:w
boundary-edge-mask
uint
swizzlelocal-normal-row:w
barycentric
if
=barycentric-index
uint0.0
vec31.00.00.0
if
=barycentric-index
uint1.0
vec30.01.00.0
vec30.00.01.0
current-clip
mesh-view-clipworld-positioncamera-positioncamera-rightcamera-upcamera-forwardcamera-projection
swizzlerender-parameters:z
previous-clip
mesh-view-clipworld-positionprevious-camera-positionprevious-camera-rightprevious-camera-upprevious-camera-forwardprevious-camera-projection
swizzlerender-parameters:z
light-clip
light-clip-positionworld-positionshadow-row-xshadow-row-yshadow-row-zshadow-row-w
jitter
swizzletemporal-parameters:xy
set-outputclip-position
vec4
+
swizzlecurrent-clip:x
*
swizzlejitter:x
swizzlecurrent-clip:w
+
swizzlecurrent-clip:y
*
swizzlejitter:y
swizzlecurrent-clip:w
swizzlecurrent-clip:z
swizzlecurrent-clip:w
set-outputworld-position-outputworld-position
set-outputmesh-normal-outputworld-normal
set-outputassembly-outputassembly-id
set-outputbarycentric-outputbarycentric
set-outputcurrent-clip-outputcurrent-clip
set-outputprevious-clip-outputprevious-clip
set-outputshadow-sample-output
vec3
+
*
swizzlelight-clip:x
0.5
0.5
+
*
swizzlelight-clip:y
0.5
0.5
swizzlelight-clip:z
set-outputboundary-edge-mask-outputboundary-edge-mask
set-outputambient-occlusion-output0.0
set-outputvoxel-light-outputvoxel-light
define-live-shadertorch-body-shadow-vertex-specification
:stage:vertex:inputs
vertex-index:uint:built-in:vertex-index
instance-index:uint:built-in:instance-index
:outputs
clip-position:vec4:built-in:position
:resources
torch-frames:storage-buffer:binding0:element:vec4
torch-body-vertices:storage-buffer:binding1:element:vec4
camera-state:uniform-block:binding2:members
camera-position:vec4
camera-right:vec4
camera-up:vec4
camera-forward:vec4
camera-projection:vec4
render-parameters:vec4
previous-camera-position:vec4
previous-camera-right:vec4
previous-camera-up:vec4
previous-camera-forward:vec4
previous-camera-projection:vec4
temporal-parameters:vec4
inspection-parameters:vec4
character-parameters:vec4
sun-vector:vec4
sun-color-vector:vec4
sky-color-vector:vec4
ground-color-vector:vec4
shadow-row-x:vec4
shadow-row-y:vec4
shadow-row-z:vec4
shadow-row-w:vec4
shadow-control:vec4
let*
frame-base
*instance-index
uint#.luft.render::+torch-flame-instance-row-count+
origin-row
buffer-elementtorch-framesframe-base
frame-normal-row
buffer-elementtorch-frames
+frame-base
uint1.0
tangent-row
buffer-elementtorch-frames
+frame-base
uint2.0
origin
swizzleorigin-row:xyz
normal
swizzleframe-normal-row:xyz
tangent
swizzletangent-row:xyz
scale
swizzletangent-row:w
vertex-base
*vertex-index
uint#.luft.render::+torch-body-vertex-row-count+
local-position-row
buffer-elementtorch-body-verticesvertex-base
world-position
torch-frame-world-position
swizzlelocal-position-row:xyz
originnormaltangentscale
set-outputclip-position
light-clip-positionworld-positionshadow-row-xshadow-row-yshadow-row-zshadow-row-w
define-shader-functiontorch-flame-field
pointoriginnormaltangentseedscaletime
"Return signed distance, density, heat, and radius at POINT."
let*
bitangent
world-up-projection
-
vec30.00.01.0
*normal
swizzlenormal:z
gravity-strength
sqrt
max
dotworld-up-projectionworld-up-projection
0.0
gravity-direction
if
>gravity-strength1e-6
/world-up-projectiongravity-strength
vec30.00.00.0
flame-length
*#.luft.render::+torch-flame-length+scale
wick
+origin
*normal
*#.luft.render::+torch-flame-wick-offset+scale
offset
-pointwick
axial
/
dotoffsetnormal
flame-length
height
clampaxial0.01.0
height-squared
sway-u
*scale0.105height-squared
sin
+
*time5.1
*seed19.7
sway-v
*scale0.075height-squared
sin
+
*time6.7
*seed31.1
gravity-bend
*scale#.luft.render::+torch-flame-wall-bend+gravity-strengthheight-squared
center
+wick
*normal
*flame-lengthheight
*tangentsway-u
*bitangentsway-v
*gravity-directiongravity-bend
radial
sqrt
max
dot
-pointcenter
-pointcenter
1e-12
bulge
radius
*scale
+0.055
*0.13bulge
signed-distance
-radialradius
begin
smoothstep-0.020.08axial
end
-1.0
smoothstep0.781.04axial
inside
-1.0
smoothstep-0.0250.035signed-distance
wave
+0.5
*0.5
sin
+
*
swizzlepoint:x
17.1
*
swizzlepoint:y
13.7
*
swizzlepoint:z
19.3
*time-8.1
*seed23.9
fine
+0.5
*0.5
sin
+
*
swizzlepoint:x
-31.7
*
swizzlepoint:y
27.3
*
swizzlepoint:z
23.1
*time11.3
*seed7.7
density
*beginendinside
+0.68
*0.22wave
*0.10fine
centrality
clamp
/
-signed-distance
maxradius0.001
0.01.0
heat
clamp
+0.20
*0.95centrality
*-0.32height
0.01.0
vec4signed-distancedensityheatradius
define-live-shadertorch-flame-vertex-specification
:stage:vertex:inputs
vertex-index:uint:built-in:vertex-index
instance-index:uint:built-in:instance-index
:outputs
clip-position:vec4:built-in:position
proxy-world-position-output:vec3:location0
origin-output:vec3:location1:interpolation:flat
normal-output:vec3:location2:interpolation:flat
tangent-output:vec3:location3:interpolation:flat
frame-parameters-output:vec2:location4:interpolation:flat
current-clip-output:vec4:location5
:resources
flame-instances:storage-buffer:binding0:element:vec4
camera-state:uniform-block:binding1:members
camera-position:vec4
camera-right:vec4
camera-up:vec4
camera-forward:vec4
camera-projection:vec4
render-parameters:vec4
let*
base-row
*instance-index
uint3.0
origin-row
buffer-elementflame-instancesbase-row
normal-row
buffer-elementflame-instances
+base-row
uint1.0
tangent-row
buffer-elementflame-instances
+base-row
uint2.0
origin
swizzleorigin-row:xyz
seed
swizzleorigin-row:w
normal
swizzlenormal-row:xyz
tangent
swizzletangent-row:xyz
scale
swizzletangent-row:w
proxy-radius
*#.luft.render::+torch-flame-proxy-radius+scale
volume-center
+origin
*normal
*scale
+#.luft.render::+torch-flame-wick-offset+
*#.luft.render::+torch-flame-length+0.5
index
floatvertex-index
right-corner
if
=index2.0
1.0
if
=index3.0
1.0
if
=index5.0
1.00.0
bottom-corner
if
=index1.0
1.0
if
=index4.0
1.0
if
=index5.0
1.00.0
corner
vec2
-
*right-corner2.0
1.0
-
*bottom-corner2.0
1.0
proxy-world-position
+
-volume-center
*
swizzlecamera-forward:xyz
proxy-radius
*
swizzlecamera-right:xyz
*
swizzlecorner:x
proxy-radius
*
swizzlecamera-up:xyz
*
swizzlecorner:y
proxy-radius
current-clip
mesh-view-clipproxy-world-positioncamera-positioncamera-rightcamera-upcamera-forwardcamera-projection
swizzlerender-parameters:z

Procedural radiance is a post-temporal composite. Rasterize the stable, unjittered proxy and never author a motion/history footprint for it.

set-outputclip-positioncurrent-clip
set-outputproxy-world-position-outputproxy-world-position
set-outputorigin-outputorigin
set-outputnormal-outputnormal
set-outputtangent-outputtangent
set-outputframe-parameters-output
set-outputcurrent-clip-outputcurrent-clip
define-live-shadertorch-flame-fragment-specification
:stage:fragment:inputs
proxy-world-position:vec3:location0
origin:vec3:location1:interpolation:flat
normal:vec3:location2:interpolation:flat
tangent:vec3:location3:interpolation:flat
frame-parameters:vec2:location4:interpolation:flat
current-clip:vec4:location5
:outputs
color-output:vec4:location0
:resources
camera-state:uniform-block:binding1:members
camera-position:vec4
camera-right:vec4
camera-up:vec4
camera-forward:vec4
camera-projection:vec4
render-parameters:vec4
previous-camera-position:vec4
previous-camera-right:vec4
previous-camera-up:vec4
previous-camera-forward:vec4
previous-camera-projection:vec4
temporal-parameters:vec4
inspection-parameters:vec4
effect-state:uniform-block:binding2:members
flame-effect-parameters:vec4
opaque-depth:depth-texture-2d:binding3
depth-sampler:sampler:binding4
let*
ray
if
<
swizzlerender-parameters:z
0.5
normalize
swizzlecamera-forward:xyz
normalize
-proxy-world-position
swizzlecamera-position:xyz
time
swizzleflame-effect-parameters:x
authored-radiance
swizzleflame-effect-parameters:yzw
seed
swizzleframe-parameters:x
scale
swizzleframe-parameters:y
full-path-length
*2.0#.luft.render::+torch-flame-proxy-radius+scale

Opaque depth was rendered with the current projection jitter while this post-temporal proxy is deliberately stable. Four nearest taps conservatively choose the closest covered internal pixel at an edge, avoiding bright half-flames leaking through a bevel silhouette.

depth-uv
+
mesh-clip-uvcurrent-clip
*
swizzletemporal-parameters:xy
0.5
half-texel
*
swizzleinspection-parameters:zw
0.5
depth-a
swizzle
sampleopaque-depthdepth-sampler
+depth-uvhalf-texel
:x
depth-b
swizzle
sampleopaque-depthdepth-sampler
-depth-uvhalf-texel
:x
depth-c
swizzle
sampleopaque-depthdepth-sampler
+depth-uv
vec2
swizzlehalf-texel:x
-
swizzlehalf-texel:y
:x
depth-d
swizzle
sampleopaque-depthdepth-sampler
+depth-uv
vec2
-
swizzlehalf-texel:x
swizzlehalf-texel:y
:x
scene-depth
min
mindepth-adepth-b
mindepth-cdepth-d
opaque-view-depth
view-depthscene-depthcamera-projection
swizzlerender-parameters:z
proxy-view-depth
dot
-proxy-world-position
swizzlecamera-position:xyz
swizzlecamera-forward:xyz
ray-view-rate
max
dotray
swizzlecamera-forward:xyz
1e-5
path-length
clamp
/
-opaque-view-depthproxy-view-depth
ray-view-rate
0.0full-path-length
step-length
/path-length
float#.luft.render::+torch-flame-sample-count+
integrated
counted-fold
sample
float#.luft.render::+torch-flame-sample-count+
state
vec40.00.00.00.0
let*
travel
*step-length
point
+proxy-world-position
*raytravel
field
torch-flame-fieldpointoriginnormaltangentseedscaletime
density
swizzlefield:y
heat
swizzlefield:z
sample-alpha
-1.0
exp
-
*density#.luft.render::+torch-flame-extinction+step-length
transmittance
-1.0
swizzlestate:w
weight
*transmittancesample-alpha
radiance-scale
+#.luft.render::+torch-flame-cool-radiance-scale+
*heat#.luft.render::+torch-flame-heat-radiance-gain+
emission
*authored-radianceradiance-scale
vec4
+
swizzlestate:xyz
*emissionweight
+
swizzlestate:w
weight
set-outputcolor-outputintegrated
define-live-shadertorch-flame-composite-copy-fragment-specification
:stage:fragment:inputs
ndc:vec2:location0
:outputs
color-output:vec4:location0
:resources
scene:texture-2d:binding0:sample-transfer:identity
scene-sampler:sampler:binding1
let*
uv
+
*ndc0.5
vec20.50.5
set-outputcolor-output
samplescenescene-sampleruv