struct Viewpoint {
view_projection: mat4x4<f32>,
// What the light drawing this map is: a lamp's position, or a sun's
// direction. The last lane holds 1.0 for a position and 0.0 for a
// direction; the camera's slot leaves it empty.
towards: vec4<f32>,
// What a world plane's own depth is read through: the plane taken
// through this comes back as the numbers `plane_depth` divides.
read_plane: mat4x4<f32>,
}
struct Frame {
// The light the frame's sky lands on a surface, as nine coefficients
// the direction a surface faces is read through.
irradiance: array<vec4<f32>, 9>,
// What a clip position is taken through to give the direction the sky
// is read along: the inverse of the camera with its viewpoint left at
// the origin.
sky_from_clip: mat4x4<f32>,
// The camera a faced draw resolves its depth against: `eye` is where its
// rays leave, `foreshortened` whether they leave that point at all
// rather than running level along `looking`, the direction it looks.
eye: vec3<f32>,
foreshortened: u32,
looking: vec3<f32>,
lights: u32,
// The mip level a fully rough surface reflects that sky from: the
// smallest mip's, counting `0.0` at the largest.
top_mip: f32,
// What a surface's reflection of the sky is scaled by; the coefficients
// already hold it.
sky_share: f32,
}
struct Light {
position: vec3<f32>,
kind: u32,
direction: vec3<f32>,
range: f32,
color: vec3<f32>,
cone: f32,
shadow: i32,
}
struct ShadowMap {
view_projection: mat4x4<f32>,
texel: f32,
layer: u32,
world_texel: vec2<f32>,
}
const DIRECTIONAL: u32 = 0u;
const POINT: u32 = 1u;
const SPOT: u32 = 2u;
// The fraction of a spotlight's cone its edge fades over.
const PENUMBRA: f32 = 0.1;
// A light with no depth map of its own.
const NO_SHADOW: i32 = -1;
// Map count a sun is drawn into, which `shadows.rs` fits densest first, and
// the texels a surface must be inside one by for its 3x3 to be
// taken from that map rather than from past its edge.
const CASCADES: u32 = 3u;
const MARGIN: f32 = 2.0;
// The power a surface of no roughness at all takes its highlight to; one
// fully rough takes it to 1.0, and the highlight covers less of a surface
// as the roughness falls between them.
const TIGHTEST: f32 = 256.0;
// The share of the sky a surface of no metallic at all reflects where the
// viewpoint faces it straight on. A fully metallic one reflects its own
// color in that share's place.
const SKY_DIELECTRIC: f32 = 0.04;
// The angle the sky's image covers across it and down it, in radians, and
// the depth the sky itself is drawn at. Every name the sky's own code
// declares starts with `sky`, so a style declaring `PI` still compiles.
const SKY_AROUND: f32 = 6.28318531;
const SKY_DOWN: f32 = 3.14159265;
const SKY_FAR: f32 = 1.0;
// A cutout instance writes its litness under this, past anything a plain
// one writes, since the instance layout has no lane left for it alone. Its
// texels are drawn only where the alpha is at least THRESHOLD.
const CUTOUT: f32 = -1.0;
const THRESHOLD: f32 = 0.5;
// The window written for a draw with no frame of its own: the whole texture,
// which the sampler's own repeat is left to resolve.
const WHOLE: vec4<f32> = vec4<f32>(0.0, 0.0, 1.0, 1.0);
// Lift distance for a surface along its normal before its depth is
// compared, in texels of the map it is compared against: a fixed lift,
// plus one per unit of slope away from the light, which is itself held to
// STEEPEST.
const LIFT: f32 = 1.0;
const LIFT_SLOPE: f32 = 2.0;
const STEEPEST: f32 = 2.0;
// The least squared length of level look direction a view must have to
// place a faced draw in an upright plane, and the furthest a corner slides
// along its own ray onto that plane, as a fraction of the ray.
const LEVEL: f32 = 1e-6;
const SLIDE: f32 = 0.5;
// What relief a draw's part has, in the order `pipelines.rs` numbers
// them: none at all, one holding a normal for its texels, and one that also
// moves them off the plane — a lane only a faced draw writes. The depth a
// draw with no solid is given follows, in sprite widths.
const NO_RELIEF: u32 = 0u;
const NORMALS: u32 = 1u;
const SOLID: u32 = 2u;
const UNMAPPED_DEPTH: f32 = 1.0;
// The sprite is at the middle of the volume a relief holds, so the surface
// it shows is half that depth out of the plane.
const HALF: f32 = 0.5;
@group(0) @binding(0) var<uniform> viewpoint: Viewpoint;
@group(0) @binding(1) var<uniform> frame: Frame;
@group(0) @binding(2) var<storage, read> lights: array<Light>;
@group(0) @binding(3) var<storage, read> maps: array<ShadowMap>;
// The frame's own sky, with every mip of it: what the frame draws where
// nothing was drawn, and what a surface reflects.
@group(0) @binding(4) var sky: texture_2d<f32>;
@group(0) @binding(5) var sky_sampler: sampler;
// The joint matrices of every posed draw of the frame, one run per draw
// record; an instance holds where its own run starts. The skinned stages
// alone read it, and every one of them reads the same run, so a posed draw
// casts the shadow of the pose it is drawn in.
@group(0) @binding(6) var<storage, read> palette: array<mat4x4<f32>>;
// One white texel is bound wherever a mesh slot holds no texture, no
// shading map, or no emissive map — leaving those lanes at what the material
// alone set — and one flat texel wherever it holds no relief.
@group(1) @binding(0) var base_color: texture_2d<f32>;
@group(1) @binding(1) var base_color_sampler: sampler;
@group(1) @binding(2) var relief: texture_2d<f32>;
@group(1) @binding(3) var shading: texture_2d<f32>;
@group(1) @binding(4) var emissive_map: texture_2d<f32>;
// A sun's or a cone's maps, a lamp's six faces at half their side, and
// the sampler that returns how much of a map a depth is in front of.
@group(2) @binding(0) var wide_maps: texture_depth_2d_array;
@group(2) @binding(1) var face_maps: texture_depth_2d_array;
@group(2) @binding(2) var map_sampler: sampler_comparison;
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
@location(2) uv: vec2<f32>,
}
struct InstanceInput {
@location(3) model_row_x: vec4<f32>,
@location(4) model_row_y: vec4<f32>,
@location(5) model_row_z: vec4<f32>,
@location(6) tint: vec4<f32>,
@location(7) params: vec4<f32>,
@location(8) window: vec4<f32>,
@location(10) relief: u32,
@location(11) plane: vec4<f32>,
@location(14) roughness: f32,
@location(15) metallic: f32,
// Where this draw's run of the palette starts. A draw of a mesh with no
// joints holds zero here and no stage reads it.
@location(9) palette: u32,
}
// The second stream a skinned mesh holds: the joints one corner takes,
// by their place in its model, and how much of each it takes. Its two
// lanes are the last a pipeline has left, so a skinned one reads all
// sixteen.
struct SkinInput {
@location(12) joints: vec4<u32>,
@location(13) weights: vec4<f32>,
}
struct Fragment {
@builtin(position) clip_position: vec4<f32>,
// Where the texel is, which the lights, the shadow comparison and a
// style's own SURFACE hook read it at: the corner its transform placed
// for a placed draw, and the point on the plane it lies in for a faced
// one.
@location(0) world: vec3<f32>,
@location(1) normal: vec3<f32>,
@location(2) tint: vec4<f32>,
@location(3) litness: f32,
@location(4) uv: vec2<f32>,
@location(5) emissive: vec3<f32>,
@location(6) @interpolate(flat) cutout: u32,
@location(7) @interpolate(flat) window: vec4<f32>,
// The drawn sprite's own across, with its width in meters, and its
// own up: the axes a relief's normals and depths are read in.
@location(8) @interpolate(flat) across: vec4<f32>,
@location(9) @interpolate(flat) upward: vec3<f32>,
@location(10) @interpolate(flat) relief: u32,
// The plane the draw takes its depth from, which a draw that takes its
// own corners' depth leaves empty.
@location(11) @interpolate(flat) plane: vec4<f32>,
@location(12) @interpolate(flat) roughness: f32,
@location(13) @interpolate(flat) metallic: f32,
}
// The values a style's vertex code is passed: the position the transform
// placed the vertex at, and what it was built with.
struct Placed {
world: vec3<f32>,
normal: vec3<f32>,
uv: vec2<f32>,
local: vec3<f32>,
}
// The values a style's fragment code is passed and returns, before the
// surface is lit: the engine draws with the first three fields.
struct Surface {
color: vec4<f32>,
normal: vec3<f32>,
emissive: vec3<f32>,
world: vec3<f32>,
uv: vec2<f32>,
}
// The cofactor matrix turns normals the way the inverse transpose does, up to
// a positive scale that normalizing drops — so no normal matrix has to be
// computed or uploaded per draw.
fn cofactor(m: mat3x3<f32>) -> mat3x3<f32> {
return mat3x3<f32>(cross(m[1], m[2]), cross(m[2], m[0]), cross(m[0], m[1]));
}
// The instance holds its transform's affine rows; the (0, 0, 0, 1) row it drops
// makes the world position three dot products.
fn placed(local: vec3<f32>, instance: InstanceInput) -> vec3<f32> {
let point = vec4<f32>(local, 1.0);
return vec3<f32>(
dot(instance.model_row_x, point),
dot(instance.model_row_y, point),
dot(instance.model_row_z, point),
);
}
// The transform's own axes, which the instance holds transposed.
fn columns(instance: InstanceInput) -> mat3x3<f32> {
return transpose(mat3x3<f32>(
instance.model_row_x.xyz,
instance.model_row_y.xyz,
instance.model_row_z.xyz,
));
}
// Where a corner lands on the screen.
fn on_screen(world: vec3<f32>) -> vec4<f32> {
return viewpoint.view_projection * vec4<f32>(world, 1.0);
}
// The depth of `plane` at the pixel `position` names, which is where the
// viewpoint's ray through that pixel intersects it. The plane and the pixel decide
// it and the drawn corners do not, so every draw of one plane writes one
// depth at a pixel and the later of them draws over the earlier, whole.
//
// A draw with no plane, and one whose plane the viewpoint meets edge-on,
// keep the depth their own corners left.
fn plane_depth(plane: vec4<f32>, position: vec4<f32>) -> f32 {
let read = viewpoint.read_plane * plane;
let depth = -(read.x * position.x + read.y * position.y + read.w) / read.z;
return select(position.z, depth, read.z != 0.0);
}
// Slides a corner along the camera's own ray through it, onto the upright
// plane its draw lies in. That leaves the corner where it is on the screen
// and places it where the draw is, so the view clips a turned draw, the
// lights land on it there and every depth map records it there, however far
// the turn moved it back.
//
// A draw that lies in no plane, and one whose plane the camera meets
// edge-on, keep the corners they were drawn with. A slide past SLIDE is held
// there rather than dropped, so a view nearly level with the plane moves
// every corner to that bound instead of leaving some of them behind.
fn onto_plane(plane: vec4<f32>, world: vec3<f32>) -> vec3<f32> {
if dot(plane.xyz, plane.xyz) < LEVEL {
return world;
}
let ray = select(frame.looking, world - frame.eye, frame.foreshortened == 1u);
let along = dot(plane.xyz, ray);
if along == 0.0 {
return world;
}
let slide = clamp(-(dot(plane.xyz, world) + plane.w) / along, -SLIDE, SLIDE);
return world + ray * slide;
}
// The four matrices a corner takes of its draw's own run of the palette,
// each at the weight that corner holds of it.
fn skin_blend(skin: SkinInput, at: u32) -> mat4x4<f32> {
return palette[at + skin.joints.x] * skin.weights.x
+ palette[at + skin.joints.y] * skin.weights.y
+ palette[at + skin.joints.z] * skin.weights.z
+ palette[at + skin.joints.w] * skin.weights.w;
}
// The same corner moved by that blend, and its normal turned by the
// blend's own cofactor, which is what a stage past this one is passed: a
// skinned corner goes through the stages a placed one does, from here on.
fn skinned(vertex: VertexInput, skin: SkinInput, instance: InstanceInput) -> VertexInput {
let blend = skin_blend(skin, instance.palette);
let turns = cofactor(mat3x3<f32>(blend[0].xyz, blend[1].xyz, blend[2].xyz));
var posed: VertexInput;
posed.position = (blend * vec4<f32>(vertex.position, 1.0)).xyz;
posed.normal = turns * vertex.normal;
posed.uv = vertex.uv;
return posed;
}
// mirage-engine:style
@vertex
fn vertex(vertex: VertexInput, instance: InstanceInput) -> Fragment {
var fragment = drawn(vertex, instance);
fragment.clip_position = on_screen(fragment.world);
return fragment;
}
// The same for a draw the viewpoint turned: its corners slide onto the
// upright plane it lies in, so the view clips it and the lights land on it
// there, however far the turn moved it back.
@vertex
fn faced_vertex(vertex: VertexInput, instance: InstanceInput) -> Fragment {
var fragment = drawn(vertex, instance);
fragment.world = onto_plane(instance.plane, fragment.world);
fragment.clip_position = on_screen(fragment.world);
return fragment;
}
// Both of those again for a draw of a mesh with joints, whose corners are
// posed before its own transform places them.
@vertex
fn skinned_vertex(
vertex: VertexInput,
skin: SkinInput,
instance: InstanceInput,
) -> Fragment {
var fragment = drawn(skinned(vertex, skin, instance), instance);
fragment.clip_position = on_screen(fragment.world);
return fragment;
}
@vertex
fn faced_skinned_vertex(
vertex: VertexInput,
skin: SkinInput,
instance: InstanceInput,
) -> Fragment {
var fragment = drawn(skinned(vertex, skin, instance), instance);
fragment.world = onto_plane(instance.plane, fragment.world);
fragment.clip_position = on_screen(fragment.world);
return fragment;
}
// Everything one corner passes into the fragment stage but where it lands
// on the screen, which each vertex stage decides for itself.
fn drawn(vertex: VertexInput, instance: InstanceInput) -> Fragment {
let orientation = columns(instance);
let normal = cofactor(orientation) * vertex.normal;
let uv = instance.window.xy + vertex.uv * instance.window.zw;
let world = displaced(Placed(
placed(vertex.position, instance),
normal,
uv,
vertex.position,
));
let shading = instance.params.x;
let cuts = shading < 0.0;
var fragment: Fragment;
fragment.world = world;
fragment.normal = normal;
fragment.tint = instance.tint;
fragment.litness = select(shading, CUTOUT - shading, cuts);
fragment.uv = uv;
fragment.emissive = instance.params.yzw;
fragment.cutout = select(0u, 1u, cuts);
fragment.window = instance.window;
let width = length(orientation[0]);
fragment.across = vec4<f32>(orientation[0] / max(width, 1e-6), width);
fragment.upward = orientation[1] / max(length(orientation[1]), 1e-6);
fragment.relief = instance.relief;
fragment.plane = instance.plane;
fragment.roughness = instance.roughness;
fragment.metallic = instance.metallic;
return fragment;
}
// Everything a caster leaves in one depth map: the same placement, from
// the light's viewpoint, with no color to write.
@vertex
fn caster(vertex: VertexInput, instance: InstanceInput) -> @builtin(position) vec4<f32> {
return depth_of(vertex, instance);
}
// The same for a draw of a mesh with joints: the pose it is drawn in is the
// pose it casts.
@vertex
fn skinned_caster(
vertex: VertexInput,
skin: SkinInput,
instance: InstanceInput,
) -> @builtin(position) vec4<f32> {
return depth_of(skinned(vertex, skin, instance), instance);
}
fn depth_of(vertex: VertexInput, instance: InstanceInput) -> vec4<f32> {
return viewpoint.view_projection * vec4<f32>(placed(vertex.position, instance), 1.0);
}
// What a caster that samples its slot passes into the fragment stage:
// where it reads its textures, what it drops by, where it is positioned, and
// how wide the sprite is.
struct Casting {
@builtin(position) clip_position: vec4<f32>,
@location(0) uv: vec2<f32>,
@location(1) @interpolate(flat) window: vec4<f32>,
@location(2) @interpolate(flat) alpha: f32,
@location(3) @interpolate(flat) cutout: u32,
@location(4) world: vec3<f32>,
@location(5) @interpolate(flat) relief: u32,
@location(6) @interpolate(flat) width: f32,
@location(7) @interpolate(flat) out: vec3<f32>,
@location(8) @interpolate(flat) plane: vec4<f32>,
}
@vertex
fn sampling_caster(vertex: VertexInput, instance: InstanceInput) -> Casting {
return casting(vertex, instance);
}
// The same for a skinned draw, which casts what its pose covers.
@vertex
fn skinned_sampling_caster(
vertex: VertexInput,
skin: SkinInput,
instance: InstanceInput,
) -> Casting {
return casting(skinned(vertex, skin, instance), instance);
}
// The axis a draw's relief holds its depth along: out of the sprite, towards
// the viewpoint that turned it.
fn out_of(orientation: mat3x3<f32>) -> vec3<f32> {
let out = cross(orientation[0], orientation[1]);
return out / max(length(out), 1e-6);
}
// How far towards a light a texel is moved, for one whose relief is
// `height` out of the plane along `out`: as far along the light as that
// surface is, and no further — nothing where the light is behind the
// sprite and the surface it meets is the plane itself. The caster and the
// forward pass both take it, so the two land on one point of the texel's own
// ray however the light crosses the sprite.
fn moved_towards(height: f32, out: vec3<f32>, towards: vec3<f32>) -> f32 {
return height * max(dot(out, towards), 0.0);
}
// A draw casting the solid its relief holds records that solid texel by
// texel, so its corners take the same slide the camera lit them through.
// Every other caster writes the depth of the plane at the pixel it lands
// on, which no slide moves, and its corners stay where they were placed.
fn casting(vertex: VertexInput, instance: InstanceInput) -> Casting {
let corner = placed(vertex.position, instance);
let world = select(
corner,
onto_plane(instance.plane, corner),
instance.relief == SOLID,
);
var casting: Casting;
casting.clip_position = on_screen(world);
casting.uv = instance.window.xy + vertex.uv * instance.window.zw;
casting.window = instance.window;
casting.alpha = instance.tint.a;
casting.cutout = select(0u, 1u, instance.params.x < 0.0);
casting.world = world;
casting.relief = instance.relief;
casting.plane = instance.plane;
let orientation = columns(instance);
casting.width = length(orientation[0]);
casting.out = out_of(orientation);
return casting;
}
// The direction from `world` to the light this map is drawn from.
fn toward_map(world: vec3<f32>) -> vec3<f32> {
if viewpoint.towards.w == 0.0 {
return viewpoint.towards.xyz;
}
let offset = viewpoint.towards.xyz - world;
return offset / max(length(offset), 1e-6);
}
// The alpha one texel of a caster covers with: the crisp shape a cutout
// draw keeps, scaled by a translucent draw's own tint, and the whole texel
// for an opaque one, whose slot ignores texture alpha as it does in every
// pass. A draw casting the solid its relief holds takes its shape from that
// relief instead, so only its tint scales what it casts.
fn covered(casting: Casting, sampled: f32) -> f32 {
if casting.relief == SOLID {
return casting.alpha;
}
if casting.cutout == 1u {
return casting.alpha * select(0.0, 1.0, sampled >= THRESHOLD);
}
return select(1.0, casting.alpha * sampled, casting.alpha < 1.0);
}
// The value partial coverage is dropped against at one texel of a map: the
// 4x4 Bayer matrix, in `(0, 1)`, so coverage of one keeps every texel and
// coverage of zero keeps none. It is laid over the map's own grid, not the
// camera's, so what a translucent caster blocks holds still as the camera
// turns; bit arithmetic is used in place of the table WGSL cannot index at a
// texel that varies.
fn dithered(at: vec2<f32>) -> f32 {
let cell = vec2<u32>(at) & vec2<u32>(3u);
let diagonal = cell.x ^ cell.y;
let rung = ((diagonal & 1u) << 3u)
| ((cell.y & 1u) << 2u)
| (diagonal & 2u)
| ((cell.y & 2u) >> 1u);
return (f32(rung) + 0.5) / 16.0;
}
// A caster's depth wherever its draw covers the texel it lands on: the
// whole of an opaque one, the shape a cutout draw keeps, and a dithered
// fraction of a translucent one, so what it takes out of a light is the
// alpha it covers with; a caster writes no color.
//
// A draw lying in a plane records that plane's own depth, so its record and
// the records of the draws of its plane land at one depth and it casts
// nothing of its own past its edges.
//
// A draw whose relief holds a depth casts the solid that relief holds
// instead: depth wherever it holds any, the base it holds under the
// sprite's own texels included, so what a sprite casts meets the ground
// under it.
// The depth written is the relief's surface, moved from the plane towards
// this map's light as far as that surface is along it, which is the point
// the forward pass rebuilds it at.
@fragment
fn caster_sampled(casting: Casting) -> @builtin(frag_depth) f32 {
let uv = held_inside(casting.uv, casting.window, textureDimensions(base_color));
let sampled = textureSample(base_color, base_color_sampler, uv).a;
let depth = textureSample(
relief,
base_color_sampler,
held_inside(casting.uv, casting.window, textureDimensions(relief)),
).a;
if covered(casting, sampled) < dithered(casting.clip_position.xy) {
discard;
}
if casting.relief != SOLID {
return plane_depth(casting.plane, casting.clip_position);
}
if depth <= 0.0 {
discard;
}
let towards = toward_map(casting.world);
let met = casting.world
+ towards * moved_towards(depth * casting.width * HALF, casting.out, towards);
let clip = viewpoint.view_projection * vec4<f32>(met, 1.0);
if clip.w <= 0.0 {
return casting.clip_position.z;
}
return clamp(clip.z / clip.w, 0.0, 1.0);
}
fn toward(light: Light, world: vec3<f32>) -> vec3<f32> {
if light.kind == DIRECTIONAL {
return -light.direction;
}
let offset = light.position - world;
return offset / max(length(offset), 1e-6);
}
// A point as the viewpoint reads it: the direction from it back to the
// viewpoint, and how far along the view it is, in meters. The rays
// leave `eye` under a foreshortened camera and run level along `looking`
// under one that is not.
struct Seen {
towards: vec3<f32>,
distance: f32,
}
fn seen_from(world: vec3<f32>) -> Seen {
let offset = frame.eye - world;
if frame.foreshortened == 0u {
return Seen(-frame.looking, -dot(offset, frame.looking));
}
let span = length(offset);
return Seen(offset / max(span, 1e-6), span);
}
// The fraction of one light a surface returns towards the viewpoint: none
// at a roughness of 1.0, and a highlight that covers less of the surface as
// the roughness falls. It is measured from the halfway direction between the
// light and the viewpoint: a normal facing along that direction returns the
// light straight back. A surface with no normal of its own returns none.
fn highlight_of(roughness: f32, normal: vec3<f32>, towards: vec3<f32>, seen: Seen) -> f32 {
let sharpness = 1.0 - roughness;
let halved = towards + seen.towards;
let halfway = halved * inverseSqrt(max(dot(halved, halved), LEVEL));
return sharpness * pow(max(dot(normal, halfway), 0.0), pow(TIGHTEST, sharpness));
}
// How a fragment meets one light: the fraction of it the surface's angle
// takes, the point the light's map is compared at, the direction that point
// is lifted along before the comparison, and how much further from the
// light the compared surface is over one step across it.
struct Incidence {
landed: f32,
compared: vec3<f32>,
lift: vec3<f32>,
slope: f32,
}
// That slope for a surface meeting the light at `squarely`, held to
// STEEPEST so a surface nearly edge-on is not lifted out of its own map.
fn slope_of(squarely: f32) -> f32 {
let straight = sqrt(max(1.0 - squarely * squarely, 0.0));
return min(straight / max(squarely, 1e-3), STEEPEST);
}
fn contribution(light: Light, world: vec3<f32>, landed: f32, towards: vec3<f32>) -> vec3<f32> {
let lambert = light.color * landed;
if light.kind == DIRECTIONAL {
return lambert;
}
let span = length(light.position - world);
let falloff = pow(clamp(1.0 - span / light.range, 0.0, 1.0), 2.0);
var cone = 1.0;
if light.kind == SPOT {
let alignment = dot(-towards, light.direction);
cone = smoothstep(light.cone, mix(light.cone, 1.0, PENUMBRA), alignment);
}
return lambert * falloff * cone;
}
// The one of a lamp's six cube faces an offset from it falls on, in the
// order `shadows.rs` draws them.
fn face_of(offset: vec3<f32>) -> u32 {
let reach = abs(offset);
if reach.x >= reach.y && reach.x >= reach.z {
return select(1u, 0u, offset.x > 0.0);
}
if reach.y >= reach.z {
return select(3u, 2u, offset.y > 0.0);
}
return select(5u, 4u, offset.z > 0.0);
}
fn compared(kind: u32, at: vec2<f32>, layer: u32, depth: f32) -> f32 {
if kind == POINT {
return textureSampleCompareLevel(face_maps, map_sampler, at, layer, depth);
}
return textureSampleCompareLevel(wide_maps, map_sampler, at, layer, depth);
}
// The fraction of a light one map allows through, and whether it holds the
// surface at all; one that does not is left to the map past it.
struct Through {
reached: f32,
held: bool,
}
// Everything a fixed 3x3 of `map` is in front of, taken no
// nearer its edge than `margin` texels.
fn through(light: Light, map: ShadowMap, met: Incidence, margin: f32) -> Through {
let texel = map.world_texel.x + map.world_texel.y * distance(met.compared, light.position);
let lifted = met.compared + met.lift * texel * (LIFT + LIFT_SLOPE * met.slope);
let clip = map.view_projection * vec4<f32>(lifted, 1.0);
if clip.w <= 0.0 {
return Through(1.0, false);
}
let ndc = clip.xyz / clip.w;
let landed = ndc.xy * vec2<f32>(0.5, -0.5) + vec2<f32>(0.5);
let edge = margin * map.texel;
let escaped = any(landed < vec2<f32>(edge)) || any(landed > vec2<f32>(1.0 - edge));
if ndc.z <= 0.0 || ndc.z >= 1.0 || (escaped && light.kind != POINT) {
return Through(1.0, false);
}
// A lamp's faces tile every direction: one the lift moved past this
// face is held at its edge, the next face being another map.
let at = clamp(landed, vec2<f32>(0.0), vec2<f32>(1.0));
var reached = 0.0;
for (var y = -1; y <= 1; y++) {
for (var x = -1; x <= 1; x++) {
let step = vec2<f32>(f32(x), f32(y)) * map.texel;
reached += compared(light.kind, at + step, map.layer, ndc.z);
}
}
return Through(reached / 9.0, true);
}
// The fraction of a light that lands on a surface: all of it wherever the light
// has no map holding the surface, and whatever the densest map that does
// returns.
fn fraction_landing(light: Light, met: Incidence) -> f32 {
if light.shadow == NO_SHADOW {
return 1.0;
}
let first = u32(light.shadow);
if light.kind == DIRECTIONAL {
for (var cascade = 0u; cascade < CASCADES; cascade++) {
let taken = through(light, maps[first + cascade], met, MARGIN);
if taken.held {
return taken.reached;
}
}
return 1.0;
}
var index = first;
if light.kind == POINT {
index += face_of(met.compared - light.position);
}
return through(light, maps[index], met, 0.0).reached;
}
// The coordinate a windowed draw reads a texture `across` texels wide at:
// within the interval its two edges span, whichever way round they run, no
// nearer either than half a texel — or its middle, where it is narrower
// than that. A draw of the whole texture is left to the sampler, which
// repeats.
fn held_inside(uv: vec2<f32>, window: vec4<f32>, across: vec2<u32>) -> vec2<f32> {
if all(window == WHOLE) {
return uv;
}
let edge = window.xy + window.zw;
let low = min(window.xy, edge);
let high = max(window.xy, edge);
let inset = min(0.5 / vec2<f32>(across), (high - low) / 2.0);
return clamp(uv, low + inset, high - inset);
}
// The surface facing `normal` before it is lit: its tint over the
// part of its texture the instance samples, with its own emissive light
// scaled by its emissive map, as the style it is drawn with leaves it.
fn surface_of(fragment: Fragment, normal: vec3<f32>) -> Surface {
let uv = held_inside(fragment.uv, fragment.window, textureDimensions(base_color));
let base = fragment.tint * textureSample(base_color, base_color_sampler, uv);
let light = textureSample(
emissive_map,
base_color_sampler,
held_inside(fragment.uv, fragment.window, textureDimensions(emissive_map)),
);
return styled(Surface(
base,
normal,
fragment.emissive * light.rgb,
fragment.world,
uv,
));
}
// The slot's shading map over this texel: the occlusion it takes of the
// sky's own light in `R`, and, in `G` and `B`, the scale it applies to the
// roughness and metallic lanes.
fn shading_of(fragment: Fragment) -> vec3<f32> {
return textureSample(
shading,
base_color_sampler,
held_inside(fragment.uv, fragment.window, textureDimensions(shading)),
).rgb;
}
// The same fragment with those two lanes scaled by `texel`.
fn scaled_by(fragment: Fragment, texel: vec3<f32>) -> Fragment {
var scaled = fragment;
scaled.roughness *= texel.g;
scaled.metallic *= texel.b;
return scaled;
}
// Where `direction` lands on the sky: the whole way around across the
// image, and zenith to nadir down it. `skybox.rs` writes a texel at the
// same place.
fn sky_at(direction: vec3<f32>) -> vec2<f32> {
let around = atan2(direction.x, -direction.z) / SKY_AROUND + 0.5;
let down = acos(clamp(direction.y, -1.0, 1.0)) / SKY_DOWN;
return vec2<f32>(around, down);
}
// The light the sky lands on a surface facing `normal`: the frame's nine
// coefficients, each scaled by its own shape at that direction, in the
// order `skybox.rs` lays them out.
//
// Nine coefficients cannot hold a sky that changes over a short angle, and
// what they return dips under zero here and there, so nothing under zero is
// returned.
fn sky_light(normal: vec3<f32>) -> vec3<f32> {
let light = frame.irradiance[0].rgb
+ frame.irradiance[1].rgb * normal.y
+ frame.irradiance[2].rgb * normal.z
+ frame.irradiance[3].rgb * normal.x
+ frame.irradiance[4].rgb * (normal.x * normal.y)
+ frame.irradiance[5].rgb * (normal.y * normal.z)
+ frame.irradiance[6].rgb * (3.0 * normal.z * normal.z - 1.0)
+ frame.irradiance[7].rgb * (normal.x * normal.z)
+ frame.irradiance[8].rgb * (normal.x * normal.x - normal.y * normal.y);
return max(light, vec3<f32>(0.0));
}
// What the sky leaves on a surface that reflects it: the sky at
// `reflect(-towards, normal)`, read from the mip `roughness` chooses, the
// largest at `0.0` and the smallest at `1.0`, scaled by the share the
// surface reflects at this angle and by the fraction of its light the sky
// lands. No draw is reflected in another.
//
// The roughness holds what is returned where the viewpoint meets the surface
// along it: the whole sky where smooth, the base share where fully rough.
fn sky_reflection(
color: vec3<f32>,
normal: vec3<f32>,
towards: vec3<f32>,
roughness: f32,
metallic: f32,
) -> vec3<f32> {
let base = mix(vec3<f32>(SKY_DIELECTRIC), color, metallic);
let squarely = clamp(dot(normal, towards), 0.0, 1.0);
let along = max(vec3<f32>(1.0 - roughness), base);
let returned = base + (along - base) * pow(1.0 - squarely, 5.0);
let level = clamp(roughness, 0.0, 1.0) * frame.top_mip;
let reflected = sky_at(reflect(-towards, normal));
return textureSampleLevel(sky, sky_sampler, reflected, level).rgb * returned
* frame.sky_share;
}
// What one surface takes from the frame: the light landing on it from
// every direction, and the highlight the frame's lights leave over that.
struct Received {
reached: vec3<f32>,
highlight: vec3<f32>,
}
// The light received by a surface placed by its own transform: what the sky
// lands on a surface facing its way and what the sky leaves reflected in
// it, both darkened by `occlusion` — the share its slot's shading map holds
// for this texel — and every light scaled by the angle its normal makes
// with it, darkened where that light's map holds something nearer. Each
// light leaves a highlight scaled by what it landed with.
fn received(fragment: Fragment, surface: Surface, occlusion: f32) -> Received {
let seen = seen_from(fragment.world);
var taken = Received(
sky_light(surface.normal) * occlusion,
sky_reflection(
surface.color.rgb,
surface.normal,
seen.towards,
fragment.roughness,
fragment.metallic,
) * occlusion,
);
for (var i = 0u; i < frame.lights; i++) {
let light = lights[i];
let towards = toward(light, fragment.world);
let landed = clamp(dot(surface.normal, towards), 0.0, 1.0);
let met = Incidence(landed, fragment.world, surface.normal, slope_of(landed));
let reached = contribution(light, fragment.world, met.landed, towards)
* fraction_landing(light, met);
taken.reached += reached;
taken.highlight += reached * highlight_of(fragment.roughness, surface.normal, towards, seen);
}
return taken;
}
// The relief texel a fragment reads, held inside the window it samples.
fn relief_at(fragment: Fragment) -> vec4<f32> {
return textureSample(
relief,
base_color_sampler,
held_inside(fragment.uv, fragment.window, textureDimensions(relief)),
);
}
// The normal one relief texel holds, in the basis its three channels are
// read in: across the map, up it, and out of the surface. A texel with no
// normal at all faces out, so a relief a source left empty there is drawn
// instead of black.
fn facing_in(basis: mat3x3<f32>, sampled: vec3<f32>) -> vec3<f32> {
let facing = basis * (sampled * 2.0 - 1.0);
let reach = dot(facing, facing);
return select(basis[2], facing * inverseSqrt(reach), reach > LEVEL);
}
// `direction` at unit length, or the zero vector where `direction` itself
// has zero length.
fn unit(direction: vec3<f32>) -> vec3<f32> {
let reach = dot(direction, direction);
return select(vec3<f32>(0.0), direction * inverseSqrt(reach), reach > 0.0);
}
// The derivative basis: the one the fragment's position and UV derivatives
// span, which is what a draw placed by its own transform reads its relief
// in, since no mesh holds a tangent stream. Across the texels, up them, and
// out along the surface's own normal.
fn derivative_basis(world: vec3<f32>, uv: vec2<f32>, normal: vec3<f32>) -> mat3x3<f32> {
let step_x = dpdx(world);
let step_y = dpdy(world);
let uv_x = dpdx(uv);
let uv_y = dpdy(uv);
let winding = sign(uv_x.x * uv_y.y - uv_y.x * uv_x.y);
let across = (step_x * uv_y.y - step_y * uv_x.y) * winding;
let down = (step_y * uv_x.x - step_x * uv_y.x) * winding;
return mat3x3<f32>(
unit(across - normal * dot(normal, across)),
unit(normal * dot(normal, down) - down),
normal,
);
}
// The normal a draw placed by its own transform shades with: the one its
// relief holds, read through that basis, or the mesh's own where its slot
// holds no relief. The basis is built whether or not the draw has a
// relief: a derivative is read only where every pixel beside this one reads
// it too.
fn placed_normal(fragment: Fragment) -> vec3<f32> {
let normal = normalize(fragment.normal);
let basis = derivative_basis(fragment.world, fragment.uv, normal);
let facing = facing_in(basis, relief_at(fragment).xyz);
return select(normal, facing, fragment.relief >= NORMALS);
}
// What one texel of a faced draw holds: the normal of the surface it
// shows, whether that normal came from the draw's own relief, how far the
// surface is offset from the plane, in meters, and whether the relief holds
// that offset at all.
struct Standing {
facing: vec3<f32>,
mapped: bool,
height: f32,
out: vec3<f32>,
solid: bool,
}
// The relief read in the drawn sprite's own axes: across it, up it, and out
// of it towards the viewpoint that turned it. A draw whose relief holds no
// depth, and one with no relief at all, are given UNMAPPED_DEPTH of the
// sprite's width; one with no relief has no normal either, and takes every
// light whole.
fn standing_of(fragment: Fragment) -> Standing {
let sampled = relief_at(fragment);
let out = cross(fragment.across.xyz, fragment.upward);
let solid = fragment.relief == SOLID;
let height = select(UNMAPPED_DEPTH, sampled.a, solid) * fragment.across.w * HALF;
if fragment.relief == NO_RELIEF {
return Standing(vec3<f32>(0.0), false, height, out, false);
}
let turned = mat3x3<f32>(fragment.across.xyz, fragment.upward, out);
return Standing(facing_in(turned, sampled.xyz), true, height, out, solid);
}
// Where a texel of a faced draw meets one light: its place on that plane,
// moved towards the light as far as the surface its relief shows is
// along that light.
//
// A mapped draw's own record is that same surface, texel for texel: the
// light's ray through this point crosses the plane where this fragment
// lies, and the caster moved that crossing off by the same depth. The two
// match, so only the depth format's own steps separate them and a faced
// draw cannot shadow itself. A draw without a relief is recorded flat and
// met half a sprite width towards the light, which clears its record by
// that much.
fn met_at(fragment: Fragment, standing: Standing, towards: vec3<f32>) -> vec3<f32> {
let reach = select(
standing.height,
moved_towards(standing.height, standing.out, towards),
standing.solid,
);
return fragment.world + towards * reach;
}
// The light received by a draw the viewpoint turned: the volume its relief
// holds, offset from the plane its record was drawn in, lit by the normals
// that relief holds. A draw without a relief takes every light whole; its
// own normal is the viewpoint's, which no light or highlight can be
// measured against. The sky is read and reflected through that normal.
fn received_facing(fragment: Fragment, surface: Surface, occlusion: f32) -> Received {
let standing = standing_of(fragment);
let upright = unit(fragment.plane.xyz);
let seen = seen_from(fragment.world);
let facing = select(seen.towards, standing.facing, standing.mapped);
var taken = Received(
sky_light(facing) * occlusion,
sky_reflection(
surface.color.rgb,
facing,
seen.towards,
fragment.roughness,
fragment.metallic,
) * occlusion,
);
for (var i = 0u; i < frame.lights; i++) {
let light = lights[i];
let towards = toward(light, fragment.world);
let landed = select(1.0, clamp(dot(standing.facing, towards), 0.0, 1.0), standing.mapped);
let met = Incidence(
landed,
met_at(fragment, standing, towards),
towards,
slope_of(abs(dot(upright, towards))),
);
let reached = contribution(light, met.compared, met.landed, towards)
* fraction_landing(light, met);
taken.reached += reached;
taken.highlight += reached * highlight_of(fragment.roughness, standing.facing, towards, seen);
}
return taken;
}
// What a surface draws once `received` light has landed on it. The
// highlight and the sky it reflects land with the rest of the light, so an
// unlit surface takes none of them.
//
// A metal takes less of the light that lands on it as its own color, until
// at 1.0 the sky it reflects and its highlight are all it draws.
fn lit(fragment: Fragment, surface: Surface, received: Received) -> vec4<f32> {
let taken = surface.color.rgb * received.reached * (1.0 - fragment.metallic);
let received_color = mix(surface.color.rgb, taken + received.highlight, fragment.litness);
return vec4<f32>(received_color + surface.emissive, surface.color.a);
}
// Whether a cutout instance leaves this texel out: every draw of the cutout
// pipeline is one, and a blended draw is where its material requested it.
fn dropped(fragment: Fragment, surface: Surface) -> bool {
return fragment.cutout == 1u && surface.color.a < THRESHOLD;
}
// What a draw leaves at a pixel, before the stage that calls it decides the
// depth; its slot's shading map scales the fragment's roughness and
// metallic lanes.
fn shaded(fragment: Fragment) -> vec4<f32> {
let texel = shading_of(fragment);
let scaled = scaled_by(fragment, texel);
let surface = surface_of(scaled, placed_normal(scaled));
return lit(scaled, surface, received(scaled, surface, texel.r));
}
// The same, dropping the texels a cutout instance leaves out.
fn shaded_tested(fragment: Fragment) -> vec4<f32> {
let texel = shading_of(fragment);
let scaled = scaled_by(fragment, texel);
let surface = surface_of(scaled, placed_normal(scaled));
if dropped(scaled, surface) {
discard;
}
return lit(scaled, surface, received(scaled, surface, texel.r));
}
// The same two, lit by the relief the draw's slot holds.
fn faced_shaded(fragment: Fragment) -> vec4<f32> {
let texel = shading_of(fragment);
let scaled = scaled_by(fragment, texel);
let surface = surface_of(scaled, normalize(scaled.normal));
return lit(scaled, surface, received_facing(scaled, surface, texel.r));
}
fn faced_shaded_tested(fragment: Fragment) -> vec4<f32> {
let texel = shading_of(fragment);
let scaled = scaled_by(fragment, texel);
let surface = surface_of(scaled, normalize(scaled.normal));
if dropped(scaled, surface) {
discard;
}
return lit(scaled, surface, received_facing(scaled, surface, texel.r));
}
// What a stage that takes its depth from the plane its draw lies in leaves:
// the color, and that depth in place of the one the corners left.
struct Lying {
@location(0) color: vec4<f32>,
@builtin(frag_depth) depth: f32,
}
@fragment
fn fragment(fragment: Fragment) -> @location(0) vec4<f32> {
return shaded(fragment);
}
// The same, dropping the texels a cutout instance leaves out.
@fragment
fn fragment_tested(fragment: Fragment) -> @location(0) vec4<f32> {
return shaded_tested(fragment);
}
// The depth a draw lying in a plane leaves at this pixel.
fn lying_depth(fragment: Fragment) -> f32 {
return plane_depth(fragment.plane, fragment.clip_position);
}
// The same three for a draw lying in a plane, which takes that plane's
// depth.
@fragment
fn flat_fragment(fragment: Fragment) -> Lying {
return Lying(shaded(fragment), lying_depth(fragment));
}
@fragment
fn flat_fragment_tested(fragment: Fragment) -> Lying {
return Lying(shaded_tested(fragment), lying_depth(fragment));
}
// The same three again, for a draw the viewpoint turned, which lies in the
// upright plane it was placed in.
@fragment
fn faced_fragment(fragment: Fragment) -> Lying {
return Lying(faced_shaded(fragment), lying_depth(fragment));
}
@fragment
fn faced_fragment_tested(fragment: Fragment) -> Lying {
return Lying(faced_shaded_tested(fragment), lying_depth(fragment));
}
// What the sky's vertex stage leaves: where the pixel lies across the
// frame, in the same `-1..1` the corners are placed at.
struct SkyCovered {
@builtin(position) clip_position: vec4<f32>,
@location(0) clip: vec2<f32>,
}
// One triangle large enough to cover the frame, at the far depth, so no
// vertex buffer is bound and the sky lands only where nothing nearer was
// drawn.
@vertex
fn sky_cover(@builtin(vertex_index) index: u32) -> SkyCovered {
let corner = vec2<f32>(f32((index << 1u) & 2u), f32(index & 2u)) * 2.0 - 1.0;
var covered: SkyCovered;
covered.clip_position = vec4<f32>(corner, SKY_FAR, 1.0);
covered.clip = corner;
return covered;
}
// The direction the camera reads the sky along at `clip`. A lens that
// foreshortens takes a ray of its own through every pixel; one that does not
// takes the direction it looks through all of them, which draws one texel
// over the whole frame.
fn sky_along(clip: vec2<f32>) -> vec3<f32> {
if frame.foreshortened == 0u {
return normalize(frame.looking);
}
let along = frame.sky_from_clip * vec4<f32>(clip, SKY_FAR, 1.0);
return normalize(along.xyz / along.w);
}
@fragment
fn sky_draw(covered: SkyCovered) -> @location(0) vec4<f32> {
let along = sky_at(sky_along(covered.clip));
return vec4<f32>(textureSampleLevel(sky, sky_sampler, along, 0.0).rgb, 1.0);
}