diff --git a/build/RayTracingRenderer.es5.js b/build/RayTracingRenderer.es5.js index 18c447c..eaaeb2e 100644 --- a/build/RayTracingRenderer.es5.js +++ b/build/RayTracingRenderer.es5.js @@ -2298,9 +2298,9 @@ return target; } - // Create a piecewise 2D cumulative distribution function of light intensity from an envmap + // Create a piecewise 2D cumulative distribution function of light intensity from an env map // http://www.pbr-book.org/3ed-2018/Monte_Carlo_Integration/2D_Sampling_with_Multidimensional_Transformations.html#Piecewise-Constant2DDistributions - function envmapDistribution(image) { + function envMapDistribution(image) { var data = image.data; var cdfImage = { width: image.width + 2, @@ -2376,15 +2376,15 @@ // Manually performs linear filtering if the extension OES_texture_float_linear is not supported var textureLinear = "\nvec4 textureLinear(sampler2D map, vec2 uv) {\n #ifdef OES_texture_float_linear\n return texture(map, uv);\n #else\n vec2 size = vec2(textureSize(map, 0));\n vec2 texelSize = 1.0 / size;\n\n uv = uv * size - 0.5;\n vec2 f = fract(uv);\n uv = floor(uv) + 0.5;\n\n vec4 s1 = texture(map, (uv + vec2(0, 0)) * texelSize);\n vec4 s2 = texture(map, (uv + vec2(1, 0)) * texelSize);\n vec4 s3 = texture(map, (uv + vec2(0, 1)) * texelSize);\n vec4 s4 = texture(map, (uv + vec2(1, 1)) * texelSize);\n\n return mix(mix(s1, s2, f.x), mix(s3, s4, f.x), f.y);\n #endif\n}\n"; - var intersect = "\n\nuniform sampler2D positions;\nuniform sampler2D normals;\nuniform sampler2D uvs;\nuniform sampler2D bvh;\n\nstruct Triangle {\n vec3 p0;\n vec3 p1;\n vec3 p2;\n};\n\nvoid surfaceInteractionFromBVH(inout SurfaceInteraction si, Triangle tri, vec3 barycentric, ivec3 index, vec3 faceNormal, int materialIndex) {\n si.hit = true;\n si.faceNormal = faceNormal;\n si.position = barycentric.x * tri.p0 + barycentric.y * tri.p1 + barycentric.z * tri.p2;\n ivec2 i0 = unpackTexel(index.x, VERTEX_COLUMNS);\n ivec2 i1 = unpackTexel(index.y, VERTEX_COLUMNS);\n ivec2 i2 = unpackTexel(index.z, VERTEX_COLUMNS);\n\n vec3 n0 = texelFetch(normals, i0, 0).xyz;\n vec3 n1 = texelFetch(normals, i1, 0).xyz;\n vec3 n2 = texelFetch(normals, i2, 0).xyz;\n vec3 normal = normalize(barycentric.x * n0 + barycentric.y * n1 + barycentric.z * n2);\n\n #if defined(NUM_DIFFUSE_MAPS) || defined(NUM_NORMAL_MAPS) || defined(NUM_PBR_MAPS)\n vec2 uv0 = texelFetch(uvs, i0, 0).xy;\n vec2 uv1 = texelFetch(uvs, i1, 0).xy;\n vec2 uv2 = texelFetch(uvs, i2, 0).xy;\n vec2 uv = fract(barycentric.x * uv0 + barycentric.y * uv1 + barycentric.z * uv2);\n #else\n vec2 uv = vec2(0.0);\n #endif\n\n si.materialType = int(getMatType(materialIndex));\n si.color = getMatColor(materialIndex, uv);\n si.roughness = getMatRoughness(materialIndex, uv);\n si.metalness = getMatMetalness(materialIndex, uv);\n\n #ifdef NUM_NORMAL_MAPS\n vec3 dp1 = tri.p0 - tri.p2;\n vec3 dp2 = tri.p1 - tri.p2;\n vec2 duv1 = uv0 - uv2;\n vec2 duv2 = uv1 - uv2;\n si.normal = getMatNormal(materialIndex, uv, normal, dp1, dp2, duv1, duv2);\n #else\n si.normal = normal;\n #endif\n}\n\nstruct TriangleIntersect {\n float t;\n vec3 barycentric;\n};\n\n// Triangle-ray intersection\n// Faster than the classic M\xF6ller\u2013Trumbore intersection algorithm\n// http://www.pbr-book.org/3ed-2018/Shapes/Triangle_Meshes.html#TriangleIntersection\nTriangleIntersect intersectTriangle(Ray r, Triangle tri, int maxDim, vec3 shear) {\n TriangleIntersect ti;\n vec3 d = r.d;\n\n // translate vertices based on ray origin\n vec3 p0t = tri.p0 - r.o;\n vec3 p1t = tri.p1 - r.o;\n vec3 p2t = tri.p2 - r.o;\n\n // permute components of triangle vertices\n if (maxDim == 0) {\n p0t = p0t.yzx;\n p1t = p1t.yzx;\n p2t = p2t.yzx;\n } else if (maxDim == 1) {\n p0t = p0t.zxy;\n p1t = p1t.zxy;\n p2t = p2t.zxy;\n }\n\n // apply shear transformation to translated vertex positions\n p0t.xy += shear.xy * p0t.z;\n p1t.xy += shear.xy * p1t.z;\n p2t.xy += shear.xy * p2t.z;\n\n // compute edge function coefficients\n vec3 e = vec3(\n p1t.x * p2t.y - p1t.y * p2t.x,\n p2t.x * p0t.y - p2t.y * p0t.x,\n p0t.x * p1t.y - p0t.y * p1t.x\n );\n\n // check if intersection is inside triangle\n if (any(lessThan(e, vec3(0))) && any(greaterThan(e, vec3(0)))) {\n return ti;\n }\n\n float det = e.x + e.y + e.z;\n\n // not needed?\n // if (det == 0.) {\n // return ti;\n // }\n\n p0t.z *= shear.z;\n p1t.z *= shear.z;\n p2t.z *= shear.z;\n float tScaled = (e.x * p0t.z + e.y * p1t.z + e.z * p2t.z);\n\n // not needed?\n // if (sign(det) != sign(tScaled)) {\n // return ti;\n // }\n\n // check if closer intersection already exists\n if (abs(tScaled) > abs(r.tMax * det)) {\n return ti;\n }\n\n float invDet = 1. / det;\n ti.t = tScaled * invDet;\n ti.barycentric = e * invDet;\n\n return ti;\n}\n\nstruct Box {\n vec3 min;\n vec3 max;\n};\n\n// Branchless ray/box intersection\n// https://tavianator.com/fast-branchless-raybounding-box-intersections/\nfloat intersectBox(Ray r, Box b) {\n vec3 tBot = (b.min - r.o) * r.invD;\n vec3 tTop = (b.max - r.o) * r.invD;\n vec3 tNear = min(tBot, tTop);\n vec3 tFar = max(tBot, tTop);\n float t0 = max(tNear.x, max(tNear.y, tNear.z));\n float t1 = min(tFar.x, min(tFar.y, tFar.z));\n\n return (t0 > t1 || t0 > r.tMax) ? -1.0 : (t0 > 0.0 ? t0 : t1);\n}\n\nint maxDimension(vec3 v) {\n return v.x > v.y ? (v.x > v.z ? 0 : 2) : (v.y > v.z ? 1 : 2);\n}\n\n// Traverse BVH, find closest triangle intersection, and return surface information\nvoid intersectScene(inout Ray ray, inout SurfaceInteraction si) {\n si.hit = false;\n\n int maxDim = maxDimension(abs(ray.d));\n\n // Permute space so that the z dimension is the one where the absolute value of the ray's direction is largest.\n // Then create a shear transformation that aligns ray direction with the +z axis\n vec3 shear;\n if (maxDim == 0) {\n shear = vec3(-ray.d.y, -ray.d.z, 1.0) * ray.invD.x;\n } else if (maxDim == 1) {\n shear = vec3(-ray.d.z, -ray.d.x, 1.0) * ray.invD.y;\n } else {\n shear = vec3(-ray.d.x, -ray.d.y, 1.0) * ray.invD.z;\n }\n\n int nodesToVisit[STACK_SIZE];\n int stack = 0;\n\n nodesToVisit[0] = 0;\n\n while(stack >= 0) {\n int i = nodesToVisit[stack--];\n\n vec4 r1 = fetchData(bvh, i, BVH_COLUMNS);\n vec4 r2 = fetchData(bvh, i + 1, BVH_COLUMNS);\n\n int splitAxisOrNumPrimitives = floatBitsToInt(r1.w);\n\n if (splitAxisOrNumPrimitives >= 0) {\n // Intersection is a bounding box. Test for box intersection and keep traversing BVH\n int splitAxis = splitAxisOrNumPrimitives;\n\n Box bbox = Box(r1.xyz, r2.xyz);\n\n if (intersectBox(ray, bbox) > 0.0) {\n // traverse near node to ray first, and far node to ray last\n if (ray.d[splitAxis] > 0.0) {\n nodesToVisit[++stack] = floatBitsToInt(r2.w);\n nodesToVisit[++stack] = i + 2;\n } else {\n nodesToVisit[++stack] = i + 2;\n nodesToVisit[++stack] = floatBitsToInt(r2.w);\n }\n }\n } else {\n ivec3 index = floatBitsToInt(r1.xyz);\n Triangle tri = Triangle(\n fetchData(positions, index.x, VERTEX_COLUMNS).xyz,\n fetchData(positions, index.y, VERTEX_COLUMNS).xyz,\n fetchData(positions, index.z, VERTEX_COLUMNS).xyz\n );\n TriangleIntersect hit = intersectTriangle(ray, tri, maxDim, shear);\n\n if (hit.t > 0.0) {\n ray.tMax = hit.t;\n int materialIndex = floatBitsToInt(r2.w);\n vec3 faceNormal = r2.xyz;\n surfaceInteractionFromBVH(si, tri, hit.barycentric, index, faceNormal, materialIndex);\n }\n }\n }\n\n // Values must be clamped outside of intersection loop. Clamping inside the loop produces incorrect numbers on some devices.\n si.roughness = clamp(si.roughness, ROUGHNESS_MIN, 1.0);\n si.metalness = clamp(si.metalness, 0.0, 1.0);\n}\n\nbool intersectSceneShadow(inout Ray ray) {\n int maxDim = maxDimension(abs(ray.d));\n\n // Permute space so that the z dimension is the one where the absolute value of the ray's direction is largest.\n // Then create a shear transformation that aligns ray direction with the +z axis\n vec3 shear;\n if (maxDim == 0) {\n shear = vec3(-ray.d.y, -ray.d.z, 1.0) * ray.invD.x;\n } else if (maxDim == 1) {\n shear = vec3(-ray.d.z, -ray.d.x, 1.0) * ray.invD.y;\n } else {\n shear = vec3(-ray.d.x, -ray.d.y, 1.0) * ray.invD.z;\n }\n\n int nodesToVisit[STACK_SIZE];\n int stack = 0;\n\n nodesToVisit[0] = 0;\n\n while(stack >= 0) {\n int i = nodesToVisit[stack--];\n\n vec4 r1 = fetchData(bvh, i, BVH_COLUMNS);\n vec4 r2 = fetchData(bvh, i + 1, BVH_COLUMNS);\n\n int splitAxisOrNumPrimitives = floatBitsToInt(r1.w);\n\n if (splitAxisOrNumPrimitives >= 0) {\n int splitAxis = splitAxisOrNumPrimitives;\n\n Box bbox = Box(r1.xyz, r2.xyz);\n\n if (intersectBox(ray, bbox) > 0.0) {\n if (ray.d[splitAxis] > 0.0) {\n nodesToVisit[++stack] = floatBitsToInt(r2.w);\n nodesToVisit[++stack] = i + 2;\n } else {\n nodesToVisit[++stack] = i + 2;\n nodesToVisit[++stack] = floatBitsToInt(r2.w);\n }\n }\n } else {\n ivec3 index = floatBitsToInt(r1.xyz);\n Triangle tri = Triangle(\n fetchData(positions, index.x, VERTEX_COLUMNS).xyz,\n fetchData(positions, index.y, VERTEX_COLUMNS).xyz,\n fetchData(positions, index.z, VERTEX_COLUMNS).xyz\n );\n\n if (intersectTriangle(ray, tri, maxDim, shear).t > 0.0) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n"; + var intersect = "\n\nuniform sampler2D positionBuffer;\nuniform sampler2D normalBuffer;\nuniform sampler2D uvBuffer;\nuniform sampler2D bvhBuffer;\n\nstruct Triangle {\n vec3 p0;\n vec3 p1;\n vec3 p2;\n};\n\nvoid surfaceInteractionFromBVH(inout SurfaceInteraction si, Triangle tri, vec3 barycentric, ivec3 index, vec3 faceNormal, int materialIndex) {\n si.hit = true;\n si.faceNormal = faceNormal;\n si.position = barycentric.x * tri.p0 + barycentric.y * tri.p1 + barycentric.z * tri.p2;\n ivec2 i0 = unpackTexel(index.x, VERTEX_COLUMNS);\n ivec2 i1 = unpackTexel(index.y, VERTEX_COLUMNS);\n ivec2 i2 = unpackTexel(index.z, VERTEX_COLUMNS);\n\n vec3 n0 = texelFetch(normalBuffer, i0, 0).xyz;\n vec3 n1 = texelFetch(normalBuffer, i1, 0).xyz;\n vec3 n2 = texelFetch(normalBuffer, i2, 0).xyz;\n vec3 normal = normalize(barycentric.x * n0 + barycentric.y * n1 + barycentric.z * n2);\n\n #if defined(NUM_DIFFUSE_MAPS) || defined(NUM_NORMAL_MAPS) || defined(NUM_PBR_MAPS)\n vec2 uv0 = texelFetch(uvBuffer, i0, 0).xy;\n vec2 uv1 = texelFetch(uvBuffer, i1, 0).xy;\n vec2 uv2 = texelFetch(uvBuffer, i2, 0).xy;\n vec2 uv = fract(barycentric.x * uv0 + barycentric.y * uv1 + barycentric.z * uv2);\n #else\n vec2 uv = vec2(0.0);\n #endif\n\n si.materialType = int(getMatType(materialIndex));\n si.color = getMatColor(materialIndex, uv);\n si.roughness = getMatRoughness(materialIndex, uv);\n si.metalness = getMatMetalness(materialIndex, uv);\n\n #ifdef NUM_NORMAL_MAPS\n vec3 dp1 = tri.p0 - tri.p2;\n vec3 dp2 = tri.p1 - tri.p2;\n vec2 duv1 = uv0 - uv2;\n vec2 duv2 = uv1 - uv2;\n si.normal = getMatNormal(materialIndex, uv, normal, dp1, dp2, duv1, duv2);\n #else\n si.normal = normal;\n #endif\n}\n\nstruct TriangleIntersect {\n float t;\n vec3 barycentric;\n};\n\n// Triangle-ray intersection\n// Faster than the classic M\xF6ller\u2013Trumbore intersection algorithm\n// http://www.pbr-book.org/3ed-2018/Shapes/Triangle_Meshes.html#TriangleIntersection\nTriangleIntersect intersectTriangle(Ray r, Triangle tri, int maxDim, vec3 shear) {\n TriangleIntersect ti;\n vec3 d = r.d;\n\n // translate vertices based on ray origin\n vec3 p0t = tri.p0 - r.o;\n vec3 p1t = tri.p1 - r.o;\n vec3 p2t = tri.p2 - r.o;\n\n // permute components of triangle vertices\n if (maxDim == 0) {\n p0t = p0t.yzx;\n p1t = p1t.yzx;\n p2t = p2t.yzx;\n } else if (maxDim == 1) {\n p0t = p0t.zxy;\n p1t = p1t.zxy;\n p2t = p2t.zxy;\n }\n\n // apply shear transformation to translated vertex positions\n p0t.xy += shear.xy * p0t.z;\n p1t.xy += shear.xy * p1t.z;\n p2t.xy += shear.xy * p2t.z;\n\n // compute edge function coefficients\n vec3 e = vec3(\n p1t.x * p2t.y - p1t.y * p2t.x,\n p2t.x * p0t.y - p2t.y * p0t.x,\n p0t.x * p1t.y - p0t.y * p1t.x\n );\n\n // check if intersection is inside triangle\n if (any(lessThan(e, vec3(0))) && any(greaterThan(e, vec3(0)))) {\n return ti;\n }\n\n float det = e.x + e.y + e.z;\n\n // not needed?\n // if (det == 0.) {\n // return ti;\n // }\n\n p0t.z *= shear.z;\n p1t.z *= shear.z;\n p2t.z *= shear.z;\n float tScaled = (e.x * p0t.z + e.y * p1t.z + e.z * p2t.z);\n\n // not needed?\n // if (sign(det) != sign(tScaled)) {\n // return ti;\n // }\n\n // check if closer intersection already exists\n if (abs(tScaled) > abs(r.tMax * det)) {\n return ti;\n }\n\n float invDet = 1. / det;\n ti.t = tScaled * invDet;\n ti.barycentric = e * invDet;\n\n return ti;\n}\n\nstruct Box {\n vec3 min;\n vec3 max;\n};\n\n// Branchless ray/box intersection\n// https://tavianator.com/fast-branchless-raybounding-box-intersections/\nfloat intersectBox(Ray r, Box b) {\n vec3 tBot = (b.min - r.o) * r.invD;\n vec3 tTop = (b.max - r.o) * r.invD;\n vec3 tNear = min(tBot, tTop);\n vec3 tFar = max(tBot, tTop);\n float t0 = max(tNear.x, max(tNear.y, tNear.z));\n float t1 = min(tFar.x, min(tFar.y, tFar.z));\n\n return (t0 > t1 || t0 > r.tMax) ? -1.0 : (t0 > 0.0 ? t0 : t1);\n}\n\nint maxDimension(vec3 v) {\n return v.x > v.y ? (v.x > v.z ? 0 : 2) : (v.y > v.z ? 1 : 2);\n}\n\n// Traverse BVH, find closest triangle intersection, and return surface information\nvoid intersectScene(inout Ray ray, inout SurfaceInteraction si) {\n si.hit = false;\n\n int maxDim = maxDimension(abs(ray.d));\n\n // Permute space so that the z dimension is the one where the absolute value of the ray's direction is largest.\n // Then create a shear transformation that aligns ray direction with the +z axis\n vec3 shear;\n if (maxDim == 0) {\n shear = vec3(-ray.d.y, -ray.d.z, 1.0) * ray.invD.x;\n } else if (maxDim == 1) {\n shear = vec3(-ray.d.z, -ray.d.x, 1.0) * ray.invD.y;\n } else {\n shear = vec3(-ray.d.x, -ray.d.y, 1.0) * ray.invD.z;\n }\n\n int nodesToVisit[STACK_SIZE];\n int stack = 0;\n\n nodesToVisit[0] = 0;\n\n while(stack >= 0) {\n int i = nodesToVisit[stack--];\n\n vec4 r1 = fetchData(bvhBuffer, i, BVH_COLUMNS);\n vec4 r2 = fetchData(bvhBuffer, i + 1, BVH_COLUMNS);\n\n int splitAxisOrNumPrimitives = floatBitsToInt(r1.w);\n\n if (splitAxisOrNumPrimitives >= 0) {\n // Intersection is a bounding box. Test for box intersection and keep traversing BVH\n int splitAxis = splitAxisOrNumPrimitives;\n\n Box bbox = Box(r1.xyz, r2.xyz);\n\n if (intersectBox(ray, bbox) > 0.0) {\n // traverse near node to ray first, and far node to ray last\n if (ray.d[splitAxis] > 0.0) {\n nodesToVisit[++stack] = floatBitsToInt(r2.w);\n nodesToVisit[++stack] = i + 2;\n } else {\n nodesToVisit[++stack] = i + 2;\n nodesToVisit[++stack] = floatBitsToInt(r2.w);\n }\n }\n } else {\n ivec3 index = floatBitsToInt(r1.xyz);\n Triangle tri = Triangle(\n fetchData(positionBuffer, index.x, VERTEX_COLUMNS).xyz,\n fetchData(positionBuffer, index.y, VERTEX_COLUMNS).xyz,\n fetchData(positionBuffer, index.z, VERTEX_COLUMNS).xyz\n );\n TriangleIntersect hit = intersectTriangle(ray, tri, maxDim, shear);\n\n if (hit.t > 0.0) {\n ray.tMax = hit.t;\n int materialIndex = floatBitsToInt(r2.w);\n vec3 faceNormal = r2.xyz;\n surfaceInteractionFromBVH(si, tri, hit.barycentric, index, faceNormal, materialIndex);\n }\n }\n }\n\n // Values must be clamped outside of intersection loop. Clamping inside the loop produces incorrect numbers on some devices.\n si.roughness = clamp(si.roughness, ROUGHNESS_MIN, 1.0);\n si.metalness = clamp(si.metalness, 0.0, 1.0);\n}\n\nbool intersectSceneShadow(inout Ray ray) {\n int maxDim = maxDimension(abs(ray.d));\n\n // Permute space so that the z dimension is the one where the absolute value of the ray's direction is largest.\n // Then create a shear transformation that aligns ray direction with the +z axis\n vec3 shear;\n if (maxDim == 0) {\n shear = vec3(-ray.d.y, -ray.d.z, 1.0) * ray.invD.x;\n } else if (maxDim == 1) {\n shear = vec3(-ray.d.z, -ray.d.x, 1.0) * ray.invD.y;\n } else {\n shear = vec3(-ray.d.x, -ray.d.y, 1.0) * ray.invD.z;\n }\n\n int nodesToVisit[STACK_SIZE];\n int stack = 0;\n\n nodesToVisit[0] = 0;\n\n while(stack >= 0) {\n int i = nodesToVisit[stack--];\n\n vec4 r1 = fetchData(bvhBuffer, i, BVH_COLUMNS);\n vec4 r2 = fetchData(bvhBuffer, i + 1, BVH_COLUMNS);\n\n int splitAxisOrNumPrimitives = floatBitsToInt(r1.w);\n\n if (splitAxisOrNumPrimitives >= 0) {\n int splitAxis = splitAxisOrNumPrimitives;\n\n Box bbox = Box(r1.xyz, r2.xyz);\n\n if (intersectBox(ray, bbox) > 0.0) {\n if (ray.d[splitAxis] > 0.0) {\n nodesToVisit[++stack] = floatBitsToInt(r2.w);\n nodesToVisit[++stack] = i + 2;\n } else {\n nodesToVisit[++stack] = i + 2;\n nodesToVisit[++stack] = floatBitsToInt(r2.w);\n }\n }\n } else {\n ivec3 index = floatBitsToInt(r1.xyz);\n Triangle tri = Triangle(\n fetchData(positionBuffer, index.x, VERTEX_COLUMNS).xyz,\n fetchData(positionBuffer, index.y, VERTEX_COLUMNS).xyz,\n fetchData(positionBuffer, index.z, VERTEX_COLUMNS).xyz\n );\n\n if (intersectTriangle(ray, tri, maxDim, shear).t > 0.0) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n"; var surfaceInteractionDirect = "\n\n uniform sampler2D gPosition;\n uniform sampler2D gNormal;\n uniform sampler2D gFaceNormal;\n uniform sampler2D gColor;\n uniform sampler2D gMatProps;\n\n void surfaceInteractionDirect(vec2 coord, inout SurfaceInteraction si) {\n vec4 positionAndMeshIndex = texture(gPosition, coord);\n\n si.position = positionAndMeshIndex.xyz;\n\n float meshIndex = positionAndMeshIndex.w;\n\n vec4 normalMaterialType = texture(gNormal, coord);\n\n si.normal = normalize(normalMaterialType.xyz);\n si.materialType = int(normalMaterialType.w);\n\n si.faceNormal = normalize(texture(gFaceNormal, coord).xyz);\n\n si.color = texture(gColor, coord).rgb;\n\n vec4 matProps = texture(gMatProps, coord);\n si.roughness = matProps.x;\n si.metalness = matProps.y;\n\n si.hit = meshIndex > 0.0 ? true : false;\n }\n"; - var random = "\n\n// Noise texture used to generate a different random number for each pixel.\n// We use blue noise in particular, but any type of noise will work.\nuniform sampler2D noise;\n\nuniform float stratifiedSamples[SAMPLING_DIMENSIONS];\nuniform float strataSize;\n\n// Every time we call randomSample() in the shader, and for every call to render,\n// we want that specific bit of the shader to fetch a sample from the same position in stratifiedSamples\n// This allows us to use stratified sampling for each random variable in our path tracing\nint sampleIndex = 0;\n\nfloat pixelSeed;\n\nvoid initRandom() {\n vec2 noiseSize = vec2(textureSize(noise, 0));\n\n // tile the small noise texture across the entire screen\n pixelSeed = texture(noise, vCoord / (pixelSize * noiseSize)).r;\n}\n\nfloat randomSample() {\n float stratifiedSample = stratifiedSamples[sampleIndex++];\n\n float random = fract((stratifiedSample + pixelSeed) * strataSize); // blue noise + stratified samples\n\n // transform random number between [0, 1] to (0, 1)\n return EPS + (1.0 - 2.0 * EPS) * random;\n}\n\nvec2 randomSampleVec2() {\n return vec2(randomSample(), randomSample());\n}\n\nstruct MaterialSamples {\n vec2 s1;\n vec2 s2;\n vec2 s3;\n};\n\nMaterialSamples getRandomMaterialSamples() {\n MaterialSamples samples;\n\n samples.s1 = randomSampleVec2();\n samples.s2 = randomSampleVec2();\n samples.s3 = randomSampleVec2();\n\n return samples;\n}\n"; + var random = "\n\n// Noise texture used to generate a different random number for each pixel.\n// We use blue noise in particular, but any type of noise will work.\nuniform sampler2D noiseTex;\n\nuniform float stratifiedSamples[SAMPLING_DIMENSIONS];\nuniform float strataSize;\n\n// Every time we call randomSample() in the shader, and for every call to render,\n// we want that specific bit of the shader to fetch a sample from the same position in stratifiedSamples\n// This allows us to use stratified sampling for each random variable in our path tracing\nint sampleIndex = 0;\n\nfloat pixelSeed;\n\nvoid initRandom() {\n vec2 noiseSize = vec2(textureSize(noiseTex, 0));\n\n // tile the small noise texture across the entire screen\n pixelSeed = texture(noiseTex, vCoord / (pixelSize * noiseSize)).r;\n}\n\nfloat randomSample() {\n float stratifiedSample = stratifiedSamples[sampleIndex++];\n\n float random = fract((stratifiedSample + pixelSeed) * strataSize); // blue noise + stratified samples\n\n // transform random number between [0, 1] to (0, 1)\n return EPS + (1.0 - 2.0 * EPS) * random;\n}\n\nvec2 randomSampleVec2() {\n return vec2(randomSample(), randomSample());\n}\n\nstruct MaterialSamples {\n vec2 s1;\n vec2 s2;\n vec2 s3;\n};\n\nMaterialSamples getRandomMaterialSamples() {\n MaterialSamples samples;\n\n samples.s1 = randomSampleVec2();\n samples.s2 = randomSampleVec2();\n samples.s3 = randomSampleVec2();\n\n return samples;\n}\n"; // Sample the environment map using a cumulative distribution function as described in // http://www.pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection/Sampling_Light_Sources.html#InfiniteAreaLights - var envmap = "\n\nuniform sampler2D envmap;\nuniform sampler2D envmapDistribution;\nuniform sampler2D backgroundMap;\n\nvec2 cartesianToEquirect(vec3 pointOnSphere) {\n float phi = mod(atan(-pointOnSphere.z, -pointOnSphere.x), TWOPI);\n float theta = acos(pointOnSphere.y);\n return vec2(phi * 0.5 * INVPI, theta * INVPI);\n}\n\nfloat getEnvmapV(float u, out int vOffset, out float pdf) {\n ivec2 size = textureSize(envmap, 0);\n\n int left = 0;\n int right = size.y + 1; // cdf length is the length of the envmap + 1\n while (left < right) {\n int mid = (left + right) >> 1;\n float s = texelFetch(envmapDistribution, ivec2(0, mid), 0).x;\n if (s <= u) {\n left = mid + 1;\n } else {\n right = mid;\n }\n }\n vOffset = left - 1;\n\n // x channel is cumulative distribution of envmap luminance\n // y channel is partial probability density of envmap luminance\n vec2 s0 = texelFetch(envmapDistribution, ivec2(0, vOffset), 0).xy;\n vec2 s1 = texelFetch(envmapDistribution, ivec2(0, vOffset + 1), 0).xy;\n\n pdf = s0.y;\n\n return (float(vOffset) + (u - s0.x) / (s1.x - s0.x)) / float(size.y);\n}\n\nfloat getEnvmapU(float u, int vOffset, out float pdf) {\n ivec2 size = textureSize(envmap, 0);\n\n int left = 0;\n int right = size.x + 1; // cdf length is the length of the envmap + 1\n while (left < right) {\n int mid = (left + right) >> 1;\n float s = texelFetch(envmapDistribution, ivec2(1 + mid, vOffset), 0).x;\n if (s <= u) {\n left = mid + 1;\n } else {\n right = mid;\n }\n }\n int uOffset = left - 1;\n\n // x channel is cumulative distribution of envmap luminance\n // y channel is partial probability density of envmap luminance\n vec2 s0 = texelFetch(envmapDistribution, ivec2(1 + uOffset, vOffset), 0).xy;\n vec2 s1 = texelFetch(envmapDistribution, ivec2(1 + uOffset + 1, vOffset), 0).xy;\n\n pdf = s0.y;\n\n return (float(uOffset) + (u - s0.x) / (s1.x - s0.x)) / float(size.x);\n}\n\n// Perform two binary searches to find light direction.\nvec3 sampleEnvmap(vec2 random, out vec2 uv, out float pdf) {\n vec2 partialPdf;\n int vOffset;\n\n uv.y = getEnvmapV(random.x, vOffset, partialPdf.y);\n uv.x = getEnvmapU(random.y, vOffset, partialPdf.x);\n\n float phi = uv.x * TWOPI;\n float theta = uv.y * PI;\n float cosTheta = cos(theta);\n float sinTheta = sin(theta);\n float cosPhi = cos(phi);\n float sinPhi = sin(phi);\n\n vec3 dir = vec3(-sinTheta * cosPhi, cosTheta, -sinTheta * sinPhi);\n\n pdf = partialPdf.x * partialPdf.y * INVPI2 / (2.0 * sinTheta);\n\n return dir;\n}\n\nfloat envmapPdf(vec2 uv) {\n vec2 size = vec2(textureSize(envmap, 0));\n\n float sinTheta = sin(uv.y * PI);\n\n uv *= size;\n\n float partialX = texelFetch(envmapDistribution, ivec2(1.0 + uv.x, uv.y), 0).y;\n float partialY = texelFetch(envmapDistribution, ivec2(0, uv.y), 0).y;\n\n return partialX * partialY * INVPI2 / (2.0 * sinTheta);\n}\n\nvec3 sampleEnvmapFromDirection(vec3 d) {\n vec2 uv = cartesianToEquirect(d);\n return textureLinear(envmap, uv).rgb;\n}\n\nvec3 sampleBackgroundFromDirection(vec3 d) {\n vec2 uv = cartesianToEquirect(d);\n return textureLinear(backgroundMap, uv).rgb;\n}\n\n"; + var envMap = "\n\nuniform sampler2D envMap;\nuniform sampler2D envMapDistribution;\nuniform sampler2D backgroundMap;\n\nvec2 cartesianToEquirect(vec3 pointOnSphere) {\n float phi = mod(atan(-pointOnSphere.z, -pointOnSphere.x), TWOPI);\n float theta = acos(pointOnSphere.y);\n return vec2(phi * 0.5 * INVPI, theta * INVPI);\n}\n\nfloat getEnvmapV(float u, out int vOffset, out float pdf) {\n ivec2 size = textureSize(envMap, 0);\n\n int left = 0;\n int right = size.y + 1; // cdf length is the length of the env map + 1\n while (left < right) {\n int mid = (left + right) >> 1;\n float s = texelFetch(envMapDistribution, ivec2(0, mid), 0).x;\n if (s <= u) {\n left = mid + 1;\n } else {\n right = mid;\n }\n }\n vOffset = left - 1;\n\n // x channel is cumulative distribution of env map luminance\n // y channel is partial probability density of env map luminance\n vec2 s0 = texelFetch(envMapDistribution, ivec2(0, vOffset), 0).xy;\n vec2 s1 = texelFetch(envMapDistribution, ivec2(0, vOffset + 1), 0).xy;\n\n pdf = s0.y;\n\n return (float(vOffset) + (u - s0.x) / (s1.x - s0.x)) / float(size.y);\n}\n\nfloat getEnvmapU(float u, int vOffset, out float pdf) {\n ivec2 size = textureSize(envMap, 0);\n\n int left = 0;\n int right = size.x + 1; // cdf length is the length of the env map + 1\n while (left < right) {\n int mid = (left + right) >> 1;\n float s = texelFetch(envMapDistribution, ivec2(1 + mid, vOffset), 0).x;\n if (s <= u) {\n left = mid + 1;\n } else {\n right = mid;\n }\n }\n int uOffset = left - 1;\n\n // x channel is cumulative distribution of env map luminance\n // y channel is partial probability density of env map luminance\n vec2 s0 = texelFetch(envMapDistribution, ivec2(1 + uOffset, vOffset), 0).xy;\n vec2 s1 = texelFetch(envMapDistribution, ivec2(1 + uOffset + 1, vOffset), 0).xy;\n\n pdf = s0.y;\n\n return (float(uOffset) + (u - s0.x) / (s1.x - s0.x)) / float(size.x);\n}\n\n// Perform two binary searches to find light direction.\nvec3 sampleEnvmap(vec2 random, out vec2 uv, out float pdf) {\n vec2 partialPdf;\n int vOffset;\n\n uv.y = getEnvmapV(random.x, vOffset, partialPdf.y);\n uv.x = getEnvmapU(random.y, vOffset, partialPdf.x);\n\n float phi = uv.x * TWOPI;\n float theta = uv.y * PI;\n float cosTheta = cos(theta);\n float sinTheta = sin(theta);\n float cosPhi = cos(phi);\n float sinPhi = sin(phi);\n\n vec3 dir = vec3(-sinTheta * cosPhi, cosTheta, -sinTheta * sinPhi);\n\n pdf = partialPdf.x * partialPdf.y * INVPI2 / (2.0 * sinTheta);\n\n return dir;\n}\n\nfloat envMapPdf(vec2 uv) {\n vec2 size = vec2(textureSize(envMap, 0));\n\n float sinTheta = sin(uv.y * PI);\n\n uv *= size;\n\n float partialX = texelFetch(envMapDistribution, ivec2(1.0 + uv.x, uv.y), 0).y;\n float partialY = texelFetch(envMapDistribution, ivec2(0, uv.y), 0).y;\n\n return partialX * partialY * INVPI2 / (2.0 * sinTheta);\n}\n\nvec3 sampleEnvmapFromDirection(vec3 d) {\n vec2 uv = cartesianToEquirect(d);\n return textureLinear(envMap, uv).rgb;\n}\n\nvec3 sampleBackgroundFromDirection(vec3 d) {\n vec2 uv = cartesianToEquirect(d);\n return textureLinear(backgroundMap, uv).rgb;\n}\n\n"; var bsdf = "\n\n// Computes the exact value of the Fresnel factor\n// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/\nfloat fresnel(float cosTheta, float eta, float invEta) {\n eta = cosTheta > 0.0 ? eta : invEta;\n cosTheta = abs(cosTheta);\n\n float gSquared = eta * eta + cosTheta * cosTheta - 1.0;\n\n if (gSquared < 0.0) {\n return 1.0;\n }\n\n float g = sqrt(gSquared);\n\n float a = (g - cosTheta) / (g + cosTheta);\n float b = (cosTheta * (g + cosTheta) - 1.0) / (cosTheta * (g - cosTheta) + 1.0);\n\n return 0.5 * a * a * (1.0 + b * b);\n}\n\nfloat fresnelSchlickWeight(float cosTheta) {\n float w = 1.0 - cosTheta;\n return (w * w) * (w * w) * w;\n}\n\n// Computes Schlick's approximation of the Fresnel factor\n// Assumes ray is moving from a less dense to a more dense medium\nfloat fresnelSchlick(float cosTheta, float r0) {\n return mix(fresnelSchlickWeight(cosTheta), 1.0, r0);\n}\n\n// Computes Schlick's approximation of Fresnel factor\n// Accounts for total internal reflection if ray is moving from a more dense to a less dense medium\nfloat fresnelSchlickTIR(float cosTheta, float r0, float ni) {\n\n // moving from a more dense to a less dense medium\n if (cosTheta < 0.0) {\n float inv_eta = ni;\n float SinT2 = inv_eta * inv_eta * (1.0f - cosTheta * cosTheta);\n if (SinT2 > 1.0) {\n return 1.0; // total internal reflection\n }\n cosTheta = sqrt(1.0f - SinT2);\n }\n\n return mix(fresnelSchlickWeight(cosTheta), 1.0, r0);\n}\n\nfloat trowbridgeReitzD(float cosTheta, float alpha2) {\n float e = cosTheta * cosTheta * (alpha2 - 1.0) + 1.0;\n return alpha2 / (PI * e * e);\n}\n\nfloat trowbridgeReitzLambda(float cosTheta, float alpha2) {\n float cos2Theta = cosTheta * cosTheta;\n float tan2Theta = (1.0 - cos2Theta) / cos2Theta;\n return 0.5 * (-1.0 + sqrt(1.0 + alpha2 * tan2Theta));\n}\n\n// An implementation of Disney's principled BRDF\n// https://disney-animation.s3.amazonaws.com/library/s2012_pbs_disney_brdf_notes_v2.pdf\nvec3 materialBrdf(SurfaceInteraction si, vec3 viewDir, vec3 lightDir, float cosThetaL, float diffuseWeight, out float pdf) {\n vec3 halfVector = normalize(viewDir + lightDir);\n\n cosThetaL = abs(cosThetaL);\n float cosThetaV = abs(dot(si.normal, viewDir));\n float cosThetaH = abs(dot(si.normal, halfVector));\n float cosThetaD = abs(dot(lightDir, halfVector));\n\n float alpha2 = (si.roughness * si.roughness) * (si.roughness * si.roughness);\n\n float F = fresnelSchlick(cosThetaD, mix(R0, 0.6, si.metalness));\n float D = trowbridgeReitzD(cosThetaH, alpha2);\n\n float roughnessRemapped = 0.5 + 0.5 * si.roughness;\n float alpha2Remapped = (roughnessRemapped * roughnessRemapped) * (roughnessRemapped * roughnessRemapped);\n\n float G = 1.0 / (1.0 + trowbridgeReitzLambda(cosThetaV, alpha2Remapped) + trowbridgeReitzLambda(cosThetaL, alpha2Remapped));\n\n float specular = F * D * G / (4.0 * cosThetaV * cosThetaL);\n float specularPdf = D * cosThetaH / (4.0 * cosThetaD);\n\n float f = -0.5 + 2.0 * cosThetaD * cosThetaD * si.roughness;\n float diffuse = diffuseWeight * INVPI * (1.0 + f * fresnelSchlickWeight(cosThetaL)) * (1.0 + f * fresnelSchlickWeight(cosThetaV));\n float diffusePdf = cosThetaL * INVPI;\n\n pdf = mix(0.5 * (specularPdf + diffusePdf), specularPdf, si.metalness);\n\n return mix(si.color * diffuse + specular, si.color * specular, si.metalness);\n}\n\n"; @@ -2392,14 +2392,14 @@ // Estimate the direct lighting integral using multiple importance sampling // http://www.pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection/Direct_Lighting.html#EstimatingtheDirectLightingIntegral - var sampleMaterial = "\n\nvoid sampleMaterial(SurfaceInteraction si, int bounce, inout Path path) {\n bool lastBounce = bounce == BOUNCES;\n mat3 basis = orthonormalBasis(si.normal);\n vec3 viewDir = -path.ray.d;\n\n MaterialSamples samples = getRandomMaterialSamples();\n\n vec2 diffuseOrSpecular = samples.s1;\n vec2 lightDirSample = samples.s2;\n vec2 bounceDirSample = samples.s3;\n\n // Step 1: Add direct illumination of the light source (the hdr map)\n // On every bounce but the last, importance sample the light source\n // On the last bounce, multiple importance sample the brdf AND the light source, determined by random var\n\n vec3 lightDir;\n vec2 uv;\n float lightPdf;\n bool brdfSample = false;\n\n if (lastBounce && diffuseOrSpecular.x < 0.5) {\n // reuse this sample by multiplying by 2 to bring sample from [0, 0.5), to [0, 1)\n lightDir = 2.0 * diffuseOrSpecular.x < mix(0.5, 0.0, si.metalness) ?\n lightDirDiffuse(si.faceNormal, viewDir, basis, lightDirSample) :\n lightDirSpecular(si.faceNormal, viewDir, basis, si.roughness, lightDirSample);\n\n uv = cartesianToEquirect(lightDir);\n lightPdf = envmapPdf(uv);\n brdfSample = true;\n } else {\n lightDir = sampleEnvmap(lightDirSample, uv, lightPdf);\n }\n\n float cosThetaL = dot(si.normal, lightDir);\n\n float occluded = 1.0;\n\n float orientation = dot(si.faceNormal, viewDir) * cosThetaL;\n if (orientation < 0.0) {\n // light dir points towards surface. invalid dir.\n occluded = 0.0;\n }\n\n float diffuseWeight = 1.0;\n\n initRay(path.ray, si.position + EPS * lightDir, lightDir);\n if (intersectSceneShadow(path.ray)) {\n if (lastBounce) {\n diffuseWeight = 0.0;\n } else {\n occluded = 0.0;\n }\n }\n\n vec3 irr = textureLinear(envmap, uv).rgb;\n\n float scatteringPdf;\n vec3 brdf = materialBrdf(si, viewDir, lightDir, cosThetaL, diffuseWeight, scatteringPdf);\n\n float weight;\n if (lastBounce) {\n weight = brdfSample ?\n 2.0 * powerHeuristic(scatteringPdf, lightPdf) / scatteringPdf :\n 2.0 * powerHeuristic(lightPdf, scatteringPdf) / lightPdf;\n } else {\n weight = powerHeuristic(lightPdf, scatteringPdf) / lightPdf;\n }\n\n path.li += path.beta * occluded * brdf * irr * abs(cosThetaL) * weight;;\n\n // Step 2: Setup ray direction for next bounce by importance sampling the BRDF\n\n if (lastBounce) {\n return;\n }\n\n lightDir = diffuseOrSpecular.y < mix(0.5, 0.0, si.metalness) ?\n lightDirDiffuse(si.faceNormal, viewDir, basis, bounceDirSample) :\n lightDirSpecular(si.faceNormal, viewDir, basis, si.roughness, bounceDirSample);\n\n cosThetaL = dot(si.normal, lightDir);\n\n orientation = dot(si.faceNormal, viewDir) * cosThetaL;\n path.abort = orientation < 0.0;\n\n if (path.abort) {\n return;\n }\n\n brdf = materialBrdf(si, viewDir, lightDir, cosThetaL, 1.0, scatteringPdf);\n\n uv = cartesianToEquirect(lightDir);\n lightPdf = envmapPdf(uv);\n\n path.misWeight = powerHeuristic(scatteringPdf, lightPdf);\n\n path.beta *= abs(cosThetaL) * brdf / scatteringPdf;\n\n path.specularBounce = false;\n\n initRay(path.ray, si.position + EPS * lightDir, lightDir);\n}\n"; + var sampleMaterial = "\n\nvoid sampleMaterial(SurfaceInteraction si, int bounce, inout Path path) {\n bool lastBounce = bounce == BOUNCES;\n mat3 basis = orthonormalBasis(si.normal);\n vec3 viewDir = -path.ray.d;\n\n MaterialSamples samples = getRandomMaterialSamples();\n\n vec2 diffuseOrSpecular = samples.s1;\n vec2 lightDirSample = samples.s2;\n vec2 bounceDirSample = samples.s3;\n\n // Step 1: Add direct illumination of the light source (the hdr map)\n // On every bounce but the last, importance sample the light source\n // On the last bounce, multiple importance sample the brdf AND the light source, determined by random var\n\n vec3 lightDir;\n vec2 uv;\n float lightPdf;\n bool brdfSample = false;\n\n if (lastBounce && diffuseOrSpecular.x < 0.5) {\n // reuse this sample by multiplying by 2 to bring sample from [0, 0.5), to [0, 1)\n lightDir = 2.0 * diffuseOrSpecular.x < mix(0.5, 0.0, si.metalness) ?\n lightDirDiffuse(si.faceNormal, viewDir, basis, lightDirSample) :\n lightDirSpecular(si.faceNormal, viewDir, basis, si.roughness, lightDirSample);\n\n uv = cartesianToEquirect(lightDir);\n lightPdf = envMapPdf(uv);\n brdfSample = true;\n } else {\n lightDir = sampleEnvmap(lightDirSample, uv, lightPdf);\n }\n\n float cosThetaL = dot(si.normal, lightDir);\n\n float occluded = 1.0;\n\n float orientation = dot(si.faceNormal, viewDir) * cosThetaL;\n if (orientation < 0.0) {\n // light dir points towards surface. invalid dir.\n occluded = 0.0;\n }\n\n float diffuseWeight = 1.0;\n\n initRay(path.ray, si.position + EPS * lightDir, lightDir);\n if (intersectSceneShadow(path.ray)) {\n if (lastBounce) {\n diffuseWeight = 0.0;\n } else {\n occluded = 0.0;\n }\n }\n\n vec3 irr = textureLinear(envMap, uv).rgb;\n\n float scatteringPdf;\n vec3 brdf = materialBrdf(si, viewDir, lightDir, cosThetaL, diffuseWeight, scatteringPdf);\n\n float weight;\n if (lastBounce) {\n weight = brdfSample ?\n 2.0 * powerHeuristic(scatteringPdf, lightPdf) / scatteringPdf :\n 2.0 * powerHeuristic(lightPdf, scatteringPdf) / lightPdf;\n } else {\n weight = powerHeuristic(lightPdf, scatteringPdf) / lightPdf;\n }\n\n path.li += path.beta * occluded * brdf * irr * abs(cosThetaL) * weight;;\n\n // Step 2: Setup ray direction for next bounce by importance sampling the BRDF\n\n if (lastBounce) {\n return;\n }\n\n lightDir = diffuseOrSpecular.y < mix(0.5, 0.0, si.metalness) ?\n lightDirDiffuse(si.faceNormal, viewDir, basis, bounceDirSample) :\n lightDirSpecular(si.faceNormal, viewDir, basis, si.roughness, bounceDirSample);\n\n cosThetaL = dot(si.normal, lightDir);\n\n orientation = dot(si.faceNormal, viewDir) * cosThetaL;\n path.abort = orientation < 0.0;\n\n if (path.abort) {\n return;\n }\n\n brdf = materialBrdf(si, viewDir, lightDir, cosThetaL, 1.0, scatteringPdf);\n\n uv = cartesianToEquirect(lightDir);\n lightPdf = envMapPdf(uv);\n\n path.misWeight = powerHeuristic(scatteringPdf, lightPdf);\n\n path.beta *= abs(cosThetaL) * brdf / scatteringPdf;\n\n path.specularBounce = false;\n\n initRay(path.ray, si.position + EPS * lightDir, lightDir);\n}\n"; - var sampleShadowCatcher = "\n\n#ifdef USE_SHADOW_CATCHER\n\nvoid sampleShadowCatcher(SurfaceInteraction si, int bounce, inout Path path) {\n bool lastBounce = bounce == BOUNCES;\n mat3 basis = orthonormalBasis(si.normal);\n vec3 viewDir = -path.ray.d;\n vec3 color = bounce == 1 || path.specularBounce ? sampleBackgroundFromDirection(-viewDir) : sampleEnvmapFromDirection(-viewDir);\n\n si.color = vec3(1, 1, 1);\n\n MaterialSamples samples = getRandomMaterialSamples();\n\n vec2 diffuseOrSpecular = samples.s1;\n vec2 lightDirSample = samples.s2;\n vec2 bounceDirSample = samples.s3;\n\n vec3 lightDir;\n vec2 uv;\n float lightPdf;\n bool brdfSample = false;\n\n if (diffuseOrSpecular.x < 0.5) {\n lightDir = 2.0 * diffuseOrSpecular.x < mix(0.5, 0.0, si.metalness) ?\n lightDirDiffuse(si.faceNormal, viewDir, basis, lightDirSample) :\n lightDirSpecular(si.faceNormal, viewDir, basis, si.roughness, lightDirSample);\n uv = cartesianToEquirect(lightDir);\n lightPdf = envmapPdf(uv);\n brdfSample = true;\n } else {\n lightDir = sampleEnvmap(lightDirSample, uv, lightPdf);\n }\n\n float cosThetaL = dot(si.normal, lightDir);\n\n float liContrib = 1.0;\n\n float orientation = dot(si.faceNormal, viewDir) * cosThetaL;\n if (orientation < 0.0) {\n liContrib = 0.0;\n }\n\n float occluded = 1.0;\n initRay(path.ray, si.position + EPS * lightDir, lightDir);\n if (intersectSceneShadow(path.ray)) {\n occluded = 0.0;\n }\n\n float irr = dot(luminance, textureLinear(envmap, uv).rgb);\n\n float scatteringPdf;\n vec3 brdf = materialBrdf(si, viewDir, lightDir, cosThetaL, 1.0, scatteringPdf);\n\n float weight = brdfSample ?\n 2.0 * powerHeuristic(scatteringPdf, lightPdf) / scatteringPdf :\n 2.0 * powerHeuristic(lightPdf, scatteringPdf) / lightPdf;\n\n float liEq = liContrib * brdf.r * irr * abs(cosThetaL) * weight;\n\n float alpha = liEq;\n path.alpha *= alpha;\n path.li *= alpha;\n\n path.li += occluded * path.beta * color * liEq;\n\n if (lastBounce) {\n return;\n }\n\n lightDir = diffuseOrSpecular.y < mix(0.5, 0.0, si.metalness) ?\n lightDirDiffuse(si.faceNormal, viewDir, basis, bounceDirSample) :\n lightDirSpecular(si.faceNormal, viewDir, basis, si.roughness, bounceDirSample);\n\n cosThetaL = dot(si.normal, lightDir);\n\n orientation = dot(si.faceNormal, viewDir) * cosThetaL;\n path.abort = orientation < 0.0;\n\n if (path.abort) {\n return;\n }\n\n brdf = materialBrdf(si, viewDir, lightDir, cosThetaL, 1.0, scatteringPdf);\n\n uv = cartesianToEquirect(lightDir);\n lightPdf = envmapPdf(uv);\n\n path.misWeight = 0.0;\n\n path.beta = color * abs(cosThetaL) * brdf.r / scatteringPdf;\n\n path.specularBounce = false;\n\n initRay(path.ray, si.position + EPS * lightDir, lightDir);\n}\n\n#endif\n\n"; + var sampleShadowCatcher = "\n\n#ifdef USE_SHADOW_CATCHER\n\nvoid sampleShadowCatcher(SurfaceInteraction si, int bounce, inout Path path) {\n bool lastBounce = bounce == BOUNCES;\n mat3 basis = orthonormalBasis(si.normal);\n vec3 viewDir = -path.ray.d;\n vec3 color = bounce == 1 || path.specularBounce ? sampleBackgroundFromDirection(-viewDir) : sampleEnvmapFromDirection(-viewDir);\n\n si.color = vec3(1, 1, 1);\n\n MaterialSamples samples = getRandomMaterialSamples();\n\n vec2 diffuseOrSpecular = samples.s1;\n vec2 lightDirSample = samples.s2;\n vec2 bounceDirSample = samples.s3;\n\n vec3 lightDir;\n vec2 uv;\n float lightPdf;\n bool brdfSample = false;\n\n if (diffuseOrSpecular.x < 0.5) {\n lightDir = 2.0 * diffuseOrSpecular.x < mix(0.5, 0.0, si.metalness) ?\n lightDirDiffuse(si.faceNormal, viewDir, basis, lightDirSample) :\n lightDirSpecular(si.faceNormal, viewDir, basis, si.roughness, lightDirSample);\n uv = cartesianToEquirect(lightDir);\n lightPdf = envMapPdf(uv);\n brdfSample = true;\n } else {\n lightDir = sampleEnvmap(lightDirSample, uv, lightPdf);\n }\n\n float cosThetaL = dot(si.normal, lightDir);\n\n float liContrib = 1.0;\n\n float orientation = dot(si.faceNormal, viewDir) * cosThetaL;\n if (orientation < 0.0) {\n liContrib = 0.0;\n }\n\n float occluded = 1.0;\n initRay(path.ray, si.position + EPS * lightDir, lightDir);\n if (intersectSceneShadow(path.ray)) {\n occluded = 0.0;\n }\n\n float irr = dot(luminance, textureLinear(envMap, uv).rgb);\n\n float scatteringPdf;\n vec3 brdf = materialBrdf(si, viewDir, lightDir, cosThetaL, 1.0, scatteringPdf);\n\n float weight = brdfSample ?\n 2.0 * powerHeuristic(scatteringPdf, lightPdf) / scatteringPdf :\n 2.0 * powerHeuristic(lightPdf, scatteringPdf) / lightPdf;\n\n float liEq = liContrib * brdf.r * irr * abs(cosThetaL) * weight;\n\n float alpha = liEq;\n path.alpha *= alpha;\n path.li *= alpha;\n\n path.li += occluded * path.beta * color * liEq;\n\n if (lastBounce) {\n return;\n }\n\n lightDir = diffuseOrSpecular.y < mix(0.5, 0.0, si.metalness) ?\n lightDirDiffuse(si.faceNormal, viewDir, basis, bounceDirSample) :\n lightDirSpecular(si.faceNormal, viewDir, basis, si.roughness, bounceDirSample);\n\n cosThetaL = dot(si.normal, lightDir);\n\n orientation = dot(si.faceNormal, viewDir) * cosThetaL;\n path.abort = orientation < 0.0;\n\n if (path.abort) {\n return;\n }\n\n brdf = materialBrdf(si, viewDir, lightDir, cosThetaL, 1.0, scatteringPdf);\n\n uv = cartesianToEquirect(lightDir);\n lightPdf = envMapPdf(uv);\n\n path.misWeight = 0.0;\n\n path.beta = color * abs(cosThetaL) * brdf.r / scatteringPdf;\n\n path.specularBounce = false;\n\n initRay(path.ray, si.position + EPS * lightDir, lightDir);\n}\n\n#endif\n\n"; var sampleGlass = "\n\n#ifdef USE_GLASS\n\nvoid sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Path path) {\n bool lastBounce = bounce == BOUNCES;\n vec3 viewDir = -path.ray.d;\n float cosTheta = dot(si.normal, viewDir);\n\n MaterialSamples samples = getRandomMaterialSamples();\n\n float reflectionOrRefraction = samples.s1.x;\n\n float F = si.materialType == THIN_GLASS ?\n fresnelSchlick(abs(cosTheta), R0) : // thin glass\n fresnelSchlickTIR(cosTheta, R0, IOR); // thick glass\n\n vec3 lightDir;\n\n if (reflectionOrRefraction < F) {\n lightDir = reflect(-viewDir, si.normal);\n } else {\n lightDir = si.materialType == THIN_GLASS ?\n refract(-viewDir, sign(cosTheta) * si.normal, INV_IOR_THIN) : // thin glass\n refract(-viewDir, sign(cosTheta) * si.normal, cosTheta < 0.0 ? IOR : INV_IOR); // thick glass\n path.beta *= si.color;\n }\n\n path.misWeight = 1.0;\n\n initRay(path.ray, si.position + EPS * lightDir, lightDir);\n\n path.li += lastBounce ? path.beta * sampleBackgroundFromDirection(lightDir) : vec3(0.0);\n\n path.specularBounce = true;\n}\n\n#endif\n\n"; var fragment$1 = { - includes: [constants$1, rayTraceCore, textureLinear, materialBuffer, intersect, surfaceInteractionDirect, random, envmap, bsdf, sample, sampleMaterial, sampleGlass, sampleShadowCatcher], + includes: [constants$1, rayTraceCore, textureLinear, materialBuffer, intersect, surfaceInteractionDirect, random, envMap, bsdf, sample, sampleMaterial, sampleGlass, sampleShadowCatcher], outputs: ['light'], source: function source(defines) { return "\n void bounce(inout Path path, int i, inout SurfaceInteraction si) {\n\n if (!si.hit) {\n vec3 irr = path.specularBounce ? sampleBackgroundFromDirection(path.ray.d) : sampleEnvmapFromDirection(path.ray.d);\n\n // hit a light source (the hdr map)\n // add contribution from light source\n // path.misWeight is the multiple importance sampled weight of this light source\n path.li += path.misWeight * path.beta * irr;\n path.abort = true;\n return;\n }\n\n #ifdef USE_GLASS\n if (si.materialType == THIN_GLASS || si.materialType == THICK_GLASS) {\n sampleGlassSpecular(si, i, path);\n }\n #endif\n #ifdef USE_SHADOW_CATCHER\n if (si.materialType == SHADOW_CATCHER) {\n sampleShadowCatcher(si, i, path);\n }\n #endif\n if (si.materialType == STANDARD) {\n sampleMaterial(si, i, path);\n }\n\n // Russian Roulette sampling\n if (i >= 2) {\n float q = 1.0 - dot(path.beta, luminance);\n if (randomSample() < q) {\n path.abort = true;\n }\n path.beta /= 1.0 - q;\n }\n\n }\n\n // Path tracing integrator as described in\n // http://www.pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection/Path_Tracing.html#\n vec4 integrator(inout Ray ray) {\n Path path;\n path.ray = ray;\n path.li = vec3(0);\n path.alpha = 1.0;\n path.beta = vec3(1.0);\n path.specularBounce = true;\n path.abort = false;\n path.misWeight = 1.0;\n\n SurfaceInteraction si;\n\n // first surface interaction from g-buffer\n surfaceInteractionDirect(vCoord, si);\n\n // first surface interaction from ray interesction\n // intersectScene(path.ray, si);\n\n bounce(path, 1, si);\n\n // Manually unroll for loop.\n // Some hardware fails to iterate over a GLSL loop, so we provide this workaround\n // for (int i = 1; i < defines.bounces + 1, i += 1)\n // equivelant to\n ".concat(unrollLoop('i', 2, defines.BOUNCES + 1, 1, "\n if (path.abort) {\n return vec4(path.li, path.alpha);\n }\n intersectScene(path.ray, si);\n bounce(path, i, si);\n "), "\n\n return vec4(path.li, path.alpha);\n }\n\n void main() {\n initRandom();\n\n vec2 vCoordAntiAlias = vCoord + jitter;\n\n vec3 direction = normalize(vec3(vCoordAntiAlias - 0.5, -1.0) * vec3(camera.aspect, 1.0, camera.fov));\n\n // Thin lens model with depth-of-field\n // http://www.pbr-book.org/3ed-2018/Camera_Models/Projective_Camera_Models.html#TheThinLensModelandDepthofField\n // vec2 lensPoint = camera.aperture * sampleCircle(randomSampleVec2());\n // vec3 focusPoint = -direction * camera.focus / direction.z; // intersect ray direction with focus plane\n\n // vec3 origin = vec3(lensPoint, 0.0);\n // direction = normalize(focusPoint - origin);\n\n // origin = vec3(camera.transform * vec4(origin, 1.0));\n // direction = mat3(camera.transform) * direction;\n\n vec3 origin = camera.transform[3].xyz;\n direction = mat3(camera.transform) * direction;\n\n Ray cam;\n initRay(cam, origin, direction);\n\n vec4 liAndAlpha = integrator(cam);\n\n if (!(liAndAlpha.x < INF && liAndAlpha.x > -EPS)) {\n liAndAlpha = vec4(0, 0, 0, 1);\n }\n\n out_light = liAndAlpha;\n\n // Stratified Sampling Sample Count Test\n // ---------------\n // Uncomment the following code\n // Then observe the colors of the image\n // If:\n // * The resulting image is pure black\n // Extra samples are being passed to the shader that aren't being used.\n // * The resulting image contains red\n // Not enough samples are being passed to the shader\n // * The resulting image contains only white with some black\n // All samples are used by the shader. Correct result!\n\n // out_light = vec4(0, 0, 0, 1);\n // if (sampleIndex == SAMPLING_DIMENSIONS) {\n // out_light = vec4(1, 1, 1, 1);\n // } else if (sampleIndex > SAMPLING_DIMENSIONS) {\n // out_light = vec4(1, 0, 0, 1);\n // }\n}\n"); @@ -2587,7 +2587,7 @@ function setNoise(noiseImage) { - renderPass.setTexture('noise', makeTexture(gl, { + renderPass.setTexture('noiseTex', makeTexture(gl, { data: noiseImage, wrapS: gl.REPEAT, wrapT: gl.REPEAT, @@ -2705,10 +2705,10 @@ renderPass.setTexture('diffuseMap', materialBuffer.textures.diffuseMap); renderPass.setTexture('normalMap', materialBuffer.textures.normalMap); renderPass.setTexture('pbrMap', materialBuffer.textures.pbrMap); - renderPass.setTexture('positions', makeDataTexture(gl, geometry.getAttribute('position').array, 3)); - renderPass.setTexture('normals', makeDataTexture(gl, geometry.getAttribute('normal').array, 3)); - renderPass.setTexture('uvs', makeDataTexture(gl, geometry.getAttribute('uv').array, 2)); - renderPass.setTexture('bvh', makeDataTexture(gl, flattenedBvh.buffer, 4)); + renderPass.setTexture('positionBuffer', makeDataTexture(gl, geometry.getAttribute('position').array, 3)); + renderPass.setTexture('normalBuffer', makeDataTexture(gl, geometry.getAttribute('normal').array, 3)); + renderPass.setTexture('uvBuffer', makeDataTexture(gl, geometry.getAttribute('uv').array, 2)); + renderPass.setTexture('bvhBuffer', makeDataTexture(gl, flattenedBvh.buffer, 4)); var envImage = generateEnvMapFromSceneComponents(directionalLights, ambientLights, environmentLights); var envImageTextureObject = makeTexture(gl, { data: envImage.data, @@ -2718,7 +2718,7 @@ width: envImage.width, height: envImage.height }); - renderPass.setTexture('envmap', envImageTextureObject); + renderPass.setTexture('envMap', envImageTextureObject); var backgroundImageTextureObject; if (background) { @@ -2736,8 +2736,8 @@ } renderPass.setTexture('backgroundMap', backgroundImageTextureObject); - var distribution = envmapDistribution(envImage); - renderPass.setTexture('envmapDistribution', makeTexture(gl, { + var distribution = envMapDistribution(envImage); + renderPass.setTexture('envMapDistribution', makeTexture(gl, { data: distribution.data, storage: 'halfFloat', width: distribution.width, @@ -2840,7 +2840,7 @@ var fragment$2 = { outputs: ['light'], includes: [textureLinear], - source: "\n in vec2 vCoord;\n\n uniform mediump sampler2D light;\n uniform mediump sampler2D position;\n uniform vec2 lightScale;\n uniform vec2 previousLightScale;\n\n uniform mediump sampler2D previousLight;\n uniform mediump sampler2D previousPosition;\n\n uniform mat4 historyCamera;\n uniform float blendAmount;\n uniform vec2 jitter;\n\n vec2 reproject(vec3 position) {\n vec4 historyCoord = historyCamera * vec4(position, 1.0);\n return 0.5 * historyCoord.xy / historyCoord.w + 0.5;\n }\n\n float getMeshId(sampler2D meshIdTex, vec2 vCoord) {\n return floor(texture(meshIdTex, vCoord).w);\n }\n\n void main() {\n vec3 currentPosition = textureLinear(position, vCoord).xyz;\n float currentMeshId = getMeshId(position, vCoord);\n\n vec4 currentLight = texture(light, lightScale * vCoord);\n\n if (currentMeshId == 0.0) {\n out_light = currentLight;\n return;\n }\n\n vec2 hCoord = reproject(currentPosition) - jitter;\n\n vec2 hSizef = previousLightScale * vec2(textureSize(previousLight, 0));\n vec2 hSizeInv = 1.0 / hSizef;\n ivec2 hSize = ivec2(hSizef);\n\n vec2 hTexelf = hCoord * hSizef - 0.5;\n ivec2 hTexel = ivec2(hTexelf);\n vec2 f = fract(hTexelf);\n\n ivec2 texel[] = ivec2[](\n hTexel + ivec2(0, 0),\n hTexel + ivec2(1, 0),\n hTexel + ivec2(0, 1),\n hTexel + ivec2(1, 1)\n );\n\n float weights[] = float[](\n (1.0 - f.x) * (1.0 - f.y),\n f.x * (1.0 - f.y),\n (1.0 - f.x) * f.y,\n f.x * f.y\n );\n\n vec4 history;\n float sum;\n\n // bilinear sampling, rejecting samples that don't have a matching mesh id\n for (int i = 0; i < 4; i++) {\n vec2 gCoord = (vec2(texel[i]) + 0.5) * hSizeInv;\n\n float histMeshId = getMeshId(previousPosition, gCoord);\n\n float isValid = histMeshId != currentMeshId || any(greaterThanEqual(texel[i], hSize)) ? 0.0 : 1.0;\n\n float weight = isValid * weights[i];\n history += weight * texelFetch(previousLight, texel[i], 0);\n sum += weight;\n }\n\n if (sum > 0.0) {\n history /= sum;\n } else {\n // If all samples of bilinear fail, try a 3x3 box filter\n hTexel = ivec2(hTexelf + 0.5);\n\n for (int x = -1; x <= 1; x++) {\n for (int y = -1; y <= 1; y++) {\n ivec2 texel = hTexel + ivec2(x, y);\n vec2 gCoord = (vec2(texel) + 0.5) * hSizeInv;\n\n float histMeshId = getMeshId(previousPosition, gCoord);\n\n float isValid = histMeshId != currentMeshId || any(greaterThanEqual(texel, hSize)) ? 0.0 : 1.0;\n\n float weight = isValid;\n vec4 h = texelFetch(previousLight, texel, 0);\n history += weight * h;\n sum += weight;\n }\n }\n history = sum > 0.0 ? history / sum : history;\n }\n\n if (history.w > MAX_SAMPLES) {\n history.xyz *= MAX_SAMPLES / history.w;\n history.w = MAX_SAMPLES;\n }\n\n out_light = blendAmount * history + currentLight;\n }\n" + source: "\n in vec2 vCoord;\n\n uniform mediump sampler2D lightTex;\n uniform mediump sampler2D positionTex;\n uniform vec2 lightScale;\n uniform vec2 previousLightScale;\n\n uniform mediump sampler2D previousLightTex;\n uniform mediump sampler2D previousPositionTex;\n\n uniform mat4 historyCamera;\n uniform float blendAmount;\n uniform vec2 jitter;\n\n vec2 reproject(vec3 position) {\n vec4 historyCoord = historyCamera * vec4(position, 1.0);\n return 0.5 * historyCoord.xy / historyCoord.w + 0.5;\n }\n\n float getMeshId(sampler2D meshIdTex, vec2 vCoord) {\n return floor(texture(meshIdTex, vCoord).w);\n }\n\n void main() {\n vec3 currentPosition = textureLinear(positionTex, vCoord).xyz;\n float currentMeshId = getMeshId(positionTex, vCoord);\n\n vec4 currentLight = texture(lightTex, lightScale * vCoord);\n\n if (currentMeshId == 0.0) {\n out_light = currentLight;\n return;\n }\n\n vec2 hCoord = reproject(currentPosition) - jitter;\n\n vec2 hSizef = previousLightScale * vec2(textureSize(previousLightTex, 0));\n vec2 hSizeInv = 1.0 / hSizef;\n ivec2 hSize = ivec2(hSizef);\n\n vec2 hTexelf = hCoord * hSizef - 0.5;\n ivec2 hTexel = ivec2(hTexelf);\n vec2 f = fract(hTexelf);\n\n ivec2 texel[] = ivec2[](\n hTexel + ivec2(0, 0),\n hTexel + ivec2(1, 0),\n hTexel + ivec2(0, 1),\n hTexel + ivec2(1, 1)\n );\n\n float weights[] = float[](\n (1.0 - f.x) * (1.0 - f.y),\n f.x * (1.0 - f.y),\n (1.0 - f.x) * f.y,\n f.x * f.y\n );\n\n vec4 history;\n float sum;\n\n // bilinear sampling, rejecting samples that don't have a matching mesh id\n for (int i = 0; i < 4; i++) {\n vec2 gCoord = (vec2(texel[i]) + 0.5) * hSizeInv;\n\n float histMeshId = getMeshId(previousPositionTex, gCoord);\n\n float isValid = histMeshId != currentMeshId || any(greaterThanEqual(texel[i], hSize)) ? 0.0 : 1.0;\n\n float weight = isValid * weights[i];\n history += weight * texelFetch(previousLightTex, texel[i], 0);\n sum += weight;\n }\n\n if (sum > 0.0) {\n history /= sum;\n } else {\n // If all samples of bilinear fail, try a 3x3 box filter\n hTexel = ivec2(hTexelf + 0.5);\n\n for (int x = -1; x <= 1; x++) {\n for (int y = -1; y <= 1; y++) {\n ivec2 texel = hTexel + ivec2(x, y);\n vec2 gCoord = (vec2(texel) + 0.5) * hSizeInv;\n\n float histMeshId = getMeshId(previousPositionTex, gCoord);\n\n float isValid = histMeshId != currentMeshId || any(greaterThanEqual(texel, hSize)) ? 0.0 : 1.0;\n\n float weight = isValid;\n vec4 h = texelFetch(previousLightTex, texel, 0);\n history += weight * h;\n sum += weight;\n }\n }\n history = sum > 0.0 ? history / sum : history;\n }\n\n if (history.w > MAX_SAMPLES) {\n history.xyz *= MAX_SAMPLES / history.w;\n history.w = MAX_SAMPLES;\n }\n\n out_light = blendAmount * history + currentLight;\n }\n" }; function makeReprojectPass(gl, params) { @@ -2875,10 +2875,10 @@ renderPass.setUniform('blendAmount', blendAmount); renderPass.setUniform('lightScale', lightScale.x, lightScale.y); renderPass.setUniform('previousLightScale', previousLightScale.x, previousLightScale.y); - renderPass.setTexture('light', light); - renderPass.setTexture('position', position); - renderPass.setTexture('previousLight', previousLight); - renderPass.setTexture('previousPosition', previousPosition); + renderPass.setTexture('lightTex', light); + renderPass.setTexture('positionTex', position); + renderPass.setTexture('previousLightTex', previousLight); + renderPass.setTexture('previousPositionTex', previousPosition); renderPass.useProgram(); fullscreenQuad.draw(); } @@ -2893,7 +2893,7 @@ var fragment$3 = { includes: [textureLinear], outputs: ['color'], - source: "\n in vec2 vCoord;\n\n uniform sampler2D light;\n uniform sampler2D position;\n\n uniform vec2 lightScale;\n\n // Tonemapping functions from THREE.js\n\n vec3 linear(vec3 color) {\n return color;\n }\n // https://www.cs.utah.edu/~reinhard/cdrom/\n vec3 reinhard(vec3 color) {\n return clamp(color / (vec3(1.0) + color), vec3(0.0), vec3(1.0));\n }\n // http://filmicworlds.com/blog/filmic-tonemapping-operators/\n #define uncharted2Helper(x) max(((x * (0.15 * x + 0.10 * 0.50) + 0.20 * 0.02) / (x * (0.15 * x + 0.50) + 0.20 * 0.30)) - 0.02 / 0.30, vec3(0.0))\n const vec3 uncharted2WhitePoint = 1.0 / uncharted2Helper(vec3(WHITE_POINT));\n vec3 uncharted2( vec3 color ) {\n // John Hable's filmic operator from Uncharted 2 video game\n return clamp(uncharted2Helper(color) * uncharted2WhitePoint, vec3(0.0), vec3(1.0));\n }\n // http://filmicworlds.com/blog/filmic-tonemapping-operators/\n vec3 cineon( vec3 color ) {\n // optimized filmic operator by Jim Hejl and Richard Burgess-Dawson\n color = max(vec3( 0.0 ), color - 0.004);\n return pow((color * (6.2 * color + 0.5)) / (color * (6.2 * color + 1.7) + 0.06), vec3(2.2));\n }\n // https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/\n vec3 acesFilmic( vec3 color ) {\n return clamp((color * (2.51 * color + 0.03)) / (color * (2.43 * color + 0.59) + 0.14), vec3(0.0), vec3(1.0));\n }\n\n #ifdef EDGE_PRESERVING_UPSCALE\n\n float getMeshId(sampler2D meshIdTex, vec2 vCoord) {\n return floor(texture(meshIdTex, vCoord).w);\n }\n\n vec4 getUpscaledLight(vec2 coord) {\n float meshId = getMeshId(position, coord);\n\n vec2 sizef = lightScale * vec2(textureSize(position, 0));\n vec2 texelf = coord * sizef - 0.5;\n ivec2 texel = ivec2(texelf);\n vec2 f = fract(texelf);\n\n ivec2 texels[] = ivec2[](\n texel + ivec2(0, 0),\n texel + ivec2(1, 0),\n texel + ivec2(0, 1),\n texel + ivec2(1, 1)\n );\n\n float weights[] = float[](\n (1.0 - f.x) * (1.0 - f.y),\n f.x * (1.0 - f.y),\n (1.0 - f.x) * f.y,\n f.x * f.y\n );\n\n vec4 upscaledLight;\n float sum;\n for (int i = 0; i < 4; i++) {\n vec2 pCoord = (vec2(texels[i]) + 0.5) / sizef;\n float isValid = getMeshId(position, pCoord) == meshId ? 1.0 : 0.0;\n float weight = isValid * weights[i];\n upscaledLight += weight * texelFetch(light, texels[i], 0);\n sum += weight;\n }\n\n if (sum > 0.0) {\n upscaledLight /= sum;\n } else {\n upscaledLight = texture(light, lightScale * coord);\n }\n\n return upscaledLight;\n }\n #endif\n\n void main() {\n #ifdef EDGE_PRESERVING_UPSCALE\n vec4 upscaledLight = getUpscaledLight(vCoord);\n #else\n vec4 upscaledLight = texture(light, lightScale * vCoord);\n #endif\n\n // alpha channel stores the number of samples progressively rendered\n // divide the sum of light by alpha to obtain average contribution of light\n\n // in addition, alpha contains a scale factor for the shadow catcher material\n // dividing by alpha normalizes the brightness of the shadow catcher to match the background envmap.\n vec3 light = upscaledLight.rgb / upscaledLight.a;\n\n light *= EXPOSURE;\n\n light = TONE_MAPPING(light);\n\n light = pow(light, vec3(1.0 / 2.2)); // gamma correction\n\n out_color = vec4(light, 1.0);\n }\n" + source: "\n in vec2 vCoord;\n\n uniform sampler2D lightTex;\n uniform sampler2D positionTex;\n\n uniform vec2 lightScale;\n\n // Tonemapping functions from THREE.js\n\n vec3 linear(vec3 color) {\n return color;\n }\n // https://www.cs.utah.edu/~reinhard/cdrom/\n vec3 reinhard(vec3 color) {\n return clamp(color / (vec3(1.0) + color), vec3(0.0), vec3(1.0));\n }\n // http://filmicworlds.com/blog/filmic-tonemapping-operators/\n #define uncharted2Helper(x) max(((x * (0.15 * x + 0.10 * 0.50) + 0.20 * 0.02) / (x * (0.15 * x + 0.50) + 0.20 * 0.30)) - 0.02 / 0.30, vec3(0.0))\n const vec3 uncharted2WhitePoint = 1.0 / uncharted2Helper(vec3(WHITE_POINT));\n vec3 uncharted2( vec3 color ) {\n // John Hable's filmic operator from Uncharted 2 video game\n return clamp(uncharted2Helper(color) * uncharted2WhitePoint, vec3(0.0), vec3(1.0));\n }\n // http://filmicworlds.com/blog/filmic-tonemapping-operators/\n vec3 cineon( vec3 color ) {\n // optimized filmic operator by Jim Hejl and Richard Burgess-Dawson\n color = max(vec3( 0.0 ), color - 0.004);\n return pow((color * (6.2 * color + 0.5)) / (color * (6.2 * color + 1.7) + 0.06), vec3(2.2));\n }\n // https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/\n vec3 acesFilmic( vec3 color ) {\n return clamp((color * (2.51 * color + 0.03)) / (color * (2.43 * color + 0.59) + 0.14), vec3(0.0), vec3(1.0));\n }\n\n #ifdef EDGE_PRESERVING_UPSCALE\n\n float getMeshId(sampler2D meshIdTex, vec2 vCoord) {\n return floor(texture(meshIdTex, vCoord).w);\n }\n\n vec4 getUpscaledLight(vec2 coord) {\n float meshId = getMeshId(positionTex, coord);\n\n vec2 sizef = lightScale * vec2(textureSize(positionTex, 0));\n vec2 texelf = coord * sizef - 0.5;\n ivec2 texel = ivec2(texelf);\n vec2 f = fract(texelf);\n\n ivec2 texels[] = ivec2[](\n texel + ivec2(0, 0),\n texel + ivec2(1, 0),\n texel + ivec2(0, 1),\n texel + ivec2(1, 1)\n );\n\n float weights[] = float[](\n (1.0 - f.x) * (1.0 - f.y),\n f.x * (1.0 - f.y),\n (1.0 - f.x) * f.y,\n f.x * f.y\n );\n\n vec4 upscaledLight;\n float sum;\n for (int i = 0; i < 4; i++) {\n vec2 pCoord = (vec2(texels[i]) + 0.5) / sizef;\n float isValid = getMeshId(positionTex, pCoord) == meshId ? 1.0 : 0.0;\n float weight = isValid * weights[i];\n upscaledLight += weight * texelFetch(lightTex, texels[i], 0);\n sum += weight;\n }\n\n if (sum > 0.0) {\n upscaledLight /= sum;\n } else {\n upscaledLight = texture(lightTex, lightScale * coord);\n }\n\n return upscaledLight;\n }\n #endif\n\n void main() {\n #ifdef EDGE_PRESERVING_UPSCALE\n vec4 upscaledLight = getUpscaledLight(vCoord);\n #else\n vec4 upscaledLight = texture(lightTex, lightScale * vCoord);\n #endif\n\n // alpha channel stores the number of samples progressively rendered\n // divide the sum of light by alpha to obtain average contribution of light\n\n // in addition, alpha contains a scale factor for the shadow catcher material\n // dividing by alpha normalizes the brightness of the shadow catcher to match the background env map.\n vec3 light = upscaledLight.rgb / upscaledLight.a;\n\n light *= EXPOSURE;\n\n light = TONE_MAPPING(light);\n\n light = pow(light, vec3(1.0 / 2.2)); // gamma correction\n\n out_color = vec4(light, 1.0);\n }\n" }; var _toneMapFunctions; @@ -2923,8 +2923,8 @@ position = params.position; var renderPass = lightScale.x !== 1 && lightScale.y !== 1 ? renderPassUpscale : renderPassNative; renderPass.setUniform('lightScale', lightScale.x, lightScale.y); - renderPass.setTexture('light', light); - renderPass.setTexture('position', position); + renderPass.setTexture('lightTex', light); + renderPass.setTexture('positionTex', position); renderPass.useProgram(); fullscreenQuad.draw(); } @@ -3091,6 +3091,7 @@ var frameTime; var elapsedFrameTime; + var sampleTime; var sampleCount = 0; var numPreviewsRendered = 0; var firstFrame = true; @@ -3357,6 +3358,9 @@ // previous rendered image was a preview image clearBuffer(hdrBuffer); reprojectPass.setPreviousCamera(lastCamera); + } else { + sampleRenderedCallback(sampleCount, frameTime - sampleTime || NaN); + sampleTime = frameTime; } updateSeed(screenWidth, screenHeight, true); @@ -3388,8 +3392,6 @@ } else { toneMapToScreen(hdrBuffer.color[0], fullscreenScale); } - - sampleRenderedCallback(sampleCount); } } diff --git a/build/RayTracingRenderer.js b/build/RayTracingRenderer.js index 6d96625..3448312 100644 --- a/build/RayTracingRenderer.js +++ b/build/RayTracingRenderer.js @@ -2040,10 +2040,10 @@ vec3 getMatNormal(int materialIndex, vec2 uv, vec3 normal, vec3 dp1, vec3 dp2, v return target; } - // Create a piecewise 2D cumulative distribution function of light intensity from an envmap + // Create a piecewise 2D cumulative distribution function of light intensity from an env map // http://www.pbr-book.org/3ed-2018/Monte_Carlo_Integration/2D_Sampling_with_Multidimensional_Transformations.html#Piecewise-Constant2DDistributions - function envmapDistribution(image) { + function envMapDistribution(image) { const data = image.data; const cdfImage = { @@ -2228,10 +2228,10 @@ vec4 textureLinear(sampler2D map, vec2 uv) { var intersect = ` -uniform sampler2D positions; -uniform sampler2D normals; -uniform sampler2D uvs; -uniform sampler2D bvh; +uniform sampler2D positionBuffer; +uniform sampler2D normalBuffer; +uniform sampler2D uvBuffer; +uniform sampler2D bvhBuffer; struct Triangle { vec3 p0; @@ -2247,15 +2247,15 @@ void surfaceInteractionFromBVH(inout SurfaceInteraction si, Triangle tri, vec3 b ivec2 i1 = unpackTexel(index.y, VERTEX_COLUMNS); ivec2 i2 = unpackTexel(index.z, VERTEX_COLUMNS); - vec3 n0 = texelFetch(normals, i0, 0).xyz; - vec3 n1 = texelFetch(normals, i1, 0).xyz; - vec3 n2 = texelFetch(normals, i2, 0).xyz; + vec3 n0 = texelFetch(normalBuffer, i0, 0).xyz; + vec3 n1 = texelFetch(normalBuffer, i1, 0).xyz; + vec3 n2 = texelFetch(normalBuffer, i2, 0).xyz; vec3 normal = normalize(barycentric.x * n0 + barycentric.y * n1 + barycentric.z * n2); #if defined(NUM_DIFFUSE_MAPS) || defined(NUM_NORMAL_MAPS) || defined(NUM_PBR_MAPS) - vec2 uv0 = texelFetch(uvs, i0, 0).xy; - vec2 uv1 = texelFetch(uvs, i1, 0).xy; - vec2 uv2 = texelFetch(uvs, i2, 0).xy; + vec2 uv0 = texelFetch(uvBuffer, i0, 0).xy; + vec2 uv1 = texelFetch(uvBuffer, i1, 0).xy; + vec2 uv2 = texelFetch(uvBuffer, i2, 0).xy; vec2 uv = fract(barycentric.x * uv0 + barycentric.y * uv1 + barycentric.z * uv2); #else vec2 uv = vec2(0.0); @@ -2398,8 +2398,8 @@ void intersectScene(inout Ray ray, inout SurfaceInteraction si) { while(stack >= 0) { int i = nodesToVisit[stack--]; - vec4 r1 = fetchData(bvh, i, BVH_COLUMNS); - vec4 r2 = fetchData(bvh, i + 1, BVH_COLUMNS); + vec4 r1 = fetchData(bvhBuffer, i, BVH_COLUMNS); + vec4 r2 = fetchData(bvhBuffer, i + 1, BVH_COLUMNS); int splitAxisOrNumPrimitives = floatBitsToInt(r1.w); @@ -2422,9 +2422,9 @@ void intersectScene(inout Ray ray, inout SurfaceInteraction si) { } else { ivec3 index = floatBitsToInt(r1.xyz); Triangle tri = Triangle( - fetchData(positions, index.x, VERTEX_COLUMNS).xyz, - fetchData(positions, index.y, VERTEX_COLUMNS).xyz, - fetchData(positions, index.z, VERTEX_COLUMNS).xyz + fetchData(positionBuffer, index.x, VERTEX_COLUMNS).xyz, + fetchData(positionBuffer, index.y, VERTEX_COLUMNS).xyz, + fetchData(positionBuffer, index.z, VERTEX_COLUMNS).xyz ); TriangleIntersect hit = intersectTriangle(ray, tri, maxDim, shear); @@ -2464,8 +2464,8 @@ bool intersectSceneShadow(inout Ray ray) { while(stack >= 0) { int i = nodesToVisit[stack--]; - vec4 r1 = fetchData(bvh, i, BVH_COLUMNS); - vec4 r2 = fetchData(bvh, i + 1, BVH_COLUMNS); + vec4 r1 = fetchData(bvhBuffer, i, BVH_COLUMNS); + vec4 r2 = fetchData(bvhBuffer, i + 1, BVH_COLUMNS); int splitAxisOrNumPrimitives = floatBitsToInt(r1.w); @@ -2486,9 +2486,9 @@ bool intersectSceneShadow(inout Ray ray) { } else { ivec3 index = floatBitsToInt(r1.xyz); Triangle tri = Triangle( - fetchData(positions, index.x, VERTEX_COLUMNS).xyz, - fetchData(positions, index.y, VERTEX_COLUMNS).xyz, - fetchData(positions, index.z, VERTEX_COLUMNS).xyz + fetchData(positionBuffer, index.x, VERTEX_COLUMNS).xyz, + fetchData(positionBuffer, index.y, VERTEX_COLUMNS).xyz, + fetchData(positionBuffer, index.z, VERTEX_COLUMNS).xyz ); if (intersectTriangle(ray, tri, maxDim, shear).t > 0.0) { @@ -2538,7 +2538,7 @@ bool intersectSceneShadow(inout Ray ray) { // Noise texture used to generate a different random number for each pixel. // We use blue noise in particular, but any type of noise will work. -uniform sampler2D noise; +uniform sampler2D noiseTex; uniform float stratifiedSamples[SAMPLING_DIMENSIONS]; uniform float strataSize; @@ -2551,10 +2551,10 @@ int sampleIndex = 0; float pixelSeed; void initRandom() { - vec2 noiseSize = vec2(textureSize(noise, 0)); + vec2 noiseSize = vec2(textureSize(noiseTex, 0)); // tile the small noise texture across the entire screen - pixelSeed = texture(noise, vCoord / (pixelSize * noiseSize)).r; + pixelSeed = texture(noiseTex, vCoord / (pixelSize * noiseSize)).r; } float randomSample() { @@ -2590,10 +2590,10 @@ MaterialSamples getRandomMaterialSamples() { // Sample the environment map using a cumulative distribution function as described in // http://www.pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection/Sampling_Light_Sources.html#InfiniteAreaLights - var envmap = ` + var envMap = ` -uniform sampler2D envmap; -uniform sampler2D envmapDistribution; +uniform sampler2D envMap; +uniform sampler2D envMapDistribution; uniform sampler2D backgroundMap; vec2 cartesianToEquirect(vec3 pointOnSphere) { @@ -2603,13 +2603,13 @@ vec2 cartesianToEquirect(vec3 pointOnSphere) { } float getEnvmapV(float u, out int vOffset, out float pdf) { - ivec2 size = textureSize(envmap, 0); + ivec2 size = textureSize(envMap, 0); int left = 0; - int right = size.y + 1; // cdf length is the length of the envmap + 1 + int right = size.y + 1; // cdf length is the length of the env map + 1 while (left < right) { int mid = (left + right) >> 1; - float s = texelFetch(envmapDistribution, ivec2(0, mid), 0).x; + float s = texelFetch(envMapDistribution, ivec2(0, mid), 0).x; if (s <= u) { left = mid + 1; } else { @@ -2618,10 +2618,10 @@ float getEnvmapV(float u, out int vOffset, out float pdf) { } vOffset = left - 1; - // x channel is cumulative distribution of envmap luminance - // y channel is partial probability density of envmap luminance - vec2 s0 = texelFetch(envmapDistribution, ivec2(0, vOffset), 0).xy; - vec2 s1 = texelFetch(envmapDistribution, ivec2(0, vOffset + 1), 0).xy; + // x channel is cumulative distribution of env map luminance + // y channel is partial probability density of env map luminance + vec2 s0 = texelFetch(envMapDistribution, ivec2(0, vOffset), 0).xy; + vec2 s1 = texelFetch(envMapDistribution, ivec2(0, vOffset + 1), 0).xy; pdf = s0.y; @@ -2629,13 +2629,13 @@ float getEnvmapV(float u, out int vOffset, out float pdf) { } float getEnvmapU(float u, int vOffset, out float pdf) { - ivec2 size = textureSize(envmap, 0); + ivec2 size = textureSize(envMap, 0); int left = 0; - int right = size.x + 1; // cdf length is the length of the envmap + 1 + int right = size.x + 1; // cdf length is the length of the env map + 1 while (left < right) { int mid = (left + right) >> 1; - float s = texelFetch(envmapDistribution, ivec2(1 + mid, vOffset), 0).x; + float s = texelFetch(envMapDistribution, ivec2(1 + mid, vOffset), 0).x; if (s <= u) { left = mid + 1; } else { @@ -2644,10 +2644,10 @@ float getEnvmapU(float u, int vOffset, out float pdf) { } int uOffset = left - 1; - // x channel is cumulative distribution of envmap luminance - // y channel is partial probability density of envmap luminance - vec2 s0 = texelFetch(envmapDistribution, ivec2(1 + uOffset, vOffset), 0).xy; - vec2 s1 = texelFetch(envmapDistribution, ivec2(1 + uOffset + 1, vOffset), 0).xy; + // x channel is cumulative distribution of env map luminance + // y channel is partial probability density of env map luminance + vec2 s0 = texelFetch(envMapDistribution, ivec2(1 + uOffset, vOffset), 0).xy; + vec2 s1 = texelFetch(envMapDistribution, ivec2(1 + uOffset + 1, vOffset), 0).xy; pdf = s0.y; @@ -2676,22 +2676,22 @@ vec3 sampleEnvmap(vec2 random, out vec2 uv, out float pdf) { return dir; } -float envmapPdf(vec2 uv) { - vec2 size = vec2(textureSize(envmap, 0)); +float envMapPdf(vec2 uv) { + vec2 size = vec2(textureSize(envMap, 0)); float sinTheta = sin(uv.y * PI); uv *= size; - float partialX = texelFetch(envmapDistribution, ivec2(1.0 + uv.x, uv.y), 0).y; - float partialY = texelFetch(envmapDistribution, ivec2(0, uv.y), 0).y; + float partialX = texelFetch(envMapDistribution, ivec2(1.0 + uv.x, uv.y), 0).y; + float partialY = texelFetch(envMapDistribution, ivec2(0, uv.y), 0).y; return partialX * partialY * INVPI2 / (2.0 * sinTheta); } vec3 sampleEnvmapFromDirection(vec3 d) { vec2 uv = cartesianToEquirect(d); - return textureLinear(envmap, uv).rgb; + return textureLinear(envMap, uv).rgb; } vec3 sampleBackgroundFromDirection(vec3 d) { @@ -2885,7 +2885,7 @@ void sampleMaterial(SurfaceInteraction si, int bounce, inout Path path) { lightDirSpecular(si.faceNormal, viewDir, basis, si.roughness, lightDirSample); uv = cartesianToEquirect(lightDir); - lightPdf = envmapPdf(uv); + lightPdf = envMapPdf(uv); brdfSample = true; } else { lightDir = sampleEnvmap(lightDirSample, uv, lightPdf); @@ -2912,7 +2912,7 @@ void sampleMaterial(SurfaceInteraction si, int bounce, inout Path path) { } } - vec3 irr = textureLinear(envmap, uv).rgb; + vec3 irr = textureLinear(envMap, uv).rgb; float scatteringPdf; vec3 brdf = materialBrdf(si, viewDir, lightDir, cosThetaL, diffuseWeight, scatteringPdf); @@ -2950,7 +2950,7 @@ void sampleMaterial(SurfaceInteraction si, int bounce, inout Path path) { brdf = materialBrdf(si, viewDir, lightDir, cosThetaL, 1.0, scatteringPdf); uv = cartesianToEquirect(lightDir); - lightPdf = envmapPdf(uv); + lightPdf = envMapPdf(uv); path.misWeight = powerHeuristic(scatteringPdf, lightPdf); @@ -2990,7 +2990,7 @@ void sampleShadowCatcher(SurfaceInteraction si, int bounce, inout Path path) { lightDirDiffuse(si.faceNormal, viewDir, basis, lightDirSample) : lightDirSpecular(si.faceNormal, viewDir, basis, si.roughness, lightDirSample); uv = cartesianToEquirect(lightDir); - lightPdf = envmapPdf(uv); + lightPdf = envMapPdf(uv); brdfSample = true; } else { lightDir = sampleEnvmap(lightDirSample, uv, lightPdf); @@ -3011,7 +3011,7 @@ void sampleShadowCatcher(SurfaceInteraction si, int bounce, inout Path path) { occluded = 0.0; } - float irr = dot(luminance, textureLinear(envmap, uv).rgb); + float irr = dot(luminance, textureLinear(envMap, uv).rgb); float scatteringPdf; vec3 brdf = materialBrdf(si, viewDir, lightDir, cosThetaL, 1.0, scatteringPdf); @@ -3048,7 +3048,7 @@ void sampleShadowCatcher(SurfaceInteraction si, int bounce, inout Path path) { brdf = materialBrdf(si, viewDir, lightDir, cosThetaL, 1.0, scatteringPdf); uv = cartesianToEquirect(lightDir); - lightPdf = envmapPdf(uv); + lightPdf = envMapPdf(uv); path.misWeight = 0.0; @@ -3113,7 +3113,7 @@ void sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Path path) { intersect, surfaceInteractionDirect, random, - envmap, + envMap, bsdf, sample, sampleMaterial, @@ -3389,7 +3389,7 @@ void sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Path path) { // noiseImage is a 32-bit PNG image function setNoise(noiseImage) { - renderPass.setTexture('noise', makeTexture(gl, { + renderPass.setTexture('noiseTex', makeTexture(gl, { data: noiseImage, wrapS: gl.REPEAT, wrapT: gl.REPEAT, @@ -3499,13 +3499,13 @@ void sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Path path) { renderPass.setTexture('normalMap', materialBuffer.textures.normalMap); renderPass.setTexture('pbrMap', materialBuffer.textures.pbrMap); - renderPass.setTexture('positions', makeDataTexture(gl, geometry.getAttribute('position').array, 3)); + renderPass.setTexture('positionBuffer', makeDataTexture(gl, geometry.getAttribute('position').array, 3)); - renderPass.setTexture('normals', makeDataTexture(gl, geometry.getAttribute('normal').array, 3)); + renderPass.setTexture('normalBuffer', makeDataTexture(gl, geometry.getAttribute('normal').array, 3)); - renderPass.setTexture('uvs', makeDataTexture(gl, geometry.getAttribute('uv').array, 2)); + renderPass.setTexture('uvBuffer', makeDataTexture(gl, geometry.getAttribute('uv').array, 2)); - renderPass.setTexture('bvh', makeDataTexture(gl, flattenedBvh.buffer, 4)); + renderPass.setTexture('bvhBuffer', makeDataTexture(gl, flattenedBvh.buffer, 4)); const envImage = generateEnvMapFromSceneComponents(directionalLights, ambientLights, environmentLights); const envImageTextureObject = makeTexture(gl, { @@ -3517,7 +3517,7 @@ void sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Path path) { height: envImage.height, }); - renderPass.setTexture('envmap', envImageTextureObject); + renderPass.setTexture('envMap', envImageTextureObject); let backgroundImageTextureObject; if (background) { @@ -3536,9 +3536,9 @@ void sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Path path) { renderPass.setTexture('backgroundMap', backgroundImageTextureObject); - const distribution = envmapDistribution(envImage); + const distribution = envMapDistribution(envImage); - renderPass.setTexture('envmapDistribution', makeTexture(gl, { + renderPass.setTexture('envMapDistribution', makeTexture(gl, { data: distribution.data, storage: 'halfFloat', width: distribution.width, @@ -3647,13 +3647,13 @@ void sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Path path) { source: ` in vec2 vCoord; - uniform mediump sampler2D light; - uniform mediump sampler2D position; + uniform mediump sampler2D lightTex; + uniform mediump sampler2D positionTex; uniform vec2 lightScale; uniform vec2 previousLightScale; - uniform mediump sampler2D previousLight; - uniform mediump sampler2D previousPosition; + uniform mediump sampler2D previousLightTex; + uniform mediump sampler2D previousPositionTex; uniform mat4 historyCamera; uniform float blendAmount; @@ -3669,10 +3669,10 @@ void sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Path path) { } void main() { - vec3 currentPosition = textureLinear(position, vCoord).xyz; - float currentMeshId = getMeshId(position, vCoord); + vec3 currentPosition = textureLinear(positionTex, vCoord).xyz; + float currentMeshId = getMeshId(positionTex, vCoord); - vec4 currentLight = texture(light, lightScale * vCoord); + vec4 currentLight = texture(lightTex, lightScale * vCoord); if (currentMeshId == 0.0) { out_light = currentLight; @@ -3681,7 +3681,7 @@ void sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Path path) { vec2 hCoord = reproject(currentPosition) - jitter; - vec2 hSizef = previousLightScale * vec2(textureSize(previousLight, 0)); + vec2 hSizef = previousLightScale * vec2(textureSize(previousLightTex, 0)); vec2 hSizeInv = 1.0 / hSizef; ivec2 hSize = ivec2(hSizef); @@ -3710,12 +3710,12 @@ void sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Path path) { for (int i = 0; i < 4; i++) { vec2 gCoord = (vec2(texel[i]) + 0.5) * hSizeInv; - float histMeshId = getMeshId(previousPosition, gCoord); + float histMeshId = getMeshId(previousPositionTex, gCoord); float isValid = histMeshId != currentMeshId || any(greaterThanEqual(texel[i], hSize)) ? 0.0 : 1.0; float weight = isValid * weights[i]; - history += weight * texelFetch(previousLight, texel[i], 0); + history += weight * texelFetch(previousLightTex, texel[i], 0); sum += weight; } @@ -3730,12 +3730,12 @@ void sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Path path) { ivec2 texel = hTexel + ivec2(x, y); vec2 gCoord = (vec2(texel) + 0.5) * hSizeInv; - float histMeshId = getMeshId(previousPosition, gCoord); + float histMeshId = getMeshId(previousPositionTex, gCoord); float isValid = histMeshId != currentMeshId || any(greaterThanEqual(texel, hSize)) ? 0.0 : 1.0; float weight = isValid; - vec4 h = texelFetch(previousLight, texel, 0); + vec4 h = texelFetch(previousLightTex, texel, 0); history += weight * h; sum += weight; } @@ -3794,10 +3794,10 @@ void sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Path path) { renderPass.setUniform('lightScale', lightScale.x, lightScale.y); renderPass.setUniform('previousLightScale', previousLightScale.x, previousLightScale.y); - renderPass.setTexture('light', light); - renderPass.setTexture('position', position); - renderPass.setTexture('previousLight', previousLight); - renderPass.setTexture('previousPosition', previousPosition); + renderPass.setTexture('lightTex', light); + renderPass.setTexture('positionTex', position); + renderPass.setTexture('previousLightTex', previousLight); + renderPass.setTexture('previousPositionTex', previousPosition); renderPass.useProgram(); fullscreenQuad.draw(); @@ -3816,8 +3816,8 @@ void sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Path path) { source: ` in vec2 vCoord; - uniform sampler2D light; - uniform sampler2D position; + uniform sampler2D lightTex; + uniform sampler2D positionTex; uniform vec2 lightScale; @@ -3855,9 +3855,9 @@ void sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Path path) { } vec4 getUpscaledLight(vec2 coord) { - float meshId = getMeshId(position, coord); + float meshId = getMeshId(positionTex, coord); - vec2 sizef = lightScale * vec2(textureSize(position, 0)); + vec2 sizef = lightScale * vec2(textureSize(positionTex, 0)); vec2 texelf = coord * sizef - 0.5; ivec2 texel = ivec2(texelf); vec2 f = fract(texelf); @@ -3880,16 +3880,16 @@ void sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Path path) { float sum; for (int i = 0; i < 4; i++) { vec2 pCoord = (vec2(texels[i]) + 0.5) / sizef; - float isValid = getMeshId(position, pCoord) == meshId ? 1.0 : 0.0; + float isValid = getMeshId(positionTex, pCoord) == meshId ? 1.0 : 0.0; float weight = isValid * weights[i]; - upscaledLight += weight * texelFetch(light, texels[i], 0); + upscaledLight += weight * texelFetch(lightTex, texels[i], 0); sum += weight; } if (sum > 0.0) { upscaledLight /= sum; } else { - upscaledLight = texture(light, lightScale * coord); + upscaledLight = texture(lightTex, lightScale * coord); } return upscaledLight; @@ -3900,14 +3900,14 @@ void sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Path path) { #ifdef EDGE_PRESERVING_UPSCALE vec4 upscaledLight = getUpscaledLight(vCoord); #else - vec4 upscaledLight = texture(light, lightScale * vCoord); + vec4 upscaledLight = texture(lightTex, lightScale * vCoord); #endif // alpha channel stores the number of samples progressively rendered // divide the sum of light by alpha to obtain average contribution of light // in addition, alpha contains a scale factor for the shadow catcher material - // dividing by alpha normalizes the brightness of the shadow catcher to match the background envmap. + // dividing by alpha normalizes the brightness of the shadow catcher to match the background env map. vec3 light = upscaledLight.rgb / upscaledLight.a; light *= EXPOSURE; @@ -3965,8 +3965,8 @@ void sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Path path) { renderPassNative; renderPass.setUniform('lightScale', lightScale.x, lightScale.y); - renderPass.setTexture('light', light); - renderPass.setTexture('position', position); + renderPass.setTexture('lightTex', light); + renderPass.setTexture('positionTex', position); renderPass.useProgram(); fullscreenQuad.draw(); @@ -4150,6 +4150,7 @@ void sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Path path) { let frameTime; let elapsedFrameTime; + let sampleTime; let sampleCount = 0; let numPreviewsRendered = 0; @@ -4394,6 +4395,9 @@ void sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Path path) { if (sampleCount === 0) { // previous rendered image was a preview image clearBuffer(hdrBuffer); reprojectPass.setPreviousCamera(lastCamera); + } else { + sampleRenderedCallback(sampleCount, frameTime - sampleTime || NaN); + sampleTime = frameTime; } updateSeed(screenWidth, screenHeight, true); @@ -4427,8 +4431,6 @@ void sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Path path) { } else { toneMapToScreen(hdrBuffer.color[0], fullscreenScale); } - - sampleRenderedCallback(sampleCount); } } diff --git a/package-lock.json b/package-lock.json index 533493f..c78ac90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "ray-tracing-renderer", - "version": "0.8.0", + "version": "0.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 4c6c075..8042a4e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ray-tracing-renderer", - "version": "0.8.0", + "version": "0.9.0", "description": "A [Three.js](https://github.com/mrdoob/three.js/) renderer which utilizes path tracing to render a scene with true photorealism. The renderer supports global illumination, reflections, soft shadows, and realistic environment lighting.", "main": "build/RayTracingRenderer.js", "scripts": {