Created
May 14, 2025 18:46
-
-
Save erichlof/4c38e828b1ec299b65f11b5fd7e85fb6 to your computer and use it in GitHub Desktop.
Experimenting with more traditional, unbiased Monte Carlo rendering
This file contains hidden or 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; | |
| #include <pathtracing_uniforms_and_defines> | |
| uniform int uMaterialType; | |
| uniform vec3 uMaterialColor; | |
| #define N_LIGHTS 3.0 | |
| #define N_SPHERES 15 | |
| //----------------------------------------------------------------------- | |
| vec3 rayOrigin, rayDirection; | |
| // recorded intersection data: | |
| vec3 hitNormal; | |
| float hitObjectID = -INFINITY; | |
| struct Material { vec3 emissiveColor; float emissiveIntensity; vec3 diffuseColor; vec3 specularColor; | |
| vec3 transparentColor; float roughness; float metalness; float IoR; float opacity; }; | |
| struct Sphere { float radius; vec3 position; Material material; }; | |
| Material hitMaterial; | |
| Sphere spheres[N_SPHERES]; | |
| #include <pathtracing_random_functions> | |
| #include <pathtracing_calc_fresnel_reflectance> | |
| #include <pathtracing_sphere_intersect> | |
| #include <pathtracing_sample_sphere_light> | |
| //--------------------------------------------------------------------------------------- | |
| float SceneIntersect( ) | |
| //--------------------------------------------------------------------------------------- | |
| { | |
| vec3 n; | |
| vec3 hitPos; | |
| float d; | |
| float t = INFINITY; | |
| float q; | |
| int objectCount = 0; | |
| hitObjectID = -INFINITY; | |
| /* for (int i = 0; i < N_SPHERES; i++) | |
| { | |
| d = SphereIntersect( spheres[i].radius, spheres[i].position, rayOrigin, rayDirection ); | |
| if (d < t) | |
| { | |
| t = d; | |
| hitNormal = (rayOrigin + rayDirection * t) - spheres[i].position; | |
| hitEmission = spheres[i].emission; | |
| hitColor = spheres[i].color; | |
| hitRoughness = spheres[i].roughness; | |
| hitType = spheres[i].type; | |
| hitObjectID = float(objectCount); | |
| } | |
| objectCount++; | |
| } */ | |
| // manually unroll the loop above - performs 2x faster on mobile! | |
| d = SphereIntersect( spheres[0].radius, spheres[0].position, rayOrigin, rayDirection ); | |
| if (d < t) | |
| { | |
| t = d; | |
| hitNormal = (rayOrigin + rayDirection * t) - spheres[0].position; | |
| hitMaterial = spheres[0].material; | |
| hitObjectID = float(objectCount); | |
| } | |
| objectCount++; | |
| d = SphereIntersect( spheres[1].radius, spheres[1].position, rayOrigin, rayDirection ); | |
| if (d < t) | |
| { | |
| t = d; | |
| hitNormal = (rayOrigin + rayDirection * t) - spheres[1].position; | |
| hitMaterial = spheres[1].material; | |
| hitObjectID = float(objectCount); | |
| } | |
| objectCount++; | |
| d = SphereIntersect( spheres[2].radius, spheres[2].position, rayOrigin, rayDirection ); | |
| if (d < t) | |
| { | |
| t = d; | |
| hitNormal = (rayOrigin + rayDirection * t) - spheres[2].position; | |
| hitMaterial = spheres[2].material; | |
| hitObjectID = float(objectCount); | |
| } | |
| objectCount++; | |
| d = SphereIntersect( spheres[3].radius, spheres[3].position, rayOrigin, rayDirection ); | |
| if (d < t) | |
| { | |
| t = d; | |
| hitPos = rayOrigin + rayDirection * t; | |
| hitNormal = hitPos - spheres[3].position; | |
| hitMaterial = spheres[3].material; | |
| if (mod(floor(hitPos.x * 0.04) + floor(hitPos.z * 0.04), 2.0) == 0.0) | |
| hitMaterial.diffuseColor = vec3(0.5); | |
| hitObjectID = float(objectCount); | |
| } | |
| objectCount++; | |
| d = SphereIntersect( spheres[4].radius, spheres[4].position, rayOrigin, rayDirection ); | |
| if (d < t) | |
| { | |
| t = d; | |
| hitNormal = (rayOrigin + rayDirection * t) - spheres[4].position; | |
| hitMaterial = spheres[4].material; | |
| hitObjectID = float(objectCount); | |
| } | |
| objectCount++; | |
| d = SphereIntersect( spheres[5].radius, spheres[5].position, rayOrigin, rayDirection ); | |
| if (d < t) | |
| { | |
| t = d; | |
| hitNormal = (rayOrigin + rayDirection * t) - spheres[5].position; | |
| hitMaterial = spheres[5].material; | |
| hitObjectID = float(objectCount); | |
| } | |
| objectCount++; | |
| d = SphereIntersect( spheres[6].radius, spheres[6].position, rayOrigin, rayDirection ); | |
| if (d < t) | |
| { | |
| t = d; | |
| hitNormal = (rayOrigin + rayDirection * t) - spheres[6].position; | |
| hitMaterial = spheres[6].material; | |
| hitObjectID = float(objectCount); | |
| } | |
| objectCount++; | |
| d = SphereIntersect( spheres[7].radius, spheres[7].position, rayOrigin, rayDirection ); | |
| if (d < t) | |
| { | |
| t = d; | |
| hitNormal = (rayOrigin + rayDirection * t) - spheres[7].position; | |
| hitMaterial = spheres[7].material; | |
| hitObjectID = float(objectCount); | |
| } | |
| objectCount++; | |
| d = SphereIntersect( spheres[8].radius, spheres[8].position, rayOrigin, rayDirection ); | |
| if (d < t) | |
| { | |
| t = d; | |
| hitNormal = (rayOrigin + rayDirection * t) - spheres[8].position; | |
| hitMaterial = spheres[8].material; | |
| hitObjectID = float(objectCount); | |
| } | |
| objectCount++; | |
| d = SphereIntersect( spheres[9].radius, spheres[9].position, rayOrigin, rayDirection ); | |
| if (d < t) | |
| { | |
| t = d; | |
| hitNormal = (rayOrigin + rayDirection * t) - spheres[9].position; | |
| hitMaterial = spheres[9].material; | |
| hitObjectID = float(objectCount); | |
| } | |
| objectCount++; | |
| d = SphereIntersect( spheres[10].radius, spheres[10].position, rayOrigin, rayDirection ); | |
| if (d < t) | |
| { | |
| t = d; | |
| hitNormal = (rayOrigin + rayDirection * t) - spheres[10].position; | |
| hitMaterial = spheres[10].material; | |
| hitObjectID = float(objectCount); | |
| } | |
| objectCount++; | |
| d = SphereIntersect( spheres[11].radius, spheres[11].position, rayOrigin, rayDirection ); | |
| if (d < t) | |
| { | |
| t = d; | |
| hitNormal = (rayOrigin + rayDirection * t) - spheres[11].position; | |
| hitMaterial = spheres[11].material; | |
| hitObjectID = float(objectCount); | |
| } | |
| objectCount++; | |
| d = SphereIntersect( spheres[12].radius, spheres[12].position, rayOrigin, rayDirection ); | |
| if (d < t) | |
| { | |
| t = d; | |
| hitNormal = (rayOrigin + rayDirection * t) - spheres[12].position; | |
| hitMaterial = spheres[12].material; | |
| hitObjectID = float(objectCount); | |
| } | |
| objectCount++; | |
| d = SphereIntersect( spheres[13].radius, spheres[13].position, rayOrigin, rayDirection ); | |
| if (d < t) | |
| { | |
| t = d; | |
| hitNormal = (rayOrigin + rayDirection * t) - spheres[13].position; | |
| hitMaterial = spheres[13].material; | |
| hitObjectID = float(objectCount); | |
| } | |
| objectCount++; | |
| d = SphereIntersect( spheres[14].radius, spheres[14].position, rayOrigin, rayDirection ); | |
| if (d < t) | |
| { | |
| t = d; | |
| hitNormal = (rayOrigin + rayDirection * t) - spheres[14].position; | |
| hitMaterial = spheres[14].material; | |
| hitObjectID = float(objectCount); | |
| } | |
| objectCount++; | |
| return t; | |
| } // end float SceneIntersect( ) | |
| //----------------------------------------------------------------------------------------------------------------------------- | |
| vec3 CalculateRadiance( out vec3 objectNormal, out vec3 objectColor, out float objectID, out float pixelSharpness ) | |
| //----------------------------------------------------------------------------------------------------------------------------- | |
| { | |
| vec3 accumColor = vec3(0); | |
| vec3 mask = vec3(1); | |
| vec3 checkCol0 = vec3(1); | |
| vec3 checkCol1 = vec3(0.5); | |
| vec3 x, n, nl, normal; | |
| float t; | |
| float nc, nt, ratioIoR, Re, Tr; | |
| float weight; | |
| float thickness = 0.05; | |
| float random, newRandom; | |
| int diffuseCount = 0; | |
| //int bounceIsSpecular = TRUE; | |
| int bounceIsIndirectDiffuse = FALSE; | |
| int sampleLight = FALSE; | |
| for (int bounces = 0; bounces < 12; bounces++) | |
| { | |
| t = SceneIntersect(); | |
| // shouldn't happen because we are inside a huge checkered sphere, but just in case | |
| if (t == INFINITY) | |
| { | |
| break; | |
| } | |
| // useful data | |
| n = normalize(hitNormal); | |
| nl = dot(n, rayDirection) < 0.0 ? n : -n; | |
| x = rayOrigin + rayDirection * t; | |
| if (bounces == 0) | |
| { | |
| objectID = hitObjectID; | |
| } | |
| if (diffuseCount == 0) | |
| { | |
| objectNormal += n; | |
| if (hitMaterial.emissiveColor.r > 0.0 || hitMaterial.emissiveColor.g > 0.0 || hitMaterial.emissiveColor.b > 0.0) | |
| objectColor += hitMaterial.emissiveColor; | |
| // else if (hitMaterial.transparentColor.r > 0.0 || hitMaterial.transparentColor.g > 0.0 || hitMaterial.transparentColor.b > 0.0) | |
| // objectColor += hitMaterial.transparentColor; | |
| // else if (hitMaterial.specularColor.r > 0.0 || hitMaterial.specularColor.g > 0.0 || hitMaterial.specularColor.b > 0.0) | |
| // objectColor += hitMaterial.specularColor; | |
| else objectColor += hitMaterial.diffuseColor; | |
| } | |
| // unbiased Monte Carlo path tracing | |
| if (bounces > 3) | |
| { | |
| // Russian roulette | |
| float p = max(mask.x, max(mask.y, mask.z)); | |
| if (rng() < p) | |
| mask *= (1.0 / p); | |
| else | |
| break; | |
| } | |
| // hit a light source? | |
| if (hitMaterial.emissiveIntensity > 0.0) | |
| { | |
| if (bounceIsIndirectDiffuse == FALSE) | |
| accumColor += mask * (hitMaterial.emissiveColor * hitMaterial.emissiveIntensity); | |
| break; | |
| } | |
| // if sampleLight is still true, and we didn't reach the intended light source ("hitMaterial.emissiveIntensity > 0.0" above), | |
| // then we must exit the bounces loop, and this pixel's color will be black and in shadow. | |
| if (sampleLight == TRUE) | |
| break; | |
| // reset bounceIsIndirectDiffuse | |
| bounceIsIndirectDiffuse = FALSE; | |
| random = rng(); | |
| if (random < (1.0 - hitMaterial.opacity)) | |
| { | |
| if (random < calcFresnelReflectance(rayDirection, n, 1.0, hitMaterial.IoR, ratioIoR)) | |
| { | |
| mask *= hitMaterial.specularColor; | |
| rayDirection = randomDirectionInSpecularLobe(reflect(rayDirection, nl), hitMaterial.roughness); | |
| rayOrigin = x + nl * uEPS_intersect; | |
| continue; | |
| } | |
| else | |
| { | |
| mask *= hitMaterial.transparentColor; | |
| rayDirection = randomDirectionInSpecularLobe(refract(rayDirection, nl, ratioIoR), (hitMaterial.roughness * sqrt(hitMaterial.roughness))); | |
| rayOrigin = x - nl * uEPS_intersect; | |
| continue; | |
| } | |
| } | |
| else if (random < hitMaterial.metalness) | |
| { | |
| mask *= hitMaterial.specularColor; | |
| rayDirection = randomDirectionInSpecularLobe(reflect(rayDirection, nl), hitMaterial.roughness); | |
| rayOrigin = x + nl * uEPS_intersect; | |
| continue; | |
| } | |
| else if (random < (hitMaterial.IoR > 1.0 ? calcFresnelReflectance(rayDirection, nl, 1.0, hitMaterial.IoR, ratioIoR) : 0.0)) | |
| { | |
| mask *= hitMaterial.specularColor; | |
| rayDirection = randomDirectionInSpecularLobe(reflect(rayDirection, nl), hitMaterial.roughness); | |
| rayOrigin = x + nl * uEPS_intersect; | |
| continue; | |
| } | |
| else // diffuse surface | |
| { | |
| //bounceIsSpecular = FALSE; | |
| diffuseCount++; | |
| mask *= hitMaterial.diffuseColor; | |
| rayOrigin = x + nl * uEPS_intersect; | |
| if (rng() < 0.5) | |
| { | |
| rayDirection = randomCosWeightedDirectionInHemisphere(nl); | |
| mask *= 2.0; | |
| bounceIsIndirectDiffuse = TRUE; | |
| continue; | |
| } | |
| newRandom = rand(); | |
| if (newRandom < 0.3333) | |
| rayDirection = sampleSphereLight(x, nl, spheres[0], weight); | |
| else if (newRandom < 0.6666) | |
| rayDirection = sampleSphereLight(x, nl, spheres[1], weight); | |
| else | |
| rayDirection = sampleSphereLight(x, nl, spheres[2], weight); | |
| mask *= weight * N_LIGHTS * 2.0; | |
| sampleLight = TRUE; | |
| continue; | |
| } | |
| } // end for (int bounces = 0; bounces < 12; bounces++) | |
| return max(vec3(0), accumColor); | |
| } // end vec3 CalculateRadiance( out vec3 objectNormal, out vec3 objectColor, out float objectID, out float pixelSharpness ) | |
| //----------------------------------------------------------------------- | |
| void SetupScene(void) | |
| //----------------------------------------------------------------------- | |
| { | |
| //Material { vec3 emissiveColor; float emissiveIntensity; vec3 diffuseColor; vec3 specularColor; | |
| // vec3 transparentColor; float roughness; float metalness; float IoR; float opacity; }; | |
| Material light0Material = Material(vec3(1.0, 1.0, 1.0), 5.0, vec3(0), vec3(0), vec3(0), 0.0, 0.0, 1.0, 1.0); | |
| Material light1Material = Material(vec3(1.0, 0.8, 0.2), 4.0, vec3(0), vec3(0), vec3(0), 0.0, 0.0, 1.0, 1.0); | |
| Material light2Material = Material(vec3(0.1, 0.7, 1.0), 2.0, vec3(0), vec3(0), vec3(0), 0.0, 0.0, 1.0, 1.0); | |
| Material checkerMaterial = Material(vec3(0), 0.0, vec3(1.0, 1.0, 1.0), vec3(0), vec3(0), 0.0, 0.0, 1.0, 1.0); | |
| Material sphereMaterial = Material(vec3(0), 0.0, uMaterialColor, vec3(1.0, 1.0, 1.0), vec3(0), 0.0, 0.0, 1.5, 1.0); | |
| if (uMaterialType == 3) | |
| sphereMaterial = Material(vec3(0), 0.0, vec3(0), uMaterialColor, vec3(0), 0.0, 1.0, 1.0, 1.0); | |
| if (uMaterialType == 2) | |
| sphereMaterial = Material(vec3(0), 0.0, vec3(0), vec3(1), uMaterialColor, 0.0, 0.0, 1.5, 0.0); | |
| if (uMaterialType == 18) | |
| sphereMaterial = Material(vec3(0), 0.0, uMaterialColor, vec3(1), vec3(0), 0.0, 1.0, 1.5, 1.0); | |
| spheres[0] = Sphere(150.0, vec3(-400, 900, 200), light0Material);//spherical white Light0 | |
| spheres[1] = Sphere(100.0, vec3( 300, 400,-300), light1Material);//spherical yellow Light1 | |
| spheres[2] = Sphere( 50.0, vec3( 500, 250,-100), light2Material);//spherical blue Light2 | |
| spheres[3] = Sphere(1000.0, vec3( 0.0, 1000.0, 0.0), checkerMaterial);//Checkered Floor | |
| spheres[4] = Sphere( 14.0, vec3(-150, 30, 0), sphereMaterial); spheres[4].material.roughness = 0.0; | |
| spheres[5] = Sphere( 14.0, vec3(-120, 30, 0), sphereMaterial); spheres[5].material.roughness = 0.1; | |
| spheres[6] = Sphere( 14.0, vec3( -90, 30, 0), sphereMaterial); spheres[6].material.roughness = 0.2; | |
| spheres[7] = Sphere( 14.0, vec3( -60, 30, 0), sphereMaterial); spheres[7].material.roughness = 0.3; | |
| spheres[8] = Sphere( 14.0, vec3( -30, 30, 0), sphereMaterial); spheres[8].material.roughness = 0.4; | |
| spheres[9] = Sphere( 14.0, vec3( 0, 30, 0), sphereMaterial); spheres[9].material.roughness = 0.5; | |
| spheres[10] = Sphere( 14.0, vec3( 30, 30, 0), sphereMaterial); spheres[10].material.roughness = 0.6; | |
| spheres[11] = Sphere( 14.0, vec3( 60, 30, 0), sphereMaterial); spheres[11].material.roughness = 0.7; | |
| spheres[12] = Sphere( 14.0, vec3( 90, 30, 0), sphereMaterial); spheres[12].material.roughness = 0.8; | |
| spheres[13] = Sphere( 14.0, vec3( 120, 30, 0), sphereMaterial); spheres[13].material.roughness = 0.9; | |
| spheres[14] = Sphere( 14.0, vec3( 150, 30, 0), sphereMaterial); spheres[14].material.roughness = 1.0; | |
| if (uMaterialType == 18) | |
| { | |
| spheres[4].material.metalness = 1.0; spheres[4].material.roughness = 0.0; | |
| spheres[5].material.metalness = 0.9; spheres[5].material.roughness = 0.0; | |
| spheres[6].material.metalness = 0.8; spheres[6].material.roughness = 0.0; | |
| spheres[7].material.metalness = 0.7; spheres[7].material.roughness = 0.0; | |
| spheres[8].material.metalness = 0.6; spheres[8].material.roughness = 0.0; | |
| spheres[9].material.metalness = 0.5; spheres[9].material.roughness = 0.0; | |
| spheres[10].material.metalness = 0.4; spheres[10].material.roughness = 0.0; | |
| spheres[11].material.metalness = 0.3; spheres[11].material.roughness = 0.0; | |
| spheres[12].material.metalness = 0.2; spheres[12].material.roughness = 0.0; | |
| spheres[13].material.metalness = 0.1; spheres[13].material.roughness = 0.0; | |
| spheres[14].material.metalness = 0.0; spheres[14].material.roughness = 0.0; | |
| } | |
| } | |
| #include <pathtracing_main> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment