mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-09 10:39:46 -10:00
Shader API: translucent terrain in the shadow pass, and its colour
Translucent terrain is drawn over a copy of the solid shadow map, so a pack can tell a point under water or stained glass from one in shadow: shadow_map_translucent holds that depth, and shadow_color the texture colour times the tint of the nearest such surface, with alpha 1 on a fluid. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
This commit is contained in:
1 parent
acc8c21f4c
commit
d36550e82c
6 files changed
+237
-36
No files matched your search
@@ -5597,6 +5597,34 @@ impl Renderer {
|
||||
pass.set_vertex_buffer(1, self.instances.slice(..));
|
||||
self.draw_sections(&mut pass, &self.indirect, 0, shadow_first, shadow_solid);
|
||||
}
|
||||
drop(pass);
|
||||
let mut pass = native
|
||||
.frame
|
||||
.shadow_map
|
||||
.begin_translucent(&mut encoder, "Native shader pack translucent shadow");
|
||||
pass.set_bind_group(0, &self.group, &[]);
|
||||
pass.set_bind_group(horizon_shader_api::stages::GROUP, &shadow.group, &[]);
|
||||
if flat_frame
|
||||
&& let Some(flat) = &self.flat
|
||||
&& let Some(pipeline) = &shadow.flat_translucent
|
||||
{
|
||||
pass.set_pipeline(pipeline);
|
||||
let [_, translucent] = flat.shadow_calls;
|
||||
flat.draw(&mut pass, &flat.calls[translucent..], |p| {
|
||||
self.arena.buffer(p)
|
||||
});
|
||||
} else if !flat_frame {
|
||||
pass.set_pipeline(&shadow.translucent);
|
||||
pass.set_index_buffer(self.indices.slice(..), wgpu::IndexFormat::Uint32);
|
||||
pass.set_vertex_buffer(1, self.instances.slice(..));
|
||||
self.draw_sections(
|
||||
&mut pass,
|
||||
&self.indirect,
|
||||
0,
|
||||
shadow_first + shadow_solid,
|
||||
shadow_translucent,
|
||||
);
|
||||
}
|
||||
}
|
||||
if native_sky_pass
|
||||
&& let Some(native) = &mut self.native
|
||||
|
||||
@@ -24,8 +24,14 @@ pub struct NativePost {
|
||||
nearest: wgpu::Sampler,
|
||||
/// Two targets of the world's size, written in turn (none with a single pass).
|
||||
targets: Option<([u32; 2], [wgpu::TextureView; 2])>,
|
||||
/// The frame's shadow map and its comparison sampler (bindings 6 and 7).
|
||||
shadow: (wgpu::TextureView, wgpu::Sampler),
|
||||
/// The frame's shadow map, its comparison sampler (bindings 6 and 7), the translucent map
|
||||
/// and its colour (bindings 9 and 10).
|
||||
shadow: (
|
||||
wgpu::TextureView,
|
||||
wgpu::Sampler,
|
||||
wgpu::TextureView,
|
||||
wgpu::TextureView,
|
||||
),
|
||||
/// Last frame's input to the last pass (`history`, binding 8), made with the targets,
|
||||
/// and a black 1×1 in its place before then.
|
||||
history: Option<wgpu::Texture>,
|
||||
@@ -165,6 +171,8 @@ impl NativeFrame {
|
||||
texture_entry(3, wgpu::TextureSampleType::Depth),
|
||||
comparison_entry(4),
|
||||
texture_entry(6, wgpu::TextureSampleType::Depth),
|
||||
texture_entry(7, wgpu::TextureSampleType::Depth),
|
||||
texture_entry(8, wgpu::TextureSampleType::Float { filterable: true }),
|
||||
texture_entry(11, wgpu::TextureSampleType::Float { filterable: false }),
|
||||
],
|
||||
});
|
||||
@@ -247,6 +255,14 @@ impl NativeFrame {
|
||||
binding: 4,
|
||||
resource: wgpu::BindingResource::Sampler(&shadow.sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 7,
|
||||
resource: wgpu::BindingResource::TextureView(&shadow.translucent_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 8,
|
||||
resource: wgpu::BindingResource::TextureView(&shadow.color_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 6,
|
||||
resource: wgpu::BindingResource::TextureView(
|
||||
@@ -449,6 +465,8 @@ impl NativePost {
|
||||
texture_entry(6, wgpu::TextureSampleType::Depth),
|
||||
comparison_entry(7),
|
||||
texture_entry(8, float),
|
||||
texture_entry(9, wgpu::TextureSampleType::Depth),
|
||||
texture_entry(10, float),
|
||||
],
|
||||
});
|
||||
let mut groups: [Option<&wgpu::BindGroupLayout>; GROUP as usize + 1] = Default::default();
|
||||
@@ -516,6 +534,8 @@ impl NativePost {
|
||||
shadow: (
|
||||
frame.shadow_map.view.clone(),
|
||||
frame.shadow_map.sampler.clone(),
|
||||
frame.shadow_map.translucent_view.clone(),
|
||||
frame.shadow_map.color_view.clone(),
|
||||
),
|
||||
linear: sampler("Native shader pack linear", wgpu::FilterMode::Linear),
|
||||
nearest: sampler("Native shader pack nearest", wgpu::FilterMode::Nearest),
|
||||
@@ -638,6 +658,14 @@ impl NativePost {
|
||||
binding: 8,
|
||||
resource: wgpu::BindingResource::TextureView(&history),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 9,
|
||||
resource: wgpu::BindingResource::TextureView(&self.shadow.2),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 10,
|
||||
resource: wgpu::BindingResource::TextureView(&self.shadow.3),
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! The shadow map of Horizon's own shader packs (`[shadow]`, docs/shaders.md): each frame the
|
||||
//! solid terrain the light (the sun by day, the moon by night) sees is drawn into a square depth
|
||||
//! map, which the material and post stages read as `shadow_map`. The shadow camera is the one
|
||||
//! map, which the material and post stages read as `shadow_map`; translucent terrain goes over a
|
||||
//! copy of it, `shadow_map_translucent`, with its colour in `shadow_color`. The shadow camera is the one
|
||||
//! `shaders/` packs use (`pack_uniforms::shadow_camera`), so the renderer's shadow visibility and
|
||||
//! draw lists (`ShadowView`) serve both; the map's texels are distorted toward the camera as the
|
||||
//! prelude's `shadow_position` undoes.
|
||||
@@ -21,8 +22,13 @@ struct HorizonShadowOut {
|
||||
@builtin(position) clip: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
@location(1) @interpolate(flat) layer: u32,
|
||||
@location(2) tint: vec3<f32>,
|
||||
@location(3) @interpolate(flat) fluid: u32,
|
||||
}
|
||||
fn horizon_shadow_vertex(pos: vec3<f32>, uv: vec2<f32>, layer: u32) -> HorizonShadowOut {
|
||||
// `color` is the vertex colour: the tint, and the fluid flag in its alpha's high nibble
|
||||
// (mesher.rs's `fluid_face`).
|
||||
fn horizon_shadow_vertex(pos: vec3<f32>, uv: vec2<f32>, layer: u32, color: vec4<f32>)
|
||||
-> HorizonShadowOut {
|
||||
let clip = frame.shadow_view_proj * vec4(pos, 1.0);
|
||||
let k = frame.shadow_distortion;
|
||||
let xy = clip.xy / (length(clip.xy) * k + 1.0 - k);
|
||||
@@ -30,27 +36,42 @@ fn horizon_shadow_vertex(pos: vec3<f32>, uv: vec2<f32>, layer: u32) -> HorizonSh
|
||||
out.clip = vec4(xy, clamp(clip.z, 0.0, 1.0), 1.0);
|
||||
out.uv = uv;
|
||||
out.layer = layer;
|
||||
out.tint = color.rgb;
|
||||
let high = u32(round(color.a * 255.0)) >> 4u;
|
||||
out.fluid = u32(high >= 8u && (high & 7u) < 6u);
|
||||
return out;
|
||||
}
|
||||
@vertex fn horizon_shadow_vs(
|
||||
@location(0) position: vec3<f32>, @location(1) uv: vec2<u32>,
|
||||
@location(2) layer: u32, @location(5) origin: vec4<f32>) -> HorizonShadowOut {
|
||||
return horizon_shadow_vertex(position + origin.xyz, horizon_terrain_terrain_uv(uv, origin.w), layer);
|
||||
@location(2) layer: u32, @location(3) color: vec4<f32>,
|
||||
@location(5) origin: vec4<f32>) -> HorizonShadowOut {
|
||||
return horizon_shadow_vertex(position + origin.xyz, horizon_terrain_terrain_uv(uv, origin.w), layer, color);
|
||||
}
|
||||
@vertex fn horizon_shadow_vs_flat(
|
||||
@builtin(vertex_index) index: u32,
|
||||
@location(0) position: vec3<f32>, @location(1) uv: vec2<u32>, @location(2) layer: u32
|
||||
@location(0) position: vec3<f32>, @location(1) uv: vec2<u32>, @location(2) layer: u32,
|
||||
@location(3) color: vec4<f32>
|
||||
) -> HorizonShadowOut {
|
||||
let origin = horizon_terrain_slot_origins[horizon_terrain_block_slots[index >> 8u]];
|
||||
return horizon_shadow_vertex(position + origin.xyz, horizon_terrain_terrain_uv(uv, origin.w), layer);
|
||||
return horizon_shadow_vertex(position + origin.xyz, horizon_terrain_terrain_uv(uv, origin.w), layer, color);
|
||||
}
|
||||
@fragment fn horizon_shadow_fs(in: HorizonShadowOut) {
|
||||
// Bit 15 of the layer marks opaque leaves (forceOpaque): no alpha cutout.
|
||||
let alpha = horizon_terrain_sprite(in.uv, in.layer & 0x7fffu).a;
|
||||
if alpha < 0.5 && (in.layer & 0x8000u) == 0u { discard; }
|
||||
}
|
||||
// Translucent terrain: its colour where it is the nearest thing the light passes through;
|
||||
// alpha 1 marks a fluid, other surfaces keep their alpha below it.
|
||||
@fragment fn horizon_shadow_fs_translucent(in: HorizonShadowOut) -> @location(0) vec4<f32> {
|
||||
let color = horizon_terrain_sprite(in.uv, in.layer & 0x7fffu);
|
||||
if color.a < 0.01 { discard; }
|
||||
return vec4(color.rgb * in.tint, select(min(color.a, 0.99), 1.0, in.fluid != 0u));
|
||||
}
|
||||
";
|
||||
|
||||
/// `shadow_color`'s format.
|
||||
const SHADOW_COLOR: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
|
||||
|
||||
/// The shader pack's shadow camera for `settings`: orthographic over `distance` each side,
|
||||
/// staying put while the camera walks a `SNAP`-block cell. The map is distorted about its
|
||||
/// centre, so a centre that moves with the camera, even by a texel, resamples every shadow but
|
||||
@@ -89,9 +110,14 @@ pub fn distortion(distance: f32, resolution: u32) -> f32 {
|
||||
}
|
||||
|
||||
/// The shadow map: a square depth map of the pack's resolution, or a 1×1 one (cleared to "far",
|
||||
/// so everything is lit) for a pack without `[shadow]`.
|
||||
/// so everything is lit) for a pack without `[shadow]`; the same with translucent terrain drawn
|
||||
/// in, and that terrain's colour.
|
||||
pub struct ShadowMap {
|
||||
depth: wgpu::Texture,
|
||||
pub view: wgpu::TextureView,
|
||||
translucent: wgpu::Texture,
|
||||
pub translucent_view: wgpu::TextureView,
|
||||
pub color_view: wgpu::TextureView,
|
||||
pub sampler: wgpu::Sampler,
|
||||
/// The 1×1 map still holds the zeroes it was made with.
|
||||
blank_uncleared: bool,
|
||||
@@ -102,20 +128,39 @@ impl ShadowMap {
|
||||
let size = settings
|
||||
.map_or(1, |s| s.resolution)
|
||||
.clamp(1, device.limits().max_texture_dimension_2d);
|
||||
let depth = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("Native shader pack shadow map"),
|
||||
size: wgpu::Extent3d {
|
||||
width: size,
|
||||
height: size,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
view_formats: &[],
|
||||
});
|
||||
let texture = |label, format, usage| {
|
||||
device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some(label),
|
||||
size: wgpu::Extent3d {
|
||||
width: size,
|
||||
height: size,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING
|
||||
| wgpu::TextureUsages::RENDER_ATTACHMENT
|
||||
| usage,
|
||||
view_formats: &[],
|
||||
})
|
||||
};
|
||||
let depth = texture(
|
||||
"Native shader pack shadow map",
|
||||
wgpu::TextureFormat::Depth32Float,
|
||||
wgpu::TextureUsages::COPY_SRC,
|
||||
);
|
||||
let translucent = texture(
|
||||
"Native shader pack translucent shadow map",
|
||||
wgpu::TextureFormat::Depth32Float,
|
||||
wgpu::TextureUsages::COPY_DST,
|
||||
);
|
||||
let color = texture(
|
||||
"Native shader pack shadow colour",
|
||||
SHADOW_COLOR,
|
||||
wgpu::TextureUsages::empty(),
|
||||
);
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("Native shader pack shadow comparison"),
|
||||
compare: Some(wgpu::CompareFunction::LessEqual),
|
||||
@@ -125,6 +170,10 @@ impl ShadowMap {
|
||||
});
|
||||
Self {
|
||||
view: depth.create_view(&Default::default()),
|
||||
depth,
|
||||
translucent_view: translucent.create_view(&Default::default()),
|
||||
translucent,
|
||||
color_view: color.create_view(&Default::default()),
|
||||
sampler,
|
||||
blank_uncleared: settings.is_none(),
|
||||
}
|
||||
@@ -151,10 +200,46 @@ impl ShadowMap {
|
||||
})
|
||||
}
|
||||
|
||||
/// The translucent pass: the solid terrain's map copied into the translucent one, which it
|
||||
/// goes on drawing into, and the colour cleared to white (nothing to pass through).
|
||||
pub fn begin_translucent<'e>(
|
||||
&self,
|
||||
encoder: &'e mut wgpu::CommandEncoder,
|
||||
label: &str,
|
||||
) -> wgpu::RenderPass<'e> {
|
||||
encoder.copy_texture_to_texture(
|
||||
self.depth.as_image_copy(),
|
||||
self.translucent.as_image_copy(),
|
||||
self.depth.size(),
|
||||
);
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some(label),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &self.color_view,
|
||||
resolve_target: None,
|
||||
depth_slice: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.translucent_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Clears the 1×1 map of a pack without `[shadow]` once.
|
||||
pub fn clear_blank(&mut self, encoder: &mut wgpu::CommandEncoder) {
|
||||
if std::mem::take(&mut self.blank_uncleared) {
|
||||
drop(self.begin(encoder, "Native shader pack shadow map clear"));
|
||||
drop(self.begin_translucent(encoder, "Native shader pack shadow colour clear"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,6 +251,10 @@ pub struct NativeShadow {
|
||||
pub camera: Shadow,
|
||||
pub solid: wgpu::RenderPipeline,
|
||||
pub flat: Option<wgpu::RenderPipeline>,
|
||||
/// The translucent terrain's draws into the translucent map and the colour, instanced and
|
||||
/// flat.
|
||||
pub translucent: wgpu::RenderPipeline,
|
||||
pub flat_translucent: Option<wgpu::RenderPipeline>,
|
||||
pub group: wgpu::BindGroup,
|
||||
}
|
||||
|
||||
@@ -245,9 +334,15 @@ impl NativeShadow {
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &instance_attributes,
|
||||
};
|
||||
let color_target = [Some(wgpu::ColorTargetState {
|
||||
format: SHADOW_COLOR,
|
||||
blend: None,
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})];
|
||||
let pipeline = |layout: &wgpu::PipelineLayout,
|
||||
vertex: &str,
|
||||
buffers: &[Option<wgpu::VertexBufferLayout<'_>>]| {
|
||||
buffers: &[Option<wgpu::VertexBufferLayout<'_>>],
|
||||
translucent: bool| {
|
||||
let options = wgpu::PipelineCompilationOptions {
|
||||
constants: &constants,
|
||||
..Default::default()
|
||||
@@ -281,26 +376,44 @@ impl NativeShadow {
|
||||
multisample: Default::default(),
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &module,
|
||||
entry_point: Some("horizon_shadow_fs"),
|
||||
entry_point: Some(if translucent {
|
||||
"horizon_shadow_fs_translucent"
|
||||
} else {
|
||||
"horizon_shadow_fs"
|
||||
}),
|
||||
compilation_options: options,
|
||||
targets: &[],
|
||||
targets: if translucent { &color_target } else { &[] },
|
||||
}),
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
})
|
||||
};
|
||||
let instanced = layout(None);
|
||||
let solid = pipeline(
|
||||
&layout(None),
|
||||
&instanced,
|
||||
"horizon_shadow_vs",
|
||||
&[Some(vertices.clone()), Some(instances.clone())],
|
||||
false,
|
||||
);
|
||||
let translucent = pipeline(
|
||||
&instanced,
|
||||
"horizon_shadow_vs",
|
||||
&[Some(vertices.clone()), Some(instances)],
|
||||
true,
|
||||
);
|
||||
let flat = shape.flat.map(|group| {
|
||||
pipeline(
|
||||
&layout(Some(group)),
|
||||
"horizon_shadow_vs_flat",
|
||||
&[Some(vertices.clone())],
|
||||
)
|
||||
});
|
||||
let flat_layout = shape.flat.map(|group| layout(Some(group)));
|
||||
let flat_pipeline = |translucent| {
|
||||
flat_layout.as_ref().map(|layout| {
|
||||
pipeline(
|
||||
layout,
|
||||
"horizon_shadow_vs_flat",
|
||||
&[Some(vertices.clone())],
|
||||
translucent,
|
||||
)
|
||||
})
|
||||
};
|
||||
let flat = flat_pipeline(false);
|
||||
let flat_translucent = flat_pipeline(true);
|
||||
if let Some(e) = pollster::block_on(scope.pop()) {
|
||||
return Err(format!("shadow pass: {e}"));
|
||||
}
|
||||
@@ -308,6 +421,8 @@ impl NativeShadow {
|
||||
camera: camera(settings),
|
||||
solid,
|
||||
flat,
|
||||
translucent,
|
||||
flat_translucent,
|
||||
group,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -54,6 +54,26 @@ const fn shadow_sampler(binding: u32) -> Resource {
|
||||
}
|
||||
}
|
||||
|
||||
/// The shadow map with translucent terrain at `binding`.
|
||||
const fn shadow_map_translucent(binding: u32) -> Resource {
|
||||
Resource {
|
||||
binding,
|
||||
name: "shadow_map_translucent",
|
||||
ty: "var shadow_map_translucent: texture_depth_2d",
|
||||
doc: "`shadow_map` with the translucent terrain (water, stained glass, ice) the light passes through drawn in too, read with `shadow_sampler` the same way: a point lit in `shadow_map` but not here lies under such a surface, which `shadow_color` gives the colour of.",
|
||||
}
|
||||
}
|
||||
|
||||
/// The colour of the translucent terrain in the shadow map at `binding`.
|
||||
const fn shadow_color(binding: u32) -> Resource {
|
||||
Resource {
|
||||
binding,
|
||||
name: "shadow_color",
|
||||
ty: "var shadow_color: texture_2d<f32>",
|
||||
doc: "The texture colour times the tint (rgb) of the nearest translucent surface the light passes through at each texel of `shadow_map_translucent`, white where there is none; alpha is 1 on a fluid (water) and the texture's alpha, at most 0.99, on anything else. Read it with `textureLoad` at `shadow_position`'s xy times `textureDimensions(shadow_color)`.",
|
||||
}
|
||||
}
|
||||
|
||||
/// A function the material and post preludes give packs.
|
||||
const SHADOW_HELPERS: &str = "\
|
||||
// Where the camera-relative point `position` is stored in the shadow map: xy its 0..1 texture
|
||||
@@ -66,7 +86,7 @@ fn shadow_position(position: vec3<f32>) -> vec3<f32> {
|
||||
}
|
||||
";
|
||||
|
||||
const POST_RESOURCES: [Resource; 9] = [
|
||||
const POST_RESOURCES: [Resource; 11] = [
|
||||
FRAME,
|
||||
Resource {
|
||||
binding: 1,
|
||||
@@ -106,11 +126,13 @@ const POST_RESOURCES: [Resource; 9] = [
|
||||
ty: "var history: texture_2d<f32>",
|
||||
doc: "Last frame's input to the last pass (what the pass before it wrote), for temporal passes (TAA, denoising): put such a pass second to last and it reads its own previous output; reproject with `frame.previous_view_projection`. Black on the first frame, after a gap of over half a second, and with a single pass.",
|
||||
},
|
||||
shadow_map_translucent(9),
|
||||
shadow_color(10),
|
||||
];
|
||||
|
||||
/// The material stage's resources: the frame, and the world as it was before translucent
|
||||
/// terrain drew (for water and glass to see what lies behind them).
|
||||
const MATERIAL_RESOURCES: [Resource; 7] = [
|
||||
const MATERIAL_RESOURCES: [Resource; 9] = [
|
||||
FRAME,
|
||||
Resource {
|
||||
binding: 1,
|
||||
@@ -126,6 +148,8 @@ const MATERIAL_RESOURCES: [Resource; 7] = [
|
||||
},
|
||||
shadow_map(3),
|
||||
shadow_sampler(4),
|
||||
shadow_map_translucent(7),
|
||||
shadow_color(8),
|
||||
Resource {
|
||||
binding: 6,
|
||||
name: "opaque_depth",
|
||||
|
||||
@@ -76,6 +76,8 @@ Resources (bind group 3):
|
||||
| 2 | `var reflection_sampler: sampler` | Bilinear filtering, clamped to the edges. |
|
||||
| 3 | `var shadow_map: texture_depth_2d` | This frame's shadow map: the depth of the solid terrain the light sees, 0 at the light, 1 away from it (and where nothing is), stored at `shadow_position`'s mapping. Valid while `frame.has_shadow` is 1 (the pack asks for it with `[shadow]`), else a 1×1 map that lights everything. |
|
||||
| 4 | `var shadow_sampler: sampler_comparison` | Depth comparison, bilinear (2×2 percentage-closer filtering): `textureSampleCompareLevel(shadow_map, shadow_sampler, p.xy, p.z)` is 1 where the point is lit, 0 in shadow. |
|
||||
| 7 | `var shadow_map_translucent: texture_depth_2d` | `shadow_map` with the translucent terrain (water, stained glass, ice) the light passes through drawn in too, read with `shadow_sampler` the same way: a point lit in `shadow_map` but not here lies under such a surface, which `shadow_color` gives the colour of. |
|
||||
| 8 | `var shadow_color: texture_2d<f32>` | The texture colour times the tint (rgb) of the nearest translucent surface the light passes through at each texel of `shadow_map_translucent`, white where there is none; alpha is 1 on a fluid (water) and the texture's alpha, at most 0.99, on anything else. Read it with `textureLoad` at `shadow_position`'s xy times `textureDimensions(shadow_color)`. |
|
||||
| 6 | `var opaque_depth: texture_depth_2d` | The world's depth before translucent terrain (reversed, like `scene_depth`), for the translucent pass: `textureLoad(opaque_depth, vec2<i32>(input.screen * frame.resolution), 0)` is what lies behind water or glass. Stale while solid terrain draws. |
|
||||
| 11 | `var opaque_color: texture_2d<f32>` | The world's picture before translucent terrain (sky, solid terrain, entities), the size of `opaque_depth`, for the translucent pass: what a surface refracts. Read it with `textureLoad`. Stale while solid terrain draws; black when the world draws straight to the window (a pack without `[[post]]` passes, at full resolution). |
|
||||
|
||||
@@ -107,6 +109,8 @@ Resources (bind group 3):
|
||||
| 6 | `var shadow_map: texture_depth_2d` | This frame's shadow map: the depth of the solid terrain the light sees, 0 at the light, 1 away from it (and where nothing is), stored at `shadow_position`'s mapping. Valid while `frame.has_shadow` is 1 (the pack asks for it with `[shadow]`), else a 1×1 map that lights everything. |
|
||||
| 7 | `var shadow_sampler: sampler_comparison` | Depth comparison, bilinear (2×2 percentage-closer filtering): `textureSampleCompareLevel(shadow_map, shadow_sampler, p.xy, p.z)` is 1 where the point is lit, 0 in shadow. |
|
||||
| 8 | `var history: texture_2d<f32>` | Last frame's input to the last pass (what the pass before it wrote), for temporal passes (TAA, denoising): put such a pass second to last and it reads its own previous output; reproject with `frame.previous_view_projection`. Black on the first frame, after a gap of over half a second, and with a single pass. |
|
||||
| 9 | `var shadow_map_translucent: texture_depth_2d` | `shadow_map` with the translucent terrain (water, stained glass, ice) the light passes through drawn in too, read with `shadow_sampler` the same way: a point lit in `shadow_map` but not here lies under such a surface, which `shadow_color` gives the colour of. |
|
||||
| 10 | `var shadow_color: texture_2d<f32>` | The texture colour times the tint (rgb) of the nearest translucent surface the light passes through at each texel of `shadow_map_translucent`, white where there is none; alpha is 1 on a fluid (water) and the texture's alpha, at most 0.99, on anything else. Read it with `textureLoad` at `shadow_position`'s xy times `textureDimensions(shadow_color)`. |
|
||||
|
||||
Helpers:
|
||||
|
||||
|
||||
@@ -107,6 +107,8 @@ stages.
|
||||
|
||||
## Changelog
|
||||
|
||||
- 2026-10-31: API 0.1 (additions): translucent terrain in the shadow pass: `shadow_map_translucent`
|
||||
and `shadow_color`, so a pack can tint the light that passes through water and stained glass.
|
||||
- 2026-10-31: API 0.1 (additions): `[shadow]`: the engine draws the solid terrain the light sees
|
||||
into a distorted depth map each frame, which the material and post stages read as `shadow_map`
|
||||
with `shadow_sampler` and the prelude's `shadow_position`; the frame's `light_direction`,
|
||||
|
||||
Reference in new issue
Block a user