Skip to content

Commit

Permalink
Improve how translucent materials handle color (#1513)
Browse files Browse the repository at this point in the history
* fix translucent coloring

* Add Redox's code for emitter lighting (fix #608)

* Create a method (translucentRayColor) for code that was previously duplicated

* Bring more of the duplicated code into translucentRayColor

* Remove Redox's fix for #608, additional testing needed

* Cap each color's transmissivity at 1
Example: RGBA = 0.8, 0.35, 0.05, 0.2
Uncapped transmissivity RGB = 1.6, 0.7, 0.1
Capped transmissivity RGB = 1, 0.775, 0.625

* Extract transmissivity cap from formula into a constant (for future configurability)
Also, select the highest transmissivity value properly this time

* improve translucent coloring

* Create a method (translucentRayColor) for code that was previously duplicated

* Bring more of the duplicated code into translucentRayColor

* Cap each color's transmissivity at 1
Example: RGBA = 0.8, 0.35, 0.05, 0.2
Uncapped transmissivity RGB = 1.6, 0.7, 0.1
Capped transmissivity RGB = 1, 0.775, 0.625

* Extract transmissivity cap from formula into a constant (for future configurability)
Also, select the highest transmissivity value properly this time

* block half-ish of the light on the diffuse pass and another half-ish on the translucent pass (actually 1 - sqrt(1-x))

* Add a slider for `Transmissivity cap` value

- Added a slider for the `Transmissivity cap` value with range [1, 3].
- Added a `group-label` CSS class for group labels.

* Move `Transmissivity cap` to the `Advanced` tab

* Ensure that total energy is never amplified, even when transmissivityCap > 1.

* you just got vectored

* Revert changes to MaterialsTab.java

`Transmissivity cap` had been moved to the `Advanced` tab, but other changes to the `Materials` tab were not reverted then.

---------

Co-authored-by: Peregrine05 <[email protected]>
  • Loading branch information
JustinTimeCuber and Peregrine05 authored Aug 10, 2023
1 parent 18ba283 commit fdecd82
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 24 deletions.
86 changes: 63 additions & 23 deletions chunky/src/java/se/llbit/chunky/renderer/scene/PathTracer.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public static boolean pathTrace(Scene scene, Ray ray, WorkerState state, int add

float pSpecular = currentMat.specular;

double pDiffuse = ray.color.w;
double pDiffuse = 1 - Math.sqrt(1 - ray.color.w);

float n1 = prevMat.ior;
float n2 = currentMat.ior;
Expand Down Expand Up @@ -348,17 +348,8 @@ public static boolean pathTrace(Scene scene, Ray ray, WorkerState state, int add
}

if (pathTrace(scene, refracted, state, 1, false)) {
ray.color.x = ray.color.x * pDiffuse + (1 - pDiffuse);
ray.color.y = ray.color.y * pDiffuse + (1 - pDiffuse);
ray.color.z = ray.color.z * pDiffuse + (1 - pDiffuse);

ray.emittance.x = ray.color.x * refracted.emittance.x;
ray.emittance.y = ray.color.y * refracted.emittance.y;
ray.emittance.z = ray.color.z * refracted.emittance.z;

ray.color.x *= refracted.color.x;
ray.color.y *= refracted.color.y;
ray.color.z *= refracted.color.z;
// Calculate the color and emittance of the refracted ray
translucentRayColor(scene, ray, refracted, pDiffuse);
hit = true;
}
}
Expand All @@ -372,17 +363,8 @@ public static boolean pathTrace(Scene scene, Ray ray, WorkerState state, int add
transmitted.o.scaleAdd(Ray.OFFSET, transmitted.d);

if (pathTrace(scene, transmitted, state, 1, false)) {
ray.color.x = ray.color.x * pDiffuse + (1 - pDiffuse);
ray.color.y = ray.color.y * pDiffuse + (1 - pDiffuse);
ray.color.z = ray.color.z * pDiffuse + (1 - pDiffuse);

ray.emittance.x = ray.color.x * transmitted.emittance.x;
ray.emittance.y = ray.color.y * transmitted.emittance.y;
ray.emittance.z = ray.color.z * transmitted.emittance.z;

ray.color.x *= transmitted.color.x;
ray.color.y *= transmitted.color.y;
ray.color.z *= transmitted.color.z;
// Calculate the color and emittance of the transmitted ray
translucentRayColor(scene, ray, transmitted, pDiffuse);
hit = true;
}
}
Expand Down Expand Up @@ -438,6 +420,64 @@ public static boolean pathTrace(Scene scene, Ray ray, WorkerState state, int add
return hit;
}

private static void translucentRayColor(Scene scene, Ray ray, Ray next, double opacity) {
// Color-based transmission value
double colorTrans = (ray.color.x + ray.color.y + ray.color.z)/3;
// Total amount of light we want to transmit (overall transparency of texture)
double shouldTrans = 1 - opacity;
// Amount of each color to transmit - default to overall transparency if RGB values add to 0 (e.g. regular glass)
Vector3 rgbTrans = new Vector3(shouldTrans, shouldTrans, shouldTrans);
if(colorTrans > 0) {
// Amount to transmit of each color is scaled so the total transmitted amount matches the texture's transparency
rgbTrans.set(ray.color.toVec3());
rgbTrans.scale(shouldTrans / colorTrans);
}
double transmissivityCap = scene.transmissivityCap;
// Determine the color with the highest transmissivity
double maxTrans = Math.max(rgbTrans.x, Math.max(rgbTrans.y, rgbTrans.z));
if(maxTrans > transmissivityCap) {
if (maxTrans == rgbTrans.x) {
// Give excess transmission from red to green and blue
double gTransNew = reassignTransmissivity(rgbTrans.x, rgbTrans.y, rgbTrans.z, shouldTrans, transmissivityCap);
rgbTrans.z = reassignTransmissivity(rgbTrans.x, rgbTrans.z, rgbTrans.y, shouldTrans, transmissivityCap);
rgbTrans.y = gTransNew;
rgbTrans.x = transmissivityCap;
} else if (maxTrans == rgbTrans.y) {
// Give excess transmission from green to red and blue
double rTransNew = reassignTransmissivity(rgbTrans.y, rgbTrans.x, rgbTrans.z, shouldTrans, transmissivityCap);
rgbTrans.z = reassignTransmissivity(rgbTrans.y, rgbTrans.z, rgbTrans.x, shouldTrans, transmissivityCap);
rgbTrans.x = rTransNew;
rgbTrans.y = transmissivityCap;
} else if (maxTrans == rgbTrans.z) {
// Give excess transmission from blue to green and red
double gTransNew = reassignTransmissivity(rgbTrans.z, rgbTrans.y, rgbTrans.x, shouldTrans, transmissivityCap);
rgbTrans.x = reassignTransmissivity(rgbTrans.z, rgbTrans.x, rgbTrans.y, shouldTrans, transmissivityCap);
rgbTrans.y = gTransNew;
rgbTrans.z = transmissivityCap;
}
}
// Don't need to check for energy gain if transmissivity cap is 1
if(transmissivityCap > 1) {
double currentEnergy = rgbTrans.x * next.color.x + rgbTrans.y * next.color.y + rgbTrans.z * next.color.z;
double nextEnergy = next.color.x + next.color.y + next.color.z;
double energyRatio = nextEnergy / currentEnergy;
// Normalize if there is net energy gain across all channels (more likely for higher transmissivityCap combined with high-saturation light source)
if(energyRatio < 1) {
rgbTrans.scale(energyRatio);
}
}
// Scale color based on next ray
ray.color.multiplyEntrywise(new Vector4(rgbTrans, 1), next.color);
// Use emittance from next ray
ray.emittance.multiplyEntrywise(rgbTrans, next.emittance);
}

private static double reassignTransmissivity(double from, double to, double other, double trans, double cap) {
// Formula here derived algebraically from this system:
// (cap - to_new)/(cap - other_new) = (from - to)/(from - other), (cap + to_new + other_new)/3 = trans
return (cap*(other - 2*to + from) + (3*trans)*(to - from))/(other + to - 2*from);
}

private static void addSkyFog(Scene scene, Ray ray, WorkerState state, Vector3 ox, Vector3 od) {
if (scene.fog.mode == FogMode.UNIFORM) {
scene.fog.addSkyFog(ray, null);
Expand Down
28 changes: 28 additions & 0 deletions chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,21 @@ public class Scene implements JsonSerializable, Refreshable {
*/
public static final double MAX_EMITTER_INTENSITY = 1000;

/**
* Default transmissivity cap.
*/
public static final double DEFAULT_TRANSMISSIVITY_CAP = 1;

/**
* Minimum transmissivity cap.
*/
public static final double MIN_TRANSMISSIVITY_CAP = 1;

/**
* Maximum transmissivity cap.
*/
public static final double MAX_TRANSMISSIVITY_CAP = 3;

/**
* Default exposure.
*/
Expand Down Expand Up @@ -192,6 +207,7 @@ public class Scene implements JsonSerializable, Refreshable {
protected boolean emittersEnabled = DEFAULT_EMITTERS_ENABLED;
protected double emitterIntensity = DEFAULT_EMITTER_INTENSITY;
protected EmitterSamplingStrategy emitterSamplingStrategy = EmitterSamplingStrategy.NONE;
protected double transmissivityCap = DEFAULT_TRANSMISSIVITY_CAP;

protected SunSamplingStrategy sunSamplingStrategy = SunSamplingStrategy.FAST;

Expand Down Expand Up @@ -430,6 +446,7 @@ public synchronized void copyState(Scene other, boolean copyChunks) {
emitterIntensity = other.emitterIntensity;
emitterSamplingStrategy = other.emitterSamplingStrategy;
preventNormalEmitterWithSampling = other.preventNormalEmitterWithSampling;
transmissivityCap = other.transmissivityCap;
transparentSky = other.transparentSky;
yClipMin = other.yClipMin;
yClipMax = other.yClipMax;
Expand Down Expand Up @@ -2595,6 +2612,7 @@ public void setUseCustomWaterColor(boolean value) {
json.add("saveSnapshots", saveSnapshots);
json.add("emittersEnabled", emittersEnabled);
json.add("emitterIntensity", emitterIntensity);
json.add("transmissivityCap", transmissivityCap);
json.add("sunSamplingStrategy", sunSamplingStrategy.getId());
json.add("stillWater", stillWater);
json.add("waterOpacity", waterOpacity);
Expand Down Expand Up @@ -2852,6 +2870,7 @@ public synchronized void importFromJson(JsonObject json) {
saveSnapshots = json.get("saveSnapshots").boolValue(saveSnapshots);
emittersEnabled = json.get("emittersEnabled").boolValue(emittersEnabled);
emitterIntensity = json.get("emitterIntensity").doubleValue(emitterIntensity);
transmissivityCap = json.get("transmissivityCap").doubleValue(transmissivityCap);

if (json.get("sunSamplingStrategy").isUnknown()) {
boolean sunSampling = json.get("sunEnabled").boolValue(false);
Expand Down Expand Up @@ -3359,4 +3378,13 @@ public boolean getHideUnknownBlocks() {
public void setHideUnknownBlocks(boolean hideUnknownBlocks) {
this.hideUnknownBlocks = hideUnknownBlocks;
}

public double getTransmissivityCap() {
return transmissivityCap;
}

public void setTransmissivityCap(double value) {
transmissivityCap = value;
refresh();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public class AdvancedTab extends ScrollPane implements RenderControlsTab, Initia
@FXML private Button mergeRenderDump;
@FXML private CheckBox shutdown;
@FXML private CheckBox fastFog;
@FXML private DoubleAdjuster transmissivityCap;
@FXML private IntegerAdjuster cacheResolution;
@FXML private DoubleAdjuster animationTime;
@FXML private ChoiceBox<PictureExportFormat> outputMode;
Expand Down Expand Up @@ -141,6 +142,11 @@ public PictureExportFormat fromString(String string) {
fastFog.setTooltip(new Tooltip("Enable faster fog rendering algorithm."));
fastFog.selectedProperty()
.addListener((observable, oldValue, newValue) -> scene.setFastFog(newValue));
transmissivityCap.setName("Transmissivity cap");
transmissivityCap.setRange(Scene.MIN_TRANSMISSIVITY_CAP, Scene.MAX_TRANSMISSIVITY_CAP);
transmissivityCap.clampBoth();
transmissivityCap.setTooltip("Maximum amplification of one color channel as a ray passes through a translucent block (stained glass, ice, etc.).\nA value of 1 prevents amplification entirely; higher values result in more vibrant colors.");
transmissivityCap.onValueChange(value -> scene.setTransmissivityCap(value));
cacheResolution.setName("Sky cache resolution");
cacheResolution.setTooltip("Resolution of the sky cache. Lower values will use less memory and improve performance but can cause sky artifacts.");
cacheResolution.setRange(1, 4096);
Expand Down Expand Up @@ -303,6 +309,7 @@ public boolean shutdownAfterCompletedRender() {
public void update(Scene scene) {
outputMode.getSelectionModel().select(scene.getOutputMode());
fastFog.setSelected(scene.fog.fastFog());
transmissivityCap.set(scene.getTransmissivityCap());
renderThreads.set(PersistentSettings.getNumThreads());
cpuLoad.set(PersistentSettings.getCPULoad());
rayDepth.set(scene.getRayDepth());
Expand Down
9 changes: 9 additions & 0 deletions chunky/src/java/se/llbit/math/Vector3.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ public final void cross(Vector3 a, Vector3 b) {
z = a.x * b.y - a.y * b.x;
}

/**
* Set this vector equal to the entrywise product of a and b.
*/
public final void multiplyEntrywise(Vector3 a, Vector3 b) {
x = a.x * b.x;
y = a.y * b.y;
z = a.z * b.z;
}

/**
* Normalize this vector (scale the vector to unit length)
*/
Expand Down
19 changes: 19 additions & 0 deletions chunky/src/java/se/llbit/math/Vector4.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public Vector4(Vector4 v) {
this(v.x, v.y, v.z, v.w);
}

public Vector4(Vector3 v, double w) { this(v.x, v.y, v.z, w); }

public Vector4(double i, double j, double k, double l) {
x = i;
y = j;
Expand Down Expand Up @@ -96,6 +98,23 @@ public void scaleAdd(double s, Vector4 v) {
w += s * v.w;
}

/**
* Set this vector equal to the entrywise product of a and b.
*/
public final void multiplyEntrywise(Vector4 a, Vector4 b) {
x = a.x * b.x;
y = a.y * b.y;
z = a.z * b.z;
w = a.w * b.w;
}

/**
* Return a Vector3 by removing the 4th entry.
*/
public final Vector3 toVec3() {
return new Vector3(x, y, z);
}

@Override public String toString() {
return String.format("(%.2f, %.2f, %.2f, %.2f)", x, y, z, w);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<Separator prefWidth="200.0" />
<CheckBox fx:id="shutdown" mnemonicParsing="false" text="Shutdown computer when render completes" />
<CheckBox fx:id="fastFog" mnemonicParsing="false" text="Fast fog" />
<DoubleAdjuster fx:id="transmissivityCap" />
<IntegerAdjuster fx:id="cacheResolution" />
<DoubleAdjuster fx:id="animationTime" />
<HBox alignment="CENTER_LEFT" spacing="10.0">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<Separator />
<HBox alignment="CENTER_LEFT" spacing="16">
<VBox spacing="4">
<Label fx:id="canvasSizeLabel" text="Canvas size:" style="-fx-font-weight: bold;"/>
<Label fx:id="canvasSizeLabel" text="Canvas size:" styleClass="group-label"/>
<padding>
<Insets left="8"/>
</padding>
Expand Down
6 changes: 6 additions & 0 deletions chunky/src/res/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ Label {
-fx-text-fill: #faebd7;
}

Label.group-label {
-fx-text-fill: #faebd7;
-fx-font-weight: bold;
-fx-font-size: 12px;
}

Text {
-fx-fill: #faebd7;
}
Expand Down

0 comments on commit fdecd82

Please sign in to comment.