initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled

This commit is contained in:
2025-09-16 20:46:46 -04:00
commit 9d30169a8d
13378 changed files with 7050105 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
if "RD_GLSL" in env["BUILDERS"]:
# find all include files
gl_include_files = [str(f) for f in Glob("*_inc.glsl")] + [str(f) for f in Glob("../*_inc.glsl")]
# find all shader code(all glsl files excluding our include files)
glsl_files = [str(f) for f in Glob("*.glsl") if str(f) not in gl_include_files]
# make sure we recompile shaders if include files change
env.Depends([f + ".gen.h" for f in glsl_files], gl_include_files + ["#glsl_builders.py"])
# compile shaders
for glsl_file in glsl_files:
env.RD_GLSL(glsl_file)

View File

@@ -0,0 +1,788 @@
#[compute]
#version 450
#VERSION_DEFINES
#ifdef SAMPLE_VOXEL_GI_NEAREST
#extension GL_EXT_samplerless_texture_functions : enable
#endif
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
#define M_PI 3.141592
/* Specialization Constants (Toggles) */
layout(constant_id = 0) const bool sc_half_res = false;
layout(constant_id = 1) const bool sc_use_full_projection_matrix = false;
layout(constant_id = 2) const bool sc_use_vrs = false;
#define SDFGI_MAX_CASCADES 8
//set 0 for SDFGI and render buffers
layout(set = 0, binding = 1) uniform texture3D sdf_cascades[SDFGI_MAX_CASCADES];
layout(set = 0, binding = 2) uniform texture3D light_cascades[SDFGI_MAX_CASCADES];
layout(set = 0, binding = 3) uniform texture3D aniso0_cascades[SDFGI_MAX_CASCADES];
layout(set = 0, binding = 4) uniform texture3D aniso1_cascades[SDFGI_MAX_CASCADES];
layout(set = 0, binding = 5) uniform texture3D occlusion_texture;
layout(set = 0, binding = 6) uniform sampler linear_sampler;
layout(set = 0, binding = 7) uniform sampler linear_sampler_with_mipmaps;
struct ProbeCascadeData {
vec3 position;
float to_probe;
ivec3 probe_world_offset;
float to_cell; // 1/bounds * grid_size
vec3 pad;
float exposure_normalization;
};
layout(rgba16f, set = 0, binding = 9) uniform restrict writeonly image2D ambient_buffer;
layout(rgba16f, set = 0, binding = 10) uniform restrict writeonly image2D reflection_buffer;
layout(set = 0, binding = 11) uniform texture2DArray lightprobe_texture;
layout(set = 0, binding = 12) uniform texture2D depth_buffer;
layout(set = 0, binding = 13) uniform texture2D normal_roughness_buffer;
layout(set = 0, binding = 14) uniform utexture2D voxel_gi_buffer;
layout(set = 0, binding = 15, std140) uniform SDFGI {
vec3 grid_size;
uint max_cascades;
bool use_occlusion;
int probe_axis_size;
float probe_to_uvw;
float normal_bias;
vec3 lightprobe_tex_pixel_size;
float energy;
vec3 lightprobe_uv_offset;
float y_mult;
vec3 occlusion_clamp;
uint pad3;
vec3 occlusion_renormalize;
uint pad4;
vec3 cascade_probe_size;
uint pad5;
ProbeCascadeData cascades[SDFGI_MAX_CASCADES];
}
sdfgi;
#define MAX_VOXEL_GI_INSTANCES 8
struct VoxelGIData {
mat4 xform; // 64 - 64
vec3 bounds; // 12 - 76
float dynamic_range; // 4 - 80
float bias; // 4 - 84
float normal_bias; // 4 - 88
bool blend_ambient; // 4 - 92
uint mipmaps; // 4 - 96
vec3 pad; // 12 - 108
float exposure_normalization; // 4 - 112
};
layout(set = 0, binding = 16, std140) uniform VoxelGIs {
VoxelGIData data[MAX_VOXEL_GI_INSTANCES];
}
voxel_gi_instances;
layout(set = 0, binding = 17) uniform texture3D voxel_gi_textures[MAX_VOXEL_GI_INSTANCES];
layout(set = 0, binding = 18, std140) uniform SceneData {
mat4x4 inv_projection[2];
mat4x4 cam_transform;
vec4 eye_offset[2];
ivec2 screen_size;
float pad1;
float pad2;
}
scene_data;
#ifdef USE_VRS
layout(r8ui, set = 0, binding = 19) uniform restrict readonly uimage2D vrs_buffer;
#endif
layout(push_constant, std430) uniform Params {
uint max_voxel_gi_instances;
bool high_quality_vct;
bool orthogonal;
uint view_index;
vec4 proj_info;
float z_near;
float z_far;
float pad2;
float pad3;
}
params;
vec2 octahedron_wrap(vec2 v) {
vec2 signVal;
signVal.x = v.x >= 0.0 ? 1.0 : -1.0;
signVal.y = v.y >= 0.0 ? 1.0 : -1.0;
return (1.0 - abs(v.yx)) * signVal;
}
vec2 octahedron_encode(vec3 n) {
// https://twitter.com/Stubbesaurus/status/937994790553227264
n /= (abs(n.x) + abs(n.y) + abs(n.z));
n.xy = n.z >= 0.0 ? n.xy : octahedron_wrap(n.xy);
n.xy = n.xy * 0.5 + 0.5;
return n.xy;
}
vec4 blend_color(vec4 src, vec4 dst) {
vec4 res;
float sa = 1.0 - src.a;
res.a = dst.a * sa + src.a;
if (res.a == 0.0) {
res.rgb = vec3(0);
} else {
res.rgb = (dst.rgb * dst.a * sa + src.rgb * src.a) / res.a;
}
return res;
}
vec3 reconstruct_position(ivec2 screen_pos) {
if (sc_use_full_projection_matrix) {
vec4 pos;
pos.xy = (2.0 * vec2(screen_pos) / vec2(scene_data.screen_size)) - 1.0;
pos.z = texelFetch(sampler2D(depth_buffer, linear_sampler), screen_pos, 0).r * 2.0 - 1.0;
pos.w = 1.0;
pos = scene_data.inv_projection[params.view_index] * pos;
return pos.xyz / pos.w;
} else {
vec3 pos;
pos.z = texelFetch(sampler2D(depth_buffer, linear_sampler), screen_pos, 0).r;
pos.z = pos.z * 2.0 - 1.0;
if (params.orthogonal) {
pos.z = -(pos.z * (params.z_far - params.z_near) - (params.z_far + params.z_near)) / 2.0;
} else {
pos.z = 2.0 * params.z_near * params.z_far / (params.z_far + params.z_near + pos.z * (params.z_far - params.z_near));
}
pos.z = -pos.z;
pos.xy = vec2(screen_pos) * params.proj_info.xy + params.proj_info.zw;
if (!params.orthogonal) {
pos.xy *= pos.z;
}
return pos;
}
}
void sdfvoxel_gi_process(uint cascade, vec3 cascade_pos, vec3 cam_pos, vec3 cam_normal, vec3 cam_specular_normal, float roughness, out vec3 diffuse_light, out vec3 specular_light) {
cascade_pos += cam_normal * sdfgi.normal_bias;
vec3 base_pos = floor(cascade_pos);
//cascade_pos += mix(vec3(0.0),vec3(0.01),lessThan(abs(cascade_pos-base_pos),vec3(0.01))) * cam_normal;
ivec3 probe_base_pos = ivec3(base_pos);
vec4 diffuse_accum = vec4(0.0);
vec3 specular_accum;
ivec3 tex_pos = ivec3(probe_base_pos.xy, int(cascade));
tex_pos.x += probe_base_pos.z * sdfgi.probe_axis_size;
tex_pos.xy = tex_pos.xy * (SDFGI_OCT_SIZE + 2) + ivec2(1);
vec3 diffuse_posf = (vec3(tex_pos) + vec3(octahedron_encode(cam_normal) * float(SDFGI_OCT_SIZE), 0.0)) * sdfgi.lightprobe_tex_pixel_size;
vec3 specular_posf = (vec3(tex_pos) + vec3(octahedron_encode(cam_specular_normal) * float(SDFGI_OCT_SIZE), 0.0)) * sdfgi.lightprobe_tex_pixel_size;
specular_accum = vec3(0.0);
vec4 light_accum = vec4(0.0);
float weight_accum = 0.0;
for (uint j = 0; j < 8; j++) {
ivec3 offset = (ivec3(j) >> ivec3(0, 1, 2)) & ivec3(1, 1, 1);
ivec3 probe_posi = probe_base_pos;
probe_posi += offset;
// Compute weight
vec3 probe_pos = vec3(probe_posi);
vec3 probe_to_pos = cascade_pos - probe_pos;
vec3 probe_dir = normalize(-probe_to_pos);
vec3 trilinear = vec3(1.0) - abs(probe_to_pos);
float weight = trilinear.x * trilinear.y * trilinear.z * max(0.005, dot(cam_normal, probe_dir));
// Compute lightprobe occlusion
if (sdfgi.use_occlusion) {
ivec3 occ_indexv = abs((sdfgi.cascades[cascade].probe_world_offset + probe_posi) & ivec3(1, 1, 1)) * ivec3(1, 2, 4);
vec4 occ_mask = mix(vec4(0.0), vec4(1.0), equal(ivec4(occ_indexv.x | occ_indexv.y), ivec4(0, 1, 2, 3)));
vec3 occ_pos = clamp(cascade_pos, probe_pos - sdfgi.occlusion_clamp, probe_pos + sdfgi.occlusion_clamp) * sdfgi.probe_to_uvw;
occ_pos.z += float(cascade);
if (occ_indexv.z != 0) { //z bit is on, means index is >=4, so make it switch to the other half of textures
occ_pos.x += 1.0;
}
occ_pos *= sdfgi.occlusion_renormalize;
float occlusion = dot(textureLod(sampler3D(occlusion_texture, linear_sampler), occ_pos, 0.0), occ_mask);
weight *= max(occlusion, 0.01);
}
// Compute lightprobe texture position
vec3 diffuse;
vec3 pos_uvw = diffuse_posf;
pos_uvw.xy += vec2(offset.xy) * sdfgi.lightprobe_uv_offset.xy;
pos_uvw.x += float(offset.z) * sdfgi.lightprobe_uv_offset.z;
diffuse = textureLod(sampler2DArray(lightprobe_texture, linear_sampler), pos_uvw, 0.0).rgb;
diffuse_accum += vec4(diffuse * weight * sdfgi.cascades[cascade].exposure_normalization, weight);
{
vec3 specular = vec3(0.0);
vec3 pos_uvw = specular_posf;
pos_uvw.xy += vec2(offset.xy) * sdfgi.lightprobe_uv_offset.xy;
pos_uvw.x += float(offset.z) * sdfgi.lightprobe_uv_offset.z;
if (roughness < 0.99) {
specular = textureLod(sampler2DArray(lightprobe_texture, linear_sampler), pos_uvw + vec3(0, 0, float(sdfgi.max_cascades)), 0.0).rgb;
}
if (roughness > 0.2) {
specular = mix(specular, textureLod(sampler2DArray(lightprobe_texture, linear_sampler), pos_uvw, 0.0).rgb, (roughness - 0.2) * 1.25);
}
specular_accum += specular * weight * sdfgi.cascades[cascade].exposure_normalization;
}
}
if (diffuse_accum.a > 0.0) {
diffuse_accum.rgb /= diffuse_accum.a;
}
diffuse_light = diffuse_accum.rgb;
if (diffuse_accum.a > 0.0) {
specular_accum /= diffuse_accum.a;
}
specular_light = specular_accum;
}
void sdfgi_process(vec3 vertex, vec3 normal, vec3 reflection, float roughness, out vec4 ambient_light, out vec4 reflection_light) {
//make vertex orientation the world one, but still align to camera
vertex.y *= sdfgi.y_mult;
normal.y *= sdfgi.y_mult;
reflection.y *= sdfgi.y_mult;
//renormalize
normal = normalize(normal);
reflection = normalize(reflection);
vec3 cam_pos = vertex;
vec3 cam_normal = normal;
vec4 light_accum = vec4(0.0);
float weight_accum = 0.0;
vec4 light_blend_accum = vec4(0.0);
float weight_blend_accum = 0.0;
float blend = -1.0;
// helper constants, compute once
uint cascade = 0xFFFFFFFF;
vec3 cascade_pos;
vec3 cascade_normal;
for (uint i = 0; i < sdfgi.max_cascades; i++) {
cascade_pos = (cam_pos - sdfgi.cascades[i].position) * sdfgi.cascades[i].to_probe;
if (any(lessThan(cascade_pos, vec3(0.0))) || any(greaterThanEqual(cascade_pos, sdfgi.cascade_probe_size))) {
continue; //skip cascade
}
cascade = i;
break;
}
if (cascade < SDFGI_MAX_CASCADES) {
ambient_light = vec4(0, 0, 0, 1);
reflection_light = vec4(0, 0, 0, 1);
float blend;
vec3 diffuse, specular;
sdfvoxel_gi_process(cascade, cascade_pos, cam_pos, cam_normal, reflection, roughness, diffuse, specular);
{
//process blend
float blend_from = (float(sdfgi.probe_axis_size - 1) / 2.0) - 2.5;
float blend_to = blend_from + 2.0;
vec3 inner_pos = cam_pos * sdfgi.cascades[cascade].to_probe;
float len = length(inner_pos);
inner_pos = abs(normalize(inner_pos));
len *= max(inner_pos.x, max(inner_pos.y, inner_pos.z));
if (len >= blend_from) {
blend = smoothstep(blend_from, blend_to, len);
} else {
blend = 0.0;
}
}
if (blend > 0.0) {
//blend
if (cascade == sdfgi.max_cascades - 1) {
ambient_light.a = 1.0 - blend;
reflection_light.a = 1.0 - blend;
} else {
vec3 diffuse2, specular2;
cascade_pos = (cam_pos - sdfgi.cascades[cascade + 1].position) * sdfgi.cascades[cascade + 1].to_probe;
sdfvoxel_gi_process(cascade + 1, cascade_pos, cam_pos, cam_normal, reflection, roughness, diffuse2, specular2);
diffuse = mix(diffuse, diffuse2, blend);
specular = mix(specular, specular2, blend);
}
}
ambient_light.rgb = diffuse;
if (roughness < 0.2) {
vec3 pos_to_uvw = 1.0 / sdfgi.grid_size;
vec4 light_accum = vec4(0.0);
float blend_size = (sdfgi.grid_size.x / float(sdfgi.probe_axis_size - 1)) * 0.5;
float radius_sizes[SDFGI_MAX_CASCADES];
cascade = 0xFFFF;
float base_distance = length(cam_pos);
for (uint i = 0; i < sdfgi.max_cascades; i++) {
radius_sizes[i] = (1.0 / sdfgi.cascades[i].to_cell) * (sdfgi.grid_size.x * 0.5 - blend_size);
if (cascade == 0xFFFF && base_distance < radius_sizes[i]) {
cascade = i;
}
}
cascade = min(cascade, sdfgi.max_cascades - 1);
float max_distance = radius_sizes[sdfgi.max_cascades - 1];
vec3 ray_pos = cam_pos;
vec3 ray_dir = reflection;
{
float prev_radius = cascade > 0 ? radius_sizes[cascade - 1] : 0.0;
float base_blend = (base_distance - prev_radius) / (radius_sizes[cascade] - prev_radius);
float bias = (1.0 + base_blend) * 1.1;
vec3 abs_ray_dir = abs(ray_dir);
//ray_pos += ray_dir * (bias / sdfgi.cascades[cascade].to_cell); //bias to avoid self occlusion
ray_pos += (ray_dir * 1.0 / max(abs_ray_dir.x, max(abs_ray_dir.y, abs_ray_dir.z)) + cam_normal * 1.4) * bias / sdfgi.cascades[cascade].to_cell;
}
float softness = 0.2 + min(1.0, roughness * 5.0) * 4.0; //approximation to roughness so it does not seem like a hard fade
uint i = 0;
bool found = false;
while (true) {
if (length(ray_pos) >= max_distance || light_accum.a > 0.99) {
break;
}
if (!found && i >= cascade && length(ray_pos) < radius_sizes[i]) {
uint next_i = min(i + 1, sdfgi.max_cascades - 1);
cascade = max(i, cascade); //never go down
vec3 pos = ray_pos - sdfgi.cascades[i].position;
pos *= sdfgi.cascades[i].to_cell * pos_to_uvw;
float fdistance = textureLod(sampler3D(sdf_cascades[i], linear_sampler), pos, 0.0).r * 255.0 - 1.1;
vec4 hit_light = vec4(0.0);
if (fdistance < softness) {
hit_light.rgb = textureLod(sampler3D(light_cascades[i], linear_sampler), pos, 0.0).rgb;
hit_light.rgb *= 0.5; //approximation given value read is actually meant for anisotropy
hit_light.a = clamp(1.0 - (fdistance / softness), 0.0, 1.0);
hit_light.rgb *= hit_light.a;
}
fdistance /= sdfgi.cascades[i].to_cell;
if (i < (sdfgi.max_cascades - 1)) {
pos = ray_pos - sdfgi.cascades[next_i].position;
pos *= sdfgi.cascades[next_i].to_cell * pos_to_uvw;
float fdistance2 = textureLod(sampler3D(sdf_cascades[next_i], linear_sampler), pos, 0.0).r * 255.0 - 1.1;
vec4 hit_light2 = vec4(0.0);
if (fdistance2 < softness) {
hit_light2.rgb = textureLod(sampler3D(light_cascades[next_i], linear_sampler), pos, 0.0).rgb;
hit_light2.rgb *= 0.5; //approximation given value read is actually meant for anisotropy
hit_light2.a = clamp(1.0 - (fdistance2 / softness), 0.0, 1.0);
hit_light2.rgb *= hit_light2.a;
}
float prev_radius = i == 0 ? 0.0 : radius_sizes[max(0, i - 1)];
float blend = clamp((length(ray_pos) - prev_radius) / (radius_sizes[i] - prev_radius), 0.0, 1.0);
fdistance2 /= sdfgi.cascades[next_i].to_cell;
hit_light = mix(hit_light, hit_light2, blend);
fdistance = mix(fdistance, fdistance2, blend);
}
light_accum += hit_light;
ray_pos += ray_dir * fdistance;
found = true;
}
i++;
if (i == sdfgi.max_cascades) {
i = 0;
found = false;
}
}
vec3 light = light_accum.rgb / max(light_accum.a, 0.00001);
float alpha = min(1.0, light_accum.a);
float b = min(1.0, roughness * 5.0);
float sa = 1.0 - b;
reflection_light.a = alpha * sa + b;
if (reflection_light.a == 0) {
specular = vec3(0.0);
} else {
specular = (light * alpha * sa + specular * b) / reflection_light.a;
}
}
reflection_light.rgb = specular;
ambient_light.rgb *= sdfgi.energy;
reflection_light.rgb *= sdfgi.energy;
} else {
ambient_light = vec4(0);
reflection_light = vec4(0);
}
}
//standard voxel cone trace
vec4 voxel_cone_trace(texture3D probe, vec3 cell_size, vec3 pos, vec3 direction, float tan_half_angle, float max_distance, float p_bias) {
float dist = p_bias;
vec4 color = vec4(0.0);
while (dist < max_distance && color.a < 0.95) {
float diameter = max(1.0, 2.0 * tan_half_angle * dist);
vec3 uvw_pos = (pos + dist * direction) * cell_size;
float half_diameter = diameter * 0.5;
//check if outside, then break
if (any(greaterThan(abs(uvw_pos - 0.5), vec3(0.5f + half_diameter * cell_size)))) {
break;
}
vec4 scolor = textureLod(sampler3D(probe, linear_sampler_with_mipmaps), uvw_pos, log2(diameter));
float a = (1.0 - color.a);
color += a * scolor;
dist += half_diameter;
}
return color;
}
vec4 voxel_cone_trace_45_degrees(texture3D probe, vec3 cell_size, vec3 pos, vec3 direction, float max_distance, float p_bias) {
float dist = p_bias;
vec4 color = vec4(0.0);
float radius = max(0.5, dist);
float lod_level = log2(radius * 2.0);
while (dist < max_distance && color.a < 0.95) {
vec3 uvw_pos = (pos + dist * direction) * cell_size;
//check if outside, then break
if (any(greaterThan(abs(uvw_pos - 0.5), vec3(0.5f + radius * cell_size)))) {
break;
}
vec4 scolor = textureLod(sampler3D(probe, linear_sampler_with_mipmaps), uvw_pos, lod_level);
lod_level += 1.0;
float a = (1.0 - color.a);
scolor *= a;
color += scolor;
dist += radius;
radius = max(0.5, dist);
}
return color;
}
void voxel_gi_compute(uint index, vec3 position, vec3 normal, vec3 ref_vec, mat3 normal_xform, float roughness, inout vec4 out_spec, inout vec4 out_diff, inout float out_blend) {
position = (voxel_gi_instances.data[index].xform * vec4(position, 1.0)).xyz;
ref_vec = normalize((voxel_gi_instances.data[index].xform * vec4(ref_vec, 0.0)).xyz);
normal = normalize((voxel_gi_instances.data[index].xform * vec4(normal, 0.0)).xyz);
position += normal * voxel_gi_instances.data[index].normal_bias;
//this causes corrupted pixels, i have no idea why..
if (any(bvec2(any(lessThan(position, vec3(0.0))), any(greaterThan(position, voxel_gi_instances.data[index].bounds))))) {
return;
}
mat3 dir_xform = mat3(voxel_gi_instances.data[index].xform) * normal_xform;
vec3 blendv = abs(position / voxel_gi_instances.data[index].bounds * 2.0 - 1.0);
float blend = clamp(1.0 - max(blendv.x, max(blendv.y, blendv.z)), 0.0, 1.0);
//float blend=1.0;
float max_distance = length(voxel_gi_instances.data[index].bounds);
vec3 cell_size = 1.0 / voxel_gi_instances.data[index].bounds;
//irradiance
vec4 light = vec4(0.0);
if (params.high_quality_vct) {
const uint cone_dir_count = 6;
vec3 cone_dirs[cone_dir_count] = vec3[](
vec3(0.0, 0.0, 1.0),
vec3(0.866025, 0.0, 0.5),
vec3(0.267617, 0.823639, 0.5),
vec3(-0.700629, 0.509037, 0.5),
vec3(-0.700629, -0.509037, 0.5),
vec3(0.267617, -0.823639, 0.5));
float cone_weights[cone_dir_count] = float[](0.25, 0.15, 0.15, 0.15, 0.15, 0.15);
float cone_angle_tan = 0.577;
for (uint i = 0; i < cone_dir_count; i++) {
vec3 dir = normalize(dir_xform * cone_dirs[i]);
light += cone_weights[i] * voxel_cone_trace(voxel_gi_textures[index], cell_size, position, dir, cone_angle_tan, max_distance, voxel_gi_instances.data[index].bias);
}
} else {
const uint cone_dir_count = 4;
vec3 cone_dirs[cone_dir_count] = vec3[](
vec3(0.707107, 0.0, 0.707107),
vec3(0.0, 0.707107, 0.707107),
vec3(-0.707107, 0.0, 0.707107),
vec3(0.0, -0.707107, 0.707107));
float cone_weights[cone_dir_count] = float[](0.25, 0.25, 0.25, 0.25);
for (int i = 0; i < cone_dir_count; i++) {
vec3 dir = normalize(dir_xform * cone_dirs[i]);
light += cone_weights[i] * voxel_cone_trace_45_degrees(voxel_gi_textures[index], cell_size, position, dir, max_distance, voxel_gi_instances.data[index].bias);
}
}
light.rgb *= voxel_gi_instances.data[index].dynamic_range * voxel_gi_instances.data[index].exposure_normalization;
if (!voxel_gi_instances.data[index].blend_ambient) {
light.a = 1.0;
}
out_diff += light * blend;
//radiance
vec4 irr_light = voxel_cone_trace(voxel_gi_textures[index], cell_size, position, ref_vec, tan(roughness * 0.5 * M_PI * 0.99), max_distance, voxel_gi_instances.data[index].bias);
irr_light.rgb *= voxel_gi_instances.data[index].dynamic_range * voxel_gi_instances.data[index].exposure_normalization;
if (!voxel_gi_instances.data[index].blend_ambient) {
irr_light.a = 1.0;
}
out_spec += irr_light * blend;
out_blend += blend;
}
vec4 fetch_normal_and_roughness(ivec2 pos) {
vec4 normal_roughness = texelFetch(sampler2D(normal_roughness_buffer, linear_sampler), pos, 0);
normal_roughness.xyz = normalize(normal_roughness.xyz * 2.0 - 1.0);
return normal_roughness;
}
void process_gi(ivec2 pos, vec3 vertex, inout vec4 ambient_light, inout vec4 reflection_light) {
vec4 normal_roughness = fetch_normal_and_roughness(pos);
vec3 normal = normal_roughness.xyz;
if (normal.length() > 0.5) {
//valid normal, can do GI
float roughness = normal_roughness.w;
bool dynamic_object = roughness > 0.5;
if (dynamic_object) {
roughness = 1.0 - roughness;
}
roughness /= (127.0 / 255.0);
vec3 view = -normalize(mat3(scene_data.cam_transform) * (vertex - scene_data.eye_offset[gl_GlobalInvocationID.z].xyz));
vertex = mat3(scene_data.cam_transform) * vertex;
normal = normalize(mat3(scene_data.cam_transform) * normal);
vec3 reflection = normalize(reflect(-view, normal));
#ifdef USE_SDFGI
sdfgi_process(vertex, normal, reflection, roughness, ambient_light, reflection_light);
#endif
#ifdef USE_VOXEL_GI_INSTANCES
{
#ifdef SAMPLE_VOXEL_GI_NEAREST
uvec2 voxel_gi_tex = texelFetch(voxel_gi_buffer, pos, 0).rg;
#else
uvec2 voxel_gi_tex = texelFetch(usampler2D(voxel_gi_buffer, linear_sampler), pos, 0).rg;
#endif
roughness *= roughness;
//find arbitrary tangent and bitangent, then build a matrix
vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);
vec3 tangent = normalize(cross(v0, normal));
vec3 bitangent = normalize(cross(tangent, normal));
mat3 normal_mat = mat3(tangent, bitangent, normal);
vec4 amb_accum = vec4(0.0);
vec4 spec_accum = vec4(0.0);
float blend_accum = 0.0;
for (uint i = 0; i < params.max_voxel_gi_instances; i++) {
if (any(equal(uvec2(i), voxel_gi_tex))) {
voxel_gi_compute(i, vertex, normal, reflection, normal_mat, roughness, spec_accum, amb_accum, blend_accum);
}
}
if (blend_accum > 0.0) {
amb_accum /= blend_accum;
spec_accum /= blend_accum;
}
#ifdef USE_SDFGI
reflection_light = blend_color(spec_accum, reflection_light);
ambient_light = blend_color(amb_accum, ambient_light);
#else
reflection_light = spec_accum;
ambient_light = amb_accum;
#endif
}
#endif
}
}
void main() {
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
uint vrs_x, vrs_y;
#ifdef USE_VRS
if (sc_use_vrs) {
ivec2 vrs_pos;
// Currently we use a 16x16 texel, possibly some day make this configurable.
if (sc_half_res) {
vrs_pos = pos >> 3;
} else {
vrs_pos = pos >> 4;
}
uint vrs_texel = imageLoad(vrs_buffer, vrs_pos).r;
// note, valid values for vrs_x and vrs_y are 1, 2 and 4.
vrs_x = 1 << ((vrs_texel >> 2) & 3);
vrs_y = 1 << (vrs_texel & 3);
if (mod(pos.x, vrs_x) != 0) {
return;
}
if (mod(pos.y, vrs_y) != 0) {
return;
}
}
#endif
if (sc_half_res) {
pos <<= 1;
}
if (any(greaterThanEqual(pos, scene_data.screen_size))) { //too large, do nothing
return;
}
vec4 ambient_light = vec4(0.0);
vec4 reflection_light = vec4(0.0);
vec3 vertex = reconstruct_position(pos);
vertex.y = -vertex.y;
process_gi(pos, vertex, ambient_light, reflection_light);
if (sc_half_res) {
pos >>= 1;
}
imageStore(ambient_buffer, pos, ambient_light);
imageStore(reflection_buffer, pos, reflection_light);
#ifdef USE_VRS
if (sc_use_vrs) {
if (vrs_x > 1) {
imageStore(ambient_buffer, pos + ivec2(1, 0), ambient_light);
imageStore(reflection_buffer, pos + ivec2(1, 0), reflection_light);
}
if (vrs_x > 2) {
imageStore(ambient_buffer, pos + ivec2(2, 0), ambient_light);
imageStore(reflection_buffer, pos + ivec2(2, 0), reflection_light);
imageStore(ambient_buffer, pos + ivec2(3, 0), ambient_light);
imageStore(reflection_buffer, pos + ivec2(3, 0), reflection_light);
}
if (vrs_y > 1) {
imageStore(ambient_buffer, pos + ivec2(0, 1), ambient_light);
imageStore(reflection_buffer, pos + ivec2(0, 1), reflection_light);
}
if (vrs_y > 1 && vrs_x > 1) {
imageStore(ambient_buffer, pos + ivec2(1, 1), ambient_light);
imageStore(reflection_buffer, pos + ivec2(1, 1), reflection_light);
}
if (vrs_y > 1 && vrs_x > 2) {
imageStore(ambient_buffer, pos + ivec2(2, 1), ambient_light);
imageStore(reflection_buffer, pos + ivec2(2, 1), reflection_light);
imageStore(ambient_buffer, pos + ivec2(3, 1), ambient_light);
imageStore(reflection_buffer, pos + ivec2(3, 1), reflection_light);
}
if (vrs_y > 2) {
imageStore(ambient_buffer, pos + ivec2(0, 2), ambient_light);
imageStore(reflection_buffer, pos + ivec2(0, 2), reflection_light);
imageStore(ambient_buffer, pos + ivec2(0, 3), ambient_light);
imageStore(reflection_buffer, pos + ivec2(0, 3), reflection_light);
}
if (vrs_y > 2 && vrs_x > 1) {
imageStore(ambient_buffer, pos + ivec2(1, 2), ambient_light);
imageStore(reflection_buffer, pos + ivec2(1, 2), reflection_light);
imageStore(ambient_buffer, pos + ivec2(1, 3), ambient_light);
imageStore(reflection_buffer, pos + ivec2(1, 3), reflection_light);
}
if (vrs_y > 2 && vrs_x > 2) {
imageStore(ambient_buffer, pos + ivec2(2, 2), ambient_light);
imageStore(reflection_buffer, pos + ivec2(2, 2), reflection_light);
imageStore(ambient_buffer, pos + ivec2(2, 3), ambient_light);
imageStore(reflection_buffer, pos + ivec2(2, 3), reflection_light);
imageStore(ambient_buffer, pos + ivec2(3, 2), ambient_light);
imageStore(reflection_buffer, pos + ivec2(3, 2), reflection_light);
imageStore(ambient_buffer, pos + ivec2(3, 3), ambient_light);
imageStore(reflection_buffer, pos + ivec2(3, 3), reflection_light);
}
}
#endif
}

