improve lighting and trile editing

This commit is contained in:
Tuomas Katajisto 2026-02-27 20:20:02 +02:00
parent 9098550523
commit 3a7f19c89d
13 changed files with 5555 additions and 5066 deletions

View File

@ -277,7 +277,11 @@ add_resources_from_pack :: (pack: *Loaded_Pack) {
}
free_resources_from_pack :: (pack: *Loaded_Pack) {
for pack.textures sg_destroy_image(it);
for *pack.audio array_free(it.data);
table_reset(*pack.textures);
table_reset(*pack.audio);
table_reset(*pack.animations);
}
find_pack_by_name :: (name: string) -> (bool, Loaded_Pack) {

View File

@ -77,6 +77,8 @@ 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();

View File

@ -540,6 +540,7 @@ gen_rdm :: (quality: s32, include_water: bool, world: World) {
rdm_save_image_to_file :: (path: string, data: *float, width: s32, height: s32) {
#if OS != .WASM {
file :: #import "File";
builder: String_Builder;
header := RDM_File_Header.{
magic = RDM_FILE_MAGIC,
@ -549,7 +550,7 @@ rdm_save_image_to_file :: (path: string, data: *float, width: s32, height: s32)
write_bytes(*builder, *header, size_of(RDM_File_Header));
pixel_bytes := cast(s64) width * cast(s64) height * 4 * size_of(float);
write_bytes(*builder, data, pixel_bytes);
rdm_file.write_entire_file(path, builder_to_string(*builder));
file.write_entire_file(path, builder_to_string(*builder));
}
}

View File

@ -376,4 +376,33 @@ draw_trile_editor_ui :: (theme: *GR.Overall_Theme) {
}
draw_picker(theme);
// View mode buttons — bottom center of the 3D viewport.
btn_w := 11.0 * vw;
btn_h := cast(float) ui_h(4, 0);
bar_w := btn_w * 4.0;
bar_x := 100.0 * vw / 2.0 - bar_w / 2.0;
bar_y := 100.0 * vh - btn_h - 1.5 * vh;
bar_r : GR.Rect;
bar_r.x = bar_x; bar_r.y = bar_y; bar_r.w = bar_w; bar_r.h = btn_h;
ui_add_mouse_occluder(bar_r);
r2 : GR.Rect;
r2.x = bar_x; r2.y = bar_y; r2.w = btn_w; r2.h = btn_h;
if GR.button(r2, "Lit", *t_button_selectable(theme, trixel_view_mode == 0)) {
trixel_view_mode = 0;
}
r2.x += btn_w;
if GR.button(r2, "Normals", *t_button_selectable(theme, trixel_view_mode == 1)) {
trixel_view_mode = 1;
}
r2.x += btn_w;
if GR.button(r2, "Albedo", *t_button_selectable(theme, trixel_view_mode == 2)) {
trixel_view_mode = 2;
}
r2.x += btn_w;
if GR.button(r2, "Mixed", *t_button_selectable(theme, trixel_view_mode == 3)) {
trixel_view_mode = 3;
}
}

View File

@ -1,6 +1,8 @@
camera: Camera;
trixel_count : s32 = 0;
trixel_count : s32 = 0;
trixel_view_mode : s32 = 0;
bypass_postprocess : bool = false;
trile_offsets : [..]s32;
current_trile_offset_index : s32 = 0;
@ -64,8 +66,11 @@ backend_update_trixels :: (cmd: Render_Command_Update_Trixels) {
trixels[trixel_count].pos.x = x * (1.0 / 16.0) + TRIXEL_SIZE_HALF;
trixels[trixel_count].pos.y = y * (1.0 / 16.0) + TRIXEL_SIZE_HALF;
trixels[trixel_count].pos.z = z * (1.0 / 16.0) + TRIXEL_SIZE_HALF;
trixels[trixel_count].pos.w = 1.0;
trixel_color := cmd.trile.trixels[x][y][z].material.color * (cmd.colMultipliers.*)[x][y][z];
cm := (cmd.colMultipliers.*)[x][y][z];
// Encode highlight state in inst.w: non-identity colorMul means highlighted.
// 0=normal, 1=hovered, (future: 2=selected, 3=in-brush).
trixels[trixel_count].pos.w = ifx (cm.x == 1.0 && cm.y == 1.0 && cm.z == 1.0) then 0.0 else 1.0;
trixel_color := cmd.trile.trixels[x][y][z].material.color * cm;
trixels[trixel_count].col = .{trixel_color.x, trixel_color.y, trixel_color.z, material_encode_to_float(cmd.trile.trixels[x][y][z].material)};
trixel_count += 1;
@ -91,10 +96,14 @@ backend_draw_trixels :: () {
wc : *World_Config = *(World_Config.{});
world_config_to_shader_type(wc, *world_conf);
fs_params : Trixel_Fs_Params;
fs_params.view_mode = trixel_view_mode;
sg_apply_pipeline(gPipelines.trixel.pipeline);
sg_apply_bindings(*gPipelines.trixel.bind);
sg_apply_uniforms(UB_vs_params, *(sg_range.{ ptr = *vs_params, size = size_of(type_of(vs_params)) }));
sg_apply_uniforms(UB_trixel_world_config, *(sg_range.{ptr = *world_conf, size = size_of(type_of(world_conf))}));
sg_apply_uniforms(UB_trixel_fs_params, *(sg_range.{ ptr = *fs_params, size = size_of(type_of(fs_params)) }));
sg_draw(0, 36, trixel_count);
}
@ -210,6 +219,7 @@ backend_draw_trile_positions_main :: (trile : string, amount : s32, worldConf: *
fs_params.rdm_diff_scale = lc.rdm_diff_scale;
fs_params.rdm_spec_scale = lc.rdm_spec_scale;
fs_params.ambient_color = lc.ambient_color.component;
fs_params.rdm_tint = lc.rdm_tint.component;
sg_apply_bindings(*bindings);
sg_apply_uniforms(UB_trile_fs_params, *(sg_range.{ ptr = *fs_params, size = size_of(type_of(fs_params)) }));
@ -241,6 +251,7 @@ backend_draw_ground :: (wc: *World_Config) {
world_conf : Plane_World_Config;
plane_data : Plane_Data;
w, h := get_render_size();
plane_data.mvp_shadow = shadow_mvp.floats;
plane_data.screen_w = w;
plane_data.screen_h = h;
plane_data.cameraPosition = camera.position.component;
@ -249,9 +260,6 @@ backend_draw_ground :: (wc: *World_Config) {
world_config_to_shader_type(wc, *world_conf);
fs_params : Plane_Fs_Params;
fs_params.mvp_shadow = shadow_mvp.floats;
fs_params.is_reflection = ifx in_reflection_pass then cast(s32)1 else cast(s32)0;
vs_params.mvp = mvp.floats;
vs_params.planeHeight = wc.planeHeight;
sg_apply_pipeline(gPipelines.plane.pipeline);
@ -259,7 +267,6 @@ backend_draw_ground :: (wc: *World_Config) {
gPipelines.plane.bind.images[1] = g_shadowmap;
sg_apply_bindings(*gPipelines.plane.bind);
sg_apply_uniforms(UB_plane_vs_params, *(sg_range.{ ptr = *vs_params, size = size_of(type_of(vs_params)) }));
sg_apply_uniforms(UB_plane_fs_params, *(sg_range.{ ptr = *fs_params, size = size_of(type_of(fs_params)) }));
sg_apply_uniforms(UB_plane_world_config, *(sg_range.{ptr = *world_conf, size = size_of(type_of(world_conf))}));
sg_apply_uniforms(UB_plane_data, *(sg_range.{ptr = *plane_data, size = size_of(type_of(plane_data))}));
sg_draw(0, 6, 2);
@ -436,21 +443,28 @@ backend_process_command_buckets :: () {
end_frame_profiling_group("Main pass");
start_frame_profiling_group("Postprocess pass");
bloom_process();
dof_process();
if !bypass_postprocess {
bloom_process();
dof_process();
}
end_frame_profiling_group("Postprocess pass");
start_frame_profiling_group("Final pass");
// Begin drawing to swapchain
sg_begin_pass(*(sg_pass.{ action = state.pass_action_clear, swapchain = cast,force(sg_swapchain) sglue_swapchain() }));
// Draw the render texture and do post processing:
sg_apply_pipeline(gPipelines.postprocess.pipeline);
gPipelines.postprocess.bind.images[0] = g_postprocess_a;
gPipelines.postprocess.bind.images[0] = ifx bypass_postprocess then g_rendertex else g_postprocess_a;
gPipelines.postprocess.bind.images[1] = LUT_list[g_current_lut_texture_index].image;
sg_apply_bindings(*gPipelines.postprocess.bind);
post_process_config_uniform : Post_Process_Config;
fill_uniform_with_engine_data(*post_process_config_uniform , *current_post_process);
if bypass_postprocess {
neutral : Post_Process;
fill_uniform_with_engine_data(*post_process_config_uniform, *neutral);
} else {
fill_uniform_with_engine_data(*post_process_config_uniform, *current_post_process);
}
sg_apply_uniforms(UB_post_process_config, *(sg_range.{ ptr = *post_process_config_uniform, size = size_of(type_of(post_process_config_uniform)) }));
sg_draw(0, 6, 1);

View File

@ -1,28 +1,28 @@
Post_Process :: struct {
exposure : float = 0.0; @Slider,0,3,0.1;
exposure : float = 1.0; @Slider,0,3,0.1;
contrast : float = 1.0; @Slider,0,6,0.1;
saturation : float = 1.0; @Slider,0.0,2.0,0.1;
gamma : float = 2.2; @Slider,0.3,3.0,0.1;
tonemap : float = 1.0; @Slider,0,1,1;
ssao : float = 1.0; @Slider,0,5,0.1;
gamma : float = 1.0; @Slider,0.3,3.0,0.1;
tonemap : float = 0.0; @Slider,0,1,1;
ssao : float = 0.0; @Slider,0,5,0.1;
ssao_size : s32 = 1; @Slider,0,5,1;
dilate_separation : float = 1.0; @Slider,0,6,0.1;
dilate_size : s32 = 2; @Slider,0,10,1;
dilate_size : s32 = 0; @Slider,0,10,1;
dilate_min : float = 0.1; @Slider,0,1,0.1;
dilate_max : float = 0.3; @Slider,0,1,0.1;
dof_blur_size : s32 = 2; @Slider,0,10,1;
dof_blur_size : s32 = 0; @Slider,0,10,1;
dof_min : float = 1.0; @Slider,0,10,1;
dof_max : float = 3.0; @Slider,0,50,1;
dof_point : float = 5.0; @Slider,0,30,1;
bloom_size : s32 = 5; @Slider,0,10,1;
bloom_separation : float = 3.0; @Slider,0,10,1;
bloom_treshold : float = 0.4; @Slider,0,1,0.1;
bloom_amount : float = 1.0; @Slider,0,5,0.1;
vignette_intensity: float = 0.5; @Slider,0,1,0.1;
vignette_radius: float = 0.5; @Slider,0,1,0.1;
scanlines_intensity: float = 0.1; @Slider,0,1,0.1;
scanlines_density: float = 1; @Slider,0,10,0.1;
bloom_amount : float = 0.0; @Slider,0,5,0.1;
vignette_intensity: float = 0.0; @Slider,0,1,0.1;
vignette_radius : float = 0.5; @Slider,0,1,0.1;
scanlines_intensity: float = 0.0; @Slider,0,1,0.1;
scanlines_density : float = 1.0; @Slider,0,10,0.1;
chromatic_aberration_intensity: float = 0.0; @Slider,0,0.05,0.001;
film_grain_intensity: float = 0.0; @Slider,0,0.5,0.001;
barrel_distortion_intensity: float = 0.0; @Slider,-2,2,0.1;
@ -40,6 +40,7 @@ Lighting_Config :: struct {
rdm_diff_scale : float = 1.0; @Slider,0,3,0.1
rdm_spec_scale : float = 1.0; @Slider,0,3,0.1
ambient_color : Vector3 = .{0.3,0.3,0.4}; @Color
rdm_tint : Vector3 = .{1.05,1.0,0.9}; @Color
}
current_lighting_config : Lighting_Config;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -29,12 +29,6 @@ in flat int idx;
in float depth;
out vec4 frag_color;
// Uniform bindings from the original shader
layout(binding=3) uniform plane_fs_params {
mat4 mvp_shadow;
int is_reflection;
};
layout(binding=1) uniform plane_world_config {
vec3 skyBase;
vec3 skyTop;
@ -54,6 +48,7 @@ layout(binding=1) uniform plane_world_config {
};
layout(binding=2) uniform plane_data {
mat4 mvp_shadow;
int screen_w;
int screen_h;
int is_reflection_pass;
@ -69,7 +64,7 @@ layout(binding = 2) uniform texture2D normal_map;
// Sampler bindings
layout(binding = 0) uniform sampler refsmp;
layout(binding = 1) uniform sampler shadowsmp;
layout(binding = 1) uniform sampler plane_shadowsmp;
layout(binding = 2) uniform sampler normalsmp;
vec3 fresnelSchlick(float cosTheta) {
@ -120,35 +115,37 @@ void main() {
vec3 normal3 = texture(sampler2D(normal_map, normalsmp), uv3).xzy * 2.0 - 1.0;
vec3 normal4 = texture(sampler2D(normal_map, normalsmp), uv4).xzy * 2.0 - 1.0;
// vec3 normal = normalize(vec3(0.0, 1.0, 0.0));
vec3 normal = normalize(normal1 + normal2 + normal3 + normal4);
vec3 view_dir = normalize(cameraPosition - pos.xyz);
vec3 light_dir = normalize(sunPosition);
vec3 halfway_dir = normalize(light_dir + view_dir);
float shadow_factor = 1.0; // shadowmap to be implemented
vec3 base_water_color = waterColor;
float diffuse = (dot(normal, light_dir)) + 0.000001 * is_reflection * shininess;
float diffuse = max(0.0, dot(normal, light_dir));
float spec = pow(max(0.0, dot(halfway_dir, normal)), 32);
float fresnel = min(1.0, fresnelSchlick(dot(view_dir, vec3(0.0, 1.0, 0.0))).x + 0.3);
vec3 refracted_color = base_water_color * diffuse * sunLightColor * sunIntensity;
vec3 specular_highlight = sunLightColor * sunIntensity * spec;
vec4 shadow_proj_pos = mvp_shadow * vec4(pos.xyz, 1.0);
vec3 shadow_pos = shadow_proj_pos.xyz / shadow_proj_pos.w;
shadow_pos = shadow_pos * 0.5 + 0.5;
shadow_pos.z -= 0.001;
float shadowp = texture(sampler2DShadow(shadow, plane_shadowsmp), shadow_pos);
vec3 refracted_color = waterColor * diffuse * sunLightColor * sunIntensity * shadowp;
vec3 specular_highlight = sunLightColor * sunIntensity * spec * shadowp;
vec2 screen_uv = gl_FragCoord.xy / vec2(screen_w, screen_h);
screen_uv.y = 1.0 - screen_uv.y;
vec3 reflected_color = texture(sampler2D(reftex, refsmp), screen_uv).rgb;
vec3 surface_color = mix(refracted_color, reflected_color, min(1.0, fresnel * 1.0));
vec3 final_color = reflected_color + specular_highlight * 0.2 + diffuse * 0.2 + 0.00001 * surface_color;
// vec3 final_color = (surface_color + specular_highlight) * shadow_factor;
float refraction_alpha = 0.4;
float reflection_alpha = 1.0;
float alpha = mix(refraction_alpha, reflection_alpha, fresnel);
// float alpha = 1.0;
vec2 distortion = normal.xz * 0.005;
vec3 reflected_color = texture(sampler2D(reftex, refsmp), screen_uv + distortion).rgb;
vec3 surface_color = mix(refracted_color, reflected_color, fresnel);
vec3 final_color = surface_color + specular_highlight;
float alpha = mix(0.3, 0.5, fresnel);
vec3 fog = skyIntensity * sky(normalize(pos.xyz), sunPosition);
float fogFactor = smoothstep(750.0, 1000.0, length(pos.xz));
frag_color = vec4(mix(final_color, fog, fogFactor), mix(alpha, 1.0, fogFactor));
} else { // Deep water plane

View File

@ -74,6 +74,7 @@ layout(binding=3) uniform trile_fs_params {
float rdm_diff_scale;
float rdm_spec_scale;
vec3 ambient_color;
vec3 rdm_tint;
};
layout(binding = 0) uniform texture2D triletex;
@ -518,12 +519,18 @@ void main() {
// Indirect specular
vec3 indirectSpec = sample_rdm(N, -cv,
hemispherePos, vpos - hemispherePos, roughnessInt, local);
hemispherePos, vpos - hemispherePos, roughnessInt, local) * rdm_tint;
// For metallic surfaces: desaturate the reflection so Frough (which uses albedo
// as F0 for metals) applies the metal tint cleanly without double-tinting.
float specLum = dot(indirectSpec, vec3(0.2126, 0.7152, 0.0722));
indirectSpec = mix(indirectSpec, vec3(specLum), metallic);
vec2 envBRDF = texture(sampler2D(brdf_lut, rdmsmp), vec2(max(dot(N, V), 0.0), roughness)).rg;
light += indirectSpec * (Frough * envBRDF.x + envBRDF.y) * rdm_spec_scale;
// Indirect diffuse (interpolated from neighbor probes)
vec3 indirectDiff = sample_rdm_diff(N, vpos - hemispherePos, local);
vec3 indirectDiff = sample_rdm_diff(N, vpos - hemispherePos, local) * rdm_tint;
vec3 kDiff = 1.0 - Frough;
kDiff *= 1.0 - metallic;
light += (kDiff * indirectDiff / PI * albedo) * ssao_sample * rdm_diff_scale;

View File

@ -14,6 +14,7 @@ out vec4 color;
out vec4 fnormal;
out vec4 pos;
out vec3 cam;
out float vtrixel_state;
void main() {
vec3 instancepos = inst.xyz;
@ -22,11 +23,16 @@ void main() {
color = inst_col;
pos = gl_Position;
cam = camera;
vtrixel_state = inst.w;
}
@end
@fs fs_trixel
layout(binding=2) uniform trixel_fs_params {
int view_mode;
};
layout(binding=1) uniform trixel_world_config {
vec3 skyBase;
vec3 skyTop;
@ -52,6 +58,7 @@ in vec4 color;
in vec4 fnormal;
in vec4 pos;
in vec3 cam;
in float vtrixel_state;
out vec4 frag_color;
const float PI = 3.1412854;
@ -123,7 +130,28 @@ void main() {
light += (kD * albedo / PI + specular) * NdotL * vec3(1.0, 1.0, 1.0);
frag_color = vec4(light + emissive, 1.0);
if (view_mode == 1) {
// Normals: map face normals to RGB so each axis gets a distinct color.
frag_color = vec4(N * 0.5 + 0.5, 1.0);
} else if (view_mode == 2) {
// Albedo: simple diffuse shading, no specular/roughness/metallic eval.
float diffuse = max(dot(N, L), 0.0) * 0.6 + 0.4;
frag_color = vec4(albedo * diffuse + emissive, 1.0);
} else if (view_mode == 3) {
// Normal+Albedo: normal color used as a per-face tint on the albedo.
// Shows paint color and face orientation simultaneously.
vec3 normal_tint = N * 0.5 + 0.5;
float diffuse = max(dot(N, L), 0.0) * 0.4 + 0.6;
frag_color = vec4(albedo * normal_tint * diffuse + emissive, 1.0);
} else {
frag_color = vec4(light + emissive, 1.0);
}
// Overlay highlight for hovered / selected / brush-radius trixels in all view modes.
// State is encoded per-instance in inst.w: 0=normal, 1=hovered, 2=selected, 3=in-brush.
if (vtrixel_state > 0.5) {
frag_color.rgb = mix(frag_color.rgb, vec3(1.0, 1.0, 0.5), 0.5);
}
}
@end

View File

@ -47,6 +47,7 @@ set_trile_gfx :: (name: string, gfx: Trile_GFX, skip_preexist_check: bool = fals
if success {
sg_destroy_buffer(old_gfx.vertex_buffer);
sg_destroy_buffer(old_gfx.normal_buffer);
sg_destroy_buffer(old_gfx.centre_buffer);
sg_destroy_image(old_gfx.trixel_colors);
array_reset(*old_gfx.vertices);
print("Destroyed old GFX buffers for trile: %\n", name);