mirror of
https://github.com/godotengine/godot
synced 2024-11-05 16:53:09 +00:00
2ee77f6f05
Previous algorithm used an algorithm to generate rays that was not completely random. This caused artifacts when large lighmap textures were used. The new algorithm creates better rays and by that prevents artifacts.
688 lines
23 KiB
GLSL
688 lines
23 KiB
GLSL
#[versions]
|
|
|
|
primary = "#define MODE_DIRECT_LIGHT";
|
|
secondary = "#define MODE_BOUNCE_LIGHT";
|
|
dilate = "#define MODE_DILATE";
|
|
unocclude = "#define MODE_UNOCCLUDE";
|
|
light_probes = "#define MODE_LIGHT_PROBES";
|
|
|
|
#[compute]
|
|
|
|
#version 450
|
|
|
|
#VERSION_DEFINES
|
|
|
|
// One 2D local group focusing in one layer at a time, though all
|
|
// in parallel (no barriers) makes more sense than a 3D local group
|
|
// as this can take more advantage of the cache for each group.
|
|
|
|
#ifdef MODE_LIGHT_PROBES
|
|
|
|
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
|
|
|
|
#else
|
|
|
|
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
|
|
|
|
#endif
|
|
|
|
#include "lm_common_inc.glsl"
|
|
|
|
#ifdef MODE_LIGHT_PROBES
|
|
|
|
layout(set = 1, binding = 0, std430) restrict buffer LightProbeData {
|
|
vec4 data[];
|
|
}
|
|
light_probes;
|
|
|
|
layout(set = 1, binding = 1) uniform texture2DArray source_light;
|
|
layout(set = 1, binding = 2) uniform texture2DArray source_direct_light; //also need the direct light, which was omitted
|
|
layout(set = 1, binding = 3) uniform texture2D environment;
|
|
#endif
|
|
|
|
#ifdef MODE_UNOCCLUDE
|
|
|
|
layout(rgba32f, set = 1, binding = 0) uniform restrict image2DArray position;
|
|
layout(rgba32f, set = 1, binding = 1) uniform restrict readonly image2DArray unocclude;
|
|
|
|
#endif
|
|
|
|
#if defined(MODE_DIRECT_LIGHT) || defined(MODE_BOUNCE_LIGHT)
|
|
|
|
layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2DArray dest_light;
|
|
layout(set = 1, binding = 1) uniform texture2DArray source_light;
|
|
layout(set = 1, binding = 2) uniform texture2DArray source_position;
|
|
layout(set = 1, binding = 3) uniform texture2DArray source_normal;
|
|
layout(rgba16f, set = 1, binding = 4) uniform restrict image2DArray accum_light;
|
|
|
|
#endif
|
|
|
|
#ifdef MODE_BOUNCE_LIGHT
|
|
layout(rgba32f, set = 1, binding = 5) uniform restrict image2DArray bounce_accum;
|
|
layout(set = 1, binding = 6) uniform texture2D environment;
|
|
#endif
|
|
#ifdef MODE_DIRECT_LIGHT
|
|
layout(rgba32f, set = 1, binding = 5) uniform restrict writeonly image2DArray primary_dynamic;
|
|
#endif
|
|
|
|
#ifdef MODE_DILATE
|
|
layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2DArray dest_light;
|
|
layout(set = 1, binding = 1) uniform texture2DArray source_light;
|
|
#endif
|
|
|
|
layout(push_constant, binding = 0, std430) uniform Params {
|
|
ivec2 atlas_size; // x used for light probe mode total probes
|
|
uint ray_count;
|
|
uint ray_to;
|
|
|
|
vec3 world_size;
|
|
float bias;
|
|
|
|
vec3 to_cell_offset;
|
|
uint ray_from;
|
|
|
|
vec3 to_cell_size;
|
|
uint light_count;
|
|
|
|
int grid_size;
|
|
int atlas_slice;
|
|
ivec2 region_ofs;
|
|
|
|
mat3x4 env_transform;
|
|
}
|
|
params;
|
|
|
|
//check it, but also return distance and barycentric coords (for uv lookup)
|
|
bool ray_hits_triangle(vec3 from, vec3 dir, float max_dist, vec3 p0, vec3 p1, vec3 p2, out float r_distance, out vec3 r_barycentric) {
|
|
const float EPSILON = 0.00001;
|
|
const vec3 e0 = p1 - p0;
|
|
const vec3 e1 = p0 - p2;
|
|
vec3 triangle_normal = cross(e1, e0);
|
|
|
|
float n_dot_dir = dot(triangle_normal, dir);
|
|
|
|
if (abs(n_dot_dir) < EPSILON) {
|
|
return false;
|
|
}
|
|
|
|
const vec3 e2 = (p0 - from) / n_dot_dir;
|
|
const vec3 i = cross(dir, e2);
|
|
|
|
r_barycentric.y = dot(i, e1);
|
|
r_barycentric.z = dot(i, e0);
|
|
r_barycentric.x = 1.0 - (r_barycentric.z + r_barycentric.y);
|
|
r_distance = dot(triangle_normal, e2);
|
|
|
|
return (r_distance > params.bias) && (r_distance < max_dist) && all(greaterThanEqual(r_barycentric, vec3(0.0)));
|
|
}
|
|
|
|
const uint RAY_MISS = 0;
|
|
const uint RAY_FRONT = 1;
|
|
const uint RAY_BACK = 2;
|
|
const uint RAY_ANY = 3;
|
|
|
|
uint trace_ray(vec3 p_from, vec3 p_to
|
|
#if defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
|
|
,
|
|
out uint r_triangle, out vec3 r_barycentric
|
|
#endif
|
|
#if defined(MODE_UNOCCLUDE)
|
|
,
|
|
out float r_distance, out vec3 r_normal
|
|
#endif
|
|
) {
|
|
|
|
/* world coords */
|
|
|
|
vec3 rel = p_to - p_from;
|
|
float rel_len = length(rel);
|
|
vec3 dir = normalize(rel);
|
|
vec3 inv_dir = 1.0 / dir;
|
|
|
|
/* cell coords */
|
|
|
|
vec3 from_cell = (p_from - params.to_cell_offset) * params.to_cell_size;
|
|
vec3 to_cell = (p_to - params.to_cell_offset) * params.to_cell_size;
|
|
|
|
//prepare DDA
|
|
vec3 rel_cell = to_cell - from_cell;
|
|
ivec3 icell = ivec3(from_cell);
|
|
ivec3 iendcell = ivec3(to_cell);
|
|
vec3 dir_cell = normalize(rel_cell);
|
|
vec3 delta = min(abs(1.0 / dir_cell), params.grid_size); // use params.grid_size as max to prevent infinity values
|
|
ivec3 step = ivec3(sign(rel_cell));
|
|
vec3 side = (sign(rel_cell) * (vec3(icell) - from_cell) + (sign(rel_cell) * 0.5) + 0.5) * delta;
|
|
|
|
uint iters = 0;
|
|
while (all(greaterThanEqual(icell, ivec3(0))) && all(lessThan(icell, ivec3(params.grid_size))) && iters < 1000) {
|
|
uvec2 cell_data = texelFetch(usampler3D(grid, linear_sampler), icell, 0).xy;
|
|
if (cell_data.x > 0) { //triangles here
|
|
uint hit = RAY_MISS;
|
|
float best_distance = 1e20;
|
|
|
|
for (uint i = 0; i < cell_data.x; i++) {
|
|
uint tidx = grid_indices.data[cell_data.y + i];
|
|
|
|
//Ray-Box test
|
|
Triangle triangle = triangles.data[tidx];
|
|
vec3 t0 = (triangle.min_bounds - p_from) * inv_dir;
|
|
vec3 t1 = (triangle.max_bounds - p_from) * inv_dir;
|
|
vec3 tmin = min(t0, t1), tmax = max(t0, t1);
|
|
|
|
if (max(tmin.x, max(tmin.y, tmin.z)) > min(tmax.x, min(tmax.y, tmax.z))) {
|
|
continue; //ray box failed
|
|
}
|
|
|
|
//prepare triangle vertices
|
|
vec3 vtx0 = vertices.data[triangle.indices.x].position;
|
|
vec3 vtx1 = vertices.data[triangle.indices.y].position;
|
|
vec3 vtx2 = vertices.data[triangle.indices.z].position;
|
|
#if defined(MODE_UNOCCLUDE) || defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
|
|
vec3 normal = -normalize(cross((vtx0 - vtx1), (vtx0 - vtx2)));
|
|
|
|
bool backface = dot(normal, dir) >= 0.0;
|
|
#endif
|
|
|
|
float distance;
|
|
vec3 barycentric;
|
|
|
|
if (ray_hits_triangle(p_from, dir, rel_len, vtx0, vtx1, vtx2, distance, barycentric)) {
|
|
#ifdef MODE_DIRECT_LIGHT
|
|
return RAY_ANY; //any hit good
|
|
#endif
|
|
|
|
#if defined(MODE_UNOCCLUDE) || defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
|
|
if (!backface) {
|
|
// the case of meshes having both a front and back face in the same plane is more common than
|
|
// expected, so if this is a front-face, bias it closer to the ray origin, so it always wins over the back-face
|
|
distance = max(params.bias, distance - params.bias);
|
|
}
|
|
|
|
if (distance < best_distance) {
|
|
hit = backface ? RAY_BACK : RAY_FRONT;
|
|
best_distance = distance;
|
|
#if defined(MODE_UNOCCLUDE)
|
|
r_distance = distance;
|
|
r_normal = normal;
|
|
#endif
|
|
#if defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
|
|
r_triangle = tidx;
|
|
r_barycentric = barycentric;
|
|
#endif
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
#if defined(MODE_UNOCCLUDE) || defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
|
|
|
|
if (hit != RAY_MISS) {
|
|
return hit;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (icell == iendcell) {
|
|
break;
|
|
}
|
|
|
|
bvec3 mask = lessThanEqual(side.xyz, min(side.yzx, side.zxy));
|
|
side += vec3(mask) * delta;
|
|
icell += ivec3(vec3(mask)) * step;
|
|
|
|
iters++;
|
|
}
|
|
|
|
return RAY_MISS;
|
|
}
|
|
|
|
// https://www.reedbeta.com/blog/hash-functions-for-gpu-rendering/
|
|
uint hash(uint value) {
|
|
uint state = value * 747796405u + 2891336453u;
|
|
uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;
|
|
return (word >> 22u) ^ word;
|
|
}
|
|
|
|
uint random_seed(ivec3 seed) {
|
|
return hash(seed.x ^ hash(seed.y ^ hash(seed.z)));
|
|
}
|
|
|
|
// generates a random value in range [0.0, 1.0)
|
|
float randomize(inout uint value) {
|
|
value = hash(value);
|
|
return float(value / 4294967296.0);
|
|
}
|
|
|
|
const float PI = 3.14159265f;
|
|
|
|
// http://www.realtimerendering.com/raytracinggems/unofficial_RayTracingGems_v1.4.pdf (chapter 15)
|
|
vec3 generate_hemisphere_uniform_direction(inout uint noise) {
|
|
float noise1 = randomize(noise);
|
|
float noise2 = randomize(noise) * 2.0 * PI;
|
|
|
|
float factor = sqrt(1 - (noise1 * noise1));
|
|
return vec3(factor * cos(noise2), factor * sin(noise2), noise1);
|
|
}
|
|
|
|
vec3 generate_hemisphere_cosine_weighted_direction(inout uint noise) {
|
|
float noise1 = randomize(noise);
|
|
float noise2 = randomize(noise) * 2.0 * PI;
|
|
|
|
return vec3(sqrt(noise1) * cos(noise2), sqrt(noise1) * sin(noise2), sqrt(1.0 - noise1));
|
|
}
|
|
|
|
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() {
|
|
#ifdef MODE_LIGHT_PROBES
|
|
int probe_index = int(gl_GlobalInvocationID.x);
|
|
if (probe_index >= params.atlas_size.x) { //too large, do nothing
|
|
return;
|
|
}
|
|
|
|
#else
|
|
ivec2 atlas_pos = ivec2(gl_GlobalInvocationID.xy) + params.region_ofs;
|
|
if (any(greaterThanEqual(atlas_pos, params.atlas_size))) { //too large, do nothing
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#ifdef MODE_DIRECT_LIGHT
|
|
|
|
vec3 normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
|
|
if (length(normal) < 0.5) {
|
|
return; //empty texel, no process
|
|
}
|
|
vec3 position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
|
|
|
|
//go through all lights
|
|
//start by own light (emissive)
|
|
vec3 static_light = vec3(0.0);
|
|
vec3 dynamic_light = vec3(0.0);
|
|
|
|
#ifdef USE_SH_LIGHTMAPS
|
|
vec4 sh_accum[4] = vec4[](
|
|
vec4(0.0, 0.0, 0.0, 1.0),
|
|
vec4(0.0, 0.0, 0.0, 1.0),
|
|
vec4(0.0, 0.0, 0.0, 1.0),
|
|
vec4(0.0, 0.0, 0.0, 1.0));
|
|
#endif
|
|
|
|
for (uint i = 0; i < params.light_count; i++) {
|
|
vec3 light_pos;
|
|
float attenuation;
|
|
if (lights.data[i].type == LIGHT_TYPE_DIRECTIONAL) {
|
|
vec3 light_vec = lights.data[i].direction;
|
|
light_pos = position - light_vec * length(params.world_size);
|
|
attenuation = 1.0;
|
|
} else {
|
|
light_pos = lights.data[i].position;
|
|
float d = distance(position, light_pos);
|
|
if (d > lights.data[i].range) {
|
|
continue;
|
|
}
|
|
|
|
attenuation = get_omni_attenuation(d, 1.0 / lights.data[i].range, lights.data[i].attenuation);
|
|
|
|
if (lights.data[i].type == LIGHT_TYPE_SPOT) {
|
|
vec3 rel = normalize(position - light_pos);
|
|
float cos_spot_angle = lights.data[i].cos_spot_angle;
|
|
float cos_angle = dot(rel, lights.data[i].direction);
|
|
|
|
if (cos_angle < cos_spot_angle) {
|
|
continue; //invisible, dont try
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
vec3 light_dir = normalize(light_pos - position);
|
|
attenuation *= max(0.0, dot(normal, light_dir));
|
|
|
|
if (attenuation <= 0.0001) {
|
|
continue; //no need to do anything
|
|
}
|
|
|
|
if (trace_ray(position + light_dir * params.bias, light_pos) == RAY_MISS) {
|
|
vec3 light = lights.data[i].color * lights.data[i].energy * attenuation;
|
|
if (lights.data[i].static_bake) {
|
|
static_light += light;
|
|
#ifdef USE_SH_LIGHTMAPS
|
|
|
|
float c[4] = float[](
|
|
0.282095, //l0
|
|
0.488603 * light_dir.y, //l1n1
|
|
0.488603 * light_dir.z, //l1n0
|
|
0.488603 * light_dir.x //l1p1
|
|
);
|
|
|
|
for (uint j = 0; j < 4; j++) {
|
|
sh_accum[j].rgb += light * c[j] * (1.0 / 3.0);
|
|
}
|
|
#endif
|
|
|
|
} else {
|
|
dynamic_light += light;
|
|
}
|
|
}
|
|
}
|
|
|
|
vec3 albedo = texelFetch(sampler2DArray(albedo_tex, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).rgb;
|
|
vec3 emissive = texelFetch(sampler2DArray(emission_tex, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).rgb;
|
|
|
|
dynamic_light *= albedo; //if it will bounce, must multiply by albedo
|
|
dynamic_light += emissive;
|
|
|
|
//keep for lightprobes
|
|
imageStore(primary_dynamic, ivec3(atlas_pos, params.atlas_slice), vec4(dynamic_light, 1.0));
|
|
|
|
dynamic_light += static_light * albedo; //send for bounces
|
|
imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice), vec4(dynamic_light, 1.0));
|
|
|
|
#ifdef USE_SH_LIGHTMAPS
|
|
//keep for adding at the end
|
|
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 0), sh_accum[0]);
|
|
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 1), sh_accum[1]);
|
|
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 2), sh_accum[2]);
|
|
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 3), sh_accum[3]);
|
|
|
|
#else
|
|
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice), vec4(static_light, 1.0));
|
|
#endif
|
|
|
|
#endif
|
|
|
|
#ifdef MODE_BOUNCE_LIGHT
|
|
|
|
vec3 normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
|
|
if (length(normal) < 0.5) {
|
|
return; //empty texel, no process
|
|
}
|
|
|
|
vec3 position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
|
|
|
|
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);
|
|
|
|
#ifdef USE_SH_LIGHTMAPS
|
|
vec4 sh_accum[4] = vec4[](
|
|
vec4(0.0, 0.0, 0.0, 1.0),
|
|
vec4(0.0, 0.0, 0.0, 1.0),
|
|
vec4(0.0, 0.0, 0.0, 1.0),
|
|
vec4(0.0, 0.0, 0.0, 1.0));
|
|
#endif
|
|
vec3 light_average = vec3(0.0);
|
|
float active_rays = 0.0;
|
|
uint noise = random_seed(ivec3(params.ray_from, atlas_pos));
|
|
for (uint i = params.ray_from; i < params.ray_to; i++) {
|
|
vec3 ray_dir = normal_mat * generate_hemisphere_cosine_weighted_direction(noise);
|
|
|
|
uint tidx;
|
|
vec3 barycentric;
|
|
|
|
vec3 light = vec3(0.0);
|
|
uint trace_result = trace_ray(position + ray_dir * params.bias, position + ray_dir * length(params.world_size), tidx, barycentric);
|
|
if (trace_result == RAY_FRONT) {
|
|
//hit a triangle
|
|
vec2 uv0 = vertices.data[triangles.data[tidx].indices.x].uv;
|
|
vec2 uv1 = vertices.data[triangles.data[tidx].indices.y].uv;
|
|
vec2 uv2 = vertices.data[triangles.data[tidx].indices.z].uv;
|
|
vec3 uvw = vec3(barycentric.x * uv0 + barycentric.y * uv1 + barycentric.z * uv2, float(triangles.data[tidx].slice));
|
|
|
|
light = textureLod(sampler2DArray(source_light, linear_sampler), uvw, 0.0).rgb;
|
|
active_rays += 1.0;
|
|
} else if (trace_result == RAY_MISS) {
|
|
if (params.env_transform[0][3] == 0.0) { // Use env_transform[0][3] to indicate when we are computing the first bounce
|
|
// Did not hit a triangle, reach out for the sky
|
|
vec3 sky_dir = normalize(mat3(params.env_transform) * ray_dir);
|
|
|
|
vec2 st = vec2(
|
|
atan(sky_dir.x, sky_dir.z),
|
|
acos(sky_dir.y));
|
|
|
|
if (st.x < 0.0)
|
|
st.x += PI * 2.0;
|
|
|
|
st /= vec2(PI * 2.0, PI);
|
|
|
|
light = textureLod(sampler2D(environment, linear_sampler), st, 0.0).rgb;
|
|
}
|
|
active_rays += 1.0;
|
|
}
|
|
|
|
light_average += light;
|
|
|
|
#ifdef USE_SH_LIGHTMAPS
|
|
|
|
float c[4] = float[](
|
|
0.282095, //l0
|
|
0.488603 * ray_dir.y, //l1n1
|
|
0.488603 * ray_dir.z, //l1n0
|
|
0.488603 * ray_dir.x //l1p1
|
|
);
|
|
|
|
for (uint j = 0; j < 4; j++) {
|
|
sh_accum[j].rgb += light * c[j] * (8.0 / float(params.ray_count));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
vec3 light_total;
|
|
if (params.ray_from == 0) {
|
|
light_total = vec3(0.0);
|
|
} else {
|
|
vec4 accum = imageLoad(bounce_accum, ivec3(atlas_pos, params.atlas_slice));
|
|
light_total = accum.rgb;
|
|
active_rays += accum.a;
|
|
}
|
|
|
|
light_total += light_average;
|
|
|
|
#ifdef USE_SH_LIGHTMAPS
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
vec4 accum = imageLoad(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + i));
|
|
accum.rgb += sh_accum[i].rgb;
|
|
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + i), accum);
|
|
}
|
|
|
|
#endif
|
|
if (params.ray_to == params.ray_count) {
|
|
if (active_rays > 0) {
|
|
light_total /= active_rays;
|
|
}
|
|
imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice), vec4(light_total, 1.0));
|
|
#ifndef USE_SH_LIGHTMAPS
|
|
vec4 accum = imageLoad(accum_light, ivec3(atlas_pos, params.atlas_slice));
|
|
accum.rgb += light_total;
|
|
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice), accum);
|
|
#endif
|
|
} else {
|
|
imageStore(bounce_accum, ivec3(atlas_pos, params.atlas_slice), vec4(light_total, active_rays));
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef MODE_UNOCCLUDE
|
|
|
|
//texel_size = 0.5;
|
|
//compute tangents
|
|
|
|
vec4 position_alpha = imageLoad(position, ivec3(atlas_pos, params.atlas_slice));
|
|
if (position_alpha.a < 0.5) {
|
|
return;
|
|
}
|
|
|
|
vec3 vertex_pos = position_alpha.xyz;
|
|
vec4 normal_tsize = imageLoad(unocclude, ivec3(atlas_pos, params.atlas_slice));
|
|
|
|
vec3 face_normal = normal_tsize.xyz;
|
|
float texel_size = normal_tsize.w;
|
|
|
|
vec3 v0 = abs(face_normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);
|
|
vec3 tangent = normalize(cross(v0, face_normal));
|
|
vec3 bitangent = normalize(cross(tangent, face_normal));
|
|
vec3 base_pos = vertex_pos + face_normal * params.bias; //raise a bit
|
|
|
|
vec3 rays[4] = vec3[](tangent, bitangent, -tangent, -bitangent);
|
|
float min_d = 1e20;
|
|
for (int i = 0; i < 4; i++) {
|
|
vec3 ray_to = base_pos + rays[i] * texel_size;
|
|
float d;
|
|
vec3 norm;
|
|
|
|
if (trace_ray(base_pos, ray_to, d, norm) == RAY_BACK) {
|
|
if (d < min_d) {
|
|
vertex_pos = base_pos + rays[i] * d + norm * params.bias * 10.0; //this bias needs to be greater than the regular bias, because otherwise later, rays will go the other side when pointing back.
|
|
min_d = d;
|
|
}
|
|
}
|
|
}
|
|
|
|
position_alpha.xyz = vertex_pos;
|
|
|
|
imageStore(position, ivec3(atlas_pos, params.atlas_slice), position_alpha);
|
|
|
|
#endif
|
|
|
|
#ifdef MODE_LIGHT_PROBES
|
|
|
|
vec3 position = probe_positions.data[probe_index].xyz;
|
|
|
|
vec4 probe_sh_accum[9] = vec4[](
|
|
vec4(0.0),
|
|
vec4(0.0),
|
|
vec4(0.0),
|
|
vec4(0.0),
|
|
vec4(0.0),
|
|
vec4(0.0),
|
|
vec4(0.0),
|
|
vec4(0.0),
|
|
vec4(0.0));
|
|
|
|
uint noise = random_seed(ivec3(params.ray_from, probe_index, 49502741 /* some prime */));
|
|
for (uint i = params.ray_from; i < params.ray_to; i++) {
|
|
vec3 ray_dir = generate_hemisphere_uniform_direction(noise);
|
|
if (bool(i & 1)) {
|
|
//throw to both sides, so alternate them
|
|
ray_dir.z *= -1.0;
|
|
}
|
|
|
|
uint tidx;
|
|
vec3 barycentric;
|
|
vec3 light;
|
|
|
|
uint trace_result = trace_ray(position + ray_dir * params.bias, position + ray_dir * length(params.world_size), tidx, barycentric);
|
|
if (trace_result == RAY_FRONT) {
|
|
vec2 uv0 = vertices.data[triangles.data[tidx].indices.x].uv;
|
|
vec2 uv1 = vertices.data[triangles.data[tidx].indices.y].uv;
|
|
vec2 uv2 = vertices.data[triangles.data[tidx].indices.z].uv;
|
|
vec3 uvw = vec3(barycentric.x * uv0 + barycentric.y * uv1 + barycentric.z * uv2, float(triangles.data[tidx].slice));
|
|
|
|
light = textureLod(sampler2DArray(source_light, linear_sampler), uvw, 0.0).rgb;
|
|
light += textureLod(sampler2DArray(source_direct_light, linear_sampler), uvw, 0.0).rgb;
|
|
} else if (trace_result == RAY_MISS) {
|
|
//did not hit a triangle, reach out for the sky
|
|
vec3 sky_dir = normalize(mat3(params.env_transform) * ray_dir);
|
|
|
|
vec2 st = vec2(
|
|
atan(sky_dir.x, sky_dir.z),
|
|
acos(sky_dir.y));
|
|
|
|
if (st.x < 0.0)
|
|
st.x += PI * 2.0;
|
|
|
|
st /= vec2(PI * 2.0, PI);
|
|
|
|
light = textureLod(sampler2D(environment, linear_sampler), st, 0.0).rgb;
|
|
}
|
|
|
|
{
|
|
float c[9] = float[](
|
|
0.282095, //l0
|
|
0.488603 * ray_dir.y, //l1n1
|
|
0.488603 * ray_dir.z, //l1n0
|
|
0.488603 * ray_dir.x, //l1p1
|
|
1.092548 * ray_dir.x * ray_dir.y, //l2n2
|
|
1.092548 * ray_dir.y * ray_dir.z, //l2n1
|
|
//0.315392 * (ray_dir.x * ray_dir.x + ray_dir.y * ray_dir.y + 2.0 * ray_dir.z * ray_dir.z), //l20
|
|
0.315392 * (3.0 * ray_dir.z * ray_dir.z - 1.0), //l20
|
|
1.092548 * ray_dir.x * ray_dir.z, //l2p1
|
|
0.546274 * (ray_dir.x * ray_dir.x - ray_dir.y * ray_dir.y) //l2p2
|
|
);
|
|
|
|
for (uint j = 0; j < 9; j++) {
|
|
probe_sh_accum[j].rgb += light * c[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (params.ray_from > 0) {
|
|
for (uint j = 0; j < 9; j++) { //accum from existing
|
|
probe_sh_accum[j] += light_probes.data[probe_index * 9 + j];
|
|
}
|
|
}
|
|
|
|
if (params.ray_to == params.ray_count) {
|
|
for (uint j = 0; j < 9; j++) { //accum from existing
|
|
probe_sh_accum[j] *= 4.0 / float(params.ray_count);
|
|
}
|
|
}
|
|
|
|
for (uint j = 0; j < 9; j++) { //accum from existing
|
|
light_probes.data[probe_index * 9 + j] = probe_sh_accum[j];
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef MODE_DILATE
|
|
|
|
vec4 c = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0);
|
|
//sides first, as they are closer
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-1, 0), params.atlas_slice), 0);
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(0, 1), params.atlas_slice), 0);
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(1, 0), params.atlas_slice), 0);
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(0, -1), params.atlas_slice), 0);
|
|
//endpoints second
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-1, -1), params.atlas_slice), 0);
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-1, 1), params.atlas_slice), 0);
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(1, -1), params.atlas_slice), 0);
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(1, 1), params.atlas_slice), 0);
|
|
|
|
//far sides third
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-2, 0), params.atlas_slice), 0);
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(0, 2), params.atlas_slice), 0);
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(2, 0), params.atlas_slice), 0);
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(0, -2), params.atlas_slice), 0);
|
|
|
|
//far-mid endpoints
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-2, -1), params.atlas_slice), 0);
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-2, 1), params.atlas_slice), 0);
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(2, -1), params.atlas_slice), 0);
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(2, 1), params.atlas_slice), 0);
|
|
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-1, -2), params.atlas_slice), 0);
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-1, 2), params.atlas_slice), 0);
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(1, -2), params.atlas_slice), 0);
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(1, 2), params.atlas_slice), 0);
|
|
//far endpoints
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-2, -2), params.atlas_slice), 0);
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-2, 2), params.atlas_slice), 0);
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(2, -2), params.atlas_slice), 0);
|
|
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(2, 2), params.atlas_slice), 0);
|
|
|
|
imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice), c);
|
|
|
|
#endif
|
|
}
|