Skip to content

Commit

Permalink
added transmission roughness to specular refractions
Browse files Browse the repository at this point in the history
  • Loading branch information
ConsueTerra committed Nov 5, 2023
1 parent 990bdc5 commit 04efe43
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 122 deletions.
30 changes: 3 additions & 27 deletions chunky/src/java/se/llbit/chunky/renderer/scene/PathTracer.java
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,8 @@ private static boolean doDiffuseReflection(Ray ray, Ray next, Material currentMa
double directLightB = 0;

boolean frontLight = next.d.dot(ray.getNormal()) > 0;

if (frontLight || (currentMat.subSurfaceScattering
//check if normal faces the sun direction, if so do sampling
if (frontLight || (false
&& random.nextFloat() < Scene.fSubSurface)) {

if (!frontLight) {
Expand Down Expand Up @@ -359,31 +359,7 @@ private static boolean doRefraction(Ray ray, Ray next, Material currentMat, Mate
}
} else {
if (doRefraction) {

double t2 = FastMath.sqrt(radicand);
Vector3 n = ray.getNormal();
if (cosTheta > 0) {
next.d.x = n1n2 * ray.d.x + (n1n2 * cosTheta - t2) * n.x;
next.d.y = n1n2 * ray.d.y + (n1n2 * cosTheta - t2) * n.y;
next.d.z = n1n2 * ray.d.z + (n1n2 * cosTheta - t2) * n.z;
} else {
next.d.x = n1n2 * ray.d.x - (-n1n2 * cosTheta - t2) * n.x;
next.d.y = n1n2 * ray.d.y - (-n1n2 * cosTheta - t2) * n.y;
next.d.z = n1n2 * ray.d.z - (-n1n2 * cosTheta - t2) * n.z;
}

next.d.normalize();

// See Ray.specularReflection for information on why this is needed
// This is the same thing but for refraction instead of reflection
// so this time we want the signs of the dot product to be the same
if (QuickMath.signum(next.getGeometryNormal().dot(next.d)) != QuickMath.signum(next.getGeometryNormal().dot(ray.d))) {
double factor = QuickMath.signum(next.getGeometryNormal().dot(ray.d)) * -Ray.EPSILON - next.d.dot(next.getGeometryNormal());
next.d.scaleAdd(factor, next.getGeometryNormal());
next.d.normalize();
}

next.o.scaleAdd(Ray.OFFSET, next.d);
next.specularRefraction(ray, random, radicand, n1n2, cosTheta);
}

if (pathTrace(scene, next, state, false)) {
Expand Down
10 changes: 10 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 @@ -3256,6 +3256,16 @@ public void setPerceptualSmoothness(String materialName, float value) {
refresh(ResetReason.MATERIALS_CHANGED);
}

/**
* Modifies the transmission roughness property for the given material.
*/
public void setPerceptualTransmissionSmoothness(String materialName, float value) {
JsonObject material = materials.getOrDefault(materialName, new JsonObject()).object();
material.set("transmissionRoughness", Json.of(Math.pow(1 - value, 2)));
materials.put(materialName, material);
refresh(ResetReason.MATERIALS_CHANGED);
}

/**
* Modifies the metalness property for the given material.
*/
Expand Down
15 changes: 14 additions & 1 deletion chunky/src/java/se/llbit/chunky/ui/render/tabs/MaterialsTab.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ public class MaterialsTab extends HBox implements RenderControlsTab, Initializab
private final DoubleAdjuster specular = new DoubleAdjuster();
private final DoubleAdjuster ior = new DoubleAdjuster();
private final DoubleAdjuster perceptualSmoothness = new DoubleAdjuster();

private final DoubleAdjuster perceptualTransmissionSmoothness = new DoubleAdjuster();
private final DoubleAdjuster metalness = new DoubleAdjuster();
private final ListView<String> listView;

Expand All @@ -69,6 +71,9 @@ public MaterialsTab() {
perceptualSmoothness.setName("Smoothness");
perceptualSmoothness.setRange(0, 1);
perceptualSmoothness.setTooltip("Smoothness of the selected material.");
perceptualTransmissionSmoothness.setName("Transmission Smoothness");
perceptualTransmissionSmoothness.setRange(0, 1);
perceptualTransmissionSmoothness.setTooltip("Smoothness of refraction though material");
metalness.setName("Metalness");
metalness.setRange(0, 1);
metalness.setTooltip("Metalness (texture-tinted reflectivity) of the selected material.");
Expand All @@ -87,7 +92,7 @@ public MaterialsTab() {
settings.setSpacing(10);
settings.getChildren().addAll(
new Label("Material Properties"),
emittance, specular, perceptualSmoothness, ior, metalness,
emittance, specular, perceptualSmoothness, ior,perceptualTransmissionSmoothness, metalness,
new Label("(set to zero to disable)"));
setPadding(new Insets(10));
setSpacing(15);
Expand Down Expand Up @@ -116,19 +121,22 @@ private void updateSelectedMaterial(String materialName) {
double specAcc = 0;
double iorAcc = 0;
double perceptualSmoothnessAcc = 0;
double perceptualTransmissionSmoothnessAcc = 0;
double metalnessAcc = 0;
Collection<Block> blocks = MaterialStore.collections.get(materialName);
for (Block block : blocks) {
emAcc += block.emittance;
specAcc += block.specular;
iorAcc += block.ior;
perceptualSmoothnessAcc += block.getPerceptualSmoothness();
perceptualTransmissionSmoothnessAcc += block.getPerceptualTransmissionSmoothness();
metalnessAcc += block.metalness;
}
emittance.set(emAcc / blocks.size());
specular.set(specAcc / blocks.size());
ior.set(iorAcc / blocks.size());
perceptualSmoothness.set(perceptualSmoothnessAcc / blocks.size());
perceptualTransmissionSmoothness.set(perceptualTransmissionSmoothnessAcc / blocks.size());
metalness.set(metalnessAcc / blocks.size());
materialExists = true;
} else if (ExtraMaterials.idMap.containsKey(materialName)) {
Expand All @@ -138,6 +146,7 @@ private void updateSelectedMaterial(String materialName) {
specular.set(material.specular);
ior.set(material.ior);
perceptualSmoothness.set(material.getPerceptualSmoothness());
perceptualTransmissionSmoothness.set(material.getPerceptualTransmissionSmoothness());
metalness.set(material.metalness);
materialExists = true;
}
Expand All @@ -148,6 +157,7 @@ private void updateSelectedMaterial(String materialName) {
specular.set(block.specular);
ior.set(block.ior);
perceptualSmoothness.set(block.getPerceptualSmoothness());
perceptualTransmissionSmoothness.set(block.getPerceptualTransmissionSmoothness());
metalness.set(block.metalness);
materialExists = true;
}
Expand All @@ -156,12 +166,15 @@ private void updateSelectedMaterial(String materialName) {
specular.onValueChange(value -> scene.setSpecular(materialName, value.floatValue()));
ior.onValueChange(value -> scene.setIor(materialName, value.floatValue()));
perceptualSmoothness.onValueChange(value -> scene.setPerceptualSmoothness(materialName, value.floatValue()));
perceptualTransmissionSmoothness.onValueChange(value -> scene.setPerceptualTransmissionSmoothness(materialName,
value.floatValue()));
metalness.onValueChange(value -> scene.setMetalness(materialName, value.floatValue()));
} else {
emittance.onValueChange(value -> {});
specular.onValueChange(value -> {});
ior.onValueChange(value -> {});
perceptualSmoothness.onValueChange(value -> {});
perceptualTransmissionSmoothness.onValueChange(value -> {});
metalness.onValueChange(value -> {});
}
}
Expand Down
24 changes: 21 additions & 3 deletions chunky/src/java/se/llbit/chunky/world/Material.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ public abstract class Material {
*/
public float roughness = 0f;

/**
* The (linear) roughness controlling how blurry a refraction/transmission though a block is. A value of 0 makes the
* effect perfectly specular, a value of 1 makes it diffuse.
*/
public float transmissionRoughness = 0f;

/**
* The metalness value controls how metal-y a block appears. In reality this is a boolean value
* but in practice usually a float is used in PBR to allow adding dirt or scratches on metals
Expand All @@ -76,9 +82,11 @@ public abstract class Material {
public float metalness = 0;

/**
* Subsurface scattering property.
* Additional amount of transmission to do for a given mat, for opaque materials this provides a first order
* approximation to subsurface scattering
* #TODO: figure out if to take a chunk of outgoing energy or use up left over energy
*/
public boolean subSurfaceScattering = false;
public float additionalTransmission = 0f;

/**
* Base texture.
Expand All @@ -104,7 +112,8 @@ public void restoreDefaults() {
specular = 0;
emittance = 0;
roughness = 0;
subSurfaceScattering = false;
transmissionRoughness = 0;
additionalTransmission = 0f;
}

public void getColor(Ray ray) {
Expand All @@ -125,6 +134,7 @@ public void loadMaterialProperties(JsonObject json) {
emittance = json.get("emittance").floatValue(emittance);
roughness = json.get("roughness").floatValue(roughness);
metalness = json.get("metalness").floatValue(metalness);
transmissionRoughness = json.get("transmissionRoughness").floatValue(metalness);
}

public boolean isWater() {
Expand All @@ -146,4 +156,12 @@ public double getPerceptualSmoothness() {
public void setPerceptualSmoothness(double perceptualSmoothness) {
roughness = (float) Math.pow(1 - perceptualSmoothness, 2);
}

public double getPerceptualTransmissionSmoothness() {
return 1 - Math.sqrt(transmissionRoughness);
}

public void setPerceptualTransmissionSmoothness(double perceptualSmoothness) {
transmissionRoughness = (float) Math.pow(1 - perceptualSmoothness, 2);
}
}
139 changes: 48 additions & 91 deletions chunky/src/java/se/llbit/math/Ray.java
Original file line number Diff line number Diff line change
Expand Up @@ -262,50 +262,8 @@ public float[] getBiomeWaterColor(Scene scene) {
*/
public final void diffuseReflection(Ray ray, Random random) {
set(ray);

// get random point on unit disk
double x1 = random.nextDouble();
double x2 = random.nextDouble();
double r = FastMath.sqrt(x1);
double theta = 2 * Math.PI * x2;

// project to point on hemisphere in tangent space
double tx = r * FastMath.cos(theta);
double ty = r * FastMath.sin(theta);
double tz = FastMath.sqrt(1 - x1);

// transform from tangent space to world space
double xx, xy, xz;
double ux, uy, uz;
double vx, vy, vz;

if (QuickMath.abs(n.x) > .1) {
xx = 0;
xy = 1;
xz = 0;
} else {
xx = 1;
xy = 0;
xz = 0;
}

ux = xy * n.z - xz * n.y;
uy = xz * n.x - xx * n.z;
uz = xx * n.y - xy * n.x;

r = 1 / FastMath.sqrt(ux * ux + uy * uy + uz * uz);

ux *= r;
uy *= r;
uz *= r;

vx = uy * n.z - uz * n.y;
vy = uz * n.x - ux * n.z;
vz = ux * n.y - uy * n.x;

d.x = ux * tx + vx * ty + n.x * tz;
d.y = uy * tx + vy * ty + n.y * tz;
d.z = uz * tx + vz * ty + n.z * tz;
// get random point on hemisphere
this.randomHemisphereDir(random);

o.scaleAdd(Ray.OFFSET, d);
currentMaterial = prevMaterial;
Expand Down Expand Up @@ -337,49 +295,8 @@ public final void specularReflection(Ray ray, Random random) {
specularDirection.scaleAdd(-2 * ray.d.dot(ray.n), ray.n, ray.d);

// 2. get diffuse reflection direction (stored in this.d)
// get random point on unit disk
double x1 = random.nextDouble();
double x2 = random.nextDouble();
double r = FastMath.sqrt(x1);
double theta = 2 * Math.PI * x2;

// project to point on hemisphere in tangent space
double tx = r * FastMath.cos(theta);
double ty = r * FastMath.sin(theta);
double tz = FastMath.sqrt(1 - x1);

// transform from tangent space to world space
double xx, xy, xz;
double ux, uy, uz;
double vx, vy, vz;

if (QuickMath.abs(n.x) > .1) {
xx = 0;
xy = 1;
xz = 0;
} else {
xx = 1;
xy = 0;
xz = 0;
}

ux = xy * n.z - xz * n.y;
uy = xz * n.x - xx * n.z;
uz = xx * n.y - xy * n.x;

r = 1 / FastMath.sqrt(ux * ux + uy * uy + uz * uz);

ux *= r;
uy *= r;
uz *= r;

vx = uy * n.z - uz * n.y;
vy = uz * n.x - ux * n.z;
vz = ux * n.y - uy * n.x;

d.x = ux * tx + vx * ty + n.x * tz;
d.y = uy * tx + vy * ty + n.y * tz;
d.z = uz * tx + vz * ty + n.z * tz;
// get random point on hemisphere
this.randomHemisphereDir(random);

// 3. scale d to be roughness * dDiffuse + (1 - roughness) * dSpecular
d.scale(roughness);
Expand Down Expand Up @@ -414,12 +331,50 @@ public final void specularReflection(Ray ray, Random random) {
}
}

public final void specularRefraction (Ray ray, Random random, double radicand, float n1n2, double cosTheta) {
set(ray);
double roughness = ray.getCurrentMaterial().transmissionRoughness;
this.randomHemisphereDir(random);
d.scale(-1.0); //invert for lower hemisphere
double t2 = FastMath.sqrt(radicand);
Vector3 n = ray.getNormal();
Vector3 refractionDirection = new Vector3();
if (cosTheta > 0) {
refractionDirection.x = n1n2 * ray.d.x + (n1n2 * cosTheta - t2) * n.x;
refractionDirection.y = n1n2 * ray.d.y + (n1n2 * cosTheta - t2) * n.y;
refractionDirection.z = n1n2 * ray.d.z + (n1n2 * cosTheta - t2) * n.z;
} else {
refractionDirection.x = n1n2 * ray.d.x - (-n1n2 * cosTheta - t2) * n.x;
refractionDirection.y = n1n2 * ray.d.y - (-n1n2 * cosTheta - t2) * n.y;
refractionDirection.z = n1n2 * ray.d.z - (-n1n2 * cosTheta - t2) * n.z;
}

refractionDirection.normalize();

// 3. scale d to be roughness * dDiffuse + (1 - roughness) * dSpecular
d.scale(roughness);
d.scaleAdd(1 - roughness, refractionDirection);
d.normalize();
o.scaleAdd(Ray.OFFSET, d);

// See Ray.specularReflection for information on why this is needed
// This is the same thing but for refraction instead of reflection
// so this time we want the signs of the dot product to be the same
if (QuickMath.signum(geomN.dot(d)) != QuickMath.signum(geomN.dot(ray.d))) {
double factor = QuickMath.signum(geomN.dot(ray.d)) * -Ray.EPSILON - d.dot(geomN);
d.scaleAdd(factor,geomN);
d.normalize();
}


}

/**
* Scatter ray normal
*
* Random direction sampled from the upper hemisphere
* stored in this.d
* @param random random number source
*/
public final void scatterNormal(Random random) {
public final void randomHemisphereDir(Random random) {
// get random point on unit disk
double x1 = random.nextDouble();
double x2 = random.nextDouble();
Expand Down Expand Up @@ -460,7 +415,9 @@ public final void scatterNormal(Random random) {
vy = uz * n.x - ux * n.z;
vz = ux * n.y - uy * n.x;

n.set(ux * tx + vx * ty + n.x * tz, uy * tx + vy * ty + n.y * tz, uz * tx + vz * ty + n.z * tz);
d.x = ux * tx + vx * ty + n.x * tz;
d.y = uy * tx + vy * ty + n.y * tz;
d.z = uz * tx + vz * ty + n.z * tz;
}

public void setPrevMaterial(Material mat, int data) {
Expand Down

0 comments on commit 04efe43

Please sign in to comment.