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 b876e46f0..166f9e39f 100644
--- a/chunky/src/java/se/llbit/chunky/renderer/scene/PathTracer.java
+++ b/chunky/src/java/se/llbit/chunky/renderer/scene/PathTracer.java
@@ -127,6 +127,19 @@ public static boolean pathTrace(Scene scene, Ray ray, WorkerState state,
break;
}
ray.depth += 1;
+
+ // Russian Roulette
+ if (!firstReflection && ray.depth >= scene.minRayDepth) {
+ double max = FastMath.max(ray.color.x, FastMath.max(ray.color.y, ray.color.z));
+ double p = QuickMath.clamp(max, 0.05, 1.0);
+ if (random.nextDouble() > p) {
+ ray.color.set(0, 0, 0, 0);
+ break;
+ } else {
+ ray.color.scale(1 / p);
+ }
+ }
+
Vector4 cumulativeColor = new Vector4(0, 0, 0, 0);
Ray next = new Ray();
float pMetal = currentMat.metalness;
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 813489894..31e77fc3b 100644
--- a/chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java
+++ b/chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java
@@ -191,6 +191,10 @@ public class Scene implements JsonSerializable {
* Recursive ray depth limit (not including Russian Roulette).
*/
protected int rayDepth = PersistentSettings.getRayDepthDefault();
+ /**
+ * Minimum number of ray bounces before Russian Roulette starts.
+ */
+ protected int minRayDepth = PersistentSettings.getMinRayDepthDefault();
protected String worldPath = "";
protected int worldDimension = 0;
protected RenderMode mode = RenderMode.PREVIEW;
@@ -1685,6 +1689,18 @@ public synchronized void setRayDepth(int value) {
}
}
+ /**
+ * Set the minimum recursive ray depth before Russian Roulette starts
+ */
+ public synchronized void setMinRayDepth(int value) {
+ value = Math.max(1, value);
+ if (minRayDepth != value) {
+ minRayDepth = value;
+ PersistentSettings.setMinRayDepth(minRayDepth);
+ refresh();
+ }
+ }
+
/**
* @return Recursive ray depth limit
*/
@@ -1692,6 +1708,13 @@ public int getRayDepth() {
return rayDepth;
}
+ /**
+ * @return Minimum ray depth before Russion Roulette is applied
+ */
+ public int getMinRayDepth() {
+ return minRayDepth;
+ }
+
/**
* Clear the scene refresh flag
*/
@@ -1939,6 +1962,7 @@ public synchronized void copyTransients(Scene other) {
sppTarget = other.sppTarget;
branchCount = other.branchCount;
rayDepth = other.rayDepth;
+ minRayDepth = other.minRayDepth;
mode = other.mode;
pictureExportFormat = other.pictureExportFormat;
cameraPresets = other.cameraPresets;
@@ -2624,6 +2648,7 @@ public void setUseCustomWaterColor(boolean value) {
json.add("sppTarget", sppTarget);
json.add("branchCount", branchCount);
json.add("rayDepth", rayDepth);
+ json.add("minRayDepth", minRayDepth);
json.add("pathTrace", mode != RenderMode.PREVIEW);
json.add("dumpFrequency", dumpFrequency);
json.add("saveSnapshots", saveSnapshots);
@@ -2880,6 +2905,7 @@ public synchronized void importFromJson(JsonObject json) {
sppTarget = json.get("sppTarget").intValue(sppTarget);
branchCount = json.get("branchCount").intValue(branchCount);
rayDepth = json.get("rayDepth").intValue(rayDepth);
+ minRayDepth = json.get("minRayDepth").intValue(minRayDepth);
if (!json.get("pathTrace").isUnknown()) {
boolean pathTrace = json.get("pathTrace").boolValue(false);
if (pathTrace) {
diff --git a/chunky/src/java/se/llbit/chunky/ui/render/tabs/AdvancedTab.java b/chunky/src/java/se/llbit/chunky/ui/render/tabs/AdvancedTab.java
index 150da3b4f..84da99942 100644
--- a/chunky/src/java/se/llbit/chunky/ui/render/tabs/AdvancedTab.java
+++ b/chunky/src/java/se/llbit/chunky/ui/render/tabs/AdvancedTab.java
@@ -64,6 +64,7 @@ public class AdvancedTab extends ScrollPane implements RenderControlsTab, Initia
@FXML private IntegerAdjuster renderThreads;
@FXML private IntegerAdjuster cpuLoad;
@FXML private IntegerAdjuster rayDepth;
+ @FXML private IntegerAdjuster minRayDepth;
@FXML private IntegerAdjuster branchCount;
@FXML private Button mergeRenderDump;
@FXML private CheckBox shutdown;
@@ -104,10 +105,15 @@ public void initialize(URL location, ResourceBundle resources) {
controller.getRenderManager().setCPULoad(value);
});
rayDepth.setName("Ray depth");
- rayDepth.setTooltip("Sets the minimum recursive ray depth.");
+ rayDepth.setTooltip("Sets the maximum recursive ray depth.");
rayDepth.setRange(1, 25);
rayDepth.clampMin();
rayDepth.onValueChange(value -> scene.setRayDepth(value));
+ minRayDepth.setName("Min ray depth");
+ minRayDepth.setTooltip("Sets the minimum recursive ray depth before Russian Roulette is applied.");
+ minRayDepth.setRange(1, 25);
+ minRayDepth.clampMin();
+ minRayDepth.onValueChange(value -> scene.setMinRayDepth(value));
branchCount.setName("Branch count");
branchCount.setTooltip("Sets the number of rays cast after the first intersection, effectively reusing the first ray that many times." +
@@ -339,6 +345,7 @@ public void update(Scene scene) {
renderThreads.set(PersistentSettings.getNumThreads());
cpuLoad.set(PersistentSettings.getCPULoad());
rayDepth.set(scene.getRayDepth());
+ minRayDepth.set(scene.getMinRayDepth());
branchCount.set(scene.getBranchCount());
octreeImplementation.getSelectionModel().select(scene.getOctreeImplementation());
bvhMethod.getSelectionModel().select(scene.getBvhImplementation());
diff --git a/chunky/src/res/se/llbit/chunky/ui/render/tabs/AdvancedTab.fxml b/chunky/src/res/se/llbit/chunky/ui/render/tabs/AdvancedTab.fxml
index 6c9b96023..25ad2369b 100644
--- a/chunky/src/res/se/llbit/chunky/ui/render/tabs/AdvancedTab.fxml
+++ b/chunky/src/res/se/llbit/chunky/ui/render/tabs/AdvancedTab.fxml
@@ -18,6 +18,7 @@
+
diff --git a/lib/src/se/llbit/chunky/PersistentSettings.java b/lib/src/se/llbit/chunky/PersistentSettings.java
index 38dfca9c5..02a6278ac 100644
--- a/lib/src/se/llbit/chunky/PersistentSettings.java
+++ b/lib/src/se/llbit/chunky/PersistentSettings.java
@@ -63,6 +63,7 @@ public final class PersistentSettings {
public static final double DEFAULT_FOG_BLUE = 1;
public static final int DEFAULT_RAY_DEPTH = 5;
+ public static final int DEFAULT_MIN_RAY_DEPTH = 3;
public static final int DEFAULT_BRANCH_COUNT = 10;
public static final int DEFAULT_SPP_TARGET = 1000;
@@ -272,6 +273,11 @@ public static void setRayDepth(int rayDepth) {
save();
}
+ public static void setMinRayDepth(int rayDepth) {
+ settings.setInt("minRayDepth", rayDepth);
+ save();
+ }
+
/**
* @return the default configured ray depth
*/
@@ -279,6 +285,10 @@ public static int getRayDepthDefault() {
return settings.getInt("rayDepth", DEFAULT_RAY_DEPTH);
}
+ public static int getMinRayDepthDefault() {
+ return settings.getInt("minRayDepth", DEFAULT_MIN_RAY_DEPTH);
+ }
+
public static int get3DCanvasHeight() {
return settings.getInt("3dcanvas.height", DEFAULT_3D_CANVAS_HEIGHT);
}