View File

@@ -0,0 +1,185 @@
#[compute]
#version 450
#VERSION_DEFINES
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
#define MAX_CASCADES 8
layout(set = 0, binding = 1) uniform texture3D sdf_cascades[MAX_CASCADES];
layout(set = 0, binding = 2) uniform texture3D light_cascades[MAX_CASCADES];
layout(set = 0, binding = 3) uniform texture3D aniso0_cascades[MAX_CASCADES];
layout(set = 0, binding = 4) uniform texture3D aniso1_cascades[MAX_CASCADES];
layout(set = 0, binding = 5) uniform texture3D occlusion_texture;
layout(set = 0, binding = 8) uniform sampler linear_sampler;
struct CascadeData {
vec3 offset; //offset of (0,0,0) in world coordinates
float to_cell; // 1/bounds * grid_size
ivec3 probe_world_offset;
uint pad;
vec4 pad2;
};
layout(set = 0, binding = 9, std140) uniform Cascades {
CascadeData data[MAX_CASCADES];
}
cascades;
layout(rgba16f, set = 0, binding = 10) uniform restrict writeonly image2D screen_buffer;
layout(set = 0, binding = 11) uniform texture2DArray lightprobe_texture;
layout(push_constant, std430) uniform Params {
vec3 grid_size;
uint max_cascades;
ivec2 screen_size;
float y_mult;
float z_near;
mat3x4 inv_projection;
// We pack these more tightly than mat3 and vec3, which will require some reconstruction trickery.
float cam_basis[3][3];
float cam_origin[3];
}
params;
vec3 linear_to_srgb(vec3 color) {
//if going to srgb, clamp from 0 to 1.
color = clamp(color, vec3(0.0), vec3(1.0));
const vec3 a = vec3(0.055f);
return mix((vec3(1.0f) + a) * pow(color.rgb, vec3(1.0f / 2.4f)) - a, 12.92f * color.rgb, lessThan(color.rgb, vec3(0.0031308f)));
}
vec2 octahedron_wrap(vec2 v) {
vec2 signVal;
signVal.x = v.x >= 0.0 ? 1.0 : -1.0;
signVal.y = v.y >= 0.0 ? 1.0 : -1.0;
return (1.0 - abs(v.yx)) * signVal;
}
vec2 octahedron_encode(vec3 n) {
// https://twitter.com/Stubbesaurus/status/937994790553227264
n /= (abs(n.x) + abs(n.y) + abs(n.z));
n.xy = n.z >= 0.0 ? n.xy : octahedron_wrap(n.xy);
n.xy = n.xy * 0.5 + 0.5;
return n.xy;
}
void main() {
// Pixel being shaded
ivec2 screen_pos = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(screen_pos, params.screen_size))) { //too large, do nothing
return;
}
vec3 ray_pos;
vec3 ray_dir;
{
ray_pos = vec3(params.cam_origin[0], params.cam_origin[1], params.cam_origin[2]);
ray_dir.xy = ((vec2(screen_pos) / vec2(params.screen_size)) * 2.0 - 1.0);
ray_dir.z = params.z_near;
ray_dir = (vec4(ray_dir, 1.0) * mat4(params.inv_projection)).xyz;
mat3 cam_basis;
{
vec3 c0 = vec3(params.cam_basis[0][0], params.cam_basis[0][1], params.cam_basis[0][2]);
vec3 c1 = vec3(params.cam_basis[1][0], params.cam_basis[1][1], params.cam_basis[1][2]);
vec3 c2 = vec3(params.cam_basis[2][0], params.cam_basis[2][1], params.cam_basis[2][2]);
cam_basis = mat3(c0, c1, c2);
}
ray_dir = normalize(cam_basis * ray_dir);
}
ray_pos.y *= params.y_mult;
ray_dir.y *= params.y_mult;
ray_dir = normalize(ray_dir);
vec3 pos_to_uvw = 1.0 / params.grid_size;
vec3 light = vec3(0.0);
float blend = 0.0;
#if 1
// No interpolation
vec3 inv_dir = 1.0 / ray_dir;
float rough = 0.5;
bool hit = false;
for (uint i = 0; i < params.max_cascades; i++) {
//convert to local bounds
vec3 pos = ray_pos - cascades.data[i].offset;
pos *= cascades.data[i].to_cell;
// Should never happen for debug, since we start mostly at the bounds center,
// but add anyway.
//if (any(lessThan(pos,vec3(0.0))) || any(greaterThanEqual(pos,params.grid_size))) {
// continue; //already past bounds for this cascade, goto next
//}
//find maximum advance distance (until reaching bounds)
vec3 t0 = -pos * inv_dir;
vec3 t1 = (params.grid_size - pos) * inv_dir;
vec3 tmax = max(t0, t1);
float max_advance = min(tmax.x, min(tmax.y, tmax.z));
float advance = 0.0;
vec3 uvw;
hit = false;
while (advance < max_advance) {
//read how much to advance from SDF
uvw = (pos + ray_dir * advance) * pos_to_uvw;
float distance = texture(sampler3D(sdf_cascades[i], linear_sampler), uvw).r * 255.0 - 1.7;
if (distance < 0.001) {
//consider hit
hit = true;
break;
}
advance += distance;
}
if (!hit) {
pos += ray_dir * min(advance, max_advance);
pos /= cascades.data[i].to_cell;
pos += cascades.data[i].offset;
ray_pos = pos;
continue;
}
//compute albedo, emission and normal at hit point
const float EPSILON = 0.001;
vec3 hit_normal = normalize(vec3(
texture(sampler3D(sdf_cascades[i], linear_sampler), uvw + vec3(EPSILON, 0.0, 0.0)).r - texture(sampler3D(sdf_cascades[i], linear_sampler), uvw - vec3(EPSILON, 0.0, 0.0)).r,
texture(sampler3D(sdf_cascades[i], linear_sampler), uvw + vec3(0.0, EPSILON, 0.0)).r - texture(sampler3D(sdf_cascades[i], linear_sampler), uvw - vec3(0.0, EPSILON, 0.0)).r,
texture(sampler3D(sdf_cascades[i], linear_sampler), uvw + vec3(0.0, 0.0, EPSILON)).r - texture(sampler3D(sdf_cascades[i], linear_sampler), uvw - vec3(0.0, 0.0, EPSILON)).r));
vec3 hit_light = texture(sampler3D(light_cascades[i], linear_sampler), uvw).rgb;
vec4 aniso0 = texture(sampler3D(aniso0_cascades[i], linear_sampler), uvw);
vec3 hit_aniso0 = aniso0.rgb;
vec3 hit_aniso1 = vec3(aniso0.a, texture(sampler3D(aniso1_cascades[i], linear_sampler), uvw).rg);
hit_light *= (dot(max(vec3(0.0), (hit_normal * hit_aniso0)), vec3(1.0)) + dot(max(vec3(0.0), (-hit_normal * hit_aniso1)), vec3(1.0)));
light = hit_light;
break;
}
#endif
imageStore(screen_buffer, screen_pos, vec4(linear_to_srgb(light), 1.0));
}

View File

@@ -0,0 +1,243 @@
#[vertex]
#version 450
#ifdef USE_MULTIVIEW
#extension GL_EXT_multiview : enable
#define ViewIndex gl_ViewIndex
#else // USE_MULTIVIEW
#define ViewIndex 0
#endif // !USE_MULTIVIEW
#VERSION_DEFINES
#define MAX_CASCADES 8
#define MAX_VIEWS 2
layout(push_constant, std430) uniform Params {
uint band_power;
uint sections_in_band;
uint band_mask;
float section_arc;
vec3 grid_size;
uint cascade;
uint pad;
float y_mult;
uint probe_debug_index;
int probe_axis_size;
}
params;
// https://in4k.untergrund.net/html_articles/hugi_27_-_coding_corner_polaris_sphere_tessellation_101.htm
vec3 get_sphere_vertex(uint p_vertex_id) {
float x_angle = float(p_vertex_id & 1u) + (p_vertex_id >> params.band_power);
float y_angle =
float((p_vertex_id & params.band_mask) >> 1) + ((p_vertex_id >> params.band_power) * params.sections_in_band);
x_angle *= params.section_arc * 0.5f; // remember - 180AA x rot not 360
y_angle *= -params.section_arc;
vec3 point = vec3(sin(x_angle) * sin(y_angle), cos(x_angle), sin(x_angle) * cos(y_angle));
return point;
}
#ifdef MODE_PROBES
layout(location = 0) out vec3 normal_interp;
layout(location = 1) out flat uint probe_index;
#endif
#ifdef MODE_VISIBILITY
layout(location = 0) out float visibility;
#endif
struct CascadeData {
vec3 offset; //offset of (0,0,0) in world coordinates
float to_cell; // 1/bounds * grid_size
ivec3 probe_world_offset;
uint pad;
vec4 pad2;
};
layout(set = 0, binding = 1, std140) uniform Cascades {
CascadeData data[MAX_CASCADES];
}
cascades;
layout(set = 0, binding = 4) uniform texture3D occlusion_texture;
layout(set = 0, binding = 3) uniform sampler linear_sampler;
layout(set = 0, binding = 5, std140) uniform SceneData {
mat4 projection[MAX_VIEWS];
}
scene_data;
void main() {
#ifdef MODE_PROBES
probe_index = gl_InstanceIndex;
normal_interp = get_sphere_vertex(gl_VertexIndex);
vec3 vertex = normal_interp * 0.2;
float probe_cell_size = float(params.grid_size / float(params.probe_axis_size - 1)) / cascades.data[params.cascade].to_cell;
ivec3 probe_cell;
probe_cell.x = int(probe_index % params.probe_axis_size);
probe_cell.y = int(probe_index / (params.probe_axis_size * params.probe_axis_size));
probe_cell.z = int((probe_index / params.probe_axis_size) % params.probe_axis_size);
vertex += (cascades.data[params.cascade].offset + vec3(probe_cell) * probe_cell_size) / vec3(1.0, params.y_mult, 1.0);
gl_Position = scene_data.projection[ViewIndex] * vec4(vertex, 1.0);
#endif
#ifdef MODE_VISIBILITY
int probe_index = int(params.probe_debug_index);
vec3 vertex = get_sphere_vertex(gl_VertexIndex) * 0.01;
float probe_cell_size = float(params.grid_size / float(params.probe_axis_size - 1)) / cascades.data[params.cascade].to_cell;
ivec3 probe_cell;
probe_cell.x = int(probe_index % params.probe_axis_size);
probe_cell.y = int((probe_index % (params.probe_axis_size * params.probe_axis_size)) / params.probe_axis_size);
probe_cell.z = int(probe_index / (params.probe_axis_size * params.probe_axis_size));
vertex += (cascades.data[params.cascade].offset + vec3(probe_cell) * probe_cell_size) / vec3(1.0, params.y_mult, 1.0);
int probe_voxels = int(params.grid_size.x) / int(params.probe_axis_size - 1);
int occluder_index = int(gl_InstanceIndex);
int diameter = probe_voxels * 2;
ivec3 occluder_pos;
occluder_pos.x = int(occluder_index % diameter);
occluder_pos.y = int(occluder_index / (diameter * diameter));
occluder_pos.z = int((occluder_index / diameter) % diameter);
float cell_size = 1.0 / cascades.data[params.cascade].to_cell;
ivec3 occluder_offset = occluder_pos - ivec3(diameter / 2);
vertex += ((vec3(occluder_offset) + vec3(0.5)) * cell_size) / vec3(1.0, params.y_mult, 1.0);
ivec3 global_cell = probe_cell + cascades.data[params.cascade].probe_world_offset;
uint occlusion_layer = 0;
if ((global_cell.x & 1) != 0) {
occlusion_layer |= 1;
}
if ((global_cell.y & 1) != 0) {
occlusion_layer |= 2;
}
if ((global_cell.z & 1) != 0) {
occlusion_layer |= 4;
}
ivec3 tex_pos = probe_cell * probe_voxels + occluder_offset;
const vec4 layer_axis[4] = vec4[](
vec4(1, 0, 0, 0),
vec4(0, 1, 0, 0),
vec4(0, 0, 1, 0),
vec4(0, 0, 0, 1));
tex_pos.z += int(params.cascade) * int(params.grid_size);
if (occlusion_layer >= 4) {
tex_pos.x += int(params.grid_size.x);
occlusion_layer &= 3;
}
visibility = dot(texelFetch(sampler3D(occlusion_texture, linear_sampler), tex_pos, 0), layer_axis[occlusion_layer]);
gl_Position = scene_data.projection[ViewIndex] * vec4(vertex, 1.0);
#endif
}
#[fragment]
#version 450
#VERSION_DEFINES
#define MAX_VIEWS 2
layout(location = 0) out vec4 frag_color;
layout(set = 0, binding = 2) uniform texture2DArray lightprobe_texture;
layout(set = 0, binding = 3) uniform sampler linear_sampler;
layout(push_constant, std430) uniform Params {
uint band_power;
uint sections_in_band;
uint band_mask;
float section_arc;
vec3 grid_size;
uint cascade;
uint pad;
float y_mult;
uint probe_debug_index;
int probe_axis_size;
}
params;
#ifdef MODE_PROBES
layout(location = 0) in vec3 normal_interp;
layout(location = 1) in flat uint probe_index;
#endif
#ifdef MODE_VISIBILITY
layout(location = 0) in float visibility;
#endif
vec2 octahedron_wrap(vec2 v) {
vec2 signVal;
signVal.x = v.x >= 0.0 ? 1.0 : -1.0;
signVal.y = v.y >= 0.0 ? 1.0 : -1.0;
return (1.0 - abs(v.yx)) * signVal;
}
vec2 octahedron_encode(vec3 n) {
// https://twitter.com/Stubbesaurus/status/937994790553227264
n /= (abs(n.x) + abs(n.y) + abs(n.z));
n.xy = n.z >= 0.0 ? n.xy : octahedron_wrap(n.xy);
n.xy = n.xy * 0.5 + 0.5;
return n.xy;
}
void main() {
#ifdef MODE_PROBES
ivec3 tex_pos;
tex_pos.x = int(probe_index) % params.probe_axis_size; //x
tex_pos.y = int(probe_index) / (params.probe_axis_size * params.probe_axis_size);
tex_pos.x += params.probe_axis_size * ((int(probe_index) / params.probe_axis_size) % params.probe_axis_size); //z
tex_pos.z = int(params.cascade);
vec3 tex_pos_ofs = vec3(octahedron_encode(normal_interp) * float(OCT_SIZE), 0.0);
vec3 tex_posf = vec3(vec2(tex_pos.xy * (OCT_SIZE + 2) + ivec2(1)), float(tex_pos.z)) + tex_pos_ofs;
tex_posf.xy /= vec2(ivec2(params.probe_axis_size * params.probe_axis_size * (OCT_SIZE + 2), params.probe_axis_size * (OCT_SIZE + 2)));
vec4 indirect_light = textureLod(sampler2DArray(lightprobe_texture, linear_sampler), tex_posf, 0.0);
frag_color = indirect_light;
#endif
#ifdef MODE_VISIBILITY
frag_color = vec4(vec3(1, visibility, visibility), 1.0);
#endif
}

