|
@@ -154,7 +154,8 @@ uint trace_ray(vec3 p_from, vec3 p_to, bool p_any_hit, out float r_distance, out
|
|
|
vec3 dir_cell = normalize(rel_cell);
|
|
|
vec3 delta = min(abs(1.0 / dir_cell), bake_params.grid_size); // Use bake_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;
|
|
|
+ const vec3 init_next_cell = vec3(icell) + max(vec3(0), sign(step));
|
|
|
+ vec3 t_max = mix(vec3(0), (init_next_cell - from_cell) / dir_cell, notEqual(step, vec3(0))); // Distance to next boundary.
|
|
|
|
|
|
uint iters = 0;
|
|
|
while (all(greaterThanEqual(icell, ivec3(0))) && all(lessThan(icell, ivec3(bake_params.grid_size))) && (iters < 1000)) {
|
|
@@ -225,7 +226,6 @@ uint trace_ray(vec3 p_from, vec3 p_to, bool p_any_hit, out float r_distance, out
|
|
|
// Return early if any hit was requested.
|
|
|
return RAY_ANY;
|
|
|
}
|
|
|
-
|
|
|
vec3 position = p_from + dir * distance;
|
|
|
vec3 hit_cell = (position - bake_params.to_cell_offset) * bake_params.to_cell_size;
|
|
|
if (icell != ivec3(hit_cell)) {
|
|
@@ -242,6 +242,17 @@ uint trace_ray(vec3 p_from, vec3 p_to, bool p_any_hit, out float r_distance, out
|
|
|
}
|
|
|
|
|
|
if (distance < best_distance) {
|
|
|
+ switch (triangle.cull_mode) {
|
|
|
+ case CULL_DISABLED:
|
|
|
+ backface = false;
|
|
|
+ break;
|
|
|
+ case CULL_FRONT:
|
|
|
+ backface = !backface;
|
|
|
+ break;
|
|
|
+ case CULL_BACK: // Default behavior.
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
hit = backface ? RAY_BACK : RAY_FRONT;
|
|
|
best_distance = distance;
|
|
|
r_distance = distance;
|
|
@@ -271,17 +282,16 @@ uint trace_ray(vec3 p_from, vec3 p_to, bool p_any_hit, out float r_distance, out
|
|
|
}
|
|
|
|
|
|
// There should be only one axis updated at a time for DDA to work properly.
|
|
|
- bvec3 mask = bvec3(true, false, false);
|
|
|
- float m = side.x;
|
|
|
- if (side.y < m) {
|
|
|
- m = side.y;
|
|
|
- mask = bvec3(false, true, false);
|
|
|
- }
|
|
|
- if (side.z < m) {
|
|
|
- mask = bvec3(false, false, true);
|
|
|
+ if (t_max.x < t_max.y && t_max.x < t_max.z) {
|
|
|
+ icell.x += step.x;
|
|
|
+ t_max.x += delta.x;
|
|
|
+ } else if (t_max.y < t_max.z) {
|
|
|
+ icell.y += step.y;
|
|
|
+ t_max.y += delta.y;
|
|
|
+ } else {
|
|
|
+ icell.z += step.z;
|
|
|
+ t_max.z += delta.z;
|
|
|
}
|
|
|
- side += vec3(mask) * delta;
|
|
|
- icell += ivec3(vec3(mask)) * step;
|
|
|
iters++;
|
|
|
}
|
|
|
|
|
@@ -294,6 +304,27 @@ uint trace_ray_closest_hit_triangle(vec3 p_from, vec3 p_to, out uint r_triangle,
|
|
|
return trace_ray(p_from, p_to, false, distance, normal, r_triangle, r_barycentric);
|
|
|
}
|
|
|
|
|
|
+uint trace_ray_closest_hit_triangle_albedo_alpha(vec3 p_from, vec3 p_to, out vec4 albedo_alpha, out vec3 hit_position) {
|
|
|
+ float distance;
|
|
|
+ vec3 normal;
|
|
|
+ uint tidx;
|
|
|
+ vec3 barycentric;
|
|
|
+
|
|
|
+ uint ret = trace_ray(p_from, p_to, false, distance, normal, tidx, barycentric);
|
|
|
+ if (ret != RAY_MISS) {
|
|
|
+ Vertex vert0 = vertices.data[triangles.data[tidx].indices.x];
|
|
|
+ Vertex vert1 = vertices.data[triangles.data[tidx].indices.y];
|
|
|
+ Vertex vert2 = vertices.data[triangles.data[tidx].indices.z];
|
|
|
+
|
|
|
+ vec3 uvw = vec3(barycentric.x * vert0.uv + barycentric.y * vert1.uv + barycentric.z * vert2.uv, float(triangles.data[tidx].slice));
|
|
|
+
|
|
|
+ albedo_alpha = textureLod(sampler2DArray(albedo_tex, linear_sampler), uvw, 0);
|
|
|
+ hit_position = barycentric.x * vert0.position + barycentric.y * vert1.position + barycentric.z * vert2.position;
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
uint trace_ray_closest_hit_distance(vec3 p_from, vec3 p_to, out float r_distance, out vec3 r_normal) {
|
|
|
uint triangle;
|
|
|
vec3 barycentric;
|
|
@@ -392,6 +423,8 @@ vec2 get_vogel_disk(float p_i, float p_rotation, float p_sample_count_sqrt) {
|
|
|
}
|
|
|
|
|
|
void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool p_soft_shadowing, out vec3 r_light, out vec3 r_light_dir, inout uint r_noise, float p_texel_size, out float r_shadow) {
|
|
|
+ const float EPSILON = 0.00001;
|
|
|
+
|
|
|
r_light = vec3(0.0f);
|
|
|
r_shadow = 0.0f;
|
|
|
|
|
@@ -460,6 +493,7 @@ void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool
|
|
|
vec3 light_to_point_bitan = normalize(cross(light_to_point, light_to_point_tan));
|
|
|
|
|
|
uint hits = 0;
|
|
|
+ float aa_power = 0.0;
|
|
|
for (uint i = 0; i < ray_count; i++) {
|
|
|
// Create a random sample within the texel.
|
|
|
vec2 disk_sample = (halton_map[i] - vec2(0.5)) * p_texel_size * light_data.shadow_blur;
|
|
@@ -468,9 +502,13 @@ void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool
|
|
|
vec3 origin = p_position - disk_aligned;
|
|
|
vec3 light_dir = normalize(light_pos - origin);
|
|
|
|
|
|
+ float power = 0.0;
|
|
|
+ uint power_accm = 0;
|
|
|
+ vec3 prev_pos = origin;
|
|
|
if (use_soft_shadows) {
|
|
|
uint soft_shadow_hits = 0;
|
|
|
for (uint j = 0; j < shadowing_ray_count; j++) {
|
|
|
+ origin = prev_pos;
|
|
|
// Optimization:
|
|
|
// Once already traced an important proportion of rays, if all are hits or misses,
|
|
|
// assume we're not in the penumbra so we can infer the rest would have the same result.
|
|
@@ -490,24 +528,116 @@ void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool
|
|
|
float vogel_index = float(total_ray_count - 1 - (i * shadowing_ray_count + j)); // Start from (total_ray_count - 1) so we check the outer points first.
|
|
|
vec2 light_disk_sample = get_vogel_disk(vogel_index, a, shadowing_ray_count_sqrt) * soft_shadowing_disk_size * light_data.shadow_blur;
|
|
|
vec3 light_disk_to_point = normalize(light_to_point + light_disk_sample.x * light_to_point_tan + light_disk_sample.y * light_to_point_bitan);
|
|
|
- // Offset the ray origin for AA, offset the light position for soft shadows.
|
|
|
- if (trace_ray_any_hit(origin - light_disk_to_point * (bake_params.bias + length(disk_sample)), p_position - light_disk_to_point * dist) == RAY_MISS) {
|
|
|
- soft_shadow_hits++;
|
|
|
+ float sample_penumbra = 0.0;
|
|
|
+ bool sample_did_hit = false;
|
|
|
+
|
|
|
+ for (uint iter = 0; iter < bake_params.transparency_rays; iter++) {
|
|
|
+ vec4 hit_albedo = vec4(1.0);
|
|
|
+ vec3 hit_position;
|
|
|
+ // Offset the ray origin for AA, offset the light position for soft shadows.
|
|
|
+ uint ret = trace_ray_closest_hit_triangle_albedo_alpha(origin - light_disk_to_point * (bake_params.bias + length(disk_sample)), p_position - light_disk_to_point * dist, hit_albedo, hit_position);
|
|
|
+ if (ret == RAY_MISS) {
|
|
|
+ if (!sample_did_hit) {
|
|
|
+ sample_penumbra = 1.0;
|
|
|
+ }
|
|
|
+ soft_shadow_hits += 1;
|
|
|
+ break;
|
|
|
+ } else if (ret == RAY_FRONT || ret == RAY_BACK) {
|
|
|
+ bool contribute = ret == RAY_FRONT || !sample_did_hit;
|
|
|
+ if (!sample_did_hit) {
|
|
|
+ sample_penumbra = 1.0;
|
|
|
+ sample_did_hit = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ soft_shadow_hits += 1;
|
|
|
+
|
|
|
+ if (contribute) {
|
|
|
+ sample_penumbra = max(sample_penumbra - hit_albedo.a - EPSILON, 0.0);
|
|
|
+ }
|
|
|
+ origin = hit_position + r_light_dir * bake_params.bias;
|
|
|
+
|
|
|
+ if (sample_penumbra - EPSILON <= 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
+ power += sample_penumbra;
|
|
|
+ power_accm++;
|
|
|
}
|
|
|
+
|
|
|
hits += soft_shadow_hits;
|
|
|
- } else {
|
|
|
- // Offset the ray origin based on the disk. Also increase the bias for further samples to avoid bleeding.
|
|
|
- if (trace_ray_any_hit(origin + light_dir * (bake_params.bias + length(disk_sample)), light_pos) == RAY_MISS) {
|
|
|
- hits++;
|
|
|
+ } else { // No soft shadows.
|
|
|
+ float sample_penumbra = 0.0;
|
|
|
+ bool sample_did_hit = false;
|
|
|
+ for (uint iter = 0; iter < bake_params.transparency_rays; iter++) {
|
|
|
+ vec4 hit_albedo = vec4(1.0);
|
|
|
+ vec3 hit_position;
|
|
|
+ // Offset the ray origin for AA, offset the light position for soft shadows.
|
|
|
+ uint ret = trace_ray_closest_hit_triangle_albedo_alpha(origin + light_dir * (bake_params.bias + length(disk_sample)), light_pos, hit_albedo, hit_position);
|
|
|
+ if (ret == RAY_MISS) {
|
|
|
+ if (!sample_did_hit) {
|
|
|
+ sample_penumbra = 1.0;
|
|
|
+ }
|
|
|
+ hits++;
|
|
|
+ break;
|
|
|
+ } else if (ret == RAY_FRONT || ret == RAY_BACK) {
|
|
|
+ bool contribute = ret == RAY_FRONT || !sample_did_hit;
|
|
|
+ if (!sample_did_hit) {
|
|
|
+ sample_penumbra = 1.0;
|
|
|
+ sample_did_hit = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ hits++;
|
|
|
+
|
|
|
+ if (contribute) {
|
|
|
+ sample_penumbra = max(sample_penumbra - hit_albedo.a - EPSILON, 0.0);
|
|
|
+ }
|
|
|
+ origin = hit_position + r_light_dir * bake_params.bias;
|
|
|
+
|
|
|
+ if (sample_penumbra - EPSILON <= 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
+ power += sample_penumbra;
|
|
|
+ power_accm = 1;
|
|
|
}
|
|
|
+ aa_power = power / float(power_accm);
|
|
|
}
|
|
|
- penumbra = float(hits) / float(total_ray_count);
|
|
|
- } else {
|
|
|
- if (trace_ray_any_hit(p_position + r_light_dir * bake_params.bias, light_pos) == RAY_MISS) {
|
|
|
- penumbra = 1.0;
|
|
|
+ penumbra = aa_power;
|
|
|
+ } else { // No soft shadows.
|
|
|
+ bool did_hit = false;
|
|
|
+ penumbra = 0.0;
|
|
|
+ for (uint iter = 0; iter < bake_params.transparency_rays; iter++) {
|
|
|
+ vec4 hit_albedo = vec4(1.0);
|
|
|
+ vec3 hit_position;
|
|
|
+ uint ret = trace_ray_closest_hit_triangle_albedo_alpha(p_position + r_light_dir * bake_params.bias, light_pos, hit_albedo, hit_position);
|
|
|
+ if (ret == RAY_MISS) {
|
|
|
+ if (!did_hit) {
|
|
|
+ penumbra = 1.0;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ } else if (ret == RAY_FRONT || ret == RAY_BACK) {
|
|
|
+ bool contribute = (ret == RAY_FRONT || !did_hit);
|
|
|
+ if (!did_hit) {
|
|
|
+ penumbra = 1.0;
|
|
|
+ did_hit = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (contribute) {
|
|
|
+ penumbra = max(penumbra - hit_albedo.a - EPSILON, 0.0);
|
|
|
+ }
|
|
|
+
|
|
|
+ p_position = hit_position + r_light_dir * bake_params.bias;
|
|
|
+
|
|
|
+ if (penumbra - EPSILON <= 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
+ penumbra = clamp(penumbra, 0.0, 1.0);
|
|
|
}
|
|
|
|
|
|
r_shadow = penumbra;
|
|
@@ -533,6 +663,7 @@ vec3 trace_indirect_light(vec3 p_position, vec3 p_ray_dir, inout uint r_noise, f
|
|
|
vec3 position = p_position;
|
|
|
vec3 ray_dir = p_ray_dir;
|
|
|
uint max_depth = max(bake_params.bounces, 1);
|
|
|
+ uint transparency_rays_left = bake_params.transparency_rays;
|
|
|
vec3 throughput = vec3(1.0);
|
|
|
vec3 light = vec3(0.0);
|
|
|
for (uint depth = 0; depth < max_depth; depth++) {
|
|
@@ -546,6 +677,8 @@ vec3 trace_indirect_light(vec3 p_position, vec3 p_ray_dir, inout uint r_noise, f
|
|
|
vec3 uvw = vec3(barycentric.x * vert0.uv + barycentric.y * vert1.uv + barycentric.z * vert2.uv, float(triangles.data[tidx].slice));
|
|
|
position = barycentric.x * vert0.position + barycentric.y * vert1.position + barycentric.z * vert2.position;
|
|
|
|
|
|
+ vec3 prev_normal = ray_dir;
|
|
|
+
|
|
|
vec3 norm0 = vec3(vert0.normal_xy, vert0.normal_z);
|
|
|
vec3 norm1 = vec3(vert1.normal_xy, vert1.normal_z);
|
|
|
vec3 norm2 = vec3(vert2.normal_xy, vert2.normal_z);
|
|
@@ -568,13 +701,29 @@ vec3 trace_indirect_light(vec3 p_position, vec3 p_ray_dir, inout uint r_noise, f
|
|
|
direct_light *= bake_params.exposure_normalization;
|
|
|
#endif
|
|
|
|
|
|
- vec3 albedo = textureLod(sampler2DArray(albedo_tex, linear_sampler), uvw, 0).rgb;
|
|
|
+ vec4 albedo_alpha = textureLod(sampler2DArray(albedo_tex, linear_sampler), uvw, 0).rgba;
|
|
|
vec3 emissive = textureLod(sampler2DArray(emission_tex, linear_sampler), uvw, 0).rgb;
|
|
|
emissive *= bake_params.exposure_normalization;
|
|
|
|
|
|
- light += throughput * emissive;
|
|
|
- throughput *= albedo;
|
|
|
- light += throughput * direct_light * bake_params.bounce_indirect_energy;
|
|
|
+ light += throughput * emissive * albedo_alpha.a;
|
|
|
+ throughput = mix(throughput, throughput * albedo_alpha.rgb, albedo_alpha.a);
|
|
|
+ light += throughput * direct_light * bake_params.bounce_indirect_energy * albedo_alpha.a;
|
|
|
+
|
|
|
+ if (albedo_alpha.a < 1.0) {
|
|
|
+ transparency_rays_left -= 1;
|
|
|
+ depth -= 1;
|
|
|
+ if (transparency_rays_left <= 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Either bounce off the transparent surface or keep going forward.
|
|
|
+ float pa = albedo_alpha.a * albedo_alpha.a;
|
|
|
+ if (randomize(r_noise) > pa) {
|
|
|
+ normal = prev_normal;
|
|
|
+ }
|
|
|
+
|
|
|
+ position += normal * bake_params.bias;
|
|
|
+ }
|
|
|
|
|
|
// Use Russian Roulette to determine a probability to terminate the bounce earlier as an optimization.
|
|
|
// <https://computergraphics.stackexchange.com/questions/2316/is-russian-roulette-really-the-answer>
|
|
@@ -592,9 +741,55 @@ vec3 trace_indirect_light(vec3 p_position, vec3 p_ray_dir, inout uint r_noise, f
|
|
|
// Look for the environment color and stop bouncing.
|
|
|
light += throughput * trace_environment_color(ray_dir);
|
|
|
break;
|
|
|
- } else {
|
|
|
- // Ignore any other trace results.
|
|
|
- break;
|
|
|
+ } else if (trace_result == RAY_BACK) {
|
|
|
+ Vertex vert0 = vertices.data[triangles.data[tidx].indices.x];
|
|
|
+ Vertex vert1 = vertices.data[triangles.data[tidx].indices.y];
|
|
|
+ Vertex vert2 = vertices.data[triangles.data[tidx].indices.z];
|
|
|
+ vec3 uvw = vec3(barycentric.x * vert0.uv + barycentric.y * vert1.uv + barycentric.z * vert2.uv, float(triangles.data[tidx].slice));
|
|
|
+ position = barycentric.x * vert0.position + barycentric.y * vert1.position + barycentric.z * vert2.position;
|
|
|
+
|
|
|
+ vec4 albedo_alpha = textureLod(sampler2DArray(albedo_tex, linear_sampler), uvw, 0).rgba;
|
|
|
+
|
|
|
+ if (albedo_alpha.a > 1.0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ transparency_rays_left -= 1;
|
|
|
+ depth -= 1;
|
|
|
+ if (transparency_rays_left <= 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ vec3 norm0 = vec3(vert0.normal_xy, vert0.normal_z);
|
|
|
+ vec3 norm1 = vec3(vert1.normal_xy, vert1.normal_z);
|
|
|
+ vec3 norm2 = vec3(vert2.normal_xy, vert2.normal_z);
|
|
|
+ vec3 normal = barycentric.x * norm0 + barycentric.y * norm1 + barycentric.z * norm2;
|
|
|
+
|
|
|
+ vec3 direct_light = vec3(0.0f);
|
|
|
+#ifdef USE_LIGHT_TEXTURE_FOR_BOUNCES
|
|
|
+ direct_light += textureLod(sampler2DArray(source_light, linear_sampler), uvw, 0.0).rgb;
|
|
|
+#else
|
|
|
+ // Trace the lights directly. Significantly more expensive but more accurate in scenarios
|
|
|
+ // where the lightmap texture isn't reliable.
|
|
|
+ for (uint i = 0; i < bake_params.light_count; i++) {
|
|
|
+ vec3 light;
|
|
|
+ vec3 light_dir;
|
|
|
+ float shadow;
|
|
|
+ trace_direct_light(position, normal, i, false, light, light_dir, r_noise, p_texel_size, shadow);
|
|
|
+ direct_light += light * lights.data[i].indirect_energy;
|
|
|
+ }
|
|
|
+
|
|
|
+ direct_light *= bake_params.exposure_normalization;
|
|
|
+#endif
|
|
|
+
|
|
|
+ vec3 emissive = textureLod(sampler2DArray(emission_tex, linear_sampler), uvw, 0).rgb;
|
|
|
+ emissive *= bake_params.exposure_normalization;
|
|
|
+
|
|
|
+ light += throughput * emissive * albedo_alpha.a;
|
|
|
+ throughput = mix(mix(throughput, throughput * albedo_alpha.rgb, albedo_alpha.a), vec3(0.0), albedo_alpha.a);
|
|
|
+ light += throughput * direct_light * bake_params.bounce_indirect_energy * albedo_alpha.a;
|
|
|
+
|
|
|
+ position += ray_dir * bake_params.bias;
|
|
|
}
|
|
|
}
|
|
|
|