start on foliage
This commit is contained in:
parent
0bd7102b35
commit
dd947e5738
@ -267,6 +267,8 @@ add_resources_from_pack :: (pack: *Loaded_Pack) {
|
|||||||
}
|
}
|
||||||
log_info("Loaded % particle definitions from pack", g_emitter_defs.count);
|
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";
|
case "hex";
|
||||||
pal : Loaded_Palette;
|
pal : Loaded_Palette;
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
#load "level_editor.jai";
|
#load "level_editor.jai";
|
||||||
#load "particle_editor.jai";
|
#load "particle_editor.jai";
|
||||||
#load "material_studio.jai";
|
#load "material_studio.jai";
|
||||||
|
#load "foliage_studio.jai";
|
||||||
}
|
}
|
||||||
#if FLAG_TACOMA_ENABLED { #load "tacoma.jai"; }
|
#if FLAG_TACOMA_ENABLED { #load "tacoma.jai"; }
|
||||||
#load "console.jai";
|
#load "console.jai";
|
||||||
@ -19,6 +20,7 @@ Editor_View :: enum {
|
|||||||
Level_Editor;
|
Level_Editor;
|
||||||
Particle_Editor;
|
Particle_Editor;
|
||||||
Material_Editor;
|
Material_Editor;
|
||||||
|
Foliage_Editor;
|
||||||
};
|
};
|
||||||
|
|
||||||
current_editor_view : Editor_View = .Trile_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; }
|
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;
|
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; }
|
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 == {
|
if current_editor_view == {
|
||||||
case .Trile_Editor;
|
case .Trile_Editor;
|
||||||
@ -75,6 +79,8 @@ draw_editor_ui :: (theme: *GR.Overall_Theme) {
|
|||||||
draw_particle_editor_ui(theme);
|
draw_particle_editor_ui(theme);
|
||||||
case .Material_Editor;
|
case .Material_Editor;
|
||||||
draw_material_studio_ui(theme);
|
draw_material_studio_ui(theme);
|
||||||
|
case .Foliage_Editor;
|
||||||
|
draw_foliage_studio_ui(theme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
draw_profiler();
|
draw_profiler();
|
||||||
|
|||||||
155
src/editor/foliage_studio.jai
Normal file
155
src/editor/foliage_studio.jai
Normal 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
88
src/foliage.jai
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -40,6 +40,7 @@ Editor_Action :: enum {
|
|||||||
STUDIO_LEVEL;
|
STUDIO_LEVEL;
|
||||||
STUDIO_MATERIAL;
|
STUDIO_MATERIAL;
|
||||||
STUDIO_SWATCHES;
|
STUDIO_SWATCHES;
|
||||||
|
STUDIO_FOLIAGE;
|
||||||
// Trile editor — view modes
|
// Trile editor — view modes
|
||||||
TRIXEL_VIEW_TILE;
|
TRIXEL_VIEW_TILE;
|
||||||
// Trile editor — tiling controls
|
// Trile editor — tiling controls
|
||||||
@ -213,6 +214,7 @@ set_default_bindings :: () {
|
|||||||
set(.STUDIO_LEVEL, cast(Key_Code) #char "2", ctrl = true);
|
set(.STUDIO_LEVEL, cast(Key_Code) #char "2", ctrl = true);
|
||||||
set(.STUDIO_MATERIAL, cast(Key_Code) #char "3", ctrl = true);
|
set(.STUDIO_MATERIAL, cast(Key_Code) #char "3", ctrl = true);
|
||||||
set(.STUDIO_SWATCHES, cast(Key_Code) #char "4", 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_VIEW_TILE, cast(Key_Code) #char "9");
|
||||||
set(.TRIXEL_TILING_AXIS, cast(Key_Code) #char "Q");
|
set(.TRIXEL_TILING_AXIS, cast(Key_Code) #char "Q");
|
||||||
set(.TRIXEL_TILING_ROTATE, cast(Key_Code) #char "R");
|
set(.TRIXEL_TILING_ROTATE, cast(Key_Code) #char "R");
|
||||||
|
|||||||
@ -42,6 +42,7 @@ FLAG_USE_TEST_GAME :: FLAG_TEST_ENGINE || FLAG_TEST_EXE_ENGINE;
|
|||||||
#load "ray.jai";
|
#load "ray.jai";
|
||||||
#load "profiling.jai";
|
#load "profiling.jai";
|
||||||
#load "particles/particles.jai";
|
#load "particles/particles.jai";
|
||||||
|
#load "foliage.jai";
|
||||||
#load "orientation.jai";
|
#load "orientation.jai";
|
||||||
#load "entities.jai";
|
#load "entities.jai";
|
||||||
#load "world.jai";
|
#load "world.jai";
|
||||||
|
|||||||
@ -3,7 +3,7 @@ VIEW_COLOR_FORMAT :: sg_pixel_format.RGBA16F;
|
|||||||
VIEW_DEPTH_FORMAT :: sg_pixel_format.DEPTH;
|
VIEW_DEPTH_FORMAT :: sg_pixel_format.DEPTH;
|
||||||
VIEW_SCENE_FORMAT :: sg_pixel_format.R32F;
|
VIEW_SCENE_FORMAT :: sg_pixel_format.R32F;
|
||||||
VIEW_GLOW_FORMAT :: sg_pixel_format.R16F;
|
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.{
|
ALPHA_BLEND :: sg_blend_state.{
|
||||||
enabled = true,
|
enabled = true,
|
||||||
|
|||||||
@ -92,6 +92,8 @@ renderer_render :: (scene: *Scene, width: s32, height: s32) {
|
|||||||
sprites_draw(*pass, transparent = true);
|
sprites_draw(*pass, transparent = true);
|
||||||
lines_draw(*pass, width, height);
|
lines_draw(*pass, width, height);
|
||||||
sg_end_pass();
|
sg_end_pass();
|
||||||
|
|
||||||
|
if kind == .MAIN view_shafts(*pass);
|
||||||
}
|
}
|
||||||
view_bloom(view_index);
|
view_bloom(view_index);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -60,6 +60,101 @@ void main() {
|
|||||||
}
|
}
|
||||||
@end
|
@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
|
@fs fs_post
|
||||||
layout(binding=0) uniform post_params {
|
layout(binding=0) uniform post_params {
|
||||||
float exposure; // in stops
|
float exposure; // in stops
|
||||||
@ -69,6 +164,7 @@ layout(binding=0) uniform post_params {
|
|||||||
};
|
};
|
||||||
layout(binding=0) uniform texture2D view_color;
|
layout(binding=0) uniform texture2D view_color;
|
||||||
layout(binding=1) uniform texture2D bloom;
|
layout(binding=1) uniform texture2D bloom;
|
||||||
|
layout(binding=2) uniform texture2D shafts;
|
||||||
layout(binding=0) uniform sampler view_smp;
|
layout(binding=0) uniform sampler view_smp;
|
||||||
layout(binding=1) uniform sampler bloom_view_smp;
|
layout(binding=1) uniform sampler bloom_view_smp;
|
||||||
out vec4 frag_color;
|
out vec4 frag_color;
|
||||||
@ -77,6 +173,13 @@ void main() {
|
|||||||
vec2 uv = gl_FragCoord.xy / vec2(textureSize(sampler2D(view_color, view_smp), 0));
|
vec2 uv = gl_FragCoord.xy / vec2(textureSize(sampler2D(view_color, view_smp), 0));
|
||||||
vec3 c = texture(sampler2D(view_color, view_smp), uv).rgb;
|
vec3 c = texture(sampler2D(view_color, view_smp), uv).rgb;
|
||||||
c += texture(sampler2D(bloom, bloom_view_smp), uv).rgb * bloom_amount;
|
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 *= exp2(exposure);
|
||||||
c = (c - 0.5) * contrast + 0.5;
|
c = (c - 0.5) * contrast + 0.5;
|
||||||
c = mix(vec3(dot(c, vec3(0.2126, 0.7152, 0.0722))), c, saturation);
|
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_glow vs_fullscreen fs_bloom_glow
|
||||||
@program bloom_down vs_fullscreen fs_bloom_down
|
@program bloom_down vs_fullscreen fs_bloom_down
|
||||||
@program bloom_up vs_fullscreen fs_bloom_up
|
@program bloom_up vs_fullscreen fs_bloom_up
|
||||||
|
@program shafts vs_shafts fs_shafts
|
||||||
@program post vs_fullscreen fs_post
|
@program post vs_fullscreen fs_post
|
||||||
|
|||||||
@ -216,7 +216,7 @@ void main() {
|
|||||||
if (texelFetch(sampler2D(palette, palette_smp), ivec2(id, 1), 0).r < 0.5) discard;
|
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;
|
vec3 c = texelFetch(sampler2D(palette, palette_smp), ivec2(id, 0), 0).rgb;
|
||||||
// Tinted rather than darkened: the glass's brightest channel passes fully.
|
// 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
|
@end
|
||||||
|
|
||||||
|
|||||||
@ -19,8 +19,13 @@ triles_init :: (orientations: [ORIENTATION_COUNT]Matrix3) {
|
|||||||
shadow_desc.face_winding = .CCW;
|
shadow_desc.face_winding = .CCW;
|
||||||
triles.shadow_pipeline = sg_make_pipeline(*shadow_desc);
|
triles.shadow_pipeline = sg_make_pipeline(*shadow_desc);
|
||||||
|
|
||||||
glass_desc := pipeline_desc(trile_glass_shadow_shader_desc(sg_query_backend()), .SHADOW, MULTIPLY_BLEND);
|
// Alpha keeps the nearest glass depth, so light shafts start at the window.
|
||||||
glass_desc.colors[0].write_mask = .RGB;
|
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.layout = desc.layout;
|
||||||
glass_desc.index_type = .UINT16;
|
glass_desc.index_type = .UINT16;
|
||||||
glass_desc.cull_mode = .FRONT;
|
glass_desc.cull_mode = .FRONT;
|
||||||
|
|||||||
@ -1,10 +1,14 @@
|
|||||||
BLOOM_LEVELS :: 5;
|
BLOOM_LEVELS :: 5;
|
||||||
BLOOM_SCATTER :: 0.7;
|
BLOOM_SCATTER :: 0.7;
|
||||||
|
SHAFT_STEPS :: 48;
|
||||||
|
SHAFT_MAX_DISTANCE :: 100.0;
|
||||||
|
|
||||||
views_init :: () {
|
views_init :: () {
|
||||||
backend := sg_query_backend();
|
backend := sg_query_backend();
|
||||||
post_desc := pipeline_desc(post_shader_desc(backend), .SCREEN);
|
post_desc := pipeline_desc(post_shader_desc(backend), .SCREEN);
|
||||||
views.post = sg_make_pipeline(*post_desc);
|
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);
|
glow_desc := pipeline_desc(bloom_glow_shader_desc(backend), .POST);
|
||||||
views.bloom_glow = sg_make_pipeline(*glow_desc);
|
views.bloom_glow = sg_make_pipeline(*glow_desc);
|
||||||
down_desc := pipeline_desc(bloom_down_shader_desc(backend), .POST);
|
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_attachments(level.attachments);
|
||||||
sg_destroy_image(level.image);
|
sg_destroy_image(level.image);
|
||||||
}
|
}
|
||||||
|
sg_destroy_attachments(it.shafts.attachments);
|
||||||
|
sg_destroy_image(it.shafts.image);
|
||||||
}
|
}
|
||||||
array_reset_keeping_memory(*views.targets);
|
array_reset_keeping_memory(*views.targets);
|
||||||
views.width = width;
|
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;
|
desc.colors[0].image = level.image;
|
||||||
level.attachments = sg_make_attachments(*desc);
|
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];
|
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);
|
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) {
|
view_composite :: (index: int, env: Environment) {
|
||||||
sg_apply_pipeline(views.post);
|
sg_apply_pipeline(views.post);
|
||||||
params := Post_Params.{
|
params := Post_Params.{
|
||||||
@ -133,6 +171,7 @@ view_composite :: (index: int, env: Environment) {
|
|||||||
bind : sg_bindings;
|
bind : sg_bindings;
|
||||||
bind.images[IMG_view_color] = views.targets[index].main.color;
|
bind.images[IMG_view_color] = views.targets[index].main.color;
|
||||||
bind.images[IMG_bloom] = views.targets[index].bloom[0].image;
|
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_view_smp] = samplers.nearest_clamp;
|
||||||
bind.samplers[SMP_bloom_view_smp] = samplers.linear_clamp;
|
bind.samplers[SMP_bloom_view_smp] = samplers.linear_clamp;
|
||||||
sg_apply_bindings(*bind);
|
sg_apply_bindings(*bind);
|
||||||
@ -152,10 +191,11 @@ View_Target :: struct {
|
|||||||
|
|
||||||
views : struct {
|
views : struct {
|
||||||
post : sg_pipeline;
|
post : sg_pipeline;
|
||||||
|
shafts : sg_pipeline;
|
||||||
bloom_glow : sg_pipeline;
|
bloom_glow : sg_pipeline;
|
||||||
bloom_down : sg_pipeline;
|
bloom_down : sg_pipeline;
|
||||||
bloom_up : 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; };
|
shadows : [..]struct { depth, tint: sg_image; attachments: sg_attachments; };
|
||||||
width : s32;
|
width : s32;
|
||||||
height : s32;
|
height : s32;
|
||||||
|
|||||||
@ -31,6 +31,8 @@ Environment :: struct {
|
|||||||
contrast : float = 1;
|
contrast : float = 1;
|
||||||
saturation : float = 1;
|
saturation : float = 1;
|
||||||
bloom_amount : float = 0.4;
|
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
|
// With cut on, triles marked ROOF or WALL are hidden from the camera around the
|
||||||
|
|||||||
@ -59,6 +59,9 @@ Chunk :: struct {
|
|||||||
occupancy_mask : [32][32]u32;
|
occupancy_mask : [32][32]u32;
|
||||||
// We want to be easily able to identify stairs from the chunk.
|
// We want to be easily able to identify stairs from the chunk.
|
||||||
stairs_mask : [32][32]u32;
|
stairs_mask : [32][32]u32;
|
||||||
|
|
||||||
|
// Foliage grid
|
||||||
|
foliage : [32][32]Foliage_Cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
Occupancy_State :: enum {
|
Occupancy_State :: enum {
|
||||||
@ -351,6 +354,8 @@ World_Json_Config :: struct {
|
|||||||
contrast : float = 1;
|
contrast : float = 1;
|
||||||
saturation : float = 1;
|
saturation : float = 1;
|
||||||
bloomAmount : float = 0.4;
|
bloomAmount : float = 0.4;
|
||||||
|
shaftDensity : float = 0.1;
|
||||||
|
shaftAmount : float = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
World_Json_Chunk :: struct {
|
World_Json_Chunk :: struct {
|
||||||
@ -512,6 +517,8 @@ world_config_to_json :: (conf: *World_Config) -> World_Json_Config {
|
|||||||
jc.contrast = conf.contrast;
|
jc.contrast = conf.contrast;
|
||||||
jc.saturation = conf.saturation;
|
jc.saturation = conf.saturation;
|
||||||
jc.bloomAmount = conf.bloomAmount;
|
jc.bloomAmount = conf.bloomAmount;
|
||||||
|
jc.shaftDensity = conf.shaftDensity;
|
||||||
|
jc.shaftAmount = conf.shaftAmount;
|
||||||
return jc;
|
return jc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -540,6 +547,8 @@ world_config_from_json :: (jc: *World_Json_Config) -> World_Config {
|
|||||||
conf.contrast = jc.contrast;
|
conf.contrast = jc.contrast;
|
||||||
conf.saturation = jc.saturation;
|
conf.saturation = jc.saturation;
|
||||||
conf.bloomAmount = jc.bloomAmount;
|
conf.bloomAmount = jc.bloomAmount;
|
||||||
|
conf.shaftDensity = jc.shaftDensity;
|
||||||
|
conf.shaftAmount = jc.shaftAmount;
|
||||||
return conf;
|
return conf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -759,6 +768,8 @@ World_Config :: struct {
|
|||||||
contrast : float = 1; @Slider,0,3,0.05
|
contrast : float = 1; @Slider,0,3,0.05
|
||||||
saturation : float = 1; @Slider,0,3,0.05
|
saturation : float = 1; @Slider,0,3,0.05
|
||||||
bloomAmount : float = 0.4; @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});
|
sunPosition : Vector3 = #run normalize(Vector3.{0.2, 0.3, 0.4});
|
||||||
sunIntensity : float = 1.0; @Slider,0,4,0.1
|
sunIntensity : float = 1.0; @Slider,0,4,0.1
|
||||||
skyIntensity : float = 0.3; @Slider,0,5,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.contrast = conf.contrast;
|
||||||
env.saturation = conf.saturation;
|
env.saturation = conf.saturation;
|
||||||
env.bloom_amount = conf.bloomAmount;
|
env.bloom_amount = conf.bloomAmount;
|
||||||
|
env.shaft_density = conf.shaftDensity;
|
||||||
|
env.shaft_amount = conf.shaftAmount;
|
||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user