View File

@@ -0,0 +1,507 @@
#[compute]
#version 450
#VERSION_DEFINES
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
#define MAX_CASCADES 8
layout(set = 0, binding = 1) uniform texture3D sdf_cascades[MAX_CASCADES];
layout(set = 0, binding = 2) uniform sampler linear_sampler;
layout(set = 0, binding = 3, std430) restrict readonly buffer DispatchData {
uint x;
uint y;
uint z;
uint total_count;
}
dispatch_data;
struct ProcessVoxel {
uint position; // xyz 7 bit packed, extra 11 bits for neighbors.
uint albedo; // rgb bits 0-15 albedo, bits 16-21 are normal bits (set if geometry exists toward that side), extra 11 bits for neighbors.
uint light; // rgbe8985 encoded total saved light, extra 2 bits for neighbors.
uint light_aniso; // 55555 light anisotropy, extra 2 bits for neighbors.
//total neighbors: 26
};
#ifdef MODE_PROCESS_STATIC
layout(set = 0, binding = 4, std430) restrict buffer ProcessVoxels {
#else
layout(set = 0, binding = 4, std430) restrict buffer readonly ProcessVoxels {
#endif
ProcessVoxel data[];
}
process_voxels;
layout(r32ui, set = 0, binding = 5) uniform restrict uimage3D dst_light;
layout(rgba8, set = 0, binding = 6) uniform restrict image3D dst_aniso0;
layout(rg8, set = 0, binding = 7) uniform restrict image3D dst_aniso1;
struct CascadeData {
vec3 offset; //offset of (0,0,0) in world coordinates
float to_cell; // 1/bounds * grid_size
ivec3 probe_world_offset;
uint pad;
vec4 pad2;
};
layout(set = 0, binding = 8, std140) uniform Cascades {
CascadeData data[MAX_CASCADES];
}
cascades;
#define LIGHT_TYPE_DIRECTIONAL 0
#define LIGHT_TYPE_OMNI 1
#define LIGHT_TYPE_SPOT 2
struct Light {
vec3 color;
float energy;
vec3 direction;
bool has_shadow;
vec3 position;
float attenuation;
uint type;
float cos_spot_angle;
float inv_spot_attenuation;
float radius;
};
layout(set = 0, binding = 9, std140) buffer restrict readonly Lights {
Light data[];
}
lights;
layout(set = 0, binding = 10) uniform texture2DArray lightprobe_texture;
layout(set = 0, binding = 11) uniform texture3D occlusion_texture;
layout(push_constant, std430) uniform Params {
vec3 grid_size;
uint max_cascades;
uint cascade;
uint light_count;
uint process_offset;
uint process_increment;
int probe_axis_size;
float bounce_feedback;
float y_mult;
bool use_occlusion;
}
params;
vec2 octahedron_wrap(vec2 v) {
vec2 signVal;
signVal.x = v.x >= 0.0 ? 1.0 : -1.0;
signVal.y = v.y >= 0.0 ? 1.0 : -1.0;
return (1.0 - abs(v.yx)) * signVal;
}
vec2 octahedron_encode(vec3 n) {
// https://twitter.com/Stubbesaurus/status/937994790553227264
n /= (abs(n.x) + abs(n.y) + abs(n.z));
n.xy = n.z >= 0.0 ? n.xy : octahedron_wrap(n.xy);
n.xy = n.xy * 0.5 + 0.5;
return n.xy;
}
float get_omni_attenuation(float distance, float inv_range, float decay) {
float nd = distance * inv_range;
nd *= nd;
nd *= nd; // nd^4
nd = max(1.0 - nd, 0.0);
nd *= nd; // nd^2
return nd * pow(max(distance, 0.0001), -decay);
}
void main() {
uint voxel_index = uint(gl_GlobalInvocationID.x);
//used for skipping voxels every N frames
if (params.process_increment > 1) {
voxel_index *= params.process_increment;
voxel_index += params.process_offset;
}
if (voxel_index >= dispatch_data.total_count) {
return;
}
uint voxel_position = process_voxels.data[voxel_index].position;
//keep for storing to texture
ivec3 positioni = ivec3((uvec3(voxel_position, voxel_position, voxel_position) >> uvec3(0, 7, 14)) & uvec3(0x7F));
vec3 position = vec3(positioni) + vec3(0.5);
position /= cascades.data[params.cascade].to_cell;
position += cascades.data[params.cascade].offset;
uint voxel_albedo = process_voxels.data[voxel_index].albedo;
vec3 albedo = vec3(uvec3(voxel_albedo >> 10, voxel_albedo >> 5, voxel_albedo) & uvec3(0x1F)) / float(0x1F);
vec3 light_accum[6] = vec3[](vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0));
uint valid_aniso = (voxel_albedo >> 15) & 0x3F;
const vec3 aniso_dir[6] = vec3[](
vec3(1, 0, 0),
vec3(0, 1, 0),
vec3(0, 0, 1),
vec3(-1, 0, 0),
vec3(0, -1, 0),
vec3(0, 0, -1));
// Add indirect light first, in order to save computation resources
#ifdef MODE_PROCESS_DYNAMIC
if (params.bounce_feedback > 0.001) {
vec3 feedback = (params.bounce_feedback < 1.0) ? (albedo * params.bounce_feedback) : mix(albedo, vec3(1.0), params.bounce_feedback - 1.0);
vec3 pos = (vec3(positioni) + vec3(0.5)) * float(params.probe_axis_size - 1) / params.grid_size;
ivec3 probe_base_pos = ivec3(pos);
float weight_accum[6] = float[](0, 0, 0, 0, 0, 0);
ivec3 tex_pos = ivec3(probe_base_pos.xy, int(params.cascade));
tex_pos.x += probe_base_pos.z * int(params.probe_axis_size);
tex_pos.xy = tex_pos.xy * (OCT_SIZE + 2) + ivec2(1);
vec3 base_tex_posf = vec3(tex_pos);
vec2 tex_pixel_size = 1.0 / vec2(ivec2((OCT_SIZE + 2) * params.probe_axis_size * params.probe_axis_size, (OCT_SIZE + 2) * params.probe_axis_size));
vec3 probe_uv_offset = vec3(ivec3(OCT_SIZE + 2, OCT_SIZE + 2, (OCT_SIZE + 2) * params.probe_axis_size)) * tex_pixel_size.xyx;
for (uint j = 0; j < 8; j++) {
ivec3 offset = (ivec3(j) >> ivec3(0, 1, 2)) & ivec3(1, 1, 1);
ivec3 probe_posi = probe_base_pos;
probe_posi += offset;
// Compute weight
vec3 probe_pos = vec3(probe_posi);
vec3 probe_to_pos = pos - probe_pos;
vec3 probe_dir = normalize(-probe_to_pos);
// Compute lightprobe texture position
vec3 trilinear = vec3(1.0) - abs(probe_to_pos);
for (uint k = 0; k < 6; k++) {
if (bool(valid_aniso & (1 << k))) {
vec3 n = aniso_dir[k];
float weight = trilinear.x * trilinear.y * trilinear.z * max(0, dot(n, probe_dir));
if (weight > 0.0 && params.use_occlusion) {
ivec3 occ_indexv = abs((cascades.data[params.cascade].probe_world_offset + probe_posi) & ivec3(1, 1, 1)) * ivec3(1, 2, 4);
vec4 occ_mask = mix(vec4(0.0), vec4(1.0), equal(ivec4(occ_indexv.x | occ_indexv.y), ivec4(0, 1, 2, 3)));
vec3 occ_pos = (vec3(positioni) + aniso_dir[k] + vec3(0.5)) / params.grid_size;
occ_pos.z += float(params.cascade);
if (occ_indexv.z != 0) { //z bit is on, means index is >=4, so make it switch to the other half of textures
occ_pos.x += 1.0;
}
occ_pos *= vec3(0.5, 1.0, 1.0 / float(params.max_cascades)); //renormalize
float occlusion = dot(textureLod(sampler3D(occlusion_texture, linear_sampler), occ_pos, 0.0), occ_mask);
weight *= occlusion;
}
if (weight > 0.0) {
vec3 tex_posf = base_tex_posf + vec3(octahedron_encode(n) * float(OCT_SIZE), 0.0);
tex_posf.xy *= tex_pixel_size;
vec3 pos_uvw = tex_posf;
pos_uvw.xy += vec2(offset.xy) * probe_uv_offset.xy;
pos_uvw.x += float(offset.z) * probe_uv_offset.z;
vec3 indirect_light = textureLod(sampler2DArray(lightprobe_texture, linear_sampler), pos_uvw, 0.0).rgb;
light_accum[k] += indirect_light * weight;
weight_accum[k] += weight;
}
}
}
}
for (uint k = 0; k < 6; k++) {
if (weight_accum[k] > 0.0) {
light_accum[k] /= weight_accum[k];
light_accum[k] *= feedback;
}
}
}
#endif
{
uint rgbe = process_voxels.data[voxel_index].light;
//read rgbe8985
float r = float((rgbe & 0xff) << 1);
float g = float((rgbe >> 8) & 0x1ff);
float b = float(((rgbe >> 17) & 0xff) << 1);
float e = float((rgbe >> 25) & 0x1F);
float m = pow(2.0, e - 15.0 - 9.0);
vec3 l = vec3(r, g, b) * m;
uint aniso = process_voxels.data[voxel_index].light_aniso;
for (uint i = 0; i < 6; i++) {
float strength = ((aniso >> (i * 5)) & 0x1F) / float(0x1F);
light_accum[i] += l * strength;
}
}
// Raytrace light
vec3 pos_to_uvw = 1.0 / params.grid_size;
vec3 uvw_ofs = pos_to_uvw * 0.5;
for (uint i = 0; i < params.light_count; i++) {
float attenuation = 1.0;
vec3 direction;
float light_distance = 1e20;
switch (lights.data[i].type) {
case LIGHT_TYPE_DIRECTIONAL: {
direction = -lights.data[i].direction;
} break;
case LIGHT_TYPE_OMNI: {
vec3 rel_vec = lights.data[i].position - position;
direction = normalize(rel_vec);
light_distance = length(rel_vec);
rel_vec.y /= params.y_mult;
attenuation = get_omni_attenuation(light_distance, 1.0 / lights.data[i].radius, lights.data[i].attenuation);
} break;
case LIGHT_TYPE_SPOT: {
vec3 rel_vec = lights.data[i].position - position;
direction = normalize(rel_vec);
light_distance = length(rel_vec);
rel_vec.y /= params.y_mult;
attenuation = get_omni_attenuation(light_distance, 1.0 / lights.data[i].radius, lights.data[i].attenuation);
float cos_spot_angle = lights.data[i].cos_spot_angle;
float cos_angle = dot(-direction, lights.data[i].direction);
if (cos_angle < cos_spot_angle) {
continue;
}
float scos = max(cos_angle, cos_spot_angle);
float spot_rim = max(0.0001, (1.0 - scos) / (1.0 - cos_spot_angle));
attenuation *= 1.0 - pow(spot_rim, lights.data[i].inv_spot_attenuation);
} break;
}
if (attenuation < 0.001) {
continue;
}
bool hit = false;
vec3 ray_pos = position;
vec3 ray_dir = direction;
vec3 inv_dir = 1.0 / ray_dir;
//this is how to properly bias outgoing rays
float cell_size = 1.0 / cascades.data[params.cascade].to_cell;
ray_pos += sign(direction) * cell_size * 0.48; // go almost to the box edge but remain inside
ray_pos += ray_dir * 0.4 * cell_size; //apply a small bias from there
for (uint j = params.cascade; j < params.max_cascades; j++) {
//convert to local bounds
vec3 pos = ray_pos - cascades.data[j].offset;
pos *= cascades.data[j].to_cell;
float local_distance = light_distance * cascades.data[j].to_cell;
if (any(lessThan(pos, vec3(0.0))) || any(greaterThanEqual(pos, params.grid_size))) {
continue; //already past bounds for this cascade, goto next
}
//find maximum advance distance (until reaching bounds)
vec3 t0 = -pos * inv_dir;
vec3 t1 = (params.grid_size - pos) * inv_dir;
vec3 tmax = max(t0, t1);
float max_advance = min(tmax.x, min(tmax.y, tmax.z));
max_advance = min(local_distance, max_advance);
float advance = 0.0;
float occlusion = 1.0;
while (advance < max_advance) {
//read how much to advance from SDF
vec3 uvw = (pos + ray_dir * advance) * pos_to_uvw;
float distance = texture(sampler3D(sdf_cascades[j], linear_sampler), uvw).r * 255.0 - 1.0;
if (distance < 0.001) {
//consider hit
hit = true;
break;
}
occlusion = min(occlusion, distance);
advance += distance;
}
if (hit) {
attenuation *= occlusion;
break;
}
if (advance >= local_distance) {
break; //past light distance, abandon search
}
//change ray origin to collision with bounds
pos += ray_dir * max_advance;
pos /= cascades.data[j].to_cell;
pos += cascades.data[j].offset;
light_distance -= max_advance / cascades.data[j].to_cell;
ray_pos = pos;
}
if (!hit) {
vec3 light = albedo * lights.data[i].color.rgb * lights.data[i].energy * attenuation;
for (int j = 0; j < 6; j++) {
if (bool(valid_aniso & (1 << j))) {
light_accum[j] += max(0.0, dot(aniso_dir[j], direction)) * light;
}
}
}
}
// Store the light in the light texture
float lumas[6];
vec3 light_total = vec3(0);
for (int i = 0; i < 6; i++) {
light_total += light_accum[i];
lumas[i] = max(light_accum[i].r, max(light_accum[i].g, light_accum[i].b));
}
float luma_total = max(light_total.r, max(light_total.g, light_total.b));
uint light_total_rgbe;
{
//compress to RGBE9995 to save space
const float pow2to9 = 512.0f;
const float B = 15.0f;
const float N = 9.0f;
const float LN2 = 0.6931471805599453094172321215;
float cRed = clamp(light_total.r, 0.0, 65408.0);
float cGreen = clamp(light_total.g, 0.0, 65408.0);
float cBlue = clamp(light_total.b, 0.0, 65408.0);
float cMax = max(cRed, max(cGreen, cBlue));
float expp = max(-B - 1.0f, floor(log(cMax) / LN2)) + 1.0f + B;
float sMax = floor((cMax / pow(2.0f, expp - B - N)) + 0.5f);
float exps = expp + 1.0f;
if (0.0 <= sMax && sMax < pow2to9) {
exps = expp;
}
float sRed = floor((cRed / pow(2.0f, exps - B - N)) + 0.5f);
float sGreen = floor((cGreen / pow(2.0f, exps - B - N)) + 0.5f);
float sBlue = floor((cBlue / pow(2.0f, exps - B - N)) + 0.5f);
#ifdef MODE_PROCESS_STATIC
//since its self-save, use RGBE8985
light_total_rgbe = ((uint(sRed) & 0x1FF) >> 1) | ((uint(sGreen) & 0x1FF) << 8) | (((uint(sBlue) & 0x1FF) >> 1) << 17) | ((uint(exps) & 0x1F) << 25);
#else
light_total_rgbe = (uint(sRed) & 0x1FF) | ((uint(sGreen) & 0x1FF) << 9) | ((uint(sBlue) & 0x1FF) << 18) | ((uint(exps) & 0x1F) << 27);
#endif
}
#ifdef MODE_PROCESS_DYNAMIC
vec4 aniso0;
aniso0.r = lumas[0] / luma_total;
aniso0.g = lumas[1] / luma_total;
aniso0.b = lumas[2] / luma_total;
aniso0.a = lumas[3] / luma_total;
vec2 aniso1;
aniso1.r = lumas[4] / luma_total;
aniso1.g = lumas[5] / luma_total;
//save to 3D textures
imageStore(dst_aniso0, positioni, aniso0);
imageStore(dst_aniso1, positioni, vec4(aniso1, 0.0, 0.0));
imageStore(dst_light, positioni, uvec4(light_total_rgbe));
//also fill neighbors, so light interpolation during the indirect pass works
//recover the neighbor list from the leftover bits
uint neighbors = (voxel_albedo >> 21) | ((voxel_position >> 21) << 11) | ((process_voxels.data[voxel_index].light >> 30) << 22) | ((process_voxels.data[voxel_index].light_aniso >> 30) << 24);
const uint max_neighbours = 26;
const ivec3 neighbour_positions[max_neighbours] = ivec3[](
ivec3(-1, -1, -1),
ivec3(-1, -1, 0),
ivec3(-1, -1, 1),
ivec3(-1, 0, -1),
ivec3(-1, 0, 0),
ivec3(-1, 0, 1),
ivec3(-1, 1, -1),
ivec3(-1, 1, 0),
ivec3(-1, 1, 1),
ivec3(0, -1, -1),
ivec3(0, -1, 0),
ivec3(0, -1, 1),
ivec3(0, 0, -1),
ivec3(0, 0, 1),
ivec3(0, 1, -1),
ivec3(0, 1, 0),
ivec3(0, 1, 1),
ivec3(1, -1, -1),
ivec3(1, -1, 0),
ivec3(1, -1, 1),
ivec3(1, 0, -1),
ivec3(1, 0, 0),
ivec3(1, 0, 1),
ivec3(1, 1, -1),
ivec3(1, 1, 0),
ivec3(1, 1, 1));
for (uint i = 0; i < max_neighbours; i++) {
if (bool(neighbors & (1 << i))) {
ivec3 neighbour_pos = positioni + neighbour_positions[i];
imageStore(dst_light, neighbour_pos, uvec4(light_total_rgbe));
imageStore(dst_aniso0, neighbour_pos, aniso0);
imageStore(dst_aniso1, neighbour_pos, vec4(aniso1, 0.0, 0.0));
}
}
#endif
#ifdef MODE_PROCESS_STATIC
//save back the anisotropic
uint light = process_voxels.data[voxel_index].light & (3 << 30);
light |= light_total_rgbe;
process_voxels.data[voxel_index].light = light; //replace
uint light_aniso = process_voxels.data[voxel_index].light_aniso & (3 << 30);
for (int i = 0; i < 6; i++) {
light_aniso |= min(31, uint((lumas[i] / luma_total) * 31.0)) << (i * 5);
}
process_voxels.data[voxel_index].light_aniso = light_aniso;
#endif
}

View File

