const BRICK_SIZE: u32 = 8u;
const BRICK_CORNER_COUNT: u32 = 9u;
const GRID_SIZE: u32 = 128u;
const EMPTY_BRICK: i32 = -1;
struct SdfUniforms {
view_projection: mat4x4<f32>,
inverse_view_projection: mat4x4<f32>,
camera_position: vec4<f32>,
clipmap_center: vec4<f32>,
level_voxel_sizes: array<vec4<f32>, 3>,
level_origins: array<array<vec4<f32>, 2>, 5>,
edits_bounds_min: vec4<f32>,
edits_bounds_max: vec4<f32>,
level_count: u32,
grid_size: u32,
brick_size: u32,
atlas_size: u32,
screen_width: f32,
screen_height: f32,
_pad_sun0: f32,
_pad_sun1: f32,
sun_direction: vec4<f32>,
sun_color: vec4<f32>,
ambient_color: vec4<f32>,
debug_brick_coloring: u32,
terrain_enabled: u32,
terrain_base_height: f32,
terrain_seed: u32,
terrain_octaves: u32,
terrain_frequency: f32,
terrain_amplitude: f32,
terrain_gain: f32,
terrain_max_extent: f32,
terrain_material_id: u32,
_padding0: u32,
_padding1: u32,
_padding2: u32,
_padding3: u32,
_padding4: u32,
_padding5: u32,
}
struct SdfMaterial {
base_color: vec4<f32>,
roughness_metallic_emissive: vec4<f32>,
}
const POINTERS_PER_LEVEL: u32 = GRID_SIZE * GRID_SIZE * GRID_SIZE;
@group(0) @binding(0) var<uniform> uniforms: SdfUniforms;
@group(0) @binding(1) var<storage, read> brick_pointers: array<i32>;
@group(0) @binding(2) var brick_atlas: texture_3d<f32>;
@group(0) @binding(3) var atlas_sampler: sampler;
@group(0) @binding(4) var<storage, read> materials: array<SdfMaterial>;
@group(0) @binding(5) var material_atlas: texture_3d<u32>;
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) uv: vec2<f32>,
}
@vertex
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
var positions = array<vec2<f32>, 6>(
vec2<f32>(-1.0, -1.0),
vec2<f32>(1.0, -1.0),
vec2<f32>(1.0, 1.0),
vec2<f32>(-1.0, -1.0),
vec2<f32>(1.0, 1.0),
vec2<f32>(-1.0, 1.0)
);
var uvs = array<vec2<f32>, 6>(
vec2<f32>(0.0, 1.0),
vec2<f32>(1.0, 1.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(0.0, 1.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(0.0, 0.0)
);
var output: VertexOutput;
output.position = vec4<f32>(positions[vertex_index], 0.0, 1.0);
output.uv = uvs[vertex_index];
return output;
}
fn brick_pointer_to_color(brick_pointer: i32) -> vec3<f32> {
let p = f32(brick_pointer);
let r = fract(p * 0.1031 + 0.1);
let g = fract(p * 0.1047 + 0.3);
let b = fract(p * 0.1087 + 0.7);
let brightness = 0.4 + 0.6 * fract(p * 0.0731);
return vec3<f32>(r, g, b) * brightness;
}
fn get_voxel_size(level: u32) -> f32 {
let array_index = level / 4u;
let component = level % 4u;
return uniforms.level_voxel_sizes[array_index][component];
}
fn get_brick_pointer_for_level(level: u32, world_brick_coord: vec3<i32>) -> i32 {
if level >= uniforms.level_count {
return EMPTY_BRICK;
}
let origin = vec3<i32>(get_level_origin_brick(level));
let relative = world_brick_coord - origin;
let grid_size_i = i32(GRID_SIZE);
if relative.x < 0 || relative.x >= grid_size_i ||
relative.y < 0 || relative.y >= grid_size_i ||
relative.z < 0 || relative.z >= grid_size_i {
return EMPTY_BRICK;
}
let wrapped = ((world_brick_coord % grid_size_i) + grid_size_i) % grid_size_i;
let local_index = u32(wrapped.x) + u32(wrapped.y) * GRID_SIZE + u32(wrapped.z) * GRID_SIZE * GRID_SIZE;
let global_index = level * POINTERS_PER_LEVEL + local_index;
return brick_pointers[global_index];
}
fn get_level_origin_brick(level: u32) -> vec3<f32> {
let array_index = level / 2u;
let component_index = level % 2u;
return uniforms.level_origins[array_index][component_index].xyz;
}
fn get_level_half_extent(level: u32) -> f32 {
let base_voxel_size = get_voxel_size(0u);
let base_brick_size = base_voxel_size * f32(BRICK_SIZE);
let level_brick_size = base_brick_size * f32(1u << level);
return level_brick_size * f32(GRID_SIZE) * 0.5;
}
fn brick_index_to_atlas(brick_index: u32) -> vec3<u32> {
let bricks_per_dim = uniforms.atlas_size / BRICK_CORNER_COUNT;
let bricks_per_slice = bricks_per_dim * bricks_per_dim;
let z = brick_index / bricks_per_slice;
let remainder = brick_index % bricks_per_slice;
let y = remainder / bricks_per_dim;
let x = remainder % bricks_per_dim;
return vec3<u32>(x * BRICK_CORNER_COUNT, y * BRICK_CORNER_COUNT, z * BRICK_CORNER_COUNT);
}
fn sample_brick_trilinear(brick_index: i32, local_uvw: vec3<f32>) -> f32 {
let atlas_base = brick_index_to_atlas(u32(brick_index));
let clamped_uvw = clamp(local_uvw, vec3<f32>(0.0), vec3<f32>(1.0));
let texel_pos = clamped_uvw * f32(BRICK_SIZE);
let x0 = u32(floor(texel_pos.x));
let y0 = u32(floor(texel_pos.y));
let z0 = u32(floor(texel_pos.z));
let x1 = min(x0 + 1u, u32(BRICK_SIZE));
let y1 = min(y0 + 1u, u32(BRICK_SIZE));
let z1 = min(z0 + 1u, u32(BRICK_SIZE));
let fx = texel_pos.x - f32(x0);
let fy = texel_pos.y - f32(y0);
let fz = texel_pos.z - f32(z0);
let d000 = textureLoad(brick_atlas, vec3<i32>(atlas_base) + vec3<i32>(i32(x0), i32(y0), i32(z0)), 0).r;
let d100 = textureLoad(brick_atlas, vec3<i32>(atlas_base) + vec3<i32>(i32(x1), i32(y0), i32(z0)), 0).r;
let d010 = textureLoad(brick_atlas, vec3<i32>(atlas_base) + vec3<i32>(i32(x0), i32(y1), i32(z0)), 0).r;
let d110 = textureLoad(brick_atlas, vec3<i32>(atlas_base) + vec3<i32>(i32(x1), i32(y1), i32(z0)), 0).r;
let d001 = textureLoad(brick_atlas, vec3<i32>(atlas_base) + vec3<i32>(i32(x0), i32(y0), i32(z1)), 0).r;
let d101 = textureLoad(brick_atlas, vec3<i32>(atlas_base) + vec3<i32>(i32(x1), i32(y0), i32(z1)), 0).r;
let d011 = textureLoad(brick_atlas, vec3<i32>(atlas_base) + vec3<i32>(i32(x0), i32(y1), i32(z1)), 0).r;
let d111 = textureLoad(brick_atlas, vec3<i32>(atlas_base) + vec3<i32>(i32(x1), i32(y1), i32(z1)), 0).r;
let c00 = mix(d000, d100, fx);
let c10 = mix(d010, d110, fx);
let c01 = mix(d001, d101, fx);
let c11 = mix(d011, d111, fx);
let c0 = mix(c00, c10, fy);
let c1 = mix(c01, c11, fy);
return mix(c0, c1, fz);
}
fn compute_analytic_normal_at_brick(brick_index: i32, local_uvw: vec3<f32>) -> vec3<f32> {
let atlas_base = brick_index_to_atlas(u32(brick_index));
let clamped_uvw = clamp(local_uvw, vec3<f32>(0.001), vec3<f32>(0.999));
let texel_pos = clamped_uvw * f32(BRICK_SIZE);
let x0 = u32(floor(texel_pos.x));
let y0 = u32(floor(texel_pos.y));
let z0 = u32(floor(texel_pos.z));
let x1 = min(x0 + 1u, u32(BRICK_SIZE));
let y1 = min(y0 + 1u, u32(BRICK_SIZE));
let z1 = min(z0 + 1u, u32(BRICK_SIZE));
let fx = texel_pos.x - f32(x0);
let fy = texel_pos.y - f32(y0);
let fz = texel_pos.z - f32(z0);
let s000 = textureLoad(brick_atlas, vec3<i32>(atlas_base) + vec3<i32>(i32(x0), i32(y0), i32(z0)), 0).r;
let s100 = textureLoad(brick_atlas, vec3<i32>(atlas_base) + vec3<i32>(i32(x1), i32(y0), i32(z0)), 0).r;
let s010 = textureLoad(brick_atlas, vec3<i32>(atlas_base) + vec3<i32>(i32(x0), i32(y1), i32(z0)), 0).r;
let s110 = textureLoad(brick_atlas, vec3<i32>(atlas_base) + vec3<i32>(i32(x1), i32(y1), i32(z0)), 0).r;
let s001 = textureLoad(brick_atlas, vec3<i32>(atlas_base) + vec3<i32>(i32(x0), i32(y0), i32(z1)), 0).r;
let s101 = textureLoad(brick_atlas, vec3<i32>(atlas_base) + vec3<i32>(i32(x1), i32(y0), i32(z1)), 0).r;
let s011 = textureLoad(brick_atlas, vec3<i32>(atlas_base) + vec3<i32>(i32(x0), i32(y1), i32(z1)), 0).r;
let s111 = textureLoad(brick_atlas, vec3<i32>(atlas_base) + vec3<i32>(i32(x1), i32(y1), i32(z1)), 0).r;
let dx_y0 = mix(s100 - s000, s110 - s010, fy);
let dx_y1 = mix(s101 - s001, s111 - s011, fy);
let grad_x = mix(dx_y0, dx_y1, fz);
let dy_x0 = mix(s010 - s000, s110 - s100, fx);
let dy_x1 = mix(s011 - s001, s111 - s101, fx);
let grad_y = mix(dy_x0, dy_x1, fz);
let dz_x0 = mix(s001 - s000, s101 - s100, fx);
let dz_x1 = mix(s011 - s010, s111 - s110, fx);
let grad_z = mix(dz_x0, dz_x1, fy);
let gradient = vec3<f32>(grad_x, grad_y, grad_z);
let len = length(gradient);
if len > 0.0001 {
return gradient / len;
}
return vec3<f32>(0.0, 1.0, 0.0);
}
fn sample_material_id(brick_pointer: i32, local_uvw: vec3<f32>) -> u32 {
let bricks_per_dim = uniforms.atlas_size / BRICK_CORNER_COUNT;
let bricks_per_slice = bricks_per_dim * bricks_per_dim;
let brick_index = u32(brick_pointer);
let brick_z = brick_index / bricks_per_slice;
let remainder = brick_index % bricks_per_slice;
let brick_y = remainder / bricks_per_dim;
let brick_x = remainder % bricks_per_dim;
let material_base = vec3<u32>(
brick_x * BRICK_SIZE,
brick_y * BRICK_SIZE,
brick_z * BRICK_SIZE
);
let clamped_uvw = clamp(local_uvw, vec3<f32>(0.0), vec3<f32>(0.999));
let voxel_coord = vec3<u32>(clamped_uvw * f32(BRICK_SIZE));
let sample_coord = material_base + voxel_coord;
return textureLoad(material_atlas, vec3<i32>(sample_coord), 0).r;
}
fn intersect_aabb(ray_origin: vec3<f32>, ray_dir: vec3<f32>, box_min: vec3<f32>, box_max: vec3<f32>) -> vec2<f32> {
let inv_dir = 1.0 / ray_dir;
let t1 = (box_min - ray_origin) * inv_dir;
let t2 = (box_max - ray_origin) * inv_dir;
let t_min = min(t1, t2);
let t_max = max(t1, t2);
let t_near = max(max(t_min.x, t_min.y), t_min.z);
let t_far = min(min(t_max.x, t_max.y), t_max.z);
return vec2<f32>(t_near, t_far);
}
struct HitResult {
hit: bool,
position: vec3<f32>,
normal: vec3<f32>,
material_id: u32,
distance: f32,
level: u32,
voxel_size: f32,
brick_pointer: i32,
}
fn select_level_for_position(world_pos: vec3<f32>) -> u32 {
let diff = world_pos - uniforms.clipmap_center.xyz;
let dist_from_center = max(abs(diff.x), max(abs(diff.y), abs(diff.z)));
for (var level = 0u; level < uniforms.level_count; level += 1u) {
let half_extent = get_level_half_extent(level);
if dist_from_center < half_extent * 0.85 {
return level;
}
}
return uniforms.level_count - 1u;
}
struct LevelBlendInfo {
primary_level: u32,
secondary_level: u32,
blend_factor: f32,
}
fn get_level_blend_info(world_pos: vec3<f32>) -> LevelBlendInfo {
let diff = world_pos - uniforms.clipmap_center.xyz;
let dist_from_center = max(abs(diff.x), max(abs(diff.y), abs(diff.z)));
var info: LevelBlendInfo;
info.primary_level = uniforms.level_count - 1u;
info.secondary_level = uniforms.level_count - 1u;
info.blend_factor = 0.0;
for (var level = 0u; level < uniforms.level_count; level += 1u) {
let half_extent = get_level_half_extent(level);
let inner_threshold = half_extent * 0.7;
let outer_threshold = half_extent * 0.85;
if dist_from_center < inner_threshold {
info.primary_level = level;
info.secondary_level = level;
info.blend_factor = 0.0;
return info;
}
if dist_from_center < outer_threshold {
let t = (dist_from_center - inner_threshold) / (outer_threshold - inner_threshold);
info.primary_level = level;
info.secondary_level = min(level + 1u, uniforms.level_count - 1u);
info.blend_factor = smoothstep(0.0, 1.0, t);
return info;
}
}
return info;
}
struct BrickLookupResult {
found: bool,
brick_pointer: i32,
level: u32,
voxel_size: f32,
brick_world_size: f32,
brick_origin: vec3<f32>,
local_uvw: vec3<f32>,
}
fn find_brick_at_position(world_pos: vec3<f32>, preferred_level: u32) -> BrickLookupResult {
var result: BrickLookupResult;
result.found = false;
result.brick_pointer = EMPTY_BRICK;
for (var check_level = preferred_level; check_level < uniforms.level_count; check_level += 1u) {
let voxel_size = get_voxel_size(check_level);
let brick_world_size = voxel_size * f32(BRICK_SIZE);
let brick_coord = vec3<i32>(floor(world_pos / brick_world_size));
let brick_pointer = get_brick_pointer_for_level(check_level, brick_coord);
if brick_pointer != EMPTY_BRICK {
result.found = true;
result.brick_pointer = brick_pointer;
result.level = check_level;
result.voxel_size = voxel_size;
result.brick_world_size = brick_world_size;
result.brick_origin = vec3<f32>(brick_coord) * brick_world_size;
result.local_uvw = (world_pos - result.brick_origin) / brick_world_size;
return result;
}
}
result.level = preferred_level;
result.voxel_size = get_voxel_size(preferred_level);
result.brick_world_size = result.voxel_size * f32(BRICK_SIZE);
return result;
}
fn terrain_hash_render(ix: i32, iz: i32, seed: u32) -> f32 {
var n = (ix * 1619) ^ (iz * 6971) ^ (i32(seed) * 1013);
n = n * n * n;
n = n ^ (n >> 13);
return f32(n & 0x7fffffff) / 2147483647.0;
}
fn noised_2d_render(px: f32, pz: f32, seed: u32) -> vec3<f32> {
let cell_x = i32(floor(px));
let cell_z = i32(floor(pz));
let wx = px - f32(cell_x);
let wz = pz - f32(cell_z);
let ux = wx * wx * wx * (wx * (wx * 6.0 - 15.0) + 10.0);
let uz = wz * wz * wz * (wz * (wz * 6.0 - 15.0) + 10.0);
let dux = 30.0 * wx * wx * (wx * (wx - 2.0) + 1.0);
let duz = 30.0 * wz * wz * (wz * (wz - 2.0) + 1.0);
let h00 = terrain_hash_render(cell_x, cell_z, seed);
let h10 = terrain_hash_render(cell_x + 1, cell_z, seed);
let h01 = terrain_hash_render(cell_x, cell_z + 1, seed);
let h11 = terrain_hash_render(cell_x + 1, cell_z + 1, seed);
let k0 = h00;
let k1 = h10 - h00;
let k2 = h01 - h00;
let k3 = h00 - h10 - h01 + h11;
let value = -1.0 + 2.0 * (k0 + k1 * ux + k2 * uz + k3 * ux * uz);
let dvdx = 2.0 * dux * (k1 + k3 * uz);
let dvdz = 2.0 * duz * (k2 + k3 * ux);
return vec3<f32>(value, dvdx, dvdz);
}
fn terrain_fbm_render(world_pos: vec3<f32>) -> f32 {
var px = world_pos.x * uniforms.terrain_frequency;
var pz = world_pos.z * uniforms.terrain_frequency;
var total = 0.0;
var amplitude = uniforms.terrain_amplitude;
var dx_sum = 0.0;
var dz_sum = 0.0;
for (var octave = 0u; octave < uniforms.terrain_octaves; octave += 1u) {
let n = noised_2d_render(px, pz, uniforms.terrain_seed);
dx_sum += n.y * 0.5;
dz_sum += n.z * 0.5;
let dampening = 1.0 / (1.0 + dx_sum * dx_sum + dz_sum * dz_sum);
total += amplitude * n.x * dampening;
let new_px = 1.6 * px - 1.2 * pz;
let new_pz = 1.2 * px + 1.6 * pz;
px = new_px;
pz = new_pz;
amplitude *= uniforms.terrain_gain;
}
return world_pos.y - uniforms.terrain_base_height - total;
}
fn analytic_terrain_distance(world_pos: vec3<f32>) -> f32 {
return terrain_fbm_render(world_pos);
}
fn analytic_terrain_normal(world_pos: vec3<f32>) -> vec3<f32> {
let epsilon = 0.1;
let dx = analytic_terrain_distance(world_pos + vec3<f32>(epsilon, 0.0, 0.0))
- analytic_terrain_distance(world_pos - vec3<f32>(epsilon, 0.0, 0.0));
let dy = analytic_terrain_distance(world_pos + vec3<f32>(0.0, epsilon, 0.0))
- analytic_terrain_distance(world_pos - vec3<f32>(0.0, epsilon, 0.0));
let dz = analytic_terrain_distance(world_pos + vec3<f32>(0.0, 0.0, epsilon))
- analytic_terrain_distance(world_pos - vec3<f32>(0.0, 0.0, epsilon));
let gradient = vec3<f32>(dx, dy, dz);
let len = length(gradient);
if len > 0.0001 {
return gradient / len;
}
return vec3<f32>(0.0, 1.0, 0.0);
}
fn is_in_edits_bounds(world_pos: vec3<f32>, margin: f32) -> bool {
let bounds_min = uniforms.edits_bounds_min.xyz - vec3<f32>(margin);
let bounds_max = uniforms.edits_bounds_max.xyz + vec3<f32>(margin);
return world_pos.x >= bounds_min.x && world_pos.x <= bounds_max.x &&
world_pos.y >= bounds_min.y && world_pos.y <= bounds_max.y &&
world_pos.z >= bounds_min.z && world_pos.z <= bounds_max.z;
}
fn sample_sdf_at_level(world_pos: vec3<f32>, level: u32) -> f32 {
let voxel_size = get_voxel_size(level);
let brick_world_size = voxel_size * f32(BRICK_SIZE);
let brick_coord = vec3<i32>(floor(world_pos / brick_world_size));
let brick_pointer = get_brick_pointer_for_level(level, brick_coord);
if brick_pointer != EMPTY_BRICK {
let brick_origin = vec3<f32>(brick_coord) * brick_world_size;
let local_uvw = (world_pos - brick_origin) / brick_world_size;
return sample_brick_trilinear(brick_pointer, local_uvw);
}
for (var fallback_level = level + 1u; fallback_level < uniforms.level_count; fallback_level += 1u) {
let fallback_voxel_size = get_voxel_size(fallback_level);
let fallback_brick_world_size = fallback_voxel_size * f32(BRICK_SIZE);
let fallback_brick_coord = vec3<i32>(floor(world_pos / fallback_brick_world_size));
let fallback_pointer = get_brick_pointer_for_level(fallback_level, fallback_brick_coord);
if fallback_pointer != EMPTY_BRICK {
let fallback_brick_origin = vec3<f32>(fallback_brick_coord) * fallback_brick_world_size;
let fallback_local_uvw = (world_pos - fallback_brick_origin) / fallback_brick_world_size;
return sample_brick_trilinear(fallback_pointer, fallback_local_uvw);
}
}
if uniforms.terrain_enabled != 0u {
return analytic_terrain_distance(world_pos);
}
return 1000.0;
}
fn sample_sdf_blended(world_pos: vec3<f32>) -> f32 {
let in_edits_region = uniforms.edits_bounds_min.x <= uniforms.edits_bounds_max.x &&
is_in_edits_bounds(world_pos, 2.0);
if in_edits_region {
let blend_info = get_level_blend_info(world_pos);
let primary_distance = sample_sdf_at_level(world_pos, blend_info.primary_level);
if blend_info.blend_factor < 0.001 {
return primary_distance;
}
let secondary_distance = sample_sdf_at_level(world_pos, blend_info.secondary_level);
return mix(primary_distance, secondary_distance, blend_info.blend_factor);
}
if uniforms.terrain_enabled != 0u {
let blend_info = get_level_blend_info(world_pos);
let primary_distance = sample_sdf_at_level(world_pos, blend_info.primary_level);
if blend_info.blend_factor < 0.001 {
return primary_distance;
}
let secondary_distance = sample_sdf_at_level(world_pos, blend_info.secondary_level);
return mix(primary_distance, secondary_distance, blend_info.blend_factor);
}
return 1000.0;
}
fn trace_sdf(ray_origin: vec3<f32>, ray_dir: vec3<f32>, max_distance: f32) -> HitResult {
var result: HitResult;
result.hit = false;
result.distance = max_distance;
result.level = 0u;
result.voxel_size = get_voxel_size(0u);
result.brick_pointer = -1;
var current_t = 0.001;
var effective_max = max_distance;
let bounds_min = uniforms.edits_bounds_min.xyz;
let bounds_max = uniforms.edits_bounds_max.xyz;
let has_edits = bounds_min.x <= bounds_max.x;
let terrain_min_y = uniforms.terrain_base_height - uniforms.terrain_max_extent;
let terrain_max_y = uniforms.terrain_base_height + uniforms.terrain_max_extent;
if uniforms.terrain_enabled != 0u {
let terrain_aabb_min = vec3<f32>(-1e6, terrain_min_y, -1e6);
let terrain_aabb_max = vec3<f32>(1e6, terrain_max_y, 1e6);
if has_edits {
let t_bounds = intersect_aabb(ray_origin, ray_dir, bounds_min, bounds_max);
let edits_t_near = max(0.001, t_bounds.x);
let edits_t_far = t_bounds.y;
let edits_hit = edits_t_near <= edits_t_far && edits_t_far >= 0.0;
let t_terrain_bounds = intersect_aabb(ray_origin, ray_dir, terrain_aabb_min, terrain_aabb_max);
let terrain_hit = t_terrain_bounds.x <= t_terrain_bounds.y && t_terrain_bounds.y >= 0.0;
if !edits_hit && !terrain_hit {
return result;
}
var t_far_combined = 0.0;
if edits_hit {
t_far_combined = max(t_far_combined, edits_t_far + 1.0);
}
if terrain_hit {
t_far_combined = max(t_far_combined, t_terrain_bounds.y + 1.0);
current_t = max(0.001, min(current_t, t_terrain_bounds.x));
}
if edits_hit {
current_t = max(0.001, min(current_t, edits_t_near));
}
effective_max = min(max_distance, t_far_combined);
} else {
let t_terrain_bounds = intersect_aabb(ray_origin, ray_dir, terrain_aabb_min, terrain_aabb_max);
if t_terrain_bounds.x > t_terrain_bounds.y || t_terrain_bounds.y < 0.0 {
return result;
}
current_t = max(0.001, t_terrain_bounds.x);
effective_max = min(max_distance, t_terrain_bounds.y + 1.0);
}
} else {
if !has_edits {
return result;
}
let t_bounds = intersect_aabb(ray_origin, ray_dir, bounds_min, bounds_max);
let t_near = t_bounds.x;
let t_far = t_bounds.y;
if t_near > t_far || t_far < 0.0 {
return result;
}
current_t = max(0.001, t_near);
effective_max = min(max_distance, t_far);
}
let base_voxel_size = get_voxel_size(0u);
let min_step = base_voxel_size * 0.5;
let surface_threshold = base_voxel_size * 0.1;
var prev_step = min_step;
for (var iteration = 0u; iteration < 512u; iteration += 1u) {
if current_t >= effective_max {
break;
}
let current_pos = ray_origin + ray_dir * current_t;
let distance = sample_sdf_blended(current_pos);
if distance > 500.0 {
let skip_level = select_level_for_position(current_pos);
let skip_voxel_size = get_voxel_size(skip_level);
let skip_step = max(skip_voxel_size * f32(BRICK_SIZE) * 2.0, 4.0);
prev_step = skip_step;
current_t += skip_step;
continue;
}
if distance < surface_threshold {
let surface_level = select_level_for_position(current_pos);
var t_lo = max(0.001, current_t - prev_step);
var t_hi = current_t;
for (var refine = 0u; refine < 8u; refine += 1u) {
let t_mid = (t_lo + t_hi) * 0.5;
let mid_pos = ray_origin + ray_dir * t_mid;
let mid_dist = sample_sdf_at_level(mid_pos, surface_level);
if mid_dist < 0.0 {
t_hi = t_mid;
} else {
t_lo = t_mid;
}
}
let hit_t = (t_lo + t_hi) * 0.5;
let hit_pos = ray_origin + ray_dir * hit_t;
let in_edits = has_edits && is_in_edits_bounds(hit_pos, 2.0);
let hit_lookup = find_brick_at_position(hit_pos, surface_level);
if hit_lookup.found {
var carved = false;
if uniforms.terrain_enabled != 0u && in_edits {
let raw_terrain_dist = analytic_terrain_distance(hit_pos);
let brick_sdf = sample_brick_trilinear(hit_lookup.brick_pointer, hit_lookup.local_uvw);
if raw_terrain_dist <= 0.0 && brick_sdf > 0.0 {
carved = true;
}
}
if !carved {
result.hit = true;
result.position = hit_pos;
result.distance = hit_t;
result.level = hit_lookup.level;
result.voxel_size = hit_lookup.voxel_size;
result.brick_pointer = hit_lookup.brick_pointer;
result.normal = compute_analytic_normal_at_brick(hit_lookup.brick_pointer, hit_lookup.local_uvw);
result.material_id = sample_material_id(hit_lookup.brick_pointer, hit_lookup.local_uvw);
return result;
}
} else if uniforms.terrain_enabled != 0u {
let raw_terrain_dist = analytic_terrain_distance(hit_pos);
if raw_terrain_dist <= 0.0 {
result.hit = true;
result.position = hit_pos;
result.distance = hit_t;
result.level = 0u;
result.voxel_size = base_voxel_size;
result.brick_pointer = -1;
result.normal = analytic_terrain_normal(hit_pos);
result.material_id = uniforms.terrain_material_id;
return result;
}
}
}
var step_size = max(distance * 0.9, min_step);
if uniforms.terrain_enabled != 0u {
let terrain_min_y = uniforms.terrain_base_height - uniforms.terrain_max_extent;
let terrain_max_y = uniforms.terrain_base_height + uniforms.terrain_max_extent;
if current_pos.y >= terrain_min_y && current_pos.y <= terrain_max_y {
step_size = max(distance * 0.5, min_step);
} else {
var dist_to_band = 0.0;
if current_pos.y > terrain_max_y {
dist_to_band = current_pos.y - terrain_max_y;
} else {
dist_to_band = terrain_min_y - current_pos.y;
}
step_size = min(step_size, max(dist_to_band, min_step));
}
}
if has_edits && uniforms.terrain_enabled != 0u {
let clamped_pos = clamp(current_pos, bounds_min, bounds_max);
let to_edits = current_pos - clamped_pos;
let dist_to_edits = length(to_edits);
if dist_to_edits > 0.0 {
step_size = min(step_size, max(dist_to_edits, min_step));
} else {
let max_step = base_voxel_size * f32(BRICK_SIZE);
step_size = min(step_size, max_step);
}
}
prev_step = step_size;
current_t += step_size;
}
return result;
}
struct FragmentOutput {
@location(0) color: vec4<f32>,
@builtin(frag_depth) depth: f32,
}
@fragment
fn fs_main(input: VertexOutput) -> FragmentOutput {
let ndc = vec4<f32>(
input.uv.x * 2.0 - 1.0,
(1.0 - input.uv.y) * 2.0 - 1.0,
0.0,
1.0
);
let ndc_far = vec4<f32>(ndc.xy, 1.0, 1.0);
let world_far = uniforms.inverse_view_projection * ndc_far;
let world_pos_far = world_far.xyz / world_far.w;
let ray_origin = uniforms.camera_position.xyz;
let ray_dir = normalize(world_pos_far - ray_origin);
let max_distance = 1000.0;
let hit = trace_sdf(ray_origin, ray_dir, max_distance);
var output: FragmentOutput;
if hit.hit {
let clip_pos = uniforms.view_projection * vec4<f32>(hit.position, 1.0);
let ndc_depth = clip_pos.z / clip_pos.w;
let sun_dir = normalize(uniforms.sun_direction.xyz);
let n_dot_l = max(dot(hit.normal, sun_dir), 0.0);
let ambient = uniforms.ambient_color.rgb * 0.3;
let diffuse = uniforms.sun_color.rgb * n_dot_l;
let view_dir = normalize(ray_origin - hit.position);
let half_dir = normalize(sun_dir + view_dir);
let spec = pow(max(dot(hit.normal, half_dir), 0.0), 32.0);
let specular = uniforms.sun_color.rgb * spec * 0.5;
var base_color: vec3<f32>;
if uniforms.debug_brick_coloring != 0u && hit.brick_pointer >= 0 {
base_color = brick_pointer_to_color(hit.brick_pointer);
} else {
let material = materials[hit.material_id];
base_color = material.base_color.rgb;
}
let final_color = base_color * (ambient + diffuse) + specular;
output.color = vec4<f32>(final_color, 1.0);
output.depth = ndc_depth;
} else {
discard;
}
return output;
}