diff --git a/res/org/lwjgl/demo/opengl/raytracing/cubetrace/grass_high.png b/res/org/lwjgl/demo/opengl/raytracing/cubetrace/grass_high.png new file mode 100644 index 00000000..0af4567e Binary files /dev/null and b/res/org/lwjgl/demo/opengl/raytracing/cubetrace/grass_high.png differ diff --git a/res/org/lwjgl/demo/opengl/raytracing/cubetrace/kdtreeseparate32grass.glsl b/res/org/lwjgl/demo/opengl/raytracing/cubetrace/kdtreeseparate32grass.glsl new file mode 100644 index 00000000..688aaacf --- /dev/null +++ b/res/org/lwjgl/demo/opengl/raytracing/cubetrace/kdtreeseparate32grass.glsl @@ -0,0 +1,178 @@ +/* + * Copyright LWJGL. All rights reserved. + * License terms: http://lwjgl.org/license.php + */ +#version 430 core +#if GL_NV_gpu_shader5 +#extension GL_NV_gpu_shader5 : enable +#elif GL_AMD_gpu_shader_int16 +#extension GL_AMD_gpu_shader_int16 : enable +#endif + +#define NO_AXIS uint(-1u) +#define NO_NODE uint(-1u) +#define MAX_DESCEND 200u +#define MAX_ROPES 100u +#define SKY_COLOR vec3(0.96, 0.98, 1.0) + +#define SIDE_Y_NEG 2u +#define SIDE_Z_NEG 4u + +layout(binding = 0, rgba8) writeonly uniform image2D framebufferImage; +uniform vec3 cam[5]; +uniform ivec2 off = ivec2(0,0), cbwidth = ivec2(1,1); +uniform int scale = 1; +uniform uint startNodeIdx = 0u; +layout(location = 0) uniform sampler2D tex; + +struct node { + uvec4 rightOrLeaf_SplitAxis_SplitPos_GlobalOffset; + uint ropes[6]; +}; +layout(std430, binding = 0) readonly restrict buffer Nodes { + node[] nodes; +}; + +struct nodegeom { + u8vec3 min, max; +}; +layout(std430, binding = 1) readonly restrict buffer NodeGeoms { + nodegeom[] nodegeoms; +}; + +struct leafnode { + uvec2 voxels; +}; +layout(std430, binding = 2) readonly restrict buffer LeafNodes { + leafnode[] leafnodes; +}; + +layout(std430, binding = 3) readonly restrict buffer Voxels { + u8vec4[] voxels; +}; + +const bool intersectGrass(const in vec3 origin, const in vec3 dir, const in vec3 bmin, const in vec3 bmax, + inout float t, out vec2 uv) { + vec3 p0l0 = (bmin + bmax) * 0.5 - origin; + float tf = (p0l0.x + p0l0.z) / (dir.x + dir.z), tf2 = (p0l0.z - p0l0.x) / (dir.z - dir.x); + vec3 p1 = origin + tf * dir, p2 = origin + tf2 * dir; + tf = mix(t, tf, all(lessThanEqual(p1, bmax)) && all(greaterThanEqual(p1, bmin)) && tf >= 0.0 && tf < t); + tf2 = mix(t, tf2, all(lessThanEqual(p2, bmax)) && all(greaterThanEqual(p2, bmin)) && tf2 >= 0.0 && tf2 < t); + vec2 uv1 = vec2(p1.z - bmin.z, 1.0 - p1.y + bmin.y), uv2 = vec2(p2.z - bmin.z, 1.0 - p2.y + bmin.y); + bool hit1 = tf < t && texture(tex, uv1).a > 0.8, hit2 = tf2 < t && texture(tex, uv2).a > 0.8; + if (hit1 || hit2) { + uv = mix(uv2, uv1, hit1); + t = min(tf, tf2); + return true; + } + return false; +} + +const bool intersectVoxels(const in vec3 origin, const in vec3 dir, const uint firstVoxel, + const uint numVoxels, inout float t, out uint vindex, out vec2 uv) { + bool hit = false; + for (uint i = 0; i < numVoxels; i++) { + const u8vec4 v = voxels[i + firstVoxel]; + const vec3 vx = vec3(v.xyz)*scale; + if (intersectGrass(origin, dir, vx, vx + vec3(scale), t, uv)) { + hit = true; + vindex = i + firstVoxel; + } + } + return hit; +} + +const float exitSide(const in vec3 origin, const in vec3 invdir, + const in uvec3 boxMin, const in uvec3 boxMax, + out uint exitSide) { + const bvec3 lt = lessThan(invdir, vec3(0.0)); + const vec3 tmax = (mix(vec3(boxMax)+vec3(1.0), vec3(boxMin), lt)*scale - origin) * invdir; + const ivec3 signs = ivec3(lt); + vec2 vals; + vals = mix(vec2(tmax.y, SIDE_Y_NEG + signs.y), vec2(tmax.x, signs.x), tmax.y > tmax.x); + vals = mix(vec2(tmax.z, SIDE_Z_NEG + signs.z), vals, tmax.z > vals.x); + exitSide = uint(vals.y); + return vals.x; +} + +const vec2 intersectBox(const in vec3 origin, const in vec3 invdir, const in u8vec3 bmin, const in u8vec3 bmax) { + const bvec3 lt = lessThan(invdir, vec3(0.0)); + const vec3 m1 = (vec3(bmin)*scale - origin) * invdir, m2 = ((vec3(bmax) + vec3(1.0))*scale - origin) * invdir; + const vec3 tmin = mix(m1, m2, lt), tmax = mix(m2, m1, lt); + return vec2(max(max(tmin.x, tmin.y), tmin.z), min(min(tmax.x, tmax.y), tmax.z)); +} + +struct scenehitinfo { + float t; + uint vindex; + uint descends, ropes; + vec2 uv; +}; +const bool intersectScene(in uint nodeIdx, const in vec3 origin, const in vec3 dir, + const in vec3 invdir, out scenehitinfo shinfo) { + node n = nodes[nodeIdx]; + vec3 o = origin; + nodegeom ng = nodegeoms[nodeIdx]; + vec2 lambda = intersectBox(o, invdir, ng.min, ng.max); + if (lambda.y < 0.0 || lambda.x > lambda.y) + return false; + lambda.x = max(lambda.x, 0.0); + shinfo.descends = 0u; + shinfo.ropes = 0u; + shinfo.t = 1.0/0.0; + shinfo.uv = vec2(0.0); + uvec3 nmin = ng.min, nmax = ng.max; + while (true) { + vec3 pEntry = dir * lambda.x + o; + while (n.rightOrLeaf_SplitAxis_SplitPos_GlobalOffset.y != NO_AXIS) { + if (float(n.rightOrLeaf_SplitAxis_SplitPos_GlobalOffset.z)*scale <= pEntry[n.rightOrLeaf_SplitAxis_SplitPos_GlobalOffset.y]) { + nodeIdx = n.rightOrLeaf_SplitAxis_SplitPos_GlobalOffset.x; + nmin[n.rightOrLeaf_SplitAxis_SplitPos_GlobalOffset.y] = n.rightOrLeaf_SplitAxis_SplitPos_GlobalOffset.z; + } else { + nodeIdx = nodeIdx + 1u; + nmax[n.rightOrLeaf_SplitAxis_SplitPos_GlobalOffset.y] = n.rightOrLeaf_SplitAxis_SplitPos_GlobalOffset.z - 1u; + } + n = nodes[nodeIdx]; + if (shinfo.descends++ > MAX_DESCEND) + return false; + } + if (n.rightOrLeaf_SplitAxis_SplitPos_GlobalOffset.x != NO_NODE) { + leafnode ln = leafnodes[n.rightOrLeaf_SplitAxis_SplitPos_GlobalOffset.x]; + if (intersectVoxels(o, dir, ln.voxels.x, ln.voxels.y, shinfo.t, shinfo.vindex, shinfo.uv)) + return true; + } + uint exit; + lambda.x = exitSide(o, invdir, nmin, nmax, exit); + nodeIdx = n.ropes[exit]; + if (nodeIdx == NO_NODE) + return false; + n = nodes[nodeIdx]; + ng = nodegeoms[nodeIdx]; + nmin = ng.min; + nmax = ng.max; + if (shinfo.ropes++ > MAX_ROPES) + return false; + o = origin; + } + return false; +} + +layout (local_size_x = 8, local_size_y = 8) in; +void main(void) { + const ivec2 pix = ivec2(gl_GlobalInvocationID.xy) * cbwidth + off; + const ivec2 size = imageSize(framebufferImage); + if (any(greaterThanEqual(pix, size))) + return; + const vec2 p = (vec2(pix) + vec2(0.5)) / vec2(size); + const vec3 dir = normalize(mix(mix(cam[1], cam[2], p.y), mix(cam[3], cam[4], p.y), p.x)); + vec3 acc = vec3(0.0); + vec3 att = vec3(1.0); + scenehitinfo shinfo; + if (intersectScene(startNodeIdx, cam[0], dir, 1.0/dir, shinfo)) { + vec3 col = texture(tex, shinfo.uv).rgb; + acc += col;// * 4.6 * vec3(float(shinfo.ropes) * 1.4E-2, float(shinfo.descends) * 4E-3, 0.0); + } else { + acc += SKY_COLOR; + } + imageStore(framebufferImage, pix, vec4(acc, 1.0)); +} diff --git a/res/org/lwjgl/demo/opengl/raytracing/cubetrace/quad.fs.glsl b/res/org/lwjgl/demo/opengl/raytracing/cubetrace/quad.fs.glsl deleted file mode 100644 index 3232c3d1..00000000 --- a/res/org/lwjgl/demo/opengl/raytracing/cubetrace/quad.fs.glsl +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright LWJGL. All rights reserved. - * License terms: https://www.lwjgl.org/license - */ -#version 330 core - -uniform sampler2D tex; -in vec2 texcoord; -out vec4 color; - -void main(void) { - color = texture(tex, texcoord); -} diff --git a/res/org/lwjgl/demo/opengl/raytracing/cubetrace/quad.vs.glsl b/res/org/lwjgl/demo/opengl/raytracing/cubetrace/quad.vs.glsl deleted file mode 100644 index 9a8a0d23..00000000 --- a/res/org/lwjgl/demo/opengl/raytracing/cubetrace/quad.vs.glsl +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright LWJGL. All rights reserved. - * License terms: https://www.lwjgl.org/license - */ -#version 330 core - -in vec2 vertex; -out vec2 texcoord; - -void main(void) { - gl_Position = vec4(vertex, 0.0, 1.0); - texcoord = vertex * 0.5 + vec2(0.5, 0.5); -} diff --git a/res/org/lwjgl/demo/opengl/raytracing/cubetrace/raytracing.glsl b/res/org/lwjgl/demo/opengl/raytracing/cubetrace/raytracing.glsl deleted file mode 100644 index 06a2ed96..00000000 --- a/res/org/lwjgl/demo/opengl/raytracing/cubetrace/raytracing.glsl +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright LWJGL. All rights reserved. - * License terms: https://www.lwjgl.org/license - */ -#version 430 core - -#define NO_NODE -1 -#define ERROR_COLOR vec3(1.0, 0.0, 1.0) -#define MAX_FOLLOWS 750 - -layout(binding = 0, rgba8) writeonly uniform image2D framebufferImage; -uniform vec3 eye, ray00, ray01, ray10, ray11; -uniform ivec2 off, cbwidth; - -struct node { - vec3 min; uint left; - vec3 max; uint right; - uint parent, firstVoxel, numVoxels; -}; -layout(std430, binding = 0) readonly buffer Nodes { - node[] nodes; -}; - -struct voxel { - uvec3 p; uint c; -}; -layout(std430, binding = 1) readonly buffer Voxels { - voxel[] voxels; -}; - -vec3 decodeColor(uint col) { - return vec3(col & 0x3FFu, (col >> 10u) & 0x3FF, (col >> 20u) & 0x3FF) / 1024.0; -} - -bool intersectBox(const in vec3 origin, const in vec3 invdir, - const in vec3 boxMin, const in vec3 boxMax, inout float t) { - bvec3 lt = lessThan(invdir, vec3(0.0)); - vec3 m1 = (boxMin - origin) * invdir, m2 = (boxMax - origin) * invdir; - vec3 tmin = mix(m1, m2, lt), tmax = mix(m2, m1, lt); - float mint = max(max(tmin.x, tmin.y), tmin.z); - float maxt = min(min(tmax.x, tmax.y), tmax.z); - bool cond = mint < t && maxt >= 0.0 && mint < maxt; - t = mix(t, mint, cond); - return cond; -} - -bool intersectVoxels(const in vec3 origin, const in vec3 invdir, const uint firstVoxel, - const uint numVoxels, inout float t, out voxel hitvoxel) { - bool hit = false; - for (uint i = 0; i < numVoxels; i++) { - const voxel v = voxels[i + firstVoxel]; - if (intersectBox(origin, invdir, vec3(v.p), vec3(v.p) + vec3(1.0), t)) { - hit = true; - hitvoxel = v; - } - } - return hit; -} - -vec3 normalForVoxel(const in vec3 hit, const in vec3 dir, const in uvec3 v) { - float dx = dir.x > 0.0 ? abs(hit.x - float(v.x)) : abs(hit.x - float(v.x+1)); - float dy = dir.y > 0.0 ? abs(hit.y - float(v.y)) : abs(hit.y - float(v.y+1)); - float dz = dir.z > 0.0 ? abs(hit.z - float(v.z)) : abs(hit.z - float(v.z+1)); - if (dx < dy && dx < dz) - return dir.x < 0.0 ? vec3(1.0, 0.0, 0.0) : vec3(-1.0, 0.0, 0.0); - else if (dy < dz) - return dir.y < 0.0 ? vec3(0.0, 1.0, 0.0) : vec3(0.0, -1.0, 0.0); - else - return dir.z < 0.0 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 0.0, -1.0); -} - -uint closestChild(const in vec3 origin, const in node n, inout uint leftRightStack) { - if (n.left == NO_NODE) - return NO_NODE; - const node left = nodes[n.left], right = nodes[n.right]; - const vec3 midLeft = (left.min + left.max) * vec3(0.5); - const vec3 midRight = (right.min + right.max) * vec3(0.5); - const vec3 dl = origin - midLeft, dr = origin - midRight; - uint res; - if (dot(dl, dl) < dot(dr, dr)) { - leftRightStack <<= 1; - res = n.left; - } else { - leftRightStack = leftRightStack << 1 | 1; - res = n.right; - } - return res; -} - -bool processNextFarChild(inout uint nearFarStack, inout uint leftRightStack, - const in uint pidx, inout uint nextIdx) { - node parent = nodes[pidx]; - while ((nearFarStack & 1u) == 1u) { - nearFarStack >>= 1; - leftRightStack >>= 1; - if (parent.parent == NO_NODE) - return false; - parent = nodes[parent.parent]; - } - nextIdx = (leftRightStack & 1u) == 0u ? parent.right : parent.left; - nearFarStack |= 1; - return true; -} - -vec3 trace(const in vec3 origin, const in vec3 dir, const in vec3 invdir) { - float nt = 1.0/0.0, bt = 1.0/0.0; - vec3 normal = vec3(0.0), col = vec3(1.0); - uint nextIdx = 0u, iterations = 0u, leftRightStack = 0u, nearFarStack = 0u; - while (true) { - if (iterations++ > MAX_FOLLOWS) - return ERROR_COLOR; - const node next = nodes[nextIdx]; - if (!intersectBox(origin, invdir, next.min, next.max, bt)) { - if (!processNextFarChild(nearFarStack, leftRightStack, next.parent, nextIdx)) - break; - } else { - if (next.numVoxels > 0) { - voxel hitvoxel; - if (intersectVoxels(origin, invdir, next.firstVoxel, next.numVoxels, nt, hitvoxel)) { - normal = normalForVoxel(origin + nt * dir, dir, hitvoxel.p); - col = decodeColor(hitvoxel.c); - } - if (!processNextFarChild(nearFarStack, leftRightStack, next.parent, nextIdx)) - break; - } else { - nextIdx = closestChild(origin, next, leftRightStack); - nearFarStack <<= 1; - } - bt = nt; - } - } - return col * max(0.2, dot(normal, vec3(0.4, 0.82, 0.4))); -} - -layout (local_size_x = 8, local_size_y = 8) in; - -void main(void) { - ivec2 pix = ivec2(gl_GlobalInvocationID.xy) * cbwidth + off; - ivec2 size = imageSize(framebufferImage); - if (any(greaterThanEqual(pix, size))) - return; - vec2 p = (vec2(pix) + vec2(0.5)) / vec2(size); - vec3 dir = normalize(mix(mix(ray00, ray01, p.y), mix(ray10, ray11, p.y), p.x)); - vec3 color = trace(eye, dir, 1.0 / dir); - imageStore(framebufferImage, pix, vec4(color, 1.0)); -} diff --git a/res/org/lwjgl/demo/opengl/raytracing/cubetrace/raytracing_16t.glsl b/res/org/lwjgl/demo/opengl/raytracing/cubetrace/raytracing_16t.glsl deleted file mode 100644 index 8d4368bc..00000000 --- a/res/org/lwjgl/demo/opengl/raytracing/cubetrace/raytracing_16t.glsl +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright LWJGL. All rights reserved. - * License terms: https://www.lwjgl.org/license - */ -#version 430 core -#if GL_NV_gpu_shader5 -#extension GL_NV_gpu_shader5 : enable -#elif GL_AMD_gpu_shader_int16 -#extension GL_AMD_gpu_shader_int16 : enable -#endif - -#define NO_NODE -1u -#define ERROR_COLOR vec3(1.0, 0.0, 1.0) -#define MAX_ITERATIONS 750u - -layout(binding = 0, rgba8) writeonly uniform image2D framebufferImage; -uniform vec3 eye, ray00, ray01, ray10, ray11; -uniform ivec2 off, cbwidth; - -struct node { - u16vec3 min; uint8_t numVoxels; - u16vec3 max; - uint left, right, parent, firstVoxel; -}; -layout(std430, binding = 0) readonly buffer Nodes { - node[] nodes; -}; - -struct voxel { - u16vec3 p; uint16_t c; -}; -layout(std430, binding = 1) readonly buffer Voxels { - voxel[] voxels; -}; - -vec3 decodeColor(uint16_t col) { - return vec3(col & 0x1Fu, (col >> 5u) & 0x1Fu, (col >> 10u) & 0x1Fu) / 31.0; -} - -bool intersectBox(const in vec3 origin, const in vec3 invdir, - const in u16vec3 bmin, const in u16vec3 bmax, inout float t) { - bvec3 lt = lessThan(invdir, vec3(0.0)); - vec3 m1 = (bmin - origin) * invdir, m2 = (bmax - origin) * invdir; - vec3 tmin = mix(m1, m2, lt), tmax = mix(m2, m1, lt); - float mint = max(max(tmin.x, tmin.y), tmin.z); - float maxt = min(min(tmax.x, tmax.y), tmax.z); - bool cond = mint < t && maxt >= 0.0 && mint < maxt; - t = mix(t, mint, cond); - return cond; -} - -bool intersectVoxels(const in vec3 origin, const in vec3 invdir, const uint firstVoxel, - const uint8_t numVoxels, inout float t, out voxel hitvoxel) { - bool hit = false; - for (uint i = 0; i < numVoxels; i++) { - const voxel v = voxels[i + firstVoxel]; - if (intersectBox(origin, invdir, v.p, v.p+u16vec3(1), t)) { - hit = true; - hitvoxel = v; - } - } - return hit; -} - -vec3 normalForVoxel(const in vec3 hit, const in vec3 dir, const in u16vec3 v) { - float dx = dir.x > 0.0 ? abs(hit.x - float(v.x)) : abs(hit.x - float(v.x+1u)); - float dy = dir.y > 0.0 ? abs(hit.y - float(v.y)) : abs(hit.y - float(v.y+1u)); - float dz = dir.z > 0.0 ? abs(hit.z - float(v.z)) : abs(hit.z - float(v.z+1u)); - if (dx < dy && dx < dz) - return dir.x < 0.0 ? vec3(1.0, 0.0, 0.0) : vec3(-1.0, 0.0, 0.0); - else if (dy < dz) - return dir.y < 0.0 ? vec3(0.0, 1.0, 0.0) : vec3(0.0, -1.0, 0.0); - else - return dir.z < 0.0 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 0.0, -1.0); -} - -uint closestChild(const in vec3 origin, const in node n, inout uint leftRightStack) { - if (n.left == NO_NODE) - return NO_NODE; - const node left = nodes[n.left], right = nodes[n.right]; - const vec3 midLeft = (left.min + left.max) * vec3(0.5); - const vec3 midRight = (right.min + right.max) * vec3(0.5); - const vec3 dl = origin - midLeft, dr = origin - midRight; - uint res; - if (dot(dl, dl) < dot(dr, dr)) { - leftRightStack <<= 1; - res = n.left; - } else { - leftRightStack = leftRightStack << 1 | 1; - res = n.right; - } - return res; -} - -bool processNextFarChild(inout uint nearFarStack, inout uint leftRightStack, - const in uint pidx, inout uint nextIdx) { - node parent = nodes[pidx]; - while ((nearFarStack & 1u) == 1u) { - nearFarStack >>= 1; - leftRightStack >>= 1; - if (parent.parent == NO_NODE) - return false; - parent = nodes[parent.parent]; - } - nextIdx = (leftRightStack & 1u) == 0u ? parent.right : parent.left; - nearFarStack |= 1; - return true; -} - -vec3 trace(const in vec3 origin, const in vec3 dir, const in vec3 invdir) { - float nt = 1.0/0.0, bt = 1.0/0.0; - vec3 normal = vec3(0.0), col = vec3(1.0); - uint nextIdx = 0u, iterations = 0u; - uint leftRightStack = 0u, nearFarStack = 0u; - while (true) { - if (iterations++ > MAX_ITERATIONS) - return ERROR_COLOR; - const node next = nodes[nextIdx]; - if (!intersectBox(origin, invdir, next.min, next.max, bt)) { - if (!processNextFarChild(nearFarStack, leftRightStack, next.parent, nextIdx)) - break; - } else { - if (next.numVoxels > 0u) { - voxel hitvoxel; - if (intersectVoxels(origin, invdir, next.firstVoxel, next.numVoxels, nt, hitvoxel)) { - normal = normalForVoxel(origin + nt * dir, dir, hitvoxel.p); - col = decodeColor(hitvoxel.c); - } - if (!processNextFarChild(nearFarStack, leftRightStack, next.parent, nextIdx)) - break; - } else { - nextIdx = closestChild(origin, next, leftRightStack); - nearFarStack <<= 1; - } - bt = nt; - } - } - return col * max(0.2, dot(normal, vec3(0.4, 0.82, 0.4))); -} - -layout (local_size_x = 8, local_size_y = 8) in; - -void main(void) { - ivec2 pix = ivec2(gl_GlobalInvocationID.xy) * cbwidth + off; - ivec2 size = imageSize(framebufferImage); - if (any(greaterThanEqual(pix, size))) - return; - vec2 p = (vec2(pix) + vec2(0.5)) / vec2(size); - vec3 dir = normalize(mix(mix(ray00, ray01, p.y), mix(ray10, ray11, p.y), p.x)); - vec3 color = trace(eye, dir, 1.0 / dir); - imageStore(framebufferImage, pix, vec4(color, 1.0)); -} diff --git a/src/org/lwjgl/demo/opengl/raytracing/CubeTrace.java b/src/org/lwjgl/demo/opengl/raytracing/CubeTrace.java index 820eccce..6cbad4c4 100644 --- a/src/org/lwjgl/demo/opengl/raytracing/CubeTrace.java +++ b/src/org/lwjgl/demo/opengl/raytracing/CubeTrace.java @@ -1,196 +1,87 @@ -/* - * Copyright LWJGL. All rights reserved. - * License terms: https://www.lwjgl.org/license +/* + * Copyright LWJGL. All rights reserved. + * License terms: https://www.lwjgl.org/license */ package org.lwjgl.demo.opengl.raytracing; import static org.lwjgl.glfw.GLFW.*; import static org.lwjgl.opengl.GL43C.*; +import static org.lwjgl.opengl.NVDrawTexture.*; +import static org.lwjgl.opengl.NVFillRectangle.*; +import static org.lwjgl.stb.STBImage.*; import static org.lwjgl.system.MemoryUtil.*; -import java.io.IOException; +import java.io.*; import java.lang.Math; import java.nio.*; import java.util.*; import org.joml.*; -import org.lwjgl.demo.opengl.raytracing.CubeTrace.IBVHMortonTree.*; import org.lwjgl.demo.opengl.util.*; import org.lwjgl.glfw.*; import org.lwjgl.opengl.*; import org.lwjgl.system.*; -/** - * Tracing Minecraft-like axis-aligned cube worlds using a BVH tree with morton - * code partitioning and bitstack traversal. - *

