187 lines
5.5 KiB
GLSL
187 lines
5.5 KiB
GLSL
#include "/Lib/Settings.glsl"
|
|
#include "/Lib/Utilities.glsl"
|
|
// MinecraftPT — Tracing Utilities
|
|
// Core voxel ray marching helpers used by all tracers.
|
|
|
|
// Custom resource declarations (bound via shaders.properties)
|
|
uniform sampler2D atlas2D;
|
|
uniform sampler3D voxelData3D;
|
|
uniform sampler2D skyBox2D;
|
|
|
|
// Sample the sky box panorama for a direction (for reflections / sky miss).
|
|
// Layout must match SkyImage_CS: 3x2 tiles:
|
|
// row 0: +X -X +Y row 1: -Y +Z -Z
|
|
vec3 SampleSkyBox(vec3 dir){
|
|
dir = normalize(dir);
|
|
|
|
float absX = abs(dir.x), absY = abs(dir.y), absZ = abs(dir.z);
|
|
float maxAxis = max(absX, max(absY, absZ));
|
|
|
|
vec3 tdir = dir / maxAxis;
|
|
|
|
vec2 uv;
|
|
vec2 tileOrigin;
|
|
vec2 tile = vec2(1.0 / 3.0, 1.0 / 2.0);
|
|
|
|
if (maxAxis == absX){
|
|
// +X (col 0) or -X (col 1), row 0
|
|
tileOrigin = vec2(dir.x > 0.0 ? 0.0 : 1.0, 0.0);
|
|
uv = vec2(tdir.y * 0.5 + 0.5, tdir.z * 0.5 + 0.5);
|
|
}else if (maxAxis == absY){
|
|
// +Y (col 2, row 0) or -Y (col 0, row 1)
|
|
tileOrigin = dir.y > 0.0 ? vec2(2.0, 0.0) : vec2(0.0, 1.0);
|
|
uv = vec2(tdir.x * 0.5 + 0.5, tdir.z * 0.5 + 0.5);
|
|
}else{
|
|
// +Z (col 1, row 1) or -Z (col 2, row 1)
|
|
tileOrigin = vec2(dir.z > 0.0 ? 1.0 : 2.0, 1.0);
|
|
uv = vec2(tdir.x * 0.5 + 0.5, tdir.y * 0.5 + 0.5);
|
|
}
|
|
|
|
uv = uv * tile + tileOrigin * tile;
|
|
|
|
return textureLod(skyBox2D, uv, 0.0).rgb;
|
|
}
|
|
|
|
|
|
#ifndef TRACING_UTILITIES_GLSL
|
|
#define TRACING_UTILITIES_GLSL
|
|
|
|
#include "/Lib/PathTracing/Voxelizer/VoxelProfile.glsl"
|
|
#include "/Lib/PathTracing/Voxelizer/BlockShape.glsl"
|
|
|
|
// Convert world position to voxel grid coordinates
|
|
vec3 WorldToVoxel(vec3 worldPos){
|
|
return worldPos + cameraPositionFract + (voxelResolution * 0.5);
|
|
}
|
|
|
|
// Sample the atlas for a hit voxel, returns albedo
|
|
vec3 SampleVoxelAlbedo(vec2 midCoord, vec3 hitVoxelPos, vec3 hitNormal){
|
|
// Reconstruct the face UV from hit position within voxel
|
|
vec3 local = fract(hitVoxelPos);
|
|
vec2 uv;
|
|
if (abs(hitNormal.x) > 0.5){
|
|
uv = vec2(local.z, local.y);
|
|
}else if (abs(hitNormal.y) > 0.5){
|
|
uv = vec2(local.x, local.z);
|
|
}else{
|
|
uv = vec2(local.x, local.y);
|
|
}
|
|
|
|
// midCoord is the tile-aligned center UV; reconstruct pixel within tile
|
|
vec2 tileCoord = midCoord;
|
|
vec2 atlasSizeF = vec2(atlasSize);
|
|
float tileW = 1.0 / atlasSizeF.x; // approximate; resolved via textureResolution
|
|
vec2 pixelUV = tileCoord + (uv - 0.5) * tileW * 4.0;
|
|
|
|
return textureLod(atlas2D, pixelUV, 0.0).rgb;
|
|
}
|
|
|
|
// Read a voxel's data
|
|
vec4 ReadVoxel(ivec3 voxelCoord){
|
|
return texelFetch(voxelData3D, voxelCoord, 0);
|
|
}
|
|
|
|
// Test whether a voxel is empty (air or empty marker)
|
|
bool IsVoxelEmpty(vec4 voxelData){
|
|
float voxelID = DecodeVoxelID(voxelData.z);
|
|
return voxelID <= 1.0;
|
|
}
|
|
|
|
// Test whether a voxel is a light source (sphere light)
|
|
bool IsLightSphere(float voxelID){
|
|
return voxelID >= 239.0 && voxelID <= 290.0;
|
|
}
|
|
|
|
// Core 3D-DDA march. Calls the callback-style inline logic via return.
|
|
// Returns: 0 = no hit, 1 = hit, 2 = hit light
|
|
int TraceRay(Ray ray, float maxDist, out float rayLength, out ivec3 hitVoxel, out float voxelID, out vec3 hitNormal){
|
|
vec3 voxelPos = ray.ori;
|
|
vec3 voxelCoord = floor(voxelPos);
|
|
vec3 totalStep = (ray.sdir * (voxelCoord - voxelPos + 0.5) + 0.5) * abs(ray.rdir);
|
|
rayLength = 0.0;
|
|
vec3 tracingNext;
|
|
hitNormal = vec3(0.0);
|
|
|
|
int result = 0;
|
|
vec3 prevCoord = voxelCoord;
|
|
|
|
for (int i = 0; i < 128; i++){
|
|
if (clamp(voxelCoord, vec3(0.0), vec3(voxelResolution - 0.5)) != voxelCoord) break;
|
|
|
|
vec4 voxelData = texelFetch(voxelData3D, ivec3(voxelCoord), 0);
|
|
float id = DecodeVoxelID(voxelData.z);
|
|
|
|
if (id <= 1.0){
|
|
// empty or full-block marker
|
|
if (id >= 0.99 && id <= 1.0){
|
|
// Full block
|
|
if (rayLength > 0.0){
|
|
hitVoxel = ivec3(voxelCoord);
|
|
voxelID = 1.0;
|
|
hitNormal = -step(vec3(rayLength), totalStep - abs(ray.rdir)) * ray.sdir;
|
|
result = 1;
|
|
break;
|
|
}
|
|
}
|
|
}else if (id >= 1000.0){
|
|
// Encoded as full block: id = rawID + 1000
|
|
float rawID = id - 1000.0;
|
|
if (rawID <= 1.0){
|
|
hitVoxel = ivec3(voxelCoord);
|
|
voxelID = rawID;
|
|
hitNormal = -step(vec3(rayLength), totalStep - abs(ray.rdir)) * ray.sdir;
|
|
result = 1;
|
|
break;
|
|
}
|
|
}else if (id > 1.0 && id < 1000.0){
|
|
// Cutout shape: id = 1000 - rawID
|
|
float rawID = 1000.0 - id;
|
|
float t = minVec3(totalStep);
|
|
rayLength = t;
|
|
vec3 n;
|
|
if (HitShape(ray, voxelCoord, rawID, rayLength, n)){
|
|
hitVoxel = ivec3(voxelCoord);
|
|
voxelID = rawID;
|
|
hitNormal = n;
|
|
result = 1;
|
|
break;
|
|
}
|
|
}else if (id >= 200.0 && id <= 290.0){
|
|
// Light source — contribute and continue
|
|
// (handled by caller; here just note it)
|
|
hitVoxel = ivec3(voxelCoord);
|
|
voxelID = id;
|
|
result = 2;
|
|
break;
|
|
}
|
|
|
|
// Sparse tracing: skip empty markers
|
|
float marker = voxelData.z;
|
|
if (marker > 0.60 && marker < 0.92){
|
|
// Hierarchical skip — approximate by stepping over the block size
|
|
float skipSize = marker > 0.90 ? 8.0 : (marker > 0.70 ? 4.0 : 2.0);
|
|
// find next boundary
|
|
vec3 nextBoundary = floor((voxelCoord + 1.0) / skipSize) * skipSize;
|
|
vec3 distToBoundary = (nextBoundary - voxelCoord) * abs(ray.rdir);
|
|
float tSkip = minVec3(distToBoundary) + 1e-4;
|
|
rayLength += tSkip;
|
|
vec3 stepVec = ray.sdir * abs(ray.rdir) * tSkip;
|
|
voxelPos += stepVec;
|
|
voxelCoord = floor(voxelPos);
|
|
totalStep = (ray.sdir * (voxelCoord - voxelPos + 0.5) + 0.5) * abs(ray.rdir);
|
|
if (rayLength > maxDist) break;
|
|
continue;
|
|
}
|
|
|
|
rayLength = minVec3(totalStep);
|
|
tracingNext = step(totalStep, vec3(rayLength));
|
|
voxelCoord += tracingNext * ray.sdir;
|
|
totalStep += tracingNext * abs(ray.rdir);
|
|
|
|
if (rayLength > maxDist) break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#endif |