@@ -0,0 +1,618 @@
#[compute]
#version 450
#VERSION_DEFINES
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
#define MAX_CASCADES 8
layout(set = 0, binding = 1) uniform texture3D sdf_cascades[MAX_CASCADES];
layout(set = 0, binding = 2) uniform texture3D light_cascades[MAX_CASCADES];
layout(set = 0, binding = 3) uniform texture3D aniso0_cascades[MAX_CASCADES];
layout(set = 0, binding = 4) uniform texture3D aniso1_cascades[MAX_CASCADES];
layout(set = 0, binding = 6) uniform sampler linear_sampler;
struct CascadeData {
vec3 offset; //offset of (0,0,0) in world coordinates
float to_cell; // 1/bounds * grid_size
ivec3 probe_world_offset;
uint pad;
vec4 pad2;
};
layout(set = 0, binding = 7, std140) uniform Cascades {
CascadeData data[MAX_CASCADES];
}
cascades;
layout(r32ui, set = 0, binding = 8) uniform restrict uimage2DArray lightprobe_texture_data;
layout(rgba16i, set = 0, binding = 9) uniform restrict iimage2DArray lightprobe_history_texture;
layout(rgba32i, set = 0, binding = 10) uniform restrict iimage2D lightprobe_average_texture;
//used for scrolling
layout(rgba16i, set = 0, binding = 11) uniform restrict iimage2DArray lightprobe_history_scroll_texture;
layout(rgba32i, set = 0, binding = 12) uniform restrict iimage2D lightprobe_average_scroll_texture;
layout(rgba32i, set = 0, binding = 13) uniform restrict iimage2D lightprobe_average_parent_texture;
layout(rgba16f, set = 0, binding = 14) uniform restrict writeonly image2DArray lightprobe_ambient_texture;
#ifdef USE_CUBEMAP_ARRAY
layout(set = 1, binding = 0) uniform textureCubeArray sky_irradiance;
#else
layout(set = 1, binding = 0) uniform textureCube sky_irradiance;
#endif
layout(set = 1, binding = 1) uniform sampler linear_sampler_mipmaps;
#define HISTORY_BITS 10
#define SKY_FLAGS_MODE_COLOR 0x01
#define SKY_FLAGS_MODE_SKY 0x02
#define SKY_FLAGS_ORIENTATION_SIGN 0x04
layout(push_constant, std430) uniform Params {
vec3 grid_size;
uint max_cascades;
uint probe_axis_size;
uint cascade;
uint history_index;
uint history_size;
uint ray_count;
float ray_bias;
ivec2 image_size;
ivec3 world_offset;
uint sky_flags;
ivec3 scroll;
float sky_energy;
vec3 sky_color_or_orientation;
float y_mult;
bool store_ambient_texture;
uint pad[3];
}
params;
const float PI = 3.14159265f;
const float GOLDEN_ANGLE = PI * (3.0 - sqrt(5.0));
vec3 vogel_hemisphere(uint p_index, uint p_count, float p_offset) {
float r = sqrt(float(p_index) + 0.5f) / sqrt(float(p_count));
float theta = float(p_index) * GOLDEN_ANGLE + p_offset;
float y = cos(r * PI * 0.5);
float l = sin(r * PI * 0.5);
return vec3(l * cos(theta), l * sin(theta), y * (float(p_index & 1) * 2.0 - 1.0));
}
uvec3 hash3(uvec3 x) {
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = (x >> 16) ^ x;
return x;
}
float hashf3(vec3 co) {
return fract(sin(dot(co, vec3(12.9898, 78.233, 137.13451))) * 43758.5453);
}
vec3 octahedron_encode(vec2 f) {
// https://twitter.com/Stubbesaurus/status/937994790553227264
f = f * 2.0 - 1.0;
vec3 n = vec3(f.x, f.y, 1.0f - abs(f.x) - abs(f.y));
float t = clamp(-n.z, 0.0, 1.0);
n.x += n.x >= 0 ? -t : t;
n.y += n.y >= 0 ? -t : t;
return normalize(n);
}
uint rgbe_encode(vec3 color) {
const float pow2to9 = 512.0f;
const float B = 15.0f;
const float N = 9.0f;
const float LN2 = 0.6931471805599453094172321215;
float cRed = clamp(color.r, 0.0, 65408.0);
float cGreen = clamp(color.g, 0.0, 65408.0);
float cBlue = clamp(color.b, 0.0, 65408.0);
float cMax = max(cRed, max(cGreen, cBlue));
float expp = max(-B - 1.0f, floor(log(cMax) / LN2)) + 1.0f + B;
float sMax = floor((cMax / pow(2.0f, expp - B - N)) + 0.5f);
float exps = expp + 1.0f;
if (0.0 <= sMax && sMax < pow2to9) {
exps = expp;
}
float sRed = floor((cRed / pow(2.0f, exps - B - N)) + 0.5f);
float sGreen = floor((cGreen / pow(2.0f, exps - B - N)) + 0.5f);
float sBlue = floor((cBlue / pow(2.0f, exps - B - N)) + 0.5f);
return (uint(sRed) & 0x1FF) | ((uint(sGreen) & 0x1FF) << 9) | ((uint(sBlue) & 0x1FF) << 18) | ((uint(exps) & 0x1F) << 27);
}
struct SH {
#if (SH_SIZE == 16)
float c[48];
#else
float c[28];
#endif
};
shared SH sh_accum[64]; //8x8
void main() {
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(pos, params.image_size))) { //too large, do nothing
return;
}
uint probe_index = gl_LocalInvocationID.x + gl_LocalInvocationID.y * 8;
#ifdef MODE_PROCESS
float probe_cell_size = float(params.grid_size.x / float(params.probe_axis_size - 1)) / cascades.data[params.cascade].to_cell;
ivec3 probe_cell;
probe_cell.x = pos.x % int(params.probe_axis_size);
probe_cell.y = pos.y;
probe_cell.z = pos.x / int(params.probe_axis_size);
vec3 probe_pos = cascades.data[params.cascade].offset + vec3(probe_cell) * probe_cell_size;
vec3 pos_to_uvw = 1.0 / params.grid_size;
for (uint i = 0; i < SH_SIZE * 3; i++) {
sh_accum[probe_index].c[i] = 0.0;
}
// quickly ensure each probe has a different "offset" for the vogel function, based on integer world position
uvec3 h3 = hash3(uvec3(params.world_offset + probe_cell));
float offset = hashf3(vec3(h3 & uvec3(0xFFFFF)));
//for a more homogeneous hemisphere, alternate based on history frames
uint ray_offset = params.history_index;
uint ray_mult = params.history_size;
uint ray_total = ray_mult * params.ray_count;
for (uint i = 0; i < params.ray_count; i++) {
vec3 ray_dir = vogel_hemisphere(ray_offset + i * ray_mult, ray_total, offset);
ray_dir.y *= params.y_mult;
ray_dir = normalize(ray_dir);
//needs to be visible
vec3 ray_pos = probe_pos;
vec3 inv_dir = 1.0 / ray_dir;
bool hit = false;
uint hit_cascade;
float bias = params.ray_bias;
vec3 abs_ray_dir = abs(ray_dir);
ray_pos += ray_dir * 1.0 / max(abs_ray_dir.x, max(abs_ray_dir.y, abs_ray_dir.z)) * bias / cascades.data[params.cascade].to_cell;
vec3 uvw;
for (uint j = params.cascade; j < params.max_cascades; j++) {
//convert to local bounds
vec3 pos = ray_pos - cascades.data[j].offset;
pos *= cascades.data[j].to_cell;
if (any(lessThan(pos, vec3(0.0))) || any(greaterThanEqual(pos, params.grid_size))) {
continue; //already past bounds for this cascade, goto next
}
//find maximum advance distance (until reaching bounds)
vec3 t0 = -pos * inv_dir;
vec3 t1 = (params.grid_size - pos) * inv_dir;
vec3 tmax = max(t0, t1);
float max_advance = min(tmax.x, min(tmax.y, tmax.z));
float advance = 0.0;
while (advance < max_advance) {
//read how much to advance from SDF
uvw = (pos + ray_dir * advance) * pos_to_uvw;
float distance = texture(sampler3D(sdf_cascades[j], linear_sampler), uvw).r * 255.0 - 1.0;
if (distance < 0.05) {
//consider hit
hit = true;
break;
}
advance += distance;
}
if (hit) {
hit_cascade = j;
break;
}
//change ray origin to collision with bounds
pos += ray_dir * max_advance;
pos /= cascades.data[j].to_cell;
pos += cascades.data[j].offset;
ray_pos = pos;
}
vec4 light;
if (hit) {
//avoid reading different texture from different threads
for (uint j = params.cascade; j < params.max_cascades; j++) {
if (j == hit_cascade) {
const float EPSILON = 0.001;
vec3 hit_normal = normalize(vec3(
texture(sampler3D(sdf_cascades[hit_cascade], linear_sampler), uvw + vec3(EPSILON, 0.0, 0.0)).r - texture(sampler3D(sdf_cascades[hit_cascade], linear_sampler), uvw - vec3(EPSILON, 0.0, 0.0)).r,
texture(sampler3D(sdf_cascades[hit_cascade], linear_sampler), uvw + vec3(0.0, EPSILON, 0.0)).r - texture(sampler3D(sdf_cascades[hit_cascade], linear_sampler), uvw - vec3(0.0, EPSILON, 0.0)).r,
texture(sampler3D(sdf_cascades[hit_cascade], linear_sampler), uvw + vec3(0.0, 0.0, EPSILON)).r - texture(sampler3D(sdf_cascades[hit_cascade], linear_sampler), uvw - vec3(0.0, 0.0, EPSILON)).r));
vec3 hit_light = texture(sampler3D(light_cascades[hit_cascade], linear_sampler), uvw).rgb;
vec4 aniso0 = texture(sampler3D(aniso0_cascades[hit_cascade], linear_sampler), uvw);
vec3 hit_aniso0 = aniso0.rgb;
vec3 hit_aniso1 = vec3(aniso0.a, texture(sampler3D(aniso1_cascades[hit_cascade], linear_sampler), uvw).rg);
//one liner magic
light.rgb = hit_light * (dot(max(vec3(0.0), (hit_normal * hit_aniso0)), vec3(1.0)) + dot(max(vec3(0.0), (-hit_normal * hit_aniso1)), vec3(1.0)));
light.a = 1.0;
}
}
} else if (bool(params.sky_flags & SKY_FLAGS_MODE_SKY)) {
// Reconstruct sky orientation as quaternion and rotate ray_dir before sampling.
float sky_sign = bool(params.sky_flags & SKY_FLAGS_ORIENTATION_SIGN) ? 1.0 : -1.0;
vec4 sky_quat = vec4(params.sky_color_or_orientation, sky_sign * sqrt(1.0 - dot(params.sky_color_or_orientation, params.sky_color_or_orientation)));
vec3 sky_dir = cross(sky_quat.xyz, ray_dir);
sky_dir = ray_dir + ((sky_dir * sky_quat.w) + cross(sky_quat.xyz, sky_dir)) * 2.0;
#ifdef USE_CUBEMAP_ARRAY
light.rgb = textureLod(samplerCubeArray(sky_irradiance, linear_sampler_mipmaps), vec4(sky_dir, 0.0), 2.0).rgb; // Use second mipmap because we don't usually throw a lot of rays, so this compensates.
#else
light.rgb = textureLod(samplerCube(sky_irradiance, linear_sampler_mipmaps), sky_dir, 2.0).rgb; // Use second mipmap because we don't usually throw a lot of rays, so this compensates.
#endif
light.rgb *= params.sky_energy;
light.a = 0.0;
} else if (bool(params.sky_flags & SKY_FLAGS_MODE_COLOR)) {
light.rgb = params.sky_color_or_orientation;
light.rgb *= params.sky_energy;
light.a = 0.0;
} else {
light = vec4(0, 0, 0, 0);
}
vec3 ray_dir2 = ray_dir * ray_dir;
#define SH_ACCUM(m_idx, m_value) \
{ \
vec3 l = light.rgb * (m_value); \
sh_accum[probe_index].c[m_idx * 3 + 0] += l.r; \
sh_accum[probe_index].c[m_idx * 3 + 1] += l.g; \
sh_accum[probe_index].c[m_idx * 3 + 2] += l.b; \
}
SH_ACCUM(0, 0.282095); //l0
SH_ACCUM(1, 0.488603 * ray_dir.y); //l1n1
SH_ACCUM(2, 0.488603 * ray_dir.z); //l1n0
SH_ACCUM(3, 0.488603 * ray_dir.x); //l1p1
SH_ACCUM(4, 1.092548 * ray_dir.x * ray_dir.y); //l2n2
SH_ACCUM(5, 1.092548 * ray_dir.y * ray_dir.z); //l2n1
SH_ACCUM(6, 0.315392 * (3.0 * ray_dir2.z - 1.0)); //l20
SH_ACCUM(7, 1.092548 * ray_dir.x * ray_dir.z); //l2p1
SH_ACCUM(8, 0.546274 * (ray_dir2.x - ray_dir2.y)); //l2p2
#if (SH_SIZE == 16)
SH_ACCUM(9, 0.590043 * ray_dir.y * (3.0f * ray_dir2.x - ray_dir2.y));
SH_ACCUM(10, 2.890611 * ray_dir.y * ray_dir.x * ray_dir.z);
SH_ACCUM(11, 0.646360 * ray_dir.y * (-1.0f + 5.0f * ray_dir2.z));
SH_ACCUM(12, 0.373176 * (5.0f * ray_dir2.z * ray_dir.z - 3.0f * ray_dir.z));
SH_ACCUM(13, 0.457045 * ray_dir.x * (-1.0f + 5.0f * ray_dir2.z));
SH_ACCUM(14, 1.445305 * (ray_dir2.x - ray_dir2.y) * ray_dir.z);
SH_ACCUM(15, 0.590043 * ray_dir.x * (ray_dir2.x - 3.0f * ray_dir2.y));
#endif
}
for (uint i = 0; i < SH_SIZE; i++) {
// store in history texture
ivec3 prev_pos = ivec3(pos.x, pos.y * SH_SIZE + i, int(params.history_index));
ivec2 average_pos = prev_pos.xy;
vec4 value = vec4(sh_accum[probe_index].c[i * 3 + 0], sh_accum[probe_index].c[i * 3 + 1], sh_accum[probe_index].c[i * 3 + 2], 1.0) * 4.0 / float(params.ray_count);
ivec4 ivalue = clamp(ivec4(value * float(1 << HISTORY_BITS)), -32768, 32767); //clamp to 16 bits, so higher values don't break average
ivec4 prev_value = imageLoad(lightprobe_history_texture, prev_pos);
ivec4 average = imageLoad(lightprobe_average_texture, average_pos);
average -= prev_value;
average += ivalue;
imageStore(lightprobe_history_texture, prev_pos, ivalue);
imageStore(lightprobe_average_texture, average_pos, average);
if (params.store_ambient_texture && i == 0) {
ivec3 ambient_pos = ivec3(pos, int(params.cascade));
vec4 ambient_light = (vec4(average) / float(params.history_size)) / float(1 << HISTORY_BITS);
ambient_light *= 0.88622; // SHL0
imageStore(lightprobe_ambient_texture, ambient_pos, ambient_light);
}
}
#endif // MODE PROCESS
#ifdef MODE_STORE
// converting to octahedral in this step is required because
// octahedral is much faster to read from the screen than spherical harmonics,
// despite the very slight quality loss
ivec2 sh_pos = (pos / OCT_SIZE) * ivec2(1, SH_SIZE);
ivec2 oct_pos = (pos / OCT_SIZE) * (OCT_SIZE + 2) + ivec2(1);
ivec2 local_pos = pos % OCT_SIZE;
//compute the octahedral normal for this texel
vec3 normal = octahedron_encode(vec2(local_pos) / float(OCT_SIZE));
// read the spherical harmonic
vec3 normal2 = normal * normal;
float c[SH_SIZE] = float[](
0.282095, //l0
0.488603 * normal.y, //l1n1
0.488603 * normal.z, //l1n0
0.488603 * normal.x, //l1p1
1.092548 * normal.x * normal.y, //l2n2
1.092548 * normal.y * normal.z, //l2n1
0.315392 * (3.0 * normal2.z - 1.0), //l20
1.092548 * normal.x * normal.z, //l2p1
0.546274 * (normal2.x - normal2.y) //l2p2
#if (SH_SIZE == 16)
,
0.590043 * normal.y * (3.0f * normal2.x - normal2.y),
2.890611 * normal.y * normal.x * normal.z,
0.646360 * normal.y * (-1.0f + 5.0f * normal2.z),
0.373176 * (5.0f * normal2.z * normal.z - 3.0f * normal.z),
0.457045 * normal.x * (-1.0f + 5.0f * normal2.z),
1.445305 * (normal2.x - normal2.y) * normal.z,
0.590043 * normal.x * (normal2.x - 3.0f * normal2.y)
#endif
);
const float l_mult[SH_SIZE] = float[](
1.0,
2.0 / 3.0,
2.0 / 3.0,
2.0 / 3.0,
1.0 / 4.0,
1.0 / 4.0,
1.0 / 4.0,
1.0 / 4.0,
1.0 / 4.0
#if (SH_SIZE == 16)
, // l4 does not contribute to irradiance
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
#endif
);
vec3 irradiance = vec3(0.0);
vec3 radiance = vec3(0.0);
for (uint i = 0; i < SH_SIZE; i++) {
// store in history texture
ivec2 average_pos = sh_pos + ivec2(0, i);
ivec4 average = imageLoad(lightprobe_average_texture, average_pos);
vec4 sh = (vec4(average) / float(params.history_size)) / float(1 << HISTORY_BITS);
vec3 m = sh.rgb * c[i] * 4.0;
irradiance += m * l_mult[i];
radiance += m;
}
//encode RGBE9995 for the final texture
uint irradiance_rgbe = rgbe_encode(irradiance);
uint radiance_rgbe = rgbe_encode(radiance);
//store in octahedral map
ivec3 texture_pos = ivec3(oct_pos, int(params.cascade));
ivec3 copy_to[4] = ivec3[](ivec3(-2, -2, -2), ivec3(-2, -2, -2), ivec3(-2, -2, -2), ivec3(-2, -2, -2));
copy_to[0] = texture_pos + ivec3(local_pos, 0);
if (local_pos == ivec2(0, 0)) {
copy_to[1] = texture_pos + ivec3(OCT_SIZE - 1, -1, 0);
copy_to[2] = texture_pos + ivec3(-1, OCT_SIZE - 1, 0);
copy_to[3] = texture_pos + ivec3(OCT_SIZE, OCT_SIZE, 0);
} else if (local_pos == ivec2(OCT_SIZE - 1, 0)) {
copy_to[1] = texture_pos + ivec3(0, -1, 0);
copy_to[2] = texture_pos + ivec3(OCT_SIZE, OCT_SIZE - 1, 0);
copy_to[3] = texture_pos + ivec3(-1, OCT_SIZE, 0);
} else if (local_pos == ivec2(0, OCT_SIZE - 1)) {
copy_to[1] = texture_pos + ivec3(-1, 0, 0);
copy_to[2] = texture_pos + ivec3(OCT_SIZE - 1, OCT_SIZE, 0);
copy_to[3] = texture_pos + ivec3(OCT_SIZE, -1, 0);
} else if (local_pos == ivec2(OCT_SIZE - 1, OCT_SIZE - 1)) {
copy_to[1] = texture_pos + ivec3(0, OCT_SIZE, 0);
copy_to[2] = texture_pos + ivec3(OCT_SIZE, 0, 0);
copy_to[3] = texture_pos + ivec3(-1, -1, 0);
} else if (local_pos.y == 0) {
copy_to[1] = texture_pos + ivec3(OCT_SIZE - local_pos.x - 1, local_pos.y - 1, 0);
} else if (local_pos.x == 0) {
copy_to[1] = texture_pos + ivec3(local_pos.x - 1, OCT_SIZE - local_pos.y - 1, 0);
} else if (local_pos.y == OCT_SIZE - 1) {
copy_to[1] = texture_pos + ivec3(OCT_SIZE - local_pos.x - 1, local_pos.y + 1, 0);
} else if (local_pos.x == OCT_SIZE - 1) {
copy_to[1] = texture_pos + ivec3(local_pos.x + 1, OCT_SIZE - local_pos.y - 1, 0);
}
for (int i = 0; i < 4; i++) {
if (copy_to[i] == ivec3(-2, -2, -2)) {
continue;
}
imageStore(lightprobe_texture_data, copy_to[i], uvec4(irradiance_rgbe));
imageStore(lightprobe_texture_data, copy_to[i] + ivec3(0, 0, int(params.max_cascades)), uvec4(radiance_rgbe));
}
#endif
#ifdef MODE_SCROLL
ivec3 probe_cell;
probe_cell.x = pos.x % int(params.probe_axis_size);
probe_cell.y = pos.y;
probe_cell.z = pos.x / int(params.probe_axis_size);
ivec3 read_probe = probe_cell - params.scroll;
if (all(greaterThanEqual(read_probe, ivec3(0))) && all(lessThan(read_probe, ivec3(params.probe_axis_size)))) {
// can scroll
ivec2 tex_pos;
tex_pos = read_probe.xy;
tex_pos.x += read_probe.z * int(params.probe_axis_size);
//scroll
for (uint j = 0; j < params.history_size; j++) {
for (int i = 0; i < SH_SIZE; i++) {
// copy from history texture
ivec3 src_pos = ivec3(tex_pos.x, tex_pos.y * SH_SIZE + i, int(j));
ivec3 dst_pos = ivec3(pos.x, pos.y * SH_SIZE + i, int(j));
ivec4 value = imageLoad(lightprobe_history_texture, src_pos);
imageStore(lightprobe_history_scroll_texture, dst_pos, value);
}
}
for (int i = 0; i < SH_SIZE; i++) {
// copy from average texture
ivec2 src_pos = ivec2(tex_pos.x, tex_pos.y * SH_SIZE + i);
ivec2 dst_pos = ivec2(pos.x, pos.y * SH_SIZE + i);
ivec4 value = imageLoad(lightprobe_average_texture, src_pos);
imageStore(lightprobe_average_scroll_texture, dst_pos, value);
}
} else if (params.cascade < params.max_cascades - 1) {
//can't scroll, must look for position in parent cascade
//to global coords
float cell_to_probe = float(params.grid_size.x / float(params.probe_axis_size - 1));
float probe_cell_size = cell_to_probe / cascades.data[params.cascade].to_cell;
vec3 probe_pos = cascades.data[params.cascade].offset + vec3(probe_cell) * probe_cell_size;
//to parent local coords
float probe_cell_size_next = cell_to_probe / cascades.data[params.cascade + 1].to_cell;
probe_pos -= cascades.data[params.cascade + 1].offset;
probe_pos /= probe_cell_size_next;
ivec3 probe_posi = ivec3(probe_pos);
//add up all light, no need to use occlusion here, since occlusion will do its work afterwards
vec4 average_light[SH_SIZE] = vec4[](vec4(0), vec4(0), vec4(0), vec4(0), vec4(0), vec4(0), vec4(0), vec4(0), vec4(0)
#if (SH_SIZE == 16)
,
vec4(0), vec4(0), vec4(0), vec4(0), vec4(0), vec4(0), vec4(0)
#endif
);
float total_weight = 0.0;
for (int i = 0; i < 8; i++) {
ivec3 offset = probe_posi + ((ivec3(i) >> ivec3(0, 1, 2)) & ivec3(1, 1, 1));
vec3 trilinear = vec3(1.0) - abs(probe_pos - vec3(offset));
float weight = trilinear.x * trilinear.y * trilinear.z;
ivec2 tex_pos;
tex_pos = offset.xy;
tex_pos.x += offset.z * int(params.probe_axis_size);
for (int j = 0; j < SH_SIZE; j++) {
// copy from history texture
ivec2 src_pos = ivec2(tex_pos.x, tex_pos.y * SH_SIZE + j);
ivec4 average = imageLoad(lightprobe_average_parent_texture, src_pos);
vec4 value = (vec4(average) / float(params.history_size)) / float(1 << HISTORY_BITS);
average_light[j] += value * weight;
}
total_weight += weight;
}
if (total_weight > 0.0) {
total_weight = 1.0 / total_weight;
}
//store the averaged values everywhere
for (int i = 0; i < SH_SIZE; i++) {
ivec4 ivalue = clamp(ivec4(average_light[i] * total_weight * float(1 << HISTORY_BITS)), ivec4(-32768), ivec4(32767)); //clamp to 16 bits, so higher values don't break average
// copy from history texture
ivec3 dst_pos = ivec3(pos.x, pos.y * SH_SIZE + i, 0);
for (uint j = 0; j < params.history_size; j++) {
dst_pos.z = int(j);
imageStore(lightprobe_history_scroll_texture, dst_pos, ivalue);
}
ivalue *= int(params.history_size); //average needs to have all history added up
imageStore(lightprobe_average_scroll_texture, dst_pos.xy, ivalue);
}
} else {
//scroll at the edge of the highest cascade, just copy what is there,
//since its the closest we have anyway
for (uint j = 0; j < params.history_size; j++) {
ivec2 tex_pos;
tex_pos = probe_cell.xy;
tex_pos.x += probe_cell.z * int(params.probe_axis_size);
for (int i = 0; i < SH_SIZE; i++) {
// copy from history texture
ivec3 src_pos = ivec3(tex_pos.x, tex_pos.y * SH_SIZE + i, int(j));
ivec3 dst_pos = ivec3(pos.x, pos.y * SH_SIZE + i, int(j));
ivec4 value = imageLoad(lightprobe_history_texture, dst_pos);
imageStore(lightprobe_history_scroll_texture, dst_pos, value);
}
}
for (int i = 0; i < SH_SIZE; i++) {
// copy from average texture
ivec2 spos = ivec2(pos.x, pos.y * SH_SIZE + i);
ivec4 average = imageLoad(lightprobe_average_texture, spos);
imageStore(lightprobe_average_scroll_texture, spos, average);
}
}
#endif
#ifdef MODE_SCROLL_STORE
//do not update probe texture, as these will be updated later
for (uint j = 0; j < params.history_size; j++) {
for (int i = 0; i < SH_SIZE; i++) {
// copy from history texture
ivec3 spos = ivec3(pos.x, pos.y * SH_SIZE + i, int(j));
ivec4 value = imageLoad(lightprobe_history_scroll_texture, spos);
imageStore(lightprobe_history_texture, spos, value);
}
}
for (int i = 0; i < SH_SIZE; i++) {
// copy from average texture
ivec2 spos = ivec2(pos.x, pos.y * SH_SIZE + i);
ivec4 average = imageLoad(lightprobe_average_scroll_texture, spos);
imageStore(lightprobe_average_texture, spos, average);
}
#endif
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,306 @@
#[vertex]
#version 450
#VERSION_DEFINES
layout(location = 0) out vec2 uv_interp;
layout(push_constant, std430) uniform Params {
mat3 orientation;
vec4 projection; // only applicable if not multiview
vec3 position;
float time;
vec2 pad;
float luminance_multiplier;
float brightness_multiplier;
}
params;
void main() {
vec2 base_arr[3] = vec2[](vec2(-1.0, -3.0), vec2(-1.0, 1.0), vec2(3.0, 1.0));
uv_interp = base_arr[gl_VertexIndex];
gl_Position = vec4(uv_interp, 0.0, 1.0);
}
#[fragment]
#version 450
#VERSION_DEFINES
#ifdef USE_MULTIVIEW
#extension GL_EXT_multiview : enable
#define ViewIndex gl_ViewIndex
#endif
#define M_PI 3.14159265359
layout(location = 0) in vec2 uv_interp;
layout(push_constant, std430) uniform Params {
mat3 orientation;
vec4 projection; // only applicable if not multiview
vec3 position;
float time;
vec2 pad;
float luminance_multiplier;
float brightness_multiplier;
}
params;
#include "../samplers_inc.glsl"
layout(set = 0, binding = 1, std430) restrict readonly buffer GlobalShaderUniformData {
vec4 data[];
}
global_shader_uniforms;
layout(set = 0, binding = 2, std140) uniform SkySceneData {
mat4 combined_reprojection[2];
mat4 view_inv_projections[2];
vec4 view_eye_offsets[2];
bool volumetric_fog_enabled; // 4 - 4
float volumetric_fog_inv_length; // 4 - 8
float volumetric_fog_detail_spread; // 4 - 12
float volumetric_fog_sky_affect; // 4 - 16
bool fog_enabled; // 4 - 20
float fog_sky_affect; // 4 - 24
float fog_density; // 4 - 28
float fog_sun_scatter; // 4 - 32
vec3 fog_light_color; // 12 - 44
float fog_aerial_perspective; // 4 - 48
float z_far; // 4 - 52
uint directional_light_count; // 4 - 56
uint pad1; // 4 - 60
uint pad2; // 4 - 64
}
sky_scene_data;
struct DirectionalLightData {
vec4 direction_energy;
vec4 color_size;
bool enabled;
};
layout(set = 0, binding = 3, std140) uniform DirectionalLights {
DirectionalLightData data[MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS];
}
directional_lights;
#ifdef MATERIAL_UNIFORMS_USED
/* clang-format off */
layout(set = 1, binding = 0, std140) uniform MaterialUniforms {
#MATERIAL_UNIFORMS
} material;
/* clang-format on */
#endif
layout(set = 2, binding = 0) uniform textureCube radiance;
#ifdef USE_CUBEMAP_PASS
layout(set = 2, binding = 1) uniform textureCube half_res;
layout(set = 2, binding = 2) uniform textureCube quarter_res;
#elif defined(USE_MULTIVIEW)
layout(set = 2, binding = 1) uniform texture2DArray half_res;
layout(set = 2, binding = 2) uniform texture2DArray quarter_res;
#else
layout(set = 2, binding = 1) uniform texture2D half_res;
layout(set = 2, binding = 2) uniform texture2D quarter_res;
#endif
layout(set = 3, binding = 0) uniform texture3D volumetric_fog_texture;
#ifdef USE_CUBEMAP_PASS
#define AT_CUBEMAP_PASS true
#else
#define AT_CUBEMAP_PASS false
#endif
#ifdef USE_HALF_RES_PASS
#define AT_HALF_RES_PASS true
#else
#define AT_HALF_RES_PASS false
#endif
#ifdef USE_QUARTER_RES_PASS
#define AT_QUARTER_RES_PASS true
#else
#define AT_QUARTER_RES_PASS false
#endif
#GLOBALS
layout(location = 0) out vec4 frag_color;
#ifdef USE_DEBANDING
// https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare
vec3 interleaved_gradient_noise(vec2 pos) {
const vec3 magic = vec3(0.06711056f, 0.00583715f, 52.9829189f);
float res = fract(magic.z * fract(dot(pos, magic.xy))) * 2.0 - 1.0;
return vec3(res, -res, res) / 255.0;
}
#endif
vec4 volumetric_fog_process(vec2 screen_uv) {
#ifdef USE_MULTIVIEW
vec4 reprojected = sky_scene_data.combined_reprojection[ViewIndex] * vec4(screen_uv * 2.0 - 1.0, 0.0, 1.0); // Unproject at the far plane
vec3 fog_pos = vec3(reprojected.xy / reprojected.w, 1.0) * 0.5 + 0.5;
#else
vec3 fog_pos = vec3(screen_uv, 1.0);
#endif
return texture(sampler3D(volumetric_fog_texture, SAMPLER_LINEAR_CLAMP), fog_pos);
}
vec4 fog_process(vec3 view, vec3 sky_color) {
vec3 fog_color = mix(sky_scene_data.fog_light_color, sky_color, sky_scene_data.fog_aerial_perspective);
if (sky_scene_data.fog_sun_scatter > 0.001) {
vec4 sun_scatter = vec4(0.0);
float sun_total = 0.0;
for (uint i = 0; i < sky_scene_data.directional_light_count; i++) {
vec3 light_color = directional_lights.data[i].color_size.xyz * directional_lights.data[i].direction_energy.w;
float light_amount = pow(max(dot(view, directional_lights.data[i].direction_energy.xyz), 0.0), 8.0) * M_PI;
fog_color += light_color * light_amount * sky_scene_data.fog_sun_scatter;
}
}
return vec4(fog_color, 1.0);
}
// Eberly approximation from https://seblagarde.wordpress.com/2014/12/01/inverse-trigonometric-functions-gpu-optimization-for-amd-gcn-architecture/.
// input [-1, 1] and output [0, PI]
float acos_approx(float p_x) {
float x = abs(p_x);
float res = -0.156583f * x + (M_PI / 2.0);
res *= sqrt(1.0f - x);
return (p_x >= 0.0) ? res : M_PI - res;
}
// Based on https://math.stackexchange.com/questions/1098487/atan2-faster-approximation
// but using the Eberly coefficients from https://seblagarde.wordpress.com/2014/12/01/inverse-trigonometric-functions-gpu-optimization-for-amd-gcn-architecture/.
float atan2_approx(float y, float x) {
float a = min(abs(x), abs(y)) / max(abs(x), abs(y));
float s = a * a;
float poly = 0.0872929f;
poly = -0.301895f + poly * s;
poly = 1.0f + poly * s;
poly = poly * a;
float r = abs(y) > abs(x) ? (M_PI / 2.0) - poly : poly;
r = x < 0.0 ? M_PI - r : r;
r = y < 0.0 ? -r : r;
return r;
}
void main() {
vec3 cube_normal;
#ifdef USE_MULTIVIEW
// In multiview our projection matrices will contain positional and rotational offsets that we need to properly unproject.
vec4 unproject = vec4(uv_interp.x, uv_interp.y, 0.0, 1.0); // unproject at the far plane
vec4 unprojected = sky_scene_data.view_inv_projections[ViewIndex] * unproject;
cube_normal = unprojected.xyz / unprojected.w;
// Unproject will give us the position between the eyes, need to re-offset
cube_normal += sky_scene_data.view_eye_offsets[ViewIndex].xyz;
#else
cube_normal.z = -1.0;
cube_normal.x = (cube_normal.z * (-uv_interp.x - params.projection.x)) / params.projection.y;
cube_normal.y = -(cube_normal.z * (uv_interp.y - params.projection.z)) / params.projection.w;
#endif
cube_normal = mat3(params.orientation) * cube_normal;
cube_normal = normalize(cube_normal);
vec2 uv = uv_interp * 0.5 + 0.5;
vec2 panorama_coords = vec2(atan2_approx(cube_normal.x, -cube_normal.z), acos_approx(cube_normal.y));
if (panorama_coords.x < 0.0) {
panorama_coords.x += M_PI * 2.0;
}
panorama_coords /= vec2(M_PI * 2.0, M_PI);
vec3 color = vec3(0.0, 0.0, 0.0);
float alpha = 1.0; // Only available to subpasses
vec4 half_res_color = vec4(1.0);
vec4 quarter_res_color = vec4(1.0);
vec4 custom_fog = vec4(0.0);
#ifdef USE_CUBEMAP_PASS
#ifdef USES_HALF_RES_COLOR
half_res_color = texture(samplerCube(half_res, SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), cube_normal) / params.luminance_multiplier;
#endif
#ifdef USES_QUARTER_RES_COLOR
quarter_res_color = texture(samplerCube(quarter_res, SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), cube_normal) / params.luminance_multiplier;
#endif
#else
#ifdef USES_HALF_RES_COLOR
#ifdef USE_MULTIVIEW
half_res_color = textureLod(sampler2DArray(half_res, SAMPLER_LINEAR_CLAMP), vec3(uv, ViewIndex), 0.0) / params.luminance_multiplier;
#else
half_res_color = textureLod(sampler2D(half_res, SAMPLER_LINEAR_CLAMP), uv, 0.0) / params.luminance_multiplier;
#endif // USE_MULTIVIEW
#endif // USES_HALF_RES_COLOR
#ifdef USES_QUARTER_RES_COLOR
#ifdef USE_MULTIVIEW
quarter_res_color = textureLod(sampler2DArray(quarter_res, SAMPLER_LINEAR_CLAMP), vec3(uv, ViewIndex), 0.0) / params.luminance_multiplier;
#else
quarter_res_color = textureLod(sampler2D(quarter_res, SAMPLER_LINEAR_CLAMP), uv, 0.0) / params.luminance_multiplier;
#endif // USE_MULTIVIEW
#endif // USES_QUARTER_RES_COLOR
#endif //USE_CUBEMAP_PASS
{
#CODE : SKY
}
frag_color.rgb = color;
frag_color.a = alpha;
// Apply environment 'brightness' setting separately before fog to ensure consistent luminance.
frag_color.rgb = frag_color.rgb * params.brightness_multiplier;
#if !defined(DISABLE_FOG) && !defined(USE_CUBEMAP_PASS)
// Draw "fixed" fog before volumetric fog to ensure volumetric fog can appear in front of the sky.
if (sky_scene_data.fog_enabled) {
vec4 fog = fog_process(cube_normal, frag_color.rgb);
frag_color.rgb = mix(frag_color.rgb, fog.rgb, fog.a * sky_scene_data.fog_sky_affect);
}
if (sky_scene_data.volumetric_fog_enabled) {
vec4 fog = volumetric_fog_process(uv);
frag_color.rgb = mix(frag_color.rgb, fog.rgb, fog.a * sky_scene_data.volumetric_fog_sky_affect);
}
if (custom_fog.a > 0.0) {
frag_color.rgb = mix(frag_color.rgb, custom_fog.rgb, custom_fog.a);
}
#endif // DISABLE_FOG
// For mobile renderer we're multiplying by 0.5 as we're using a UNORM buffer.
// For both mobile and clustered, we also bake in the exposure value for the environment and camera.
frag_color.rgb = frag_color.rgb * params.luminance_multiplier;
// Blending is disabled for Sky, so alpha doesn't blend.
// Alpha is used for subsurface scattering so make sure it doesn't get applied to Sky.
if (!AT_CUBEMAP_PASS && !AT_HALF_RES_PASS && !AT_QUARTER_RES_PASS) {
frag_color.a = 0.0;
}
#ifdef USE_DEBANDING
frag_color.rgb += interleaved_gradient_noise(gl_FragCoord.xy) * params.luminance_multiplier;
#endif
}

