From 04efe43ec04b59495c50972492456fdca2d71169 Mon Sep 17 00:00:00 2001 From: Consue Date: Sat, 4 Nov 2023 23:44:09 -0700 Subject: [PATCH 1/7] added transmission roughness to specular refractions --- .../chunky/renderer/scene/PathTracer.java | 30 +--- .../se/llbit/chunky/renderer/scene/Scene.java | 10 ++ .../chunky/ui/render/tabs/MaterialsTab.java | 15 +- .../java/se/llbit/chunky/world/Material.java | 24 ++- chunky/src/java/se/llbit/math/Ray.java | 139 ++++++------------ 5 files changed, 96 insertions(+), 122 deletions(-) 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 277f8ec3f0..d9f7eb1120 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/PathTracer.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/PathTracer.java @@ -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) { @@ -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)) { 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 8ebe411106..76c9474800 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java @@ -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. */ diff --git a/chunky/src/java/se/llbit/chunky/ui/render/tabs/MaterialsTab.java b/chunky/src/java/se/llbit/chunky/ui/render/tabs/MaterialsTab.java index a1644556ee..39120ce386 100644 --- a/chunky/src/java/se/llbit/chunky/ui/render/tabs/MaterialsTab.java +++ b/chunky/src/java/se/llbit/chunky/ui/render/tabs/MaterialsTab.java @@ -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 listView; @@ -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."); @@ -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); @@ -116,6 +121,7 @@ private void updateSelectedMaterial(String materialName) { double specAcc = 0; double iorAcc = 0; double perceptualSmoothnessAcc = 0; + double perceptualTransmissionSmoothnessAcc = 0; double metalnessAcc = 0; Collection blocks = MaterialStore.collections.get(materialName); for (Block block : blocks) { @@ -123,12 +129,14 @@ private void updateSelectedMaterial(String materialName) { 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)) { @@ -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; } @@ -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; } @@ -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 -> {}); } } diff --git a/chunky/src/java/se/llbit/chunky/world/Material.java b/chunky/src/java/se/llbit/chunky/world/Material.java index 572af56774..a6a7c78e96 100644 --- a/chunky/src/java/se/llbit/chunky/world/Material.java +++ b/chunky/src/java/se/llbit/chunky/world/Material.java @@ -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 @@ -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. @@ -104,7 +112,8 @@ public void restoreDefaults() { specular = 0; emittance = 0; roughness = 0; - subSurfaceScattering = false; + transmissionRoughness = 0; + additionalTransmission = 0f; } public void getColor(Ray ray) { @@ -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() { @@ -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); + } } diff --git a/chunky/src/java/se/llbit/math/Ray.java b/chunky/src/java/se/llbit/math/Ray.java index 11deb1e4f4..4879d12610 100644 --- a/chunky/src/java/se/llbit/math/Ray.java +++ b/chunky/src/java/se/llbit/math/Ray.java @@ -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; @@ -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); @@ -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(); @@ -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) { From 9c011703ca23a957a6e5f6056e9d810fa8a51fab Mon Sep 17 00:00:00 2001 From: Consue Date: Mon, 6 Nov 2023 19:04:18 -0800 Subject: [PATCH 2/7] added diffuse transmission --- .../llbit/chunky/renderer/scene/PathTracer.java | 16 ++++++++++++++-- .../se/llbit/chunky/renderer/scene/Scene.java | 10 ++++++++++ .../chunky/ui/render/tabs/MaterialsTab.java | 13 +++++++++++++ .../src/java/se/llbit/chunky/world/Material.java | 3 ++- 4 files changed, 39 insertions(+), 3 deletions(-) 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 d9f7eb1120..bf71d8af9a 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/PathTracer.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/PathTracer.java @@ -226,6 +226,16 @@ private static boolean doDiffuseReflection(Ray ray, Ray next, Material currentMa Vector3 emittance = new Vector3(); Vector4 indirectEmitterColor = new Vector4(0, 0, 0, 0); + float pTransmit = currentMat.additionalTransmission; + + boolean transmitBack = pTransmit > Ray.EPSILON && random.nextFloat() < pTransmit; + + double eventProb = (transmitBack ? pTransmit: 1- pTransmit ) + Ray.EPSILON; + + if (transmitBack) { + ray.invertNormal(); + } + if (scene.emittersEnabled && (!scene.isPreventNormalEmitterWithSampling() || scene.getEmitterSamplingStrategy() == EmitterSamplingStrategy.NONE || ray.depth == 0) && currentMat.emittance > Ray.EPSILON) { // Quadratic emittance mapping, so a pixel that's 50% darker will emit only 25% as much light @@ -530,12 +540,14 @@ public static void getDirectLightAttenuation(Scene scene, Ray ray, WorkerState s attenuation.y = 1; attenuation.z = 1; attenuation.w = 1; - while (attenuation.w > 0) { + while (attenuation.w > Ray.EPSILON) { ray.o.scaleAdd(Ray.OFFSET, ray.d); if (!PreviewRayTracer.nextIntersection(scene, ray)) { break; } - double mult = 1 - ray.color.w; + Material mat = ray.getCurrentMaterial(); + double pDiffuse = scene.fancierTranslucency ? 1 - Math.sqrt(1 - ray.color.w) : ray.color.w; + double mult = 1 - pDiffuse*(1 -Math.pow(mat.additionalTransmission, 1)); attenuation.x *= ray.color.x * ray.color.w + mult; attenuation.y *= ray.color.y * ray.color.w + mult; attenuation.z *= ray.color.z * ray.color.w + mult; 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 76c9474800..aac56c70a2 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java @@ -3276,6 +3276,16 @@ public void setMetalness(String materialName, float value) { refresh(ResetReason.MATERIALS_CHANGED); } + /** + * Modifies the additional transmission through diffuse property for the given material. + */ + public void setAdditionalTransmission(String materialName, float value) { + JsonObject material = materials.getOrDefault(materialName, new JsonObject()).object(); + material.set("additionalTransmission", Json.of(value)); + materials.put(materialName, material); + refresh(ResetReason.MATERIALS_CHANGED); + } + public int getYClipMin() { return yClipMin; } diff --git a/chunky/src/java/se/llbit/chunky/ui/render/tabs/MaterialsTab.java b/chunky/src/java/se/llbit/chunky/ui/render/tabs/MaterialsTab.java index 39120ce386..8c4276e302 100644 --- a/chunky/src/java/se/llbit/chunky/ui/render/tabs/MaterialsTab.java +++ b/chunky/src/java/se/llbit/chunky/ui/render/tabs/MaterialsTab.java @@ -56,6 +56,8 @@ public class MaterialsTab extends HBox implements RenderControlsTab, Initializab private final DoubleAdjuster perceptualTransmissionSmoothness = new DoubleAdjuster(); private final DoubleAdjuster metalness = new DoubleAdjuster(); + + private final DoubleAdjuster additionalTransmission = new DoubleAdjuster(); private final ListView listView; public MaterialsTab() { @@ -77,6 +79,9 @@ public MaterialsTab() { metalness.setName("Metalness"); metalness.setRange(0, 1); metalness.setTooltip("Metalness (texture-tinted reflectivity) of the selected material."); + additionalTransmission.setName("Additional Transmission"); + additionalTransmission.setRange(0, 1); + additionalTransmission.setTooltip("Amount of transmitted light to back in diffuse component"); ObservableList blockIds = FXCollections.observableArrayList(); blockIds.addAll(MaterialStore.collections.keySet()); blockIds.addAll(ExtraMaterials.idMap.keySet()); @@ -93,6 +98,7 @@ public MaterialsTab() { settings.getChildren().addAll( new Label("Material Properties"), emittance, specular, perceptualSmoothness, ior,perceptualTransmissionSmoothness, metalness, + additionalTransmission, new Label("(set to zero to disable)")); setPadding(new Insets(10)); setSpacing(15); @@ -123,6 +129,7 @@ private void updateSelectedMaterial(String materialName) { double perceptualSmoothnessAcc = 0; double perceptualTransmissionSmoothnessAcc = 0; double metalnessAcc = 0; + double additionalTransmissionAcc = 0; Collection blocks = MaterialStore.collections.get(materialName); for (Block block : blocks) { emAcc += block.emittance; @@ -131,6 +138,7 @@ private void updateSelectedMaterial(String materialName) { perceptualSmoothnessAcc += block.getPerceptualSmoothness(); perceptualTransmissionSmoothnessAcc += block.getPerceptualTransmissionSmoothness(); metalnessAcc += block.metalness; + additionalTransmissionAcc += block.additionalTransmission; } emittance.set(emAcc / blocks.size()); specular.set(specAcc / blocks.size()); @@ -138,6 +146,7 @@ private void updateSelectedMaterial(String materialName) { perceptualSmoothness.set(perceptualSmoothnessAcc / blocks.size()); perceptualTransmissionSmoothness.set(perceptualTransmissionSmoothnessAcc / blocks.size()); metalness.set(metalnessAcc / blocks.size()); + additionalTransmission.set(additionalTransmissionAcc/blocks.size()); materialExists = true; } else if (ExtraMaterials.idMap.containsKey(materialName)) { Material material = ExtraMaterials.idMap.get(materialName); @@ -148,6 +157,7 @@ private void updateSelectedMaterial(String materialName) { perceptualSmoothness.set(material.getPerceptualSmoothness()); perceptualTransmissionSmoothness.set(material.getPerceptualTransmissionSmoothness()); metalness.set(material.metalness); + additionalTransmission.set(material.additionalTransmission); materialExists = true; } } else if (MaterialStore.blockIds.contains(materialName)) { @@ -159,6 +169,7 @@ private void updateSelectedMaterial(String materialName) { perceptualSmoothness.set(block.getPerceptualSmoothness()); perceptualTransmissionSmoothness.set(block.getPerceptualTransmissionSmoothness()); metalness.set(block.metalness); + additionalTransmission.set(block.additionalTransmission); materialExists = true; } if (materialExists) { @@ -169,6 +180,7 @@ private void updateSelectedMaterial(String materialName) { perceptualTransmissionSmoothness.onValueChange(value -> scene.setPerceptualTransmissionSmoothness(materialName, value.floatValue())); metalness.onValueChange(value -> scene.setMetalness(materialName, value.floatValue())); + additionalTransmission.onValueChange(value -> scene.setAdditionalTransmission(materialName, value.floatValue())); } else { emittance.onValueChange(value -> {}); specular.onValueChange(value -> {}); @@ -176,6 +188,7 @@ private void updateSelectedMaterial(String materialName) { perceptualSmoothness.onValueChange(value -> {}); perceptualTransmissionSmoothness.onValueChange(value -> {}); metalness.onValueChange(value -> {}); + additionalTransmission.onValueChange(value -> {}); } } diff --git a/chunky/src/java/se/llbit/chunky/world/Material.java b/chunky/src/java/se/llbit/chunky/world/Material.java index a6a7c78e96..c716b85e6a 100644 --- a/chunky/src/java/se/llbit/chunky/world/Material.java +++ b/chunky/src/java/se/llbit/chunky/world/Material.java @@ -134,7 +134,8 @@ 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); + transmissionRoughness = json.get("transmissionRoughness").floatValue(transmissionRoughness); + additionalTransmission = json.get("additionalTransmission").floatValue(additionalTransmission); } public boolean isWater() { From 59c195cb58d64507e26dfef4a78dd0ee348f5931 Mon Sep 17 00:00:00 2001 From: Consue Date: Mon, 6 Nov 2023 20:55:03 -0800 Subject: [PATCH 3/7] fix diffuse direction? --- .../java/se/llbit/chunky/renderer/scene/PathTracer.java | 9 ++++++--- chunky/src/java/se/llbit/math/Ray.java | 6 +++--- 2 files changed, 9 insertions(+), 6 deletions(-) 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 bf71d8af9a..f445a3e9ec 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/PathTracer.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/PathTracer.java @@ -17,7 +17,6 @@ */ package se.llbit.chunky.renderer.scene; -import org.apache.commons.math3.util.FastMath; import se.llbit.chunky.block.minecraft.Air; import se.llbit.chunky.block.minecraft.Water; import se.llbit.chunky.renderer.EmitterSamplingStrategy; @@ -297,7 +296,7 @@ private static boolean doDiffuseReflection(Ray ray, Ray next, Material currentMa } } - next.diffuseReflection(ray, random); + next.diffuseLobes(ray, random, transmitBack); hit = pathTrace(scene, next, state, false) || hit; if (hit) { cumulativeColor.x += emittance.x + ray.color.x * (directLightR * scene.sun.emittance.x + next.color.x + indirectEmitterColor.x); @@ -311,7 +310,7 @@ private static boolean doDiffuseReflection(Ray ray, Ray next, Material currentMa } } else { - next.diffuseReflection(ray, random); + next.diffuseLobes(ray, random, transmitBack); hit = pathTrace(scene, next, state, false) || hit; if (hit) { @@ -325,6 +324,10 @@ private static boolean doDiffuseReflection(Ray ray, Ray next, Material currentMa cumulativeColor.z += ray.color.z * indirectEmitterColor.z; } } + //fix the normal if inverted for use in other things + if (transmitBack) { + ray.invertNormal(); + } return hit; } diff --git a/chunky/src/java/se/llbit/math/Ray.java b/chunky/src/java/se/llbit/math/Ray.java index 4879d12610..e91253ab08 100644 --- a/chunky/src/java/se/llbit/math/Ray.java +++ b/chunky/src/java/se/llbit/math/Ray.java @@ -258,9 +258,9 @@ public float[] getBiomeWaterColor(Scene scene) { } /** - * Set this ray to a random diffuse reflection of the input ray. + * Set this ray to a random diffuse reflection or transmission of the input ray. */ - public final void diffuseReflection(Ray ray, Random random) { + public final void diffuseLobes(Ray ray, Random random, boolean transmitBack) { set(ray); // get random point on hemisphere this.randomHemisphereDir(random); @@ -270,7 +270,7 @@ public final void diffuseReflection(Ray ray, Random random) { specular = false; // See specularReflection for explanation of why this is needed - if(QuickMath.signum(geomN.dot(d)) == QuickMath.signum(geomN.dot(ray.d))) { + if(QuickMath.signum(geomN.dot(d)) == QuickMath.signum(geomN.dot(ray.d))^transmitBack) { double factor = QuickMath.signum(geomN.dot(ray.d)) * -Ray.EPSILON - d.dot(geomN); d.scaleAdd(factor, geomN); d.normalize(); From 9b31dd11514b3dfe22366e9c8f9eca820231a8aa Mon Sep 17 00:00:00 2001 From: Consue Date: Sat, 4 Nov 2023 23:44:09 -0700 Subject: [PATCH 4/7] added transmission roughness to specular refractions --- .../chunky/renderer/scene/PathTracer.java | 30 +--- .../se/llbit/chunky/renderer/scene/Scene.java | 10 ++ .../chunky/ui/render/tabs/MaterialsTab.java | 15 +- .../java/se/llbit/chunky/world/Material.java | 24 ++- chunky/src/java/se/llbit/math/Ray.java | 139 ++++++------------ 5 files changed, 96 insertions(+), 122 deletions(-) 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 277f8ec3f0..d9f7eb1120 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/PathTracer.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/PathTracer.java @@ -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) { @@ -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)) { 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 fb4f8adcf4..c40e7a2176 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java @@ -3201,6 +3201,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. */ diff --git a/chunky/src/java/se/llbit/chunky/ui/render/tabs/MaterialsTab.java b/chunky/src/java/se/llbit/chunky/ui/render/tabs/MaterialsTab.java index a1644556ee..39120ce386 100644 --- a/chunky/src/java/se/llbit/chunky/ui/render/tabs/MaterialsTab.java +++ b/chunky/src/java/se/llbit/chunky/ui/render/tabs/MaterialsTab.java @@ -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 listView; @@ -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."); @@ -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); @@ -116,6 +121,7 @@ private void updateSelectedMaterial(String materialName) { double specAcc = 0; double iorAcc = 0; double perceptualSmoothnessAcc = 0; + double perceptualTransmissionSmoothnessAcc = 0; double metalnessAcc = 0; Collection blocks = MaterialStore.collections.get(materialName); for (Block block : blocks) { @@ -123,12 +129,14 @@ private void updateSelectedMaterial(String materialName) { 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)) { @@ -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; } @@ -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; } @@ -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 -> {}); } } diff --git a/chunky/src/java/se/llbit/chunky/world/Material.java b/chunky/src/java/se/llbit/chunky/world/Material.java index 572af56774..a6a7c78e96 100644 --- a/chunky/src/java/se/llbit/chunky/world/Material.java +++ b/chunky/src/java/se/llbit/chunky/world/Material.java @@ -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 @@ -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. @@ -104,7 +112,8 @@ public void restoreDefaults() { specular = 0; emittance = 0; roughness = 0; - subSurfaceScattering = false; + transmissionRoughness = 0; + additionalTransmission = 0f; } public void getColor(Ray ray) { @@ -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() { @@ -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); + } } diff --git a/chunky/src/java/se/llbit/math/Ray.java b/chunky/src/java/se/llbit/math/Ray.java index 11deb1e4f4..4879d12610 100644 --- a/chunky/src/java/se/llbit/math/Ray.java +++ b/chunky/src/java/se/llbit/math/Ray.java @@ -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; @@ -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); @@ -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(); @@ -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) { From 6c8ba86e33854051f8c99e1ea46b1b195b9def6f Mon Sep 17 00:00:00 2001 From: Consue Date: Mon, 6 Nov 2023 19:04:18 -0800 Subject: [PATCH 5/7] added diffuse transmission --- .../llbit/chunky/renderer/scene/PathTracer.java | 16 ++++++++++++++-- .../se/llbit/chunky/renderer/scene/Scene.java | 10 ++++++++++ .../chunky/ui/render/tabs/MaterialsTab.java | 13 +++++++++++++ .../src/java/se/llbit/chunky/world/Material.java | 3 ++- 4 files changed, 39 insertions(+), 3 deletions(-) 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 d9f7eb1120..bf71d8af9a 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/PathTracer.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/PathTracer.java @@ -226,6 +226,16 @@ private static boolean doDiffuseReflection(Ray ray, Ray next, Material currentMa Vector3 emittance = new Vector3(); Vector4 indirectEmitterColor = new Vector4(0, 0, 0, 0); + float pTransmit = currentMat.additionalTransmission; + + boolean transmitBack = pTransmit > Ray.EPSILON && random.nextFloat() < pTransmit; + + double eventProb = (transmitBack ? pTransmit: 1- pTransmit ) + Ray.EPSILON; + + if (transmitBack) { + ray.invertNormal(); + } + if (scene.emittersEnabled && (!scene.isPreventNormalEmitterWithSampling() || scene.getEmitterSamplingStrategy() == EmitterSamplingStrategy.NONE || ray.depth == 0) && currentMat.emittance > Ray.EPSILON) { // Quadratic emittance mapping, so a pixel that's 50% darker will emit only 25% as much light @@ -530,12 +540,14 @@ public static void getDirectLightAttenuation(Scene scene, Ray ray, WorkerState s attenuation.y = 1; attenuation.z = 1; attenuation.w = 1; - while (attenuation.w > 0) { + while (attenuation.w > Ray.EPSILON) { ray.o.scaleAdd(Ray.OFFSET, ray.d); if (!PreviewRayTracer.nextIntersection(scene, ray)) { break; } - double mult = 1 - ray.color.w; + Material mat = ray.getCurrentMaterial(); + double pDiffuse = scene.fancierTranslucency ? 1 - Math.sqrt(1 - ray.color.w) : ray.color.w; + double mult = 1 - pDiffuse*(1 -Math.pow(mat.additionalTransmission, 1)); attenuation.x *= ray.color.x * ray.color.w + mult; attenuation.y *= ray.color.y * ray.color.w + mult; attenuation.z *= ray.color.z * ray.color.w + mult; 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 c40e7a2176..048c92e852 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java @@ -3221,6 +3221,16 @@ public void setMetalness(String materialName, float value) { refresh(ResetReason.MATERIALS_CHANGED); } + /** + * Modifies the additional transmission through diffuse property for the given material. + */ + public void setAdditionalTransmission(String materialName, float value) { + JsonObject material = materials.getOrDefault(materialName, new JsonObject()).object(); + material.set("additionalTransmission", Json.of(value)); + materials.put(materialName, material); + refresh(ResetReason.MATERIALS_CHANGED); + } + public int getYClipMin() { return yClipMin; } diff --git a/chunky/src/java/se/llbit/chunky/ui/render/tabs/MaterialsTab.java b/chunky/src/java/se/llbit/chunky/ui/render/tabs/MaterialsTab.java index 39120ce386..8c4276e302 100644 --- a/chunky/src/java/se/llbit/chunky/ui/render/tabs/MaterialsTab.java +++ b/chunky/src/java/se/llbit/chunky/ui/render/tabs/MaterialsTab.java @@ -56,6 +56,8 @@ public class MaterialsTab extends HBox implements RenderControlsTab, Initializab private final DoubleAdjuster perceptualTransmissionSmoothness = new DoubleAdjuster(); private final DoubleAdjuster metalness = new DoubleAdjuster(); + + private final DoubleAdjuster additionalTransmission = new DoubleAdjuster(); private final ListView listView; public MaterialsTab() { @@ -77,6 +79,9 @@ public MaterialsTab() { metalness.setName("Metalness"); metalness.setRange(0, 1); metalness.setTooltip("Metalness (texture-tinted reflectivity) of the selected material."); + additionalTransmission.setName("Additional Transmission"); + additionalTransmission.setRange(0, 1); + additionalTransmission.setTooltip("Amount of transmitted light to back in diffuse component"); ObservableList blockIds = FXCollections.observableArrayList(); blockIds.addAll(MaterialStore.collections.keySet()); blockIds.addAll(ExtraMaterials.idMap.keySet()); @@ -93,6 +98,7 @@ public MaterialsTab() { settings.getChildren().addAll( new Label("Material Properties"), emittance, specular, perceptualSmoothness, ior,perceptualTransmissionSmoothness, metalness, + additionalTransmission, new Label("(set to zero to disable)")); setPadding(new Insets(10)); setSpacing(15); @@ -123,6 +129,7 @@ private void updateSelectedMaterial(String materialName) { double perceptualSmoothnessAcc = 0; double perceptualTransmissionSmoothnessAcc = 0; double metalnessAcc = 0; + double additionalTransmissionAcc = 0; Collection blocks = MaterialStore.collections.get(materialName); for (Block block : blocks) { emAcc += block.emittance; @@ -131,6 +138,7 @@ private void updateSelectedMaterial(String materialName) { perceptualSmoothnessAcc += block.getPerceptualSmoothness(); perceptualTransmissionSmoothnessAcc += block.getPerceptualTransmissionSmoothness(); metalnessAcc += block.metalness; + additionalTransmissionAcc += block.additionalTransmission; } emittance.set(emAcc / blocks.size()); specular.set(specAcc / blocks.size()); @@ -138,6 +146,7 @@ private void updateSelectedMaterial(String materialName) { perceptualSmoothness.set(perceptualSmoothnessAcc / blocks.size()); perceptualTransmissionSmoothness.set(perceptualTransmissionSmoothnessAcc / blocks.size()); metalness.set(metalnessAcc / blocks.size()); + additionalTransmission.set(additionalTransmissionAcc/blocks.size()); materialExists = true; } else if (ExtraMaterials.idMap.containsKey(materialName)) { Material material = ExtraMaterials.idMap.get(materialName); @@ -148,6 +157,7 @@ private void updateSelectedMaterial(String materialName) { perceptualSmoothness.set(material.getPerceptualSmoothness()); perceptualTransmissionSmoothness.set(material.getPerceptualTransmissionSmoothness()); metalness.set(material.metalness); + additionalTransmission.set(material.additionalTransmission); materialExists = true; } } else if (MaterialStore.blockIds.contains(materialName)) { @@ -159,6 +169,7 @@ private void updateSelectedMaterial(String materialName) { perceptualSmoothness.set(block.getPerceptualSmoothness()); perceptualTransmissionSmoothness.set(block.getPerceptualTransmissionSmoothness()); metalness.set(block.metalness); + additionalTransmission.set(block.additionalTransmission); materialExists = true; } if (materialExists) { @@ -169,6 +180,7 @@ private void updateSelectedMaterial(String materialName) { perceptualTransmissionSmoothness.onValueChange(value -> scene.setPerceptualTransmissionSmoothness(materialName, value.floatValue())); metalness.onValueChange(value -> scene.setMetalness(materialName, value.floatValue())); + additionalTransmission.onValueChange(value -> scene.setAdditionalTransmission(materialName, value.floatValue())); } else { emittance.onValueChange(value -> {}); specular.onValueChange(value -> {}); @@ -176,6 +188,7 @@ private void updateSelectedMaterial(String materialName) { perceptualSmoothness.onValueChange(value -> {}); perceptualTransmissionSmoothness.onValueChange(value -> {}); metalness.onValueChange(value -> {}); + additionalTransmission.onValueChange(value -> {}); } } diff --git a/chunky/src/java/se/llbit/chunky/world/Material.java b/chunky/src/java/se/llbit/chunky/world/Material.java index a6a7c78e96..c716b85e6a 100644 --- a/chunky/src/java/se/llbit/chunky/world/Material.java +++ b/chunky/src/java/se/llbit/chunky/world/Material.java @@ -134,7 +134,8 @@ 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); + transmissionRoughness = json.get("transmissionRoughness").floatValue(transmissionRoughness); + additionalTransmission = json.get("additionalTransmission").floatValue(additionalTransmission); } public boolean isWater() { From 0237c80c047bd6d026bcd0e7f9d34cb2bb4360d6 Mon Sep 17 00:00:00 2001 From: Consue Date: Mon, 6 Nov 2023 20:55:03 -0800 Subject: [PATCH 6/7] fix diffuse direction? --- .../java/se/llbit/chunky/renderer/scene/PathTracer.java | 9 ++++++--- chunky/src/java/se/llbit/math/Ray.java | 6 +++--- 2 files changed, 9 insertions(+), 6 deletions(-) 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 bf71d8af9a..f445a3e9ec 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/PathTracer.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/PathTracer.java @@ -17,7 +17,6 @@ */ package se.llbit.chunky.renderer.scene; -import org.apache.commons.math3.util.FastMath; import se.llbit.chunky.block.minecraft.Air; import se.llbit.chunky.block.minecraft.Water; import se.llbit.chunky.renderer.EmitterSamplingStrategy; @@ -297,7 +296,7 @@ private static boolean doDiffuseReflection(Ray ray, Ray next, Material currentMa } } - next.diffuseReflection(ray, random); + next.diffuseLobes(ray, random, transmitBack); hit = pathTrace(scene, next, state, false) || hit; if (hit) { cumulativeColor.x += emittance.x + ray.color.x * (directLightR * scene.sun.emittance.x + next.color.x + indirectEmitterColor.x); @@ -311,7 +310,7 @@ private static boolean doDiffuseReflection(Ray ray, Ray next, Material currentMa } } else { - next.diffuseReflection(ray, random); + next.diffuseLobes(ray, random, transmitBack); hit = pathTrace(scene, next, state, false) || hit; if (hit) { @@ -325,6 +324,10 @@ private static boolean doDiffuseReflection(Ray ray, Ray next, Material currentMa cumulativeColor.z += ray.color.z * indirectEmitterColor.z; } } + //fix the normal if inverted for use in other things + if (transmitBack) { + ray.invertNormal(); + } return hit; } diff --git a/chunky/src/java/se/llbit/math/Ray.java b/chunky/src/java/se/llbit/math/Ray.java index 4879d12610..e91253ab08 100644 --- a/chunky/src/java/se/llbit/math/Ray.java +++ b/chunky/src/java/se/llbit/math/Ray.java @@ -258,9 +258,9 @@ public float[] getBiomeWaterColor(Scene scene) { } /** - * Set this ray to a random diffuse reflection of the input ray. + * Set this ray to a random diffuse reflection or transmission of the input ray. */ - public final void diffuseReflection(Ray ray, Random random) { + public final void diffuseLobes(Ray ray, Random random, boolean transmitBack) { set(ray); // get random point on hemisphere this.randomHemisphereDir(random); @@ -270,7 +270,7 @@ public final void diffuseReflection(Ray ray, Random random) { specular = false; // See specularReflection for explanation of why this is needed - if(QuickMath.signum(geomN.dot(d)) == QuickMath.signum(geomN.dot(ray.d))) { + if(QuickMath.signum(geomN.dot(d)) == QuickMath.signum(geomN.dot(ray.d))^transmitBack) { double factor = QuickMath.signum(geomN.dot(ray.d)) * -Ray.EPSILON - d.dot(geomN); d.scaleAdd(factor, geomN); d.normalize(); From 673c79c3c545e515868e087ab2294223827cd7fd Mon Sep 17 00:00:00 2001 From: Consue Date: Wed, 13 Dec 2023 11:57:19 -0800 Subject: [PATCH 7/7] material setting bug --- chunky/src/java/se/llbit/math/Ray.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chunky/src/java/se/llbit/math/Ray.java b/chunky/src/java/se/llbit/math/Ray.java index e91253ab08..ba6ba7af98 100644 --- a/chunky/src/java/se/llbit/math/Ray.java +++ b/chunky/src/java/se/llbit/math/Ray.java @@ -266,7 +266,9 @@ public final void diffuseLobes(Ray ray, Random random, boolean transmitBack) { this.randomHemisphereDir(random); o.scaleAdd(Ray.OFFSET, d); - currentMaterial = prevMaterial; + //if a block is solid while transmiting then we want to keep the old material as it penetrates + // might be able to remove the solid check but test correctness and performance + if (!transmitBack | !currentMaterial.solid) currentMaterial = prevMaterial; specular = false; // See specularReflection for explanation of why this is needed