Created
August 7, 2023 05:38
-
-
Save erichlof/f8f907d0595c2787296e160be8535827 to your computer and use it in GitHub Desktop.
Static scene for Ocean and Sky Rendering demo (time is frozen)
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
// scene/demo-specific variables go here | |
let sunAngle = 0; | |
let sunDirection = new THREE.Vector3(); | |
let tallBoxGeometry, tallBoxMaterial, tallBoxMesh; | |
let shortBoxGeometry, shortBoxMaterial, shortBoxMesh; | |
let PerlinNoiseTexture; | |
// called automatically from within initTHREEjs() function (located in InitCommon.js file) | |
function initSceneData() | |
{ | |
demoFragmentShaderFileName = 'Ocean_And_Sky_Rendering_Fragment.glsl'; | |
// scene/demo-specific three.js objects setup goes here | |
///sceneIsDynamic = true; | |
///pixelEdgeSharpness = 0.5; | |
cameraFlightSpeed = 300; | |
// pixelRatio is resolution - range: 0.5(half resolution) to 1.0(full resolution) | |
pixelRatio = mouseControl ? 0.75 : 0.8; | |
EPS_intersect = 0.01; | |
// Boxes | |
tallBoxGeometry = new THREE.BoxGeometry(1, 1, 1); | |
tallBoxMaterial = new THREE.MeshPhysicalMaterial({ | |
color: new THREE.Color(0.95, 0.95, 0.95), //RGB, ranging from 0.0 - 1.0 | |
roughness: 1.0 // ideal Diffuse material | |
}); | |
tallBoxMesh = new THREE.Mesh(tallBoxGeometry, tallBoxMaterial); | |
pathTracingScene.add(tallBoxMesh); | |
tallBoxMesh.visible = false; // disable normal Three.js rendering updates of this object: | |
// it is just a data placeholder as well as an Object3D that can be transformed/manipulated by | |
// using familiar Three.js library commands. It is then fed into the GPU path tracing renderer | |
// through its 'matrixWorld' matrix. See below: | |
tallBoxMesh.rotation.set(0, Math.PI * 0.1, 0); | |
tallBoxMesh.position.set(180, 170, -350); | |
tallBoxMesh.updateMatrixWorld(true); // 'true' forces immediate matrix update | |
shortBoxGeometry = new THREE.BoxGeometry(1, 1, 1); | |
shortBoxMaterial = new THREE.MeshPhysicalMaterial({ | |
color: new THREE.Color(0.95, 0.95, 0.95), //RGB, ranging from 0.0 - 1.0 | |
roughness: 1.0 // ideal Diffuse material | |
}); | |
shortBoxMesh = new THREE.Mesh(shortBoxGeometry, shortBoxMaterial); | |
pathTracingScene.add(shortBoxMesh); | |
shortBoxMesh.visible = false; | |
shortBoxMesh.rotation.set(0, -Math.PI * 0.09, 0); | |
shortBoxMesh.position.set(370, 85, -170); | |
shortBoxMesh.updateMatrixWorld(true); // 'true' forces immediate matrix update | |
// set camera's field of view | |
worldCamera.fov = 60; | |
focusDistance = 1180.0; | |
apertureChangeSpeed = 100; | |
// position and orient camera | |
cameraControlsObject.position.set(278, 270, 1050); | |
///cameraControlsYawObject.rotation.y = 0.0; | |
// look slightly upward | |
cameraControlsPitchObject.rotation.x = 0.005; | |
// scene/demo-specific uniforms go here | |
pathTracingUniforms.t_PerlinNoise = { value: PerlinNoiseTexture }; | |
pathTracingUniforms.uCameraUnderWater = { value: 0.0 }; | |
pathTracingUniforms.uSunDirection = { value: new THREE.Vector3() }; | |
pathTracingUniforms.uTallBoxInvMatrix = { value: new THREE.Matrix4() }; | |
pathTracingUniforms.uShortBoxInvMatrix = { value: new THREE.Matrix4() }; | |
} // end function initSceneData() | |
// called automatically from within the animate() function (located in InitCommon.js file) | |
function updateVariablesAndUniforms() | |
{ | |
// scene/demo-specific variables | |
// NOTE: try different values for elapsedTime to move the static-captured scene through time | |
elapsedTime = 2; // a constant number 'freezes' time at this second | |
///sunAngle = (elapsedTime * 0.035) % (Math.PI + 0.2) - 0.11; | |
///sunDirection.set(Math.cos(sunAngle), Math.sin(sunAngle), -Math.cos(sunAngle) * 2.0); | |
// NOTE: try different values for sunAngle (0 to PI) to lower and raise the Sun in the sky | |
sunAngle = Math.PI * 0.15; // * 0.0 // * 0.5 // * 1.0 | |
sunDirection.set(Math.cos(sunAngle), Math.sin(sunAngle), -Math.cos(sunAngle)); | |
sunDirection.normalize(); | |
// scene/demo-specific uniforms | |
pathTracingUniforms.uSunDirection.value.copy(sunDirection); | |
// BOXES | |
pathTracingUniforms.uTallBoxInvMatrix.value.copy(tallBoxMesh.matrixWorld).invert(); | |
pathTracingUniforms.uShortBoxInvMatrix.value.copy(shortBoxMesh.matrixWorld).invert(); | |
// CAMERA | |
if (cameraControlsObject.position.y < 2.0) | |
pathTracingUniforms.uCameraUnderWater.value = 1.0; | |
else | |
pathTracingUniforms.uCameraUnderWater.value = 0.0; | |
// INFO | |
cameraInfoElement.innerHTML = "FOV: " + worldCamera.fov + " / Aperture: " + apertureSize.toFixed(2) + " / FocusDistance: " + focusDistance + "<br>" + "Samples: " + sampleCounter; | |
} // end function updateUniforms() | |
// load a resource | |
PerlinNoiseTexture = textureLoader.load( | |
// resource URL | |
'textures/perlin256.png', | |
// onLoad callback | |
function (texture) | |
{ | |
texture.wrapS = THREE.RepeatWrapping; | |
texture.wrapT = THREE.RepeatWrapping; | |
texture.flipY = false; | |
texture.minFilter = THREE.LinearFilter; | |
texture.magFilter = THREE.LinearFilter; | |
texture.generateMipmaps = false; | |
// now that the texture has been loaded, we can init | |
init(); | |
} | |
); |
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
precision highp float; | |
precision highp int; | |
precision highp sampler2D; | |
uniform sampler2D t_PerlinNoise; | |
uniform float uCameraUnderWater; | |
uniform vec3 uSunDirection; | |
uniform mat4 uShortBoxInvMatrix; | |
uniform mat4 uTallBoxInvMatrix; | |
#include <pathtracing_uniforms_and_defines> | |
#include <pathtracing_calc_fresnel_reflectance> | |
#include <pathtracing_skymodel_defines> | |
#define N_QUADS 4 | |
#define N_BOXES 2 | |
#define N_OPENCYLINDERS 4 | |
//----------------------------------------------------------------------- | |
vec3 rayOrigin, rayDirection; | |
// recorded intersection data: | |
vec3 hitNormal, hitEmission, hitColor; | |
vec2 hitUV; | |
float hitObjectID; | |
int hitType = -100; | |
struct OpenCylinder { float radius; vec3 pos1; vec3 pos2; vec3 emission; vec3 color; int type; }; | |
struct Quad { vec3 normal; vec3 v0; vec3 v1; vec3 v2; vec3 v3; vec3 emission; vec3 color; int type; }; | |
struct Box { vec3 minCorner; vec3 maxCorner; vec3 emission; vec3 color; int type; }; | |
OpenCylinder openCylinders[N_OPENCYLINDERS]; | |
Quad quads[N_QUADS]; | |
Box boxes[N_BOXES]; | |
#include <pathtracing_random_functions> | |
#include <pathtracing_sphere_intersect> | |
#include <pathtracing_opencylinder_intersect> | |
#include <pathtracing_plane_intersect> | |
#include <pathtracing_quad_intersect> | |
#include <pathtracing_box_intersect> | |
#include <pathtracing_physical_sky_functions> | |
//--------------------------------------------------------------------------------------------------------- | |
float DisplacementBoxIntersect( vec3 minCorner, vec3 maxCorner, vec3 rayOrigin, vec3 rayDirection ) | |
//--------------------------------------------------------------------------------------------------------- | |
{ | |
vec3 invDir = 1.0 / rayDirection; | |
vec3 tmin = (minCorner - rayOrigin) * invDir; | |
vec3 tmax = (maxCorner - rayOrigin) * invDir; | |
vec3 real_min = min(tmin, tmax); | |
vec3 real_max = max(tmin, tmax); | |
float minmax = min( min(real_max.x, real_max.y), real_max.z); | |
float maxmin = max( max(real_min.x, real_min.y), real_min.z); | |
// early out | |
if (minmax < maxmin) return INFINITY; | |
if (maxmin > 0.0) // if we are outside the box | |
{ | |
return maxmin; | |
} | |
if (minmax > 0.0) // else if we are inside the box | |
{ | |
return minmax; | |
} | |
return INFINITY; | |
} | |
// SEA | |
/* Credit: some of the following ocean code is borrowed from https://www.shadertoy.com/view/Ms2SD1 posted by user 'TDM' */ | |
#define SEA_HEIGHT 1.0 // this is how many units from the top of the ocean bounding box | |
#define SEA_FREQ 1.5 // wave density: lower = spread out, higher = close together | |
#define SEA_CHOPPY 2.0 // smaller beachfront-type waves, they travel in parallel | |
#define SEA_SPEED 0.15 // how quickly time passes | |
#define OCTAVE_M mat2(1.6, 1.2, -1.2, 1.6); | |
float hash( vec2 p ) | |
{ | |
float h = dot(p,vec2(127.1,311.7)); | |
return fract(sin(h)*43758.5453123); | |
} | |
float noise( in vec2 p ) | |
{ | |
vec2 i = floor( p ); | |
vec2 f = fract( p ); | |
vec2 u = f*f*(3.0-2.0*f); | |
return -1.0+2.0*mix( mix( hash( i + vec2(0.0,0.0) ), | |
hash( i + vec2(1.0,0.0) ), u.x), | |
mix( hash( i + vec2(0.0,1.0) ), | |
hash( i + vec2(1.0,1.0) ), u.x), u.y); | |
} | |
float sea_octave( vec2 uv, float choppy ) | |
{ | |
uv += noise(uv); | |
vec2 wv = 1.0 - abs(sin(uv)); | |
vec2 swv = abs(cos(uv)); | |
wv = mix(wv, swv, clamp(wv, 0.0, 1.0)); | |
return pow(1.0 - pow(wv.x * wv.y, 0.65), choppy); | |
} | |
float getOceanWaterHeight( vec3 p ) | |
{ | |
p.x *= 0.001; | |
p.z *= 0.001; | |
float freq = SEA_FREQ; | |
float amp = SEA_HEIGHT; | |
float choppy = SEA_CHOPPY; | |
float sea_time = uTime * SEA_SPEED; | |
vec2 uv = p.xz; uv.x *= 0.75; | |
float d, h = 0.0; | |
d = sea_octave((uv + sea_time) * freq, choppy); | |
d += sea_octave((uv - sea_time) * freq, choppy); | |
h += d * amp; | |
return 50.0 * h - 10.0; | |
} | |
float getOceanWaterHeight_Detail( vec3 p ) | |
{ | |
p.x *= 0.001; | |
p.z *= 0.001; | |
float freq = SEA_FREQ; | |
float amp = SEA_HEIGHT; | |
float choppy = SEA_CHOPPY; | |
float sea_time = uTime * SEA_SPEED; | |
vec2 uv = p.xz; uv.x *= 0.75; | |
float d, h = 0.0; | |
for(int i = 0; i < 4; i++) | |
{ | |
d = sea_octave((uv + sea_time) * freq, choppy); | |
d += sea_octave((uv - sea_time) * freq, choppy); | |
h += d * amp; | |
uv *= OCTAVE_M; freq *= 1.9; amp *= 0.22; | |
choppy = mix(choppy, 1.0, 0.2); | |
} | |
return 50.0 * h - 10.0; | |
} | |
// CLOUDS | |
/* Credit: some of the following cloud code is borrowed from https://www.shadertoy.com/view/XtBXDw posted by user 'valentingalea' */ | |
#define THICKNESS 25.0 | |
#define ABSORPTION 0.45 | |
#define N_MARCH_STEPS 12 | |
#define N_LIGHT_STEPS 3 | |
float noise3D( in vec3 p ) | |
{ | |
return texture(t_PerlinNoise, p.xz).x; | |
} | |
const mat3 m = 1.21 * mat3( 0.00, 0.80, 0.60, | |
-0.80, 0.36, -0.48, | |
-0.60, -0.48, 0.64 ); | |
float fbm( vec3 p ) | |
{ | |
float t; | |
float mult = 2.0; | |
t = 1.0 * noise3D(p); p = m * p * mult; | |
t += 0.5 * noise3D(p); p = m * p * mult; | |
t += 0.25 * noise3D(p); | |
return t; | |
} | |
float cloud_density( vec3 pos, float cov ) | |
{ | |
float dens = fbm(pos * 0.002); | |
dens *= smoothstep(cov, cov + 0.05, dens); | |
return clamp(dens, 0.0, 1.0); | |
} | |
float cloud_light( vec3 pos, vec3 dir_step, float cov ) | |
{ | |
float T = 1.0; // transmitance | |
float dens; | |
float T_i; | |
for (int i = 0; i < N_LIGHT_STEPS; i++) | |
{ | |
dens = cloud_density(pos, cov); | |
T_i = exp(-ABSORPTION * dens); | |
T *= T_i; | |
pos += dir_step; | |
} | |
return T; | |
} | |
vec4 render_clouds(vec3 rayOrigin, vec3 rayDirection) | |
{ | |
float march_step = THICKNESS / float(N_MARCH_STEPS); | |
vec3 pos = rayOrigin + vec3(uTime * -3.0, uTime * -0.5, uTime * -2.0); | |
vec3 dir_step = rayDirection / clamp(rayDirection.y, 0.3, 1.0) * march_step; | |
vec3 light_step = uSunDirection * 5.0; | |
float covAmount = (sin(mod(uTime * 0.1, TWO_PI))) * 0.5 + 0.5; | |
float coverage = mix(1.0, 1.5, clamp(covAmount, 0.0, 1.0)); | |
float T = 1.0; // transmitance | |
vec3 C = vec3(0); // color | |
float alpha = 0.0; | |
float dens; | |
float T_i; | |
float cloudLight; | |
for (int i = 0; i < N_MARCH_STEPS; i++) | |
{ | |
dens = cloud_density(pos, coverage); | |
T_i = exp(-ABSORPTION * dens * march_step); | |
T *= T_i; | |
cloudLight = cloud_light(pos, light_step, coverage); | |
C += T * cloudLight * dens * march_step; | |
C = mix(C * 0.95, C, clamp(cloudLight, 0.0, 1.0)); | |
alpha += (1.0 - T_i) * (1.0 - alpha); | |
pos += dir_step; | |
} | |
return vec4(C, alpha); | |
} | |
float checkCloudCover(vec3 rayOrigin, vec3 rayDirection) | |
{ | |
float march_step = THICKNESS / float(N_MARCH_STEPS); | |
vec3 pos = rayOrigin + vec3(uTime * -3.0, uTime * -0.5, uTime * -2.0); | |
vec3 dir_step = rayDirection / clamp(rayDirection.y, 0.001, 1.0) * march_step; | |
float covAmount = (sin(mod(uTime * 0.1, TWO_PI))) * 0.5 + 0.5; | |
float coverage = mix(1.0, 1.5, clamp(covAmount, 0.0, 1.0)); | |
float alpha = 0.0; | |
float dens; | |
float T_i; | |
for (int i = 0; i < N_MARCH_STEPS; i++) | |
{ | |
dens = cloud_density(pos, coverage); | |
T_i = exp(-ABSORPTION * dens * march_step); | |
alpha += (1.0 - T_i) * (1.0 - alpha); | |
pos += dir_step; | |
} | |
return clamp(1.0 - alpha, 0.0, 1.0); | |
} | |
//----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
float SceneIntersect( int checkOcean ) | |
//----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
{ | |
vec3 rObjOrigin, rObjDirection; | |
vec3 hitObjectSpace; | |
vec3 hitWorldSpace; | |
vec3 normal; | |
vec3 pos; | |
vec3 dir; | |
float h; | |
float d, dw, dc; | |
float dx, dy, dz; | |
float t = INFINITY; | |
float eps = 0.1; | |
float waterWaveHeight; | |
int objectCount = 0; | |
int isRayExiting = FALSE; | |
// SEA FLOOR | |
d = PlaneIntersect(vec4(0, 1, 0, -1000.0), rayOrigin, rayDirection); | |
if (d < t) | |
{ | |
t = d; | |
hitNormal = vec3(0,1,0); | |
hitEmission = vec3(0); | |
hitColor = vec3(0.0, 0.07, 0.07); | |
hitType = SEAFLOOR; | |
hitObjectID = -1.0; | |
} | |
d = OpenCylinderIntersect( openCylinders[0].pos1, openCylinders[0].pos2, openCylinders[0].radius, rayOrigin, rayDirection, normal ); | |
if (d < t) | |
{ | |
t = d; | |
hitNormal = normal; | |
hitEmission = vec3(0); | |
hitColor = openCylinders[0].color; | |
hitType = WOOD; | |
hitObjectID = float(objectCount); | |
} | |
objectCount++; | |
d = OpenCylinderIntersect( openCylinders[1].pos1, openCylinders[1].pos2, openCylinders[1].radius, rayOrigin, rayDirection, normal ); | |
if (d < t) | |
{ | |
t = d; | |
hitNormal = normal; | |
hitEmission = vec3(0); | |
hitColor = openCylinders[0].color; | |
hitType = WOOD; | |
hitObjectID = float(objectCount); | |
} | |
objectCount++; | |
d = OpenCylinderIntersect( openCylinders[2].pos1, openCylinders[2].pos2, openCylinders[2].radius, rayOrigin, rayDirection, normal ); | |
if (d < t) | |
{ | |
t = d; | |
hitNormal = normal; | |
hitEmission = vec3(0); | |
hitColor = openCylinders[0].color; | |
hitType = WOOD; | |
hitObjectID = float(objectCount); | |
} | |
objectCount++; | |
d = OpenCylinderIntersect( openCylinders[3].pos1, openCylinders[3].pos2, openCylinders[3].radius, rayOrigin, rayDirection, normal ); | |
if (d < t) | |
{ | |
t = d; | |
hitNormal = normal; | |
hitEmission = vec3(0); | |
hitColor = openCylinders[0].color; | |
hitType = WOOD; | |
hitObjectID = float(objectCount); | |
} | |
objectCount++; | |
for (int i = 0; i < N_QUADS; i++) | |
{ | |
d = QuadIntersect( quads[i].v0, quads[i].v1, quads[i].v2, quads[i].v3, rayOrigin, rayDirection, TRUE ); | |
if (d < t) | |
{ | |
t = d; | |
hitNormal = quads[i].normal; | |
hitEmission = quads[i].emission; | |
hitColor = quads[i].color; | |
hitType = quads[i].type; | |
hitObjectID = float(objectCount); | |
} | |
objectCount++; | |
} | |
// TALL MIRROR BOX | |
// transform ray into Tall Box's object space | |
rObjOrigin = vec3( uTallBoxInvMatrix * vec4(rayOrigin, 1.0) ); | |
rObjDirection = vec3( uTallBoxInvMatrix * vec4(rayDirection, 0.0) ); | |
d = BoxIntersect( boxes[0].minCorner, boxes[0].maxCorner, rObjOrigin, rObjDirection, normal, isRayExiting ); | |
if (d < t) | |
{ | |
t = d; | |
// transfom normal back into world space | |
hitNormal = transpose(mat3(uTallBoxInvMatrix)) * normal; | |
hitEmission = boxes[0].emission; | |
hitColor = boxes[0].color; | |
hitType = boxes[0].type; | |
hitObjectID = float(objectCount); | |
} | |
objectCount++; | |
// SHORT DIFFUSE WHITE BOX | |
// transform ray into Short Box's object space | |
rObjOrigin = vec3( uShortBoxInvMatrix * vec4(rayOrigin, 1.0) ); | |
rObjDirection = vec3( uShortBoxInvMatrix * vec4(rayDirection, 0.0) ); | |
d = BoxIntersect( boxes[1].minCorner, boxes[1].maxCorner, rObjOrigin, rObjDirection, normal, isRayExiting ); | |
if (d < t) | |
{ | |
t = d; | |
// transfom normal back into world space | |
hitNormal = transpose(mat3(uShortBoxInvMatrix)) * normal; | |
hitEmission = boxes[1].emission; | |
hitColor = boxes[1].color; | |
hitType = boxes[1].type; | |
hitObjectID = float(objectCount); | |
} | |
objectCount++; | |
/////////////////////////////////////////////////////////////////////////////////////////////////////// | |
// OCEAN | |
/////////////////////////////////////////////////////////////////////////////////////////////////////// | |
if ( checkOcean == FALSE ) | |
{ | |
return t; | |
} | |
pos = rayOrigin; | |
dir = rayDirection; | |
h = 0.0; | |
d = 0.0; // reset d | |
for(int i = 0; i < 100; i++) | |
{ | |
h = abs(pos.y - getOceanWaterHeight(pos)); | |
if (d > 4000.0 || h < 1.0) break; | |
d += h; | |
pos += dir * h; | |
} | |
hitWorldSpace = pos; | |
if (d > 4000.0) | |
{ | |
d = PlaneIntersect( vec4(0, 1, 0, 0.0), rayOrigin, rayDirection ); | |
if ( d >= INFINITY ) return t; | |
hitWorldSpace = rayOrigin + rayDirection * d; | |
waterWaveHeight = getOceanWaterHeight_Detail(hitWorldSpace); | |
d = DisplacementBoxIntersect( vec3(-INFINITY, -INFINITY, -INFINITY), vec3(INFINITY, waterWaveHeight, INFINITY), rayOrigin, rayDirection); | |
hitWorldSpace = rayOrigin + rayDirection * d; | |
} | |
if (d < t) | |
{ | |
eps = 1.0; | |
t = d; | |
dx = getOceanWaterHeight_Detail(hitWorldSpace - vec3(eps,0,0)) - getOceanWaterHeight_Detail(hitWorldSpace + vec3(eps,0,0)); | |
dy = eps * 2.0; // (the water wave height is a function of x and z, not dependent on y) | |
dz = getOceanWaterHeight_Detail(hitWorldSpace - vec3(0,0,eps)) - getOceanWaterHeight_Detail(hitWorldSpace + vec3(0,0,eps)); | |
hitNormal = vec3(dx,dy,dz); | |
hitEmission = vec3(0); | |
hitColor = vec3(0.6, 1.0, 1.0); | |
hitType = REFR; | |
hitObjectID = -1.0; // same as sea floor above | |
} | |
return t; | |
} | |
//----------------------------------------------------------------------------------------------------------------------------------------- | |
vec3 CalculateRadiance(out vec3 objectNormal, out vec3 objectColor, out float objectID, out float pixelSharpness) | |
//----------------------------------------------------------------------------------------------------------------------------------------- | |
{ | |
vec3 skyRayOrigin, skyRayDirection; | |
vec3 cameraRayOrigin, cameraRayDirection; | |
vec3 cloudShadowRayOrigin, cloudShadowRayDirection; | |
cameraRayOrigin = rayOrigin; | |
cameraRayDirection = rayDirection; | |
vec3 randVec = vec3(rng() * 2.0 - 1.0, rng() * 2.0 - 1.0, rng() * 2.0 - 1.0); | |
randVec = normalize(randVec); | |
vec3 initialSkyColor = Get_Sky_Color(rayDirection); | |
skyRayOrigin = rayOrigin * vec3(0.02); | |
skyRayDirection = normalize(vec3(rayDirection.x, abs(rayDirection.y), rayDirection.z)); | |
float dc = SphereIntersect( 20000.0, vec3(skyRayOrigin.x, -19900.0, skyRayOrigin.z) + vec3(rng() * 2.0), skyRayOrigin, skyRayDirection ); | |
vec3 skyPos = skyRayOrigin + skyRayDirection * dc; | |
vec4 cld = render_clouds(skyPos, skyRayDirection); | |
cloudShadowRayOrigin = rayOrigin * vec3(0.02); | |
cloudShadowRayDirection = normalize(uSunDirection + (randVec * 0.05)); | |
float dcs = SphereIntersect( 20000.0, vec3(skyRayOrigin.x, -19900.0, skyRayOrigin.z) + vec3(rng() * 2.0), cloudShadowRayOrigin, cloudShadowRayDirection ); | |
vec3 cloudShadowPos = cloudShadowRayOrigin + cloudShadowRayDirection * dcs; | |
float cloudShadowFactor = checkCloudCover(cloudShadowPos, cloudShadowRayDirection); | |
vec3 accumCol = vec3(0); | |
vec3 mask = vec3(1); | |
vec3 reflectionMask = vec3(1); | |
vec3 reflectionRayOrigin = vec3(0); | |
vec3 reflectionRayDirection = vec3(0); | |
vec3 n, nl, x; | |
vec3 firstX = vec3(0); | |
vec3 tdir; | |
float nc, nt, ratioIoR, Re, Tr; | |
//float P, RP, TP; | |
float weight; | |
float t = INFINITY; | |
float hitObjectID; | |
int diffuseCount = 0; | |
int previousIntersecType = -100; | |
hitType = -100; | |
int checkOcean = TRUE; | |
int skyHit = FALSE; | |
int sampleLight = FALSE; | |
int bounceIsSpecular = TRUE; | |
int willNeedReflectionRay = FALSE; | |
for (int bounces = 0; bounces < 6; bounces++) | |
{ | |
previousIntersecType = hitType; | |
t = SceneIntersect(checkOcean); | |
checkOcean = FALSE; | |
if (t == INFINITY) | |
{ | |
vec3 skyColor = Get_Sky_Color(rayDirection); | |
if (bounces == 0) // ray hits sky first | |
{ | |
pixelSharpness = 1.01; | |
skyHit = TRUE; | |
firstX = skyPos; | |
initialSkyColor = skyColor; | |
accumCol += skyColor; | |
break; // exit early | |
} | |
else if (bounces == 1 && previousIntersecType == SPEC) // ray reflects off of mirror box first, then hits sky | |
{ | |
pixelSharpness = 1.01; | |
skyHit = TRUE; | |
firstX = skyPos; | |
initialSkyColor = mask * skyColor; | |
accumCol += initialSkyColor; | |
//break; // exit early | |
} | |
else if (diffuseCount == 0 && bounceIsSpecular == TRUE) // ray reflects off of the ocean | |
{ | |
pixelSharpness = 1.01; | |
//skyHit = TRUE; | |
firstX = skyPos; | |
initialSkyColor = mask * skyColor; | |
accumCol += initialSkyColor; | |
//break; // exit early | |
} | |
else if (sampleLight == TRUE) // diffuse direct sun light sampling (Cornell Box) | |
{ | |
accumCol += mask * skyColor; | |
//break; | |
} | |
else if (diffuseCount > 0) // random diffuse ray hit sky (don't count Sun hits, otherwise will make fireflies) | |
{ | |
weight = dot(rayDirection, uSunDirection) < 0.99 ? 1.0 : 0.0; | |
accumCol += mask * skyColor * weight; | |
//break; | |
} | |
if (willNeedReflectionRay == TRUE) | |
{ | |
mask = reflectionMask; | |
rayOrigin = reflectionRayOrigin; | |
rayDirection = reflectionRayDirection; | |
willNeedReflectionRay = FALSE; | |
bounceIsSpecular = TRUE; | |
sampleLight = FALSE; | |
continue; | |
} | |
// reached the sky light, so we can exit | |
break; | |
} // end if (t == INFINITY) | |
if (hitType == SEAFLOOR) | |
{ | |
float waterDotSun = max(0.0, dot(vec3(0,1,0), uSunDirection)); | |
float waterDotCamera = max(0.4, dot(vec3(0,1,0), -cameraRayDirection)); | |
accumCol += mask * hitColor * waterDotSun * waterDotCamera; | |
if (willNeedReflectionRay == TRUE) | |
{ | |
mask = reflectionMask; | |
rayOrigin = reflectionRayOrigin; | |
rayDirection = reflectionRayDirection; | |
willNeedReflectionRay = FALSE; | |
bounceIsSpecular = TRUE; | |
sampleLight = FALSE; | |
continue; | |
} | |
break; | |
} // end if (hitType == SEAFLOOR) | |
// if we get here and sampleLight is still TRUE, shadow ray failed to find the light source | |
// the ray hit an occluding object along its way to the light | |
if (sampleLight == TRUE) | |
{ | |
if (willNeedReflectionRay == TRUE) | |
{ | |
mask = reflectionMask; | |
rayOrigin = reflectionRayOrigin; | |
rayDirection = reflectionRayDirection; | |
willNeedReflectionRay = FALSE; | |
bounceIsSpecular = TRUE; | |
sampleLight = FALSE; | |
diffuseCount = 0; | |
continue; | |
} | |
break; | |
} | |
// useful data | |
n = normalize(hitNormal); | |
nl = dot(n, rayDirection) < 0.0 ? n : -n; | |
x = rayOrigin + rayDirection * t; | |
if (bounces == 0) | |
{ | |
firstX = x; | |
objectNormal = nl; | |
objectColor = hitColor; | |
objectID = hitObjectID; | |
} | |
if (bounces == 1 && previousIntersecType == SPEC) | |
{ | |
objectNormal = nl; | |
} | |
if (hitType == DIFF) // Ideal DIFFUSE reflection | |
{ | |
diffuseCount++; | |
mask *= hitColor; | |
bounceIsSpecular = FALSE; | |
if (diffuseCount == 1 && rand() < 0.5) | |
{ | |
mask *= 2.0; | |
// choose random Diffuse sample vector | |
rayDirection = randomCosWeightedDirectionInHemisphere(nl); | |
rayOrigin = x + nl * uEPS_intersect; | |
continue; | |
} | |
rayDirection = randomDirectionInSpecularLobe(uSunDirection, 0.1); // create shadow ray pointed towards light | |
rayOrigin = x + nl * uEPS_intersect; | |
weight = max(0.0, dot(rayDirection, nl)) * 0.05; // down-weight directSunLight contribution | |
mask *= diffuseCount == 1 ? 2.0 : 1.0; | |
mask *= weight * cloudShadowFactor; | |
sampleLight = TRUE; | |
continue; | |
} // end if (hitType == DIFF) | |
if (hitType == SPEC) // Ideal SPECULAR reflection | |
{ | |
mask *= hitColor; | |
rayDirection = reflect(rayDirection, nl); | |
rayOrigin = x + nl * uEPS_intersect; | |
if (bounces == 0) | |
checkOcean = TRUE; | |
//bounceIsSpecular = TRUE; // turn on mirror caustics | |
continue; | |
} | |
if (hitType == REFR) // Ideal dielectric REFRACTION | |
{ | |
//pixelSharpness = diffuseCount == 0 ? -1.0 : pixelSharpness; | |
nc = 1.0; // IOR of Air | |
nt = 1.33; // IOR of Water | |
Re = calcFresnelReflectance(rayDirection, n, nc, nt, ratioIoR); | |
Tr = 1.0 - Re; | |
if ( (bounces == 0 || (bounces == 1 && previousIntersecType == SPEC)) ) | |
{ | |
reflectionMask = mask * Re; | |
reflectionRayDirection = reflect(rayDirection, nl); // reflect ray from surface | |
reflectionRayOrigin = x + nl * uEPS_intersect; | |
willNeedReflectionRay = TRUE; | |
} | |
if (Re == 1.0) | |
{ | |
mask = reflectionMask; | |
rayOrigin = reflectionRayOrigin; | |
rayDirection = reflectionRayDirection; | |
willNeedReflectionRay = FALSE; | |
bounceIsSpecular = TRUE; | |
sampleLight = FALSE; | |
continue; | |
} | |
mask *= Tr; | |
mask *= hitColor; | |
// transmit ray through surface | |
tdir = refract(rayDirection, nl, ratioIoR); | |
rayDirection = tdir; | |
rayOrigin = x - nl * uEPS_intersect; | |
continue; | |
} // end if (hitType == REFR) | |
if (hitType == WOOD) // Diffuse object underneath with thin layer of Water on top | |
{ | |
nc = 1.0; // IOR of air | |
nt = 1.1; // IOR of ClearCoat | |
Re = calcFresnelReflectance(rayDirection, n, nc, nt, ratioIoR); | |
Tr = 1.0 - Re; | |
if (bounces == 0)// || (bounces == 1 && hitObjectID != objectID && bounceIsSpecular == TRUE)) | |
{ | |
reflectionMask = mask * Re; | |
reflectionRayDirection = reflect(rayDirection, nl); // reflect ray from surface | |
reflectionRayOrigin = x + nl * uEPS_intersect; | |
willNeedReflectionRay = TRUE; | |
} | |
diffuseCount++; | |
if (bounces == 0) | |
mask *= Tr; | |
float pattern = noise( vec2( x.x * 0.5 * x.z * 0.5 + sin(x.y*0.005) ) ); | |
float woodPattern = 1.0 / max(1.0, pattern * 100.0); | |
hitColor *= woodPattern; | |
if (bounces == 0) | |
objectColor = hitColor; | |
mask *= hitColor; | |
bounceIsSpecular = FALSE; | |
if (diffuseCount == 1 && rand() < 0.5) | |
{ | |
mask *= 2.0; | |
// choose random Diffuse sample vector | |
rayDirection = randomCosWeightedDirectionInHemisphere(nl); | |
rayOrigin = x + nl * uEPS_intersect; | |
continue; | |
} | |
rayDirection = randomDirectionInSpecularLobe(uSunDirection, 0.1); // create shadow ray pointed towards light | |
mask *= diffuseCount == 1 ? 2.0 : 1.0; | |
rayOrigin = x + nl * uEPS_intersect; | |
weight = max(0.0, dot(rayDirection, nl)) * 0.05; // down-weight directSunLight contribution | |
mask *= weight; | |
sampleLight = TRUE; | |
continue; | |
} //end if (hitType == WOOD) | |
} // end for (int bounces = 0; bounces < 6; bounces++) | |
// atmospheric haze effect (aerial perspective) | |
float hitDistance; | |
if ( skyHit == TRUE ) // sky and clouds | |
{ | |
vec3 cloudColor = cld.rgb / (cld.a + 0.00001); | |
vec3 sunColor = clamp( Get_Sky_Color(randomDirectionInSpecularLobe(uSunDirection, 0.1)), 0.0, 5.0 ); | |
cloudColor *= sunColor; | |
cloudColor = mix(initialSkyColor, cloudColor, clamp(cld.a, 0.0, 1.0)); | |
hitDistance = distance(skyRayOrigin, skyPos); | |
accumCol = mask * mix( accumCol, cloudColor, clamp( exp2( -hitDistance * 0.004 ), 0.0, 1.0 ) ); | |
} | |
else // terrain and other objects | |
{ | |
hitDistance = distance(cameraRayOrigin, firstX); | |
accumCol = mix( initialSkyColor, accumCol, clamp( exp2( -log(hitDistance * 0.00003) ), 0.0, 1.0 ) ); | |
// underwater fog effect | |
hitDistance = distance(cameraRayOrigin, firstX); | |
hitDistance *= uCameraUnderWater; | |
accumCol = mix( vec3(0.0,0.05,0.05), accumCol, clamp( exp2( -hitDistance * 0.001 ), 0.0, 1.0 ) ); | |
} | |
return max(vec3(0), accumCol); // prevents black spot artifacts appearing in the water | |
} | |
//----------------------------------------------------------------------- | |
void SetupScene( void ) | |
//----------------------------------------------------------------------- | |
{ | |
vec3 z = vec3(0);// No color value, Black | |
quads[0] = Quad( vec3(0,0,1), vec3( 0.0, 0.0,-559.2), vec3(549.6, 0.0,-559.2), vec3(549.6, 548.8,-559.2), vec3( 0.0, 548.8,-559.2), z, vec3(0.9), DIFF);// Back Wall | |
quads[1] = Quad( vec3(1,0,0),vec3( 0.0, 0.0, 0.0), vec3( 0.0, 0.0,-559.2), vec3( 0.0, 548.8,-559.2), vec3( 0.0, 548.8, 0.0), z, vec3(0.7, 0.12,0.05), DIFF);// Left Wall Red | |
quads[2] = Quad( vec3(-1,0,0),vec3(549.6, 0.0,-559.2), vec3(549.6, 0.0, 0.0), vec3(549.6, 548.8, 0.0), vec3(549.6, 548.8,-559.2), z, vec3(0.2, 0.4, 0.36), DIFF);// Right Wall Green | |
//quads[3] = Quad( vec3(0,-1,0), vec3( 0.0, 548.8,-559.2), vec3(549.6, 548.8,-559.2), vec3(549.6, 548.8, 0.0), vec3(0.0, 548.8, 0.0), z, vec3(0.9), DIFF);// Ceiling | |
quads[3] = Quad( vec3(0,1,0),vec3( 0.0, 0.0, 0.0), vec3(549.6, 0.0, 0.0), vec3(549.6, 0.0,-559.2), vec3( 0.0, 0.0,-559.2), z, vec3(0.9), DIFF);// Floor | |
openCylinders[0] = OpenCylinder( 50.0, vec3(50 , 0, -50), vec3(50 ,-1000, -50), z, vec3(0.05, 0.0, 0.0), WOOD);// wooden support OpenCylinder | |
openCylinders[1] = OpenCylinder( 50.0, vec3(500, 0, -50), vec3(500,-1000, -50), z, vec3(0.05, 0.0, 0.0), WOOD);// wooden support OpenCylinder | |
openCylinders[2] = OpenCylinder( 50.0, vec3(50 , 0,-510), vec3(50 ,-1000,-510), z, vec3(0.05, 0.0, 0.0), WOOD);// wooden support OpenCylinder | |
openCylinders[3] = OpenCylinder( 50.0, vec3(500, 0,-510), vec3(500,-1000,-510), z, vec3(0.05, 0.0, 0.0), WOOD);// wooden support OpenCylinder | |
boxes[0] = Box( vec3( -82.0,-170.0, -80.0), vec3( 82.0, 170.0, 80.0), z, vec3(1.0), SPEC);// Tall Mirror Box Left | |
boxes[1] = Box( vec3( -86.0, -85.0, -80.0), vec3( 86.0, 85.0, 80.0), z, vec3(0.9), DIFF);// Short Diffuse Box Right | |
} | |
#include <pathtracing_main> | |
/* | |
// tentFilter from Peter Shirley's 'Realistic Ray Tracing (2nd Edition)' book, pg. 60 | |
float tentFilter(float x) | |
{ | |
return (x < 0.5) ? sqrt(2.0 * x) - 1.0 : 1.0 - sqrt(2.0 - (2.0 * x)); | |
} | |
void main( void ) | |
{ | |
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]); | |
// the following is not needed - three.js has a built-in uniform named cameraPosition | |
//vec3 camPos = vec3( uCameraMatrix[3][0], uCameraMatrix[3][1], uCameraMatrix[3][2]); | |
// calculate unique seed for rng() function | |
seed = uvec2(uFrameCounter, uFrameCounter + 1.0) * uvec2(gl_FragCoord); | |
// initialize rand() variables | |
counter = -1.0; // will get incremented by 1 on each call to rand() | |
channel = 0; // the final selected color channel to use for rand() calc (range: 0 to 3, corresponds to R,G,B, or A) | |
randNumber = 0.0; // the final randomly-generated number (range: 0.0 to 1.0) | |
randVec4 = vec4(0); // samples and holds the RGBA blueNoise texture value for this pixel | |
randVec4 = texelFetch(tBlueNoiseTexture, ivec2(mod(gl_FragCoord.xy + floor(uRandomVec2 * 256.0), 256.0)), 0); | |
vec2 pixelOffset = vec2( tentFilter(rand()), tentFilter(rand()) ) * 0.5; | |
// we must map pixelPos into the range -1.0 to +1.0 | |
vec2 pixelPos = ((gl_FragCoord.xy + pixelOffset) / uResolution) * 2.0 - 1.0; | |
vec3 rayDir = uUseOrthographicCamera ? camForward : | |
normalize( pixelPos.x * camRight * uULen + pixelPos.y * camUp * uVLen + camForward ); | |
// depth of field | |
vec3 focalPoint = uFocusDistance * rayDir; | |
float randomAngle = rand() * TWO_PI; // pick random point on aperture | |
float randomRadius = rand() * uApertureSize; | |
vec3 randomAperturePos = ( cos(randomAngle) * camRight + sin(randomAngle) * camUp ) * sqrt(randomRadius); | |
// point on aperture to focal point | |
vec3 finalRayDir = normalize(focalPoint - randomAperturePos); | |
rayOrigin = uUseOrthographicCamera ? cameraPosition + (camRight * pixelPos.x * uULen * 100.0) + (camUp * pixelPos.y * uVLen * 100.0) + randomAperturePos : | |
cameraPosition + randomAperturePos; | |
rayDirection = finalRayDir; | |
SetupScene(); | |
// Edge Detection - don't want to blur edges where either surface normals change abruptly (i.e. room wall corners), objects overlap each other (i.e. edge of a foreground sphere in front of another sphere right behind it), | |
// or an abrupt color variation on the same smooth surface, even if it has similar surface normals (i.e. checkerboard pattern). Want to keep all of these cases as sharp as possible - no blur filter will be applied. | |
vec3 objectNormal, objectColor; | |
float objectID = -INFINITY; | |
float pixelSharpness = 0.0; | |
// perform path tracing and get resulting pixel color | |
vec4 currentPixel = vec4( vec3(CalculateRadiance(objectNormal, objectColor, objectID, pixelSharpness)), 0.0 ); | |
// if difference between normals of neighboring pixels is less than the first edge0 threshold, the white edge line effect is considered off (0.0) | |
float edge0 = 0.2; // edge0 is the minimum difference required between normals of neighboring pixels to start becoming a white edge line | |
// any difference between normals of neighboring pixels that is between edge0 and edge1 smoothly ramps up the white edge line brightness (smoothstep 0.0-1.0) | |
float edge1 = 0.6; // once the difference between normals of neighboring pixels is >= this edge1 threshold, the white edge line is considered fully bright (1.0) | |
float difference_Nx = fwidth(objectNormal.x); | |
float difference_Ny = fwidth(objectNormal.y); | |
float difference_Nz = fwidth(objectNormal.z); | |
float normalDifference = smoothstep(edge0, edge1, difference_Nx) + smoothstep(edge0, edge1, difference_Ny) + smoothstep(edge0, edge1, difference_Nz); | |
float objectDifference = min(fwidth(objectID), 1.0); | |
float colorDifference = (fwidth(objectColor.r) + fwidth(objectColor.g) + fwidth(objectColor.b)) > 0.0 ? 1.0 : 0.0; | |
// white-line debug visualization for normal difference | |
//currentPixel.rgb += (rng() * 1.5) * vec3(normalDifference); | |
// white-line debug visualization for object difference | |
//currentPixel.rgb += (rng() * 1.5) * vec3(objectDifference); | |
// white-line debug visualization for color difference | |
//currentPixel.rgb += (rng() * 1.5) * vec3(colorDifference); | |
// white-line debug visualization for all 3 differences | |
//currentPixel.rgb += (rng() * 1.5) * vec3( clamp(max(normalDifference, max(objectDifference, colorDifference)), 0.0, 1.0) ); | |
vec4 previousPixel = texelFetch(tPreviousTexture, ivec2(gl_FragCoord.xy), 0); | |
if (uCameraIsMoving) // camera is currently moving | |
{ | |
previousPixel.rgb *= 0.7; // motion-blur trail amount (old image) | |
currentPixel.rgb *= 0.3; // brightness of new image (noisy) | |
previousPixel.a = 0.0; | |
} | |
else | |
{ | |
previousPixel.rgb *= 0.9; // motion-blur trail amount (old image) | |
currentPixel.rgb *= 0.1; // brightness of new image (noisy) | |
} | |
// if current raytraced pixel didn't return any color value, just use the previous frame's pixel color | |
if (currentPixel.rgb == vec3(0.0)) | |
{ | |
currentPixel.rgb = previousPixel.rgb; | |
previousPixel.rgb *= 0.5; | |
currentPixel.rgb *= 0.5; | |
} | |
if (colorDifference >= 1.0 || normalDifference >= 1.0 || objectDifference >= 1.0) | |
pixelSharpness = 1.01; | |
currentPixel.a = pixelSharpness; | |
// makes sharp edges more stable | |
if (previousPixel.a == 1.01) | |
currentPixel.a = 1.01; | |
// for dynamic scenes (to clear out old, dark, sharp pixel trails left behind from moving objects) | |
if (previousPixel.a == 1.01 && rng() < 0.05) | |
currentPixel.a = 1.0; | |
pc_fragColor = vec4(previousPixel.rgb + currentPixel.rgb, currentPixel.a); | |
} */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
If you replace both Ocean_And_Sky_Rendering.js and Ocean_And_Sky_Rendering_Fragment.glsl in your project for the Ocean and Sky Rendering demo, then it should stop time and freeze all of the dynamic objects (ocean, sun, clouds) in place. By changing the elapsedTime variable and the sunAngle variable, you can capture different moments (seconds) in time and different sun angles in the sky (sunrise to sunset).