WIP: sh probe grid
This commit is contained in:
parent
4099fd4af1
commit
3bcece4bc3
BIN
libtacoma.so
BIN
libtacoma.so
Binary file not shown.
Binary file not shown.
@ -10,8 +10,9 @@ Sky_Config :: struct {
|
||||
sunHalo : Vector3;
|
||||
sunLightColor : Vector3 = .{1.0, 1.0, 1.0};
|
||||
sunPosition : Vector3 = #run normalize(Vector3.{0.5, 0.5, 0.5});
|
||||
sunIntensity : float = 1.0;
|
||||
skyIntensity : float = 10.0;
|
||||
sunIntensity : float = 1.0;
|
||||
skyIntensity : float = 10.0;
|
||||
skyDesaturation : float = 0.0;
|
||||
}
|
||||
|
||||
Trixel_Data :: struct {
|
||||
@ -49,6 +50,7 @@ tacoma_destroy :: (ctx: *Tacoma_Context) #foreign libtacoma;
|
||||
tacoma_load_scene :: (ctx: *Tacoma_Context, sky: Sky_Config, ts: Trile_Set, world: World, include_water: s32) #foreign libtacoma;
|
||||
tacoma_render_rdm :: (ctx: *Tacoma_Context, world_trile_index: s32, roughness: s32, quality: s32, size: s32) -> *float #foreign libtacoma;
|
||||
tacoma_render_reference :: (ctx: *Tacoma_Context, width: s32, height: s32, eye: Vector3, target: Vector3, roughness: float, quality: s32) -> *float #foreign libtacoma;
|
||||
tacoma_render_sh_chunk :: (ctx: *Tacoma_Context, origin_x: float, origin_y: float, origin_z: float, probe_n: s32, probe_spacing: float, quality: s32) -> *float #foreign libtacoma;
|
||||
|
||||
tacoma_free_result :: (data: *float) #foreign libtacoma;
|
||||
|
||||
|
||||
Binary file not shown.
BIN
modules/Tacoma/shaders/sh_bake.comp.glsl.spv
Normal file
BIN
modules/Tacoma/shaders/sh_bake.comp.glsl.spv
Normal file
Binary file not shown.
@ -27,6 +27,7 @@ Fetch_Type :: enum {
|
||||
WORLD_CHUNKS;
|
||||
RDM_ATLAS;
|
||||
RDM_LOOKUP;
|
||||
SHGRID;
|
||||
}
|
||||
|
||||
Fetch_Request :: struct {
|
||||
@ -63,6 +64,7 @@ g_asset_manager : Asset_Manager;
|
||||
#scope_file
|
||||
|
||||
#load "rdm_loader.jai";
|
||||
#load "sh_loader.jai";
|
||||
|
||||
fetch_callback :: (res: *sfetch_response_t) #c_call {
|
||||
push_context,defer_pop default_context;
|
||||
@ -139,6 +141,9 @@ handle_fetch_failed :: (req: *Fetch_Request) {
|
||||
if req.rdm_pending_atlas.id != 0 then sg_destroy_image(req.rdm_pending_atlas);
|
||||
log_error("RDM: failed to load lookup for chunk %", req.chunk_key);
|
||||
|
||||
case .SHGRID;
|
||||
sh_loader_handle_failed(req);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,6 +183,7 @@ process_completed_fetch :: (req: *Fetch_Request, data: []u8) {
|
||||
if ok {
|
||||
set_loaded_world(world);
|
||||
rdm_loader_enqueue_world(*get_current_world().world);
|
||||
shgrid_loader_enqueue_world(*get_current_world().world);
|
||||
log_info("Loaded world (legacy): %", req.world_name);
|
||||
} else {
|
||||
log_error("Failed to parse world '%'", req.world_name);
|
||||
@ -196,6 +202,7 @@ process_completed_fetch :: (req: *Fetch_Request, data: []u8) {
|
||||
if ok {
|
||||
set_loaded_world(world);
|
||||
rdm_loader_enqueue_world(*get_current_world().world);
|
||||
shgrid_loader_enqueue_world(*get_current_world().world);
|
||||
log_info("Loaded world: %", req.world_name);
|
||||
} else {
|
||||
log_error("Failed to parse world '%'", req.world_name);
|
||||
@ -290,6 +297,9 @@ process_completed_fetch :: (req: *Fetch_Request, data: []u8) {
|
||||
sg_destroy_image(req.rdm_pending_atlas);
|
||||
}
|
||||
|
||||
case .SHGRID;
|
||||
sh_loader_handle_completed(req, data);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
100
src/assets/sh_loader.jai
Normal file
100
src/assets/sh_loader.jai
Normal file
@ -0,0 +1,100 @@
|
||||
// SH probe grid streaming helpers.
|
||||
// The unified fetch queue lives in asset_manager.jai; these functions
|
||||
// add entries to that queue and handle completed/failed fetches.
|
||||
|
||||
#scope_export
|
||||
|
||||
shgrid_loader_enqueue_world :: (world: *World) {
|
||||
for *chunk: world.chunks {
|
||||
if chunk.sh_valid then continue;
|
||||
|
||||
af := *g_asset_manager.active[CHANNEL_RDM];
|
||||
if af.occupied && af.req.type == .SHGRID
|
||||
&& af.req.world_name == world.name && af.req.chunk_key == chunk.coord
|
||||
then continue;
|
||||
|
||||
already_queued := false;
|
||||
for g_asset_manager.rdm_queue {
|
||||
if it.type == .SHGRID && it.world_name == world.name && it.chunk_key == chunk.coord {
|
||||
already_queued = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if already_queued then continue;
|
||||
|
||||
req : Fetch_Request;
|
||||
req.type = .SHGRID;
|
||||
req.world_name = world.name;
|
||||
req.chunk_key = chunk.coord;
|
||||
req.path = shgrid_filename(world.name, chunk.coord);
|
||||
array_add(*g_asset_manager.rdm_queue, req);
|
||||
}
|
||||
}
|
||||
|
||||
sh_loader_handle_failed :: (req: *Fetch_Request) {
|
||||
log_error("SH: failed to load probe grid for chunk %", req.chunk_key);
|
||||
}
|
||||
|
||||
sh_loader_handle_completed :: (req: *Fetch_Request, data: []u8) {
|
||||
curworld := get_current_world();
|
||||
if !curworld.valid || curworld.world.name != req.world_name then return;
|
||||
|
||||
header_size := cast(s64) size_of(SH_Grid_File_Header);
|
||||
if data.count < header_size {
|
||||
log_error("SH: probe grid too small for chunk %", req.chunk_key);
|
||||
return;
|
||||
}
|
||||
sh_header := cast(*SH_Grid_File_Header) data.data;
|
||||
if sh_header.magic != SH_FILE_MAGIC {
|
||||
log_error("SH: bad magic for chunk %", req.chunk_key);
|
||||
return;
|
||||
}
|
||||
n := sh_header.probe_n;
|
||||
expected_floats := cast(s64) n * n * n * 12;
|
||||
if data.count < header_size + expected_floats * size_of(float) {
|
||||
log_error("SH: probe grid data too short for chunk %", req.chunk_key);
|
||||
return;
|
||||
}
|
||||
|
||||
// Pack flat float[n^3 * 12] → 3-RGBA16F-texels-per-probe 2D texture layout.
|
||||
// Texture dims: (n*3) x (n*n). Probe (px,py,pz) at row (pz*n+py), col (px*3+k).
|
||||
// Coefficient layout in 3 texels (12 slots):
|
||||
// t0: R.c0-3 t1: G.c0-3 t2: B.c0-3
|
||||
tex_w := n * 3;
|
||||
n_tex := cast(s64) tex_w * n * n;
|
||||
packed := NewArray(n_tex * 4, u16);
|
||||
src := cast(*float) (data.data + header_size);
|
||||
for pz: 0..n-1 {
|
||||
for py: 0..n-1 {
|
||||
for px: 0..n-1 {
|
||||
probe_idx := px + py * n + pz * n * n;
|
||||
s := src + probe_idx * 12;
|
||||
for k: 0..2 {
|
||||
tex_idx := (pz * n + py) * tex_w + (px * 3 + k);
|
||||
d := packed.data + tex_idx * 4;
|
||||
for ch: 0..3 {
|
||||
d[ch] = f32_to_f16((s + k * 4 + ch).*);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sh_imgdata : sg_image_data;
|
||||
sh_imgdata.subimage[0][0] = .{ packed.data, cast(u64)(n_tex * 4 * size_of(u16)) };
|
||||
sh_desc : sg_image_desc = .{
|
||||
width = tex_w,
|
||||
height = n * n,
|
||||
pixel_format = sg_pixel_format.RGBA16F,
|
||||
sample_count = 1,
|
||||
data = sh_imgdata,
|
||||
};
|
||||
chunk := table_find_pointer(*curworld.world.chunks, req.chunk_key);
|
||||
if chunk != null {
|
||||
if chunk.sh_probe_grid.id != 0 then sg_destroy_image(chunk.sh_probe_grid);
|
||||
chunk.sh_probe_grid = sg_make_image(*sh_desc);
|
||||
chunk.sh_valid = true;
|
||||
log_debug("SH: loaded probe grid for chunk %", req.chunk_key);
|
||||
}
|
||||
free(packed.data);
|
||||
}
|
||||
@ -80,8 +80,6 @@ draw_editor :: () {
|
||||
#if OS != .WASM {
|
||||
if !in_editor_view then return;
|
||||
|
||||
bypass_postprocess = (current_editor_view == .Trile_Editor);
|
||||
|
||||
if current_editor_view == {
|
||||
case .Trile_Editor;
|
||||
draw_trile_editor();
|
||||
@ -97,7 +95,6 @@ tick_editor_ui :: () {
|
||||
if is_action_start(Editor_Action.TOGGLE_EDITOR) {
|
||||
in_editor_view = !in_editor_view;
|
||||
g_mixer.paused = in_editor_view;
|
||||
if !in_editor_view then bypass_postprocess = false;
|
||||
clear_particles();
|
||||
}
|
||||
|
||||
|
||||
@ -264,6 +264,10 @@ draw_tacoma_tab :: (theme: *GR.Overall_Theme, total_r: GR.Rect) {
|
||||
}
|
||||
}
|
||||
r.y += r.h;
|
||||
if GR.button(r, "Bake all chunk SH grids", *t_button_selectable(theme, !sh_bake.active)) {
|
||||
if curworld.valid && !sh_bake.active sh_bake_start(tacomaSamples);
|
||||
}
|
||||
r.y += r.h;
|
||||
if rdm_bake.active {
|
||||
total := cast(s32) rdm_bake.jobs.count;
|
||||
done := rdm_bake.current_job;
|
||||
@ -271,6 +275,13 @@ draw_tacoma_tab :: (theme: *GR.Overall_Theme, total_r: GR.Rect) {
|
||||
GR.label(r, tprint("Baking RDMs: %/% (\%%)", done, total, pct), *t_label_left(theme));
|
||||
r.y += r.h;
|
||||
}
|
||||
if sh_bake.active {
|
||||
total := cast(s32) sh_bake.chunk_keys.count;
|
||||
done := sh_bake.current_chunk;
|
||||
pct := ifx total > 0 then done * 100 / total else 0;
|
||||
GR.label(r, tprint("Baking SH grids: %/% (\%%)", done, total, pct), *t_label_left(theme));
|
||||
r.y += r.h;
|
||||
}
|
||||
if current_screenshot.valid {
|
||||
aspect := cast(float)current_screenshot.width / cast(float)current_screenshot.height;
|
||||
|
||||
@ -304,6 +315,14 @@ draw_tacoma_tab :: (theme: *GR.Overall_Theme, total_r: GR.Rect) {
|
||||
r.y += r.h;
|
||||
GR.slider(r, *tacomaSaturation, 0.5, 3.0, 0.1, *theme.slider_theme);
|
||||
r.y += r.h;
|
||||
GR.label(r, tprint("Sky Scale (bake): %", formatFloat(bake_sky_scale, trailing_width=2)), *t_label_left(theme));
|
||||
r.y += r.h;
|
||||
GR.slider(r, *bake_sky_scale, 0.0, 5.0, 0.05, *theme.slider_theme);
|
||||
r.y += r.h;
|
||||
GR.label(r, tprint("Sky Desaturation (bake): %", formatFloat(bake_sky_desaturation, trailing_width=2)), *t_label_left(theme));
|
||||
r.y += r.h;
|
||||
GR.slider(r, *bake_sky_desaturation, 0.0, 1.0, 0.05, *theme.slider_theme);
|
||||
r.y += r.h;
|
||||
|
||||
|
||||
} else {
|
||||
@ -517,7 +536,7 @@ editor_edit_y :: () -> float {
|
||||
}
|
||||
|
||||
tick_level_editor :: () {
|
||||
#if FLAG_TACOMA_ENABLED { rdm_bake_tick(); }
|
||||
#if FLAG_TACOMA_ENABLED { rdm_bake_tick(); sh_bake_tick(); }
|
||||
tick_level_editor_camera();
|
||||
tick_particles(cast(float)delta_time);
|
||||
|
||||
|
||||
@ -12,3 +12,33 @@ RDM_FILE_MAGIC :: u32.[0x4D445254][0]; // "TRDM" as little-endian u32
|
||||
rdm_chunk_filename :: (world_name: string, chunk_key: Chunk_Key, suffix: string) -> string {
|
||||
return sprint("%/worlds/%/%_%_%.%", GAME_RESOURCES_DIR, world_name, chunk_key.x, chunk_key.y, chunk_key.z, suffix);
|
||||
}
|
||||
|
||||
// SH probe grid (2 probes per trile per axis = 64x64x64 per 32x32x32 chunk)
|
||||
|
||||
SH_FILE_MAGIC :: u32.[0x44475348][0]; // "SHGD" as little-endian u32
|
||||
SH_PROBE_N :: 64; // probes per axis (2 per trile in a 32-trile chunk)
|
||||
SH_TEX_W :: SH_PROBE_N * 3; // 192 — 3 RGBA texels per probe along X
|
||||
|
||||
SH_Grid_File_Header :: struct {
|
||||
magic: u32;
|
||||
version: u16;
|
||||
probe_n: s32;
|
||||
}
|
||||
|
||||
shgrid_filename :: (world_name: string, chunk_key: Chunk_Key) -> string {
|
||||
return sprint("%/worlds/%/%_%_%.shgrid", GAME_RESOURCES_DIR, world_name, chunk_key.x, chunk_key.y, chunk_key.z);
|
||||
}
|
||||
|
||||
f32_to_f16 :: (f: float) -> u16 {
|
||||
b := (cast(*u32) *f).*;
|
||||
sign := (b >> 31) & 0x1;
|
||||
exp := cast(s32)((b >> 23) & 0xFF) - 127;
|
||||
mant := b & 0x7FFFFF;
|
||||
if exp >= 16 return cast(u16)((sign << 15) | 0x7C00); // clamp to inf
|
||||
if exp >= -14 return cast(u16)((sign << 15) | (cast(u32)(exp + 15) << 10) | (mant >> 13));
|
||||
if exp >= -24 {
|
||||
shift := cast(u32)(-exp - 1);
|
||||
return cast(u16)((sign << 15) | ((mant | 0x800000) >> shift));
|
||||
}
|
||||
return cast(u16)(sign << 15); // underflow → ±0
|
||||
}
|
||||
|
||||
35
src/editor/rdm_disk_test.jai
Normal file
35
src/editor/rdm_disk_test.jai
Normal file
@ -0,0 +1,35 @@
|
||||
test_f32_to_f16 :: () {
|
||||
s := begin_suite("f32_to_f16");
|
||||
|
||||
check(*s, "0.0 → 0x0000", f32_to_f16(0.0) == 0x0000);
|
||||
check(*s, "-0.0 → 0x8000", f32_to_f16(-0.0) == 0x8000);
|
||||
check(*s, "1.0 → 0x3C00", f32_to_f16(1.0) == 0x3C00);
|
||||
check(*s, "-1.0 → 0xBC00", f32_to_f16(-1.0) == 0xBC00);
|
||||
check(*s, "0.5 → 0x3800", f32_to_f16(0.5) == 0x3800);
|
||||
check(*s, "2.0 → 0x4000", f32_to_f16(2.0) == 0x4000);
|
||||
check(*s, "1.5 → 0x3E00", f32_to_f16(1.5) == 0x3E00);
|
||||
|
||||
// Max normal f16 = 65504
|
||||
check(*s, "65504.0 → 0x7BFF", f32_to_f16(65504.0) == 0x7BFF);
|
||||
|
||||
// Min normal f16 = 2^-14
|
||||
check(*s, "2^-14 → 0x0400", f32_to_f16(cast(float)(1.0 / 16384.0)) == 0x0400);
|
||||
|
||||
// Overflow clamps to infinity
|
||||
check(*s, "65536.0 → 0x7C00 (inf)", f32_to_f16(65536.0) == 0x7C00);
|
||||
check(*s, "1e10 → 0x7C00 (inf)", f32_to_f16(1.0e10) == 0x7C00);
|
||||
check(*s, "-65536.0 → 0xFC00 (-inf)", f32_to_f16(-65536.0) == 0xFC00);
|
||||
|
||||
// Denormals: 2^-15 is the largest f16 denormal base
|
||||
check(*s, "2^-15 → 0x0200", f32_to_f16(cast(float)(1.0 / 32768.0)) == 0x0200);
|
||||
// Smallest representable f16 denormal = 2^-24
|
||||
check(*s, "2^-24 → 0x0001", f32_to_f16(cast(float)(1.0 / 16777216.0)) == 0x0001);
|
||||
|
||||
// Underflow to zero
|
||||
check(*s, "2^-25 → 0x0000", f32_to_f16(cast(float)(1.0 / 33554432.0)) == 0x0000);
|
||||
check(*s, "-2^-25 → 0x8000", f32_to_f16(cast(float)(-1.0 / 33554432.0)) == 0x8000);
|
||||
|
||||
end_suite(s);
|
||||
}
|
||||
|
||||
#run test_f32_to_f16();
|
||||
@ -63,6 +63,16 @@ RDM_Bake_State :: struct {
|
||||
|
||||
rdm_bake : RDM_Bake_State;
|
||||
|
||||
SH_Bake_State :: struct {
|
||||
active : bool;
|
||||
quality : s32;
|
||||
include_water : bool;
|
||||
chunk_keys : [..]Chunk_Key;
|
||||
current_chunk : s32;
|
||||
}
|
||||
|
||||
sh_bake : SH_Bake_State;
|
||||
|
||||
rdm_job_size :: (job: RDM_Bake_Job) -> s32 {
|
||||
if job.size_override > 0 then return job.size_override;
|
||||
return g_rdm_default_sizes[job.roughness];
|
||||
@ -221,20 +231,9 @@ rdm_bake_start :: (world: World, quality: s32, include_water: bool, chunk_keys:
|
||||
}
|
||||
}
|
||||
|
||||
sky : Tacoma.Sky_Config;
|
||||
sky.skyBase = world.conf.skyBase;
|
||||
sky.skyTop = world.conf.skyTop;
|
||||
sky.sunDisk = world.conf.sunDisk;
|
||||
sky.horizonHalo = world.conf.horizonHalo;
|
||||
sky.sunHalo = world.conf.sunHalo;
|
||||
sky.sunLightColor = world.conf.sunLightColor;
|
||||
sky.sunPosition = world.conf.sunPosition;
|
||||
sky.sunIntensity = world.conf.sunIntensity;
|
||||
sky.skyIntensity = world.conf.skyIntensity;
|
||||
|
||||
blases : Tacoma.Trile_Set = .{trile_list.data, cast(s32)trile_list.count};
|
||||
tlas : Tacoma.World = .{world_triles.data, cast(s32)world_triles.count};
|
||||
|
||||
sky : Tacoma.Sky_Config = world_to_sky_config(world);
|
||||
blases : Tacoma.Trile_Set = .{trile_list.data, cast(s32) trile_list.count};
|
||||
tlas : Tacoma.World = .{world_triles.data, cast(s32) world_triles.count};
|
||||
ctx = Tacoma.tacoma_init("./modules/Tacoma/");
|
||||
Tacoma.tacoma_load_scene(ctx, sky, blases, tlas, cast(s32) include_water);
|
||||
|
||||
@ -493,72 +492,64 @@ rdm_cleanup_chunk_bakes :: () {
|
||||
rdm_chunk_bakes = .{};
|
||||
}
|
||||
|
||||
tacoma_start :: (world: World, include_water: bool) {
|
||||
// Trile BLASes.
|
||||
trile_list : [..]Tacoma.Trile_Data;
|
||||
trile_list.allocator = temp;
|
||||
// BLAS instances to create TLAS.
|
||||
world_triles : [..]Tacoma.World_Trile;
|
||||
world_triles.allocator = temp;
|
||||
bake_sky_scale : float = 1.0;
|
||||
bake_sky_desaturation : float = 0.0;
|
||||
|
||||
// Build trile type list and gather world positions from chunks.
|
||||
trile_name_to_index: Table(string, s32);
|
||||
trile_name_to_index.allocator = temp;
|
||||
world_to_sky_config :: (world: World) -> Tacoma.Sky_Config {
|
||||
sky : Tacoma.Sky_Config;
|
||||
sky.skyBase = world.conf.skyBase;
|
||||
sky.skyTop = world.conf.skyTop;
|
||||
sky.sunDisk = world.conf.sunDisk;
|
||||
sky.horizonHalo = world.conf.horizonHalo;
|
||||
sky.sunHalo = world.conf.sunHalo;
|
||||
sky.sunLightColor = world.conf.sunLightColor;
|
||||
sky.sunPosition = world.conf.sunPosition;
|
||||
sky.sunIntensity = world.conf.sunIntensity;
|
||||
sky.skyIntensity = world.conf.skyIntensity * bake_sky_scale;
|
||||
sky.skyDesaturation = bake_sky_desaturation;
|
||||
return sky;
|
||||
}
|
||||
|
||||
// Build trile BLASes + world TLAS from a world, then initialize ctx.
|
||||
tacoma_init_scene :: (world: World, include_water: bool) {
|
||||
trile_list : [..]Tacoma.Trile_Data; trile_list.allocator = temp;
|
||||
world_triles : [..]Tacoma.World_Trile; world_triles.allocator = temp;
|
||||
trile_name_to_index : Table(string, s32); trile_name_to_index.allocator = temp;
|
||||
|
||||
for chunk: world.chunks {
|
||||
for group: chunk.groups {
|
||||
// Ensure this trile type is in the list.
|
||||
success, idx := table_find(*trile_name_to_index, group.trile_name);
|
||||
if !success {
|
||||
trile := get_trile(group.trile_name);
|
||||
trile, trile_ok := get_trile(group.trile_name);
|
||||
if !trile_ok continue;
|
||||
ttrile : Tacoma.Trile_Data;
|
||||
for x: 0..15 {
|
||||
for y: 0..15 {
|
||||
for z: 0..15 {
|
||||
ttrile.trixels[x][y][z] = .{
|
||||
trile.trixels[x][y][z].empty,
|
||||
trile.trixels[x][y][z].material.color,
|
||||
material_encode_to_float(trile.trixels[x][y][z].material)
|
||||
};
|
||||
}
|
||||
}
|
||||
for x: 0..15 for y: 0..15 for z: 0..15 {
|
||||
ttrile.trixels[x][y][z] = .{
|
||||
trile.trixels[x][y][z].empty,
|
||||
trile.trixels[x][y][z].material.color,
|
||||
material_encode_to_float(trile.trixels[x][y][z].material)
|
||||
};
|
||||
}
|
||||
gfx := get_trile_gfx(group.trile_name);
|
||||
ttrile.vertices = gfx.vertices.data;
|
||||
ttrile.vertexCount = cast(s32) (gfx.vertices.count / 3);
|
||||
ttrile.vertices = gfx.vertices.data;
|
||||
ttrile.vertexCount = cast(s32)(gfx.vertices.count / 3);
|
||||
idx = cast(s32) trile_list.count;
|
||||
array_add(*trile_list, ttrile);
|
||||
table_set(*trile_name_to_index, group.trile_name, idx);
|
||||
}
|
||||
|
||||
for inst: group.instances {
|
||||
wx, wy, wz := chunk_local_to_world(chunk.coord, inst.x, inst.y, inst.z);
|
||||
array_add(*world_triles, Tacoma.World_Trile.{idx, Vector3.{cast(float) wx, cast(float) wy, cast(float) wz}, cast(s32) inst.orientation});
|
||||
array_add(*world_triles, Tacoma.World_Trile.{idx,
|
||||
Vector3.{cast(float) wx, cast(float) wy, cast(float) wz},
|
||||
cast(s32) inst.orientation});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sky : Tacoma.Sky_Config;
|
||||
|
||||
sky.skyBase = world.conf.skyBase;
|
||||
sky.skyTop = world.conf.skyTop;
|
||||
sky.sunDisk = world.conf.sunDisk;
|
||||
sky.horizonHalo = world.conf.horizonHalo;
|
||||
sky.sunHalo = world.conf.sunHalo;
|
||||
sky.sunLightColor = world.conf.sunLightColor;
|
||||
sky.sunPosition = world.conf.sunPosition;
|
||||
sky.sunIntensity = world.conf.sunIntensity;
|
||||
sky.skyIntensity = world.conf.skyIntensity;
|
||||
|
||||
blases : Tacoma.Trile_Set = .{trile_list.data, cast(s32)trile_list.count};
|
||||
for world_triles {
|
||||
log_debug("World trile %", it);
|
||||
}
|
||||
tlas : Tacoma.World = .{world_triles.data, cast(s32)world_triles.count};
|
||||
|
||||
sky : Tacoma.Sky_Config = world_to_sky_config(world);
|
||||
blases : Tacoma.Trile_Set = .{trile_list.data, cast(s32) trile_list.count};
|
||||
tlas : Tacoma.World = .{world_triles.data, cast(s32) world_triles.count};
|
||||
ctx = Tacoma.tacoma_init("./modules/Tacoma/");
|
||||
log_debug("CTX: %", ctx);
|
||||
|
||||
Tacoma.tacoma_load_scene(ctx, sky, blases, tlas, cast(s32) include_water);
|
||||
}
|
||||
|
||||
@ -607,20 +598,126 @@ tacoma_handle_result :: (ptr: *float, w: s32, h: s32) {
|
||||
}
|
||||
|
||||
gen_reference :: (w: s32, h: s32, eye: Vector3, target: Vector3, quality: s32, include_water: bool, world: World) {
|
||||
tacoma_start(world, include_water);
|
||||
tacoma_init_scene(world, include_water);
|
||||
ptr := Tacoma.tacoma_render_reference(ctx, w, h, eye, target, 0.01, quality);
|
||||
tacoma_handle_result(ptr, w, h);
|
||||
tacoma_stop();
|
||||
}
|
||||
|
||||
gen_rdm :: (quality: s32, include_water: bool, world: World) {
|
||||
tacoma_start(world, include_water);
|
||||
tacoma_init_scene(world, include_water);
|
||||
size := g_rdm_default_sizes[0];
|
||||
ptr := Tacoma.tacoma_render_rdm(ctx, 0, 0, quality, size);
|
||||
tacoma_handle_result(ptr, 2 * size, 3 * size);
|
||||
tacoma_stop();
|
||||
}
|
||||
|
||||
sh_bake_start :: (quality: s32 = 50, include_water: bool = false) {
|
||||
if sh_bake.active then return;
|
||||
curworld := get_current_world();
|
||||
if !curworld.valid { log_warn("sh_bake_start: no world loaded"); return; }
|
||||
|
||||
world := *curworld.world;
|
||||
for _, key: world.chunks array_add(*sh_bake.chunk_keys, key);
|
||||
|
||||
if sh_bake.chunk_keys.count == 0 then return;
|
||||
|
||||
tacoma_init_scene(world.*, include_water);
|
||||
sh_bake.active = true;
|
||||
sh_bake.quality = quality;
|
||||
sh_bake.include_water = include_water;
|
||||
sh_bake.current_chunk = 0;
|
||||
} @Command
|
||||
|
||||
sh_bake_tick :: () {
|
||||
if !sh_bake.active then return;
|
||||
if sh_bake.current_chunk >= cast(s32) sh_bake.chunk_keys.count {
|
||||
sh_bake_finish();
|
||||
return;
|
||||
}
|
||||
|
||||
curworld := get_current_world();
|
||||
if !curworld.valid { sh_bake_finish(); return; }
|
||||
|
||||
n :: SH_PROBE_N;
|
||||
tex_w :: SH_TEX_W;
|
||||
|
||||
chunk_key := sh_bake.chunk_keys[sh_bake.current_chunk];
|
||||
chunk := table_find_pointer(*curworld.world.chunks, chunk_key);
|
||||
sh_bake.current_chunk += 1;
|
||||
if chunk == null then return;
|
||||
|
||||
ox, oy, oz := chunk_local_to_world(chunk_key, 0, 0, 0);
|
||||
ptr := Tacoma.tacoma_render_sh_chunk(ctx,
|
||||
cast(float) ox, cast(float) oy, cast(float) oz,
|
||||
n, 0.5, sh_bake.quality);
|
||||
|
||||
tex_halfs :: tex_w * n * n * 4;
|
||||
packed : *u16 = cast(*u16) alloc(tex_halfs * size_of(u16));
|
||||
defer free(packed);
|
||||
|
||||
src := ptr;
|
||||
for pz: 0..n-1 for py: 0..n-1 for px: 0..n-1 {
|
||||
probe_idx := px + py * n + pz * n * n;
|
||||
s := src + probe_idx * 12;
|
||||
for k: 0..2 {
|
||||
tex_idx := (pz * n + py) * tex_w + (px * 3 + k);
|
||||
d := packed + tex_idx * 4;
|
||||
for ch: 0..3 {
|
||||
(d + ch).* = f32_to_f16((s + k * 4 + ch).*);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tex_byte_size := cast(u64)(tex_halfs * size_of(u16));
|
||||
imgdata : sg_image_data;
|
||||
imgdata.subimage[0][0] = .{packed, tex_byte_size};
|
||||
sh_desc : sg_image_desc = .{
|
||||
width = tex_w,
|
||||
height = n * n,
|
||||
pixel_format = sg_pixel_format.RGBA16F,
|
||||
sample_count = 1,
|
||||
data = imgdata,
|
||||
};
|
||||
|
||||
if chunk.sh_valid sg_destroy_image(chunk.sh_probe_grid);
|
||||
chunk.sh_probe_grid = sg_make_image(*sh_desc);
|
||||
chunk.sh_valid = true;
|
||||
chunk.sh_dirty = false;
|
||||
|
||||
#if OS != .WASM {
|
||||
shgrid_save_to_disk(curworld.world.name, chunk_key, ptr, n);
|
||||
}
|
||||
|
||||
Tacoma.tacoma_free_result(ptr);
|
||||
log_info("SH baked chunk % (%/%)", chunk_key, sh_bake.current_chunk, sh_bake.chunk_keys.count);
|
||||
}
|
||||
|
||||
sh_bake_finish :: () {
|
||||
tacoma_stop();
|
||||
array_free(sh_bake.chunk_keys);
|
||||
sh_bake = .{};
|
||||
log_info("SH bake complete.");
|
||||
}
|
||||
|
||||
shgrid_save_to_disk :: (world_name: string, chunk_key: Chunk_Key, data: *float, probe_n: s32) {
|
||||
#if OS != .WASM {
|
||||
file :: #import "File";
|
||||
path := shgrid_filename(world_name, chunk_key);
|
||||
|
||||
builder : String_Builder;
|
||||
header := SH_Grid_File_Header.{
|
||||
magic = SH_FILE_MAGIC,
|
||||
version = 1,
|
||||
probe_n = probe_n,
|
||||
};
|
||||
write_bytes(*builder, *header, size_of(SH_Grid_File_Header));
|
||||
total_floats := cast(s64) probe_n * probe_n * probe_n * 12;
|
||||
write_bytes(*builder, data, total_floats * size_of(float));
|
||||
file.write_entire_file(path, builder_to_string(*builder));
|
||||
}
|
||||
}
|
||||
|
||||
// --- RDM disk persistence ---
|
||||
// (RDM_File_Header, RDM_FILE_MAGIC, rdm_chunk_filename, rdm_load_from_disk
|
||||
// are defined in rdm_disk.jai which is always loaded on non-WASM builds.)
|
||||
|
||||
@ -221,15 +221,20 @@ backend_draw_trile_positions_main :: (trile : string, amount : s32, worldConf: *
|
||||
// The shader gates all RDM sampling on atlas_rect.z > 0, which the
|
||||
// fallback texture returns as 0, so the ambient fallback path is taken.
|
||||
curworld := get_current_world();
|
||||
rdm_chunk := table_find_pointer(*curworld.world.chunks, chunk_key);
|
||||
if rdm_chunk != null && rdm_chunk.rdm_valid {
|
||||
bindings.images[3] = rdm_chunk.rdm_lookup;
|
||||
bindings.images[4] = rdm_chunk.rdm_atlas;
|
||||
chunk := table_find_pointer(*curworld.world.chunks, chunk_key);
|
||||
if chunk != null && chunk.rdm_valid {
|
||||
bindings.images[3] = chunk.rdm_lookup;
|
||||
bindings.images[4] = chunk.rdm_atlas;
|
||||
} else {
|
||||
bindings.images[3] = g_rdm_fallback;
|
||||
bindings.images[4] = g_rdm_fallback;
|
||||
}
|
||||
bindings.images[5] = g_brdf_lut;
|
||||
if chunk != null && chunk.sh_valid {
|
||||
bindings.images[6] = chunk.sh_probe_grid;
|
||||
} else {
|
||||
bindings.images[6] = g_sh_fallback;
|
||||
}
|
||||
bindings.samplers[3] = gPipelines.trile.bind.samplers[3];
|
||||
|
||||
fs_params : Trile_Fs_Params;
|
||||
@ -248,6 +253,7 @@ backend_draw_trile_positions_main :: (trile : string, amount : s32, worldConf: *
|
||||
fs_params.is_preview = preview_mode;
|
||||
fs_params.rdm_tint = lc.rdm_tint.component;
|
||||
fs_params.rdm_diff_saturation = worldConf.rdmDiffSaturation;
|
||||
fs_params.sh_enabled = ifx (chunk != null && chunk.sh_valid && !in_reflection_pass) then cast(s32)1 else cast(s32)0;
|
||||
|
||||
sg_apply_bindings(*bindings);
|
||||
sg_apply_uniforms(UB_trile_fs_params, *(sg_range.{ ptr = *fs_params, size = size_of(type_of(fs_params)) }));
|
||||
|
||||
@ -10,6 +10,7 @@ Pipeline_Binding :: struct {
|
||||
g_specular_lut : sg_image;
|
||||
g_brdf_lut : sg_image;
|
||||
g_rdm_fallback : sg_image; // 1x1 black image used when a chunk has no baked RDM data
|
||||
g_sh_fallback : sg_image; // 1x1 black 2D image used when a chunk has no SH probe grid
|
||||
|
||||
g_shadowmap : sg_image;
|
||||
g_shadowmap_img : sg_image;
|
||||
@ -1286,6 +1287,18 @@ init_brdf_lut :: () {
|
||||
data = imgdata,
|
||||
};
|
||||
g_rdm_fallback = sg_make_image(*desc);
|
||||
|
||||
// 1x1 RGBA16F 2D texture — fallback when a chunk has no SH probe grid.
|
||||
zero_sh : [4]u16 = .[0, 0, 0, 0];
|
||||
sh_imgdata : sg_image_data;
|
||||
sh_imgdata.subimage[0][0] = .{ zero_sh.data, size_of(type_of(zero_sh)) };
|
||||
sh_desc := sg_image_desc.{
|
||||
width = 1,
|
||||
height = 1,
|
||||
pixel_format = .RGBA16F,
|
||||
data = sh_imgdata,
|
||||
};
|
||||
g_sh_fallback = sg_make_image(*sh_desc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -58,7 +58,7 @@ create_shadow_viewproj :: (cam: *Camera, conf: *World_Config) -> Matrix4 {
|
||||
B.z, C.z, A.z, 0,
|
||||
-dot(B, sunCameraPosition), -dot(C, sunCameraPosition), -dot(A, sunCameraPosition), 1
|
||||
};
|
||||
proj := matrix_ortho(-15, 15, -15, 15, 0, 100);
|
||||
proj := matrix_ortho(-60, 60, -60, 60, 0, 100);
|
||||
return view*proj;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -124,15 +124,16 @@ void main() {
|
||||
}
|
||||
}
|
||||
|
||||
float r = texture(sampler2D(pptex, ppsmp), distorted_texcoord + vec2(chromatic_aberration_intensity, 0.0)).r;
|
||||
float g = texture(sampler2D(pptex, ppsmp), distorted_texcoord).g;
|
||||
float b = texture(sampler2D(pptex, ppsmp), distorted_texcoord - vec2(chromatic_aberration_intensity, 0.0)).b;
|
||||
vec3 sampled_color_hdr;
|
||||
if(chromatic_aberration_intensity > 0.01) {
|
||||
float r = texture(sampler2D(pptex, ppsmp), distorted_texcoord + vec2(chromatic_aberration_intensity, 0.0)).r;
|
||||
float g = texture(sampler2D(pptex, ppsmp), distorted_texcoord).g;
|
||||
float b = texture(sampler2D(pptex, ppsmp), distorted_texcoord - vec2(chromatic_aberration_intensity, 0.0)).b;
|
||||
sampled_color_hdr = vec3(r,g,b + dof_min * 0.00000000000001);
|
||||
} else {
|
||||
sampled_color_hdr = texture(sampler2D(pptex, ppsmp), distorted_texcoord).rgb;
|
||||
}
|
||||
|
||||
vec3 out_focus = texture_bicubic(dof_tex, dof_smp, distorted_texcoord, vec2(dof_tex_width, dof_tex_height)).xyz;
|
||||
vec3 in_focus = vec3(r, g, b);
|
||||
vec4 position = texture(sampler2D(pos_buf, dof_smp), distorted_texcoord);
|
||||
float blur = smoothstep(dof_min, dof_max, abs(position.z + dof_point));
|
||||
vec3 sampled_color_hdr = mix(in_focus, out_focus, blur);
|
||||
|
||||
vec3 bloom_color = texture(sampler2D(bloom_tex, bloom_smp), distorted_texcoord).rgb;
|
||||
vec3 color_hdr = (sampled_color_hdr + bloom_color * bloom_amount) * exposure;
|
||||
|
||||
@ -104,6 +104,7 @@ layout(binding=3) uniform trile_fs_params {
|
||||
int is_preview;
|
||||
vec3 rdm_tint;
|
||||
float rdm_diff_saturation;
|
||||
int sh_enabled;
|
||||
};
|
||||
|
||||
layout(binding = 0) uniform texture2D triletex;
|
||||
@ -115,6 +116,7 @@ layout(binding = 2) uniform sampler shadowsmp;
|
||||
layout(binding = 3) uniform texture2D rdm_lookup;
|
||||
layout(binding = 4) uniform texture2D rdm_atlas;
|
||||
layout(binding = 5) uniform texture2D brdf_lut;
|
||||
layout(binding = 6) uniform texture2D sh_chunk;
|
||||
layout(binding = 3) uniform sampler rdmsmp;
|
||||
|
||||
const float PI = 3.1415927;
|
||||
@ -308,16 +310,81 @@ vec3 rdm_indirect_diffuse(vec3 N, vec3 diff, ivec3 local_pos) {
|
||||
s3 = ivec3(0, isign(delta.y), isign(delta.x));
|
||||
}
|
||||
|
||||
vec3 p0 = rdm_sample_diff_probe(N, ivec3(mod(vec3(local_pos), 32.0)), ambient);
|
||||
vec3 p1 = rdm_sample_diff_probe(N, ivec3(mod(vec3(local_pos + s1), 32.0)), ambient);
|
||||
vec3 p2 = rdm_sample_diff_probe(N, ivec3(mod(vec3(local_pos + s2), 32.0)), ambient);
|
||||
vec3 p3 = rdm_sample_diff_probe(N, ivec3(mod(vec3(local_pos + s1 + s2),32.0)), ambient);
|
||||
vec3 p0 = rdm_sample_diff_probe(N, clamp(local_pos, ivec3(0), ivec3(31)), ambient);
|
||||
vec3 p1 = rdm_sample_diff_probe(N, clamp(local_pos + s1, ivec3(0), ivec3(31)), ambient);
|
||||
vec3 p2 = rdm_sample_diff_probe(N, clamp(local_pos + s2, ivec3(0), ivec3(31)), ambient);
|
||||
vec3 p3 = rdm_sample_diff_probe(N, clamp(local_pos + s1 + s2,ivec3(0), ivec3(31)), ambient);
|
||||
|
||||
return smix(smix(p0, p1, abs(delta.x)),
|
||||
smix(p2, p3, abs(delta.x)),
|
||||
abs(delta.y));
|
||||
}
|
||||
|
||||
// ---- SH PROBE GRID ----
|
||||
// Each probe stores 27 L2 SH coefficients (9 per RGB channel), packed into
|
||||
// 3 RGBA16F texels per probe along the X axis of a 192x4096 2D texture.
|
||||
// Row = probe.z * 64 + probe.y, col = probe.x * 3 + k.
|
||||
// Texel layout per probe (px,py,pz):
|
||||
// t0: R.c0-3 t1: G.c0-3 t2: B.c0-3
|
||||
// Probe index from chunk-local world position p (0..32 range):
|
||||
// ivec3(floor(p * 2.0)) clamped to [0,63]
|
||||
// SH evaluation: Lambertian irradiance (L1 only), A0=PI, A1=2PI/3.
|
||||
|
||||
vec3 sh_eval(ivec3 probe, vec3 N) {
|
||||
int base = probe.x * 3;
|
||||
int row = probe.z * 64 + probe.y;
|
||||
vec4 t0 = texelFetch(sampler2D(sh_chunk, rdmsmp), ivec2(base, row), 0);
|
||||
vec4 t1 = texelFetch(sampler2D(sh_chunk, rdmsmp), ivec2(base+1, row), 0);
|
||||
vec4 t2 = texelFetch(sampler2D(sh_chunk, rdmsmp), ivec2(base+2, row), 0);
|
||||
|
||||
float x = N.x, y = N.y, z = N.z;
|
||||
float r = 0.886227*t0.x + 1.023327*(t0.w*x + t0.y*y + t0.z*z);
|
||||
float g = 0.886227*t1.x + 1.023327*(t1.w*x + t1.y*y + t1.z*z);
|
||||
float b = 0.886227*t2.x + 1.023327*(t2.w*x + t2.y*y + t2.z*z);
|
||||
|
||||
return max(vec3(r, g, b) / PI, vec3(0.0));
|
||||
}
|
||||
|
||||
// Sum of L0 irradiance across RGB — proxy for total incoming energy.
|
||||
// Near-zero means the probe is buried inside solid geometry.
|
||||
float sh_probe_energy(ivec3 probe) {
|
||||
int base = probe.x * 3;
|
||||
int row = probe.z * 64 + probe.y;
|
||||
vec4 t0 = texelFetch(sampler2D(sh_chunk, rdmsmp), ivec2(base, row), 0);
|
||||
vec4 t1 = texelFetch(sampler2D(sh_chunk, rdmsmp), ivec2(base+1, row), 0);
|
||||
vec4 t2 = texelFetch(sampler2D(sh_chunk, rdmsmp), ivec2(base+2, row), 0);
|
||||
return max(0.886227 * (t0.x + t1.x + t2.x), 0.0);
|
||||
}
|
||||
|
||||
// Trilinear SH evaluation with confidence weighting.
|
||||
// Probes with near-zero energy (buried in geometry) are downweighted
|
||||
// so they don't pull the result toward black.
|
||||
vec3 sh_eval_trilinear(ivec3 p0, ivec3 p1, vec3 t, vec3 N) {
|
||||
float wx[2] = float[2](1.0 - t.x, t.x);
|
||||
float wy[2] = float[2](1.0 - t.y, t.y);
|
||||
float wz[2] = float[2](1.0 - t.z, t.z);
|
||||
|
||||
vec3 result = vec3(0.0);
|
||||
float total_w = 0.0;
|
||||
|
||||
for (int iz = 0; iz < 2; iz++) {
|
||||
for (int iy = 0; iy < 2; iy++) {
|
||||
for (int ix = 0; ix < 2; ix++) {
|
||||
ivec3 probe = ivec3(
|
||||
ix == 0 ? p0.x : p1.x,
|
||||
iy == 0 ? p0.y : p1.y,
|
||||
iz == 0 ? p0.z : p1.z
|
||||
);
|
||||
float w = wx[ix] * wy[iy] * wz[iz] * sh_probe_energy(probe);
|
||||
result += sh_eval(probe, N) * w;
|
||||
total_w += w;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return total_w > 0.001 ? result / total_w : vec3(0.0);
|
||||
}
|
||||
|
||||
// ---- HSV ----
|
||||
|
||||
vec3 rgb2hsv(vec3 c) {
|
||||
@ -386,7 +453,7 @@ void main() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Evaluate direct light.
|
||||
// ---- 1. VIEW / LIGHT VECTORS ----
|
||||
vec3 V = normalize(cam - vpos);
|
||||
vec3 L = normalize(sunPosition);
|
||||
vec3 H = normalize(V + L);
|
||||
@ -394,82 +461,98 @@ void main() {
|
||||
float NdotV = max(dot(N, V), 0.0);
|
||||
float HdotV = max(dot(H, V), 0.0);
|
||||
|
||||
// ---- 2. PBR TERMS ----
|
||||
vec3 F0 = mix(vec3(0.04), albedo, metallic);
|
||||
vec3 F = fresnelSchlick(HdotV, F0);
|
||||
float NDF = DistributionGGX(N, H, roughness);
|
||||
float G = GeometrySmith(N, V, L, roughness);
|
||||
vec3 kD = (1.0 - F) * (1.0 - metallic);
|
||||
|
||||
vec3 specular = (NDF * G * F) / (4.0 * NdotV * NdotL + 0.0001);
|
||||
vec3 kD = (1.0 - F) * (1.0 - metallic);
|
||||
|
||||
// Shadow lookup.
|
||||
// ---- 3. DIRECT LIGHT (sun + shadow) ----
|
||||
vec4 light_proj = mvp_shadow * vec4(floor(vpos * 16.0) / 16.0, 1.0);
|
||||
vec3 light_ndc = light_proj.xyz / light_proj.w * 0.5 + 0.5;
|
||||
light_ndc.z -= 0.001;
|
||||
float shadow = texture(sampler2DShadow(shadowtex, shadowsmp), light_ndc);
|
||||
|
||||
vec3 light = shadow * (kD * albedo / PI + specular) * NdotL * sunLightColor * sunIntensity;
|
||||
vec3 direct_specular = (NDF * G * F) / (4.0 * NdotV * NdotL + 0.0001);
|
||||
vec3 light = shadow * (kD * albedo / PI + direct_specular) * NdotL * sunLightColor * sunIntensity;
|
||||
|
||||
// --- Indirect lighting ---
|
||||
ivec3 local = ivec3(mod(floor(trileCenter), 32.0));
|
||||
// ---- 4. INDIRECT LIGHT (RDM / SH / ambient fallback) ----
|
||||
ivec3 local = ivec3(mod(floor(trileCenter), 32.0));
|
||||
vec4 atlas_rect = rdm_atlas_rect(local, roughnessInt);
|
||||
float ssao = texture(sampler2D(ssaotex, rdmsmp),
|
||||
gl_FragCoord.xy / vec2(float(screen_w), float(screen_h))).r;
|
||||
float ssao = texture(sampler2D(ssaotex, rdmsmp),
|
||||
gl_FragCoord.xy / vec2(float(screen_w), float(screen_h))).r;
|
||||
vec3 emissive = albedo * emittance * emissive_scale;
|
||||
|
||||
vec3 emissive = albedo * emittance * emissive_scale;
|
||||
|
||||
if (rdm_enabled == 1 && atlas_rect.z > 0.0) {
|
||||
vec3 Frough = FresnelSchlickRoughness(NdotV, F0, roughness);
|
||||
if (rdm_enabled == 1) {
|
||||
vec3 Frough = FresnelSchlickRoughness(NdotV, F0, roughness);
|
||||
vec3 hemispherePos = trileCenter + N * 0.49;
|
||||
vec3 diff = vpos - hemispherePos;
|
||||
vec3 diff = vpos - hemispherePos;
|
||||
|
||||
// Indirect specular
|
||||
if (roughness < ROUGHNESS_SPEC_CUTOFF) {
|
||||
// 4a. Indirect specular.
|
||||
// roughnessInt 0-1 with a baked RDM: ray-march or single-sample into the atlas.
|
||||
// roughnessInt 2+ without RDM data: sky reflection.
|
||||
// roughnessInt > ROUGHNESS_SPEC_CUTOFF: skip specular entirely.
|
||||
if (roughnessInt <= 1 && atlas_rect.z > 0.0) {
|
||||
int face = rdm_face_from_normal(N);
|
||||
ivec2 atlasSize = textureSize(sampler2D(rdm_atlas, rdmsmp), 0);
|
||||
vec2 atlasInvSize = 1.0 / vec2(atlasSize);
|
||||
int rdmSize = int(atlas_rect.z * float(atlasSize.x)) / 2;
|
||||
ivec2 fOff = rdm_face_offset(atlas_rect, face, rdmSize, atlasSize);
|
||||
ivec2 atlasSize = textureSize(sampler2D(rdm_atlas, rdmsmp), 0);
|
||||
vec2 atlasInvSz = 1.0 / vec2(atlasSize);
|
||||
int rdmSize = int(atlas_rect.z * float(atlasSize.x)) / 2;
|
||||
ivec2 fOff = rdm_face_offset(atlas_rect, face, rdmSize, atlasSize);
|
||||
|
||||
vec3 indirectSpec;
|
||||
if (roughness < ROUGHNESS_RAYMARCH_MAX) {
|
||||
indirectSpec = rdm_spec_raymarch(N, -cv, diff, face, fOff, rdmSize, atlasInvSize);
|
||||
} else {
|
||||
indirectSpec = rdm_spec_single(N, -cv, diff, face, fOff, rdmSize, atlasInvSize);
|
||||
}
|
||||
vec3 indirectSpec = roughness < ROUGHNESS_RAYMARCH_MAX
|
||||
? rdm_spec_raymarch(N, -cv, diff, face, fOff, rdmSize, atlasInvSz)
|
||||
: rdm_spec_single (N, -cv, diff, face, fOff, rdmSize, atlasInvSz);
|
||||
indirectSpec *= rdm_tint;
|
||||
|
||||
// Desaturate for metals to avoid double-tinting
|
||||
float specLum = dot(indirectSpec, vec3(0.2126, 0.7152, 0.0722));
|
||||
indirectSpec = mix(indirectSpec, vec3(specLum), metallic);
|
||||
indirectSpec = mix(indirectSpec, vec3(specLum), metallic);
|
||||
|
||||
vec2 envBRDF = texture(sampler2D(brdf_lut, rdmsmp), vec2(NdotV, roughness)).rg;
|
||||
float roughnessBell = 1.0 - 0.7 * sin(roughness * PI);
|
||||
float grazingSuppress = 1.0 - 0.9 * roughness * sin(roughness * PI) * pow(1.0 - NdotV, 2.0);
|
||||
float specRoughFade = 1.0 - clamp((roughness - 0.5) / 0.3, 0.0, 1.0);
|
||||
vec2 envBRDF = texture(sampler2D(brdf_lut, rdmsmp), vec2(NdotV, roughness)).rg;
|
||||
float roughnessBell = 1.0 - 0.7 * sin(roughness * PI);
|
||||
float grazingSuppr = 1.0 - 0.9 * roughness * sin(roughness * PI) * pow(1.0 - NdotV, 2.0);
|
||||
float specRoughFade = 1.0 - clamp((roughness - 0.5) / 0.3, 0.0, 1.0);
|
||||
|
||||
light += indirectSpec * (Frough * envBRDF.x + envBRDF.y)
|
||||
* rdm_spec_scale * roughnessBell * grazingSuppress * specRoughFade;
|
||||
* rdm_spec_scale * roughnessBell * grazingSuppr * specRoughFade;
|
||||
} else if (roughness < ROUGHNESS_SPEC_CUTOFF) {
|
||||
vec3 R = reflect(-V, N);
|
||||
vec2 envBRDF = texture(sampler2D(brdf_lut, rdmsmp), vec2(NdotV, roughness)).rg;
|
||||
float specRoughFd = 1.0 - clamp((roughness - 0.5) / 0.3, 0.0, 1.0);
|
||||
light += sky_reflect(R, sunPosition) * (Frough * envBRDF.x + envBRDF.y)
|
||||
* rdm_spec_scale * specRoughFd;
|
||||
}
|
||||
|
||||
// Indirect diffuse
|
||||
vec3 indirectDiff = rdm_indirect_diffuse(N, diff, local) * rdm_tint;
|
||||
// 4b. Indirect diffuse.
|
||||
// SH probe grid when available (trilinear, confidence-weighted).
|
||||
// Falls back to RDM level-7 diffuse probes.
|
||||
vec3 indirectDiff;
|
||||
if (sh_enabled == 1) {
|
||||
vec3 trile_origin = floor(trileCenter);
|
||||
vec3 local_frag = vec3(local) + (vpos - trile_origin);
|
||||
vec3 probe_f = clamp(local_frag * 2.0, vec3(0.0), vec3(63.0));
|
||||
ivec3 p0 = ivec3(floor(probe_f));
|
||||
ivec3 p1 = min(p0 + ivec3(1), ivec3(63));
|
||||
indirectDiff = sh_eval_trilinear(p0, p1, fract(probe_f), N) * rdm_tint;
|
||||
} else {
|
||||
indirectDiff = rdm_indirect_diffuse(N, diff, local) * rdm_tint;
|
||||
}
|
||||
float diffLuma = dot(indirectDiff, vec3(0.2126, 0.7152, 0.0722));
|
||||
indirectDiff = mix(vec3(diffLuma), indirectDiff, rdm_diff_saturation);
|
||||
vec3 kDiff = (1.0 - Frough) * (1.0 - metallic);
|
||||
indirectDiff = mix(vec3(diffLuma), indirectDiff, rdm_diff_saturation);
|
||||
|
||||
light += kDiff * indirectDiff / PI * albedo * ssao * rdm_diff_scale;
|
||||
light += (1.0 - Frough) * (1.0 - metallic) * indirectDiff / PI * albedo * ssao * rdm_diff_scale;
|
||||
|
||||
// Ambient floor
|
||||
// 4c. Ambient floor — kicks in when indirect light is below the configured minimum.
|
||||
if (rdm_diff_scale < 0.001 || length(light) < ambient_intensity)
|
||||
light += ambient_color * max(ambient_intensity - length(light), 0.0) * albedo * ssao;
|
||||
|
||||
} else {
|
||||
// No baked data: flat ambient + sky specular.
|
||||
light += ambient_color * ambient_intensity * albedo * ssao;
|
||||
vec3 R = reflect(-V, N);
|
||||
light += F * sky_reflect(R, sunPosition) * 0.1;
|
||||
light += F * sky_reflect(reflect(-V, N), sunPosition) * 0.1;
|
||||
}
|
||||
|
||||
// ---- 5. FINAL COMPOSITE ----
|
||||
vec3 final_color = light + emissive;
|
||||
frag_color = vec4(mix(deepColor, final_color, smoothstep(0.0, planeHeight, vpos.y)), 1.0);
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#load "utils.jai";
|
||||
#load "world_test.jai";
|
||||
#load "../editor/rdm_disk_test.jai";
|
||||
|
||||
#load "engine_exe_tests/index.jai";
|
||||
#load "exe_tests/index.jai";
|
||||
|
||||
@ -58,6 +58,10 @@ Chunk :: struct {
|
||||
rdm_dirty: bool;
|
||||
rdm_atlas_path: string;
|
||||
rdm_lookup_path: string;
|
||||
|
||||
sh_probe_grid: sg_image; // 192x4096 RGBA16F 2D texture (2 probes/trile/axis)
|
||||
sh_valid: bool;
|
||||
sh_dirty: bool;
|
||||
#if !FLAG_RELEASE_BUILD {
|
||||
rdm_lookup_cpu: []float;
|
||||
rdm_lookup_w: s32;
|
||||
@ -707,8 +711,8 @@ World_Config :: struct {
|
||||
sunHalo : Vector3 = .{1.0, 1.0, 1.0}; @Color
|
||||
sunLightColor : Vector3 = .{1.0, 1.0, 1.0}; @Color
|
||||
sunPosition : Vector3 = #run normalize(Vector3.{0.2, 0.3, 0.4});
|
||||
sunIntensity : float = 2.0; @Slider,0,100,0.5
|
||||
skyIntensity : float = 1.0; @Slider,0,10,0.5
|
||||
sunIntensity : float = 1.0; @Slider,0,4,0.1
|
||||
skyIntensity : float = 0.3; @Slider,0,5,0.1
|
||||
|
||||
hasClouds : s32 = 1; @Slider,0,1,1
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user