Skip to content

Instantly share code, notes, and snippets.

@erichlof
Created May 14, 2025 18:46
Show Gist options
  • Save erichlof/4c38e828b1ec299b65f11b5fd7e85fb6 to your computer and use it in GitHub Desktop.
Save erichlof/4c38e828b1ec299b65f11b5fd7e85fb6 to your computer and use it in GitHub Desktop.
Experimenting with more traditional, unbiased Monte Carlo rendering
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