added physically based materials
This commit is contained in:
parent
e97416c8ea
commit
efc094e8cd
|
@ -105,7 +105,7 @@ public class Vector {
|
|||
}
|
||||
|
||||
// based on https://bheisler.github.io/post/writing-raytracer-in-rust-part-3/
|
||||
public Vector refract(Vector normal, Vector incoming, double ior) {
|
||||
public static Vector refract(Vector normal, Vector incoming, double ior) {
|
||||
|
||||
double iDotN = incoming.dot(normal);
|
||||
double etaI = 1;
|
||||
|
|
|
@ -11,7 +11,7 @@ public class Main {
|
|||
|
||||
public static void main(String[] args) {
|
||||
|
||||
Resolution resolution = new Resolution(800, 800, 2);
|
||||
Resolution resolution = new Resolution(800, 800, 4);
|
||||
|
||||
PlayerController controller = new PlayerController();
|
||||
|
||||
|
|
|
@ -3,17 +3,16 @@ package geometry.scene;
|
|||
import basics.math.algebra.Ray;
|
||||
import basics.math.algebra.Vector;
|
||||
import optics.light.Color;
|
||||
import optics.light.Directional;
|
||||
import optics.light.LightSource;
|
||||
import optics.light.Point;
|
||||
import pathtracing.BSDF;
|
||||
import pathtracing.pbr.Dielectric;
|
||||
import pathtracing.pbr.Emissive;
|
||||
import pathtracing.pbr.PhysicallyBasedMaterial;
|
||||
import raytracing.BasicMaterial;
|
||||
import renderer.mesh.BasicMesh;
|
||||
import renderer.mesh.Mesh;
|
||||
import renderer.primitive.Disk;
|
||||
import renderer.primitive.Plane;
|
||||
import renderer.primitive.Sphere;
|
||||
import shading.Material;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -62,11 +61,11 @@ public class Scene {
|
|||
public static Scene generatePathtracingExampleScene() {
|
||||
Scene scene = new Scene(new LinearList());
|
||||
|
||||
BSDF red = new BSDF(new Color(1, 0.3, 0.3), new Color(0), 1.0);
|
||||
BSDF green = new BSDF(new Color(0.3,1, 0.3), new Color(0), 1);
|
||||
BSDF blue = new BSDF(new Color(0.3, 0.3, 1), new Color(0), 1);
|
||||
BSDF white = new BSDF(new Color(1, 1, 1), new Color(0), 1);
|
||||
BSDF light = new BSDF(Color.BLACK, new Color(300), 1.0);
|
||||
var red = new Dielectric(new Color(1.0, 0.3, 0.3), 1.0, false, 1.5);
|
||||
var green = new Dielectric(new Color(0.3, 1.0, 0.3), 1.0, false, 1.5);
|
||||
var blue = new Dielectric(new Color(0.3, 0.3, 1.0), 1.0, false, 1.5);
|
||||
var white = new Dielectric(new Color(1.0, 1.0, 1.0), 1.0, false, 1.5);
|
||||
var light = new Emissive(Color.WHITE, 8.0);
|
||||
|
||||
scene.addMesh(new BasicMesh(white, new Sphere(new Vector(0,0,0), 1.0)));
|
||||
scene.addMesh(new BasicMesh(white, new Sphere(new Vector(3,2,1), 1.0)));
|
||||
|
|
|
@ -105,17 +105,11 @@ public class Color {
|
|||
}
|
||||
|
||||
public Color lerp(Color other, double k) {
|
||||
return this.scale(k).add(other.scale(1.0 - k));
|
||||
return this.scale(1.0 - k).add(other.scale(k));
|
||||
}
|
||||
|
||||
public void colorCorrect() {
|
||||
this.r = tonemap(this.r);
|
||||
this.g = tonemap(this.g);
|
||||
this.b = tonemap(this.b);
|
||||
}
|
||||
|
||||
private double tonemap(double k) {
|
||||
return -1 / (k + 1) + 1;
|
||||
public Color lerp(Color other, Color k) {
|
||||
return this.mul(Color.WHITE.sub(k)).add(other.mul(k));
|
||||
}
|
||||
|
||||
public int getIntRGB() {
|
||||
|
@ -126,6 +120,10 @@ public class Color {
|
|||
return 0xff000000 | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
public double max() {
|
||||
return Math.max(Math.max(this.r, this.g), this.b);
|
||||
}
|
||||
|
||||
public enum SwizzleMask {
|
||||
rgb,
|
||||
grb,
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
package pathtracing;
|
||||
|
||||
import optics.light.Color;
|
||||
import shading.Material;
|
||||
|
||||
public class BSDF implements Material {
|
||||
|
||||
private Color reflectance;
|
||||
private Color emission;
|
||||
|
||||
private double roughness;
|
||||
|
||||
public BSDF(Color reflectance, Color emission, double roughness) {
|
||||
this.reflectance = reflectance;
|
||||
this.emission = emission;
|
||||
this.roughness = roughness;
|
||||
}
|
||||
|
||||
public Color getReflectance() {
|
||||
return reflectance;
|
||||
}
|
||||
|
||||
public Color getEmission() {
|
||||
return emission;
|
||||
}
|
||||
|
||||
public double getRoughness() {
|
||||
return roughness;
|
||||
}
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
package pathtracing;
|
||||
|
||||
import basics.math.algebra.Basis;
|
||||
import basics.math.algebra.Ray;
|
||||
import basics.math.algebra.Vector;
|
||||
import geometry.scene.Hit;
|
||||
import geometry.scene.Scene;
|
||||
import optics.light.Color;
|
||||
import optics.light.LightSource;
|
||||
import optics.view.Camera;
|
||||
import optics.view.PinholeCamera;
|
||||
import pathtracing.pbr.Emissive;
|
||||
import pathtracing.pbr.PhysicallyBasedMaterial;
|
||||
import renderer.Renderer;
|
||||
|
||||
import java.util.Optional;
|
||||
|
@ -30,15 +30,8 @@ public class Pathtracer implements Renderer {
|
|||
return 0.0;
|
||||
}
|
||||
|
||||
private double castShadow(LightSource target, Vector point, Vector surfaceNormal) {
|
||||
Vector lightDirection = target.getDirection(point);
|
||||
double distance = target.getDistance(point);
|
||||
double shadow = traceShadow(point, lightDirection, distance);
|
||||
return Math.max(surfaceNormal.dot(lightDirection), 0.0) * shadow;
|
||||
}
|
||||
|
||||
private Color traceDirect(Ray directRay, int depth) {
|
||||
if (depth == 8) {
|
||||
public Color traceDirect(Ray directRay, int depth) {
|
||||
if (depth == 4) {
|
||||
return Color.BLACK;
|
||||
}
|
||||
|
||||
|
@ -52,58 +45,9 @@ public class Pathtracer implements Renderer {
|
|||
Vector intersection = directRay.travel(result.get().getDistance());
|
||||
Vector normal = result.get().getMesh().normalAt(intersection, directRay.getDirection());
|
||||
|
||||
BSDF material = (BSDF) result.get().getMesh().getMaterial();
|
||||
var material = (PhysicallyBasedMaterial) result.get().getMesh().getMaterial();
|
||||
|
||||
var diffuse = diffuseCosineWeightedBRDF(intersection, normal, material, depth);
|
||||
|
||||
return diffuse.add(material.getEmission());
|
||||
}
|
||||
|
||||
private Color diffuseCosineWeightedBRDF(Vector intersection, Vector normal, BSDF bsdf, int depth) {
|
||||
var next = new Ray(intersection, sampleNormalHemisphere(normal, bsdf.getRoughness()), 1e-3, 1e3);
|
||||
|
||||
var cosTheta = next.getDirection().dot(normal);
|
||||
|
||||
var pdf = cosTheta / Math.PI;
|
||||
|
||||
var indirectLight = traceDirect(next, depth + 1).scale(pdf);
|
||||
|
||||
// direct light
|
||||
|
||||
Color directLight = Color.BLACK;
|
||||
|
||||
for (LightSource light : scene.getLights()) {
|
||||
double directInfluence = castShadow(light, intersection, normal);
|
||||
|
||||
Color incoming = bsdf.getReflectance().scale(directInfluence).mul(light.getColor());
|
||||
|
||||
directLight = directLight.add(incoming);
|
||||
}
|
||||
|
||||
return bsdf.getReflectance().mul(indirectLight.add(directLight));
|
||||
}
|
||||
|
||||
private Vector cosineHemisphere() {
|
||||
double u1 = Math.random();
|
||||
double u2 = Math.random();
|
||||
|
||||
double r = Math.sqrt(u1);
|
||||
double theta = 2 * Math.PI * u2;
|
||||
|
||||
double x = r * Math.cos(theta);
|
||||
double y = r * Math.sin(theta);
|
||||
|
||||
return new Vector(x, y, Math.sqrt(1 - u1));
|
||||
}
|
||||
|
||||
private Vector sampleNormalHemisphere(Vector normal, double roughness) {
|
||||
Vector hemisphereSample = cosineHemisphere();
|
||||
|
||||
var orthnormalBasis = Basis.constructOrthonormalBasis(normal);
|
||||
|
||||
return orthnormalBasis.vectors[0].scale(hemisphereSample.z)
|
||||
.add(orthnormalBasis.vectors[1].scale(hemisphereSample.x))
|
||||
.add(orthnormalBasis.vectors[2].scale(hemisphereSample.y));
|
||||
return material.surfaceColor(normal, directRay.getDirection(), intersection, this, depth + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package pathtracing.bdf;
|
||||
|
||||
import basics.math.algebra.Basis;
|
||||
import basics.math.algebra.Vector;
|
||||
|
||||
public abstract class BDF {
|
||||
|
||||
public static Vector sampleBTDF(Vector normal, Vector incoming, double roughness, double ior) {
|
||||
Vector up = Vector.refract(normal, incoming, ior).lerp(normal.negate(), roughness);
|
||||
|
||||
Vector hemisphereSample = BDF.cosineHemisphere(roughness);
|
||||
|
||||
var orthnormalBasis = Basis.constructOrthonormalBasis(up);
|
||||
|
||||
return orthnormalBasis.vectors[0].scale(hemisphereSample.z)
|
||||
.add(orthnormalBasis.vectors[1].scale(hemisphereSample.x))
|
||||
.add(orthnormalBasis.vectors[2].scale(hemisphereSample.y));
|
||||
}
|
||||
|
||||
public static Vector sampleBRDF(Vector normal, Vector incoming, double roughness) {
|
||||
Vector up = incoming.reflect(normal).lerp(normal, roughness);
|
||||
|
||||
Vector hemisphereSample = BDF.cosineHemisphere(roughness);
|
||||
|
||||
var orthnormalBasis = Basis.constructOrthonormalBasis(up);
|
||||
|
||||
return orthnormalBasis.vectors[0].scale(hemisphereSample.z)
|
||||
.add(orthnormalBasis.vectors[1].scale(hemisphereSample.x))
|
||||
.add(orthnormalBasis.vectors[2].scale(hemisphereSample.y));
|
||||
}
|
||||
|
||||
public static Vector cosineHemisphere(double maximumAngle) {
|
||||
double u1 = Math.random();
|
||||
double u2 = Math.random() * maximumAngle;
|
||||
|
||||
double r = Math.sqrt(u1);
|
||||
double theta = 2 * Math.PI * u2;
|
||||
|
||||
double x = r * Math.cos(theta);
|
||||
double y = r * Math.sin(theta);
|
||||
|
||||
return new Vector(x, y, Math.sqrt(1 - u1));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package pathtracing.bdf;
|
||||
|
||||
import basics.math.algebra.Basis;
|
||||
import basics.math.algebra.Vector;
|
||||
|
||||
public class BRDF {
|
||||
|
||||
public static Vector sampleDirection(Vector incoming, Vector normal, double roughness) {
|
||||
Vector hemisphereSample = BDF.cosineHemisphere(roughness);
|
||||
|
||||
var direction = incoming.reflect(normal).lerp(normal, roughness);
|
||||
|
||||
var orthnormalBasis = Basis.constructOrthonormalBasis(direction);
|
||||
|
||||
return orthnormalBasis.vectors[0].scale(hemisphereSample.z)
|
||||
.add(orthnormalBasis.vectors[1].scale(hemisphereSample.x))
|
||||
.add(orthnormalBasis.vectors[2].scale(hemisphereSample.y));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package pathtracing.bdf;
|
||||
|
||||
import basics.math.algebra.Basis;
|
||||
import basics.math.algebra.Vector;
|
||||
|
||||
public class BTDF {
|
||||
|
||||
public static Vector sampleDirection(Vector incoming, Vector normal, double roughness, double ior) {
|
||||
Vector hemisphereSample = BDF.cosineHemisphere(roughness);
|
||||
|
||||
var direction = Vector.refract(normal, incoming, ior)
|
||||
.lerp(normal.negate(), roughness);
|
||||
|
||||
var orthnormalBasis = Basis.constructOrthonormalBasis(direction);
|
||||
|
||||
return orthnormalBasis.vectors[0].scale(hemisphereSample.z)
|
||||
.add(orthnormalBasis.vectors[1].scale(hemisphereSample.x))
|
||||
.add(orthnormalBasis.vectors[2].scale(hemisphereSample.y));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package pathtracing.pbr;
|
||||
|
||||
import basics.math.algebra.Basis;
|
||||
import basics.math.algebra.Ray;
|
||||
import basics.math.algebra.Vector;
|
||||
import optics.light.Color;
|
||||
import pathtracing.Pathtracer;
|
||||
import pathtracing.bdf.BDF;
|
||||
|
||||
public class Dielectric extends PhysicallyBasedMaterial {
|
||||
|
||||
private final Color reflectance;
|
||||
private final double roughness;
|
||||
private final boolean transmissive;
|
||||
private final double ior;
|
||||
|
||||
public Dielectric(Color reflectance, double roughness, boolean transmissive, double ior) {
|
||||
this.reflectance = reflectance;
|
||||
this.roughness = roughness;
|
||||
this.transmissive = transmissive;
|
||||
this.ior = ior;
|
||||
}
|
||||
|
||||
private Color schlickFresnel(double cosTheta) {
|
||||
return reflectance.lerp(Color.WHITE, Math.pow(1.0 - cosTheta, 5.0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color surfaceColor(Vector normal, Vector incident, Vector intersection, Pathtracer pathtracer, int depth) {
|
||||
var cosTheta = Math.abs(incident.dot(normal));
|
||||
|
||||
var schlick = schlickFresnel(cosTheta).scale(1.0 - roughness);
|
||||
|
||||
var reflectionRay = new Ray(intersection, BDF.sampleBRDF(normal, incident, roughness), 1e-3, 1e3);
|
||||
var reflectedColor = pathtracer.traceDirect(reflectionRay, depth);
|
||||
|
||||
//if (transmissive) {
|
||||
// var refractionRay = new Ray(intersection, BDF.sampleBTDF(normal, incident, roughness, ior), 1e-3, 1e3);
|
||||
// var refractionColor = reflectance.mul(pathtracer.traceDirect(refractionRay, depth));
|
||||
|
||||
// return refractionColor.lerp(reflectedColor, schlick);
|
||||
//} else {
|
||||
var diffuseRay = new Ray(intersection, BDF.sampleBRDF(normal, incident, 1.0), 1e-3, 1e3);
|
||||
var diffuseColor = reflectance.mul(pathtracer.traceDirect(diffuseRay, depth));
|
||||
|
||||
return diffuseColor.lerp(reflectedColor, schlick);
|
||||
// }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package pathtracing.pbr;
|
||||
|
||||
import basics.math.algebra.Vector;
|
||||
import optics.light.Color;
|
||||
import pathtracing.Pathtracer;
|
||||
|
||||
public class Emissive extends PhysicallyBasedMaterial {
|
||||
|
||||
private final Color emission;
|
||||
private final double strength;
|
||||
|
||||
public Emissive(Color emission, double strength) {
|
||||
this.emission = emission;
|
||||
this.strength = strength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color surfaceColor(Vector normal, Vector incident, Vector intersection, Pathtracer pathtracer, int depth) {
|
||||
return emission.scale(strength);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package pathtracing.pbr;
|
||||
|
||||
import basics.math.algebra.Vector;
|
||||
import optics.light.Color;
|
||||
import pathtracing.Pathtracer;
|
||||
import shading.Material;
|
||||
|
||||
public abstract class PhysicallyBasedMaterial implements Material {
|
||||
|
||||
public abstract Color surfaceColor(Vector normal, Vector incident, Vector intersection, Pathtracer pathtracer, int depth);
|
||||
}
|
|
@ -13,7 +13,7 @@ public class Scheduler implements AutoCloseable {
|
|||
|
||||
private static final Scheduler INSTANCE = new Scheduler();
|
||||
|
||||
private ExecutorService executorService = Executors.newCachedThreadPool();
|
||||
private final ExecutorService executorService = Executors.newCachedThreadPool();
|
||||
|
||||
private Scheduler() {
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ public class Scheduler implements AutoCloseable {
|
|||
int tileWidth = buffer.getWidth() / THREADS_PER_SIDE;
|
||||
int tileHeight = buffer.getHeight() / THREADS_PER_SIDE;
|
||||
|
||||
ArrayList processors = new ArrayList(totalThreads);
|
||||
var processors = new ArrayList(totalThreads);
|
||||
|
||||
for (int x = 0; x < THREADS_PER_SIDE; x++) {
|
||||
int x0 = x * tileWidth;
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
package renderer.canvas;
|
||||
|
||||
import basics.math.algebra.Vector;
|
||||
import optics.light.Color;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ContributionBuffer {
|
||||
|
||||
// linear rgb colors
|
||||
private volatile Color[][] buffer;
|
||||
private final Color[][] buffer;
|
||||
// amount of samples for every pixel
|
||||
private volatile double[][] samples;
|
||||
private final double[][] samples;
|
||||
|
||||
private int width;
|
||||
private int height;
|
||||
private final int width;
|
||||
private final int height;
|
||||
|
||||
private int maxSample;
|
||||
|
||||
|
@ -22,15 +23,17 @@ public class ContributionBuffer {
|
|||
this.buffer = new Color[width][height];
|
||||
this.samples = new double[width][height];
|
||||
|
||||
for (int i = 0; i < width; i++) {
|
||||
Arrays.fill(this.samples[i], 1.0);
|
||||
}
|
||||
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
this.buffer[x][y] = Color.BLACK;
|
||||
}
|
||||
Arrays.fill(this.buffer[x], Color.BLACK);
|
||||
}
|
||||
}
|
||||
|
||||
public void contribute(int x, int y, Color color) {
|
||||
buffer[x][y] = buffer[x][y].add(color);
|
||||
buffer[x][y] = buffer[x][y].lerp(color, 1.0 / samples[x][y]);
|
||||
samples[x][y]++;
|
||||
|
||||
maxSample = Math.max(maxSample, (int) samples[x][y]);
|
||||
|
@ -47,15 +50,13 @@ public class ContributionBuffer {
|
|||
Color color = buffer[x][y];
|
||||
color.r = color.g = color.b = 0;
|
||||
// reset sample count
|
||||
samples[x][y] = 0;
|
||||
samples[x][y] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getRGB(int x, int y) {
|
||||
Color linearRGB = buffer[x][y].scale(1.0 / samples[x][y]);
|
||||
|
||||
linearRGB.colorCorrect();
|
||||
Color linearRGB = buffer[x][y];
|
||||
|
||||
int red = (int) (linearRGB.r* 255.0);
|
||||
int green = (int) (linearRGB.g * 255.0);
|
||||
|
|
Reference in New Issue