paint: improve ssao

This commit is contained in:
luboslenco
2026-08-25 20:09:09 +02:00
parent 41645f39c5
commit 444d477240
10 changed files with 203 additions and 159 deletions
+17 -49
View File
@@ -1,4 +1,3 @@
#[set(everything)]
const constants: {
dir_inv: float2;
@@ -13,11 +12,10 @@ const tex: tex2d;
#[set(everything)]
const gbuffer0: tex2d;
// const blur_weights: float[] = {
// 0.132572, 0.125472, 0.106373, 0.08078, 0.05495, 0.033482, 0.018275, 0.008934, 0.003912, 0.001535
// };
const discard_threshold: float = 0.95;
const num_taps: float = 4.0;
const blur_sigma: float = 0.125;
const nor_threshold: float = 0.8;
const nor_scale: float = 5.0;
struct vert_in {
pos: float2;
@@ -58,8 +56,6 @@ fun octahedron_wrap(v: float2): float2 {
r.x = 1.0 - r.x;
r.y = 1.0 - r.y;
return r * a;
// return (1.0 - abs(v.yx)) * (float2(v.x >= 0.0 ? 1.0 : -1.0, v.y >= 0.0 ? 1.0 : -1.0));
}
fun get_nor(enc: float2): float3 {
@@ -78,54 +74,26 @@ fun get_nor(enc: float2): float3 {
fun ssao_blur_pass_frag(input: vert_out): float {
var nor: float3 = get_nor(sample_lod(gbuffer0, sampler_linear, input.tex, 0.0).rg);
// var weight: float = blur_weights[0];
var weight: float = 0.132572;
var color: float = sample_lod(tex, sampler_linear, input.tex, 0.0).r * weight;
var weight: float = 1.0;
var color: float = sample_lod(tex, sampler_linear, input.tex, 0.0).r;
for (var i: int = 1; i < 8; i += 1) {
var blur_weight_i: float; // = blur_weights[i];
if (i == 1) {
blur_weight_i = 0.125472;
}
if (i == 2) {
blur_weight_i = 0.106373;
}
if (i == 3) {
blur_weight_i = 0.08078;
}
if (i == 4) {
blur_weight_i = 0.05495;
}
if (i == 5) {
blur_weight_i = 0.033482;
}
if (i == 6) {
blur_weight_i = 0.018275;
}
if (i == 7) {
blur_weight_i = 0.008934;
}
for (var i: int = 1; i <= int(num_taps); i += 1) {
var fi: float = float(i);
var gw: float = exp(0.0 - fi * fi * blur_sigma);
var off: float2 = fi * constants.dir_inv;
var posadd: float = float(i);
var nor2: float3 = get_nor(sample_lod(gbuffer0, sampler_linear, input.tex + posadd * constants.dir_inv, 0.0).rg);
var influence_factor: float = step(discard_threshold, dot(nor2, nor));
var col: float = sample_lod(tex, sampler_linear, input.tex + posadd * constants.dir_inv, 0.0).r;
var w: float = blur_weight_i * influence_factor;
color += col * w;
var nor2: float3 = get_nor(sample_lod(gbuffer0, sampler_linear, input.tex + off, 0.0).rg);
var w: float = gw * clamp((dot(nor2, nor) - nor_threshold) * nor_scale, 0.0, 1.0);
color += sample_lod(tex, sampler_linear, input.tex + off, 0.0).r * w;
weight += w;
nor2 = get_nor(sample_lod(gbuffer0, sampler_linear, input.tex - posadd * constants.dir_inv, 0.0).rg);
influence_factor = step(discard_threshold, dot(nor2, nor));
col = sample_lod(tex, sampler_linear, input.tex - posadd * constants.dir_inv, 0.0).r;
w = blur_weight_i * influence_factor;
color += col * w;
nor2 = get_nor(sample_lod(gbuffer0, sampler_linear, input.tex - off, 0.0).rg);
w = gw * clamp((dot(nor2, nor) - nor_threshold) * nor_scale, 0.0, 1.0);
color += sample_lod(tex, sampler_linear, input.tex - off, 0.0).r * w;
weight += w;
}
color = color / weight;
return color;
return color / weight;
}
#[pipe]
+122 -78
View File
@@ -1,9 +1,11 @@
#[set(everything)]
const constants: {
invP: float4x4;
P: float4x4;
V3: float3x3;
camera_proj: float2;
screen_size_inv: float2;
frame_offset: float;
ssao_strength: float;
};
#[set(everything)]
@@ -15,8 +17,16 @@ const gbufferD: tex2d;
#[set(everything)]
const gbuffer0: tex2d;
const max_steps: float = 30.0;
const ray_step: float = 0.005;
const num_slices: float = 3.0;
const num_steps: float = 5.0;
const radius: float = 0.15;
const min_radius_px: float = 2.0;
const max_radius_frac: float = 0.35;
const falloff_start: float = 0.75;
const ao_power: float = 1.5;
const ao_dim: float = 0.6;
const PI: float = 3.14159265358979;
const HALF_PI: float = 1.57079632679;
struct vert_in {
pos: float2;
@@ -43,60 +53,6 @@ fun ssao_pass_vert(input: vert_in): vert_out {
return output;
}
fun get_projected_coord(hit_coord: float3): float2 {
var projected_coord: float4 = constants.P * float4(hit_coord, 1.0);
projected_coord.xy = projected_coord.xy / projected_coord.w;
projected_coord.xy = projected_coord.xy * 0.5 + 0.5;
projected_coord.y = 1.0 - projected_coord.y;
return projected_coord.xy;
}
fun get_pos_view(view_ray: float3, depth: float): float3 {
var linear_depth: float = constants.camera_proj.y / (constants.camera_proj.x - depth);
return view_ray * linear_depth;
}
fun get_delta_depth(hit_coord: float3): float {
var coord: float2 = get_projected_coord(hit_coord);
if (coord.x < 0.0 || coord.x > 1.0 || coord.y < 0.0 || coord.y > 1.0) {
return 1000.0;
}
var depth: float = sample_lod(gbufferD, sampler_linear, coord, 0.0).r;
var linear_depth: float = constants.camera_proj.y / (constants.camera_proj.x - depth);
return linear_depth - hit_coord.z;
}
fun ray_cast(dir: float3, vpos: float3): float {
var n_dir: float3 = normalize(dir);
var step_vec: float3 = n_dir * ray_step;
var hit_coord: float3 = vpos;
var t: float = 0.0;
var i: int = 0;
while (i < int(max_steps)) {
hit_coord += step_vec;
t += ray_step;
var delta: float = get_delta_depth(hit_coord);
if (delta > 0.0 && delta < 0.2) {
return t;
}
if (t > 2.0) {
return 0.15; // max_radius;
}
i += 1;
}
return 0.15; // max_radius;
}
fun tangent(n: float3): float3 {
var t1: float3 = cross(n, float3(0.0, 0.0, 1.0));
var t2: float3 = cross(n, float3(0.0, 1.0, 0.0));
if (length(t1) > length(t2)) {
return normalize(t1);
}
return normalize(t2);
}
fun octahedron_wrap(v: float2): float2 {
var a: float2;
if (v.x >= 0.0) {
@@ -121,11 +77,7 @@ fun octahedron_wrap(v: float2): float2 {
return r * a;
}
fun ssao_pass_frag(input: vert_out): float {
var g0: float4 = sample_lod(gbuffer0, sampler_linear, input.tex, 0.0);
var d: float = sample_lod(gbufferD, sampler_linear, input.tex, 0.0).r;
var enc: float2 = g0.rg;
fun get_nor(enc: float2): float3 {
var n: float3;
n.z = 1.0 - abs(enc.x) - abs(enc.y);
if (n.z >= 0.0) {
@@ -133,27 +85,119 @@ fun ssao_pass_frag(input: vert_out): float {
n.y = enc.y;
}
else {
var f2: float2 = octahedron_wrap(enc.xy);
var f2: float2 = octahedron_wrap(enc);
n.x = f2.x;
n.y = f2.y;
}
n = normalize(constants.V3 * n);
return normalize(n);
}
var vpos: float3 = get_pos_view(input.view_ray, d);
var o1: float3 = tangent(n);
var o2: float3 = cross(o1, n);
var c1: float3 = 0.5 * (o1 + o2);
var c2: float3 = 0.5 * (o1 - o2);
fun get_linear_depth(d: float): float {
return constants.camera_proj.y / (constants.camera_proj.x - d);
}
var col: float = ray_cast(lerp3(n, o1, 0.5), vpos);
col += ray_cast(lerp3(n, -o1, 0.5), vpos);
col += ray_cast(lerp3(n, o2, 0.5), vpos);
col += ray_cast(lerp3(n, -o2, 0.5), vpos);
col += ray_cast(lerp3(n, c1, 0.5), vpos);
col += ray_cast(lerp3(n, -c1, 0.5), vpos);
col += ray_cast(lerp3(n, c2, 0.5), vpos);
col += ray_cast(lerp3(n, -c2, 0.5), vpos);
return col * 0.5;
fun gradient_noise(x: float, y: float): float {
return frac(52.9829189 * frac(0.06711056 * x + 0.00583715 * y));
}
fun horizon_cos(uv: float2, dndc: float2, ray_c: float3, rx: float2, ry: float2, vpos: float3, view_v: float3, fall_scale: float,
fall_bias: float): float {
if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
return -1.0;
}
var ds: float = sample_lod(gbufferD, sampler_linear, uv, 0.0).r;
if (ds >= 1.0) {
return -1.0;
}
var ray: float3 = float3(ray_c.x + dndc.x * rx.x + dndc.y * ry.x, ray_c.y + dndc.x * rx.y + dndc.y * ry.y, 1.0);
var delta: float3 = ray * get_linear_depth(ds) - vpos;
var d2: float = dot(delta, delta);
var inv_len: float = rsqrt(max(d2, 0.00000001));
var len: float = d2 * inv_len;
var ch: float = dot(delta, view_v) * inv_len;
return lerp(ch, -1.0, clamp(len * fall_scale - fall_bias, 0.0, 1.0));
}
fun ssao_pass_frag(input: vert_out): float {
var ao: float = 1.0;
var d: float = sample_lod(gbufferD, sampler_linear, input.tex, 0.0).r;
if (d < 1.0) {
var p0: float4 = constants.invP * float4(0.0, 0.0, 1.0, 1.0);
var px: float4 = constants.invP * float4(1.0, 0.0, 1.0, 1.0);
var py: float4 = constants.invP * float4(0.0, 1.0, 1.0, 1.0);
var r0: float2 = p0.xy / p0.z;
var rx: float2 = (px.xy / px.z) - r0;
var ry: float2 = (py.xy / py.z) - r0;
var vpos: float3 = input.view_ray * get_linear_depth(d);
var view_v: float3 = normalize(0.0 - vpos);
var n: float3 = normalize(constants.V3 * get_nor(sample_lod(gbuffer0, sampler_linear, input.tex, 0.0).rg));
var screen_w: float = 1.0 / constants.screen_size_inv.x;
var screen_h: float = 1.0 / constants.screen_size_inv.y;
var view_to_px: float = screen_w * 0.5 / (abs(rx.x) * abs(vpos.z));
var radius_px: float = clamp(radius * view_to_px, min_radius_px, screen_h * max_radius_frac);
var eff_radius: float = radius_px / view_to_px;
var fall_scale: float = 1.0 / max(eff_radius * (1.0 - falloff_start), 0.0001);
var fall_bias: float = eff_radius * falloff_start * fall_scale;
var px_x: float = input.tex.x * screen_w;
var px_y: float = input.tex.y * screen_h;
var slice_noise: float = gradient_noise(px_x, px_y) + constants.frame_offset;
var step_noise: float = frac(gradient_noise(px_y, px_x) + constants.frame_offset);
var vis: float = 0.0;
var slice: int = 0;
while (slice < int(num_slices)) {
var phi: float = (float(slice) + slice_noise) * (PI / num_slices);
var cp: float = cos(phi);
var sp: float = sin(phi);
var dir_uv: float2 = float2(cp * constants.screen_size_inv.x, sp * constants.screen_size_inv.y);
var dir_v: float3 = float3(cp, 0.0 - sp, 0.0);
var axis: float3 = normalize(cross(dir_v, view_v));
var proj_n: float3 = n - axis * dot(n, axis);
var proj_len: float = max(length(proj_n), 0.0001);
var cos_n: float = clamp(dot(proj_n, view_v) / proj_len, -1.0, 1.0);
var ortho: float3 = cross(view_v, axis);
var n_ang: float = sign(dot(proj_n, ortho)) * acos(cos_n);
var sin_n: float = sin(n_ang);
var h_pos: float = -1.0;
var h_neg: float = -1.0;
var s: int = 0;
while (s < int(num_steps)) {
var t: float = (float(s) + step_noise) / num_steps;
var dist_px: float = max(t * t * radius_px, min_radius_px);
var off: float2 = dir_uv * dist_px;
var dndc: float2 = float2(off.x * 2.0, off.y * -2.0);
h_pos = max(h_pos, horizon_cos(input.tex + off, dndc, input.view_ray, rx, ry, vpos, view_v, fall_scale, fall_bias));
h_neg = max(h_neg, horizon_cos(input.tex - off, 0.0 - dndc, input.view_ray, rx, ry, vpos, view_v, fall_scale, fall_bias));
s += 1;
}
var ang_pos: float = acos(clamp(h_pos, -1.0, 1.0));
var ang_neg: float = 0.0 - acos(clamp(h_neg, -1.0, 1.0));
ang_pos = n_ang + min(ang_pos - n_ang, HALF_PI);
ang_neg = n_ang + max(ang_neg - n_ang, 0.0 - HALF_PI);
vis += proj_len * 0.25 *
((cos_n - cos(2.0 * ang_neg - n_ang) + 2.0 * ang_neg * sin_n) + (cos_n - cos(2.0 * ang_pos - n_ang) + 2.0 * ang_pos * sin_n));
slice += 1;
}
ao = pow(clamp(vis / num_slices, 0.0, 1.0), ao_power);
}
return ao_dim * clamp(1.0 - (1.0 - ao) * constants.ssao_strength, 0.0, 1.0);
}
#[pipe]
+3 -3
View File
@@ -72,7 +72,7 @@ void config_save() {
json_encode_i32("window_frequency", g_config->window_frequency);
json_encode_f32("window_scale", g_config->window_scale);
json_encode_f32("rp_supersample", g_config->rp_supersample);
json_encode_bool("rp_ssao", g_config->rp_ssao);
json_encode_f32("rp_ssao", g_config->rp_ssao);
json_encode_f32("rp_bloom", g_config->rp_bloom);
json_encode_f32("rp_vignette", g_config->rp_vignette);
json_encode_f32("rp_grain", g_config->rp_grain);
@@ -189,9 +189,9 @@ void config_init() {
g_config->lut_path = "";
g_config->texture_filter = true;
#if defined(IRON_ANDROID) || defined(IRON_IOS)
g_config->rp_ssao = false;
g_config->rp_ssao = 0.0;
#else
g_config->rp_ssao = true;
g_config->rp_ssao = 1.0;
#endif
g_config->rp_supersample = 1.0;
#ifdef IRON_ANDROID
+1
View File
@@ -429,6 +429,7 @@ f32 render_path_base_get_super_sampling();
void render_path_base_commands(void (*draw_commands)(void));
void render_path_base_draw_bloom(char *source, char *target);
void render_path_base_init_ssao();
char *render_path_base_ssao_target();
void render_path_base_draw_ssao();
void render_path_base_draw_deferred_light();
void render_path_base_draw_taa(char *bufa, char *bufb);
+26 -15
View File
@@ -12,12 +12,13 @@ void render_path_base_init() {
void render_path_base_apply_config() {
if (render_path_base_super_sample != g_config->rp_supersample) {
f32 last = render_path_base_super_sample;
render_path_base_super_sample = g_config->rp_supersample;
string_array_t *keys = map_keys(render_path_render_targets);
for (i32 i = 0; i < keys->length; ++i) {
render_target_t *rt = any_map_get(render_path_render_targets, keys->buffer[i]);
if (rt->width == 0) {
rt->scale = render_path_base_super_sample;
rt->scale = rt->scale / last * render_path_base_super_sample;
}
}
array_free(keys);
@@ -190,19 +191,13 @@ void render_path_base_draw_bloom(char *source, char *target) {
}
void render_path_base_init_ssao() {
#if defined(IRON_MACOS) || defined(IRON_IOS) || defined(IRON_ANDROID)
f32 scale = 0.5;
#else
f32 scale = 1.0;
#endif
{
render_target_t *t = render_target_create();
t->name = "singlea";
t->width = 0;
t->height = 0;
t->format = "R8";
t->scale = scale * render_path_base_get_super_sampling();
t->scale = render_path_base_get_super_sampling();
render_path_create_render_target(t);
}
@@ -212,7 +207,17 @@ void render_path_base_init_ssao() {
t->width = 0;
t->height = 0;
t->format = "R8";
t->scale = scale * render_path_base_get_super_sampling();
t->scale = render_path_base_get_super_sampling();
render_path_create_render_target(t);
}
{
render_target_t *t = render_target_create();
t->name = "singlec";
t->width = 0;
t->height = 0;
t->format = "R8";
t->scale = render_path_base_get_super_sampling();
render_path_create_render_target(t);
}
@@ -221,24 +226,30 @@ void render_path_base_init_ssao() {
render_path_load_shader("Scene/ssao_blur_pass/ssao_blur_pass_y");
}
char *render_path_base_ssao_target() {
return scene_camera->frame % 2 == 0 ? "singlea" : "singlec";
}
void render_path_base_draw_ssao() {
bool ssao = g_config->rp_ssao != false && g_context->camera_type == CAMERA_TYPE_PERSPECTIVE;
bool ssao = g_config->rp_ssao > 0.0 && g_context->camera_type == CAMERA_TYPE_PERSPECTIVE;
if (ssao && g_context->ddirty > -6 && _render_path_frame > 0) {
if (any_map_get(render_path_render_targets, "singlea") == NULL) {
render_path_base_init_ssao();
}
render_path_set_target("singlea", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
char *target = render_path_base_ssao_target();
render_path_set_target(target, NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
render_path_bind_target("main", "gbufferD");
render_path_bind_target("gbuffer0", "gbuffer0");
render_path_draw_shader("Scene/ssao_pass/ssao_pass");
render_path_set_target("singleb", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
render_path_bind_target("singlea", "tex");
render_path_bind_target(target, "tex");
render_path_bind_target("gbuffer0", "gbuffer0");
render_path_draw_shader("Scene/ssao_blur_pass/ssao_blur_pass_x");
render_path_set_target("singlea", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
render_path_set_target(target, NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
render_path_bind_target("singleb", "tex");
render_path_bind_target("gbuffer0", "gbuffer0");
render_path_draw_shader("Scene/ssao_blur_pass/ssao_blur_pass_y");
@@ -250,9 +261,9 @@ void render_path_base_draw_deferred_light() {
render_path_bind_target("main", "gbufferD");
render_path_bind_target("gbuffer0", "gbuffer0");
render_path_bind_target("gbuffer1", "gbuffer1");
bool ssao = g_config->rp_ssao != false && g_context->camera_type == CAMERA_TYPE_PERSPECTIVE;
bool ssao = g_config->rp_ssao > 0.0 && g_context->camera_type == CAMERA_TYPE_PERSPECTIVE;
if (ssao && _render_path_frame > 0) {
render_path_bind_target("singlea", "ssaotex");
render_path_bind_target(render_path_base_ssao_target(), "ssaotex");
}
else {
render_path_bind_target("empty_white", "ssaotex");
+1 -1
View File
@@ -87,7 +87,7 @@ void render_path_deferred_init() {
any_map_set(render_path_render_targets, t->name, t);
}
if (g_config->rp_ssao) {
if (g_config->rp_ssao > 0.0) {
render_path_base_init_ssao();
}
+23 -9
View File
@@ -3401,7 +3401,7 @@ scene_t *startup_get_scene(void) {
ve->data = "float2";
sc->vertex_elements->buffer[0] = ve;
}
sc->constants = (shader_const_t_array_t *)any_array_create(4);
sc->constants = (shader_const_t_array_t *)any_array_create(6);
{
shader_const_t *c = (shader_const_t *)calloc(1, sizeof(shader_const_t));
c->name = "invP";
@@ -3409,27 +3409,41 @@ scene_t *startup_get_scene(void) {
c->link = "_inv_proj_matrix";
sc->constants->buffer[0] = c;
}
{
shader_const_t *c = (shader_const_t *)calloc(1, sizeof(shader_const_t));
c->name = "P";
c->type = "float4x4";
c->link = "_proj_matrix";
sc->constants->buffer[1] = c;
}
{
shader_const_t *c = (shader_const_t *)calloc(1, sizeof(shader_const_t));
c->name = "V3";
c->type = "float3x3";
c->link = "_view_matrix3";
sc->constants->buffer[2] = c;
sc->constants->buffer[1] = c;
}
{
shader_const_t *c = (shader_const_t *)calloc(1, sizeof(shader_const_t));
c->name = "camera_proj";
c->type = "float2";
c->link = "_camera_plane_proj";
sc->constants->buffer[2] = c;
}
{
shader_const_t *c = (shader_const_t *)calloc(1, sizeof(shader_const_t));
c->name = "screen_size_inv";
c->type = "float2";
c->link = "_screen_size_inv";
sc->constants->buffer[3] = c;
}
{
shader_const_t *c = (shader_const_t *)calloc(1, sizeof(shader_const_t));
c->name = "frame_offset";
c->type = "float";
c->link = "_ssao_frame";
sc->constants->buffer[4] = c;
}
{
shader_const_t *c = (shader_const_t *)calloc(1, sizeof(shader_const_t));
c->name = "ssao_strength";
c->type = "float";
c->link = "_ssao_strength";
sc->constants->buffer[5] = c;
}
sc->texture_units = (tex_unit_t_array_t *)any_array_create(2);
{
tex_unit_t *tu = (tex_unit_t *)calloc(1, sizeof(tex_unit_t));
+1 -1
View File
@@ -94,7 +94,7 @@ typedef struct config {
f32 window_scale;
// Render path
f32 rp_supersample;
bool rp_ssao;
f32 rp_ssao;
f32 rp_bloom;
f32 rp_vignette;
f32 rp_grain;
+3 -3
View File
@@ -648,10 +648,10 @@ void box_preferences_viewport_tab() {
if (g_config->render_mode == RENDER_MODE_DEFERRED) {
ui_handle_t *h_ssao = ui_handle(__ID__);
h_ssao->b = g_config->rp_ssao;
g_config->rp_ssao = ui_check(h_ssao, tr("SSAO"), "");
h_ssao->f = g_config->rp_ssao;
g_config->rp_ssao = ui_slider(h_ssao, tr("SSAO"), 0.0, 1.0, true, 100.0, true, UI_ALIGN_RIGHT, true);
if (h_ssao->changed) {
config_apply();
g_context->ddirty = 2;
}
ui_handle_t *h_bloom = ui_handle(__ID__);
+6
View File
@@ -143,6 +143,12 @@ f32 uniforms_ext_f32_link(object_t *object, shader_data_t *mat, char *link) {
else if (string_equals(link, "_taa_blend")) {
return scene_camera->frame == 0 ? 0.0 : 0.5;
}
else if (string_equals(link, "_ssao_strength")) {
return g_config->rp_ssao;
}
else if (string_equals(link, "_ssao_frame")) {
return scene_camera->frame % 2 == 0 ? 0.0 : 0.5;
}
if (parser_material_script_links != NULL) {
string_array_t *keys = map_keys(parser_material_script_links);
bool found = keys->length > 0;