View File

@@ -0,0 +1,303 @@
#[compute]
#version 450
#VERSION_DEFINES
#ifdef USE_VULKAN_MEMORY_MODEL
#pragma use_vulkan_memory_model
#endif
layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
#define DENSITY_SCALE 1024.0
#include "../cluster_data_inc.glsl"
#include "../light_data_inc.glsl"
#define M_PI 3.14159265359
#include "../samplers_inc.glsl"
layout(set = 0, binding = 2, std430) restrict readonly buffer GlobalShaderUniformData {
vec4 data[];
}
global_shader_uniforms;
layout(push_constant, std430) uniform Params {
vec3 position;
float pad;
vec3 size;
float pad2;
ivec3 corner;
uint shape;
mat4 transform;
}
params;
#ifdef NO_IMAGE_ATOMICS
layout(set = 1, binding = 1) volatile buffer emissive_only_map_buffer {
uint emissive_only_map[];
};
#else
layout(r32ui, set = 1, binding = 1) uniform volatile uimage3D emissive_only_map;
#endif
layout(set = 1, binding = 2, std140) uniform SceneParams {
vec2 fog_frustum_size_begin;
vec2 fog_frustum_size_end;
float fog_frustum_end;
float z_near; //
float z_far; //
float time;
ivec3 fog_volume_size;
uint directional_light_count; //
bool use_temporal_reprojection;
uint temporal_frame;
float detail_spread;
float temporal_blend;
mat4 to_prev_view;
mat4 transform;
}
scene_params;
#ifdef NO_IMAGE_ATOMICS
layout(set = 1, binding = 3) volatile buffer density_only_map_buffer {
uint density_only_map[];
};
layout(set = 1, binding = 4) volatile buffer light_only_map_buffer {
uint light_only_map[];
};
#else
layout(r32ui, set = 1, binding = 3) uniform volatile uimage3D density_only_map;
layout(r32ui, set = 1, binding = 4) uniform volatile uimage3D light_only_map;
#endif
#ifdef MATERIAL_UNIFORMS_USED
/* clang-format off */
layout(set = 2, binding = 0, std140) uniform MaterialUniforms {
#MATERIAL_UNIFORMS
} material;
/* clang-format on */
#endif
#GLOBALS
float get_depth_at_pos(float cell_depth_size, int z) {
float d = float(z) * cell_depth_size + cell_depth_size * 0.5; //center of voxels
d = pow(d, scene_params.detail_spread);
return scene_params.fog_frustum_end * d;
}
#define TEMPORAL_FRAMES 16
const vec3 halton_map[TEMPORAL_FRAMES] = vec3[](
vec3(0.5, 0.33333333, 0.2),
vec3(0.25, 0.66666667, 0.4),
vec3(0.75, 0.11111111, 0.6),
vec3(0.125, 0.44444444, 0.8),
vec3(0.625, 0.77777778, 0.04),
vec3(0.375, 0.22222222, 0.24),
vec3(0.875, 0.55555556, 0.44),
vec3(0.0625, 0.88888889, 0.64),
vec3(0.5625, 0.03703704, 0.84),
vec3(0.3125, 0.37037037, 0.08),
vec3(0.8125, 0.7037037, 0.28),
vec3(0.1875, 0.14814815, 0.48),
vec3(0.6875, 0.48148148, 0.68),
vec3(0.4375, 0.81481481, 0.88),
vec3(0.9375, 0.25925926, 0.12),
vec3(0.03125, 0.59259259, 0.32));
void main() {
vec3 fog_cell_size = 1.0 / vec3(scene_params.fog_volume_size);
ivec3 pos = ivec3(gl_GlobalInvocationID.xyz) + params.corner;
if (any(greaterThanEqual(pos, scene_params.fog_volume_size))) {
return; //do not compute
}
#ifdef NO_IMAGE_ATOMICS
uint lpos = pos.z * scene_params.fog_volume_size.x * scene_params.fog_volume_size.y + pos.y * scene_params.fog_volume_size.x + pos.x;
#endif
vec3 posf = vec3(pos);
vec3 fog_unit_pos = posf * fog_cell_size + fog_cell_size * 0.5; //center of voxels
fog_unit_pos.z = pow(fog_unit_pos.z, scene_params.detail_spread);
vec3 view_pos;
view_pos.xy = (fog_unit_pos.xy * 2.0 - 1.0) * mix(scene_params.fog_frustum_size_begin, scene_params.fog_frustum_size_end, vec2(fog_unit_pos.z));
view_pos.z = -scene_params.fog_frustum_end * fog_unit_pos.z;
view_pos.y = -view_pos.y;
if (scene_params.use_temporal_reprojection) {
vec3 prev_view = (scene_params.to_prev_view * vec4(view_pos, 1.0)).xyz;
//undo transform into prev view
prev_view.y = -prev_view.y;
//z back to unit size
prev_view.z /= -scene_params.fog_frustum_end;
//xy back to unit size
prev_view.xy /= mix(scene_params.fog_frustum_size_begin, scene_params.fog_frustum_size_end, vec2(prev_view.z));
prev_view.xy = prev_view.xy * 0.5 + 0.5;
//z back to unspread value
prev_view.z = pow(prev_view.z, 1.0 / scene_params.detail_spread);
if (all(greaterThan(prev_view, vec3(0.0))) && all(lessThan(prev_view, vec3(1.0)))) {
//reprojectinon fits
// Since we can reproject, now we must jitter the current view pos.
// This is done here because cells that can't reproject should not jitter.
fog_unit_pos = posf * fog_cell_size + fog_cell_size * halton_map[scene_params.temporal_frame]; //center of voxels, offset by halton table
fog_unit_pos.z = pow(fog_unit_pos.z, scene_params.detail_spread);
view_pos.xy = (fog_unit_pos.xy * 2.0 - 1.0) * mix(scene_params.fog_frustum_size_begin, scene_params.fog_frustum_size_end, vec2(fog_unit_pos.z));
view_pos.z = -scene_params.fog_frustum_end * fog_unit_pos.z;
view_pos.y = -view_pos.y;
}
}
float density = 0.0;
vec3 emission = vec3(0.0);
vec3 albedo = vec3(0.0);
float cell_depth_size = abs(view_pos.z - get_depth_at_pos(fog_cell_size.z, pos.z + 1));
vec4 world = scene_params.transform * vec4(view_pos, 1.0);
world.xyz /= world.w;
vec3 uvw = fog_unit_pos;
vec4 local_pos = params.transform * world;
local_pos.xyz /= local_pos.w;
vec3 half_size = params.size / 2.0;
float sdf = -1.0;
if (params.shape == 0) {
// Ellipsoid
// https://www.shadertoy.com/view/tdS3DG
float k0 = length(local_pos.xyz / half_size);
float k1 = length(local_pos.xyz / (half_size * half_size));
sdf = k0 * (k0 - 1.0) / k1;
} else if (params.shape == 1) {
// Cone
// https://iquilezles.org/www/articles/distfunctions/distfunctions.htm
// Compute the cone angle automatically to fit within the volume's size.
float inv_height = 1.0 / max(0.001, half_size.y);
float radius = 1.0 / max(0.001, (min(half_size.x, half_size.z) * 0.5));
float hypotenuse = sqrt(radius * radius + inv_height * inv_height);
float rsin = radius / hypotenuse;
float rcos = inv_height / hypotenuse;
vec2 c = vec2(rsin, rcos);
float q = length(local_pos.xz);
sdf = max(dot(c, vec2(q, local_pos.y - half_size.y)), -half_size.y - local_pos.y);
} else if (params.shape == 2) {
// Cylinder
// https://iquilezles.org/www/articles/distfunctions/distfunctions.htm
vec2 d = abs(vec2(length(local_pos.xz), local_pos.y)) - vec2(min(half_size.x, half_size.z), half_size.y);
sdf = min(max(d.x, d.y), 0.0) + length(max(d, 0.0));
} else if (params.shape == 3) {
// Box
// https://iquilezles.org/www/articles/distfunctions/distfunctions.htm
vec3 q = abs(local_pos.xyz) - half_size;
sdf = length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0);
}
float cull_mask = 1.0; //used to cull cells that do not contribute
if (params.shape <= 3) {
#ifndef SDF_USED
cull_mask = 1.0 - smoothstep(-0.1, 0.0, sdf);
#endif
uvw = clamp((local_pos.xyz + half_size) / params.size, 0.0, 1.0);
}
if (cull_mask > 0.0) {
{
#CODE : FOG
}
#ifdef DENSITY_USED
density *= cull_mask;
if (abs(density) > 0.001) {
int final_density = int(density * DENSITY_SCALE);
#ifdef NO_IMAGE_ATOMICS
atomicAdd(density_only_map[lpos], uint(final_density));
#else
imageAtomicAdd(density_only_map, pos, uint(final_density));
#endif
#ifdef EMISSION_USED
{
emission *= clamp(density, 0.0, 1.0);
emission = clamp(emission, vec3(0.0), vec3(4.0));
// Scale to fit into R11G11B10 with a range of 0-4
uvec3 emission_u = uvec3(emission.r * 511.0, emission.g * 511.0, emission.b * 255.0);
// R and G have 11 bits each and B has 10. Then pack them into a 32 bit uint
uint final_emission = emission_u.r << 21 | emission_u.g << 10 | emission_u.b;
#ifdef NO_IMAGE_ATOMICS
uint prev_emission = atomicAdd(emissive_only_map[lpos], final_emission);
#else
uint prev_emission = imageAtomicAdd(emissive_only_map, pos, final_emission);
#endif
// Adding can lead to colors overflowing, so validate
uvec3 prev_emission_u = uvec3(prev_emission >> 21, (prev_emission << 11) >> 21, prev_emission % 1024);
uint add_emission = final_emission + prev_emission;
uvec3 add_emission_u = uvec3(add_emission >> 21, (add_emission << 11) >> 21, add_emission % 1024);
bvec3 overflowing = lessThan(add_emission_u, prev_emission_u + emission_u);
if (any(overflowing)) {
uvec3 overflow_factor = mix(uvec3(0), uvec3(2047 << 21, 2047 << 10, 1023), overflowing);
uint force_max = overflow_factor.r | overflow_factor.g | overflow_factor.b;
#ifdef NO_IMAGE_ATOMICS
atomicOr(emissive_only_map[lpos], force_max);
#else
imageAtomicOr(emissive_only_map, pos, force_max);
#endif
}
}
#endif
#ifdef ALBEDO_USED
{
vec3 scattering = albedo * clamp(density, 0.0, 1.0);
scattering = clamp(scattering, vec3(0.0), vec3(1.0));
uvec3 scattering_u = uvec3(scattering.r * 2047.0, scattering.g * 2047.0, scattering.b * 1023.0);
// R and G have 11 bits each and B has 10. Then pack them into a 32 bit uint
uint final_scattering = scattering_u.r << 21 | scattering_u.g << 10 | scattering_u.b;
#ifdef NO_IMAGE_ATOMICS
uint prev_scattering = atomicAdd(light_only_map[lpos], final_scattering);
#else
uint prev_scattering = imageAtomicAdd(light_only_map, pos, final_scattering);
#endif
// Adding can lead to colors overflowing, so validate
uvec3 prev_scattering_u = uvec3(prev_scattering >> 21, (prev_scattering << 11) >> 21, prev_scattering % 1024);
uint add_scattering = final_scattering + prev_scattering;
uvec3 add_scattering_u = uvec3(add_scattering >> 21, (add_scattering << 11) >> 21, add_scattering % 1024);
bvec3 overflowing = lessThan(add_scattering_u, prev_scattering_u + scattering_u);
if (any(overflowing)) {
uvec3 overflow_factor = mix(uvec3(0), uvec3(2047 << 21, 2047 << 10, 1023), overflowing);
uint force_max = overflow_factor.r | overflow_factor.g | overflow_factor.b;
#ifdef NO_IMAGE_ATOMICS
atomicOr(light_only_map[lpos], force_max);
#else
imageAtomicOr(light_only_map, pos, force_max);
#endif
}
}
#endif // ALBEDO_USED
}
#endif // DENSITY_USED
}
}

