From 94622f8a8f432b2ee50848005f88f3f00d9e9ace Mon Sep 17 00:00:00 2001 From: Alex <42661490+ThatRedox@users.noreply.github.com> Date: Sat, 3 Dec 2022 01:37:06 -0800 Subject: [PATCH] Fix ray clipping in alpha pass for parallel projection. (#1508) --- .../projection/ParallelProjector.java | 29 +++++++ .../se/llbit/chunky/renderer/scene/Scene.java | 77 ++++++------------- 2 files changed, 51 insertions(+), 55 deletions(-) diff --git a/chunky/src/java/se/llbit/chunky/renderer/projection/ParallelProjector.java b/chunky/src/java/se/llbit/chunky/renderer/projection/ParallelProjector.java index 940ba1e2d5..95f0200da8 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/projection/ParallelProjector.java +++ b/chunky/src/java/se/llbit/chunky/renderer/projection/ParallelProjector.java @@ -18,6 +18,8 @@ import java.util.Random; +import se.llbit.chunky.renderer.scene.Scene; +import se.llbit.math.Ray; import se.llbit.math.Vector3; /** @@ -52,4 +54,31 @@ public ParallelProjector(double worldDiagonalSize, double fov) { @Override public double getDefaultFoV() { return worldDiagonalSize / 2; } + + public static void fixRay(Ray ray, Scene scene) { + // When in parallel projection, push the ray origin back so the + // ray start outside the octree to prevent ray spawning inside some blocks + int limit = (1 << scene.getWorldOctree().getDepth()); + Vector3 o = ray.o; + Vector3 d = ray.d; + double t = 0; + // simplified intersection test with the 6 planes that form the bounding box of the octree + if(Math.abs(d.x) > Ray.EPSILON) { + t = Math.min(t, -o.x / d.x); + t = Math.min(t, (limit - o.x) / d.x); + } + if(Math.abs(d.y) > Ray.EPSILON) { + t = Math.min(t, -o.y / d.y); + t = Math.min(t, (limit - o.y) / d.y); + } + if(Math.abs(d.z) > Ray.EPSILON) { + t = Math.min(t, -o.z / d.z); + t = Math.min(t, (limit - o.z) / d.z); + } + // set the origin to the farthest intersection point behind + // In theory, we only would need to set it to the closest intersection point behind + // but this doesn't matter because the Octree.enterOctree function + // will do the same amount of math for the same result no matter what the exact point is + ray.o.scaleAdd(t, d); + } } diff --git a/chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java b/chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java index eb8054e9e6..e3a3bb6921 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java @@ -39,6 +39,7 @@ import se.llbit.chunky.renderer.postprocessing.PostProcessingFilter; import se.llbit.chunky.renderer.postprocessing.PostProcessingFilters; import se.llbit.chunky.renderer.postprocessing.PreviewFilter; +import se.llbit.chunky.renderer.projection.ParallelProjector; import se.llbit.chunky.renderer.projection.ProjectionMode; import se.llbit.chunky.renderer.renderdump.RenderDump; import se.llbit.chunky.renderer.scene.biome.BiomeStructure; @@ -683,30 +684,7 @@ public void rayTrace(RayTracer rayTracer, WorkerState state) { state.ray.o.z -= origin.z; if(camera.getProjectionMode() == ProjectionMode.PARALLEL) { - // When in parallel projection, push the ray origin back so the - // ray start outside the octree to prevent ray spawning inside some blocks - int limit = (1 << worldOctree.getDepth()); - Vector3 o = state.ray.o; - Vector3 d = state.ray.d; - double t = 0; - // simplified intersection test with the 6 planes that form the bounding box of the octree - if(Math.abs(d.x) > Ray.EPSILON) { - t = Math.min(t, -o.x / d.x); - t = Math.min(t, (limit - o.x) / d.x); - } - if(Math.abs(d.y) > Ray.EPSILON) { - t = Math.min(t, -o.y / d.y); - t = Math.min(t, (limit - o.y) / d.y); - } - if(Math.abs(d.z) > Ray.EPSILON) { - t = Math.min(t, -o.z / d.z); - t = Math.min(t, (limit - o.z) / d.z); - } - // set the origin to the farthest intersection point behind - // In theory, we only would need to set it to the closest intersection point behind - // but this doesn't matter because the Octree.enterOctree function - // will do the same amount of math for the same result no matter what the exact point is - o.scaleAdd(t, d); + ParallelProjector.fixRay(state.ray, this); } rayTracer.trace(this, state); @@ -2380,38 +2358,27 @@ public void computeAlpha(int x, int y, WorkerState state) { double invHeight = 1.0 / height; // Rotated grid supersampling. + double[][] offsets = new double[][] { + { -3.0 / 8.0, 1.0 / 8.0 }, + { 1.0 / 8.0, 3.0 / 8.0 }, + { -1.0 / 8.0, -3.0 / 8.0 }, + { 3.0 / 8.0, -1.0 / 8.0 }, + }; - camera - .calcViewRay(ray, -halfWidth + (x - 3 / 8.0) * invHeight, -.5 + (y + 1 / 8.0) * invHeight); - ray.o.x -= origin.x; - ray.o.y -= origin.y; - ray.o.z -= origin.z; - - double occlusion = PreviewRayTracer.skyOcclusion(this, state); - - camera - .calcViewRay(ray, -halfWidth + (x + 1 / 8.0) * invHeight, -.5 + (y + 3 / 8.0) * invHeight); - ray.o.x -= origin.x; - ray.o.y -= origin.y; - ray.o.z -= origin.z; - - occlusion += PreviewRayTracer.skyOcclusion(this, state); - - camera - .calcViewRay(ray, -halfWidth + (x - 1 / 8.0) * invHeight, -.5 + (y - 3 / 8.0) * invHeight); - ray.o.x -= origin.x; - ray.o.y -= origin.y; - ray.o.z -= origin.z; - - occlusion += PreviewRayTracer.skyOcclusion(this, state); - - camera - .calcViewRay(ray, -halfWidth + (x + 3 / 8.0) * invHeight, -.5 + (y - 1 / 8.0) * invHeight); - ray.o.x -= origin.x; - ray.o.y -= origin.y; - ray.o.z -= origin.z; - - occlusion += PreviewRayTracer.skyOcclusion(this, state); + double occlusion = 0.0; + for (double[] offset : offsets) { + camera.calcViewRay(ray, + -halfWidth + (x + offset[0]) * invHeight, + -0.5 + (y + offset[1]) * invHeight); + ray.o.x -= origin.x; + ray.o.y -= origin.y; + ray.o.z -= origin.z; + + if (camera.getProjectionMode() == ProjectionMode.PARALLEL) { + ParallelProjector.fixRay(state.ray, this); + } + occlusion += PreviewRayTracer.skyOcclusion(this, state); + } alphaChannel[y * width + x] = (byte) (255 * occlusion * 0.25 + 0.5); }