xash3d-fwgs/ref/vk/shaders/light_polygon.glsl

354 lines
12 KiB
GLSL

#define MAX_POLYGON_VERTEX_COUNT 8
#define MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING 3
#include "peters2021-sampling/polygon_clipping.glsl"
#include "peters2021-sampling/polygon_sampling.glsl"
#include "debug.glsl"
#include "noise.glsl"
#include "utils.glsl"
#define DO_ALL_IN_CLUSTER 1
#ifndef RAY_BOUNCE
#define PROJECTED
//#define SOLID
//#define SIMPLE_SOLID
#else
//#define PROJECTED
//#define SOLID
//#define SIMPLE_SOLID
#endif
struct SampleContext {
mat4x3 world_to_shading;
};
SampleContext buildSampleContext(vec3 position, vec3 normal, vec3 view_dir) {
SampleContext ctx;
const float normal_dot_outgoing = dot(normal, -view_dir);
const vec3 x_axis = normalize(fma(vec3(-normal_dot_outgoing), normal, -view_dir));
const vec3 y_axis = cross(normal, x_axis);
const mat3 rotation = transpose(mat3(x_axis, y_axis, normal));
ctx.world_to_shading = mat4x3(rotation[0], rotation[1], rotation[2], -rotation * position);
return ctx;
}
vec4 getPolygonLightSampleSimple(vec3 P, vec3 view_dir, const PolygonLight poly) {
const uint vertices_offset = poly.vertices_count_offset & 0xffffu;
uint vertices_count = poly.vertices_count_offset >> 16;
vec3 v[3];
vertices_count = 3; // FIXME
for (uint i = 0; i < vertices_count; ++i) {
v[i] = lights.m.polygon_vertices[vertices_offset + i].xyz;
}
vec2 rnd = vec2(sqrt(rand01()), rand01());
rnd.y *= rnd.x;
rnd.x = 1.f - rnd.x;
const vec3 light_dir = baryMix(v[0], v[1], v[2], rnd) - P;
const vec3 light_dir_n = normalize(light_dir);
const float contrib = - poly.area * dot(light_dir_n, poly.plane.xyz ) / dot(light_dir, light_dir);
return vec4(light_dir_n, contrib);
}
vec4 getPolygonLightSampleSimpleSolid(vec3 P, vec3 view_dir, const PolygonLight poly) {
const uint vertices_offset = poly.vertices_count_offset & 0xffffu;
uint vertices_count = poly.vertices_count_offset >> 16;
uint selected = 0;
float total_contrib = 0.;
float eps1 = rand01();
vec3 v[3];
v[0] = normalize(lights.m.polygon_vertices[vertices_offset + 0].xyz - P);
v[1] = normalize(lights.m.polygon_vertices[vertices_offset + 1].xyz - P);
const float householder_sign = (v[0].x > 0.0f) ? -1.0f : 1.0f;
const vec2 householder_yz = v[0].yz * (1.0f / (abs(v[0].x) + 1.0f));
for (uint i = 2; i < vertices_count; ++i) {
v[2] = normalize(lights.m.polygon_vertices[vertices_offset + i].xyz - P);
// effectively mindlessly copypasted from polygon_sampling.glsl, Peters 2021
// https://github.com/MomentsInGraphics/vulkan_renderer/blob/main/src/shaders/polygon_sampling.glsl
const float dot_0_1 = dot(v[0], v[1]);
const float dot_0_2 = dot(v[1], v[2]);
const float dot_1_2 = dot(v[0], v[2]);
const float dot_householder_0 = fma(-householder_sign, v[1].x, dot_0_1);
const float dot_householder_2 = fma(-householder_sign, v[2].x, dot_1_2);
const mat2 bottom_right_minor = mat2(
fma(vec2(-dot_householder_0), householder_yz, v[1].yz),
fma(vec2(-dot_householder_2), householder_yz, v[2].yz));
const float simplex_volume = abs(determinant(bottom_right_minor));
const float dot_0_2_plus_1_2 = dot_0_2 + dot_1_2;
const float one_plus_dot_0_1 = 1.0f + dot_0_1;
const float tangent = simplex_volume / (one_plus_dot_0_1 + dot_0_2_plus_1_2);
const float contrib = 2.f * (atan(tangent) + (tangent < 0.f ? M_PI : 0.));
if (contrib < 1e-6)
continue;
const float tau = total_contrib / (total_contrib + contrib);
total_contrib += contrib;
if (eps1 < tau) {
eps1 /= tau;
} else {
selected = i;
eps1 = (eps1 - tau) / (1. - tau);
}
// selected = 2;
// break;
v[1] = v[2];
}
if (selected == 0)
return vec4(0.);
vec2 rnd = vec2(sqrt(rand01()), rand01());
rnd.y *= rnd.x;
rnd.x = 1.f - rnd.x;
const vec3 light_dir = baryMix(
lights.m.polygon_vertices[vertices_offset + 0].xyz,
lights.m.polygon_vertices[vertices_offset + selected - 1].xyz,
lights.m.polygon_vertices[vertices_offset + selected].xyz,
rnd) - P;
const vec3 light_dir_n = normalize(light_dir);
return vec4(light_dir_n, total_contrib);
}
vec4 getPolygonLightSampleProjected(vec3 view_dir, SampleContext ctx, const PolygonLight poly) {
vec3 clipped[MAX_POLYGON_VERTEX_COUNT];
const uint vertices_offset = poly.vertices_count_offset & 0xffffu;
uint vertices_count = poly.vertices_count_offset >> 16;
for (uint i = 0; i < vertices_count; ++i) {
clipped[i] = ctx.world_to_shading * vec4(lights.m.polygon_vertices[vertices_offset + i].xyz, 1.);
}
vertices_count = clip_polygon(vertices_count, clipped);
if (vertices_count == 0)
return vec4(0.f);
const projected_solid_angle_polygon_t sap = prepare_projected_solid_angle_polygon_sampling(vertices_count, clipped);
const float contrib = sap.projected_solid_angle;
if (contrib <= 0.f)
return vec4(0.f);
vec2 rnd = vec2(rand01(), rand01());
const vec3 light_dir = (transpose(ctx.world_to_shading) * sample_projected_solid_angle_polygon(sap, rnd)).xyz;
return vec4(light_dir, contrib);
}
vec4 getPolygonLightSampleSolid(vec3 P, vec3 view_dir, SampleContext ctx, const PolygonLight poly) {
vec3 clipped[MAX_POLYGON_VERTEX_COUNT];
const uint vertices_offset = poly.vertices_count_offset & 0xffffu;
uint vertices_count = poly.vertices_count_offset >> 16;
for (uint i = 0; i < vertices_count; ++i) {
clipped[i] = lights.m.polygon_vertices[vertices_offset + i].xyz;
}
#define DONT_CLIP
#ifndef DONT_CLIP
vertices_count = clip_polygon(vertices_count, clipped);
if (vertices_count == 0)
return vec4(0.f);
#endif
const solid_angle_polygon_t sap = prepare_solid_angle_polygon_sampling(vertices_count, clipped, P);
const float contrib = sap.solid_angle;
if (contrib <= 0.f)
return vec4(0.f);
vec2 rnd = vec2(rand01(), rand01());
const vec3 light_dir = sample_solid_angle_polygon(sap, rnd).xyz;
return vec4(light_dir, contrib);
}
void sampleSinglePolygonLight(in vec3 P, in vec3 N, in vec3 view_dir, in SampleContext ctx, in MaterialProperties material, in PolygonLight poly, inout vec3 diffuse, inout vec3 specular) {
// TODO cull by poly plane
#ifdef PROJECTED
const vec4 light_sample_dir = getPolygonLightSampleProjected(view_dir, ctx, poly);
#else
const vec4 light_sample_dir = getPolygonLightSampleSolid(P, view_dir, ctx, poly);
#endif
if (light_sample_dir.w <= 0.)
return;
const float dist = - dot(vec4(P, 1.f), poly.plane) / dot(light_sample_dir.xyz, poly.plane.xyz);
if (shadowed(P, light_sample_dir.xyz, dist))
return;
vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.);
evalSplitBRDF(N, light_sample_dir.xyz, view_dir, material, poly_diffuse, poly_specular);
const float estimate = light_sample_dir.w;
const vec3 emissive = poly.emissive * estimate;
diffuse += emissive * poly_diffuse;
specular += emissive * poly_specular;
}
#if 0
// Sample random one
void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) {
const uint num_polygons = uint(light_grid.clusters_[cluster_index].num_polygons);
if (num_polygons == 0)
return;
const uint selected = uint(light_grid.clusters_[cluster_index].polygons[rand_range(num_polygons)]);
const PolygonLight poly = lights.m.polygons[selected];
const SampleContext ctx = buildSampleContext(P, N, view_dir);
sampleSinglePolygonLight(P, N, view_dir, ctx, material, poly, diffuse, specular);
const float sampling_factor = float(num_polygons);
diffuse *= sampling_factor;
specular *= sampling_factor;
}
#elif 1
void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) {
#if DO_ALL_IN_CLUSTER
const SampleContext ctx = buildSampleContext(P, N, view_dir);
#define USE_CLUSTERS
#ifdef USE_CLUSTERS
const uint num_polygons = uint(light_grid.clusters_[cluster_index].num_polygons);
for (uint i = 0; i < num_polygons; ++i) {
const uint index = uint(light_grid.clusters_[cluster_index].polygons[i]);
//if (index != 5) // < 6 || index >= 8)
// continue;
#else
for (uint index = 0; index < lights.m.num_polygons; ++index) {
#endif
const PolygonLight poly = lights.m.polygons[index];
const float plane_dist = dot(poly.plane, vec4(P, 1.f));
if (plane_dist < 0.)
continue;
#ifdef PROJECTED
const vec4 light_sample_dir = getPolygonLightSampleProjected(view_dir, ctx, poly);
#elif defined(SOLID)
const vec4 light_sample_dir = getPolygonLightSampleSolid(P, view_dir, ctx, poly);
#elif defined(SIMPLE_SOLID)
const vec4 light_sample_dir = getPolygonLightSampleSimpleSolid(P, view_dir, poly);
#else
const vec4 light_sample_dir = getPolygonLightSampleSimple(P, view_dir, poly);
#endif
if (light_sample_dir.w <= 0.)
continue;
const float dist = - plane_dist / dot(light_sample_dir.xyz, poly.plane.xyz);
const vec3 emissive = poly.emissive;
if (!shadowed(P, light_sample_dir.xyz, dist)) {
//const float estimate = total_contrib;
const float estimate = light_sample_dir.w;
vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.);
evalSplitBRDF(N, light_sample_dir.xyz, view_dir, material, poly_diffuse, poly_specular);
diffuse += emissive * estimate * poly_diffuse;
specular += emissive * estimate * poly_specular;
if (IS_INVALID3(specular) || any(lessThan(specular,vec3(0.)))) {
#ifdef SHADER_DEBUG_ENABLE
debugPrintfEXT("%d INVALID specular=(%f,%f,%f) light=%d emissive=(%f,%f,%f) estimate=%f poly_specular=(%f,%f,%f)",
__LINE__, PRIVEC3(specular), index, PRIVEC3(emissive), estimate, PRIVEC3(poly_specular));
#endif
specular = vec3(0.);
}
}
}
#else // DO_ALL_IN_CLUSTERS
#ifdef USE_CLUSTERS
// TODO move this to pickPolygonLight function
const uint num_polygons = uint(light_grid.clusters_[cluster_index].num_polygons);
#else
const uint num_polygons = lights.m.num_polygons;
#endif
uint selected = 0;
float total_contrib = 0.;
float eps1 = rand01();
for (uint i = 0; i < num_polygons; ++i) {
#ifdef USE_CLUSTERS
const uint index = uint(light_grid.clusters_[cluster_index].polygons[i]);
#else
const uint index = i;
#endif
const PolygonLight poly = lights.m.polygons[index];
const vec3 dir = poly.center - P;
const vec3 light_dir = normalize(dir);
float contrib_estimate = poly.area * dot(-light_dir, poly.plane.xyz) / (1e-3 + dot(dir, dir));
if (contrib_estimate < 1e-6)
continue;
contrib_estimate = 1.f;
const float tau = total_contrib / (total_contrib + contrib_estimate);
total_contrib += contrib_estimate;
if (eps1 < tau) {
eps1 /= tau;
} else {
selected = index + 1;
eps1 = (eps1 - tau) / (1. - tau);
}
}
if (selected == 0) {
//diffuse = vec3(1., 0., 0.);
return;
}
#if 0
const PolygonLight poly = lights.m.polygons[selected - 1];
const vec3 emissive = poly.emissive;
vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.);
evalSplitBRDF(N, normalize(poly.center-P), view_dir, material, poly_diffuse, poly_specular);
diffuse += emissive * total_contrib;
specular += emissive * total_contrib;
#else
const SampleContext ctx = buildSampleContext(P, N, view_dir);
const PolygonLight poly = lights.m.polygons[selected - 1];
#ifdef PROJECTED
const vec4 light_sample_dir = getPolygonLightSampleProjected(view_dir, ctx, poly);
#else
const vec4 light_sample_dir = getPolygonLightSampleSolid(P, view_dir, ctx, poly);
#endif
if (light_sample_dir.w <= 0.)
return;
const float dist = - dot(vec4(P, 1.f), poly.plane) / dot(light_sample_dir.xyz, poly.plane.xyz);
const vec3 emissive = poly.emissive;
//if (true) {//!shadowed(P, light_sample_dir.xyz, dist)) {
if (!shadowed(P, light_sample_dir.xyz, dist)) {
//const float estimate = total_contrib;
const float estimate = light_sample_dir.w;
vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.);
evalSplitBRDF(N, light_sample_dir.xyz, view_dir, material, poly_diffuse, poly_specular);
diffuse += emissive * estimate;
specular += emissive * estimate;
}
#endif
#endif
}
#endif