View File

@@ -0,0 +1,753 @@
#[compute]
#version 450
#VERSION_DEFINES
#ifdef USE_VULKAN_MEMORY_MODEL
#pragma use_vulkan_memory_model
#endif
#ifdef MODE_DENSITY
layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
#else
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
#endif
#include "../cluster_data_inc.glsl"
#include "../light_data_inc.glsl"
#define M_PI 3.14159265359
#define DENSITY_SCALE 1024.0
layout(set = 0, binding = 1) uniform texture2D shadow_atlas;
layout(set = 0, binding = 2) uniform texture2D directional_shadow_atlas;
layout(set = 0, binding = 3, std430) restrict readonly buffer OmniLights {
LightData data[];
}
omni_lights;
layout(set = 0, binding = 4, std430) restrict readonly buffer SpotLights {
LightData data[];
}
spot_lights;
layout(set = 0, binding = 5, std140) uniform DirectionalLights {
DirectionalLightData data[MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS];
}
directional_lights;
layout(set = 0, binding = 6, std430) buffer restrict readonly ClusterBuffer {
uint data[];
}
cluster_buffer;
layout(set = 0, binding = 7) uniform sampler linear_sampler;
#ifdef MODE_DENSITY
layout(rgba16f, set = 0, binding = 8) uniform restrict writeonly image3D density_map;
#endif
#ifdef MODE_FOG
layout(rgba16f, set = 0, binding = 8) uniform restrict readonly image3D density_map;
layout(rgba16f, set = 0, binding = 9) uniform restrict writeonly image3D fog_map;
#endif
#ifdef MODE_COPY
layout(rgba16f, set = 0, binding = 8) uniform restrict readonly image3D source_map;
layout(rgba16f, set = 0, binding = 9) uniform restrict writeonly image3D dest_map;
#endif
#ifdef MODE_FILTER
layout(rgba16f, set = 0, binding = 8) uniform restrict readonly image3D source_map;
layout(rgba16f, set = 0, binding = 9) uniform restrict writeonly image3D dest_map;
#endif
layout(set = 0, binding = 10) uniform sampler shadow_sampler;
#define MAX_VOXEL_GI_INSTANCES 8
struct VoxelGIData {
mat4 xform; // 64 - 64
vec3 bounds; // 12 - 76
float dynamic_range; // 4 - 80
float bias; // 4 - 84
float normal_bias; // 4 - 88
bool blend_ambient; // 4 - 92
uint mipmaps; // 4 - 96
vec3 pad; // 12 - 108
float exposure_normalization; // 4 - 112
};
layout(set = 0, binding = 11, std140) uniform VoxelGIs {
VoxelGIData data[MAX_VOXEL_GI_INSTANCES];
}
voxel_gi_instances;
layout(set = 0, binding = 12) uniform texture3D voxel_gi_textures[MAX_VOXEL_GI_INSTANCES];
layout(set = 0, binding = 13) uniform sampler linear_sampler_with_mipmaps;
#ifdef ENABLE_SDFGI
// SDFGI Integration on set 1
#define SDFGI_MAX_CASCADES 8
struct SDFVoxelGICascadeData {
vec3 position;
float to_probe;
ivec3 probe_world_offset;
float to_cell; // 1/bounds * grid_size
vec3 pad;
float exposure_normalization;
};
layout(set = 1, binding = 0, std140) uniform SDFGI {
vec3 grid_size;
uint max_cascades;
bool use_occlusion;
int probe_axis_size;
float probe_to_uvw;
float normal_bias;
vec3 lightprobe_tex_pixel_size;
float energy;
vec3 lightprobe_uv_offset;
float y_mult;
vec3 occlusion_clamp;
uint pad3;
vec3 occlusion_renormalize;
uint pad4;
vec3 cascade_probe_size;
uint pad5;
SDFVoxelGICascadeData cascades[SDFGI_MAX_CASCADES];
}
sdfgi;
layout(set = 1, binding = 1) uniform texture2DArray sdfgi_ambient_texture;
layout(set = 1, binding = 2) uniform texture3D sdfgi_occlusion_texture;
#endif //SDFGI
layout(set = 0, binding = 14, std140) uniform Params {
vec2 fog_frustum_size_begin;
vec2 fog_frustum_size_end;
float fog_frustum_end;
float ambient_inject;
float z_far;
int filter_axis;
vec3 ambient_color;
float sky_contribution;
ivec3 fog_volume_size;
uint directional_light_count;
vec3 base_emission;
float base_density;
vec3 base_scattering;
float phase_g;
float detail_spread;
float gi_inject;
uint max_voxel_gi_instances;
uint cluster_type_size;
vec2 screen_size;
uint cluster_shift;
uint cluster_width;
uint max_cluster_element_count_div_32;
bool use_temporal_reprojection;
uint temporal_frame;
float temporal_blend;
mat3x4 cam_rotation;
mat4 to_prev_view;
mat3 radiance_inverse_xform;
}
params;
#ifndef MODE_COPY
layout(set = 0, binding = 15) uniform texture3D prev_density_texture;
#ifdef NO_IMAGE_ATOMICS
layout(set = 0, binding = 16) buffer density_only_map_buffer {
uint density_only_map[];
};
layout(set = 0, binding = 17) buffer light_only_map_buffer {
uint light_only_map[];
};
layout(set = 0, binding = 18) buffer emissive_only_map_buffer {
uint emissive_only_map[];
};
#else
layout(r32ui, set = 0, binding = 16) uniform uimage3D density_only_map;
layout(r32ui, set = 0, binding = 17) uniform uimage3D light_only_map;
layout(r32ui, set = 0, binding = 18) uniform uimage3D emissive_only_map;
#endif
#ifdef USE_RADIANCE_CUBEMAP_ARRAY
layout(set = 0, binding = 19) uniform textureCubeArray sky_texture;
#else
layout(set = 0, binding = 19) uniform textureCube sky_texture;
#endif
#endif // MODE_COPY
float get_depth_at_pos(float cell_depth_size, int z) {
float d = float(z) * cell_depth_size + cell_depth_size * 0.5; //center of voxels
d = pow(d, params.detail_spread);
return params.fog_frustum_end * d;
}
vec3 hash3f(uvec3 x) {
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = (x >> 16) ^ x;
return vec3(x & 0xFFFFF) / vec3(float(0xFFFFF));
}
float get_omni_attenuation(float dist, float inv_range, float decay) {
float nd = dist * inv_range;
nd *= nd;
nd *= nd; // nd^4
nd = max(1.0 - nd, 0.0);
nd *= nd; // nd^2
return nd * pow(max(dist, 0.0001), -decay);
}
void cluster_get_item_range(uint p_offset, out uint item_min, out uint item_max, out uint item_from, out uint item_to) {
uint item_min_max = cluster_buffer.data[p_offset];
item_min = item_min_max & 0xFFFF;
item_max = item_min_max >> 16;
item_from = item_min >> 5;
item_to = (item_max == 0) ? 0 : ((item_max - 1) >> 5) + 1; //side effect of how it is stored, as item_max 0 means no elements
}
uint cluster_get_range_clip_mask(uint i, uint z_min, uint z_max) {
int local_min = clamp(int(z_min) - int(i) * 32, 0, 31);
int mask_width = min(int(z_max) - int(z_min), 32 - local_min);
return bitfieldInsert(uint(0), uint(0xFFFFFFFF), local_min, mask_width);
}
float henyey_greenstein(float cos_theta, float g) {
const float k = 0.0795774715459; // 1 / (4 * PI)
return k * (1.0 - g * g) / (pow(1.0 + g * g - 2.0 * g * cos_theta, 1.5));
}
#define TEMPORAL_FRAMES 16
const vec3 halton_map[TEMPORAL_FRAMES] = vec3[](
vec3(0.5, 0.33333333, 0.2),
vec3(0.25, 0.66666667, 0.4),
vec3(0.75, 0.11111111, 0.6),
vec3(0.125, 0.44444444, 0.8),
vec3(0.625, 0.77777778, 0.04),
vec3(0.375, 0.22222222, 0.24),
vec3(0.875, 0.55555556, 0.44),
vec3(0.0625, 0.88888889, 0.64),
vec3(0.5625, 0.03703704, 0.84),
vec3(0.3125, 0.37037037, 0.08),
vec3(0.8125, 0.7037037, 0.28),
vec3(0.1875, 0.14814815, 0.48),
vec3(0.6875, 0.48148148, 0.68),
vec3(0.4375, 0.81481481, 0.88),
vec3(0.9375, 0.25925926, 0.12),
vec3(0.03125, 0.59259259, 0.32));
// Higher values will make light in volumetric fog fade out sooner when it's occluded by shadow.
const float INV_FOG_FADE = 10.0;
void main() {
vec3 fog_cell_size = 1.0 / vec3(params.fog_volume_size);
#ifdef MODE_DENSITY
ivec3 pos = ivec3(gl_GlobalInvocationID.xyz);
if (any(greaterThanEqual(pos, params.fog_volume_size))) {
return; //do not compute
}
#ifdef NO_IMAGE_ATOMICS
uint lpos = pos.z * params.fog_volume_size.x * params.fog_volume_size.y + pos.y * params.fog_volume_size.x + pos.x;
#endif
vec3 posf = vec3(pos);
//posf += mix(vec3(0.0),vec3(1.0),0.3) * hash3f(uvec3(pos)) * 2.0 - 1.0;
vec3 fog_unit_pos = posf * fog_cell_size + fog_cell_size * 0.5; //center of voxels
uvec2 screen_pos = uvec2(fog_unit_pos.xy * params.screen_size);
uvec2 cluster_pos = screen_pos >> params.cluster_shift;
uint cluster_offset = (params.cluster_width * cluster_pos.y + cluster_pos.x) * (params.max_cluster_element_count_div_32 + 32);
//positions in screen are too spread apart, no hopes for optimizing with subgroups
fog_unit_pos.z = pow(fog_unit_pos.z, params.detail_spread);
vec3 view_pos;
view_pos.xy = (fog_unit_pos.xy * 2.0 - 1.0) * mix(params.fog_frustum_size_begin, params.fog_frustum_size_end, vec2(fog_unit_pos.z));
view_pos.z = -params.fog_frustum_end * fog_unit_pos.z;
view_pos.y = -view_pos.y;
vec4 reprojected_density = vec4(0.0);
float reproject_amount = 0.0;
if (params.use_temporal_reprojection) {
vec3 prev_view = (params.to_prev_view * vec4(view_pos, 1.0)).xyz;
//undo transform into prev view
prev_view.y = -prev_view.y;
//z back to unit size
prev_view.z /= -params.fog_frustum_end;
//xy back to unit size
prev_view.xy /= mix(params.fog_frustum_size_begin, params.fog_frustum_size_end, vec2(prev_view.z));
prev_view.xy = prev_view.xy * 0.5 + 0.5;
//z back to unspread value
prev_view.z = pow(prev_view.z, 1.0 / params.detail_spread);
if (all(greaterThan(prev_view, vec3(0.0))) && all(lessThan(prev_view, vec3(1.0)))) {
//reprojectinon fits
reprojected_density = textureLod(sampler3D(prev_density_texture, linear_sampler), prev_view, 0.0);
reproject_amount = params.temporal_blend;
// Since we can reproject, now we must jitter the current view pos.
// This is done here because cells that can't reproject should not jitter.
fog_unit_pos = posf * fog_cell_size + fog_cell_size * halton_map[params.temporal_frame]; //center of voxels, offset by halton table
screen_pos = uvec2(fog_unit_pos.xy * params.screen_size);
cluster_pos = screen_pos >> params.cluster_shift;
cluster_offset = (params.cluster_width * cluster_pos.y + cluster_pos.x) * (params.max_cluster_element_count_div_32 + 32);
//positions in screen are too spread apart, no hopes for optimizing with subgroups
fog_unit_pos.z = pow(fog_unit_pos.z, params.detail_spread);
view_pos.xy = (fog_unit_pos.xy * 2.0 - 1.0) * mix(params.fog_frustum_size_begin, params.fog_frustum_size_end, vec2(fog_unit_pos.z));
view_pos.z = -params.fog_frustum_end * fog_unit_pos.z;
view_pos.y = -view_pos.y;
}
}
uint cluster_z = uint(clamp((abs(view_pos.z) / params.z_far) * 32.0, 0.0, 31.0));
vec3 total_light = vec3(0.0);
float total_density = params.base_density;
#ifdef NO_IMAGE_ATOMICS
uint local_density = density_only_map[lpos];
#else
uint local_density = imageLoad(density_only_map, pos).x;
#endif
total_density += float(int(local_density)) / DENSITY_SCALE;
total_density = max(0.0, total_density);
#ifdef NO_IMAGE_ATOMICS
uint scattering_u = light_only_map[lpos];
#else
uint scattering_u = imageLoad(light_only_map, pos).x;
#endif
vec3 scattering = vec3(scattering_u >> 21, (scattering_u << 11) >> 21, scattering_u % 1024) / vec3(2047.0, 2047.0, 1023.0);
scattering += params.base_scattering * params.base_density;
#ifdef NO_IMAGE_ATOMICS
uint emission_u = emissive_only_map[lpos];
#else
uint emission_u = imageLoad(emissive_only_map, pos).x;
#endif
vec3 emission = vec3(emission_u >> 21, (emission_u << 11) >> 21, emission_u % 1024) / vec3(511.0, 511.0, 255.0);
emission += params.base_emission * params.base_density;
float cell_depth_size = abs(view_pos.z - get_depth_at_pos(fog_cell_size.z, pos.z + 1));
//compute directional lights
if (total_density > 0.00005) {
for (uint i = 0; i < params.directional_light_count; i++) {
if (directional_lights.data[i].volumetric_fog_energy > 0.001) {
vec3 shadow_attenuation = vec3(1.0);
if (directional_lights.data[i].shadow_opacity > 0.001) {
float depth_z = -view_pos.z;
vec4 pssm_coord;
vec3 light_dir = directional_lights.data[i].direction;
vec4 v = vec4(view_pos, 1.0);
float z_range;
if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
pssm_coord = (directional_lights.data[i].shadow_matrix1 * v);
pssm_coord /= pssm_coord.w;
z_range = directional_lights.data[i].shadow_z_range.x;
} else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
pssm_coord /= pssm_coord.w;
z_range = directional_lights.data[i].shadow_z_range.y;
} else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
pssm_coord /= pssm_coord.w;
z_range = directional_lights.data[i].shadow_z_range.z;
} else {
pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
pssm_coord /= pssm_coord.w;
z_range = directional_lights.data[i].shadow_z_range.w;
}
float depth = texture(sampler2D(directional_shadow_atlas, linear_sampler), pssm_coord.xy).r;
float shadow = exp(min(0.0, (pssm_coord.z - depth)) * z_range * INV_FOG_FADE);
shadow = mix(shadow, 1.0, smoothstep(directional_lights.data[i].fade_from, directional_lights.data[i].fade_to, view_pos.z)); //done with negative values for performance
shadow_attenuation = mix(vec3(1.0 - directional_lights.data[i].shadow_opacity), vec3(1.0), shadow);
}
total_light += shadow_attenuation * directional_lights.data[i].color * directional_lights.data[i].energy * henyey_greenstein(dot(normalize(view_pos), normalize(directional_lights.data[i].direction)), params.phase_g) * directional_lights.data[i].volumetric_fog_energy;
}
}
// Compute light from sky
if (params.ambient_inject > 0.0) {
vec3 isotropic = vec3(0.0);
vec3 anisotropic = vec3(0.0);
if (params.sky_contribution > 0.0) {
float mip_bias = 2.0 + total_density * (MAX_SKY_LOD - 2.0); // Not physically based, but looks nice
vec3 scatter_direction = (params.radiance_inverse_xform * normalize(view_pos)) * sign(params.phase_g);
#ifdef USE_RADIANCE_CUBEMAP_ARRAY
isotropic = texture(samplerCubeArray(sky_texture, linear_sampler_with_mipmaps), vec4(0.0, 1.0, 0.0, mip_bias)).rgb;
anisotropic = texture(samplerCubeArray(sky_texture, linear_sampler_with_mipmaps), vec4(scatter_direction, mip_bias)).rgb;
#else
isotropic = textureLod(samplerCube(sky_texture, linear_sampler_with_mipmaps), vec3(0.0, 1.0, 0.0), mip_bias).rgb;
anisotropic = textureLod(samplerCube(sky_texture, linear_sampler_with_mipmaps), vec3(scatter_direction), mip_bias).rgb;
#endif //USE_RADIANCE_CUBEMAP_ARRAY
}
total_light += mix(params.ambient_color, mix(isotropic, anisotropic, abs(params.phase_g)), params.sky_contribution) * params.ambient_inject;
}
//compute lights from cluster
{ //omni lights
uint cluster_omni_offset = cluster_offset;
uint item_min;
uint item_max;
uint item_from;
uint item_to;
cluster_get_item_range(cluster_omni_offset + params.max_cluster_element_count_div_32 + cluster_z, item_min, item_max, item_from, item_to);
for (uint i = item_from; i < item_to; i++) {
uint mask = cluster_buffer.data[cluster_omni_offset + i];
mask &= cluster_get_range_clip_mask(i, item_min, item_max);
uint merged_mask = mask;
while (merged_mask != 0) {
uint bit = findMSB(merged_mask);
merged_mask &= ~(1 << bit);
uint light_index = 32 * i + bit;
//if (!bool(omni_omni_lights.data[light_index].mask & draw_call.layer_mask)) {
// continue; //not masked
//}
vec3 light_pos = omni_lights.data[light_index].position;
float d = distance(omni_lights.data[light_index].position, view_pos);
float shadow_attenuation = 1.0;
if (omni_lights.data[light_index].volumetric_fog_energy > 0.001 && d * omni_lights.data[light_index].inv_radius < 1.0) {
float attenuation = get_omni_attenuation(d, omni_lights.data[light_index].inv_radius, omni_lights.data[light_index].attenuation);
vec3 light = omni_lights.data[light_index].color;
if (omni_lights.data[light_index].shadow_opacity > 0.001) {
//has shadow
vec4 uv_rect = omni_lights.data[light_index].atlas_rect;
vec2 flip_offset = omni_lights.data[light_index].direction.xy;
vec3 local_vert = (omni_lights.data[light_index].shadow_matrix * vec4(view_pos, 1.0)).xyz;
float shadow_len = length(local_vert); //need to remember shadow len from here
vec3 shadow_sample = normalize(local_vert);
if (shadow_sample.z >= 0.0) {
uv_rect.xy += flip_offset;
}
shadow_sample.z = 1.0 + abs(shadow_sample.z);
vec3 pos = vec3(shadow_sample.xy / shadow_sample.z, shadow_len - omni_lights.data[light_index].shadow_bias);
pos.z *= omni_lights.data[light_index].inv_radius;
pos.z = 1.0 - pos.z;
pos.xy = pos.xy * 0.5 + 0.5;
pos.xy = uv_rect.xy + pos.xy * uv_rect.zw;
float depth = texture(sampler2D(shadow_atlas, linear_sampler), pos.xy).r;
shadow_attenuation = mix(1.0 - omni_lights.data[light_index].shadow_opacity, 1.0, exp(min(0.0, (pos.z - depth)) / omni_lights.data[light_index].inv_radius * INV_FOG_FADE));
}
total_light += light * attenuation * shadow_attenuation * henyey_greenstein(dot(normalize(light_pos - view_pos), normalize(view_pos)), params.phase_g) * omni_lights.data[light_index].volumetric_fog_energy;
}
}
}
}
{ //spot lights
uint cluster_spot_offset = cluster_offset + params.cluster_type_size;
uint item_min;
uint item_max;
uint item_from;
uint item_to;
cluster_get_item_range(cluster_spot_offset + params.max_cluster_element_count_div_32 + cluster_z, item_min, item_max, item_from, item_to);
for (uint i = item_from; i < item_to; i++) {
uint mask = cluster_buffer.data[cluster_spot_offset + i];
mask &= cluster_get_range_clip_mask(i, item_min, item_max);
uint merged_mask = mask;
while (merged_mask != 0) {
uint bit = findMSB(merged_mask);
merged_mask &= ~(1 << bit);
//if (!bool(omni_lights.data[light_index].mask & draw_call.layer_mask)) {
// continue; //not masked
//}
uint light_index = 32 * i + bit;
vec3 light_pos = spot_lights.data[light_index].position;
vec3 light_rel_vec = spot_lights.data[light_index].position - view_pos;
float d = length(light_rel_vec);
float shadow_attenuation = 1.0;
if (spot_lights.data[light_index].volumetric_fog_energy > 0.001 && d * spot_lights.data[light_index].inv_radius < 1.0) {
float attenuation = get_omni_attenuation(d, spot_lights.data[light_index].inv_radius, spot_lights.data[light_index].attenuation);
vec3 spot_dir = spot_lights.data[light_index].direction;
float cone_angle = spot_lights.data[light_index].cone_angle;
float scos = max(dot(-normalize(light_rel_vec), spot_dir), cone_angle);
float spot_rim = max(0.0001, (1.0 - scos) / (1.0 - cone_angle));
attenuation *= 1.0 - pow(spot_rim, spot_lights.data[light_index].cone_attenuation);
vec3 light = spot_lights.data[light_index].color;
if (spot_lights.data[light_index].shadow_opacity > 0.001) {
//has shadow
vec4 uv_rect = spot_lights.data[light_index].atlas_rect;
vec4 v = vec4(view_pos, 1.0);
vec4 splane = (spot_lights.data[light_index].shadow_matrix * v);
splane.z -= spot_lights.data[light_index].shadow_bias / (d * spot_lights.data[light_index].inv_radius);
splane /= splane.w;
vec3 pos = vec3(splane.xy * spot_lights.data[light_index].atlas_rect.zw + spot_lights.data[light_index].atlas_rect.xy, splane.z);
float depth = texture(sampler2D(shadow_atlas, linear_sampler), pos.xy).r;
shadow_attenuation = mix(1.0 - spot_lights.data[light_index].shadow_opacity, 1.0, exp(min(0.0, (pos.z - depth)) / spot_lights.data[light_index].inv_radius * INV_FOG_FADE));
}
total_light += light * attenuation * shadow_attenuation * henyey_greenstein(dot(normalize(light_rel_vec), normalize(view_pos)), params.phase_g) * spot_lights.data[light_index].volumetric_fog_energy;
}
}
}
}
vec3 world_pos = mat3(params.cam_rotation) * view_pos;
for (uint i = 0; i < params.max_voxel_gi_instances; i++) {
vec3 position = (voxel_gi_instances.data[i].xform * vec4(world_pos, 1.0)).xyz;
//this causes corrupted pixels, i have no idea why..
if (all(bvec2(all(greaterThanEqual(position, vec3(0.0))), all(lessThan(position, voxel_gi_instances.data[i].bounds))))) {
position /= voxel_gi_instances.data[i].bounds;
vec4 light = vec4(0.0);
for (uint j = 0; j < voxel_gi_instances.data[i].mipmaps; j++) {
vec4 slight = textureLod(sampler3D(voxel_gi_textures[i], linear_sampler_with_mipmaps), position, float(j));
float a = (1.0 - light.a);
light += a * slight;
}
light.rgb *= voxel_gi_instances.data[i].dynamic_range * params.gi_inject * voxel_gi_instances.data[i].exposure_normalization;
total_light += light.rgb;
}
}
//sdfgi
#ifdef ENABLE_SDFGI
{
float blend = -1.0;
vec3 ambient_total = vec3(0.0);
for (uint i = 0; i < sdfgi.max_cascades; i++) {
vec3 cascade_pos = (world_pos - sdfgi.cascades[i].position) * sdfgi.cascades[i].to_probe;
if (any(lessThan(cascade_pos, vec3(0.0))) || any(greaterThanEqual(cascade_pos, sdfgi.cascade_probe_size))) {
continue; //skip cascade
}
vec3 base_pos = floor(cascade_pos);
ivec3 probe_base_pos = ivec3(base_pos);
vec4 ambient_accum = vec4(0.0);
ivec3 tex_pos = ivec3(probe_base_pos.xy, int(i));
tex_pos.x += probe_base_pos.z * sdfgi.probe_axis_size;
for (uint j = 0; j < 8; j++) {
ivec3 offset = (ivec3(j) >> ivec3(0, 1, 2)) & ivec3(1, 1, 1);
ivec3 probe_posi = probe_base_pos;
probe_posi += offset;
// Compute weight
vec3 probe_pos = vec3(probe_posi);
vec3 probe_to_pos = cascade_pos - probe_pos;
vec3 trilinear = vec3(1.0) - abs(probe_to_pos);
float weight = trilinear.x * trilinear.y * trilinear.z;
// Compute lightprobe occlusion
if (sdfgi.use_occlusion) {
ivec3 occ_indexv = abs((sdfgi.cascades[i].probe_world_offset + probe_posi) & ivec3(1, 1, 1)) * ivec3(1, 2, 4);
vec4 occ_mask = mix(vec4(0.0), vec4(1.0), equal(ivec4(occ_indexv.x | occ_indexv.y), ivec4(0, 1, 2, 3)));
vec3 occ_pos = clamp(cascade_pos, probe_pos - sdfgi.occlusion_clamp, probe_pos + sdfgi.occlusion_clamp) * sdfgi.probe_to_uvw;
occ_pos.z += float(i);
if (occ_indexv.z != 0) { //z bit is on, means index is >=4, so make it switch to the other half of textures
occ_pos.x += 1.0;
}
occ_pos *= sdfgi.occlusion_renormalize;
float occlusion = dot(textureLod(sampler3D(sdfgi_occlusion_texture, linear_sampler), occ_pos, 0.0), occ_mask);
weight *= max(occlusion, 0.01);
}
// Compute ambient texture position
ivec3 uvw = tex_pos;
uvw.xy += offset.xy;
uvw.x += offset.z * sdfgi.probe_axis_size;
vec3 ambient = texelFetch(sampler2DArray(sdfgi_ambient_texture, linear_sampler), uvw, 0).rgb;
ambient_accum.rgb += ambient * weight * sdfgi.cascades[i].exposure_normalization;
ambient_accum.a += weight;
}
if (ambient_accum.a > 0) {
ambient_accum.rgb /= ambient_accum.a;
}
ambient_total = ambient_accum.rgb;
break;
}
total_light += ambient_total * params.gi_inject;
}
#endif
}
vec4 final_density = vec4(total_light * scattering + emission, total_density);
final_density = mix(final_density, reprojected_density, reproject_amount);
imageStore(density_map, pos, final_density);
#ifdef NO_IMAGE_ATOMICS
density_only_map[lpos] = 0;
light_only_map[lpos] = 0;
emissive_only_map[lpos] = 0;
#else
imageStore(density_only_map, pos, uvec4(0));
imageStore(light_only_map, pos, uvec4(0));
imageStore(emissive_only_map, pos, uvec4(0));
#endif
#endif
#ifdef MODE_FOG
ivec3 pos = ivec3(gl_GlobalInvocationID.xy, 0);
if (any(greaterThanEqual(pos, params.fog_volume_size))) {
return; //do not compute
}
vec4 fog_accum = vec4(0.0, 0.0, 0.0, 1.0);
float prev_z = 0.0;
for (int i = 0; i < params.fog_volume_size.z; i++) {
//compute fog position
ivec3 fog_pos = pos + ivec3(0, 0, i);
//get fog value
vec4 fog = imageLoad(density_map, fog_pos);
//get depth at cell pos
float z = get_depth_at_pos(fog_cell_size.z, i);
//get distance from previous pos
float d = abs(prev_z - z);
//compute transmittance using beer's law
float transmittance = exp(-d * fog.a);
fog_accum.rgb += ((fog.rgb - fog.rgb * transmittance) / max(fog.a, 0.00001)) * fog_accum.a;
fog_accum.a *= transmittance;
prev_z = z;
imageStore(fog_map, fog_pos, vec4(fog_accum.rgb, 1.0 - fog_accum.a));
}
#endif
#ifdef MODE_FILTER
ivec3 pos = ivec3(gl_GlobalInvocationID.xyz);
const float gauss[7] = float[](0.071303, 0.131514, 0.189879, 0.214607, 0.189879, 0.131514, 0.071303);
const ivec3 filter_dir[3] = ivec3[](ivec3(1, 0, 0), ivec3(0, 1, 0), ivec3(0, 0, 1));
ivec3 offset = filter_dir[params.filter_axis];
vec4 accum = vec4(0.0);
for (int i = -3; i <= 3; i++) {
accum += imageLoad(source_map, clamp(pos + offset * i, ivec3(0), params.fog_volume_size - ivec3(1))) * gauss[i + 3];
}
imageStore(dest_map, pos, accum);
#endif
#ifdef MODE_COPY
ivec3 pos = ivec3(gl_GlobalInvocationID.xyz);
if (any(greaterThanEqual(pos, params.fog_volume_size))) {
return; //do not compute
}
imageStore(dest_map, pos, imageLoad(source_map, pos));
#endif
}

