Permalink
Fetching contributors…
Cannot retrieve contributors at this time
718 lines (568 sloc) 20.1 KB
/*
* Copyright (C) 2016,2017 Edd Biddulph
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*
* =======================================================================
*
* GPU-based pathtracing - Ray intersection and shading.
*
* =======================================================================
*/
#ifndef NUM_BOUNCES
# define NUM_BOUNCES 0
#endif
#ifndef NUM_SHADOW_SAMPLES
# define NUM_SHADOW_SAMPLES 1
#endif
#ifndef NUM_LIGHT_SAMPLES
# define NUM_LIGHT_SAMPLES 1
#endif
#ifndef NUM_SKY_SAMPLES
# define NUM_SKY_SAMPLES 1
#endif
#ifndef NUM_AO_SAMPLES
# define NUM_AO_SAMPLES 0
#endif
#ifndef TRI_SHADOWS_ENABLE
# define TRI_SHADOWS_ENABLE 1
#endif
#ifndef DIFFUSE_MAP_ENABLE
# define DIFFUSE_MAP_ENABLE 1
#endif
#ifndef RAND_TEX_LAYERS
# define RAND_TEX_LAYERS 1
#endif
#ifndef BLUENOISE_TEX_WIDTH
# define BLUENOISE_TEX_WIDTH 64
#endif
#ifndef BLUENOISE_TEX_HEIGHT
# define BLUENOISE_TEX_HEIGHT 64
#endif
#ifndef TAA_ENABLE
# define TAA_ENABLE 0
#endif
#ifndef BUMP_ENABLE
# define BUMP_ENABLE 1
#endif
#define EPS (1.0 / 32.0)
#define MAXT 2048.0
#define PI acos(-1.0)
// Diffuse (albedo) texture map.
uniform sampler2D diffuse_texture;
// BSP tree data.
uniform sampler2D bsp_planes;
uniform sampler2D bsp_branches;
uniform isampler2D bsp_lightrefs;
// Triangle mesh data.
uniform isamplerBuffer tri_nodes0;
uniform isamplerBuffer tri_nodes1;
uniform samplerBuffer tri_vertices;
uniform isamplerBuffer triangles;
#if TAA_ENABLE
// Triangle mesh data from the previous frame.
uniform isamplerBuffer tri_nodes0_prev;
uniform isamplerBuffer tri_nodes1_prev;
uniform samplerBuffer tri_vertices_prev;
uniform isamplerBuffer triangles_prev;
#endif
// Lightsource data.
uniform samplerBuffer lights;
uniform isamplerBuffer lightrefs;
// PRNG table.
uniform sampler1D randtex;
uniform sampler2DArray bluenoise;
// TAA feedback textures and transformations.
uniform sampler2D taa_world;
uniform mat4 current_world_matrix = mat4(1);
uniform mat4 previous_world_matrix = mat4(1);
uniform vec3 previous_view_origin = vec3(0);
// Uniform attributes.
uniform int frame = 0;
uniform float ao_radius = 150.0;
uniform vec3 ao_color = vec3(1);
uniform float bounce_factor = 0.75;
uniform float exposure = 1.5;
uniform float gamma = 2.2;
uniform float bump_factor = 0.045;
// node offset for restricting the way in which shadows are received from triangle meshes.
uniform int shadow_ray_node_offset = 0;
// Inputs from the vertex stage.
in vec4 texcoords[8], color;
// Globals.
vec4 out_pln;
vec3 bluenoise_sample = texture(bluenoise, vec3(gl_FragCoord.st / vec2(BLUENOISE_TEX_WIDTH, BLUENOISE_TEX_HEIGHT), mod(frame, RAND_TEX_LAYERS))).rgb;
// Produces a pair of random numbers.
vec2 rand(int index)
{
return fract(texelFetch(randtex, index, 0).rg + bluenoise_sample.rg);
}
// Returns the interval along the given ray which intersects an axis-aligned cuboid of the given size centered at the origin.
vec2 boxInterval(vec3 ro, vec3 rd, vec3 size)
{
vec3 mins = (size * -sign(rd) - ro) / rd;
vec3 maxs = (size * +sign(rd) - ro) / rd;
return vec2(max(max(mins.x, mins.y), mins.z), min(min(maxs.x, maxs.y), maxs.z));
}
// Returns false if the given ray intersects any triangle mesh in the scene and the distance
// to the intersection along the ray is less than maxdist, otherwise returns true.
bool traceRayShadowTri(vec3 ro, vec3 rd, float maxdist,
isamplerBuffer nodes0, isamplerBuffer nodes1, samplerBuffer vertices, isamplerBuffer tris)
{
#if TRI_SHADOWS_ENABLE
// Start at the root node, or an offset node index.
int node = shadow_ray_node_offset;
do
{
ivec4 n0 = texelFetch(nodes0, node);
ivec4 n1 = texelFetch(nodes1, node);
// Test for intersection with the bounding box.
vec2 i = boxInterval(ro - intBitsToFloat(n1.xyz), rd, intBitsToFloat(n0.xyz));
if (i.x < i.y && i.x < maxdist && i.y > 0.0)
{
// The ray does intersect this node, so don't skip it.
if (n1.w != -1)
{
// The node is a leaf node, so test for intersection with the referenced triangle.
ivec2 tri = texelFetch(tris, n1.w).xy;
vec3 p0 = texelFetch(vertices, tri.x & 0xffff).xyz;
vec3 p1 = texelFetch(vertices, tri.x >> 16).xyz;
vec3 p2 = texelFetch(vertices, tri.y & 0xffff).xyz;
// Plane intersection.
vec3 n = cross(p1 - p0, p2 - p0);
float t = dot(p0 - ro, n) / dot(rd, n);
// Signed areas of subtriangles.
float d0 = dot(rd, cross(p0 - ro, p1 - ro));
float d1 = dot(rd, cross(p1 - ro, p2 - ro));
float d2 = dot(rd, cross(p2 - ro, p0 - ro));
// The ray intersects the triangle only if the subtriangles all have the same sign and
// the ray intersects the triangle's plane.
if (sign(d0) == sign(d1) && sign(d0) == sign(d2) && t < maxdist && t > 0.0)
return false;
}
// Step to the immediate next node.
++node;
}
else
{
// The ray does not intersect this node, so skip over all the nodes contained within it.
node = n0.w;
}
} while (node > 0);
#endif
// There is no intersection detected.
return true;
}
// Returns the closest distance along the given ray at which the ray intersects a solid leaf in the
// BSP, or returns MAXT if there was no intersection.
float traceRayBSP(vec3 org, vec3 dir, float t0, float max_t)
{
// The 'other' variables represent a one-level stack.
vec2 other_node = vec2(0);
float other_t1 = max_t;
vec4 other_pln0;
// Continue visiting nodes until the next nearest plane lies beyond max_t (in which case
// there can be no more intersections possible).
while (t0 < max_t)
{
// Pop from the stack.
vec2 node = other_node;
float t1 = other_t1;
vec4 pln0 = other_pln0;
// Reset the stack.
other_node = vec2(0);
other_t1 = max_t;
// Travel down the BSP.
do
{
vec4 pln = texture(bsp_planes, node);
vec4 children = texture(bsp_branches, node);
// Perform ray-plane intersection and order the children according to traversal order.
float t = dot(pln.xyz, dir);
children = (t > 0.0) ? children.zwxy : children.xyzw;
t = (pln.w - dot(pln.xyz, org)) / t;
if (t > t0)
{
// The near node is intersected.
node = children.xy;
if (t < t1)
{
// Both children need to be visited, so push the further children onto the stack.
other_t1 = t1;
t1 = t;
other_pln0 = pln0;
pln0 = pln;
other_node = children.zw;
}
}
else
{
// Only the far node is intersected.
node = children.zw;
}
if (node.y == 1.0)
{
return t0;
}
} while (node != vec2(0));
// Store the plane of intersection and push the current ray traversal point just beyond it.
out_pln = pln0;
t0 = t1 + EPS;
}
return MAXT;
}
// Returns false if the given ray intersects a solid leaf in the BSP and the distance
// to the intersection along the ray is less than maxdist, otherwise returns true.
bool traceRayShadowBSP(vec3 org, vec3 dir, float t0, float max_t)
{
return traceRayBSP(org, dir, t0, max_t) >= max_t;
}
// Returns the light list head references for the given point. The X component points to the
// direct light list and the Y component points to the skyportal list.
ivec2 getLightRef(vec3 p)
{
vec2 node = vec2(0), prev_node;
float d;
// Travel down the BSP.
do
{
vec4 pln = texture(bsp_planes, node);
vec4 children = texture(bsp_branches, node);
d = dot(pln.xyz, p) - pln.w;
if (d < 0.0)
{
prev_node = node;
node = children.zw;
}
else
{
prev_node = node;
node = children.xy;
}
if (node.y == 1.0)
return ivec2(0);
} while (node != vec2(0));
ivec4 light_indices = texture(bsp_lightrefs, prev_node);
return d < 0.0 ? light_indices.yw : light_indices.xz;
}
// Returns the amount of direct light incident at the given point with the given surface normal.
// oli is an index to the first lightsource reference.
vec3 sampleDirectLight(vec3 rp, vec3 rn, int oli)
{
#if NUM_LIGHT_SAMPLES == 0
return vec3(0);
#else
vec3 r = vec3(0);
oli = oli < 0 ? -oli : +oli;
int li = oli;
int ref = texelFetch(lightrefs, li).r;
if (ref != -1)
{
float wsum = 0.0;
// Visit each referenced light and sum up the importance weights for the purpose of weight normalisation.
do
{
vec4 light = texelFetch(lights, ref);
ivec2 tri = texelFetch(triangles, floatBitsToInt(light.w)).xy;
vec3 p0 = texelFetch(tri_vertices, tri.x & 0xffff).xyz;
vec3 p1 = texelFetch(tri_vertices, tri.x >> 16).xyz;
vec3 p2 = texelFetch(tri_vertices, tri.y & 0xffff).xyz;
vec3 n = cross(p2 - p0, p1 - p0);
// Calculate the importance weight.
float d = distance(rp, (p0 + p1 + p2) / 3.0);
float pd = dot(normalize(rp - (p0 + p1 + p2) / 3.0), n);
float w = length(light.rgb) * max(0.0, pd) / (d * d);
wsum += w;
++li;
ref = texelFetch(lightrefs, li).r;
} while (ref != -1);
// Sample the set of lightsources using importance sampling.
for (int light_sample = 0; light_sample < NUM_LIGHT_SAMPLES; ++light_sample)
{
li = oli;
ref = texelFetch(lightrefs, li).r;
vec3 p0, p1, p2, n;
// Choose a lightsource randomly, with each lightsource having a probability of being chosen
// proportional to it's importance weight.
float x = rand(light_sample).x * wsum, w=1.0;
vec4 j = vec4(0);
do
{
vec4 light=texelFetch(lights, ref);
ivec2 tri = texelFetch(triangles, floatBitsToInt(light.w)).xy;
p0 = texelFetch(tri_vertices, tri.x & 0xffff).xyz;
p1 = texelFetch(tri_vertices, tri.x >> 16).xyz;
p2 = texelFetch(tri_vertices, tri.y & 0xffff).xyz;
n = cross(p2 - p0, p1 - p0);
// Calculate the importance weight.
float d = distance(rp, (p0 + p1 + p2) / 3.0);
float pd = dot(normalize(rp - (p0 + p1 + p2) / 3.0), n);
w = length(light.rgb) * max(0.0, pd) / (d * d);
x -= w;
if (x < 0.0)
{
// This lightsource has been chosen.
j = light;
break;
}
++li;
ref = texelFetch(lightrefs, li).r;
} while (ref != -1);
#if NUM_SHADOW_SAMPLES > 0
// Trace shadow rays towards the lightsource to calculate a shadowing term.
for (int shadow_sample = 0; shadow_sample < NUM_SHADOW_SAMPLES; ++shadow_sample)
{
vec3 sp = rp;
vec3 sn = rn.xyz;
vec3 lp = p0;
vec2 uv = rand(shadow_sample);
// Flip the barycentric coordinates if this is a triangle, don't if it's a parallelogram.
if ((uv.x + uv.y) > 1.0 && dot(j.rgb, vec3(1)) > 0.0)
uv = vec2(1) - uv;
lp += (p1 - p0) * uv.x + (p2 - p0) * uv.y;
float ld = distance(sp, lp);
vec3 l = (lp - sp) / ld;
float ndotl = dot(l, sn), lndotl = dot(-l, n);
if (ndotl > 0.0 && lndotl > 0.0)
{
float s = (traceRayShadowBSP(sp, l, EPS * 16, ld) && traceRayShadowTri(sp, l, ld, tri_nodes0, tri_nodes1, tri_vertices, triangles)) ? 1.0 / (ld * ld) : 0.0;
r += s * ndotl * lndotl * abs(j.rgb) * wsum / w;
}
}
#else
r += abs(j.rgb) * wsum / w;
#endif
}
}
return r / float(NUM_LIGHT_SAMPLES * NUM_SHADOW_SAMPLES);
#endif
}
float bumpHeightForDiffuseTexel(vec2 st)
{
return dot(vec3(1.0 / 3.0), texture(diffuse_texture, st).rgb);
}
void main()
{
vec4 pln;
gl_FragColor = vec4(0);
// Get the ray origin point.
vec3 rp = texcoords[1].xyz + texcoords[3].xyz * EPS * 16;
// Get the surface normal of the surface being rasterised.
pln.xyz = normalize(texcoords[3].xyz);
pln.w = dot(rp, pln.xyz);
#if BUMP_ENABLE
// Construct a shading normal for bump-mapping.
vec2 bump_texel_size = vec2(1) / textureSize(diffuse_texture, 0).xy;
float bump_height0 = bumpHeightForDiffuseTexel(texcoords[0].st);
vec2 bump_gradients = vec2(bumpHeightForDiffuseTexel(texcoords[0].st + vec2(bump_texel_size.x, 0.0)) - bump_height0,
bumpHeightForDiffuseTexel(texcoords[0].st + vec2(0.0, bump_texel_size.y)) - bump_height0) / bump_texel_size;
vec3 shading_normal = normalize(pln.xyz - (texcoords[5].xyz * bump_gradients.x + texcoords[6].xyz * bump_gradients.y) * bump_factor);
#else
vec3 shading_normal = normalize(pln.xyz);
#endif
// Sample the direct light at this point.
int rpli = getLightRef(rp).x;
vec3 r = sampleDirectLight(rp, shading_normal, rpli);
// Add the emissive light (light emitted towards the eye immediately from this surface).
r += texcoords[4].rgb;
#if NUM_BOUNCES > 0 || NUM_AO_SAMPLES > 0 || NUM_SKY_SAMPLES > 0
// Make an orthonormal basis for the primary ray intersection point.
vec3 u = normalize(cross(pln.xyz, pln.zxy));
vec3 v = cross(pln.xyz, u);
#endif
#if NUM_AO_SAMPLES > 0
{
// Apply importance-sampled (lambert diffuse BRDF) ambient occlusion.
float ao = 0.0;
for (int ao_sample = 0; ao_sample < NUM_AO_SAMPLES; ++ao_sample)
{
vec2 rr = rand(ao_sample);
float r1 = 2.0 * PI * rr.x;
float r2s = sqrt(rr.y);
// Lambert diffuse BRDF.
vec3 rd = u * cos(r1) * r2s + v * sin(r1) * r2s + pln.xyz * sqrt(1.0 - rr.y);
if (traceRayShadowBSP(rp, rd, EPS * 16, ao_radius) && traceRayShadowTri(rp, rd, ao_radius, tri_nodes0, tri_nodes1, tri_vertices, triangles))
ao += 1.0;
}
gl_FragColor.rgb += ao_color * ao / float(NUM_AO_SAMPLES);
}
#endif
#if NUM_BOUNCES > 0 || NUM_SKY_SAMPLES > 0
vec3 sp, rd;
float t;
float factor = bounce_factor;
#if NUM_BOUNCES == 0
// If there are no secondary bounces and there are no visible skyportals then there is no
// need to sample the BRDF and trace a ray with it.
if (rpli < 0)
#endif
{
float fresnel = texcoords[5].w * mix(1.0, 0.5, pow(clamp(1.0 - dot(shading_normal, -normalize(texcoords[7].xyz)), 0.0, 1.0), 5.0));
if (bluenoise_sample.b < fresnel)
{
// Lambert diffuse BRDF.
vec2 rr = rand(0);
float r1 = 2.0 * PI * rr.x;
float r2s = sqrt(rr.y);
rd = normalize(u * cos(r1) * r2s + v * sin(r1) * r2s + pln.xyz * sqrt(1.0 - rr.y));
factor = clamp(dot(rd, shading_normal) / dot(rd, pln.xyz), 0.1, 2.0);
}
else
{
// Specular reflection.
rd = normalize(reflect(normalize(texcoords[7].xyz), shading_normal));
}
// Trace a ray against the BSP. This intersection point is later used for secondary bounces and testing
// for containment in skyportals polygons (skyportals aren't sampled directly).
t = traceRayBSP(rp, rd, EPS * 16, MAXT) - 1.0;
if ((dot(out_pln.xyz, rp) - out_pln.w) < 0.0)
out_pln *= -1.0;
sp = rp + rd * max(0.0, t);
}
#if NUM_BOUNCES > 0
// Test for intersection with triangle meshes. This is done so bounce light can be shadowed
// by triangle meshes.
if (traceRayShadowTri(rp, rd, t, tri_nodes0, tri_nodes1, tri_vertices, triangles))
{
int li = getLightRef(sp).x;
r += factor * sampleDirectLight(sp, out_pln.xyz, li);
}
#endif
#if NUM_SKY_SAMPLES > 0
if (rpli < 0)
{
// The list of skyportals is non-empty, so trace rays by sampling the BRDF and test for containment
// of the intersection points from those rays in skyportal polygons.
vec3 sky_r = vec3(0);
for (int sky_sample = 0; sky_sample < NUM_SKY_SAMPLES; ++sky_sample)
{
int li = getLightRef(sp).y;
int ref = texelFetch(lightrefs, li).r;
if (ref != -1)
{
// Test for intersection with any triangle meshes. This is done so triangle meshes can cast shadows from sky light.
if (traceRayShadowTri(rp, rd, t, tri_nodes0, tri_nodes1, tri_vertices, triangles))
{
// Visit each skyportal in this cluster and test for containment.
do
{
vec4 light = texelFetch(lights, ref);
ivec2 tri = texelFetch(triangles, floatBitsToInt(light.w)).xy;
vec3 p0 = texelFetch(tri_vertices, tri.x & 0xffff).xyz;
vec3 p1 = texelFetch(tri_vertices, tri.x >> 16).xyz;
vec3 p2 = texelFetch(tri_vertices, tri.y & 0xffff).xyz;
vec3 n = normalize(cross(p1 - p0, p2 - p0));
vec3 sp2 = sp;
float s1 = dot(cross(p1 - sp2, p2 - sp2), n);
// If the skyportal is a parallelogram and the subtriangle opposing the reflected
// vertex has negative area then reflect the sample point back into the triangle.
if (dot(light.rgb, vec3(1)) < 0.0 && s1 < 0.0)
{
s1 *= -1.0;
vec3 mirror = normalize(p2 - p1);
sp2 -= 2.0 * mirror * dot(sp2 - (p1 + p2) * 0.5, mirror);
}
float s0 = dot(cross(p0 - sp2, p1 - sp2), n);
float s2 = dot(cross(p2 - sp2, p0 - sp2), n);
if (s0 > 0.0 && s1 > 0.0 && s2 > 0.0 && abs(dot(n, sp2 - p0)) < 1.0)
{
// The sample point is contained by this polygon, so add the light contribution.
sky_r += abs(light.rgb) * clamp(dot(rd, shading_normal) / dot(rd, pln.xyz), 0.1, 2.0);
}
++li;
ref = texelFetch(lightrefs, li).r;
} while (ref != -1);
}
}
#if NUM_SKY_SAMPLES > 1
// There are further sky samples to be made, so sample the BRDF again to trace a new ray
// out into the scene to create a new sample point.
vec2 rr = rand(sky_sample + 1);
float r1 = 2.0 * PI * rr.x;
float r2s = sqrt(rr.y);
// Lambert diffuse BRDF.
rd = normalize(u * cos(r1) * r2s + v * sin(r1) * r2s + pln.xyz * sqrt(1.0 - rr.y));
t = traceRayBSP(rp, rd, EPS * 16, MAXT) - 1.0;
sp = rp + rd * max(0.0, t);
#endif
}
r += sky_r / float(NUM_SKY_SAMPLES);
}
#endif
#if NUM_BOUNCES > 1
{
if ((dot(out_pln.xyz, rp) - out_pln.w) < 0.0)
out_pln *= -1.0;
// Trace secondary rays to gather bounce light from surrounding surfaces. This does not include sky light.
float factor = bounce_factor;
rp = sp;
for (int bounce = 1; bounce < NUM_BOUNCES; ++bounce)
{
vec2 rr = rand(bounce);
float r1 = 2.0 * PI * rr.x;
float r2s = sqrt(rr.y);
// Lambert diffuse BRDF.
vec3 rd = normalize(u * cos(r1) * r2s + v * sin(r1) * r2s + out_pln.xyz * sqrt(1.0 - rr.y));
t = traceRayBSP(rp, rd, EPS * 16, MAXT) - 1.0;
if (!traceRayShadowTri(rp, rd, t, tri_nodes0, tri_nodes1, tri_vertices, triangles))
break;
if ((dot(out_pln.xyz, rp) - out_pln.w) < 0.0)
out_pln *= -1.0;
rp = rp + rd * max(0.0, t);
// Simulate (achromatic) absorption of light at the surface.
factor *= bounce_factor;
int li = getLightRef(rp).x;
pln = out_pln;
r += factor * sampleDirectLight(rp, out_pln.xyz, li);
out_pln = pln;
#if NUM_BOUNCES > 2
u = normalize(cross(out_pln.xyz, out_pln.zxy));
v = cross(out_pln.xyz, u);
#endif
}
}
#endif
#endif
// Apply an arbitrary scaling to empirically match the output of qrad3.
gl_FragColor.rgb += r / 1024.;
gl_FragColor.a = color.a;
#if DIFFUSE_MAP_ENABLE
// Multiply the gathered light with the diffuse albedo.
gl_FragColor.rgb *= texture(diffuse_texture, texcoords[0].st).rgb + vec3(1e-2);
#endif
// Tone mapping (Simple Reinhard).
gl_FragColor.rgb *= exposure / (vec3(1) + gl_FragColor.rgb / exposure);
// Gamma.
gl_FragColor.rgb = pow(gl_FragColor.rgb, vec3(1.0 / gamma));
#if TAA_ENABLE
if (texcoords[4].w > 0.0)
{
// Apply TAA.
vec4 clip = previous_world_matrix * texcoords[1];
vec2 ndc = clip.xy / clip.w * 0.5 + vec2(0.5);
rp = texcoords[1].xyz + texcoords[3].xyz * EPS * 16;
// Check if the fragment was visible in the previous frame. If not, then the information in the previous framebuffer does not match and it cannot be reused.
vec3 disocclusion_test_ray = normalize(previous_view_origin - rp);
float disocclusion_test_distance = distance(rp, previous_view_origin);
bool previously_visible = traceRayShadowBSP(rp, disocclusion_test_ray, EPS * 16, disocclusion_test_distance) &&
traceRayShadowTri(rp, disocclusion_test_ray, disocclusion_test_distance, tri_nodes0_prev, tri_nodes1_prev, tri_vertices_prev, triangles_prev) &&
dot(normalize(texcoords[3].xyz), disocclusion_test_ray) > 0.01;
gl_FragColor.rgb = mix(gl_FragColor.rgb, texture(taa_world, ndc).rgb,
all(greaterThan(ndc, vec2(0))) && all(lessThan(ndc, vec2(1))) && previously_visible ? 0.45 * texcoords[4].w : 0.0);
}
#endif
}