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 @@ +