diff --git a/chunky/src/java/se/llbit/chunky/renderer/WorkerState.java b/chunky/src/java/se/llbit/chunky/renderer/WorkerState.java index 380ffa28af..a838c207a1 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/WorkerState.java +++ b/chunky/src/java/se/llbit/chunky/renderer/WorkerState.java @@ -19,6 +19,8 @@ import se.llbit.math.Ray; import se.llbit.math.Vector4; +import java.util.LinkedList; +import java.util.List; import java.util.Random; /** @@ -28,4 +30,33 @@ public class WorkerState { public Ray ray; public Vector4 attenuation = new Vector4(); public Random random; + private List pool = new LinkedList<>(); + + public WorkerState() { + for (int i = 0; i < 10; i++) { + pool.add(new Ray()); + } + } + + public Ray newRay() { + if (pool.isEmpty()) { + return new Ray(); + } + Ray ray = pool.remove(0); + ray.setDefault(); + return ray; + } + + public void returnRay(Ray ray) { + pool.add(ray); + } + + public Ray newRay(Ray original) { + if (pool.isEmpty()) { + return new Ray(original); + } + Ray ray = pool.remove(0); + ray.set(original); + return ray; + } } diff --git a/chunky/src/java/se/llbit/chunky/renderer/scene/NishitaSky.java b/chunky/src/java/se/llbit/chunky/renderer/scene/NishitaSky.java index 9e72c733e3..b0ba52aa9c 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/NishitaSky.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/NishitaSky.java @@ -84,10 +84,10 @@ public String getDescription() { } @Override - public Vector3 calcIncidentLight(Ray ray) { + public Vector3 calcIncidentLight(Vector3 d) { // Render from just above the surface of "earth" - Vector3 origin = new Vector3(0, ray.o.y + EARTH_RADIUS + 1, 0); - Vector3 direction = ray.d; + Vector3 origin = new Vector3(0, EARTH_RADIUS + 1, 0); + Vector3 direction = d; direction.y += horizonOffset; direction.normalize(); diff --git a/chunky/src/java/se/llbit/chunky/renderer/scene/PathTracer.java b/chunky/src/java/se/llbit/chunky/renderer/scene/PathTracer.java index 1e2127593a..bead386334 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/PathTracer.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/PathTracer.java @@ -65,7 +65,7 @@ public static boolean pathTrace(Scene scene, Ray ray, WorkerState state, int add while (true) { - if (!PreviewRayTracer.nextIntersection(scene, ray)) { + if (!PreviewRayTracer.nextIntersection(scene, ray, state)) { if (ray.getPrevMaterial().isWater()) { ray.color.set(0, 0, 0, 1); hit = true; @@ -126,7 +126,7 @@ public static boolean pathTrace(Scene scene, Ray ray, WorkerState state, int add break; } Vector4 cumulativeColor = new Vector4(0, 0, 0, 0); - Ray next = new Ray(); + Ray next = state.newRay(); float pMetal = currentMat.metalness; // Reusing first rays - a simplified form of "branched path tracing" (what Blender used to call it before they implemented something fancier) // The initial rays cast into the scene are very similar between each sample, since they are almost entirely a function of the pixel coordinates @@ -187,7 +187,7 @@ public static boolean pathTrace(Scene scene, Ray ray, WorkerState state, int add // travelled through glass or other materials between air gaps. // However, the results are probably close enough to not be distracting, // so this seems like a reasonable approximation. - Ray atmos = new Ray(); + Ray atmos = state.newRay(); double offset = scene.fog.sampleGroundScatterOffset(ray, ox, random); atmos.o.scaleAdd(offset, od, ox); scene.sun.getRandomSunDirection(atmos, random); @@ -196,6 +196,7 @@ public static boolean pathTrace(Scene scene, Ray ray, WorkerState state, int add // Check sun visibility at random point to determine inscatter brightness. getDirectLightAttenuation(scene, atmos, state); scene.fog.addGroundFog(ray, ox, airDistance, state.attenuation, offset); + state.returnRay(atmos); } return hit; @@ -495,20 +496,21 @@ private static void addSkyFog(Scene scene, Ray ray, WorkerState state, Vector3 o if (scene.fog.mode == FogMode.UNIFORM) { scene.fog.addSkyFog(ray, null); } else if (scene.fog.mode == FogMode.LAYERED) { - Ray atmos = new Ray(); + Ray atmos = state.newRay(); double offset = scene.fog.sampleSkyScatterOffset(scene, ray, state.random); atmos.o.scaleAdd(offset, od, ox); scene.sun.getRandomSunDirection(atmos, state.random); atmos.setCurrentMaterial(Air.INSTANCE); getDirectLightAttenuation(scene, atmos, state); scene.fog.addSkyFog(ray, state.attenuation); + state.returnRay(atmos); } } - private static void sampleEmitterFace(Scene scene, Ray ray, Grid.EmitterPosition pos, int face, Vector4 result, double scaler, Random random) { - Ray emitterRay = new Ray(ray); + private static void sampleEmitterFace(Scene scene, Ray ray, Grid.EmitterPosition pos, int face, Vector4 result, double scaler, WorkerState state) { + Ray emitterRay = state.newRay(ray); - pos.sampleFace(face, emitterRay.d, random); + pos.sampleFace(face, emitterRay.d, state.random); emitterRay.d.sub(emitterRay.o); if (emitterRay.d.dot(ray.getNormal()) > 0) { @@ -517,7 +519,7 @@ private static void sampleEmitterFace(Scene scene, Ray ray, Grid.EmitterPosition emitterRay.o.scaleAdd(Ray.OFFSET, emitterRay.d); emitterRay.distance += Ray.OFFSET; - PreviewRayTracer.nextIntersection(scene, emitterRay); + PreviewRayTracer.nextIntersection(scene, emitterRay, state); if (Math.abs(emitterRay.distance - distance) < Ray.OFFSET) { double e = Math.abs(emitterRay.d.dot(emitterRay.getNormal())); e /= Math.max(distance * distance, 1); @@ -529,6 +531,8 @@ private static void sampleEmitterFace(Scene scene, Ray ray, Grid.EmitterPosition result.scaleAdd(e, emitterRay.color); } } + + state.returnRay(emitterRay); } /** @@ -540,20 +544,20 @@ private static void sampleEmitterFace(Scene scene, Ray ray, Grid.EmitterPosition * @param random RNG * @return The contribution of the emitter */ - private static Vector4 sampleEmitter(Scene scene, Ray ray, Grid.EmitterPosition pos, Random random) { + private static Vector4 sampleEmitter(Scene scene, Ray ray, Grid.EmitterPosition pos, WorkerState state) { Vector4 result = new Vector4(); result.set(0, 0, 0, 1); switch (scene.getEmitterSamplingStrategy()) { default: case ONE: - sampleEmitterFace(scene, ray, pos, random.nextInt(pos.block.faceCount()), result, 1, random); + sampleEmitterFace(scene, ray, pos, state.random.nextInt(pos.block.faceCount()), result, 1, state); break; case ONE_BLOCK: case ALL: double scaler = 1.0 / pos.block.faceCount(); for (int i = 0; i < pos.block.faceCount(); i++) { - sampleEmitterFace(scene, ray, pos, i, result, scaler, random); + sampleEmitterFace(scene, ray, pos, i, result, scaler, state); } break; } @@ -573,7 +577,7 @@ public static void getDirectLightAttenuation(Scene scene, Ray ray, WorkerState s attenuation.w = 1; while (attenuation.w > 0) { ray.o.scaleAdd(Ray.OFFSET, ray.d); - if (!PreviewRayTracer.nextIntersection(scene, ray)) { + if (!PreviewRayTracer.nextIntersection(scene, ray, state)) { break; } double mult = 1 - ray.color.w; diff --git a/chunky/src/java/se/llbit/chunky/renderer/scene/PreethamSky.java b/chunky/src/java/se/llbit/chunky/renderer/scene/PreethamSky.java index 5f71ded379..9eb92145b3 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/PreethamSky.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/PreethamSky.java @@ -125,12 +125,12 @@ public String getDescription() { } @Override - public Vector3 calcIncidentLight(Ray ray) { - double cosTheta = ray.d.y; + public Vector3 calcIncidentLight(Vector3 d) { + double cosTheta = d.y; cosTheta += horizonOffset; if (cosTheta < 0) cosTheta = 0; - double cosGamma = ray.d.dot(sw); + double cosGamma = d.dot(sw); double gamma = FastMath.acos(cosGamma); double cos2Gamma = cosGamma * cosGamma; double x = zenith_x * perezF(cosTheta, gamma, cos2Gamma, A.x, B.x, C.x, D.x, E.x) * f0_x; diff --git a/chunky/src/java/se/llbit/chunky/renderer/scene/PreviewRayTracer.java b/chunky/src/java/se/llbit/chunky/renderer/scene/PreviewRayTracer.java index c2446e9c23..a14c88d14d 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/PreviewRayTracer.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/PreviewRayTracer.java @@ -41,7 +41,7 @@ public class PreviewRayTracer implements RayTracer { ray.setCurrentMaterial(Air.INSTANCE); } while (true) { - if (!nextIntersection(scene, ray)) { + if (!nextIntersection(scene, ray, state)) { if (mapIntersection(scene, ray)) { break; } @@ -68,7 +68,7 @@ public static double skyOcclusion(Scene scene, WorkerState state) { Ray ray = state.ray; double occlusion = 1.0; while (true) { - if (!nextIntersection(scene, ray)) { + if (!nextIntersection(scene, ray, state)) { break; } else { occlusion *= (1 - ray.color.w); @@ -85,7 +85,7 @@ public static double skyOcclusion(Scene scene, WorkerState state) { * Find next ray intersection. * @return Next intersection */ - public static boolean nextIntersection(Scene scene, Ray ray) { + public static boolean nextIntersection(Scene scene, Ray ray, WorkerState state) { ray.setPrevMaterial(ray.getCurrentMaterial(), ray.getCurrentData()); ray.t = Double.POSITIVE_INFINITY; boolean hit = false; @@ -95,7 +95,7 @@ public static boolean nextIntersection(Scene scene, Ray ray) { if (scene.isWaterPlaneEnabled()) { hit = waterPlaneIntersection(scene, ray) || hit; } - if (scene.intersect(ray)) { + if (scene.intersect(ray, state)) { // Octree tracer handles updating distance. return true; } 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 fd1c4536d7..b01a7f8da8 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java @@ -678,7 +678,7 @@ public void rayTrace(RayTracer rayTracer, WorkerState state) { * @param ray ray to test against scene * @return true if an intersection was found */ - public boolean intersect(Ray ray) { + public boolean intersect(Ray ray, WorkerState state) { boolean hit = false; if (Double.isNaN(ray.d.x) || Double.isNaN(ray.d.y) || Double.isNaN(ray.d.z) || @@ -696,7 +696,7 @@ public boolean intersect(Ray ray) { if (entities.intersect(ray)) { hit = true; } - if (worldIntersection(ray)) { + if (worldIntersection(ray, state)) { hit = true; } if (hit) { @@ -714,11 +714,11 @@ public boolean intersect(Ray ray) { * @param ray the ray * @return {@code true} if the ray intersects a voxel */ - private boolean worldIntersection(Ray ray) { - Ray start = new Ray(ray); + private boolean worldIntersection(Ray ray, WorkerState state) { + Ray start = state.newRay(ray); start.setCurrentMaterial(ray.getPrevMaterial(), ray.getPrevData()); boolean hit = false; - Ray r = new Ray(start); + Ray r = state.newRay(start); r.setCurrentMaterial(start.getPrevMaterial(), start.getPrevData()); if (worldOctree.enterBlock(this, r, palette) && r.distance < ray.t) { ray.t = r.distance; @@ -729,7 +729,7 @@ private boolean worldIntersection(Ray ray) { hit = true; } if (start.getCurrentMaterial().isWater()) { - r = new Ray(start); + r.set(start); r.setCurrentMaterial(start.getPrevMaterial(), start.getPrevData()); if(waterOctree.exitWater(this, r, palette) && r.distance < ray.t - Ray.EPSILON) { ray.t = r.distance; @@ -742,7 +742,7 @@ private boolean worldIntersection(Ray ray) { ray.setPrevMaterial(Water.INSTANCE, 1 << Water.FULL_BLOCK); } } else { - r = new Ray(start); + r.set(start); r.setCurrentMaterial(start.getPrevMaterial(), start.getPrevData()); if (waterOctree.enterBlock(this, r, palette) && r.distance < ray.t) { ray.t = r.distance; @@ -753,6 +753,8 @@ private boolean worldIntersection(Ray ray) { hit = true; } } + state.returnRay(start); + state.returnRay(r); return hit; } @@ -1669,7 +1671,7 @@ public boolean traceTarget(Ray ray) { ray.o.x -= origin.x; ray.o.y -= origin.y; ray.o.z -= origin.z; - while (PreviewRayTracer.nextIntersection(this, ray)) { + while (PreviewRayTracer.nextIntersection(this, ray, state)) { if (ray.getCurrentMaterial() != Air.INSTANCE) { return true; } diff --git a/chunky/src/java/se/llbit/chunky/renderer/scene/SimulatedSky.java b/chunky/src/java/se/llbit/chunky/renderer/scene/SimulatedSky.java index 6469d20d77..74f518b7ca 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/SimulatedSky.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/SimulatedSky.java @@ -31,7 +31,7 @@ public interface SimulatedSky { /** * Calculate the sky color for a given ray. */ - Vector3 calcIncidentLight(Ray ray); + Vector3 calcIncidentLight(Vector3 d); /** * Get the friendly name. diff --git a/chunky/src/java/se/llbit/chunky/renderer/scene/Sky.java b/chunky/src/java/se/llbit/chunky/renderer/scene/Sky.java index fce0a6abda..ab7c74e68a 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/Sky.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/Sky.java @@ -288,8 +288,7 @@ public void getSkyDiffuseColorInner(Ray ray) { break; } case SIMULATED: { - Vector3 color = skyCache.calcIncidentLight(ray); - ray.color.set(color.x, color.y, color.z, 1); + skyCache.calcIncidentLight(ray); break; } case SKYMAP_EQUIRECTANGULAR: { diff --git a/chunky/src/java/se/llbit/chunky/renderer/scene/SkyCache.java b/chunky/src/java/se/llbit/chunky/renderer/scene/SkyCache.java index 4402aebfec..b901cc0970 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/SkyCache.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/SkyCache.java @@ -20,13 +20,11 @@ import java.util.concurrent.ExecutionException; import java.util.stream.IntStream; + import org.apache.commons.math3.util.FastMath; import se.llbit.chunky.main.Chunky; import se.llbit.log.Log; -import se.llbit.math.ColorUtil; -import se.llbit.math.QuickMath; -import se.llbit.math.Ray; -import se.llbit.math.Vector3; +import se.llbit.math.*; /** * A sky cache. Precalculates sky colors and them uses cached values with bilinear interpolation. @@ -128,15 +126,14 @@ public void setSimulatedSkyMode(SimulatedSky skyMode) { * @param ray Ray to calculate the incident light for * @return Incident light color (RGB) */ - public Vector3 calcIncidentLight(Ray ray) { + public void calcIncidentLight(Ray ray) { double theta = FastMath.atan2(ray.d.z, ray.d.x); theta /= PI * 2; theta = ((theta % 1) + 1) % 1; double phi = (FastMath.asin(QuickMath.clamp(ray.d.y, -1, 1)) + PI / 2) / PI; - Vector3 color = getColorInterpolated(theta, phi); - ColorUtil.RGBfromHSL(color, color.x, color.y, color.z); - return color; + getColorInterpolated(theta, phi, ray.color); + ColorUtil.RGBfromHSL(ray.color, ray.color.x, ray.color.y, ray.color.z); } // Linear interpolation between 2 points in 1 dimension @@ -147,35 +144,38 @@ private static double interp1D(double x, double x0, double x1, double y0, double /** * Calculate the bilinearly interpolated value from the cache. */ - private Vector3 getColorInterpolated(double normX, double normY) { + private void getColorInterpolated(double normX, double normY, Vector4 out) { double x = normX * skyResolution; double y = normY * skyResolution; int floorX = (int) QuickMath.clamp(x, 0, skyResolution - 1); int floorY = (int) QuickMath.clamp(y, 0, skyResolution - 1); - double[] color = new double[3]; for (int i = 0; i < 3; i++) { double y0 = interp1D(x, floorX, floorX + 1, skyTexture[floorX][floorY][i], - skyTexture[floorX + 1][floorY][i]); + skyTexture[floorX + 1][floorY][i]); double y1 = interp1D(x, floorX, floorX + 1, skyTexture[floorX][floorY + 1][i], - skyTexture[floorX + 1][floorY + 1][i]); - color[i] = interp1D(y, floorY, floorY + 1, y0, y1); + skyTexture[floorX + 1][floorY + 1][i]); + double c = interp1D(y, floorY, floorY + 1, y0, y1); + if (i == 0) { + out.x = c; + } else if (i == 1) { + out.y = c; + } else if (i == 2) { + out.z = c; + } + out.w = 1; } - return new Vector3(color[0], color[1], color[2]); } /** * Calculate the sky color for a pixel on the cache. */ private Vector3 getSkyColorAt(int x, int y) { - Ray ray = new Ray(); - double theta = ((double) x / skyResolution) * 2 * PI; double phi = ((double) y / skyResolution) * PI - PI / 2; double r = FastMath.cos(phi); - ray.d.set(FastMath.cos(theta) * r, FastMath.sin(phi), FastMath.sin(theta) * r); - Vector3 color = simSky.calcIncidentLight(ray); + Vector3 color = simSky.calcIncidentLight(new Vector3(FastMath.cos(theta) * r, FastMath.sin(phi), FastMath.sin(theta) * r)); ColorUtil.RGBtoHSL(color, color.x, color.y, color.z); return color; } diff --git a/chunky/src/java/se/llbit/math/ColorUtil.java b/chunky/src/java/se/llbit/math/ColorUtil.java index 9d1d01ccd3..e0ec8d456b 100644 --- a/chunky/src/java/se/llbit/math/ColorUtil.java +++ b/chunky/src/java/se/llbit/math/ColorUtil.java @@ -291,22 +291,22 @@ public static Vector3 RGBtoHSL(double r, double g, double b) { return color; } - public static void RGBfromHSL(Vector3 rgb, double hue, double saturation, double lightness) { + public static void RGBfromHSL(Vector4 rgb, double hue, double saturation, double lightness) { double c = Math.min(1, (1 - Math.abs(2 * lightness - 1)) * saturation); double h = hue * 6; double x = c * (1 - Math.abs(h % 2 - 1)); if (h < 1) { - rgb.set(c, x, 0); + rgb.set(c, x, 0, 1); } else if (h < 2) { - rgb.set(x, c, 0); + rgb.set(x, c, 0, 1); } else if (h < 3) { - rgb.set(0, c, x); + rgb.set(0, c, x, 1); } else if (h < 4) { - rgb.set(0, x, c); + rgb.set(0, x, c, 1); } else if (h < 5) { - rgb.set(x, 0, c); + rgb.set(x, 0, c, 1); } else { - rgb.set(c, 0, x); + rgb.set(c, 0, x, 1); } double m = Math.max(0, lightness - 0.5 * c); rgb.x += m;