start on foliage

This commit is contained in:
Tuomas Katajisto 2026-10-05 19:58:18 +03:00
parent 0bd7102b35
commit dd947e5738
15 changed files with 2476 additions and 169 deletions

View File

@ -267,6 +267,8 @@ add_resources_from_pack :: (pack: *Loaded_Pack) {
}
log_info("Loaded % particle definitions from pack", g_emitter_defs.count);
}
} else if name == "foliage" {
load_foliage_defs_from_json(create_string_from_memory(v.data));
}
case "hex";
pal : Loaded_Palette;

View File

@ -7,6 +7,7 @@
#load "level_editor.jai";
#load "particle_editor.jai";
#load "material_studio.jai";
#load "foliage_studio.jai";
}
#if FLAG_TACOMA_ENABLED { #load "tacoma.jai"; }
#load "console.jai";
@ -19,6 +20,7 @@ Editor_View :: enum {
Level_Editor;
Particle_Editor;
Material_Editor;
Foliage_Editor;
};
current_editor_view : Editor_View = .Trile_Editor;
@ -65,6 +67,8 @@ draw_editor_ui :: (theme: *GR.Overall_Theme) {
if keybind_button(r, "Particle studio", .STUDIO_MATERIAL, *t_button_selectable(theme, current_editor_view == .Particle_Editor)) { clear_particles(); current_editor_view = .Particle_Editor; }
r.x -= r.w;
if keybind_button(r, "Material studio", .STUDIO_SWATCHES, *t_button_selectable(theme, current_editor_view == .Material_Editor)) { clear_particles(); current_editor_view = .Material_Editor; }
r.x -= r.w;
if keybind_button(r, "Foliage studio", .STUDIO_FOLIAGE, *t_button_selectable(theme, current_editor_view == .Foliage_Editor)) { clear_particles(); current_editor_view = .Foliage_Editor; }
if current_editor_view == {
case .Trile_Editor;
@ -75,6 +79,8 @@ draw_editor_ui :: (theme: *GR.Overall_Theme) {
draw_particle_editor_ui(theme);
case .Material_Editor;
draw_material_studio_ui(theme);
case .Foliage_Editor;
draw_foliage_studio_ui(theme);
}
}
draw_profiler();

View File

@ -0,0 +1,155 @@
#scope_file
fs_selected_index : s32 = -1;
fs_scroll : float = 0;
fs_name_input_start : string = "";
fs_anim_input_start : string = "";
fs_label :: (r: *GR.Rect, text: string, theme: *GR.Overall_Theme) {
GR.label(r.*, text, *t_label_left(theme));
r.y += r.h;
}
fs_float :: (r: *GR.Rect, label: string, value: *float, lo: float, hi: float, step: float, theme: *GR.Overall_Theme, loc := #caller_location) {
GR.label(r.*, label, *t_label_left(theme));
r.y += r.h;
GR.slider(r.*, value, lo, hi, step, *theme.slider_theme, loc = loc);
r.y += r.h;
}
fs_select_def :: (idx: s32) {
fs_selected_index = idx;
if idx >= 0 && idx < cast(s32) g_foliage_defs.count {
fs_name_input_start = g_foliage_defs[idx].name;
fs_anim_input_start = g_foliage_defs[idx].animation_name;
}
}
#scope_export
draw_foliage_studio_ui :: (theme: *GR.Overall_Theme) {
panel_w := ui_w(20, 20);
row_h := ui_h(3, 0);
list_r := GR.get_rect(ui_w(80, 20), ui_h(5,0), panel_w, ui_h(95, 0));
ui_add_mouse_occluder(list_r);
draw_bg_rectangle(list_r, theme);
r := list_r;
r.h = row_h;
half_w := r.w / 2;
orig_w := r.w;
r.w = half_w;
if GR.button(r, "New", *theme.button_theme) {
def : Foliage_Def;
def.name = sprint("foliage_%", g_foliage_defs.count);
array_add(*g_foliage_defs, def);
fs_select_def(cast(s32)(g_foliage_defs.count - 1));
}
r.x += half_w;
if GR.button(r, "Duplicate", *theme.button_theme) {
if fs_selected_index >= 0 && fs_selected_index < cast(s32) g_foliage_defs.count {
def := g_foliage_defs[fs_selected_index];
def.name = sprint("%_copy", def.name);
def.animation_name = copy_string(def.animation_name);
array_add(*g_foliage_defs, def);
fs_select_def(cast(s32)(g_foliage_defs.count - 1));
}
}
r.x -= half_w;
r.w = orig_w;
r.y += r.h;
for def, idx : g_foliage_defs {
if r.y > list_r.y + list_r.h - row_h * 3 then break;
selected := (cast(s32)idx == fs_selected_index);
if GR.button(r, def.name, *t_button_selectable(theme, selected), cast(s32)idx) {
fs_select_def(cast(s32)idx);
}
r.y += r.h;
}
r.y = list_r.y + list_r.h - row_h * 2;
r.w = half_w;
if keybind_button(r, "Save", .SAVE, *theme.button_theme) {
save_foliage_defs();
}
r.x += half_w;
if GR.button(r, "Delete", *theme.button_theme) {
if fs_selected_index >= 0 && fs_selected_index < cast(s32) g_foliage_defs.count {
array_ordered_remove_by_index(*g_foliage_defs, fs_selected_index);
if fs_selected_index >= cast(s32) g_foliage_defs.count then fs_selected_index -= 1;
fs_select_def(fs_selected_index);
}
}
r.w = orig_w;
if fs_selected_index < 0 || fs_selected_index >= cast(s32) g_foliage_defs.count then return;
def := *g_foliage_defs[fs_selected_index];
edit_r := GR.get_rect(0, ui_h(5,0), panel_w, ui_h(95, 0));
ui_add_mouse_occluder(edit_r);
draw_bg_rectangle(edit_r, theme);
region, inside := GR.begin_scrollable_region(edit_r, *theme.scrollable_region_theme);
er := inside;
er.y -= fs_scroll;
er.h = row_h;
fs_label(*er, "Name", theme);
a, new_name, _ := GR.text_input(er, fs_name_input_start, *theme.text_input_theme, 0);
if a & .ENTERED {
def.name = copy_string(new_name);
fs_name_input_start = def.name;
}
er.y += er.h;
fs_label(*er, "Animation (pack.anim)", theme);
a2, new_anim, _ := GR.text_input(er, fs_anim_input_start, *theme.text_input_theme, 1);
if a2 & .ENTERED {
def.animation_name = copy_string(new_anim);
fs_anim_input_start = def.animation_name;
}
er.y += er.h;
anim := get_animation_from_string(def.animation_name);
if anim != null {
fs_label(*er, tprint("Animation: % frames", anim.frames.count), theme);
} else {
fs_label(*er, tprint("Animation: not found ('%')", def.animation_name), theme);
}
orig_w2 := er.w;
er.w = orig_w2 / 2;
fs_label(*er, "Type", theme);
if GR.button(er, "Grass", *t_button_selectable(theme, def.type == .GRASS), 100) then def.type = .GRASS;
er.x += er.w;
if GR.button(er, "Tree", *t_button_selectable(theme, def.type == .TREE), 101) then def.type = .TREE;
er.x -= er.w;
er.y += er.h;
fs_label(*er, "Frames", theme);
if GR.button(er, "Variant", *t_button_selectable(theme, def.frame_mode == .VARIANT), 102) then def.frame_mode = .VARIANT;
er.x += er.w;
if GR.button(er, "Loop", *t_button_selectable(theme, def.frame_mode == .LOOP), 103) then def.frame_mode = .LOOP;
er.x -= er.w;
er.y += er.h;
er.w = orig_w2;
if def.type == .GRASS {
fs_float(*er, tprint("Density: %", def.density), *def.density, 0, 1, 0.05, theme);
fs_float(*er, tprint("Jitter: %", def.jitter), *def.jitter, 0, 0.5, 0.05, theme);
}
fs_float(*er, tprint("Scale Min: %", def.scale_min), *def.scale_min, 0.1, 4, 0.05, theme);
fs_float(*er, tprint("Scale Max: %", def.scale_max), *def.scale_max, 0.1, 4, 0.05, theme);
if def.scale_max < def.scale_min then def.scale_max = def.scale_min;
fs_float(*er, tprint("Sway Intensity: %", def.sway_intensity), *def.sway_intensity, 0, 1, 0.05, theme);
fs_float(*er, tprint("Sway Speed: %", def.sway_speed), *def.sway_speed, 0, 5, 0.1, theme);
GR.end_scrollable_region(region, er.x + er.w, er.y, *fs_scroll);
}

88
src/foliage.jai Normal file
View File

@ -0,0 +1,88 @@
// Our foliage system. Cells on a 2D grid.
Foliage_Type :: enum u8 {
GRASS; // For stuff that you want to just paint and gets rendered a lot. Grass or hay.
TREE; // For when you want single placements of something. Trees or bushes.
}
Foliage_Frame_Mode :: enum u8 {
VARIANT; // The seed picks one frame and it stays there.
LOOP; // Plays the animation, with the seed offsetting the start.
}
Foliage_Def :: struct {
name : string;
type : Foliage_Type;
// What we render
animation_name : string;
frame_mode : Foliage_Frame_Mode;
density : float = 0.5; // Only does anything when foliage type is grass.
scale_min : float = 1;
scale_max : float = 1;
jitter : float = 0.4;
sway_intensity : float;
sway_speed : float = 1;
}
FOLIAGE_NONE :: 0;
// Chunks in the world contain a 2D grid of these.
Foliage_Cell :: struct {
id : u8; // FOLIAGE_NONE when empty.
y : u8; // Local y inside the chunk, so we don't draw grass on top of a bridge.
}
g_foliage_defs : [..]Foliage_Def;
get_foliage_def :: (name: string) -> *Foliage_Def {
for *def: g_foliage_defs {
if def.name == name then return def;
}
return null;
}
register_foliage_def :: (def: Foliage_Def) {
for *existing: g_foliage_defs {
if existing.name == def.name {
existing.* = def;
return;
}
}
array_add(*g_foliage_defs, def);
}
get_foliage_def_names :: () -> []string {
names : [..]string;
names.allocator = temp;
for def: g_foliage_defs {
array_add(*names, def.name);
}
return names;
}
load_foliage_defs_from_json :: (s: string) {
success, defs := Jaison.json_parse_string(s, [..]Foliage_Def,, temp);
if !success {
log_error("Failed to parse foliage definitions");
return;
}
array_reset(*g_foliage_defs);
for defs {
def := it;
def.name = sprint("%", it.name);
def.animation_name = sprint("%", it.animation_name);
array_add(*g_foliage_defs, def);
}
log_info("Loaded % foliage definitions from pack", g_foliage_defs.count);
}
save_foliage_defs :: () {
#if OS != .WASM {
file :: #import "File";
json := Jaison.json_write_string(g_foliage_defs, " ");
file.write_entire_file(tprint("%/game_core/foliage.json", GAME_RESOURCES_DIR), json);
log_info("Saved % foliage definitions", g_foliage_defs.count);
}
}