View File

@@ -0,0 +1,620 @@
#[compute]
#version 450
#VERSION_DEFINES
#ifdef MODE_DYNAMIC
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
#else
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
#endif
#ifndef MODE_DYNAMIC
#define NO_CHILDREN 0xFFFFFFFF
struct CellChildren {
uint children[8];
};
layout(set = 0, binding = 1, std430) buffer CellChildrenBuffer {
CellChildren data[];
}
cell_children;
struct CellData {
uint position; // xyz 10 bits
uint albedo; //rgb albedo
uint emission; //rgb normalized with e as multiplier
uint normal; //RGB normal encoded
};
layout(set = 0, binding = 2, std430) buffer CellDataBuffer {
CellData data[];
}
cell_data;
#endif // MODE DYNAMIC
#define LIGHT_TYPE_DIRECTIONAL 0
#define LIGHT_TYPE_OMNI 1
#define LIGHT_TYPE_SPOT 2
#if defined(MODE_COMPUTE_LIGHT) || defined(MODE_DYNAMIC_LIGHTING)
struct Light {
uint type;
float energy;
float radius;
float attenuation;
vec3 color;
float cos_spot_angle;
vec3 position;
float inv_spot_attenuation;
vec3 direction;
bool has_shadow;
};
layout(set = 0, binding = 3, std140) uniform Lights {
Light data[MAX_LIGHTS];
}
lights;
#endif // MODE COMPUTE LIGHT
#ifdef MODE_SECOND_BOUNCE
layout(set = 0, binding = 5) uniform texture3D color_texture;
#endif // MODE_SECOND_BOUNCE
#ifndef MODE_DYNAMIC
layout(push_constant, std430) uniform Params {
ivec3 limits;
uint stack_size;
float emission_scale;
float propagation;
float dynamic_range;
uint light_count;
uint cell_offset;
uint cell_count;
float aniso_strength;
float cell_size;
}
params;
layout(set = 0, binding = 4, std430) buffer Outputs {
vec4 data[];
}
outputs;
#endif // MODE DYNAMIC
layout(set = 0, binding = 9) uniform texture3D texture_sdf;
layout(set = 0, binding = 10) uniform sampler texture_sampler;
#ifdef MODE_WRITE_TEXTURE
layout(rgba8, set = 0, binding = 5) uniform restrict writeonly image3D color_tex;
#endif
#ifdef MODE_DYNAMIC
layout(push_constant, std430) uniform Params {
ivec3 limits;
uint light_count; //when not lighting
ivec3 x_dir;
float z_base;
ivec3 y_dir;
float z_sign;
ivec3 z_dir;
float pos_multiplier;
ivec2 rect_pos;
ivec2 rect_size;
ivec2 prev_rect_ofs;
ivec2 prev_rect_size;
bool flip_x;
bool flip_y;
float dynamic_range;
bool on_mipmap;
float propagation;
float cell_size;
float pad[2];
}
params;
#ifdef MODE_DYNAMIC_LIGHTING
layout(rgba8, set = 0, binding = 5) uniform restrict readonly image2D source_albedo;
layout(rgba8, set = 0, binding = 6) uniform restrict readonly image2D source_normal;
layout(rgba8, set = 0, binding = 7) uniform restrict readonly image2D source_orm;
//layout (set=0,binding=8) uniform texture2D source_depth;
layout(rgba16f, set = 0, binding = 11) uniform restrict image2D emission;
layout(r32f, set = 0, binding = 12) uniform restrict image2D depth;
#endif
#ifdef MODE_DYNAMIC_SHRINK
layout(rgba16f, set = 0, binding = 5) uniform restrict readonly image2D source_light;
layout(r32f, set = 0, binding = 6) uniform restrict readonly image2D source_depth;
#ifdef MODE_DYNAMIC_SHRINK_WRITE
layout(rgba16f, set = 0, binding = 7) uniform restrict writeonly image2D light;
layout(r32f, set = 0, binding = 8) uniform restrict writeonly image2D depth;
#endif // MODE_DYNAMIC_SHRINK_WRITE
#ifdef MODE_DYNAMIC_SHRINK_PLOT
layout(rgba8, set = 0, binding = 11) uniform restrict image3D color_texture;
#endif //MODE_DYNAMIC_SHRINK_PLOT
#endif // MODE_DYNAMIC_SHRINK
//layout (rgba8,set=0,binding=5) uniform restrict writeonly image3D color_tex;
#endif // MODE DYNAMIC
#if defined(MODE_COMPUTE_LIGHT) || defined(MODE_DYNAMIC_LIGHTING)
float raymarch(float distance, float distance_adv, vec3 from, vec3 direction) {
vec3 cell_size = 1.0 / vec3(params.limits);
float occlusion = 1.0;
while (distance > 0.5) { //use this to avoid precision errors
float advance = texture(sampler3D(texture_sdf, texture_sampler), from * cell_size).r * 255.0 - 1.0;
if (advance < 0.0) {
occlusion = 0.0;
break;
}
occlusion = min(advance, occlusion);
advance = max(distance_adv, advance - mod(advance, distance_adv)); //should always advance in multiples of distance_adv
from += direction * advance;
distance -= advance;
}
return occlusion; //max(0.0,distance);
}
float get_omni_attenuation(float distance, float inv_range, float decay) {
float nd = distance * inv_range;
nd *= nd;
nd *= nd; // nd^4
nd = max(1.0 - nd, 0.0);
nd *= nd; // nd^2
return nd * pow(max(distance, 0.0001), -decay);
}
bool compute_light_vector(uint light, vec3 pos, out float attenuation, out vec3 light_pos) {
if (lights.data[light].type == LIGHT_TYPE_DIRECTIONAL) {
light_pos = pos - lights.data[light].direction * length(vec3(params.limits));
attenuation = 1.0;
} else {
light_pos = lights.data[light].position;
float distance = length(pos - light_pos);
if (distance >= lights.data[light].radius) {
return false;
}
attenuation = get_omni_attenuation(
distance * params.cell_size,
1.0 / (lights.data[light].radius * params.cell_size),
lights.data[light].attenuation);
if (lights.data[light].type == LIGHT_TYPE_SPOT) {
vec3 rel = normalize(pos - light_pos);
float cos_spot_angle = lights.data[light].cos_spot_angle;
float cos_angle = dot(rel, lights.data[light].direction);
if (cos_angle < cos_spot_angle) {
return false;
}
float scos = max(cos_angle, cos_spot_angle);
float spot_rim = max(0.0001, (1.0 - scos) / (1.0 - cos_spot_angle));
attenuation *= 1.0 - pow(spot_rim, lights.data[light].inv_spot_attenuation);
}
}
return true;
}
float get_normal_advance(vec3 p_normal) {
vec3 normal = p_normal;
vec3 unorm = abs(normal);
if ((unorm.x >= unorm.y) && (unorm.x >= unorm.z)) {
// x code
unorm = normal.x > 0.0 ? vec3(1.0, 0.0, 0.0) : vec3(-1.0, 0.0, 0.0);
} else if ((unorm.y > unorm.x) && (unorm.y >= unorm.z)) {
// y code
unorm = normal.y > 0.0 ? vec3(0.0, 1.0, 0.0) : vec3(0.0, -1.0, 0.0);
} else if ((unorm.z > unorm.x) && (unorm.z > unorm.y)) {
// z code
unorm = normal.z > 0.0 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 0.0, -1.0);
} else {
// oh-no we messed up code
// has to be
unorm = vec3(1.0, 0.0, 0.0);
}
return 1.0 / dot(normal, unorm);
}
void clip_segment(vec4 plane, vec3 begin, inout vec3 end) {
vec3 segment = begin - end;
float den = dot(plane.xyz, segment);
//printf("den is %i\n",den);
if (den < 0.0001) {
return;
}
float dist = (dot(plane.xyz, begin) - plane.w) / den;
if (dist < 0.0001 || dist > 1.0001) {
return;
}
end = begin + segment * -dist;
}
bool compute_light_at_pos(uint index, vec3 pos, vec3 normal, inout vec3 light, inout vec3 light_dir) {
float attenuation;
vec3 light_pos;
if (!compute_light_vector(index, pos, attenuation, light_pos)) {
return false;
}
light_dir = normalize(pos - light_pos);
if (attenuation < 0.01 || (length(normal) > 0.2 && dot(normal, light_dir) >= 0)) {
return false; //not facing the light, or attenuation is near zero
}
if (lights.data[index].has_shadow) {
float distance_adv = get_normal_advance(light_dir);
vec3 to = pos;
if (length(normal) > 0.2) {
to += normal * distance_adv * 0.51;
} else {
to -= sign(light_dir) * 0.45; //go near the edge towards the light direction to avoid self occlusion
}
//clip
clip_segment(mix(vec4(-1.0, 0.0, 0.0, 0.0), vec4(1.0, 0.0, 0.0, float(params.limits.x - 1)), bvec4(light_dir.x < 0.0)), to, light_pos);
clip_segment(mix(vec4(0.0, -1.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, float(params.limits.y - 1)), bvec4(light_dir.y < 0.0)), to, light_pos);
clip_segment(mix(vec4(0.0, 0.0, -1.0, 0.0), vec4(0.0, 0.0, 1.0, float(params.limits.z - 1)), bvec4(light_dir.z < 0.0)), to, light_pos);
float distance = length(to - light_pos);
if (distance < 0.1) {
return false; // hit
}
distance += distance_adv - mod(distance, distance_adv); //make it reach the center of the box always
light_pos = to - light_dir * distance;
//from -= sign(light_dir)*0.45; //go near the edge towards the light direction to avoid self occlusion
/*float dist = raymarch(distance,distance_adv,light_pos,light_dir);
if (dist > distance_adv) {
return false;
}
attenuation *= 1.0 - smoothstep(0.1*distance_adv,distance_adv,dist);
*/
float occlusion = raymarch(distance, distance_adv, light_pos, light_dir);
if (occlusion == 0.0) {
return false;
}
attenuation *= occlusion; //1.0 - smoothstep(0.1*distance_adv,distance_adv,dist);
}
light = lights.data[index].color * attenuation * lights.data[index].energy;
return true;
}
#endif // MODE COMPUTE LIGHT
void main() {
#ifndef MODE_DYNAMIC
uint cell_index = gl_GlobalInvocationID.x;
if (cell_index >= params.cell_count) {
return;
}
cell_index += params.cell_offset;
uvec3 posu = uvec3(cell_data.data[cell_index].position & 0x7FF, (cell_data.data[cell_index].position >> 11) & 0x3FF, cell_data.data[cell_index].position >> 21);
vec4 albedo = unpackUnorm4x8(cell_data.data[cell_index].albedo);
#endif
/////////////////COMPUTE LIGHT///////////////////////////////
#ifdef MODE_COMPUTE_LIGHT
vec3 pos = vec3(posu) + vec3(0.5);
vec3 emission = vec3(uvec3(cell_data.data[cell_index].emission & 0x1ff, (cell_data.data[cell_index].emission >> 9) & 0x1ff, (cell_data.data[cell_index].emission >> 18) & 0x1ff)) * pow(2.0, float(cell_data.data[cell_index].emission >> 27) - 15.0 - 9.0);
vec3 normal = unpackSnorm4x8(cell_data.data[cell_index].normal).xyz;
vec3 accum = vec3(0.0);
for (uint i = 0; i < params.light_count; i++) {
vec3 light;
vec3 light_dir;
if (!compute_light_at_pos(i, pos, normal.xyz, light, light_dir)) {
continue;
}
light *= albedo.rgb;
if (length(normal) > 0.2) {
accum += max(0.0, dot(normal, -light_dir)) * light;
} else {
//all directions
accum += light;
}
}
outputs.data[cell_index] = vec4(accum + emission, 0.0);
#endif //MODE_COMPUTE_LIGHT
/////////////////SECOND BOUNCE///////////////////////////////
#ifdef MODE_SECOND_BOUNCE
vec3 pos = vec3(posu) + vec3(0.5);
ivec3 ipos = ivec3(posu);
vec4 normal = unpackSnorm4x8(cell_data.data[cell_index].normal);
vec3 accum = outputs.data[cell_index].rgb;
if (length(normal.xyz) > 0.2) {
vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);
vec3 tangent = normalize(cross(v0, normal.xyz));
vec3 bitangent = normalize(cross(tangent, normal.xyz));
mat3 normal_mat = mat3(tangent, bitangent, normal.xyz);
#define MAX_CONE_DIRS 6
vec3 cone_dirs[MAX_CONE_DIRS] = vec3[](
vec3(0.0, 0.0, 1.0),
vec3(0.866025, 0.0, 0.5),
vec3(0.267617, 0.823639, 0.5),
vec3(-0.700629, 0.509037, 0.5),
vec3(-0.700629, -0.509037, 0.5),
vec3(0.267617, -0.823639, 0.5));
float cone_weights[MAX_CONE_DIRS] = float[](0.25, 0.15, 0.15, 0.15, 0.15, 0.15);
float tan_half_angle = 0.577;
for (int i = 0; i < MAX_CONE_DIRS; i++) {
vec3 direction = normal_mat * cone_dirs[i];
vec4 color = vec4(0.0);
{
float dist = 1.5;
float max_distance = length(vec3(params.limits));
vec3 cell_size = 1.0 / vec3(params.limits);
while (dist < max_distance && color.a < 0.95) {
float diameter = max(1.0, 2.0 * tan_half_angle * dist);
vec3 uvw_pos = (pos + dist * direction) * cell_size;
float half_diameter = diameter * 0.5;
//check if outside, then break
//if ( any(greaterThan(abs(uvw_pos - 0.5),vec3(0.5f + half_diameter * cell_size)) ) ) {
// break;
//}
float log2_diameter = log2(diameter);
vec4 scolor = textureLod(sampler3D(color_texture, texture_sampler), uvw_pos, log2_diameter);
float a = (1.0 - color.a);
color += a * scolor;
dist += half_diameter;
}
}
color *= cone_weights[i] * vec4(albedo.rgb, 1.0) * params.dynamic_range; //restore range
accum += color.rgb;
}
}
outputs.data[cell_index] = vec4(accum, 0.0);
#endif // MODE_SECOND_BOUNCE
/////////////////UPDATE MIPMAPS///////////////////////////////
#ifdef MODE_UPDATE_MIPMAPS
{
vec3 light_accum = vec3(0.0);
float count = 0.0;
for (uint i = 0; i < 8; i++) {
uint child_index = cell_children.data[cell_index].children[i];
if (child_index == NO_CHILDREN) {
continue;
}
light_accum += outputs.data[child_index].rgb;
count += 1.0;
}
float divisor = mix(8.0, count, params.propagation);
outputs.data[cell_index] = vec4(light_accum / divisor, 0.0);
}
#endif
///////////////////WRITE TEXTURE/////////////////////////////
#ifdef MODE_WRITE_TEXTURE
{
imageStore(color_tex, ivec3(posu), vec4(outputs.data[cell_index].rgb / params.dynamic_range, albedo.a));
}
#endif
///////////////////DYNAMIC LIGHTING/////////////////////////////
#ifdef MODE_DYNAMIC
ivec2 pos_xy = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(pos_xy, params.rect_size))) {
return; //out of bounds
}
ivec2 uv_xy = pos_xy;
if (params.flip_x) {
uv_xy.x = params.rect_size.x - pos_xy.x - 1;
}
if (params.flip_y) {
uv_xy.y = params.rect_size.y - pos_xy.y - 1;
}
#ifdef MODE_DYNAMIC_LIGHTING
{
float z = params.z_base + imageLoad(depth, uv_xy).x * params.z_sign;
ivec3 pos = params.x_dir * (params.rect_pos.x + pos_xy.x) + params.y_dir * (params.rect_pos.y + pos_xy.y) + abs(params.z_dir) * int(z);
vec3 normal = normalize(imageLoad(source_normal, uv_xy).xyz * 2.0 - 1.0);
normal = vec3(params.x_dir) * normal.x * mix(1.0, -1.0, params.flip_x) + vec3(params.y_dir) * normal.y * mix(1.0, -1.0, params.flip_y) - vec3(params.z_dir) * normal.z;
vec4 albedo = imageLoad(source_albedo, uv_xy);
//determine the position in space
vec3 accum = vec3(0.0);
for (uint i = 0; i < params.light_count; i++) {
vec3 light;
vec3 light_dir;
if (!compute_light_at_pos(i, vec3(pos) * params.pos_multiplier, normal, light, light_dir)) {
continue;
}
light *= albedo.rgb;
accum += max(0.0, dot(normal, -light_dir)) * light;
}
accum += imageLoad(emission, uv_xy).xyz;
imageStore(emission, uv_xy, vec4(accum, albedo.a));
imageStore(depth, uv_xy, vec4(z));
}
#endif // MODE DYNAMIC LIGHTING
#ifdef MODE_DYNAMIC_SHRINK
{
vec4 accum = vec4(0.0);
float accum_z = 0.0;
float count = 0.0;
for (int i = 0; i < 4; i++) {
ivec2 ofs = pos_xy * 2 + ivec2(i & 1, i >> 1) - params.prev_rect_ofs;
if (any(lessThan(ofs, ivec2(0))) || any(greaterThanEqual(ofs, params.prev_rect_size))) {
continue;
}
if (params.flip_x) {
ofs.x = params.prev_rect_size.x - ofs.x - 1;
}
if (params.flip_y) {
ofs.y = params.prev_rect_size.y - ofs.y - 1;
}
vec4 light = imageLoad(source_light, ofs);
if (light.a == 0.0) { //ignore empty
continue;
}
accum += light;
float z = imageLoad(source_depth, ofs).x;
accum_z += z * 0.5; //shrink half too
count += 1.0;
}
if (params.on_mipmap) {
accum.rgb /= mix(8.0, count, params.propagation);
accum.a /= 8.0;
} else {
accum /= 4.0;
}
if (count == 0.0) {
accum_z = 0.0; //avoid nan
} else {
accum_z /= count;
}
#ifdef MODE_DYNAMIC_SHRINK_WRITE
imageStore(light, uv_xy, accum);
imageStore(depth, uv_xy, vec4(accum_z));
#endif
#ifdef MODE_DYNAMIC_SHRINK_PLOT
if (accum.a < 0.001) {
return; //do not blit if alpha is too low
}
ivec3 pos = params.x_dir * (params.rect_pos.x + pos_xy.x) + params.y_dir * (params.rect_pos.y + pos_xy.y) + abs(params.z_dir) * int(accum_z);
float z_frac = fract(accum_z);
for (int i = 0; i < 2; i++) {
ivec3 pos3d = pos + abs(params.z_dir) * i;
if (any(lessThan(pos3d, ivec3(0))) || any(greaterThanEqual(pos3d, params.limits))) {
//skip if offlimits
continue;
}
vec4 color_blit = accum * (i == 0 ? 1.0 - z_frac : z_frac);
vec4 color = imageLoad(color_texture, pos3d);
color.rgb *= params.dynamic_range;
#if 0
color.rgb = mix(color.rgb,color_blit.rgb,color_blit.a);
color.a+=color_blit.a;
#else
float sa = 1.0 - color_blit.a;
vec4 result;
result.a = color.a * sa + color_blit.a;
if (result.a == 0.0) {
result = vec4(0.0);
} else {
result.rgb = (color.rgb * color.a * sa + color_blit.rgb * color_blit.a) / result.a;
color = result;
}
#endif
color.rgb /= params.dynamic_range;
imageStore(color_texture, pos3d, color);
//imageStore(color_texture,pos3d,vec4(1,1,1,1));
}
#endif // MODE_DYNAMIC_SHRINK_PLOT
}
#endif
#endif // MODE DYNAMIC
}

