254 lines
9.1 KiB
GLSL
254 lines
9.1 KiB
GLSL
#extension GL_EXT_control_flow_attributes : require
|
|
#include "debug.glsl"
|
|
|
|
const float color_culling_threshold = 0;//600./color_factor;
|
|
const float shadow_offset_fudge = .1;
|
|
|
|
#include "brdf.glsl"
|
|
#include "light_common.glsl"
|
|
|
|
#if LIGHT_POLYGON
|
|
#include "light_polygon.glsl"
|
|
#endif
|
|
|
|
#if LIGHT_POINT
|
|
// TODO Consider splitting into different arrays:
|
|
// 1. Spherical lights
|
|
// 2. Spotlights
|
|
// 3. Env|dir lights
|
|
void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) {
|
|
diffuse = specular = vec3(0.);
|
|
|
|
//diffuse = vec3(1.);//float(lights.m.num_point_lights) / 64.);
|
|
#define USE_CLUSTERS
|
|
#ifdef USE_CLUSTERS
|
|
const uint num_point_lights = uint(light_grid.clusters_[cluster_index].num_point_lights);
|
|
for (uint j = 0; j < num_point_lights; ++j) {
|
|
const uint i = uint(light_grid.clusters_[cluster_index].point_lights[j]);
|
|
|
|
// HACK: work around corrupted/stale cluster indexes
|
|
// See https://github.com/w23/xash3d-fwgs/issues/730
|
|
if (i >= lights.m.num_point_lights)
|
|
continue;
|
|
#else
|
|
for (uint i = 0; i < lights.m.num_point_lights; ++i) {
|
|
#endif
|
|
|
|
const vec3 spotlight_dir = lights.m.point_lights[i].dir_stopdot2.xyz;
|
|
const bool is_environment = (lights.m.point_lights[i].environment != 0);
|
|
|
|
// TODO blue noise
|
|
const vec2 rnd = vec2(rand01(), rand01());
|
|
|
|
vec3 light_dir;
|
|
vec3 color = lights.m.point_lights[i].color_stopdot.rgb;
|
|
float light_dist = 0.;
|
|
float one_over_pdf = 1.;
|
|
if (is_environment) {
|
|
// Environment/directional light
|
|
// FIXME extract, it is rather different from other point/sphere/spotlights
|
|
const float cos_theta_max = lights.m.point_lights[i].dir_stopdot2.a;
|
|
const vec3 dir_sample_z = sampleConeZ(rnd, cos_theta_max);
|
|
light_dir = normalize(orthonormalBasisZ(spotlight_dir) * dir_sample_z);
|
|
|
|
// If light sample is below horizon, skip
|
|
const float light_dot = dot(light_dir, N);
|
|
if (light_dot < 1e-5)
|
|
continue;
|
|
|
|
one_over_pdf = 2. * kPi * max(0., 1. - cos_theta_max);
|
|
} /* is_environment */ else {
|
|
// Spherical lights
|
|
const vec3 light_pos = lights.m.point_lights[i].origin_r2.xyz;
|
|
const float light_r2 = lights.m.point_lights[i].origin_r2.w;
|
|
|
|
#ifdef DEBUG_VALIDATE_EXTRA
|
|
if (IS_INVALID(light_r2) || light_r2 <= 0.) {
|
|
debugPrintfEXT("light %d INVALID light_r2 = %f", i, light_r2);
|
|
}
|
|
#endif
|
|
|
|
//const vec3 ld = light_pos - P;
|
|
light_dir = light_pos - P;
|
|
const float light_dist2 = dot(light_dir, light_dir);
|
|
|
|
// Light is too close, skip
|
|
const float d2_minus_r2 = light_dist2 - light_r2;
|
|
if (d2_minus_r2 <= 0.)
|
|
continue;
|
|
|
|
light_dist = sqrt(light_dist2);
|
|
|
|
#ifdef DEBUG_VALIDATE_EXTRA
|
|
if (IS_INVALID(light_dist)) {
|
|
debugPrintfEXT("light.glsl:%d P=(%f,%f,%f) light_pos=(%f,%f,%f) light_dist2=%f light_r2=%f INVALID light_dist=%f",
|
|
__LINE__, PRIVEC3(P), PRIVEC3(light_pos), light_dist2, light_r2, light_dist);
|
|
}
|
|
#endif
|
|
|
|
// Cosine of "solid angle"
|
|
// Bad precision: const float cos_theta_max = min(1., sqrt(d2_minus_r2) / light_dist);
|
|
// Meh precision (for small r and big light dist):
|
|
const float cos_theta_max = min(1., sqrt(d2_minus_r2 / light_dist2));
|
|
// Worse precision: const float cos_theta_max = min(1., sqrt(1. - light_r2 / light_dist2));
|
|
// Possible TODO: dither cos_theta_max for large distances a bit
|
|
|
|
#ifdef DEBUG_VALIDATE_EXTRA
|
|
if (IS_INVALID(cos_theta_max) || cos_theta_max < 0. || cos_theta_max > 1.) {
|
|
debugPrintfEXT("light.glsl:%d P=(%f,%f,%f) light_pos=(%f,%f,%f) light_dist2=%f light_r2=%f INVALID cos_theta_max=%f",
|
|
__LINE__, PRIVEC3(P), PRIVEC3(light_pos), light_dist2, light_r2, cos_theta_max);
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
// Sample on the visible disc
|
|
const vec3 dir_sample_z = sampleConeZ(rnd, cos_theta_max);
|
|
const mat3 basis = orthonormalBasisZ(light_dir / light_dist);
|
|
light_dir = normalize(basis * dir_sample_z);
|
|
//light_dir = normalize(light_dir);
|
|
|
|
#ifdef DEBUG_VALIDATE_EXTRA
|
|
//DEBUG_VALIDATE_RANGE_VEC3("light.glsl", light_dir, -1., 1.);
|
|
if (IS_INVALIDV(light_dir) || any(lessThan(light_dir,vec3(-1.))) || any(greaterThan(light_dir,vec3(1.)))) { \
|
|
/*debugPrintfEXT("ld=(%f,%f,%f), ldn=(%f,%f,%f); basis=((%f,%f,%f), (%f,%f,%f), (%f,%f,%f))",
|
|
PRIVEC3(ld),
|
|
PRIVEC3(normalize(ld)),
|
|
PRIVEC3(basis[0]),
|
|
PRIVEC3(basis[1]),
|
|
PRIVEC3(basis[2]));*/
|
|
debugPrintfEXT("light.glsl:%d cos_theta_max=%f light_dist=%f dir_sample_z=(%f,%f,%f) INVALID light_dir=(%f, %f, %f)",
|
|
__LINE__, cos_theta_max, light_dist, PRIVEC3(dir_sample_z), PRIVEC3(light_dir));
|
|
}
|
|
#endif
|
|
|
|
// If light sample is below horizon, skip
|
|
const float light_dot = dot(light_dir, N);
|
|
if (light_dot < 1e-5)
|
|
continue;
|
|
|
|
float spot_attenuation = 1.;
|
|
// Spotlights
|
|
// Check for angles early
|
|
// TODO split into separate spotlights and point lights arrays
|
|
const float spot_dot = dot(light_dir, spotlight_dir);
|
|
const float stopdot2 = lights.m.point_lights[i].dir_stopdot2.a;
|
|
if (spot_dot < stopdot2)
|
|
continue;
|
|
|
|
const float stopdot = lights.m.point_lights[i].color_stopdot.a;
|
|
|
|
// For non-spotlighths stopdot will be -1.. spot_dot can never be less than that
|
|
if (spot_dot < stopdot) {
|
|
spot_attenuation = (spot_dot - stopdot2) / (stopdot - stopdot2);
|
|
#ifdef DEBUG_VALIDATE_EXTRA
|
|
if (IS_INVALID(spot_attenuation)) {
|
|
debugPrintfEXT("light.glsl:%d spot_dot=%f stopdot=%f stopdot2=%f INVALID spot_attenuation=%f",
|
|
__LINE__, spot_dot, stopdot, stopdot2, spot_attenuation);
|
|
}
|
|
#endif
|
|
// Skip the rest of the computation for points completely outside of the light cone
|
|
if (spot_attenuation <= 0.)
|
|
continue;
|
|
} // Spotlight
|
|
|
|
// d2=4489108.500000 r2=1.000000 dist=2118.751465 spot_attenuation=0.092902 INVALID pdf=-316492608.000000
|
|
// Therefore, need to clamp denom with max
|
|
// TODO need better sampling right nao
|
|
one_over_pdf = 2. * kPi * max(0., 1. - cos_theta_max) * spot_attenuation;
|
|
#ifdef DEBUG_VALIDATE_EXTRA
|
|
if (IS_INVALID(one_over_pdf) || one_over_pdf < 0.) {
|
|
debugPrintfEXT("light.glsl:%d light_dist2=%f light_r2=%f light_dist=%f spot_attenuation=%f INVALID one_over_pdf=%f",
|
|
__LINE__, light_dist2, light_r2, light_dist, spot_attenuation, one_over_pdf);
|
|
}
|
|
#endif
|
|
} // Sphere/spot lights
|
|
|
|
color *= one_over_pdf;
|
|
|
|
vec3 ldiffuse, lspecular;
|
|
evalSplitBRDF(N, light_dir, view_dir, material, ldiffuse, lspecular);
|
|
ldiffuse *= color;
|
|
lspecular *= color;
|
|
|
|
// TODO does this make sense for diffuse-vs-specular bounce modes?
|
|
const vec3 combined = ldiffuse + lspecular;
|
|
if (dot(combined,combined) < color_culling_threshold)
|
|
continue;
|
|
|
|
if (is_environment) {
|
|
if (shadowedSky(P, light_dir))
|
|
continue;
|
|
} else {
|
|
if (shadowed(P, light_dir, light_dist + shadow_offset_fudge))
|
|
continue;
|
|
}
|
|
|
|
diffuse += ldiffuse;
|
|
specular += lspecular;
|
|
} // for all lights
|
|
}
|
|
#endif
|
|
|
|
void computeLighting(vec3 P, vec3 N, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) {
|
|
diffuse = specular = vec3(0.);
|
|
|
|
// No direct lighting for white furnace mode. The only light sources is no-hit|SURF_SKY bounce indirect light.
|
|
if ((ubo.ubo.debug_flags & DEBUG_FLAG_WHITE_FURNACE) != 0) {
|
|
return;
|
|
}
|
|
|
|
#ifdef DEBUG_VALIDATE_EXTRA
|
|
if (IS_INVALIDV(P) || IS_INVALIDV(N) || IS_INVALIDV(view_dir)) {
|
|
debugPrintfEXT("INVALID computeLighting(P=(%f,%f,%f), N=(%f,%f,%f), view_dir=(%f,%f,%f))",
|
|
PRIVEC3(P), PRIVEC3(N), PRIVEC3(view_dir));
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
const ivec3 light_cell = ivec3(floor(P / LIGHT_GRID_CELL_SIZE)) - lights.m.grid_min_cell;
|
|
const uint cluster_index = uint(dot(light_cell, ivec3(1, lights.m.grid_size.x, lights.m.grid_size.x * lights.m.grid_size.y)));
|
|
|
|
#ifdef USE_CLUSTERS
|
|
if (any(greaterThanEqual(light_cell, lights.m.grid_size)) || cluster_index >= MAX_LIGHT_CLUSTERS) {
|
|
#ifdef DEBUG_VALIDATE_EXTRA
|
|
debugPrintfEXT("light_cell=(%d,%d,%d) OOB size=(%d, %d, %d)", PRIVEC3(light_cell), PRIVEC3(lights.m.grid_size));
|
|
#endif
|
|
return; // vec3(1., 0., 0.);
|
|
}
|
|
#endif
|
|
|
|
// const uint cluster_offset = cluster_index * LIGHT_CLUSTER_SIZE + HACK_OFFSET;
|
|
// const int num_dlights = int(light_grid.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_DLIGHTS_OFFSET]);
|
|
// const int num_emissive_surfaces = int(light_grid.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_EMISSIVE_SURFACES_OFFSET]);
|
|
// const uint emissive_surfaces_offset = cluster_offset + LIGHT_CLUSTER_EMISSIVE_SURFACES_DATA_OFFSET;
|
|
//C = vec3(float(num_emissive_surfaces));
|
|
|
|
//C = vec3(float(int(light_grid.clusters[cluster_index].num_emissive_surfaces)));
|
|
//C += .3 * fract(vec3(light_cell) / 4.);
|
|
|
|
#if LIGHT_POLYGON
|
|
sampleEmissiveSurfaces(P, N, view_dir, material, cluster_index, diffuse, specular);
|
|
#endif
|
|
|
|
#if LIGHT_POINT
|
|
vec3 ldiffuse = vec3(0.), lspecular = vec3(0.);
|
|
computePointLights(P, N, cluster_index, view_dir, material, ldiffuse, lspecular);
|
|
diffuse += ldiffuse;
|
|
specular += lspecular;
|
|
#endif
|
|
|
|
#ifdef DEBUG_VALIDATE_EXTRA
|
|
if (IS_INVALIDV(diffuse) || any(lessThan(diffuse,vec3(0.)))) {
|
|
debugPrintfEXT("P=(%f,%f,%f) N=(%f,%f,%f) INVALID diffuse=(%f,%f,%f)",
|
|
PRIVEC3(P), PRIVEC3(N), PRIVEC3(diffuse));
|
|
diffuse = vec3(0.);
|
|
}
|
|
|
|
if (IS_INVALIDV(specular) || any(lessThan(specular,vec3(0.)))) {
|
|
debugPrintfEXT("P=(%f,%f,%f) N=(%f,%f,%f) INVALID specular=(%f,%f,%f)",
|
|
PRIVEC3(P), PRIVEC3(N), PRIVEC3(specular));
|
|
specular = vec3(0.);
|
|
}
|
|
#endif
|
|
}
|