View File

@ -40,6 +40,7 @@ Editor_Action :: enum {
STUDIO_LEVEL;
STUDIO_MATERIAL;
STUDIO_SWATCHES;
STUDIO_FOLIAGE;
// Trile editor — view modes
TRIXEL_VIEW_TILE;
// Trile editor — tiling controls
@ -213,6 +214,7 @@ set_default_bindings :: () {
set(.STUDIO_LEVEL, cast(Key_Code) #char "2", ctrl = true);
set(.STUDIO_MATERIAL, cast(Key_Code) #char "3", ctrl = true);
set(.STUDIO_SWATCHES, cast(Key_Code) #char "4", ctrl = true);
set(.STUDIO_FOLIAGE, cast(Key_Code) #char "5", ctrl = true);
set(.TRIXEL_VIEW_TILE, cast(Key_Code) #char "9");
set(.TRIXEL_TILING_AXIS, cast(Key_Code) #char "Q");
set(.TRIXEL_TILING_ROTATE, cast(Key_Code) #char "R");

View File

@ -42,6 +42,7 @@ FLAG_USE_TEST_GAME :: FLAG_TEST_ENGINE || FLAG_TEST_EXE_ENGINE;
#load "ray.jai";
#load "profiling.jai";
#load "particles/particles.jai";
#load "foliage.jai";
#load "orientation.jai";
#load "entities.jai";
#load "world.jai";

View File

@ -3,7 +3,7 @@ VIEW_COLOR_FORMAT :: sg_pixel_format.RGBA16F;
VIEW_DEPTH_FORMAT :: sg_pixel_format.DEPTH;
VIEW_SCENE_FORMAT :: sg_pixel_format.R32F;
VIEW_GLOW_FORMAT :: sg_pixel_format.R16F;
SHADOW_TINT_FORMAT :: sg_pixel_format.RGBA8; // how strongly each pixel blooms, can exceed 1
SHADOW_TINT_FORMAT :: sg_pixel_format.RGBA16F; // glass colour, and the nearest glass depth in alpha
ALPHA_BLEND :: sg_blend_state.{
enabled = true,

View File

@ -92,6 +92,8 @@ renderer_render :: (scene: *Scene, width: s32, height: s32) {
sprites_draw(*pass, transparent = true);
lines_draw(*pass, width, height);
sg_end_pass();
if kind == .MAIN view_shafts(*pass);
}
view_bloom(view_index);
}

File diff suppressed because it is too large Load Diff

View File

@ -60,6 +60,101 @@ void main() {
}
@end
// Sunlight that came through glass, scattered toward the camera by the air between
// it and the first opaque surface. Half resolution.
@vs vs_shafts
@include_block view
@include_block fullscreen
out vec4 v_point;
out vec3 v_camera;
out vec3 v_sun;
out float v_time;
void main() {
vec2 p = fullscreen_position(gl_VertexIndex);
gl_Position = vec4(p, 0.0, 1.0);
v_point = inv_view_proj * vec4(p, 0.5, 1.0);
v_camera = camera.xyz;
v_sun = sun_color.rgb;
v_time = camera.w;
}
@end
@fs fs_shafts
@include_block shadow
layout(binding=1) uniform shaft_params {
float density; // how much light scatters per unit, and how fast it fades
float amount;
float max_distance;
float steps;
};
layout(binding=0) uniform texture2D shaft_scene;
layout(binding=0) uniform sampler shaft_scene_smp;
@image_sample_type shaft_scene unfilterable_float
@sampler_type shaft_scene_smp nonfiltering
in vec4 v_point;
in vec3 v_camera;
in vec3 v_sun;
in float v_time;
out vec4 frag_color;
const vec3 SHAFT_DRIFT = vec3(0.4, 0.15, 0.3); // how the air moves, units per second
const float SHAFT_SCALE = 0.8; // size of the dusty patches, inverse units
const float SHAFT_SHIMMER = 1.0; // how much the dust thins and thickens the light
const float SHAFT_TINT = 0.6; // how much of the glass colour the shafts keep
float hash(vec3 p) {
p = fract(p * 0.3183099 + 0.1);
p *= 17.0;
return fract(p.x * p.y * p.z * (p.x + p.y + p.z));
}
float value_noise(vec3 p) {
vec3 i = floor(p);
vec3 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
return mix(mix(mix(hash(i), hash(i + vec3(1, 0, 0)), f.x),
mix(hash(i + vec3(0, 1, 0)), hash(i + vec3(1, 1, 0)), f.x), f.y),
mix(mix(hash(i + vec3(0, 0, 1)), hash(i + vec3(1, 0, 1)), f.x),
mix(hash(i + vec3(0, 1, 1)), hash(i + vec3(1, 1, 1)), f.x), f.y), f.z);
}
// Two layers drifting at different speeds, so the dust churns instead of sliding.
float dust(vec3 p) {
vec3 q = p * SHAFT_SCALE;
float n = value_noise(q + SHAFT_DRIFT * v_time * SHAFT_SCALE) * 0.65
+ value_noise(q * 2.3 - SHAFT_DRIFT.zxy * v_time * SHAFT_SCALE * 1.7) * 0.35;
// Sharpened, so the patches read as clear gaps and thick wisps rather than a soft wobble.
n = smoothstep(0.25, 0.75, n);
return mix(1.0 - SHAFT_SHIMMER, 1.0 + SHAFT_SHIMMER, n);
}
void main() {
vec3 dir = normalize(v_point.xyz / v_point.w - v_camera);
float len = min(texelFetch(sampler2D(shaft_scene, shaft_scene_smp), ivec2(gl_FragCoord.xy) * 2, 0).r, max_distance);
float step = len / steps;
// Interleaved gradient noise spreads the step banding into a fine dither.
float jitter = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));
vec3 light = vec3(0.0);
for (float i = 0.0; i < steps; i += 1.0) {
float t = (i + jitter) * step;
// Snapped to the trixel grid, so the shafts are blocky like the patches they make.
vec3 p = (floor((v_camera + dir * t) * 16.0) + 0.5) / 16.0;
vec4 s = shadow_uv * vec4(p, 1.0);
if (s.x < 0.0 || s.x > 1.0 || s.y < 0.0 || s.y > 1.0 || s.z > 1.0) continue;
vec4 tint = texture(sampler2D(shadow_tint, shadow_tint_smp), s.xy);
if (s.z < tint.a + 0.001) continue;
float visible = texture(sampler2DShadow(shadow_map, shadow_smp), vec3(s.xy, s.z - 0.0005));
vec3 color = mix(vec3(brightest(tint.rgb)), tint.rgb, SHAFT_TINT);
light += visible * color * exp(-density * t) * dust(p);
}
frag_color = vec4(light * v_sun * density * step * amount, 1.0);
}
@end
@fs fs_post
layout(binding=0) uniform post_params {
float exposure; // in stops
@ -69,6 +164,7 @@ layout(binding=0) uniform post_params {
};
layout(binding=0) uniform texture2D view_color;
layout(binding=1) uniform texture2D bloom;
layout(binding=2) uniform texture2D shafts;
layout(binding=0) uniform sampler view_smp;
layout(binding=1) uniform sampler bloom_view_smp;
out vec4 frag_color;
@ -77,6 +173,13 @@ void main() {
vec2 uv = gl_FragCoord.xy / vec2(textureSize(sampler2D(view_color, view_smp), 0));
vec3 c = texture(sampler2D(view_color, view_smp), uv).rgb;
c += texture(sampler2D(bloom, bloom_view_smp), uv).rgb * bloom_amount;
// A small tent blur over the half resolution shafts hides their dither.
vec2 st = 1.0 / vec2(textureSize(sampler2D(shafts, bloom_view_smp), 0));
c += (texture(sampler2D(shafts, bloom_view_smp), uv).rgb * 2.0
+ texture(sampler2D(shafts, bloom_view_smp), uv + st * vec2( 1, 1)).rgb
+ texture(sampler2D(shafts, bloom_view_smp), uv + st * vec2(-1, 1)).rgb
+ texture(sampler2D(shafts, bloom_view_smp), uv + st * vec2( 1, -1)).rgb
+ texture(sampler2D(shafts, bloom_view_smp), uv + st * vec2(-1, -1)).rgb) / 6.0;
c *= exp2(exposure);
c = (c - 0.5) * contrast + 0.5;
c = mix(vec3(dot(c, vec3(0.2126, 0.7152, 0.0722))), c, saturation);
@ -87,4 +190,5 @@ void main() {
@program bloom_glow vs_fullscreen fs_bloom_glow
@program bloom_down vs_fullscreen fs_bloom_down
@program bloom_up vs_fullscreen fs_bloom_up
@program shafts vs_shafts fs_shafts
@program post vs_fullscreen fs_post

View File

@ -216,7 +216,7 @@ void main() {
if (texelFetch(sampler2D(palette, palette_smp), ivec2(id, 1), 0).r < 0.5) discard;
vec3 c = texelFetch(sampler2D(palette, palette_smp), ivec2(id, 0), 0).rgb;
// Tinted rather than darkened: the glass's brightest channel passes fully.
frag_color = vec4(c / max(max(c.r, max(c.g, c.b)), 0.001), 1.0);
frag_color = vec4(c / max(max(c.r, max(c.g, c.b)), 0.001), gl_FragCoord.z);
}
@end

View File

@ -19,8 +19,13 @@ triles_init :: (orientations: [ORIENTATION_COUNT]Matrix3) {
shadow_desc.face_winding = .CCW;
triles.shadow_pipeline = sg_make_pipeline(*shadow_desc);
glass_desc := pipeline_desc(trile_glass_shadow_shader_desc(sg_query_backend()), .SHADOW, MULTIPLY_BLEND);
glass_desc.colors[0].write_mask = .RGB;
// Alpha keeps the nearest glass depth, so light shafts start at the window.
glass_blend := MULTIPLY_BLEND;
glass_blend.src_factor_alpha = .ONE;
glass_blend.dst_factor_alpha = .ONE;
glass_blend.op_alpha = .MIN;
glass_desc := pipeline_desc(trile_glass_shadow_shader_desc(sg_query_backend()), .SHADOW, glass_blend);
glass_desc.colors[0].write_mask = .RGBA;
glass_desc.layout = desc.layout;
glass_desc.index_type = .UINT16;
glass_desc.cull_mode = .FRONT;

View File

@ -1,10 +1,14 @@
BLOOM_LEVELS :: 5;
BLOOM_SCATTER :: 0.7;
SHAFT_STEPS :: 48;
SHAFT_MAX_DISTANCE :: 100.0;
views_init :: () {
backend := sg_query_backend();
post_desc := pipeline_desc(post_shader_desc(backend), .SCREEN);
views.post = sg_make_pipeline(*post_desc);
shafts_desc := pipeline_desc(shafts_shader_desc(backend), .POST);
views.shafts = sg_make_pipeline(*shafts_desc);
glow_desc := pipeline_desc(bloom_glow_shader_desc(backend), .POST);
views.bloom_glow = sg_make_pipeline(*glow_desc);
down_desc := pipeline_desc(bloom_down_shader_desc(backend), .POST);
@ -25,6 +29,8 @@ view_begin_pass :: (index: int, width: s32, height: s32, kind: Pass_Kind, opaque
sg_destroy_attachments(level.attachments);
sg_destroy_image(level.image);
}
sg_destroy_attachments(it.shafts.attachments);
sg_destroy_image(it.shafts.image);
}
array_reset_keeping_memory(*views.targets);
views.width = width;
@ -42,6 +48,10 @@ view_begin_pass :: (index: int, width: s32, height: s32, kind: Pass_Kind, opaque
desc.colors[0].image = level.image;
level.attachments = sg_make_attachments(*desc);
}
targets.shafts.image = sg_make_image(*(sg_image_desc.{ render_target = true, width = max(width / 2, 1), height = max(height / 2, 1), pixel_format = VIEW_COLOR_FORMAT, sample_count = 1 }));
desc : sg_attachments_desc;
desc.colors[0].image = targets.shafts.image;
targets.shafts.attachments = sg_make_attachments(*desc);
}
targets := views.targets[index];
@ -120,6 +130,34 @@ view_bloom :: (index: int) {
for < 1..BLOOM_LEVELS-1 run(views.bloom_up, targets.bloom[it].image, targets.bloom[it - 1].attachments, true);
}
// Marches each view ray through the coloured shadow map, after the main pass has
// written how far it reaches.
view_shafts :: (pass: *Pass) {
env := view_environment(pass.view);
action : sg_pass_action;
action.colors[0] = .{ load_action = .CLEAR, clear_value = .{0, 0, 0, 1} };
sg_begin_pass(*(sg_pass.{ action = action, attachments = views.targets[pass.view_index].shafts.attachments }));
if env.shaft_amount > 0 {
sg_apply_pipeline(views.shafts);
params := Shaft_Params.{
density = env.shaft_density,
amount = env.shaft_amount,
max_distance = SHAFT_MAX_DISTANCE,
steps = SHAFT_STEPS,
};
apply_uniforms(UB_view_params, *pass.params);
apply_uniforms(UB_shadow_params, *pass.shadow);
apply_uniforms(UB_shaft_params, *params);
bind : sg_bindings;
bind.images[IMG_shaft_scene] = views.targets[pass.view_index].main.scene;
bind.samplers[SMP_shaft_scene_smp] = samplers.nearest_clamp;
bind_shadow(pass, *bind);
sg_apply_bindings(*bind);
sg_draw(0, 3, 1);
}
sg_end_pass();
}
view_composite :: (index: int, env: Environment) {
sg_apply_pipeline(views.post);
params := Post_Params.{
@ -133,6 +171,7 @@ view_composite :: (index: int, env: Environment) {
bind : sg_bindings;
bind.images[IMG_view_color] = views.targets[index].main.color;
bind.images[IMG_bloom] = views.targets[index].bloom[0].image;
bind.images[IMG_shafts] = views.targets[index].shafts.image;
bind.samplers[SMP_view_smp] = samplers.nearest_clamp;
bind.samplers[SMP_bloom_view_smp] = samplers.linear_clamp;
sg_apply_bindings(*bind);
@ -152,10 +191,11 @@ View_Target :: struct {
views : struct {
post : sg_pipeline;
shafts : sg_pipeline;
bloom_glow : sg_pipeline;
bloom_down : sg_pipeline;
bloom_up : sg_pipeline;
targets : [..]struct { main, reflection: View_Target; bloom: [BLOOM_LEVELS]struct { image: sg_image; attachments: sg_attachments; }; };
targets : [..]struct { main, reflection: View_Target; bloom: [BLOOM_LEVELS]struct { image: sg_image; attachments: sg_attachments; }; shafts: struct { image: sg_image; attachments: sg_attachments; }; };
shadows : [..]struct { depth, tint: sg_image; attachments: sg_attachments; };
width : s32;
height : s32;

View File

@ -31,6 +31,8 @@ Environment :: struct {
contrast : float = 1;
saturation : float = 1;
bloom_amount : float = 0.4;
shaft_density : float = 0.1; // light shafts through glass
shaft_amount : float = 1;
}
// With cut on, triles marked ROOF or WALL are hidden from the camera around the

View File

@ -59,6 +59,9 @@ Chunk :: struct {
occupancy_mask : [32][32]u32;
// We want to be easily able to identify stairs from the chunk.
stairs_mask : [32][32]u32;
// Foliage grid
foliage : [32][32]Foliage_Cell;
}
Occupancy_State :: enum {
@ -351,6 +354,8 @@ World_Json_Config :: struct {
contrast : float = 1;
saturation : float = 1;
bloomAmount : float = 0.4;
shaftDensity : float = 0.1;
shaftAmount : float = 1;
}
World_Json_Chunk :: struct {
@ -512,6 +517,8 @@ world_config_to_json :: (conf: *World_Config) -> World_Json_Config {
jc.contrast = conf.contrast;
jc.saturation = conf.saturation;
jc.bloomAmount = conf.bloomAmount;
jc.shaftDensity = conf.shaftDensity;
jc.shaftAmount = conf.shaftAmount;
return jc;
}
@ -540,6 +547,8 @@ world_config_from_json :: (jc: *World_Json_Config) -> World_Config {
conf.contrast = jc.contrast;
conf.saturation = jc.saturation;
conf.bloomAmount = jc.bloomAmount;
conf.shaftDensity = jc.shaftDensity;
conf.shaftAmount = jc.shaftAmount;
return conf;
}
@ -759,6 +768,8 @@ World_Config :: struct {
contrast : float = 1; @Slider,0,3,0.05
saturation : float = 1; @Slider,0,3,0.05
bloomAmount : float = 0.4; @Slider,0,3,0.05
shaftDensity : float = 0.1; @Slider,0,1,0.01
shaftAmount : float = 1; @Slider,0,5,0.1
sunPosition : Vector3 = #run normalize(Vector3.{0.2, 0.3, 0.4});
sunIntensity : float = 1.0; @Slider,0,4,0.1
skyIntensity : float = 0.3; @Slider,0,5,0.1
@ -810,6 +821,8 @@ world_environment :: (world: *World) -> *Environment {
env.contrast = conf.contrast;
env.saturation = conf.saturation;
env.bloom_amount = conf.bloomAmount;
env.shaft_density = conf.shaftDensity;
env.shaft_amount = conf.shaftAmount;
return env;
}