Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a ray pool and misc other optimizations (requires benchmarking) #1599

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions chunky/src/java/se/llbit/chunky/renderer/WorkerState.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -28,4 +30,33 @@ public class WorkerState {
public Ray ray;
public Vector4 attenuation = new Vector4();
public Random random;
private List<Ray> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
28 changes: 16 additions & 12 deletions chunky/src/java/se/llbit/chunky/renderer/scene/PathTracer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand All @@ -529,6 +531,8 @@ private static void sampleEmitterFace(Scene scene, Ray ray, Grid.EmitterPosition
result.scaleAdd(e, emitterRay.color);
}
}

state.returnRay(emitterRay);
}

/**
Expand All @@ -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;
}
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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;
}
Expand Down
18 changes: 10 additions & 8 deletions chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,7 @@ public void rayTrace(RayTracer rayTracer, WorkerState state) {
* @param ray ray to test against scene
* @return <code>true</code> 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) ||
Expand All @@ -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) {
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -753,6 +753,8 @@ private boolean worldIntersection(Ray ray) {
hit = true;
}
}
state.returnRay(start);
state.returnRay(r);
return hit;
}

Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 1 addition & 2 deletions chunky/src/java/se/llbit/chunky/renderer/scene/Sky.java
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
36 changes: 18 additions & 18 deletions chunky/src/java/se/llbit/chunky/renderer/scene/SkyCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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;
}
Expand Down
Loading
Loading