Skip to content

Instantly share code, notes, and snippets.

@erichlof
Created February 20, 2025 21:20
Show Gist options
  • Save erichlof/bd2bee54e96c704f160756fae8862052 to your computer and use it in GitHub Desktop.
Save erichlof/bd2bee54e96c704f160756fae8862052 to your computer and use it in GitHub Desktop.
js file for experimental sphere BVH (as opposed to typical AABB BVH)
// scene/demo-specific variables go here
let modelMesh;
let modelScale = 1.0;
let modelPositionOffset = new THREE.Vector3();
let albedoTexture;
let total_number_of_triangles = 0;
let triangle_array;
let triangleMaterialMarkers = [];
let pathTracingMaterialList = [];
let uniqueMaterialTextures = [];
let meshList = [];
let geoList = [];
let triangleDataTexture;
let aabb_array;
let aabbDataTexture;
let totalWorklist;
let vp0 = new THREE.Vector3();
let vp1 = new THREE.Vector3();
let vp2 = new THREE.Vector3();
let vn0 = new THREE.Vector3();
let vn1 = new THREE.Vector3();
let vn2 = new THREE.Vector3();
let vt0 = new THREE.Vector2();
let vt1 = new THREE.Vector2();
let vt2 = new THREE.Vector2();
function MaterialObject()
{
// a list of material types and their corresponding numbers are found in the 'pathTracingCommon.js' file
this.type = 1; // default is '1': diffuse type
this.albedoTextureID = -1; // which diffuse map to use for model's color / '-1' = no textures are used
this.color = new THREE.Color(1.0, 1.0, 1.0); // takes on different meanings, depending on 'type' above
this.roughness = 0.0; // 0.0 to 1.0 range, perfectly smooth to extremely rough
this.metalness = 0.0; // 0.0 to 1.0 range, usually either 0 or 1, either non-metal or metal
this.opacity = 1.0; // 0.0 to 1.0 range, fully transparent to fully opaque
this.refractiveIndex = 1.0; // 1.0=air, 1.33=water, 1.4=clearCoat, 1.5=glass, etc.
}
function load_GLTF_Model()
{
let gltfLoader = new GLTFLoader();
gltfLoader.load("models/StanfordBunny.glb", function (meshGroup) // Triangles: 30,338
//gltfLoader.load("models/UtahTeapot.glb", function( meshGroup ) // Triangles: 992
//gltfLoader.load("models/TheSentinel_tree.glb", function (meshGroup) // Triangles: 18-60
//gltfLoader.load("models/StanfordDragon.glb", function (meshGroup) // Triangles: 100,000
//gltfLoader.load("models/FairyScene.glb", function (meshGroup) // Triangles: 100,000
{
if (meshGroup.scene)
meshGroup = meshGroup.scene;
meshGroup.traverse(function (child)
{
if (child.isMesh)
{
let mat = new MaterialObject();
mat.type = 1;
mat.albedoTextureID = -1;
mat.color = child.material.color;
mat.roughness = child.material.roughness || 0.0;
mat.metalness = child.material.metalness || 0.0;
mat.opacity = child.material.opacity || 1.0;
mat.refractiveIndex = 1.0;
pathTracingMaterialList.push(mat);
triangleMaterialMarkers.push(child.geometry.attributes.position.array.length / 9);
meshList.push(child);
}
});
modelMesh = meshList[0].clone();
for (let i = 0; i < meshList.length; i++)
{
geoList.push(meshList[i].geometry);
}
modelMesh.geometry = mergeGeometries(geoList);
if (modelMesh.geometry.index)
modelMesh.geometry = modelMesh.geometry.toNonIndexed();
modelMesh.geometry.center();
for (let i = 1; i < triangleMaterialMarkers.length; i++)
{
triangleMaterialMarkers[i] += triangleMaterialMarkers[i - 1];
}
/* for (let i = 0; i < meshList.length; i++)
{
if (meshList[i].material.map != undefined)
uniqueMaterialTextures.push(meshList[i].material.map);
}
for (let i = 0; i < uniqueMaterialTextures.length; i++)
{
for (let j = i + 1; j < uniqueMaterialTextures.length; j++)
{
if (uniqueMaterialTextures[i].image.src == uniqueMaterialTextures[j].image.src)
{
uniqueMaterialTextures.splice(j, 1);
j -= 1;
}
}
}
for (let i = 0; i < meshList.length; i++)
{
if (meshList[i].material.map != undefined)
{
for (let j = 0; j < uniqueMaterialTextures.length; j++)
{
if (meshList[i].material.map.image.src == uniqueMaterialTextures[j].image.src)
{
pathTracingMaterialList[i].albedoTextureID = j;
}
}
}
} */
// ********* different GLTF Model Settings **********
// settings for StanfordBunny model
modelScale = 0.5;//0.04;
modelPositionOffset.set(0, 27.6, -40);
// settings for UtahTeapot model
// modelScale = 1.0;
// modelPositionOffset.set(0, 25.6, -40);
// settings for TheSentinel models
// modelScale = 20.0;
// modelPositionOffset.set(0, 30, 0);
// settings for StanfordDragon model
// modelScale = 2.0;
// modelPositionOffset.set(0, 28, -40);
// settings for various test models
// modelScale = 6;
// modelPositionOffset.set(0, 20, 0);
// now that the model has been loaded, we can init
init();
});
} // end function load_GLTF_Model()
// called automatically from within initTHREEjs() function (located in InitCommon.js file)
function initSceneData()
{
demoFragmentShaderFileName = 'BVH_Point_Light_Source_Fragment.glsl';
// scene/demo-specific three.js objects setup goes here
sceneIsDynamic = false;
cameraFlightSpeed = 60;
// pixelRatio is resolution - range: 0.5(half resolution) to 1.0(full resolution)
pixelRatio = mouseControl ? 0.5 : 0.75; // less demanding on battery-powered mobile devices
EPS_intersect = 0.001;
// set camera's field of view
worldCamera.fov = 60;
focusDistance = 95.0;
apertureChangeSpeed = 5;
// position and orient camera
cameraControlsObject.position.set(0, 30, 60);
// look slightly downward
//cameraControlsPitchObject.rotation.x = -0.2;
total_number_of_triangles = modelMesh.geometry.attributes.position.array.length / 9;
console.log("Triangle count:" + total_number_of_triangles);
totalWorklist = new Uint32Array(total_number_of_triangles);
triangle_array = new Float32Array(4096 * 4096 * 4);
// 4096 = width of texture, 4096 = height of texture, 4 = r,g,b, and a components
aabb_array = new Float32Array(4096 * 4096 * 4);
// 4096 = width of texture, 4096 = height of texture, 4 = r,g,b, and a components
let triangle_b_box_min = new THREE.Vector3();
let triangle_b_box_max = new THREE.Vector3();
let triangle_centroid = new THREE.Vector3();
//let triangle_b_box_centroid = new THREE.Vector3();
let vpa = new Float32Array(modelMesh.geometry.attributes.position.array);
let vna = new Float32Array(modelMesh.geometry.attributes.normal.array);
let vta = null;
let modelHasUVs = false;
if (modelMesh.geometry.attributes.uv !== undefined)
{
vta = new Float32Array(modelMesh.geometry.attributes.uv.array);
modelHasUVs = true;
}
let materialNumber = 0;
for (let i = 0; i < total_number_of_triangles; i++)
{
triangle_b_box_min.set(Infinity, Infinity, Infinity);
triangle_b_box_max.set(-Infinity, -Infinity, -Infinity);
for (let j = 0; j < pathTracingMaterialList.length; j++)
{
if (i < triangleMaterialMarkers[j])
{
materialNumber = j;
break;
}
}
// record vertex texture coordinates (UVs)
if (modelHasUVs)
{
vt0.set(vta[6 * i + 0], vta[6 * i + 1]);
vt1.set(vta[6 * i + 2], vta[6 * i + 3]);
vt2.set(vta[6 * i + 4], vta[6 * i + 5]);
}
else
{
vt0.set(-1, -1);
vt1.set(-1, -1);
vt2.set(-1, -1);
}
// record vertex normals
vn0.set(vna[9 * i + 0], vna[9 * i + 1], vna[9 * i + 2]).normalize();
vn1.set(vna[9 * i + 3], vna[9 * i + 4], vna[9 * i + 5]).normalize();
vn2.set(vna[9 * i + 6], vna[9 * i + 7], vna[9 * i + 8]).normalize();
// record vertex positions
vp0.set(vpa[9 * i + 0], vpa[9 * i + 1], vpa[9 * i + 2]);
vp1.set(vpa[9 * i + 3], vpa[9 * i + 4], vpa[9 * i + 5]);
vp2.set(vpa[9 * i + 6], vpa[9 * i + 7], vpa[9 * i + 8]);
vp0.multiplyScalar(modelScale);
vp1.multiplyScalar(modelScale);
vp2.multiplyScalar(modelScale);
vp0.add(modelPositionOffset);
vp1.add(modelPositionOffset);
vp2.add(modelPositionOffset);
//slot 0
triangle_array[32 * i + 0] = vp0.x; // r or x
triangle_array[32 * i + 1] = vp0.y; // g or y
triangle_array[32 * i + 2] = vp0.z; // b or z
triangle_array[32 * i + 3] = vp1.x; // a or w
//slot 1
triangle_array[32 * i + 4] = vp1.y; // r or x
triangle_array[32 * i + 5] = vp1.z; // g or y
triangle_array[32 * i + 6] = vp2.x; // b or z
triangle_array[32 * i + 7] = vp2.y; // a or w
//slot 2
triangle_array[32 * i + 8] = vp2.z; // r or x
triangle_array[32 * i + 9] = vn0.x; // g or y
triangle_array[32 * i + 10] = vn0.y; // b or z
triangle_array[32 * i + 11] = vn0.z; // a or w
//slot 3
triangle_array[32 * i + 12] = vn1.x; // r or x
triangle_array[32 * i + 13] = vn1.y; // g or y
triangle_array[32 * i + 14] = vn1.z; // b or z
triangle_array[32 * i + 15] = vn2.x; // a or w
//slot 4
triangle_array[32 * i + 16] = vn2.y; // r or x
triangle_array[32 * i + 17] = vn2.z; // g or y
triangle_array[32 * i + 18] = vt0.x; // b or z
triangle_array[32 * i + 19] = vt0.y; // a or w
//slot 5
triangle_array[32 * i + 20] = vt1.x; // r or x
triangle_array[32 * i + 21] = vt1.y; // g or y
triangle_array[32 * i + 22] = vt2.x; // b or z
triangle_array[32 * i + 23] = vt2.y; // a or w
// the remaining slots are used for PBR material properties
//slot 6
triangle_array[32 * i + 24] = pathTracingMaterialList[materialNumber].type; // r or x
triangle_array[32 * i + 25] = pathTracingMaterialList[materialNumber].color.r; // g or y
triangle_array[32 * i + 26] = pathTracingMaterialList[materialNumber].color.g; // b or z
triangle_array[32 * i + 27] = pathTracingMaterialList[materialNumber].color.b; // a or w
//slot 7
triangle_array[32 * i + 28] = pathTracingMaterialList[materialNumber].albedoTextureID; // r or x
triangle_array[32 * i + 29] = 0; // g or y
triangle_array[32 * i + 30] = 0; // b or z
triangle_array[32 * i + 31] = 0; // a or w
triangle_b_box_min.copy(triangle_b_box_min.min(vp0));
triangle_b_box_max.copy(triangle_b_box_max.max(vp0));
triangle_b_box_min.copy(triangle_b_box_min.min(vp1));
triangle_b_box_max.copy(triangle_b_box_max.max(vp1));
triangle_b_box_min.copy(triangle_b_box_min.min(vp2));
triangle_b_box_max.copy(triangle_b_box_max.max(vp2));
//triangle_b_box_centroid.copy(triangle_b_box_min).add(triangle_b_box_max).multiplyScalar(0.5);
triangle_centroid.copy(vp0).add(vp1).add(vp2).multiplyScalar(0.3333333333333333);
aabb_array[9 * i + 0] = triangle_b_box_min.x;
aabb_array[9 * i + 1] = triangle_b_box_min.y;
aabb_array[9 * i + 2] = triangle_b_box_min.z;
aabb_array[9 * i + 3] = triangle_b_box_max.x;
aabb_array[9 * i + 4] = triangle_b_box_max.y;
aabb_array[9 * i + 5] = triangle_b_box_max.z;
aabb_array[9 * i + 6] = triangle_centroid.x;
aabb_array[9 * i + 7] = triangle_centroid.y;
aabb_array[9 * i + 8] = triangle_centroid.z;
totalWorklist[i] = i;
}
for (let i = 0; i < total_number_of_triangles * 2; i++)
buildnodes[i] = new BVH_Node();
console.log("BvhGeneration...");
console.time("BvhGeneration");
BVH_QuickBuild(totalWorklist, aabb_array);
console.timeEnd("BvhGeneration");
let box = new THREE.Box3();
let sphere = new THREE.Sphere();
let sphereRadiusPacked = 0;
let leafOrChildID = 0;
// Copy the buildnodes array into the aabb_array
for (let n = 0; n < buildnodes.length; n++)
{
box.set(buildnodes[n].minCorner, buildnodes[n].maxCorner);
box.getBoundingSphere(sphere);
sphereRadiusPacked = sphere.radius * 0.001; // shove radius over into the fractional part of a float number
leafOrChildID = Math.floor(buildnodes[n].leafOrChild_ID); // keep leftChildID in the integer part of a float number
// this packed bounding volume node now takes only 1 rgba texel!
if (buildnodes[n].primitiveCount == 0) // inner nodes are encoded as a negative number
aabb_array[4 * n + 0] = -leafOrChildID - sphereRadiusPacked; // r or x component
else
aabb_array[4 * n + 0] = leafOrChildID + sphereRadiusPacked; // r or x component
aabb_array[4 * n + 1] = sphere.center.x; // g or y component
aabb_array[4 * n + 2] = sphere.center.y; // b or z component
aabb_array[4 * n + 3] = sphere.center.z; // a or w component
}
triangleDataTexture = new THREE.DataTexture(triangle_array,
4096,
4096,
THREE.RGBAFormat,
THREE.FloatType,
THREE.Texture.DEFAULT_MAPPING,
THREE.ClampToEdgeWrapping,
THREE.ClampToEdgeWrapping,
THREE.NearestFilter,
THREE.NearestFilter,
1,
THREE.NoColorSpace);
triangleDataTexture.flipY = false;
triangleDataTexture.generateMipmaps = false;
triangleDataTexture.needsUpdate = true;
aabbDataTexture = new THREE.DataTexture(aabb_array,
4096,
4096,
THREE.RGBAFormat,
THREE.FloatType,
THREE.Texture.DEFAULT_MAPPING,
THREE.ClampToEdgeWrapping,
THREE.ClampToEdgeWrapping,
THREE.NearestFilter,
THREE.NearestFilter,
1,
THREE.NoColorSpace);
aabbDataTexture.flipY = false;
aabbDataTexture.generateMipmaps = false;
aabbDataTexture.needsUpdate = true;
// scene/demo-specific uniforms go here
pathTracingUniforms.tTriangleTexture = { value: triangleDataTexture };
pathTracingUniforms.tAABBTexture = { value: aabbDataTexture };
} // end function initSceneData()
// called automatically from within the animate() function (located in InitCommon.js file)
function updateVariablesAndUniforms()
{
// INFO
cameraInfoElement.innerHTML = "FOV: " + worldCamera.fov + " / Aperture: " + apertureSize.toFixed(2) +
" / FocusDistance: " + focusDistance + "<br>" + "Samples: " + sampleCounter;
} // end function updateUniforms()
load_GLTF_Model(); // load model, init app, and start animating
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment