From 51a9d6c107d1ddd6102dae014077bc543bcd171b Mon Sep 17 00:00:00 2001 From: Jacco Bikker Date: Thu, 9 Jan 2025 14:55:58 +0100 Subject: [PATCH] Custom intersectors via callbacks. --- tiny_bvh.h | 40 ++++++- tiny_bvh_custom.cpp | 155 ++++++++++++++++++++++++ tiny_bvh_fenster.cpp | 12 +- tiny_bvh_test.sln | 10 ++ vcproj/tiny_bvh_custom.vcxproj | 156 +++++++++++++++++++++++++ vcproj/tiny_bvh_custom.vcxproj.filters | 9 ++ vcproj/tiny_bvh_custom.vcxproj.user | 11 ++ 7 files changed, 380 insertions(+), 13 deletions(-) create mode 100644 tiny_bvh_custom.cpp create mode 100644 vcproj/tiny_bvh_custom.vcxproj create mode 100644 vcproj/tiny_bvh_custom.vcxproj.filters create mode 100644 vcproj/tiny_bvh_custom.vcxproj.user diff --git a/tiny_bvh.h b/tiny_bvh.h index 0d0ff58..5d39e0a 100644 --- a/tiny_bvh.h +++ b/tiny_bvh.h @@ -99,7 +99,10 @@ THE SOFTWARE. // Features #define DOUBLE_PRECISION_SUPPORT -//#define TINYBVH_USE_CUSTOM_VECTOR_TYPES +// #define TINYBVH_USE_CUSTOM_VECTOR_TYPES +// #define TINYBVH_NO_SIMD +#define ENABLE_INDEXED_GEOMETRY +#define ENABLE_CUSTOM_GEOMETRY // CWBVH triangle format: doesn't seem to help on GPU? // #define CWBVH_COMPRESSED_TRIS @@ -110,6 +113,7 @@ THE SOFTWARE. #define FALLBACK_SHADOW_QUERY( s ) { Ray r = s; float d = s.hit.t; Intersect( r ); return r.hit.t < d; } // include fast AVX BVH builder +#ifndef TINYBVH_NO_SIMD #if defined(__x86_64__) || defined(_M_X64) || defined(__wasm_simd128__) || defined(__wasm_relaxed_simd__) #define BVH_USEAVX #include "immintrin.h" // for __m128 and __m256 @@ -117,11 +121,12 @@ THE SOFTWARE. #define BVH_USENEON #include "arm_neon.h" #endif +#endif // TINYBVH_NO_SIMD // library version #define TINY_BVH_VERSION_MAJOR 1 #define TINY_BVH_VERSION_MINOR 2 -#define TINY_BVH_VERSION_SUB 1 +#define TINY_BVH_VERSION_SUB 2 // ============================================================================ // @@ -624,6 +629,9 @@ class BVH : public BVHBase BVHNode* bvhNode = 0; // BVH node pool, Wald 32-byte format. Root is always in node 0. uint32_t newNodePtr = 0; // used during build to keep track of next free node in pool. Fragment* fragment = 0; // input primitive bounding boxes. + // Custom geometry intersection callback + void (*customIntersect)(Ray&, unsigned) = 0; + bool (*customIsOccluded)(const Ray&, unsigned) = 0; }; #ifdef DOUBLE_PRECISION_SUPPORT @@ -998,6 +1006,19 @@ class QBVH6 #pragma GCC diagnostic ignored "-Wstrict-aliasing" #endif +// Some constexpr stuff to produce nice-looking branches in +// *::Intersect with proper dead code elinimation. +#ifdef ENABLE_INDEXED_GEOMETRY +static constexpr bool indexedEnabled = true; +#else +static constexpr bool indexedEnabled = false; +#endif +#ifdef ENABLE_CUSTOM_GEOMETRY +static constexpr bool customEnabled = true; +#else +static constexpr bool customEnabled = false; +#endif + namespace tinybvh { #if defined BVH_USEAVX || defined BVH_USENEON @@ -1891,8 +1912,10 @@ int32_t BVH::Intersect( Ray& ray ) const cost += C_TRAV; if (node->isLeaf()) { - if (vertIdx) for (uint32_t i = 0; i < node->triCount; i++, cost += C_INT) + if (indexedEnabled && vertIdx != 0) for (uint32_t i = 0; i < node->triCount; i++, cost += C_INT) IntersectTriIndexed( ray, verts, vertIdx, triIdx[node->leftFirst + i] ); + else if (customEnabled && customIntersect != 0) for (uint32_t i = 0; i < node->triCount; i++, cost += C_INT) + (*customIntersect)(ray, triIdx[node->leftFirst + i]); else for (uint32_t i = 0; i < node->triCount; i++, cost += C_INT) IntersectTri( ray, verts, triIdx[node->leftFirst + i] ); if (stackPtr == 0) break; else node = stack[--stackPtr]; @@ -1923,11 +1946,16 @@ bool BVH::IsOccluded( const Ray& ray ) const { if (node->isLeaf()) { - if (vertIdx) + if (indexedEnabled && vertIdx != 0) { for (uint32_t i = 0; i < node->triCount; i++) if (IndexedTriOccludes( ray, verts, vertIdx, triIdx[node->leftFirst + i] )) return true; } + else if (customEnabled && customIsOccluded != 0) + { + for (uint32_t i = 0; i < node->triCount; i++) + if ((*customIsOccluded)(ray, triIdx[node->leftFirst + i])) return true; + } else { for (uint32_t i = 0; i < node->triCount; i++) @@ -2765,10 +2793,10 @@ template void MBVH::Refit( const uint32_t nodeIdx ) } else { - for( unsigned i = 0; i < node.childCount; i++ ) Refit( node.child[i] ); + for (unsigned i = 0; i < node.childCount; i++) Refit( node.child[i] ); MBVHNode& firstChild = mbvhNode[node.child[0]]; bvhvec3 bmin = firstChild.aabbMin, bmax = firstChild.aabbMax; - for( unsigned i = 1; i < node.childCount; i++ ) + for (unsigned i = 1; i < node.childCount; i++) { MBVHNode& child = mbvhNode[node.child[i]]; bmin = tinybvh_min( bmin, child.aabbMin ); diff --git a/tiny_bvh_custom.cpp b/tiny_bvh_custom.cpp new file mode 100644 index 0000000..129bb73 --- /dev/null +++ b/tiny_bvh_custom.cpp @@ -0,0 +1,155 @@ +#define FENSTER_APP_IMPLEMENTATION +#define SCRWIDTH 800 +#define SCRHEIGHT 600 +#include "external/fenster.h" // https://github.com/zserge/fenster + +#define TINYBVH_IMPLEMENTATION +#include "tiny_bvh.h" +#include + +using namespace tinybvh; + +struct Sphere +{ + bvhvec3 pos; + float r; +}; + +BVH bvh; +int frameIdx = 0; +bvhvec4* triangles = 0; +Sphere* spheres = 0; +int verts = 0; + +// setup view pyramid for a pinhole camera +static bvhvec3 eye( -15.24f, 21.5f, 2.54f ), p1, p2, p3; +static bvhvec3 view = normalize( bvhvec3( 0.826f, -0.438f, -0.356f ) ); + +// callback for custom geometry: ray/sphere intersection +void sphereIntersect( tinybvh::Ray& ray, unsigned primID ) +{ + bvhvec3 oc = ray.O - spheres[primID].pos; + float b = dot( oc, ray.D ); + float r = spheres[primID].r; + float c = dot( oc, oc ) - r * r; + float t, d = b * b - c; + if (d <= 0) return; + d = sqrtf( d ), t = -b - d; + bool hit = t < ray.hit.t && t > 0; + if (hit) ray.hit.t = t, ray.hit.prim = primID; +} + +bool sphereIsOccluded( const tinybvh::Ray& ray, unsigned primID ) +{ + bvhvec3 oc = ray.O - spheres[primID].pos; + float b = dot( oc, ray.D ); + float r = spheres[primID].r; + float c = dot( oc, oc ) - r * r; + float t, d = b * b - c; + if (d <= 0) return false; + d = sqrtf( d ), t = -b - d; + return t < ray.hit.t && t > 0; +} + +void Init() +{ + // load raw vertex data for Crytek's Sponza + std::fstream s{ "./testdata/cryteksponza.bin", s.binary | s.in }; + s.seekp( 0 ); + s.read( (char*)&verts, 4 ); + printf( "Loading triangle data (%i tris).\n", verts ); + verts *= 3, triangles = (bvhvec4*)malloc64( verts * 16 ); + s.read( (char*)triangles, verts * 16 ); + s.close(); + + // turn the array of triangles into an array of spheres + spheres = new Sphere[verts / 3]; + for (int i = 0; i < verts / 3; i++) + { + bvhvec3 v0 = triangles[i * 3 + 0]; + bvhvec3 v1 = triangles[i * 3 + 1]; + bvhvec3 v2 = triangles[i * 3 + 2]; + spheres[i].r = min( 0.05f, tinybvh_min( length( v1 - v0 ), length( v2 - v0 ) ) ); + spheres[i].pos = (v0 + v1 + v2) * 0.33333f; + } + + // abuse the triangle array to hold sphere bounding boxes + for (int i = 0; i < verts / 3; i++) + { + bvhvec3 aabbMin = spheres[i].pos - bvhvec3( spheres[i].r ); + bvhvec3 aabbMax = spheres[i].pos + bvhvec3( spheres[i].r ); + triangles[i * 3 + 0] = aabbMin; + triangles[i * 3 + 1] = (aabbMax + aabbMin) * 0.5f; + triangles[i * 3 + 2] = aabbMax; // so, a degenerate tri: just a diagonal line. + } + + // build the BVH over the aabbs + bvh.Build( triangles, verts / 3 ); + + // set custom intersection callbacks + bvh.customIntersect = &sphereIntersect; + bvh.customIsOccluded = &sphereIsOccluded; +} + +bool UpdateCamera( float delta_time_s, fenster& f ) +{ + bvhvec3 right = normalize( cross( bvhvec3( 0, 1, 0 ), view ) ); + bvhvec3 up = 0.8f * cross( view, right ); + + // get camera controls. + bool moved = false; + if (f.keys['A']) eye += right * -1.0f * delta_time_s * 10, moved = true; + if (f.keys['D']) eye += right * delta_time_s * 10, moved = true; + if (f.keys['W']) eye += view * delta_time_s * 10, moved = true; + if (f.keys['S']) eye += view * -1.0f * delta_time_s * 10, moved = true; + if (f.keys['R']) eye += up * delta_time_s * 10, moved = true; + if (f.keys['F']) eye += up * -1.0f * delta_time_s * 10, moved = true; + if (f.keys[20]) view = normalize( view + right * -1.0f * delta_time_s ), moved = true; + if (f.keys[19]) view = normalize( view + right * delta_time_s ), moved = true; + if (f.keys[17]) view = normalize( view + up * -1.0f * delta_time_s ), moved = true; + if (f.keys[18]) view = normalize( view + up * delta_time_s ), moved = true; + + // recalculate right, up + right = normalize( cross( bvhvec3( 0, 1, 0 ), view ) ); + up = 0.8f * cross( view, right ); + bvhvec3 C = eye + 2 * view; + p1 = C - right + up, p2 = C + right + up, p3 = C - right - up; + return moved; +} + +void Tick( float delta_time_s, fenster& f, uint32_t* buf ) +{ + // handle user input and update camera + bool moved = UpdateCamera( delta_time_s, f ) || frameIdx++ == 0; + + // clear the screen with a debug-friendly color + for (int i = 0; i < SCRWIDTH * SCRHEIGHT; i++) buf[i] = 0xaaaaff; + + // trace rays + const bvhvec3 L = normalize( bvhvec3( 1, 2, 3 ) ); + for (int ty = 0; ty < SCRHEIGHT / 4; ty++) for (int tx = 0; tx < SCRWIDTH / 4; tx++) + { + for (int y = 0; y < 4; y++) for (int x = 0; x < 4; x++) + { + float u = (float)(tx * 4 + x) / SCRWIDTH, v = (float)(ty * 4 + y) / SCRHEIGHT; + bvhvec3 D = normalize( p1 + u * (p2 - p1) + v * (p3 - p1) - eye ); + Ray ray( eye, D, 1e30f ); + bvh.Intersect( ray ); + if (ray.hit.t < 10000) + { + int pixel_x = tx * 4 + x, pixel_y = ty * 4 + y, primIdx = ray.hit.prim; + bvhvec3 v0 = triangles[primIdx * 3]; + bvhvec3 v1 = triangles[primIdx * 3 + 1]; + bvhvec3 v2 = triangles[primIdx * 3 + 2]; + bvhvec3 N = normalize( cross( v1 - v0, v2 - v0 ) ); + int c = (int)(255.9f * fabs( dot( N, L ) )); + buf[pixel_x + pixel_y * SCRWIDTH] = c + (c << 8) + (c << 16); + } + } + } +} + +void Shutdown() +{ + // nothing here. +} \ No newline at end of file diff --git a/tiny_bvh_fenster.cpp b/tiny_bvh_fenster.cpp index 95e8aa0..bbc0acb 100644 --- a/tiny_bvh_fenster.cpp +++ b/tiny_bvh_fenster.cpp @@ -3,7 +3,7 @@ #define SCRHEIGHT 600 #include "external/fenster.h" // https://github.com/zserge/fenster -// #define LOADSCENE +#define LOADSCENE #define TINYBVH_IMPLEMENTATION #include "tiny_bvh.h" @@ -17,7 +17,8 @@ Ray* rays = 0; int* depths = 0; #ifdef LOADSCENE -bvhvec4* triangles = 0; +bvhvec4* vertices = 0; +uint32_t* indices = 0; const char scene[] = "cryteksponza.bin"; #else ALIGNED( 16 ) bvhvec4 vertices[259 /* level 3 */ * 6 * 2 * 49 * 3]{}; @@ -90,8 +91,8 @@ void Init() s.seekp( 0 ); s.read( (char*)&verts, 4 ); printf( "Loading triangle data (%i tris).\n", verts ); - verts *= 3, triangles = (bvhvec4*)malloc64( verts * 16 ); - s.read( (char*)triangles, verts * 16 ); + verts *= 3, vertices = (bvhvec4*)malloc64( verts * 16 ); + s.read( (char*)vertices, verts * 16 ); s.close(); #else // generate a sphere flake scene @@ -192,9 +193,6 @@ void Tick( float delta_time_s, fenster& f, uint32_t* buf ) // buf[pixel_x + pixel_y * SCRWIDTH] = depths[i] << 17; // render depth as red } } - - tinybvh::free64( rays ); - tinybvh::free64( depths ); } void Shutdown() diff --git a/tiny_bvh_test.sln b/tiny_bvh_test.sln index bb4456e..818df93 100644 --- a/tiny_bvh_test.sln +++ b/tiny_bvh_test.sln @@ -15,6 +15,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tiny_bvh_pt", "vcproj\tiny_ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tiny_bvh_gpu", "vcproj\tiny_bvh_gpu.vcxproj", "{4B0A219D-68D0-41EA-A0C4-8EB9E6171218}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tiny_bvh_custom", "vcproj\tiny_bvh_custom.vcxproj", "{05E87951-A43C-49D9-BC3B-5F6387285D2E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -71,6 +73,14 @@ Global {4B0A219D-68D0-41EA-A0C4-8EB9E6171218}.Release|x64.Build.0 = Release|x64 {4B0A219D-68D0-41EA-A0C4-8EB9E6171218}.Release|x86.ActiveCfg = Release|Win32 {4B0A219D-68D0-41EA-A0C4-8EB9E6171218}.Release|x86.Build.0 = Release|Win32 + {05E87951-A43C-49D9-BC3B-5F6387285D2E}.Debug|x64.ActiveCfg = Debug|x64 + {05E87951-A43C-49D9-BC3B-5F6387285D2E}.Debug|x64.Build.0 = Debug|x64 + {05E87951-A43C-49D9-BC3B-5F6387285D2E}.Debug|x86.ActiveCfg = Debug|Win32 + {05E87951-A43C-49D9-BC3B-5F6387285D2E}.Debug|x86.Build.0 = Debug|Win32 + {05E87951-A43C-49D9-BC3B-5F6387285D2E}.Release|x64.ActiveCfg = Release|x64 + {05E87951-A43C-49D9-BC3B-5F6387285D2E}.Release|x64.Build.0 = Release|x64 + {05E87951-A43C-49D9-BC3B-5F6387285D2E}.Release|x86.ActiveCfg = Release|Win32 + {05E87951-A43C-49D9-BC3B-5F6387285D2E}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/vcproj/tiny_bvh_custom.vcxproj b/vcproj/tiny_bvh_custom.vcxproj new file mode 100644 index 0000000..48337dd --- /dev/null +++ b/vcproj/tiny_bvh_custom.vcxproj @@ -0,0 +1,156 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + 17.0 + Win32Proj + {05E87951-A43C-49D9-BC3B-5F6387285D2E} + tinybvhcustom + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + NotSet + + + Application + false + v143 + true + NotSet + + + + + + + + + + + + + + + + + + + + + $(SolutionDir) + $(Platform)\$(ProjectName)\$(Configuration)\ + + + $(SolutionDir) + $(Platform)\$(ProjectName)\$(Configuration)\ + + + + Level3 + true + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + + + + + Level3 + true + _DEBUG;_WINDOWS;%(PreprocessorDefinitions);_CRT_SECURE_NO_WARNINGS + true + stdcpp20 + + + + + Windows + true + ../external/embree/lib + $(CoreLibraryDependencies);%(AdditionalDependencies);embree4.lib;tbb12.lib + + + + + Level3 + true + true + true + NDEBUG;_WINDOWS;%(PreprocessorDefinitions);_CRT_SECURE_NO_WARNINGS + true + stdcpp20 + + + + + Windows + true + true + true + ../external/embree/lib + $(CoreLibraryDependencies);%(AdditionalDependencies);embree4.lib;tbb12.lib + + + + + + \ No newline at end of file diff --git a/vcproj/tiny_bvh_custom.vcxproj.filters b/vcproj/tiny_bvh_custom.vcxproj.filters new file mode 100644 index 0000000..4c394f0 --- /dev/null +++ b/vcproj/tiny_bvh_custom.vcxproj.filters @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/vcproj/tiny_bvh_custom.vcxproj.user b/vcproj/tiny_bvh_custom.vcxproj.user new file mode 100644 index 0000000..58dbbfa --- /dev/null +++ b/vcproj/tiny_bvh_custom.vcxproj.user @@ -0,0 +1,11 @@ + + + + $(SolutionDir) + WindowsLocalDebugger + + + $(SolutionDir) + WindowsLocalDebugger + + \ No newline at end of file