Files
CDAG/Research/shaders/StackRaytrace.frag

340 lines
12 KiB
GLSL

#version 440 core
in vec3 pos;
in vec2 uv;
in vec3 normal;
out vec4 color;
uniform sampler2D textureSampler;
uniform usampler3D octreeSampler;
layout(location = $width$) uniform int width;
layout(location = $height$) uniform int height;
layout(location = $angle$) uniform float angle;
layout(location = $aspect$) uniform float aspect;
layout(location = $lightDirection$) uniform vec3 lightDirection;
layout(location = $cameraPosition$) uniform vec3 cameraPosition;
struct Ray
{
vec3 p0;
vec3 dir;
vec3 invDir;
vec3 dirSign;
uvec3 dirSignNegMask;
};
ivec3 texSize;
const uint range = 1 << ($max_level$ + 1);
const vec3 rootCenter = vec3(range) * 0.5;
const float rootExtent = float(range) * 0.5;
// Initialize the stack
uvec3 pointerStack[$max_level$ + 1];
vec3 positionStack[$max_level$ + 1];
float extentStack[$max_level$ + 1];
uint stackPos;
Ray getRay(vec3 p0, vec3 dir)
{
dir = normalize(dir);
vec3 dirSign = sign(dir);
return Ray(
p0,
dir,
1 / dir,
dirSign,
uvec3(-clamp(sign(dir), -1, 0)));
}
vec3 sampleRay(Ray ray, float time)
{
return ray.dir * time + ray.p0;
}
//************************************
// Intersection test between ray and cube, also gives intersection points as p0 + tmin * ray and p0 + tmax * ray
//************************************
bool rayCube(in Ray ray, in vec3 center, in float extent, out float tmin, out float tmax, out vec3 minAxis, out vec3 maxAxis) {
// Translate ray origin based on cube center
vec3 p0 = ray.p0 - center;
// Get t from ray and cube's plane equations and use it to get the intersection coordinates
float t1 = -(dot(p0, vec3(1., 0., 0.)) - extent) / dot(ray.dir, vec3(1., 0., 0.)); vec3 test1 = p0 + t1*ray.dir;
float t2 = -(dot(p0, vec3(-1., 0., 0.)) - extent) / dot(ray.dir, vec3(-1., 0., 0.)); vec3 test2 = p0 + t2*ray.dir;
float t3 = -(dot(p0, vec3(0., 1., 0.)) - extent) / dot(ray.dir, vec3(0., 1., 0.)); vec3 test3 = p0 + t3*ray.dir;
float t4 = -(dot(p0, vec3(0., -1., 0.)) - extent) / dot(ray.dir, vec3(0., -1., 0.)); vec3 test4 = p0 + t4*ray.dir;
float t5 = -(dot(p0, vec3(0., 0., 1.)) - extent) / dot(ray.dir, vec3(0., 0., 1.)); vec3 test5 = p0 + t5*ray.dir;
float t6 = -(dot(p0, vec3(0., 0., -1.)) - extent) / dot(ray.dir, vec3(0., 0., -1.)); vec3 test6 = p0 + t6*ray.dir;
// Check if t was not negative and that the ray-plane intersection falls within the cube face
if (t1 < 0. || any(greaterThan(test1.yz, vec2(extent))) || any(lessThan(test1.yz, vec2(-extent))))
t1 = 0.;
if (t2 < 0. || any(greaterThan(test2.yz, vec2(extent))) || any(lessThan(test2.yz, vec2(-extent))))
t2 = 0.;
if (t3 < 0. || any(greaterThan(test3.xz, vec2(extent))) || any(lessThan(test3.xz, vec2(-extent))))
t3 = 0.;
if (t4 < 0. || any(greaterThan(test4.xz, vec2(extent))) || any(lessThan(test4.xz, vec2(-extent))))
t4 = 0.;
if (t5 < 0. || any(greaterThan(test5.xy, vec2(extent))) || any(lessThan(test5.xy, vec2(-extent))))
t5 = 0.;
if (t6 < 0. || any(greaterThan(test6.xy, vec2(extent))) || any(lessThan(test6.xy, vec2(-extent))))
t6 = 0.;
// Initialize tmin and tmax values that define the two intersection points
tmin = 9999999999.;
tmax = 0.;
// Use the lowest value of t that is not 0 for tmin
if (t1 > 0. && t1 < tmin) { tmin = t1; minAxis = vec3(1, 0, 0); }
if (t2 > 0. && t2 < tmin) { tmin = t2; minAxis = vec3(1, 0, 0); }
if (t3 > 0. && t3 < tmin) { tmin = t3; minAxis = vec3(0, 1, 0); }
if (t4 > 0. && t4 < tmin) { tmin = t4; minAxis = vec3(0, 1, 0); }
if (t5 > 0. && t5 < tmin) { tmin = t5; minAxis = vec3(0, 0, 1); }
if (t6 > 0. && t6 < tmin) { tmin = t6; minAxis = vec3(0, 0, 1); }
// Use the highest value of t that is not 0 for tmax
if (t1 > 0. && t1 > tmax) { tmax = t1; maxAxis = vec3(1, 0, 0); }
if (t2 > 0. && t2 > tmax) { tmax = t2; maxAxis = vec3(1, 0, 0); }
if (t3 > 0. && t3 > tmax) { tmax = t3; maxAxis = vec3(0, 1, 0); }
if (t4 > 0. && t4 > tmax) { tmax = t4; maxAxis = vec3(0, 1, 0); }
if (t5 > 0. && t5 > tmax) { tmax = t5; maxAxis = vec3(0, 0, 1); }
if (t6 > 0. && t6 > tmax) { tmax = t6; maxAxis = vec3(0, 0, 1); }
// If tmin = tmax, the ray origin is within the cube, so set tmin to 0
if (tmin == tmax)
tmin = 0.;
// If tmax is not 0, an intersection was found
return tmax > 0.;
}
// Assuming the ray hits the given cube, calculates the time at which it hits it
void rayCubeExit(in Ray ray, in vec3 center, in float extent, out float t, out vec3 axis)
{
// Edges taking into account the direction of the ray
vec3 voxelEdges = center + (extent * ray.dirSign);
// Calculate the times at which the planes that span on the edges of the cubes are hit
vec3 axisTimes = (voxelEdges - ray.p0) * ray.invDir;
// The smallest of these times is side of the cube the ray hits (assuming it hits the cube)
t = min(min(axisTimes.x, axisTimes.y), axisTimes.z);
if (axisTimes.x == t) axis = vec3(1, 0, 0);
else if (axisTimes.y == t) axis = vec3(0, 1, 0);
else axis = vec3(0, 0, 1);
}
ivec3 wrapNodePointer(in ivec3 samplePos)
{
// Make sure that the indices are wrapped if the pointer doesn't fit in the current texture
if (samplePos.x >= texSize.x)
{
samplePos.x = samplePos.x % texSize.x;
samplePos.y++;
if (samplePos.y >= texSize.y)
{
samplePos.y = samplePos.y % texSize.y;
samplePos.z++;
}
//return vec4(255, 255, 0, float($max_level$ + 1));
}
return samplePos;
}
//************************************
// Given a pointer to a node, and it's childmask, and some child index
// returns the nodePointer to the child at the given index.
// Note that if the child doesn't exist, it still returns some pointer as if it would.
//************************************
uvec3 getNodePointer(uvec3 curNodePointer, uint childMask, uint childIndex)
{
// Based on the childIndex and the childmask, find out what the index of the childpointer is
int childPointerIndex = bitCount(childMask << (31 - childIndex));
// Find sample position in 3D texture corresponding to current node and locat
ivec3 samplePos = ivec3(curNodePointer);
samplePos.x += childPointerIndex;
// Make sure that the indices are wrapped if the pointer doesn't fit in the current texture
samplePos = wrapNodePointer(samplePos);
// Fetch the pointer from the texture
return texelFetch(octreeSampler, samplePos, 0).rgb;
}
// Fetches the childmask of the node at curNodePointer (note that this is not a mask for leaf nodes!)
uint getChildMask(in uvec3 nodePointer)
{
return texelFetch(octreeSampler, ivec3(nodePointer), 0).b;
}
uvec3 getColor(in uvec3 nodePointer)
{
return texelFetch(octreeSampler, ivec3(nodePointer), 0).rgb;
}
bool hasChild(in uint childMask, in uint childIndex)
{
uint mask = 1 << childIndex;
return (childMask & mask) == mask;
}
ivec3 getNextULoc(in Ray ray, in float t, in ivec3 lastULoc, in vec3 hitAxis)
{
vec3 samplePoint = ray.p0 + t * ray.dir/* + 0.005 * ray.dirSign*/;
vec3 hitDirMask = ray.dirSign * hitAxis;
// Calculate the location of the first leaf node that the ray enters
// HACK: Cast to int, and then to uint, because casting int to uint gets uint.maxValue if the value == -1,
// but float to uint directly rounds and gives 0, meaning you will never get out of the cube!
ivec3 nextULoc = ivec3(floor(samplePoint + hitDirMask));
return ivec3(ray.dirSign) * max(ivec3(ray.dirSign) * nextULoc, ivec3(ray.dirSign) * lastULoc);
}
uint getChildIndex(uvec3 uLoc, uint level)
{
uint range = 1 << ($max_level$ - level);
return int((uLoc.x & range) == range) * 1
+ int((uLoc.y & range) == range) * 2
+ int((uLoc.z & range) == range) * 4;
}
//****************************************
// Gets the position and scale (extent) of the child voxel at the given index
//****************************************
vec3 getChildPosition(vec3 parentPos, float childExtent, uint childIndex)
{
vec3 childOffset = vec3(
((childIndex & 1) == 1) ? childExtent : -childExtent,
((childIndex & 2) == 2) ? childExtent : -childExtent,
((childIndex & 4) == 4) ? childExtent : -childExtent);
return parentPos + childOffset;
}
//***********************************************
// Casts a ray against the octree structure, which is assumed to be located at rootcenter, with leaf voxels a size of 1, 1, 1
// Returns 0 if no intersection, 1 if intersection, and -1 if it is unknown if there was an intersection due to the maximum loop being reached
//***********************************************
int castRay(in Ray ray, in uint maxLoop, out float t, out uvec3 hitNodePointer, out vec3 hitAxis)
{
// Initialize out values
t = 0;
hitNodePointer = uvec3(0);
hitAxis = vec3(0);
// Initialize the stack
stackPos = 0;
for (int i = 0; i <= $max_level$; i++)
{
pointerStack[i] = uvec3(0); // The root node is always at position 0, 0, 0 in the texture
positionStack[i] = rootCenter;
extentStack[i] = float(1 << ($max_level$ - i + 1)) * 0.5;
}
// Find out if the ray intersects with the root node
float tminRoot, tmaxRoot;
vec3 minAxis, maxAxis;
bool collision = rayCube(ray, rootCenter, rootExtent, tminRoot, tmaxRoot, minAxis, maxAxis);
if (!collision) // Ray doesn't intersect with root.
return 0;
// Calculate the location of the first leaf node that the ray enters
ivec3 uLoc = getNextULoc(ray, tminRoot, ivec3(ray.p0), minAxis);
// Keep testing ray box intersection, but prevent endless looping
int loop = 0;
uvec3 rangeU = uvec3(range);
while (stackPos < $max_level$ && loop < maxLoop) {
uint preferredChild = getChildIndex(uLoc, stackPos);
float preferredChildExtent = extentStack[stackPos + 1];
vec3 preferredChildPosition = getChildPosition(positionStack[stackPos], preferredChildExtent, preferredChild);
uint childMask = getChildMask(pointerStack[stackPos]);
//************************************
// PUSH
// If we have the preferred child, explore it
//************************************
if (hasChild(childMask, preferredChild))
{
pointerStack[stackPos + 1] = getNodePointer(pointerStack[stackPos], childMask, preferredChild);
positionStack[stackPos + 1] = preferredChildPosition;
stackPos++;
}
//*************************************
// ADVANCE/POP
// If we didn't have the preferred child, find the next preferred child.
//*************************************
else
{
// Find the first child that the ray enters after it exits the current preferred child
rayCubeExit(ray, preferredChildPosition, preferredChildExtent, t, maxAxis);
ivec3 lastULoc = uLoc;
uLoc = getNextULoc(ray, t - 0.05, lastULoc, maxAxis);
if (any(lessThan(uLoc, uvec3(0))) || any(greaterThanEqual(uLoc, rangeU)))
return 0;
// Count the number of bits that were the same in the last uLoc (until they start not being the same)
// Since lastULoc is in the current node, we know that this number of bits is the last common parent, so we pop up to that level.
// Note that this level is indeed one higher, but we need that to advance to the next cell?
ivec3 equalBits = $max_level$ - findMSB(lastULoc ^ uLoc);
stackPos = min(min(equalBits.x, equalBits.y), equalBits.z);
}
++loop;
}
hitAxis = maxAxis;
hitNodePointer = pointerStack[stackPos];
// Check if the current node is a leaf node. If so, draw it:
if (stackPos >= $max_level$)
return 1;
return -1;
}
void main() {
uint maxLoop = 4096;
texSize = textureSize(octreeSampler, 0);
// Set color using textures
color = texture(textureSampler, uv);
// Scale the grid so that all leaf cells have a width of exactly 1:
float extent = $extent$;
float rangeF = float(range);
float scale = rangeF / (extent * 2.);
float offset = extent + 0.5 / scale;
// Get ray and sample point
vec3 p0 = (cameraPosition + offset) * scale;
vec3 rayDir = normalize(pos - cameraPosition);
Ray ray = getRay(p0, rayDir);
// Cast the primary ray
float time;
vec3 hitAxis;
uvec3 hitNodePointer;
int primaryRayHit = castRay(ray, maxLoop, time, hitNodePointer, hitAxis);
if (primaryRayHit == 1)
{
color.rgb = vec3(getColor(hitNodePointer)) / 255.;
// Cast one shadow ray
if (lightDirection != vec3(0))
{
Ray lightRay = getRay(ray.dir * time + p0 - ray.dir * 0.01, -lightDirection);
int shadowResult = castRay(lightRay, max(range / 1024, 1000), time, hitNodePointer, hitAxis);
if (shadowResult == 1 || shadowResult == -1)
color.rgb *= 0.5 + (0.5 - float(abs(shadowResult)) * 0.5);
}
}
// Discard pixels that are transparent
if (color.a < 0.5)
discard;
}