- * Axis-aligned cubes are really a natural fit for BVH trees, since they align - * very well with the BVH subdivisions and a terrain with cubes at regular - * positions can be subdivided optimally in log2(N) iterations. - * - * @author Kai Burjack - */ public class CubeTrace { - /** - * Bounding Volume Hierarchy for integer lattices using morton code - * partitioning. - * - * @author Kai Burjack - */ - public static class IBVHMortonTree { - public static class Voxel implements Comparable { - public int x, y, z; - public long morton; - public float r, g, b; - - public Voxel(int x, int y, int z, float r, float g, float b) { - this.x = x; - this.y = y; - this.z = z; - this.r = r; - this.g = g; - this.b = b; - } - - @Override - public int compareTo(Voxel o) { - return Long.compare(morton, o.morton); - } - } - - public static final int MAX_POINTS_IN_NODE = 8; - public IBVHMortonTree parent; - public IBVHMortonTree left; - public IBVHMortonTree right; - public List voxels; - public int index; - public int first, last = -1; - public int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE, minZ = Integer.MAX_VALUE; - public int maxX = Integer.MIN_VALUE, maxY = Integer.MIN_VALUE, maxZ = Integer.MIN_VALUE; - - IBVHMortonTree(IBVHMortonTree parent) { - this.parent = parent; - } - - IBVHMortonTree(IBVHMortonTree parent, List voxels, int first, int last) { - this.parent = parent; - this.first = first; - this.last = last; - this.voxels = voxels; - voxels.forEach(v -> { - this.minX = this.minX < v.x ? this.minX : v.x; - this.minY = this.minY < v.y ? this.minY : v.y; - this.minZ = this.minZ < v.z ? this.minZ : v.z; - this.maxX = this.maxX > v.x ? this.maxX : v.x; - this.maxY = this.maxY > v.y ? this.maxY : v.y; - this.maxZ = this.maxZ > v.z ? this.maxZ : v.z; - }); - } - - private static long expandBits(long v) { - v &= 0x00000000001fffffL; - v = (v | v << 32L) & 0x001f00000000ffffL; - v = (v | v << 16L) & 0x001f0000ff0000ffL; - v = (v | v << 8L) & 0x010f00f00f00f00fL; - v = (v | v << 4L) & 0x10c30c30c30c30c3L; - v = (v | v << 2L) & 0x1249249249249249L; - return v; - } - - private static long morton3d(int x, int y, int z) { - return (expandBits(y) << 2L) + (expandBits(z) << 1L) + expandBits(x); - } - - private static int findSplit(List sortedMortonCodes, int first, int last) { - long firstCode = sortedMortonCodes.get(first).morton; - long lastCode = sortedMortonCodes.get(last).morton; - if (firstCode == lastCode) - return first + last >> 1; - int commonPrefix = Long.numberOfLeadingZeros(firstCode ^ lastCode); - int split = first; - int step = last - first; - do { - step = step + 1 >> 1; - int newSplit = split + step; - if (newSplit < last) { - long splitCode = sortedMortonCodes.get(newSplit).morton; - int splitPrefix = Long.numberOfLeadingZeros(firstCode ^ splitCode); - if (splitPrefix > commonPrefix) - split = newSplit; - } - } while (step > 1); - return split; - } - - public static IBVHMortonTree build(List voxels) { - voxels.forEach(v -> v.morton = morton3d(v.x, v.y, v.z)); - Collections.sort(voxels); - return build(null, voxels, 0, voxels.size() - 1); - } - - private static IBVHMortonTree build(IBVHMortonTree parent, List mortonSortedTriangles, int first, - int last) { - if (first > last - MAX_POINTS_IN_NODE) - return new IBVHMortonTree(parent, mortonSortedTriangles.subList(first, last + 1), first, last); - int split = findSplit(mortonSortedTriangles, first, last); - IBVHMortonTree tree = new IBVHMortonTree(parent); - tree.left = build(tree, mortonSortedTriangles, first, split); - tree.right = build(tree, mortonSortedTriangles, split + 1, last); - tree.minX = java.lang.Math.min(tree.left.minX, tree.right.minX); - tree.minY = java.lang.Math.min(tree.left.minY, tree.right.minY); - tree.minZ = java.lang.Math.min(tree.left.minZ, tree.right.minZ); - tree.maxX = java.lang.Math.max(tree.left.maxX, tree.right.maxX); - tree.maxY = java.lang.Math.max(tree.left.maxY, tree.right.maxY); - tree.maxZ = java.lang.Math.max(tree.left.maxZ, tree.right.maxZ); - return tree; - } - } - private long window; private int width = 1920; private int height = 1080; private boolean resetFramebuffer; private int cbWidth = 3; - private byte[] samplePattern = samplePattern(); private int cbPixel; - private int levelWidth = 2048; - private int levelDepth = 1024; - private int levelHeight = 64; + private byte[] samplePattern = samplePattern(); + private int scale = 1; + private int levelWidth = 256 / scale; + private int levelDepth = 256 / scale; + private int levelHeight = 128 / scale; private int pttex; private int vao; - private int computeProgram; + private int tex; + private int finalGatherProgram; private int quadProgram; - private int sampler; - private int eyeUniform; - private int offUniform, cbwidthUniform; - private int ray00Uniform, ray10Uniform, ray01Uniform, ray11Uniform; - private int framebufferImageBinding; + private int camUniform; + private KDTreei.Node cameraNode; + private int startNodeIdxUniform; + private int prevVPUniform; + private int texUniform; + private int offUniform, cbwidthUniform, scaleUniform; private int nodesSsbo; - private int nodesSsboBinding; + private int nodeGeomsSsbo; + private int leafNodesSsbo; private int voxelsSsbo; - private int voxelsSsboBinding; - private int workGroupSizeX; - private int workGroupSizeY; + private int finalGatherTimeUniform; + private int finalGatherWorkGroupSizeX; + private int finalGatherWorkGroupSizeY; private float mouseX, mouseY; - private boolean mouseDown; + private boolean leftMouseDown; private boolean[] keydown = new boolean[GLFW.GLFW_KEY_LAST + 1]; private Matrix4d projMatrix = new Matrix4d(); private Matrix4d viewMatrix = new Matrix4d(); + private Matrix4d viewProjMatrix = new Matrix4d(); + private Matrix4d prevViewProjMatrix = new Matrix4d(); private Matrix4d invViewProjMatrix = new Matrix4d(); - private Vector3d tmpVector = new Vector3d(); - private Vector3d cameraPosition = new Vector3d(levelWidth * 0.9f, levelHeight * 1.4f, levelDepth * 0.5f); - private Vector3d cameraLookAt = new Vector3d(levelWidth * 0.5f, levelHeight * 0.5f, levelDepth * 0.5f); - private Vector3d cameraUp = new Vector3d(0.0f, 1.0f, 0.0f); + private Vector3d tmpVector = new Vector3d(), tmpVector2 = new Vector3d(); + private Vector3d cameraPosition = new Vector3d(levelWidth * 0.3f * scale, levelHeight * 0.4f * scale, + levelDepth * 0.5f * scale); + private Vector3d cameraLookDir = new Vector3d(0, -0.1f, 1).normalize(); + private Quaterniond quaternion = new Quaterniond(); + private KDTreei root; private GLFWErrorCallback errCallback; private GLFWKeyCallback keyCallback; private GLFWFramebufferSizeCallback fbCallback; private GLFWCursorPosCallback cpCallback; private GLFWMouseButtonCallback mbCallback; private Callback debugProc; - private long lastTime = System.nanoTime(); + private GLCapabilities caps; + private boolean GL_NV_draw_texture, GL_NV_fill_rectangle; private int frame = 0; - private float avgTime = 0.0f; - private boolean hasShortsInShader; + private int[] viewport = { 0, 0, width, height }; + private List voxels; + + { + cameraPosition.set(2.033E+2f, 4.511E+0f, 1.232E+2f); + cameraLookDir.set(-1.504E-1f, 4.513E-2f, 9.876E-1f); + } private void init() throws IOException { glfwSetErrorCallback(errCallback = GLFWErrorCallback.createPrint(System.err)); @@ -202,13 +93,12 @@ private void init() throws IOException { glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - window = glfwCreateWindow(width, height, "Tracing axis-aligned cubes with BVH", NULL, NULL); +// long monitor = glfwGetMonitors().get(1); +// GLFWVidMode vidmode = glfwGetVideoMode(monitor); +// window = glfwCreateWindow(vidmode.width(), vidmode.height(), "Raytracing water", monitor, NULL); + window = glfwCreateWindow(width, height, "Raytracing water", NULL, NULL); if (window == NULL) throw new AssertionError("Failed to create the GLFW window"); - System.out.println("Press WSAD, LCTRL, SPACE to move around in the scene."); - System.out.println("Press Q/E to roll left/right."); - System.out.println("Hold down left shift to move faster."); - System.out.println("Move the mouse to look around."); glfwSetKeyCallback(window, keyCallback = new GLFWKeyCallback() { public void invoke(long window, int key, int scancode, int action, int mods) { if (key == GLFW_KEY_ESCAPE && action != GLFW_RELEASE) @@ -216,11 +106,18 @@ public void invoke(long window, int key, int scancode, int action, int mods) { if (key == -1) return; keydown[key] = action == GLFW_PRESS || action == GLFW_REPEAT; + if (action == GLFW_PRESS && key == GLFW_KEY_R) { + frame = 0; + } else if (action == GLFW_PRESS && key == GLFW_KEY_C) { + System.out.println(cameraPosition); + System.out.println(cameraLookDir); + } } }); glfwSetFramebufferSizeCallback(window, fbCallback = new GLFWFramebufferSizeCallback() { public void invoke(long window, int width, int height) { - if (width > 0 && height > 0 && (CubeTrace.this.width != width || CubeTrace.this.height != height)) { + if (width > 0 && height > 0 && (CubeTrace.this.width != width + || CubeTrace.this.height != height)) { CubeTrace.this.width = width; CubeTrace.this.height = height; CubeTrace.this.resetFramebuffer = true; @@ -229,12 +126,12 @@ public void invoke(long window, int width, int height) { }); glfwSetCursorPosCallback(window, cpCallback = new GLFWCursorPosCallback() { public void invoke(long window, double x, double y) { - if (mouseDown) { + if (leftMouseDown) { float deltaX = (float) x - CubeTrace.this.mouseX; float deltaY = (float) y - CubeTrace.this.mouseY; - CubeTrace.this.viewMatrix.rotateLocalY(deltaX * 0.002f); - CubeTrace.this.viewMatrix.rotateLocalX(deltaY * 0.002f); - CubeTrace.this.viewMatrix.normalize3x3(); + cameraLookDir + .rotate(quaternion.identity().rotateAxis(-deltaY * 0.002f, viewMatrix.positiveX(tmpVector)) + .rotateAxis(-deltaX * 0.002f, viewMatrix.positiveY(tmpVector))); } CubeTrace.this.mouseX = (float) x; CubeTrace.this.mouseY = (float) y; @@ -242,14 +139,14 @@ public void invoke(long window, double x, double y) { }); glfwSetMouseButtonCallback(window, mbCallback = new GLFWMouseButtonCallback() { public void invoke(long window, int button, int action, int mods) { - if (action == GLFW_PRESS) - CubeTrace.this.mouseDown = true; - else if (action == GLFW_RELEASE) - CubeTrace.this.mouseDown = false; + if (action == GLFW_PRESS && button == GLFW_MOUSE_BUTTON_1) + leftMouseDown = true; + else if (action == GLFW_RELEASE && button == GLFW_MOUSE_BUTTON_1) + leftMouseDown = false; } }); - GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor()); - glfwSetWindowPos(window, (vidmode.width() - width) / 2, (vidmode.height() - height) / 2); +// GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor()); +// glfwSetWindowPos(window, (vidmode.width() - width) / 2, (vidmode.height() - height) / 2); glfwMakeContextCurrent(window); glfwSwapInterval(0); try (MemoryStack frame = MemoryStack.stackPush()) { @@ -258,28 +155,53 @@ else if (action == GLFW_RELEASE) width = framebufferSize.get(0); height = framebufferSize.get(1); } - GLCapabilities caps = GL.createCapabilities(); - hasShortsInShader = caps.GL_NV_gpu_shader5 || caps.GL_AMD_gpu_shader_int16; - // debugProc = GLUtil.setupDebugMessageCallback(); - viewMatrix.setLookAt(cameraPosition, cameraLookAt, cameraUp); - System.out.println("Building terrain..."); + caps = GL.createCapabilities(); + GL_NV_draw_texture = caps.GL_NV_draw_texture; + GL_NV_fill_rectangle = caps.GL_NV_fill_rectangle; + debugProc = GLUtil.setupDebugMessageCallback(); + loadTextures(); createFramebufferTextures(); - createSampler(); - quadFullScreenVao(); - createComputeProgram(); - initComputeProgram(); - createQuadProgram(); - initQuadProgram(); - createSceneSSBOs(buildTerrainVoxels()); + createFinalGatherProgram(); + if (!GL_NV_draw_texture) { + createFullScreenVao(); + createQuadProgram(); + initQuadProgram(); + } + System.out.println("Building terrain..."); + voxels = buildTerrainVoxels(); + createSceneSSBOs(voxels); glfwShowWindow(window); } + private void loadTextures() throws IOException { + try (MemoryStack frame = MemoryStack.stackPush()) { + IntBuffer width = frame.mallocInt(1); + IntBuffer height = frame.mallocInt(1); + IntBuffer components = frame.mallocInt(1); + tex = glGenTextures(); + glBindTexture(GL_TEXTURE_2D, tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + ByteBuffer data; + data = stbi_load_from_memory(DemoUtils.ioResourceToByteBuffer("org/lwjgl/demo/opengl/raytracing/cubetrace/grass_high.png", 1024), width, + height, components, 4); + int w = width.get(0), h = height.get(0); + glTexStorage2D(GL_TEXTURE_2D, 6, GL_RGBA8, w, h); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, data); + stbi_image_free(data); + glGenerateMipmap(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, 0); + } + } + private byte[] samplePattern() { switch (cbWidth) { case 1: return new byte[] { 0, 0 }; case 2: - /* Use custom sample patterns for 2x2 up to 4x4 pixel groups */ + /* Use custom sample patterns for 2x2 up to 4x4 pixel groups */ return new byte[] { 0, 0, 1, 1, 1, 0, 0, 1 }; case 3: return new byte[] { 0, 0, 1, 2, 2, 1, 0, 1, 2, 0, 1, 1, 2, 2, 0, 2, 1, 0 }; @@ -291,144 +213,165 @@ private byte[] samplePattern() { } } - private List buildTerrainVoxels() { + private static int idx(int x, int y, int z, int width, int depth) { + return x + z * width + y * width * depth; + } + + private List buildTerrainVoxels() { int width = levelWidth, height = levelHeight, depth = levelDepth; - float xzScale = 0.01353f, yScale = 0.0322f; + float xzScale = 0.01543f * scale; byte[] field = new byte[width * depth * height]; - Thread[] threads = new Thread[Runtime.getRuntime().availableProcessors()]; - for (int i = 0; i < threads.length; i++) { - final int ti = i; - threads[i] = new Thread(() -> { - int depthSlice = depth / threads.length; - for (int z = ti * depthSlice; z < (ti + 1) * depthSlice; z++) - for (int y = 0; y < height; y++) - for (int x = 0; x < width; x++) { - float v = SimplexNoise.noise(x * xzScale, z * xzScale, y * yScale); - if (y == 0 || v > 0.15f) - field[x + y * width + z * width * height] = ~0; - } - }); - threads[i].start(); - } - for (int i = 0; i < threads.length; i++) { - try { - threads[i].join(); - } catch (InterruptedException e) { + for (int z = 0; z < depth; z++) { + for (int x = 0; x < width; x++) { + float dx = (x - width * 0.5f) / width * 2, dz = (z - depth * 0.5f) / depth * 2; + float dist = (float) Math.min(1.0, Math.sqrt(dx * dx + dz * dz)); + float y = height * (SimplexNoise.noise(x * xzScale, z * xzScale) * 0.5f + 0.5f) + * ((float) Math.pow(1.0 - dist * 0.8, 5)); + y = Math.min(y, height); + for (int y0 = 0; y0 <= y; y0++) { + field[idx(x, y0, z, width, depth)] = ~0; + } } } /* Remove voxels that have neighbors at all sides */ - int removed = 0; - for (int z = 1; z < depth - 1; z++) - for (int y = 1; y < height - 1; y++) - for (int x = 1; x < width - 1; x++) { - int idx = x + y * width + z * width * height; - int left = (x - 1) + y * width + z * width * height; - int right = (x + 1) + y * width + z * width * height; - int up = x + (y + 1) * width + z * width * height; - int down = x + (y - 1) * width + z * width * height; - int front = x + y * width + (z - 1) * width * height; - int back = x + y * width + (z + 1) * width * height; - if ((field[idx] & 1) == 1 && (field[left] & 1) == 1 && (field[right] & 1) == 1 - && (field[up] & 1) == 1 && (field[down] & 1) == 1 && (field[front] & 1) == 1 - && (field[back] & 1) == 1) { - field[idx] = 3; // <- remember that this once was a filled voxel! - removed++; + for (int z = 0; z < depth; z++) { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int idx = idx(x, y, z, width, depth); + int left = idx(x - 1, y, z, width, depth); + int right = idx(x + 1, y, z, width, depth); + int up = idx(x, y + 1, z, width, depth); + int down = idx(x, y - 1, z, width, depth); + int front = idx(x, y, z - 1, width, depth); + int back = idx(x, y, z + 1, width, depth); + if ((field[idx] & 1) == 1 && (x == 0 || (field[left] & 1) == 1) + && (x == width - 1 || (field[right] & 1) == 1) && (y == height - 1 || (field[up] & 1) == 1) + && (y == 0 || (field[down] & 1) == 1) && (z == 0 || (field[front] & 1) == 1) + && (z == depth - 1 || (field[back] & 1) == 1)) { + field[idx] = 1; // <- remember that this once was a filled voxel! } } - List voxels = new ArrayList<>(); - for (int z = 0; z < depth; z++) - for (int y = 0; y < height; y++) - for (int x = 0; x < width; x++) { - int idx = x + y * width + z * width * height; - float r = 0.9f * (SimplexNoise.noise(x * 0.012f, y * 0.04f, z * 0.012f) * 0.5f + 0.5f); - float g = r; - float b = g; + } + } + List voxels = new ArrayList<>(); + for (short z = 0; z < depth; z++) { + for (short y = 0; y < height; y++) { + for (short x = 0; x < width; x++) { + int idx = idx(x, y, z, width, depth); + int left = x > 0 && (field[idx(x - 1, y, z, width, depth)] & 1) == 1 ? 1 : 0; + int right = x < width - 1 && (field[idx(x + 1, y, z, width, depth)] & 1) == 1 ? 1 : 0; + int down = y > 0 && (field[idx(x, y - 1, z, width, depth)] & 1) == 1 ? 1 : 0; + int up = y < height - 1 && (field[idx(x, y + 1, z, width, depth)] & 1) == 1 ? 1 : 0; + int back = z > 0 && (field[idx(x, y, z - 1, width, depth)] & 1) == 1 ? 1 : 0; + int front = z < depth - 1 && (field[idx(x, y, z + 1, width, depth)] & 1) == 1 ? 1 : 0; + int sidesFlag = left | (right << 1) | (down << 2) | (up << 3) | (back << 4) | (front << 5); if (field[idx] == ~0) - voxels.add(new Voxel(x, y, z, r, g, b)); + voxels.add(new KDTreei.Voxel((byte) x, (byte) y, (byte) z, (byte) ((sidesFlag << 1) | 1))); } - System.out.println("Removed voxels: " + removed); + } + } System.out.println("Retained voxels: " + voxels.size()); return voxels; } - private static List allocate(IBVHMortonTree node) { - List linearNodes = new ArrayList<>(); - Queue nodes = new ArrayDeque<>(); - int index = 0; + private static List> allocate(KDTreei.Node node) { + List> linearNodes = new ArrayList<>(); + LinkedList> nodes = new LinkedList<>(); + int index = 0, leafIndex = 0; nodes.add(node); while (!nodes.isEmpty()) { - IBVHMortonTree n = nodes.poll(); + KDTreei.Node n = nodes.removeFirst(); linearNodes.add(n); n.index = index++; if (n.left != null) { - nodes.add(n.left); - nodes.add(n.right); + nodes.addFirst(n.right); + nodes.addFirst(n.left); + } else { + if (!n.voxels.isEmpty()) + n.leafIndex = leafIndex++; + else + n.leafIndex = -1; + n.voxels.forEach(v -> { + v.nindex = n.index; + }); } } return linearNodes; } - private void createSceneSSBOs(List voxels) { - System.out.println("Building BVH..."); - IBVHMortonTree root = IBVHMortonTree.build(voxels); - System.out.println("Writing BVH to buffers..."); - DynamicByteBuffer voxelsBuffer = new DynamicByteBuffer(voxels.size() * 4 * 4); - if (hasShortsInShader) { - voxels.forEach(v -> { - int r = (int) (v.r * 32.0f); - int g = (int) (v.g * 32.0f); - int b = (int) (v.b * 32.0f); - voxelsBuffer.putShort(v.x).putShort(v.y).putShort(v.z) - .putShort((r & 0x1F) | ((g & 0x1F) << 5) | ((b & 0x1F) << 10)); - }); - } else { - voxels.forEach(v -> { - int r = (int) (v.r * 1024.0f); - int g = (int) (v.g * 1024.0f); - int b = (int) (v.b * 1024.0f); - voxelsBuffer.putInt(v.x).putInt(v.y).putInt(v.z) - .putInt((r & 0x3FF) | ((g & 0x3FF) << 10) | ((b & 0x3FF) << 20)); - }); + private void createSceneSSBOs(List voxels) { + System.out.println("Building kd tree..."); + for (int i = 0; i < 1; i++) { + long time1 = System.nanoTime(); + root = KDTreei.build(voxels, 17); + long time2 = System.nanoTime(); + System.out.println("KD tree build took: " + (time2 - time1) * 1E-6f + " ms."); } - voxelsBuffer.flip(); - System.out.println("Voxels SSBO size: " + voxelsBuffer.remaining() / 1024 / 1024 + " MB"); - DynamicByteBuffer nodesBuffer = new DynamicByteBuffer(8192); - bhvToBuffers(root, nodesBuffer); - nodesBuffer.flip(); - System.out.println("BVH SSBO size: " + nodesBuffer.remaining() / 1024 / 1024 + " MB"); - this.nodesSsbo = glGenBuffers(); + System.out.println("Writing kd and voxels to buffers..."); + DynamicByteBuffer voxelsBuffer = new DynamicByteBuffer(); + DynamicByteBuffer nodesBuffer = new DynamicByteBuffer(); + DynamicByteBuffer nodeGeomsBuffer = new DynamicByteBuffer(); + DynamicByteBuffer leafNodesBuffer = new DynamicByteBuffer(); + long time1 = System.nanoTime(); + kdTreeToBuffers(root, 0, 0, nodesBuffer, nodeGeomsBuffer, leafNodesBuffer, voxelsBuffer); + long time2 = System.nanoTime(); + System.out.println("kd tree and voxels to buffers took: " + (time2 - time1) * 1E-6f + " ms."); + System.out.println("Voxels SSBO size: " + String.format("%.2f", voxelsBuffer.pos / 1024.0 / 1024.0) + " MB"); + System.out.println("Nodes SSBO size: " + String.format("%.2f", nodesBuffer.pos / 1024.0 / 1024.0) + " MB"); + System.out.println( + "Nodes Geometry SSBO size: " + String.format("%.2f", nodeGeomsBuffer.pos / 1024.0 / 1024.0) + " MB"); + System.out.println( + "Leaf Nodes SSBO size: " + String.format("%.2f", leafNodesBuffer.pos / 1024.0 / 1024.0) + " MB"); + nodesSsbo = glGenBuffers(); glBindBuffer(GL_ARRAY_BUFFER, nodesSsbo); - glBufferData(GL_ARRAY_BUFFER, nodesBuffer.bb, GL_STATIC_DRAW); + nglBufferData(GL_ARRAY_BUFFER, nodesBuffer.pos, nodesBuffer.addr, GL_STATIC_DRAW); nodesBuffer.free(); - glBindBuffer(GL_ARRAY_BUFFER, 0); - this.voxelsSsbo = glGenBuffers(); + nodeGeomsSsbo = glGenBuffers(); + glBindBuffer(GL_ARRAY_BUFFER, nodeGeomsSsbo); + nglBufferData(GL_ARRAY_BUFFER, nodeGeomsBuffer.pos, nodeGeomsBuffer.addr, GL_STATIC_DRAW); + nodeGeomsBuffer.free(); + leafNodesSsbo = glGenBuffers(); + glBindBuffer(GL_ARRAY_BUFFER, leafNodesSsbo); + nglBufferData(GL_ARRAY_BUFFER, leafNodesBuffer.pos, leafNodesBuffer.addr, GL_STATIC_DRAW); + leafNodesBuffer.free(); + voxelsSsbo = glGenBuffers(); glBindBuffer(GL_ARRAY_BUFFER, voxelsSsbo); - glBufferData(GL_ARRAY_BUFFER, voxelsBuffer.bb, GL_STATIC_DRAW); + nglBufferData(GL_ARRAY_BUFFER, voxelsBuffer.pos, voxelsBuffer.addr, GL_STATIC_DRAW); voxelsBuffer.free(); glBindBuffer(GL_ARRAY_BUFFER, 0); } - private void bhvToBuffers(IBVHMortonTree root, DynamicByteBuffer nodesBuffer) { - if (hasShortsInShader) { - for (IBVHMortonTree n : allocate(root)) { - nodesBuffer.putShort(n.minX).putShort(n.minY).putShort(n.minZ).putByte(n.last - n.first + 1).putByte(0); - nodesBuffer.putShort(n.maxX + 1).putShort(n.maxY + 1).putShort(n.maxZ + 1).putShort(0); - nodesBuffer.putInt(n.left != null ? n.left.index : -1).putInt(n.right != null ? n.right.index : -1); - nodesBuffer.putInt(n.parent != null ? n.parent.index : -1).putInt(n.first); - } - } else { - for (IBVHMortonTree n : allocate(root)) { - nodesBuffer.putFloat(n.minX).putFloat(n.minY).putFloat(n.minZ) - .putInt(n.left != null ? n.left.index : -1); - nodesBuffer.putFloat(n.maxX + 1).putFloat(n.maxY + 1).putFloat(n.maxZ + 1) - .putInt(n.right != null ? n.right.index : -1); - nodesBuffer.putInt(n.parent != null ? n.parent.index : -1).putInt(n.first).putInt(n.last - n.first + 1) - .putInt(-1); + private void kdTreeToBuffers(KDTreei root, int nodeIndexOffset, int voxelIndexOffset, + DynamicByteBuffer nodesBuffer, DynamicByteBuffer nodeGeomsBuffer, DynamicByteBuffer leafNodesBuffer, + DynamicByteBuffer voxelsBuffer) { + int first = 0; + List> nodes = allocate(root.root); + System.out.println("Num nodes: " + nodes.size()); + for (KDTreei.Node n : nodes) { + int numVoxels = 0; + if (n.left == null) { + numVoxels = n.voxels.size(); + n.voxels.forEach(v -> { + voxelsBuffer.putByte(v.x).putByte(v.y).putByte(v.z).putByte(v.paletteIndex); + }); + if (n.leafIndex != -1) + leafNodesBuffer.putInt(first).putInt(numVoxels); } + nodeGeomsBuffer.putByte(n.boundingBox.minX).putByte(n.boundingBox.minY).putByte(n.boundingBox.minZ) + .putByte(0); + nodeGeomsBuffer.putByte(n.boundingBox.maxX - 1).putByte(n.boundingBox.maxY - 1) + .putByte(n.boundingBox.maxZ - 1).putByte(0); + nodesBuffer.putInt(n.right != null ? n.right.index + nodeIndexOffset : n.leafIndex); + nodesBuffer.putInt(n.splitAxis).putInt(n.splitPos); + nodesBuffer.putByte(0).putByte(0).putByte(0).putByte(0); + for (int i = 0; i < 6; i++) + nodesBuffer.putInt(n.ropes[i] != null ? n.ropes[i].index : -1); + nodesBuffer.putInt(0).putInt(0); + first += numVoxels; } } - private void quadFullScreenVao() { + private void createFullScreenVao() { this.vao = glGenVertexArrays(); int vbo = glGenBuffers(); glBindVertexArray(vao); @@ -438,9 +381,11 @@ private void quadFullScreenVao() { fv.put(-1.0f).put(-1.0f); fv.put(1.0f).put(-1.0f); fv.put(1.0f).put(1.0f); - fv.put(1.0f).put(1.0f); - fv.put(-1.0f).put(1.0f); - fv.put(-1.0f).put(-1.0f); + if (!GL_NV_fill_rectangle) { + fv.put(1.0f).put(1.0f); + fv.put(-1.0f).put(1.0f); + fv.put(-1.0f).put(-1.0f); + } glBufferData(GL_ARRAY_BUFFER, bb, GL_STATIC_DRAW); MemoryUtil.memFree(bb); glEnableVertexAttribArray(0); @@ -451,10 +396,8 @@ private void quadFullScreenVao() { private void createQuadProgram() throws IOException { int program = glCreateProgram(); - int vshader = DemoUtils.createShader("org/lwjgl/demo/opengl/raytracing/cubetrace/quad.vs.glsl", - GL_VERTEX_SHADER); - int fshader = DemoUtils.createShader("org/lwjgl/demo/opengl/raytracing/cubetrace/quad.fs.glsl", - GL_FRAGMENT_SHADER); + int vshader = DemoUtils.createShader("cubetrace/experiments/quad.vs.glsl", GL_VERTEX_SHADER); + int fshader = DemoUtils.createShader("cubetrace/experiments/quad.fs.glsl", GL_FRAGMENT_SHADER); glAttachShader(program, vshader); glAttachShader(program, fshader); glBindAttribLocation(program, 0, "vertex"); @@ -462,76 +405,58 @@ private void createQuadProgram() throws IOException { glLinkProgram(program); int linked = glGetProgrami(program, GL_LINK_STATUS); String programLog = glGetProgramInfoLog(program); - if (programLog.trim().length() > 0) + if (programLog != null && programLog.trim().length() > 0) System.err.println(programLog); if (linked == 0) throw new AssertionError("Could not link program"); this.quadProgram = program; } - private void createComputeProgram() throws IOException { + private void initQuadProgram() { + glUseProgram(quadProgram); + int texUniform = glGetUniformLocation(quadProgram, "tex"); + glUniform1i(texUniform, 0); + glUseProgram(0); + } + + private void createFinalGatherProgram() throws IOException { int program = glCreateProgram(); - int cshader = DemoUtils.createShader( - "org/lwjgl/demo/opengl/raytracing/cubetrace/raytracing" + (hasShortsInShader ? "_16t" : "") + ".glsl", - GL_COMPUTE_SHADER); + int cshader = DemoUtils.createShader("org/lwjgl/demo/opengl/raytracing/cubetrace/kdtreeseparate32grass.glsl", GL_COMPUTE_SHADER); glAttachShader(program, cshader); glLinkProgram(program); int linked = glGetProgrami(program, GL_LINK_STATUS); String programLog = glGetProgramInfoLog(program); - if (programLog.trim().length() > 0) + if (programLog != null && programLog.trim().length() > 0) System.err.println(programLog); if (linked == 0) throw new AssertionError("Could not link program"); - this.computeProgram = program; - } - - private void initQuadProgram() { - glUseProgram(quadProgram); - int texUniform = glGetUniformLocation(quadProgram, "tex"); - glUniform1i(texUniform, 0); - glUseProgram(0); - } - - private void initComputeProgram() { - glUseProgram(computeProgram); + this.finalGatherProgram = program; + glUseProgram(finalGatherProgram); try (MemoryStack frame = MemoryStack.stackPush()) { IntBuffer workGroupSize = frame.mallocInt(3); - glGetProgramiv(computeProgram, GL_COMPUTE_WORK_GROUP_SIZE, workGroupSize); - workGroupSizeX = workGroupSize.get(0); - workGroupSizeY = workGroupSize.get(1); - cbwidthUniform = glGetUniformLocation(computeProgram, "cbwidth"); - offUniform = glGetUniformLocation(computeProgram, "off"); - eyeUniform = glGetUniformLocation(computeProgram, "eye"); - ray00Uniform = glGetUniformLocation(computeProgram, "ray00"); - ray10Uniform = glGetUniformLocation(computeProgram, "ray10"); - ray01Uniform = glGetUniformLocation(computeProgram, "ray01"); - ray11Uniform = glGetUniformLocation(computeProgram, "ray11"); - IntBuffer props = frame.mallocInt(1); - IntBuffer params = frame.mallocInt(1); - props.put(0, GL_BUFFER_BINDING); - int nodesResourceIndex = glGetProgramResourceIndex(computeProgram, GL_SHADER_STORAGE_BLOCK, "Nodes"); - glGetProgramResourceiv(computeProgram, GL_SHADER_STORAGE_BLOCK, nodesResourceIndex, props, null, params); - nodesSsboBinding = params.get(0); - int voxelsResourceIndex = glGetProgramResourceIndex(computeProgram, GL_SHADER_STORAGE_BLOCK, "Voxels"); - glGetProgramResourceiv(computeProgram, GL_SHADER_STORAGE_BLOCK, voxelsResourceIndex, props, null, params); - voxelsSsboBinding = params.get(0); - int loc = glGetUniformLocation(computeProgram, "framebufferImage"); - glGetUniformiv(computeProgram, loc, params); - framebufferImageBinding = params.get(0); + glGetProgramiv(finalGatherProgram, GL_COMPUTE_WORK_GROUP_SIZE, workGroupSize); + finalGatherWorkGroupSizeX = workGroupSize.get(0); + finalGatherWorkGroupSizeY = workGroupSize.get(1); } + cbwidthUniform = glGetUniformLocation(finalGatherProgram, "cbwidth"); + scaleUniform = glGetUniformLocation(finalGatherProgram, "scale"); + texUniform = glGetUniformLocation(finalGatherProgram, "tex"); + glUniform1i(texUniform, 0); + offUniform = glGetUniformLocation(finalGatherProgram, "off"); + camUniform = glGetUniformLocation(finalGatherProgram, "cam"); + startNodeIdxUniform = glGetUniformLocation(finalGatherProgram, "startNodeIdx"); + prevVPUniform = glGetUniformLocation(finalGatherProgram, "prevVP"); + finalGatherTimeUniform = glGetUniformLocation(finalGatherProgram, "time"); + glUniform1i(scaleUniform, scale); glUseProgram(0); } private void createFramebufferTextures() { pttex = glGenTextures(); glBindTexture(GL_TEXTURE_2D, pttex); - glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, width, height); - } - - private void createSampler() { - this.sampler = glGenSamplers(); - glSamplerParameteri(this.sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glSamplerParameteri(this.sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); } private void resizeFramebufferTexture() { @@ -540,104 +465,115 @@ private void resizeFramebufferTexture() { } private void update(float dt) { - float factor = 20.0f; + if (resetFramebuffer) { + resizeFramebufferTexture(); + resetFramebuffer = false; + } + viewport[2] = width; + viewport[3] = height; + float factor = 10.0f; if (keydown[GLFW_KEY_LEFT_SHIFT]) - factor = 50.0f; + factor = 20.0f; if (keydown[GLFW_KEY_W]) - viewMatrix.translateLocal(0, 0, factor * dt); + cameraPosition.sub(viewMatrix.positiveZ(tmpVector).mul(dt * factor)); if (keydown[GLFW_KEY_S]) - viewMatrix.translateLocal(0, 0, -factor * dt); + cameraPosition.add(viewMatrix.positiveZ(tmpVector).mul(dt * factor)); if (keydown[GLFW_KEY_A]) - viewMatrix.translateLocal(factor * dt, 0, 0); + cameraPosition.sub(viewMatrix.positiveX(tmpVector).mul(dt * factor)); if (keydown[GLFW_KEY_D]) - viewMatrix.translateLocal(-factor * dt, 0, 0); - if (keydown[GLFW_KEY_Q]) - viewMatrix.rotateLocalZ(factor * 0.02f * -dt); - if (keydown[GLFW_KEY_E]) - viewMatrix.rotateLocalZ(factor * 0.02f * dt); + cameraPosition.add(viewMatrix.positiveX(tmpVector).mul(dt * factor)); if (keydown[GLFW_KEY_LEFT_CONTROL]) - viewMatrix.translateLocal(0, factor * dt, 0); + cameraPosition.sub(tmpVector.set(0, 1, 0).mul(dt * factor)); if (keydown[GLFW_KEY_SPACE]) - viewMatrix.translateLocal(0, -factor * dt, 0); - projMatrix.setPerspective((float) Math.toRadians(60.0f), (float) width / height, 0.01f, 100.0f); + cameraPosition.add(tmpVector.set(0, 1, 0).mul(dt * factor)); + projMatrix.setPerspective((float) Math.toRadians(60.0f), (float) width / height, 1f, 1000.0f); + viewMatrix.setLookAt(cameraPosition, tmpVector.set(cameraPosition).add(cameraLookDir), tmpVector2.set(0, 1, 0)); + viewProjMatrix.set(projMatrix).mul(viewMatrix).invert(invViewProjMatrix); + /* Determine camera node */ + cameraNode = root.findNode(cameraPosition); } - private void trace() { - glUseProgram(computeProgram); - if (resetFramebuffer) { - resizeFramebufferTexture(); - resetFramebuffer = false; - } - projMatrix.invertPerspectiveView(viewMatrix, invViewProjMatrix); + private void trace(float elapsedSeconds) { + glUseProgram(finalGatherProgram); viewMatrix.originAffine(cameraPosition); + glUniform1ui(startNodeIdxUniform, cameraNode.index); + glUniform1f(finalGatherTimeUniform, elapsedSeconds); glUniform2i(cbwidthUniform, cbWidth, cbWidth); glUniform2i(offUniform, samplePattern[cbPixel << 1], samplePattern[(cbPixel << 1) + 1]); cbPixel = (cbPixel + 1) % (cbWidth * cbWidth); - glUniform3f(eyeUniform, (float) cameraPosition.x, (float) cameraPosition.y, (float) cameraPosition.z); - invViewProjMatrix.transformProject(tmpVector.set(-1, -1, 0)).sub(cameraPosition); - glUniform3f(ray00Uniform, (float) tmpVector.x, (float) tmpVector.y, (float) tmpVector.z); - invViewProjMatrix.transformProject(tmpVector.set(-1, 1, 0)).sub(cameraPosition); - glUniform3f(ray01Uniform, (float) tmpVector.x, (float) tmpVector.y, (float) tmpVector.z); - invViewProjMatrix.transformProject(tmpVector.set(1, -1, 0)).sub(cameraPosition); - glUniform3f(ray10Uniform, (float) tmpVector.x, (float) tmpVector.y, (float) tmpVector.z); - invViewProjMatrix.transformProject(tmpVector.set(1, 1, 0)).sub(cameraPosition); - glUniform3f(ray11Uniform, (float) tmpVector.x, (float) tmpVector.y, (float) tmpVector.z); - glBindImageTexture(framebufferImageBinding, pttex, 0, false, 0, GL_WRITE_ONLY, GL_RGBA32F); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, nodesSsboBinding, nodesSsbo); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, voxelsSsboBinding, voxelsSsbo); - int numGroupsX = (int) Math.ceil((double)width / workGroupSizeX); - int numGroupsY = (int) Math.ceil((double)height / workGroupSizeY); - int q0 = ARBOcclusionQuery.glGenQueriesARB(); - int q1 = ARBOcclusionQuery.glGenQueriesARB(); - ARBTimerQuery.glQueryCounter(q0, ARBTimerQuery.GL_TIMESTAMP); - glDispatchCompute(numGroupsX, numGroupsY, 1); - ARBTimerQuery.glQueryCounter(q1, ARBTimerQuery.GL_TIMESTAMP); - while (ARBOcclusionQuery.glGetQueryObjectiARB(q0, ARBOcclusionQuery.GL_QUERY_RESULT_AVAILABLE_ARB) == 0 - || ARBOcclusionQuery.glGetQueryObjectiARB(q1, ARBOcclusionQuery.GL_QUERY_RESULT_AVAILABLE_ARB) == 0) - Thread.yield(); - long time1 = ARBTimerQuery.glGetQueryObjectui64(q0, ARBOcclusionQuery.GL_QUERY_RESULT_ARB); - long time2 = ARBTimerQuery.glGetQueryObjectui64(q1, ARBOcclusionQuery.GL_QUERY_RESULT_ARB); - float factor = (float) frame / (frame + 1); - avgTime = (1.0f - factor) * avgTime + factor * (time2 - time1); - frame++; - if (System.nanoTime() - lastTime >= 1E9) { - System.err.println(avgTime * 1E-6 + " ms."); - lastTime = System.nanoTime(); - frame = 0; + glUniform3f(camUniform, (float) cameraPosition.x, (float) cameraPosition.y, (float) cameraPosition.z); + invViewProjMatrix.transformProject(tmpVector.set(-1, -1, -1)).sub(cameraPosition); + glUniform3f(camUniform + 1, (float) tmpVector.x, (float) tmpVector.y, (float) tmpVector.z); + invViewProjMatrix.transformProject(tmpVector.set(-1, 1, -1)).sub(cameraPosition); + glUniform3f(camUniform + 2, (float) tmpVector.x, (float) tmpVector.y, (float) tmpVector.z); + invViewProjMatrix.transformProject(tmpVector.set(1, -1, -1)).sub(cameraPosition); + glUniform3f(camUniform + 3, (float) tmpVector.x, (float) tmpVector.y, (float) tmpVector.z); + invViewProjMatrix.transformProject(tmpVector.set(1, 1, -1)).sub(cameraPosition); + glUniform3f(camUniform + 4, (float) tmpVector.x, (float) tmpVector.y, (float) tmpVector.z); + try (MemoryStack frame = MemoryStack.stackPush()) { + glUniformMatrix4fv(prevVPUniform, false, prevViewProjMatrix.get(frame.mallocFloat(16))); } - ARBOcclusionQuery.glDeleteQueriesARB(q0); - ARBOcclusionQuery.glDeleteQueriesARB(q1); + glBindImageTexture(0, pttex, 0, false, 0, GL_WRITE_ONLY, GL_RGBA8); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, nodesSsbo); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, nodeGeomsSsbo); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, leafNodesSsbo); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, voxelsSsbo); + glBindTexture(GL_TEXTURE_2D, tex); + int worksizeX = (int) Math.ceil((double) width / cbWidth / finalGatherWorkGroupSizeX); + int worksizeY = (int) Math.ceil((double) height / cbWidth / finalGatherWorkGroupSizeY); + glDispatchCompute(worksizeX, worksizeY, 1); glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, nodesSsboBinding, 0); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, voxelsSsboBinding, 0); - glBindImageTexture(framebufferImageBinding, 0, 0, false, 0, GL_WRITE_ONLY, GL_RGBA32F); + glBindTexture(GL_TEXTURE_2D, 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, 0); + glBindImageTexture(0, 0, 0, false, 0, GL_WRITE_ONLY, GL_RGBA8); glUseProgram(0); } private void present() { - glUseProgram(quadProgram); - glBindVertexArray(vao); - glBindTexture(GL_TEXTURE_2D, pttex); - glBindSampler(0, this.sampler); - glDrawArrays(GL_TRIANGLES, 0, 6); - glBindSampler(0, 0); - glBindTexture(GL_TEXTURE_2D, 0); - glBindVertexArray(0); - glUseProgram(0); + if (GL_NV_draw_texture) { + glDrawTextureNV(pttex, 0, 0, 0, width, height, 0, 0, 0, 1, 1); + } else { + glUseProgram(quadProgram); + glBindVertexArray(vao); + glBindTexture(GL_TEXTURE_2D, pttex); + if (GL_NV_fill_rectangle) { + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL_RECTANGLE_NV); + glDrawArrays(GL_TRIANGLES, 0, 3); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } else { + glDrawArrays(GL_TRIANGLES, 0, 6); + } + glBindTexture(GL_TEXTURE_2D, 0); + glBindVertexArray(0); + glUseProgram(0); + } } + private int averageOver = 400; + private void loop() { - float lastTime = System.nanoTime(); + long lastTime = System.nanoTime(); + long lastAvg = System.nanoTime(); while (!glfwWindowShouldClose(window)) { - float thisTime = System.nanoTime(); - float dt = (thisTime - lastTime) / 1E9f; - lastTime = thisTime; glfwPollEvents(); + long thisTime = System.nanoTime(); + float dt = (thisTime - lastTime) * 1E-9f; + if ((frame % averageOver) == 0) { + String text = String.format("%.3f", (thisTime - lastAvg) * 1E-6f / averageOver) + " ms."; + glfwSetWindowTitle(window, text); + System.out.println(text); + lastAvg = thisTime; + } + lastTime = thisTime; + update((float) dt); glViewport(0, 0, width, height); - update(dt); - trace(); + trace(thisTime * 1E-9f); present(); glfwSwapBuffers(window); + frame++; } } diff --git a/src/org/lwjgl/demo/opengl/raytracing/DemoSsboTrianglesStacklessKdTree.java b/src/org/lwjgl/demo/opengl/raytracing/DemoSsboTrianglesStacklessKdTree.java index 23ef4f61..ba4b74e6 100644 --- a/src/org/lwjgl/demo/opengl/raytracing/DemoSsboTrianglesStacklessKdTree.java +++ b/src/org/lwjgl/demo/opengl/raytracing/DemoSsboTrianglesStacklessKdTree.java @@ -337,16 +337,14 @@ void createSceneSSBO() { DynamicByteBuffer nodesBuffer = new DynamicByteBuffer(); DynamicByteBuffer trianglesBuffer = new DynamicByteBuffer(); kdTreeToBuffers(kdtree, nodesBuffer, trianglesBuffer); - nodesBuffer.flip(); - trianglesBuffer.flip(); this.nodesSsbo = glGenBuffers(); glBindBuffer(GL_ARRAY_BUFFER, nodesSsbo); - glBufferData(GL_ARRAY_BUFFER, nodesBuffer.bb, GL_STATIC_DRAW); + nglBufferData(GL_ARRAY_BUFFER, nodesBuffer.pos, nodesBuffer.addr, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); this.trianglesSsbo = glGenBuffers(); glBindBuffer(GL_ARRAY_BUFFER, trianglesSsbo); - glBufferData(GL_ARRAY_BUFFER, trianglesBuffer.bb, GL_STATIC_DRAW); + nglBufferData(GL_ARRAY_BUFFER, trianglesBuffer.pos, trianglesBuffer.addr, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); } diff --git a/src/org/lwjgl/demo/opengl/raytracing/HybridDemoSsboTriangles.java b/src/org/lwjgl/demo/opengl/raytracing/HybridDemoSsboTriangles.java index 9173b230..17bc9e5c 100644 --- a/src/org/lwjgl/demo/opengl/raytracing/HybridDemoSsboTriangles.java +++ b/src/org/lwjgl/demo/opengl/raytracing/HybridDemoSsboTriangles.java @@ -298,8 +298,7 @@ private void createSceneSSBO() { objects.add(obj); } Std430Writer.write(objects, GPUObject.class, objectsBuffer); - objectsBuffer.flip(); - glBufferData(GL_ARRAY_BUFFER, objectsBuffer.bb, GL_STATIC_DRAW); + nglBufferData(GL_ARRAY_BUFFER, objectsBuffer.pos, objectsBuffer.addr, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); } diff --git a/src/org/lwjgl/demo/opengl/raytracing/tutorial/Tutorial6.java b/src/org/lwjgl/demo/opengl/raytracing/tutorial/Tutorial6.java index 7e4f3b02..706fafca 100644 --- a/src/org/lwjgl/demo/opengl/raytracing/tutorial/Tutorial6.java +++ b/src/org/lwjgl/demo/opengl/raytracing/tutorial/Tutorial6.java @@ -502,18 +502,16 @@ private void createSceneSSBOs() { DynamicByteBuffer nodesBuffer = new DynamicByteBuffer(); DynamicByteBuffer trianglesBuffer = new DynamicByteBuffer(); bhvToBuffers(root, nodesBuffer, trianglesBuffer); - nodesBuffer.flip(); - trianglesBuffer.flip(); /* * And finally we upload the two buffers to the SSBOs. */ this.nodesSsbo = glGenBuffers(); glBindBuffer(GL_ARRAY_BUFFER, nodesSsbo); - glBufferData(GL_ARRAY_BUFFER, nodesBuffer.bb, GL_STATIC_DRAW); + nglBufferData(GL_ARRAY_BUFFER, nodesBuffer.pos, nodesBuffer.addr, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); this.trianglesSsbo = glGenBuffers(); glBindBuffer(GL_ARRAY_BUFFER, trianglesSsbo); - glBufferData(GL_ARRAY_BUFFER, trianglesBuffer.bb, GL_STATIC_DRAW); + nglBufferData(GL_ARRAY_BUFFER, trianglesBuffer.pos, trianglesBuffer.addr, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); } diff --git a/src/org/lwjgl/demo/opengl/raytracing/tutorial/Tutorial6_2.java b/src/org/lwjgl/demo/opengl/raytracing/tutorial/Tutorial6_2.java index 34723a9b..28c26745 100644 --- a/src/org/lwjgl/demo/opengl/raytracing/tutorial/Tutorial6_2.java +++ b/src/org/lwjgl/demo/opengl/raytracing/tutorial/Tutorial6_2.java @@ -497,18 +497,16 @@ private void createSceneSSBOs() { DynamicByteBuffer nodesBuffer = new DynamicByteBuffer(); DynamicByteBuffer trianglesBuffer = new DynamicByteBuffer(); bhvToBuffers(root, nodesBuffer, trianglesBuffer); - nodesBuffer.flip(); - trianglesBuffer.flip(); /* * And finally we upload the two buffers to the SSBOs. */ this.nodesSsbo = glGenBuffers(); glBindBuffer(GL_ARRAY_BUFFER, nodesSsbo); - glBufferData(GL_ARRAY_BUFFER, nodesBuffer.bb, GL_STATIC_DRAW); + nglBufferData(GL_ARRAY_BUFFER, nodesBuffer.pos, nodesBuffer.addr, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); this.trianglesSsbo = glGenBuffers(); glBindBuffer(GL_ARRAY_BUFFER, trianglesSsbo); - glBufferData(GL_ARRAY_BUFFER, trianglesBuffer.bb, GL_STATIC_DRAW); + nglBufferData(GL_ARRAY_BUFFER, trianglesBuffer.pos, trianglesBuffer.addr, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); } diff --git a/src/org/lwjgl/demo/opengl/raytracing/tutorial/Tutorial7.java b/src/org/lwjgl/demo/opengl/raytracing/tutorial/Tutorial7.java index b905956d..f43dacbc 100644 --- a/src/org/lwjgl/demo/opengl/raytracing/tutorial/Tutorial7.java +++ b/src/org/lwjgl/demo/opengl/raytracing/tutorial/Tutorial7.java @@ -495,16 +495,14 @@ private void createSceneSSBOs() { DynamicByteBuffer nodesBuffer = new DynamicByteBuffer(); DynamicByteBuffer trianglesBuffer = new DynamicByteBuffer(); kdTreeToBuffers(kdtree, nodesBuffer, trianglesBuffer); - nodesBuffer.flip(); - trianglesBuffer.flip(); this.nodesSsbo = glGenBuffers(); glBindBuffer(GL_ARRAY_BUFFER, nodesSsbo); - glBufferData(GL_ARRAY_BUFFER, nodesBuffer.bb, GL_STATIC_DRAW); + nglBufferData(GL_ARRAY_BUFFER, nodesBuffer.pos, nodesBuffer.addr, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); this.trianglesSsbo = glGenBuffers(); glBindBuffer(GL_ARRAY_BUFFER, trianglesSsbo); - glBufferData(GL_ARRAY_BUFFER, trianglesBuffer.bb, GL_STATIC_DRAW); + nglBufferData(GL_ARRAY_BUFFER, trianglesBuffer.pos, trianglesBuffer.addr, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); } diff --git a/src/org/lwjgl/demo/opengl/util/Boundable.java b/src/org/lwjgl/demo/opengl/util/Boundable.java new file mode 100644 index 00000000..a9af51f6 --- /dev/null +++ b/src/org/lwjgl/demo/opengl/util/Boundable.java @@ -0,0 +1,9 @@ +package org.lwjgl.demo.opengl.util; + +public interface Boundable { + int min(int axis); + int max(int axis); + void extend(int axis); + T splitLeft(int splitAxis, int splitPos); + T splitRight(int splitAxis, int splitPos); +} diff --git a/src/org/lwjgl/demo/opengl/util/DynamicByteBuffer.java b/src/org/lwjgl/demo/opengl/util/DynamicByteBuffer.java index 184e91b0..abe2529e 100644 --- a/src/org/lwjgl/demo/opengl/util/DynamicByteBuffer.java +++ b/src/org/lwjgl/demo/opengl/util/DynamicByteBuffer.java @@ -1,12 +1,8 @@ -/* - * Copyright LWJGL. All rights reserved. - * License terms: https://www.lwjgl.org/license - */ package org.lwjgl.demo.opengl.util; -import java.nio.ByteBuffer; +import static org.lwjgl.system.MemoryUtil.*; -import org.lwjgl.system.*; +import java.nio.*; /** * Dynamically growable {@link ByteBuffer}. @@ -15,70 +11,76 @@ */ public class DynamicByteBuffer { - public ByteBuffer bb; + public long addr; + public int pos; + public int cap; public DynamicByteBuffer() { this(8192); } + public DynamicByteBuffer(int initialSize) { - bb = MemoryUtil.memAlloc(initialSize); + addr = nmemAlloc(initialSize); + cap = initialSize; } private void grow() { - ByteBuffer newbb = MemoryUtil.memRealloc(bb, (int) (bb.capacity() * 1.5)); - bb = newbb; + int newCap = (int) (cap * 1.5f); + long newAddr = nmemRealloc(addr, newCap); + cap = newCap; + addr = newAddr; } public void free() { - MemoryUtil.memFree(bb); + nmemFree(addr); } public DynamicByteBuffer putFloat(float v) { - if (bb.remaining() < 4) { + if (cap - pos < 4) grow(); - } - bb.putFloat(v); + memPutFloat(addr + pos, v); + pos += 4; return this; } public DynamicByteBuffer putLong(long v) { - if (bb.remaining() < 8) { + if (cap - pos < 8) grow(); - } - bb.putLong(v); + memPutLong(addr + pos, v); + pos += 8; return this; } public DynamicByteBuffer putInt(int v) { - if (bb.remaining() < 4) { + if (cap - pos < 4) grow(); - } - bb.putInt(v); + memPutInt(addr + pos, v); + pos += 4; return this; } public DynamicByteBuffer putShort(int v) { - if (bb.remaining() < 2) { + if (v > 1 << 16) + throw new IllegalArgumentException(); + if (cap - pos < 2) grow(); - } - bb.putShort((short) (v & 0xFFFF)); + memPutShort(addr + pos, (short) v); + pos += 2; return this; } public DynamicByteBuffer putByte(int v) { - if (bb.remaining() < 1) { + if (v > 255) + throw new IllegalArgumentException(); + if (cap - pos < 2) grow(); - } - bb.put((byte) (v & 0xFF)); + memPutByte(addr + pos, (byte) (v & 0xFF)); + pos++; return this; } - public void flip() { - bb.flip(); - } - public int remaining() { - return bb.remaining(); + return (int) (cap - pos); } } diff --git a/src/org/lwjgl/demo/opengl/util/KDTreei.java b/src/org/lwjgl/demo/opengl/util/KDTreei.java new file mode 100644 index 00000000..250c5af4 --- /dev/null +++ b/src/org/lwjgl/demo/opengl/util/KDTreei.java @@ -0,0 +1,538 @@ +package org.lwjgl.demo.opengl.util; + +import java.lang.Math; +import java.nio.*; +import java.util.*; +import org.joml.*; + +public class KDTreei> { + + public Node root; + private int maxVoxelCount = 2; + private float nodeIntersectCosts = 1.0f; + private float voxelIntersectCosts = 1.0f; + + private class IntervalBoundary { + int type; + int pos; + + IntervalBoundary(int t, int p) { + type = t; + pos = p; + } + + int compareTo(IntervalBoundary sib) { + return Integer.compare(pos, sib.pos); + } + } + + public static class Box implements Boundable { + public int minX, minY, minZ; + public int maxX, maxY, maxZ; + + public Box() { + } + + public Box(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { + this.minX = minX; + this.minY = minY; + this.minZ = minZ; + this.maxX = maxX; + this.maxY = maxY; + this.maxZ = maxZ; + } + + public Box(Box bbox) { + this.minX = bbox.minX; + this.minY = bbox.minY; + this.minZ = bbox.minZ; + this.maxX = bbox.maxX; + this.maxY = bbox.maxY; + this.maxZ = bbox.maxZ; + } + + public float diagonal() { + float dx = maxX - minX; + float dy = maxY - minY; + float dz = maxZ - minZ; + return (float) Math.sqrt(dx * dx + dy * dy + dz * dz); + } + + public int min(int axis) { + switch (axis) { + case 0: + return minX; + case 1: + return minY; + case 2: + return minZ; + default: + throw new IllegalArgumentException(); + } + } + + public int max(int axis) { + switch (axis) { + case 0: + return maxX; + case 1: + return maxY; + case 2: + return maxZ; + default: + throw new IllegalArgumentException(); + } + } + + public void setMax(int splitAxis, int split) { + switch (splitAxis) { + case 0: + maxX = split; + break; + case 1: + maxY = split; + break; + case 2: + maxZ = split; + break; + } + } + + public void setMin(int splitAxis, int split) { + switch (splitAxis) { + case 0: + minX = split; + break; + case 1: + minY = split; + break; + case 2: + minZ = split; + break; + } + } + + public boolean intersectsWithBox(Boundable vx) { + return maxX >= vx.min(0) && maxY >= vx.min(1) && maxZ >= vx.min(2) && minX <= vx.max(0) && minY <= vx.max(1) + && minZ <= vx.max(2); + } + + @Override + public void extend(int axis) { + throw new UnsupportedOperationException(); + } + + @Override + public Box splitLeft(int splitAxis, int splitPos) { + throw new UnsupportedOperationException(); + } + + @Override + public Box splitRight(int splitAxis, int splitPos) { + throw new UnsupportedOperationException(); + } + } + + public static class Voxel implements Boundable, Comparable { + public byte x, y, z; + public byte ex, ey, ez; + public int morton; + public byte paletteIndex; + public int nindex; + + public Voxel() { + } + + public Voxel(byte x, byte y, byte z, byte paletteIndex) { + this.x = x; + this.y = y; + this.z = z; + this.paletteIndex = paletteIndex; + } + + public Voxel(byte x, byte y, byte z, byte ex, byte ey, byte ez, byte paletteIndex) { + this.x = x; + this.y = y; + this.z = z; + this.ex = ex; + this.ey = ey; + this.ez = ez; + this.paletteIndex = paletteIndex; + } + + public int min(int axis) { + switch (axis) { + case 0: + return x & 0xFF; + case 1: + return y & 0xFF; + case 2: + return z & 0xFF; + default: + throw new IllegalArgumentException(); + } + } + + public int max(int axis) { + switch (axis) { + case 0: + return (x & 0xFF) + 1 + (ex & 0xFF); + case 1: + return (y & 0xFF) + 1 + (ey & 0xFF); + case 2: + return (z & 0xFF) + 1 + (ez & 0xFF); + default: + throw new IllegalArgumentException(); + } + } + + @Override + public void extend(int axis) { + switch (axis) { + case 0: + this.ex++; + break; + case 1: + this.ey++; + break; + case 2: + this.ez++; + break; + default: + throw new IllegalArgumentException(); + } + } + + @Override + public Voxel splitLeft(int axis, int pos) { + switch (axis) { + case 0: + return new Voxel(x, y, z, (byte) (pos - (x & 0xFF) - 1), ey, ez, paletteIndex); + case 1: + return new Voxel(x, y, z, ex, (byte) (pos - (y & 0xFF) - 1), ez, paletteIndex); + case 2: + return new Voxel(x, y, z, ex, ey, (byte) (pos - (z & 0xFF) - 1), paletteIndex); + default: + throw new IllegalArgumentException(); + } + } + + @Override + public Voxel splitRight(int axis, int pos) { + switch (axis) { + case 0: + return new Voxel((byte) pos, y, z, (byte) ((ex & 0xFF) - (pos - (x & 0xFF))), ey, ez, paletteIndex); + case 1: + return new Voxel(x, (byte) pos, z, ex, (byte) ((ey & 0xFF) - (pos - (y & 0xFF))), ez, paletteIndex); + case 2: + return new Voxel(x, y, (byte) pos, ex, ey, (byte) ((ez & 0xFF) - (pos - (z & 0xFF))), paletteIndex); + default: + throw new IllegalArgumentException(); + } + } + + @Override + public int compareTo(Voxel o) { + return Long.compare(morton, o.morton); + } + + @Override + public String toString() { + return "[" + x + ", " + y + ", " + z + "]"; + } + } + + public static class Node { + private static final int SIDE_X_POS = 0; + private static final int SIDE_X_NEG = 1; + private static final int SIDE_Y_POS = 2; + private static final int SIDE_Y_NEG = 3; + private static final int SIDE_Z_POS = 4; + private static final int SIDE_Z_NEG = 5; + + public int splitAxis = -1; + public int splitPos; + public Box boundingBox; + public Node left; + public Node right; + public List voxels = new ArrayList<>(); + public Node[] ropes; + public int index; + public int leafIndex; + public int first; + public int count; + + int isParallelTo(int side) { + switch (splitAxis) { + case 0: + return side == SIDE_X_NEG ? -1 : side == SIDE_X_POS ? +1 : 0; + case 1: + return side == SIDE_Y_NEG ? -1 : side == SIDE_Y_POS ? +1 : 0; + case 2: + return side == SIDE_Z_NEG ? -1 : side == SIDE_Z_POS ? +1 : 0; + default: + throw new IllegalArgumentException(); + } + } + + public final boolean isLeafNode() { + return splitAxis == -1; + } + + @SuppressWarnings("unchecked") + protected void processNode(Node[] ropes) { + if (isLeafNode()) { + this.ropes = ropes; + } else { + int sideLeft; + int sideRight; + if (splitAxis == 0) { + sideLeft = SIDE_X_NEG; + sideRight = SIDE_X_POS; + } else if (splitAxis == 1) { + sideLeft = SIDE_Y_NEG; + sideRight = SIDE_Y_POS; + } else if (splitAxis == 2) { + sideLeft = SIDE_Z_NEG; + sideRight = SIDE_Z_POS; + } else { + throw new AssertionError(); + } + this.left.ropes = new Node[6]; + System.arraycopy(ropes, 0, this.left.ropes, 0, 6); + this.left.ropes[sideRight] = this.right; + this.left.processNode(this.left.ropes); + this.right.ropes = new Node[6]; + System.arraycopy(ropes, 0, this.right.ropes, 0, 6); + this.right.ropes[sideLeft] = this.left; + this.right.processNode(this.right.ropes); + } + } + + protected void optimizeRopes() { + for (int i = 0; i < 6; i++) { + ropes[i] = optimizeRope(ropes[i], i); + } + if (left != null) + left.optimizeRopes(); + if (right != null) + right.optimizeRopes(); + } + + protected Node optimizeRope(Node rope, int side) { + if (rope == null) { + return rope; + } + Node r = rope; + while (!r.isLeafNode()) { + int parallelSide = r.isParallelTo(side); + if (parallelSide == +1) { + r = r.left; + } else if (parallelSide == -1) { + r = r.right; + } else { + if (r.splitPos < boundingBox.min(r.splitAxis)) { + r = r.right; + } else if (r.splitPos > boundingBox.max(r.splitAxis)) { + r = r.left; + } else { + break; + } + } + } + return r; + } + + public void frustumCull(Matrix4d viewProjection, Vector3d p, int depth, int maxDepth, int maxNodes, + PriorityQueue> nodes) { + Box b = boundingBox; + if (nodes.size() > maxNodes) + return; + if (!viewProjection.testAab(b.minX, b.minY, b.minZ, b.maxX, b.maxY, b.maxZ)) + return; + if (left == null || depth >= maxDepth) { + if (this.count > 0) + nodes.add(this); + } else if (left != null) { + if (p.get(splitAxis) < splitPos) { + left.frustumCull(viewProjection, p, depth + 1, maxDepth, maxNodes, nodes); + right.frustumCull(viewProjection, p, depth + 1, maxDepth, maxNodes, nodes); + } else { + right.frustumCull(viewProjection, p, depth + 1, maxDepth, maxNodes, nodes); + left.frustumCull(viewProjection, p, depth + 1, maxDepth, maxNodes, nodes); + } + } + } + + public Node findNode(Vector3d cameraPosition) { + Vector3d p = cameraPosition; + Box b = boundingBox; + if (p.x < b.minX || p.x > b.maxX || p.y < b.minY || p.y > b.maxY || p.z < b.minZ || p.z > b.maxZ + || left == null) + return this; + if (cameraPosition.get(splitAxis) < splitPos) + return left.findNode(cameraPosition); + return right.findNode(cameraPosition); + } + } + + @SuppressWarnings("unchecked") + public static > KDTreei build(List voxels, int maxDepth) { + return build(voxels, new Node[6], maxDepth); + } + + public static > KDTreei build(List voxels, Node[] neighbors, int maxDepth) { + Box b = new Box(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE, + Integer.MIN_VALUE); + for (T v : voxels) { + b.minX = b.minX < v.min(0) ? b.minX : v.min(0); + b.minY = b.minY < v.min(1) ? b.minY : v.min(1); + b.minZ = b.minZ < v.min(2) ? b.minZ : v.min(2); + b.maxX = b.maxX > v.max(0) ? b.maxX : v.max(0); + b.maxY = b.maxY > v.max(1) ? b.maxY : v.max(1); + b.maxZ = b.maxZ > v.max(2) ? b.maxZ : v.max(2); + } + KDTreei root = new KDTreei(); + root.buildTree(voxels, b, neighbors, maxDepth); + return root; + } + + public void frustumCull(Matrix4d viewProjection, Vector3d p, int maxDepth, int maxNodes, + PriorityQueue> nodes) { + root.frustumCull(viewProjection, p, 0, maxDepth, maxNodes, nodes); + } + + public Node findNode(Vector3d cameraPosition) { + return root.findNode(cameraPosition); + } + + public void level(int level, ShortBuffer nodes) { + level(root, level, nodes); + } + + private void level(Node n, int level, ShortBuffer nodes) { + if (level == 0 || n.left == null) { + nodes.put((short) n.index); + } else { + level(n.left, level - 1, nodes); + level(n.right, level - 1, nodes); + } + } + + public void buildTree(List list, Box bbox, Node[] neighbors, int maxDepth) { + if (root != null) + root = null; + root = new Node(); + root.voxels = list; + root.boundingBox = bbox; + buildTree(root, 0, 0, maxDepth); + root.processNode(root.ropes = neighbors); + root.optimizeRopes(); + } + + private void buildTree(Node node, int axis, int depth, int maxDepth) { + if (node.voxels.size() > maxVoxelCount && depth < maxDepth) { + node.splitAxis = axis; + node.splitPos = findSplitPlane(node); + } else { + node.splitAxis = -1; + } + if (node.splitAxis == -1) { + return; + } + if (node.voxels.size() > maxVoxelCount) { + node.left = new Node(); + node.right = new Node(); + node.left.boundingBox = new Box(node.boundingBox); + node.left.boundingBox.setMax(node.splitAxis, node.splitPos); + node.right.boundingBox = new Box(node.boundingBox); + node.right.boundingBox.setMin(node.splitAxis, node.splitPos); + node.voxels.forEach(vx -> { + if (vx.min(node.splitAxis) >= node.splitPos) { + node.right.voxels.add(vx); + } else if (vx.max(node.splitAxis) <= node.splitPos) { + node.left.voxels.add(vx); + } else { + T left = vx.splitLeft(node.splitAxis, node.splitPos); + T right = vx.splitRight(node.splitAxis, node.splitPos); + if (left.max(node.splitAxis) > node.splitPos) + throw new AssertionError(); + if (right.min(node.splitAxis) < node.splitPos) + throw new AssertionError(); + node.left.voxels.add(left); + node.right.voxels.add(right); + } + }); + node.voxels.clear(); + int nextAxis = (axis + 1) % 3; + buildTree(node.left, nextAxis, depth + 1, maxDepth); + buildTree(node.right, nextAxis, depth + 1, maxDepth); + } + } + + private int findSplitPlane(Node node) { + if (node == null) + return -1; + Box bb = node.boundingBox; + int nPrims = node.voxels.size(); + int xw = bb.maxX - bb.minX; + int yw = bb.maxY - bb.minY; + int zw = bb.maxZ - bb.minZ; + int box_width; + int ax; + if (xw > yw && xw > zw) { + ax = 0; + box_width = xw; + } else if (yw > zw) { + ax = 1; + box_width = yw; + } else { + ax = 2; + box_width = zw; + } + float inv_box_width = 1.0f / box_width; + List intervals = new ArrayList(); + final int count = node.voxels.size(); + final int divisor = (int) Math.ceil(count / 100.0); + nPrims /= divisor; + for (int i = 0; i < count; i += divisor) { + T vx = node.voxels.get(i); + if (!bb.intersectsWithBox(vx)) { + throw new IllegalStateException("!!! KDTree.findSplitPlane: no intersection of boxes"); + } + intervals.add(new IntervalBoundary(0, vx.min(ax))); + intervals.add(new IntervalBoundary(1, vx.max(ax))); + } + Collections.sort(intervals, (sib1, sib2) -> sib1.compareTo(sib2)); + int done_intervals = 0; + int open_intervals = 0; + float alpha; + int minid = 0; + float mincost = Float.MAX_VALUE; + for (int i = 0; i < intervals.size(); i++) { + IntervalBoundary in = intervals.get(i); + if (in.type == 1) { + open_intervals--; + done_intervals++; + } + alpha = (in.pos - bb.min(ax)) * inv_box_width; + float cost = voxelIntersectCosts + nodeIntersectCosts + * ((done_intervals + open_intervals) * alpha + (nPrims - done_intervals) * (1.0f - alpha)); + if (cost < mincost) { + minid = i; + mincost = cost; + } + if (in.type == 0) { + open_intervals++; + } + } + int splitPlane = intervals.get(minid).pos; + if (splitPlane == bb.min(ax) || splitPlane == bb.max(ax)) { + node.splitAxis = -1; + return -1; + } + node.splitAxis = ax; + return intervals.get(minid).pos; + } + +}