View File

@@ -0,0 +1,184 @@
#[vertex]
#version 450
#VERSION_DEFINES
struct CellData {
uint position; // xyz 10 bits
uint albedo; //rgb albedo
uint emission; //rgb normalized with e as multiplier
uint normal; //RGB normal encoded
};
layout(set = 0, binding = 1, std140) buffer CellDataBuffer {
CellData data[];
}
cell_data;
layout(set = 0, binding = 2) uniform texture3D color_tex;
layout(set = 0, binding = 3) uniform sampler tex_sampler;
layout(push_constant, std430) uniform Params {
mat4 projection;
uint cell_offset;
float dynamic_range;
float alpha;
uint level;
ivec3 bounds;
uint pad;
}
params;
layout(location = 0) out vec4 color_interp;
void main() {
const vec3 cube_triangles[36] = vec3[](
vec3(-1.0f, -1.0f, -1.0f),
vec3(-1.0f, -1.0f, 1.0f),
vec3(-1.0f, 1.0f, 1.0f),
vec3(1.0f, 1.0f, -1.0f),
vec3(-1.0f, -1.0f, -1.0f),
vec3(-1.0f, 1.0f, -1.0f),
vec3(1.0f, -1.0f, 1.0f),
vec3(-1.0f, -1.0f, -1.0f),
vec3(1.0f, -1.0f, -1.0f),
vec3(1.0f, 1.0f, -1.0f),
vec3(1.0f, -1.0f, -1.0f),
vec3(-1.0f, -1.0f, -1.0f),
vec3(-1.0f, -1.0f, -1.0f),
vec3(-1.0f, 1.0f, 1.0f),
vec3(-1.0f, 1.0f, -1.0f),
vec3(1.0f, -1.0f, 1.0f),
vec3(-1.0f, -1.0f, 1.0f),
vec3(-1.0f, -1.0f, -1.0f),
vec3(-1.0f, 1.0f, 1.0f),
vec3(-1.0f, -1.0f, 1.0f),
vec3(1.0f, -1.0f, 1.0f),
vec3(1.0f, 1.0f, 1.0f),
vec3(1.0f, -1.0f, -1.0f),
vec3(1.0f, 1.0f, -1.0f),
vec3(1.0f, -1.0f, -1.0f),
vec3(1.0f, 1.0f, 1.0f),
vec3(1.0f, -1.0f, 1.0f),
vec3(1.0f, 1.0f, 1.0f),
vec3(1.0f, 1.0f, -1.0f),
vec3(-1.0f, 1.0f, -1.0f),
vec3(1.0f, 1.0f, 1.0f),
vec3(-1.0f, 1.0f, -1.0f),
vec3(-1.0f, 1.0f, 1.0f),
vec3(1.0f, 1.0f, 1.0f),
vec3(-1.0f, 1.0f, 1.0f),
vec3(1.0f, -1.0f, 1.0f));
vec3 vertex = cube_triangles[gl_VertexIndex] * 0.5 + 0.5;
#ifdef MODE_DEBUG_LIGHT_FULL
uvec3 posu = uvec3(gl_InstanceIndex % params.bounds.x, (gl_InstanceIndex / params.bounds.x) % params.bounds.y, gl_InstanceIndex / (params.bounds.y * params.bounds.x));
#else
uint cell_index = gl_InstanceIndex + params.cell_offset;
uvec3 posu = uvec3(cell_data.data[cell_index].position & 0x7FF, (cell_data.data[cell_index].position >> 11) & 0x3FF, cell_data.data[cell_index].position >> 21);
#endif
#ifdef MODE_DEBUG_EMISSION
color_interp.xyz = vec3(uvec3(cell_data.data[cell_index].emission & 0x1ff, (cell_data.data[cell_index].emission >> 9) & 0x1ff, (cell_data.data[cell_index].emission >> 18) & 0x1ff)) * pow(2.0, float(cell_data.data[cell_index].emission >> 27) - 15.0 - 9.0);
#endif
#ifdef MODE_DEBUG_COLOR
color_interp.xyz = unpackUnorm4x8(cell_data.data[cell_index].albedo).xyz;
#endif
#ifdef MODE_DEBUG_LIGHT
color_interp = texelFetch(sampler3D(color_tex, tex_sampler), ivec3(posu), int(params.level));
color_interp.xyz *params.dynamic_range;
#endif
float scale = (1 << params.level);
gl_Position = params.projection * vec4((vec3(posu) + vertex) * scale, 1.0);
#ifdef MODE_DEBUG_LIGHT_FULL
if (color_interp.a == 0.0) {
gl_Position = vec4(0.0); //force clip and not draw
}
#else
color_interp.a = params.alpha;
#endif
}
#[fragment]
#version 450
#VERSION_DEFINES
layout(location = 0) in vec4 color_interp;
layout(location = 0) out vec4 frag_color;
void main() {
frag_color = color_interp;
#ifdef MODE_DEBUG_LIGHT_FULL
//there really is no alpha, so use dither
int x = int(gl_FragCoord.x) % 4;
int y = int(gl_FragCoord.y) % 4;
int index = x + y * 4;
float limit = 0.0;
if (x < 8) {
if (index == 0) {
limit = 0.0625;
}
if (index == 1) {
limit = 0.5625;
}
if (index == 2) {
limit = 0.1875;
}
if (index == 3) {
limit = 0.6875;
}
if (index == 4) {
limit = 0.8125;
}
if (index == 5) {
limit = 0.3125;
}
if (index == 6) {
limit = 0.9375;
}
if (index == 7) {
limit = 0.4375;
}
if (index == 8) {
limit = 0.25;
}
if (index == 9) {
limit = 0.75;
}
if (index == 10) {
limit = 0.125;
}
if (index == 11) {
limit = 0.625;
}
if (index == 12) {
limit = 1.0;
}
if (index == 13) {
limit = 0.5;
}
if (index == 14) {
limit = 0.875;
}
if (index == 15) {
limit = 0.375;
}
}
if (frag_color.a < limit) {
discard;
}
#endif
}

View File

@@ -0,0 +1,180 @@
#[compute]
#version 450
#VERSION_DEFINES
layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
#define MAX_DISTANCE 100000.0
#define NO_CHILDREN 0xFFFFFFFF
struct CellChildren {
uint children[8];
};
layout(set = 0, binding = 1, std430) buffer CellChildrenBuffer {
CellChildren data[];
}
cell_children;
struct CellData {
uint position; // xyz 10 bits
uint albedo; //rgb albedo
uint emission; //rgb normalized with e as multiplier
uint normal; //RGB normal encoded
};
layout(set = 0, binding = 2, std430) buffer CellDataBuffer {
CellData data[];
}
cell_data;
layout(r8ui, set = 0, binding = 3) uniform restrict writeonly uimage3D sdf_tex;
layout(push_constant, std430) uniform Params {
uint offset;
uint end;
uint pad0;
uint pad1;
}
params;
void main() {
vec3 pos = vec3(gl_GlobalInvocationID);
float closest_dist = MAX_DISTANCE;
for (uint i = params.offset; i < params.end; i++) {
vec3 posu = vec3(uvec3(cell_data.data[i].position & 0x7FF, (cell_data.data[i].position >> 11) & 0x3FF, cell_data.data[i].position >> 21));
float dist = length(pos - posu);
if (dist < closest_dist) {
closest_dist = dist;
}
}
uint dist_8;
if (closest_dist < 0.0001) { // same cell
dist_8 = 0; //equals to -1
} else {
dist_8 = clamp(uint(closest_dist), 0, 254) + 1; //conservative, 0 is 1, so <1 is considered solid
}
imageStore(sdf_tex, ivec3(gl_GlobalInvocationID), uvec4(dist_8));
//imageStore(sdf_tex,pos,uvec4(pos*2,0));
}
#if 0
layout(push_constant, std430) uniform Params {
ivec3 limits;
uint stack_size;
}
params;
float distance_to_aabb(ivec3 pos, ivec3 aabb_pos, ivec3 aabb_size) {
vec3 delta = vec3(max(ivec3(0), max(aabb_pos - pos, pos - (aabb_pos + aabb_size - ivec3(1)))));
return length(delta);
}
void main() {
ivec3 pos = ivec3(gl_GlobalInvocationID);
uint stack[10] = uint[](0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
uint stack_indices[10] = uint[](0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
ivec3 stack_positions[10] = ivec3[](ivec3(0), ivec3(0), ivec3(0), ivec3(0), ivec3(0), ivec3(0), ivec3(0), ivec3(0), ivec3(0), ivec3(0));
const uint cell_orders[8] = uint[](
0x11f58d1,
0xe2e70a,
0xd47463,
0xbb829c,
0x8d11f5,
0x70ae2e,
0x463d47,
0x29cbb8);
bool cell_found = false;
bool cell_found_exact = false;
ivec3 closest_cell_pos;
float closest_distance = MAX_DISTANCE;
int stack_pos = 0;
while (true) {
uint index = stack_indices[stack_pos] >> 24;
if (index == 8) {
//go up
if (stack_pos == 0) {
break; //done going through octree
}
stack_pos--;
continue;
}
stack_indices[stack_pos] = (stack_indices[stack_pos] & ((1 << 24) - 1)) | ((index + 1) << 24);
uint cell_index = (stack_indices[stack_pos] >> (index * 3)) & 0x7;
uint child_cell = cell_children.data[stack[stack_pos]].children[cell_index];
if (child_cell == NO_CHILDREN) {
continue;
}
ivec3 child_cell_size = params.limits >> (stack_pos + 1);
ivec3 child_cell_pos = stack_positions[stack_pos];
child_cell_pos += mix(ivec3(0), child_cell_size, bvec3(uvec3(index & 1, index & 2, index & 4) != uvec3(0)));
bool is_leaf = stack_pos == (params.stack_size - 2);
if (child_cell_pos == pos && is_leaf) {
//we may actually end up in the exact cell.
//if this happens, just abort
cell_found_exact = true;
break;
}
if (cell_found) {
//discard by distance
float distance = distance_to_aabb(pos, child_cell_pos, child_cell_size);
if (distance >= closest_distance) {
continue; //pointless, just test next child
} else if (is_leaf) {
//closer than what we have AND end of stack, save and continue
closest_cell_pos = child_cell_pos;
closest_distance = distance;
continue;
}
} else if (is_leaf) {
//first solid cell we find, save and continue
closest_distance = distance_to_aabb(pos, child_cell_pos, child_cell_size);
closest_cell_pos = child_cell_pos;
cell_found = true;
continue;
}
bvec3 direction = greaterThan((pos - (child_cell_pos + (child_cell_size >> 1))), ivec3(0));
uint cell_order = 0;
cell_order |= mix(0, 1, direction.x);
cell_order |= mix(0, 2, direction.y);
cell_order |= mix(0, 4, direction.z);
stack[stack_pos + 1] = child_cell;
stack_indices[stack_pos + 1] = cell_orders[cell_order]; //start counting
stack_positions[stack_pos + 1] = child_cell_pos;
stack_pos++; //go up stack
}
uint dist_8;
if (cell_found_exact) {
dist_8 = 0; //equals to -1
} else {
float closest_distance = length(vec3(pos - closest_cell_pos));
dist_8 = clamp(uint(closest_distance), 0, 254) + 1; //conservative, 0 is 1, so <1 is considered solid
}
imageStore(sdf_tex, pos, uvec4(dist_8));
}
#endif