Created
July 30, 2020 01:55
Added multisampling to erichlof's path tracing shader
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#version 300 es | |
precision highp float; | |
precision highp int; | |
precision highp sampler2D; | |
#include <pathtracing_uniforms_and_defines> | |
uniform mat4 uTorusInvMatrix; | |
uniform mat3 uTorusNormalMatrix; | |
#define N_LIGHTS 3.0 | |
#define N_SPHERES 6 | |
#define N_PLANES 1 | |
#define N_DISKS 1 | |
#define N_TRIANGLES 1 | |
#define N_QUADS 1 | |
#define N_BOXES 2 | |
#define N_ELLIPSOIDS 1 | |
#define N_PARABOLOIDS 1 | |
#define N_OPENCYLINDERS 1 | |
#define N_CAPPEDCYLINDERS 1 | |
#define N_CONES 1 | |
#define N_CAPSULES 1 | |
#define N_TORII 1 | |
#define N_SAMPLES 4.0 | |
//----------------------------------------------------------------------- | |
struct Ray { vec3 origin; vec3 direction; }; | |
struct Sphere { float radius; vec3 position; vec3 emission; vec3 color; int type; }; | |
struct Ellipsoid { vec3 radii; vec3 position; vec3 emission; vec3 color; int type; }; | |
struct Paraboloid { float rad; float height; vec3 pos; vec3 emission; vec3 color; int type; }; | |
struct OpenCylinder { float radius; float height; vec3 position; vec3 emission; vec3 color; int type; }; | |
struct CappedCylinder { float radius; vec3 cap1pos; vec3 cap2pos; vec3 emission; vec3 color; int type; }; | |
struct Cone { vec3 pos0; float radius0; vec3 pos1; float radius1; vec3 emission; vec3 color; int type; }; | |
struct Capsule { vec3 pos0; float radius0; vec3 pos1; float radius1; vec3 emission; vec3 color; int type; }; | |
struct Torus { float radius0; float radius1; vec3 emission; vec3 color; int type; }; | |
struct Box { vec3 minCorner; vec3 maxCorner; vec3 emission; vec3 color; int type; }; | |
struct Intersection { vec3 normal; vec3 emission; vec3 color; int type; }; | |
Sphere spheres[N_SPHERES]; | |
Ellipsoid ellipsoids[N_ELLIPSOIDS]; | |
Paraboloid paraboloids[N_PARABOLOIDS]; | |
OpenCylinder openCylinders[N_OPENCYLINDERS]; | |
CappedCylinder cappedCylinders[N_CAPPEDCYLINDERS]; | |
Cone cones[N_CONES]; | |
Capsule capsules[N_CAPSULES]; | |
Torus torii[N_TORII]; | |
Box boxes[N_BOXES]; | |
#include <pathtracing_random_functions> | |
#include <pathtracing_calc_fresnel_reflectance> | |
#include <pathtracing_sphere_intersect> | |
#include <pathtracing_ellipsoid_intersect> | |
#include <pathtracing_opencylinder_intersect> | |
#include <pathtracing_cappedcylinder_intersect> | |
#include <pathtracing_cone_intersect> | |
#include <pathtracing_capsule_intersect> | |
#include <pathtracing_paraboloid_intersect> | |
#include <pathtracing_torus_intersect> | |
#include <pathtracing_box_intersect> | |
#include <pathtracing_sample_sphere_light> | |
//------------------------------------------------------------------------------------ | |
float SceneIntersect( Ray r, inout Intersection intersec, out bool finalIsRayExiting ) | |
//------------------------------------------------------------------------------------ | |
{ | |
vec3 n; | |
float d; | |
float t = INFINITY; | |
bool isRayExiting = false; | |
for (int i = 0; i < N_SPHERES; i++) | |
{ | |
d = SphereIntersect( spheres[i].radius, spheres[i].position, r ); | |
if (d < t) | |
{ | |
t = d; | |
intersec.normal = normalize((r.origin + r.direction * t) - spheres[i].position); | |
intersec.emission = spheres[i].emission; | |
intersec.color = spheres[i].color; | |
intersec.type = spheres[i].type; | |
} | |
} | |
for (int i = 0; i < N_BOXES; i++) | |
{ | |
d = BoxIntersect( boxes[i].minCorner, boxes[i].maxCorner, r, n, isRayExiting ); | |
if (d < t) | |
{ | |
t = d; | |
intersec.normal = normalize(n); | |
intersec.emission = boxes[i].emission; | |
intersec.color = boxes[i].color; | |
intersec.type = boxes[i].type; | |
finalIsRayExiting = isRayExiting; | |
} | |
} | |
d = EllipsoidIntersect( ellipsoids[0].radii, ellipsoids[0].position, r ); | |
if (d < t) | |
{ | |
t = d; | |
intersec.normal = normalize( ((r.origin + r.direction * t) - | |
ellipsoids[0].position) / (ellipsoids[0].radii * ellipsoids[0].radii) ); | |
intersec.emission = ellipsoids[0].emission; | |
intersec.color = ellipsoids[0].color; | |
intersec.type = ellipsoids[0].type; | |
} | |
d = ParaboloidIntersect( paraboloids[0].rad, paraboloids[0].height, paraboloids[0].pos, r, n ); | |
if (d < t) | |
{ | |
t = d; | |
intersec.normal = normalize(n); | |
intersec.emission = paraboloids[0].emission; | |
intersec.color = paraboloids[0].color; | |
intersec.type = paraboloids[0].type; | |
} | |
d = OpenCylinderIntersect( openCylinders[0].position, openCylinders[0].position + vec3(0,30,30), openCylinders[0].radius, r, n ); | |
if (d < t) | |
{ | |
t = d; | |
intersec.normal = normalize(n); | |
intersec.emission = openCylinders[0].emission; | |
intersec.color = openCylinders[0].color; | |
intersec.type = openCylinders[0].type; | |
} | |
d = CappedCylinderIntersect( cappedCylinders[0].cap1pos, cappedCylinders[0].cap2pos, cappedCylinders[0].radius, r, n); | |
if (d < t) | |
{ | |
t = d; | |
intersec.normal = normalize(n); | |
intersec.emission = cappedCylinders[0].emission; | |
intersec.color = cappedCylinders[0].color; | |
intersec.type = cappedCylinders[0].type; | |
} | |
d = ConeIntersect( cones[0].pos0, cones[0].radius0, cones[0].pos1, cones[0].radius1, r, n ); | |
if (d < t) | |
{ | |
t = d; | |
intersec.normal = normalize(n); | |
intersec.emission = cones[0].emission; | |
intersec.color = cones[0].color; | |
intersec.type = cones[0].type; | |
} | |
d = CapsuleIntersect( capsules[0].pos0, capsules[0].radius0, capsules[0].pos1, capsules[0].radius1, r, n ); | |
if (d < t) | |
{ | |
t = d; | |
intersec.normal = normalize(n); | |
intersec.emission = capsules[0].emission; | |
intersec.color = capsules[0].color; | |
intersec.type = capsules[0].type; | |
} | |
Ray rObj; | |
// transform ray into Torus's object space | |
rObj.origin = vec3( uTorusInvMatrix * vec4(r.origin, 1.0) ); | |
rObj.direction = vec3( uTorusInvMatrix * vec4(r.direction, 0.0) ); | |
d = TorusIntersect( torii[0].radius0, torii[0].radius1, rObj ); | |
if (d < t) | |
{ | |
t = d; | |
vec3 hit = rObj.origin + rObj.direction * t; | |
intersec.normal = calcNormal_Torus(hit); | |
// transfom normal back into world space | |
intersec.normal = vec3(uTorusNormalMatrix * intersec.normal); | |
intersec.emission = torii[0].emission; | |
intersec.color = torii[0].color; | |
intersec.type = torii[0].type; | |
} | |
return t; | |
} // end float SceneIntersect( Ray r, inout Intersection intersec ) | |
//--------------------------------------------------------------------------- | |
vec3 CalculateRadiance( Ray r, inout uvec2 seed ) | |
//--------------------------------------------------------------------------- | |
{ | |
Intersection intersec; | |
Sphere lightChoice; | |
Ray firstRay; | |
Ray secondaryRay; | |
vec3 accumCol = vec3(0); | |
vec3 mask = vec3(1); | |
vec3 firstMask = vec3(1); | |
vec3 secondaryMask = vec3(1); | |
vec3 checkCol0 = vec3(1); | |
vec3 checkCol1 = vec3(0.5); | |
vec3 dirToLight; | |
vec3 tdir; | |
vec3 n, nl, x; | |
float t; | |
float nc, nt, ratioIoR, Re, Tr; | |
float weight; | |
float randChoose; | |
float thickness = 0.1; | |
int diffuseCount = 0; | |
bool bounceIsSpecular = true; | |
bool sampleLight = false; | |
bool firstTypeWasREFR = false; | |
bool reflectionTime = false; | |
bool firstTypeWasDIFF = false; | |
bool shadowTime = false; | |
bool firstTypeWasCOAT = false; | |
bool isRayExiting = false; | |
for (int bounces = 0; bounces < 7; bounces++) | |
{ | |
t = SceneIntersect(r, intersec, isRayExiting); | |
/* | |
//not used in this scene because we are inside a huge sphere - no rays can escape | |
if (t == INFINITY) | |
{ | |
break; | |
} | |
*/ | |
if (intersec.type == LIGHT) | |
{ | |
if (bounces == 0) | |
{ | |
accumCol = mask * intersec.emission; | |
break; | |
} | |
if (firstTypeWasDIFF) | |
{ | |
if (!shadowTime) | |
{ | |
if (sampleLight) | |
accumCol = mask * intersec.emission * 0.5; | |
else if (bounceIsSpecular) | |
accumCol = mask * intersec.emission; | |
// start back at the diffuse surface, but this time follow shadow ray branch | |
r = firstRay; | |
r.direction = normalize(r.direction); | |
mask = firstMask; | |
// set/reset variables | |
shadowTime = true; | |
bounceIsSpecular = false; | |
sampleLight = true; | |
// continue with the shadow ray | |
continue; | |
} | |
accumCol += mask * intersec.emission * 0.5; // add shadow ray result to the colorbleed result (if any) | |
break; | |
} | |
if (firstTypeWasREFR) | |
{ | |
if (!reflectionTime) | |
{ | |
if (bounceIsSpecular || sampleLight) | |
accumCol = mask * intersec.emission; | |
// start back at the refractive surface, but this time follow reflective branch | |
r = firstRay; | |
r.direction = normalize(r.direction); | |
mask = firstMask; | |
// set/reset variables | |
reflectionTime = true; | |
bounceIsSpecular = true; | |
sampleLight = false; | |
// continue with the reflection ray | |
continue; | |
} | |
if (bounceIsSpecular || sampleLight) | |
accumCol += mask * intersec.emission; // add reflective result to the refractive result (if any) | |
break; | |
} | |
if (firstTypeWasCOAT) | |
{ | |
if (!shadowTime) | |
{ | |
if (sampleLight) | |
accumCol = mask * intersec.emission * 0.5; | |
// start back at the diffuse surface, but this time follow shadow ray branch | |
r = secondaryRay; | |
r.direction = normalize(r.direction); | |
mask = secondaryMask; | |
// set/reset variables | |
shadowTime = true; | |
bounceIsSpecular = false; | |
sampleLight = true; | |
// continue with the shadow ray | |
continue; | |
} | |
if (!reflectionTime) | |
{ | |
// add initial shadow ray result to secondary shadow ray result (if any) | |
accumCol += mask * intersec.emission * 0.5; | |
// start back at the coat surface, but this time follow reflective branch | |
r = firstRay; | |
r.direction = normalize(r.direction); | |
mask = firstMask; | |
// set/reset variables | |
reflectionTime = true; | |
bounceIsSpecular = true; | |
sampleLight = false; | |
// continue with the reflection ray | |
continue; | |
} | |
// add reflective result to the diffuse result | |
if (sampleLight || bounceIsSpecular) | |
accumCol += mask * intersec.emission; | |
break; | |
} | |
if (sampleLight || bounceIsSpecular) | |
accumCol = mask * intersec.emission; // looking at light through a reflection | |
// reached a light, so we can exit | |
break; | |
} // end if (intersec.type == LIGHT) | |
// if we get here and sampleLight is still true, shadow ray failed to find a light source | |
if (sampleLight) | |
{ | |
if (firstTypeWasDIFF && !shadowTime) | |
{ | |
// start back at the diffuse surface, but this time follow shadow ray branch | |
r = firstRay; | |
r.direction = normalize(r.direction); | |
mask = firstMask; | |
// set/reset variables | |
shadowTime = true; | |
bounceIsSpecular = false; | |
sampleLight = true; | |
// continue with the shadow ray | |
continue; | |
} | |
if (firstTypeWasREFR && !reflectionTime) | |
{ | |
// start back at the refractive surface, but this time follow reflective branch | |
r = firstRay; | |
r.direction = normalize(r.direction); | |
mask = firstMask; | |
// set/reset variables | |
reflectionTime = true; | |
bounceIsSpecular = true; | |
sampleLight = false; | |
// continue with the reflection ray | |
continue; | |
} | |
if (firstTypeWasCOAT && !shadowTime) | |
{ | |
// start back at the diffuse surface, but this time follow shadow ray branch | |
r = secondaryRay; | |
r.direction = normalize(r.direction); | |
mask = secondaryMask; | |
// set/reset variables | |
shadowTime = true; | |
bounceIsSpecular = false; | |
sampleLight = true; | |
// continue with the shadow ray | |
continue; | |
} | |
if (firstTypeWasCOAT && !reflectionTime) | |
{ | |
// start back at the refractive surface, but this time follow reflective branch | |
r = firstRay; | |
r.direction = normalize(r.direction); | |
mask = firstMask; | |
// set/reset variables | |
reflectionTime = true; | |
bounceIsSpecular = true; | |
sampleLight = false; | |
// continue with the reflection ray | |
continue; | |
} | |
// nothing left to calculate, so exit | |
//break; | |
} | |
// useful data | |
n = normalize(intersec.normal); | |
nl = dot(n, r.direction) < 0.0 ? normalize(n) : normalize(-n); | |
x = r.origin + r.direction * t; | |
randChoose = rand(seed) * 3.0; // 3 lights to choose from | |
lightChoice = spheres[int(randChoose)]; | |
if (intersec.type == DIFF || intersec.type == CHECK) // Ideal DIFFUSE reflection | |
{ | |
diffuseCount++; | |
if( intersec.type == CHECK ) | |
{ | |
float q = clamp( mod( dot( floor(x.xz * 0.04), vec2(1.0) ), 2.0 ) , 0.0, 1.0 ); | |
intersec.color = checkCol0 * q + checkCol1 * (1.0 - q); | |
} | |
mask *= intersec.color; | |
bounceIsSpecular = false; | |
if (diffuseCount == 1 && !firstTypeWasDIFF && !firstTypeWasREFR) | |
{ | |
// save intersection data for future shadowray trace | |
firstTypeWasDIFF = true; | |
dirToLight = sampleSphereLight(x, nl, lightChoice, dirToLight, weight, seed); | |
firstMask = mask * weight * N_LIGHTS; | |
firstRay = Ray( x, normalize(dirToLight) ); // create shadow ray pointed towards light | |
firstRay.origin += nl * uEPS_intersect; | |
// choose random Diffuse sample vector | |
r = Ray( x, normalize(randomCosWeightedDirectionInHemisphere(nl, seed)) ); | |
r.origin += nl * uEPS_intersect; | |
continue; | |
} | |
else if ((firstTypeWasREFR || reflectionTime) && rand(seed) < 0.5) | |
{ | |
r = Ray( x, normalize(randomCosWeightedDirectionInHemisphere(nl, seed)) ); | |
r.origin += nl * uEPS_intersect; | |
continue; | |
} | |
dirToLight = sampleSphereLight(x, nl, lightChoice, dirToLight, weight, seed); | |
mask *= weight * N_LIGHTS; | |
r = Ray( x, normalize(dirToLight) ); | |
r.origin += nl * uEPS_intersect; | |
sampleLight = true; | |
continue; | |
} // end if (intersec.type == DIFF) | |
if (intersec.type == SPEC) // Ideal SPECULAR reflection | |
{ | |
mask *= intersec.color; | |
r = Ray( x, reflect(r.direction, nl) ); | |
r.origin += nl * uEPS_intersect; | |
//bounceIsSpecular = true; // turn on mirror caustics | |
continue; | |
} | |
if (intersec.type == REFR) // Ideal dielectric REFRACTION | |
{ | |
nc = 1.0; // IOR of Air | |
nt = 1.5; // IOR of common Glass | |
Re = calcFresnelReflectance(r.direction, n, nc, nt, ratioIoR); | |
Tr = 1.0 - Re; | |
if (!firstTypeWasREFR && diffuseCount == 0) | |
{ | |
// save intersection data for future reflection trace | |
firstTypeWasREFR = true; | |
firstMask = mask * Re; | |
firstRay = Ray( x, reflect(r.direction, nl) ); // create reflection ray from surface | |
firstRay.origin += nl * uEPS_intersect; | |
mask *= Tr; | |
} | |
else if (bounceIsSpecular && n == nl && rand(seed) < Re) | |
{ | |
r = Ray( x, reflect(r.direction, nl) ); // reflect ray from surface | |
r.origin += nl * uEPS_intersect; | |
continue; | |
} | |
// transmit ray through surface | |
// hack to make real time caustics | |
if (shadowTime) | |
mask *= 3.0; | |
// is ray leaving a solid object from the inside? | |
// If so, attenuate ray color with object color by how far ray has travelled through the medium | |
if (isRayExiting) | |
{ | |
isRayExiting = false; | |
mask *= exp(log(intersec.color) * thickness * t); | |
} | |
else | |
mask *= intersec.color; | |
tdir = refract(r.direction, nl, ratioIoR); | |
r = Ray(x, normalize(tdir)); | |
r.origin -= nl * uEPS_intersect; | |
//if (diffuseCount == 1) | |
// bounceIsSpecular = true; // turn on refracting caustics | |
continue; | |
} // end if (intersec.type == REFR) | |
if (intersec.type == COAT) // Diffuse object underneath with ClearCoat on top | |
{ | |
nc = 1.0; // IOR of Air | |
nt = 1.4; // IOR of Clear Coat | |
Re = calcFresnelReflectance(r.direction, n, nc, nt, ratioIoR); | |
Tr = 1.0 - Re; | |
if (!firstTypeWasREFR && !firstTypeWasCOAT && diffuseCount == 0) | |
{ | |
// save intersection data for future reflection trace | |
firstTypeWasCOAT = true; | |
firstMask = mask * Re; | |
firstRay = Ray( x, reflect(r.direction, nl) ); // create reflection ray from surface | |
firstRay.origin += nl * uEPS_intersect; | |
mask *= Tr; | |
} | |
else if (bounceIsSpecular && rand(seed) < Re) | |
{ | |
r = Ray( x, reflect(r.direction, nl) ); // reflect ray from surface | |
r.origin += nl * uEPS_intersect; | |
continue; | |
} | |
diffuseCount++; | |
mask *= intersec.color; | |
bounceIsSpecular = false; | |
if (intersec.color.r == 1.0 && rand(seed) < 0.9) | |
lightChoice = spheres[0]; // this makes capsule more white | |
if (firstTypeWasCOAT && diffuseCount == 1) | |
{ | |
// save intersection data for future shadowray trace | |
dirToLight = sampleSphereLight(x, nl, lightChoice, dirToLight, weight, seed); | |
secondaryMask = mask * weight * N_LIGHTS; | |
secondaryRay = Ray( x, normalize(dirToLight) ); // create shadow ray pointed towards light | |
secondaryRay.origin += nl * uEPS_intersect; | |
// choose random Diffuse sample vector | |
r = Ray( x, normalize(randomCosWeightedDirectionInHemisphere(nl, seed)) ); | |
r.origin += nl * uEPS_intersect; | |
continue; | |
} | |
else if ((firstTypeWasREFR || reflectionTime) && rand(seed) < 0.5) | |
{ | |
// choose random Diffuse sample vector | |
r = Ray( x, normalize(randomCosWeightedDirectionInHemisphere(nl, seed)) ); | |
r.origin += nl * uEPS_intersect; | |
continue; | |
} | |
dirToLight = sampleSphereLight(x, nl, lightChoice, dirToLight, weight, seed); | |
mask *= weight * N_LIGHTS; | |
r = Ray( x, normalize(dirToLight) ); | |
r.origin += nl * uEPS_intersect; | |
sampleLight = true; | |
continue; | |
} //end if (intersec.type == COAT) | |
} // end for (int bounces = 0; bounces < 7; bounces++) | |
return max(vec3(0), accumCol); | |
} // end vec3 CalculateRadiance( Ray r, inout uvec2 seed ) | |
//----------------------------------------------------------------------- | |
void SetupScene(void) | |
//----------------------------------------------------------------------- | |
{ | |
vec3 z = vec3(0); | |
vec3 L1 = vec3(1.0, 1.0, 1.0) * 13.0;// White light | |
vec3 L2 = vec3(1.0, 0.8, 0.2) * 10.0;// Yellow light | |
vec3 L3 = vec3(0.1, 0.7, 1.0) * 5.0; // Blue light | |
spheres[0] = Sphere(150.0, vec3(-400, 900, 200), L1, z, LIGHT);//spherical white Light1 | |
spheres[1] = Sphere(100.0, vec3( 300, 400,-300), L2, z, LIGHT);//spherical yellow Light2 | |
spheres[2] = Sphere( 50.0, vec3( 500, 250,-100), L3, z, LIGHT);//spherical blue Light3 | |
spheres[3] = Sphere(1000.0, vec3( 0.0, 1000.0, 0.0), z, vec3(1.0, 1.0, 1.0), CHECK);//Checkered Floor | |
spheres[4] = Sphere( 16.5, vec3(-26.0, 17.2, 5.0), z, vec3(0.95, 0.95, 0.95), SPEC);//Mirror sphere | |
spheres[5] = Sphere( 15.0, vec3( sin(mod(uTime * 0.3, TWO_PI)) * 80.0, 25, cos(mod(uTime * 0.1, TWO_PI)) * 80.0 ), z, vec3(1.0, 1.0, 1.0), REFR);//Glass sphere | |
ellipsoids[0] = Ellipsoid( vec3(30,40,16), vec3(cos(mod(uTime * 0.5, TWO_PI)) * 80.0,5,-30), z, vec3(1.0, 0.765557, 0.336057), SPEC);//metallic gold ellipsoid | |
paraboloids[0] = Paraboloid( 16.5, 50.0, vec3(20,1,-50), z, vec3(1.0, 0.2, 0.7), REFR);//paraboloid | |
openCylinders[0] = OpenCylinder( 15.0, 30.0, vec3( cos(mod(uTime * 0.1, TWO_PI)) * 100.0, 10, sin(mod(uTime * 0.4, TWO_PI)) * 100.0 ), z, vec3(0.9,0.01,0.01), REFR);//red glass open Cylinder | |
cappedCylinders[0] = CappedCylinder( 14.0, vec3(-60,0,20), vec3(-60,14,20), z, vec3(0.05,0.05,0.05), COAT);//dark gray capped Cylinder | |
cones[0] = Cone( vec3(1,20,-12), 15.0, vec3(1,0,-12), 0.0, z, vec3(0.01,0.1,0.5), REFR);//blue Cone | |
capsules[0] = Capsule( vec3(80,13,15), 10.0, vec3(110,15.8,15), 10.0, z, vec3(1.0,1.0,1.0), COAT);//white Capsule | |
torii[0] = Torus( 10.0, 1.5, z, vec3(0.955008, 0.637427, 0.538163), SPEC);//copper Torus | |
boxes[0] = Box( vec3(50.0,21.0,-60.0), vec3(100.0,28.0,-130.0), z, vec3(0.2,0.9,0.7), REFR);//Glass Box | |
boxes[1] = Box( vec3(56.0,23.0,-66.0), vec3(94.0,26.0,-124.0), z, vec3(0.0,0.0,0.0), DIFF);//Diffuse Box | |
} | |
//#include <pathtracing_main> | |
// tentFilter from Peter Shirley's 'Realistic Ray Tracing (2nd Edition)' book, pg. 60 | |
float tentFilter(float x) | |
{ | |
if (x < 0.5) | |
return sqrt(2.0 * x) - 1.0; | |
else return 1.0 - sqrt(2.0 - (2.0 * x)); | |
} | |
void main( void ) | |
{ | |
// not needed, three.js has a built-in uniform named cameraPosition | |
//vec3 camPos = vec3( uCameraMatrix[3][0], uCameraMatrix[3][1], uCameraMatrix[3][2]); | |
vec3 camRight = vec3( uCameraMatrix[0][0], uCameraMatrix[0][1], uCameraMatrix[0][2]); | |
vec3 camUp = vec3( uCameraMatrix[1][0], uCameraMatrix[1][1], uCameraMatrix[1][2]); | |
vec3 camForward = vec3(-uCameraMatrix[2][0], -uCameraMatrix[2][1], -uCameraMatrix[2][2]); | |
SetupScene(); | |
vec4 previousImage = texelFetch(tPreviousTexture, ivec2(gl_FragCoord.xy), 0); | |
vec3 previousColor = previousImage.rgb; | |
for (float i = 0.0; i < N_SAMPLES; i++) | |
{ | |
// seed for rand(seed) function | |
uvec2 seed = uvec2(uFrameCounter*N_SAMPLES+i, uFrameCounter*N_SAMPLES+i + 1.0) * uvec2(gl_FragCoord); | |
vec2 pixelPos = vec2(0); | |
vec2 pixelOffset = vec2(0); | |
float x = rand(seed); | |
float y = rand(seed); | |
//if (!uCameraIsMoving) | |
{ | |
pixelOffset.x = tentFilter(x); | |
pixelOffset.y = tentFilter(y); | |
} | |
// pixelOffset ranges from -1.0 to +1.0, so only need to divide by half resolution | |
pixelOffset /= (uResolution * 1.0); // normally this is * 0.5, but for dynamic scenes, * 1.0 looks sharper | |
// we must map pixelPos into the range -1.0 to +1.0 | |
pixelPos = (gl_FragCoord.xy / uResolution) * 2.0 - 1.0; | |
pixelPos += pixelOffset; | |
vec3 rayDir = normalize( pixelPos.x * camRight * uULen + pixelPos.y * camUp * uVLen + camForward ); | |
// depth of field | |
vec3 focalPoint = uFocusDistance * rayDir; | |
float randomAngle = rand(seed) * TWO_PI; // pick random point on aperture | |
float randomRadius = rand(seed) * uApertureSize; | |
vec3 randomAperturePos = ( cos(randomAngle) * camRight + sin(randomAngle) * camUp ) * sqrt(randomRadius); | |
// point on aperture to focal point | |
vec3 finalRayDir = normalize(focalPoint - randomAperturePos); | |
Ray ray = Ray( cameraPosition + randomAperturePos, finalRayDir ); | |
// perform path tracing and get resulting pixel color | |
vec3 pixelColor = CalculateRadiance( ray, seed ); | |
// with low N_SAMPLES we still need this if/else to avoid the blur during camera movement | |
// but as N_SAMPLES increases we can start to increase the importance of the old color | |
// and when N_SAMPLES gets big enough we can just always use the else path | |
if (uCameraIsMoving && N_SAMPLES < 11.0) | |
{ | |
previousColor *= 0.5 + N_SAMPLES*0.04; // motion-blur trail amount (old image) | |
pixelColor *= 0.5 - N_SAMPLES*0.04; // brightness of new image (noisy) | |
} | |
else | |
{ | |
previousColor *= 0.94; // motion-blur trail amount (old image) | |
pixelColor *= 0.06; // brightness of new image (noisy) | |
} | |
previousColor += pixelColor; | |
} | |
out_FragColor = vec4( previousColor, 1.0 ); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment