Implement path tracer

This commit is contained in:
kappa 2023-10-07 13:49:28 +02:00
parent bae05d4df5
commit 5dc461055e
3 changed files with 98 additions and 97 deletions

View file

@ -8,21 +8,23 @@ import xyz.marsavic.gfxlab.SplineSpectrum;
public record Material( public record Material(
Spectrum diffuse, Spectrum diffuse,
Spectrum specular, Spectrum specular,
Spectrum emissive,
double shininess, double shininess,
Spectrum reflective, Spectrum reflective,
Spectrum refractive, Spectrum refractive,
Spectrum refractiveIndex Spectrum refractiveIndex
) { ) {
public Material diffuse (Spectrum diffuse ) { return new Material(diffuse, specular, shininess, reflective, refractive, refractiveIndex); } public Material diffuse (Spectrum diffuse ) { return new Material(diffuse, specular, emissive, shininess, reflective, refractive, refractiveIndex); }
public Material specular (Spectrum specular ) { return new Material(diffuse, specular, shininess, reflective, refractive, refractiveIndex); } public Material specular (Spectrum specular ) { return new Material(diffuse, specular, emissive, shininess, reflective, refractive, refractiveIndex); }
public Material shininess (double shininess ) { return new Material(diffuse, specular, shininess, reflective, refractive, refractiveIndex); } public Material emissive (Spectrum emissive ) { return new Material(diffuse, specular, emissive, shininess, reflective, refractive, refractiveIndex); }
public Material reflective (Spectrum reflective ) { return new Material(diffuse, specular, shininess, reflective, refractive, refractiveIndex); } public Material shininess (double shininess ) { return new Material(diffuse, specular, emissive, shininess, reflective, refractive, refractiveIndex); }
public Material refractive (Spectrum refractive ) { return new Material(diffuse, specular, shininess, reflective, refractive, refractiveIndex); } public Material reflective (Spectrum reflective ) { return new Material(diffuse, specular, emissive, shininess, reflective, refractive, refractiveIndex); }
public Material refractiveIndex(Spectrum refractiveIndex) { return new Material(diffuse, specular, shininess, reflective, refractive, refractiveIndex); } public Material refractive (Spectrum refractive ) { return new Material(diffuse, specular, emissive, shininess, reflective, refractive, refractiveIndex); }
public Material refractiveIndex(Spectrum refractiveIndex) { return new Material(diffuse, specular, emissive, shininess, reflective, refractive, refractiveIndex); }
// Since refractive index is a function from wavelength to a real number, it can be viewed as a spectrum // Since refractive index is a function from wavelength to a real number, it can be viewed as a spectrum
public static final Material BLACK = new Material(w -> 0, w -> 0, 32, w -> 0, w -> 0, w -> 1.5); public static final Material BLACK = new Material(w -> 0, w -> 0, w -> 0, 32, w -> 0, w -> 0, w -> 1.5);
public static Material matte (Spectrum s) { return BLACK.diffuse(s); } public static Material matte (Spectrum s) { return BLACK.diffuse(s); }
public static Material matte (double k) { return matte(w -> k); } public static Material matte (double k) { return matte(w -> k); }
@ -39,4 +41,6 @@ public record Material(
public static final Material GLASS = BLACK.refractive(w -> 1.0) public static final Material GLASS = BLACK.refractive(w -> 1.0)
.refractiveIndex(w -> 1.6 + (w-400)/(800-400) * (1.55 - 1.6)); /* Made to roughly resemble refractive index .refractiveIndex(w -> 1.6 + (w-400)/(800-400) * (1.55 - 1.6)); /* Made to roughly resemble refractive index
of BaK4 crown glass*/ of BaK4 crown glass*/
public static Material light (Spectrum s) { return BLACK.emissive(s); }
} }

View file

@ -1,9 +1,11 @@
package xyz.marsavic.gfxlab.graphics3d.raytracers; package xyz.marsavic.gfxlab.graphics3d.raytracers;
import xyz.marsavic.geometry.Vec;
import xyz.marsavic.gfxlab.Color; import xyz.marsavic.gfxlab.Color;
import xyz.marsavic.gfxlab.Vec3; import xyz.marsavic.gfxlab.Vec3;
import xyz.marsavic.gfxlab.graphics3d.*; import xyz.marsavic.gfxlab.graphics3d.*;
import xyz.marsavic.random.RNG; import xyz.marsavic.random.RNG;
import xyz.marsavic.utils.Numeric;
public class RayTracerSimple extends RayTracer { public class RayTracerSimple extends RayTracer {
@ -11,6 +13,8 @@ public class RayTracerSimple extends RayTracer {
private static final double minWavelength = 380; private static final double minWavelength = 380;
private static final double maxWavelength = 780; private static final double maxWavelength = 780;
private static final double EPSILON = 1e-9; private static final double EPSILON = 1e-9;
private static final int sampleNumber = 4;
private static final double stopProbability = 1.0;
public RayTracerSimple(Scene scene, Camera camera) { public RayTracerSimple(Scene scene, Camera camera) {
super(scene, camera); super(scene, camera);
@ -20,34 +24,31 @@ public class RayTracerSimple extends RayTracer {
@Override @Override
protected Color sample(Ray ray) { protected Color sample(Ray ray) {
Color s = Color.BLACK; Color result = Color.BLACK;
int n = 5; for (int i = 0; i < sampleNumber; i++) {
for (int i = 0; i < n; i++) { double x = 0.0, y = 0.0, z = 0.0;
s = s.add(sample(ray, 64)); for (int j = 0; j < spectrumSamples; j++) {
} double wavelength = minWavelength + (((double) j + rng.nextDouble()) / spectrumSamples) * (maxWavelength - minWavelength);
return s.div(n); double intensity = sample(ray, wavelength, 7);
x += intensity * Xyz.x[(int) wavelength];
y += intensity * Xyz.y[(int) wavelength];
z += intensity * Xyz.z[(int) wavelength];
}
result = result.add(Color.xyz(x, y, z));
}
return result.div(sampleNumber);
} }
protected Color sample(Ray ray, int depthRemaining) { protected double sample(Ray ray, double wavelength, int depthRemaining) {
double x = 0.0, y = 0.0, z = 0.0; double returnFactor = 1.0;
for (int i = 0; i < spectrumSamples; i++) { if (depthRemaining <= 0) {
double wavelength = minWavelength + (((double) i + rng.nextDouble())/spectrumSamples)*(maxWavelength - minWavelength); if (rng.nextDouble(1.0) <= stopProbability) {
double intensity = sample(ray, depthRemaining, wavelength); return 0.0;
} else {
x += intensity * Xyz.x[(int) wavelength]; returnFactor = 1/(1-stopProbability);
y += intensity * Xyz.y[(int) wavelength]; }
z += intensity * Xyz.z[(int) wavelength];
}
Color result = Color.xyz(x,y,z);
return result;
}
protected double sample(Ray ray, int depthRemaining, double wavelength) {
if (depthRemaining == 0) {
return 0.0;
} }
Hit hit = scene.solid().firstHit(ray, EPSILON); Hit hit = scene.solid().firstHit(ray, EPSILON);
@ -62,44 +63,31 @@ public class RayTracerSimple extends RayTracer {
Vec3 r = GeometryUtils.reflectedN(n_, i); // Reflected ray (i reflected over n) Vec3 r = GeometryUtils.reflectedN(n_, i); // Reflected ray (i reflected over n)
Vec3 r_ = r.div(lI); // Reflected ray (i reflected over n) Vec3 r_ = r.div(lI); // Reflected ray (i reflected over n)
double lightDiffuse = 0.0; // The sum of diffuse contributions from all the lights
double lightSpecular = 0.0; // The sum of specular contributions from all the lights
Material material = hit.material(); Material material = hit.material();
for (Light light : scene.lights()) { double lightDiffuse = 0.0; // The diffuse contribution of the generated path
Vec3 l = light.p().sub(p); // Vector from p to the light; double lightSpecular = 0.0; // The specular contribution of the generated path
double lightReflected = 0.0; // The reflective contribution of the generated path
double lightRefracted = 0.0; // The refractive contribution of the generated path
double lightEmissive = material.emissive().at(wavelength); // The contribution of the light surface itself
Ray rayToLight = Ray.pd(p, l); double result = lightEmissive;
if (scene.solid().hitBetween(rayToLight, EPSILON, 1)) continue;
double lLSqr = l.lengthSquared(); // Distance from p to the light squared double diffuse = material.diffuse().at(wavelength);
double lL = Math.sqrt(lLSqr); // Distance from p to the light if (diffuse != 0.0) {
double cosLN = n_.dot(l) / lL; // Cosine of the angle between l and n_ double r1 = rng.nextDouble(1.0); // Angle of projected random vector in the plain normal to n_
double r2 = rng.nextDouble(0.25); // Angle of vector compared to n_
if (cosLN > 0) { // If the light is above the surface Vec3 u_ = GeometryUtils.normal(n_).normalized_(); // One of the normalized vectors normal to n
double irradiance = light.s().at(wavelength) * cosLN / lLSqr; Vec3 v_ = n_.cross(u_); // Doesn't need to be normalized because n_ and u_ are normalized and normal
// The irradiance represents how much light is received by a unit area of the surface. It is Vec3 o = u_.mul(Numeric.sinT(r1)).add(v_.mul(Numeric.cosT(r1)))
// proportional to the cosine of the incoming angle and inversely proportional to the distance squared .mul(Numeric.cosT(r2)).add(n_.mul(Numeric.sinT(r2))); // Outgoing sample vector
// (inverse-square law). lightDiffuse = diffuse * sample(Ray.pd(p, o), wavelength, depthRemaining - 1);
lightDiffuse = lightDiffuse+irradiance;
double cosLR = l.dot(r_);
if (cosLR > 0) { // If the angle between l and r is acute
cosLR /= lL;
lightSpecular = lightSpecular + irradiance * Math.pow(cosLR, material.shininess());
}
}
} }
double result = 0.0; result += lightDiffuse;
result += material.diffuse().at(wavelength) * lightDiffuse;
result += material.specular().at(wavelength) * lightSpecular;
double reflective = material.reflective().at(wavelength); double reflective = material.reflective().at(wavelength);
if (reflective != 0.0) { if (reflective != 0.0) {
// When material has reflective properties we recursively find the color visible along the ray (p, r). lightReflected = sample(Ray.pd(p, r), wavelength, depthRemaining - 1);
double lightReflected = sample(Ray.pd(p, r), depthRemaining - 1, wavelength);
result += reflective * lightReflected; result += reflective * lightReflected;
} }
@ -126,10 +114,10 @@ public class RayTracerSimple extends RayTracer {
} }
b = bRejection.add(bProjection); b = bRejection.add(bProjection);
} }
double lightRefracted = sample(Ray.pd(p, b), depthRemaining - 1, wavelength); lightRefracted = sample(Ray.pd(p, b), wavelength, depthRemaining - 1);
result += refractive * lightRefracted; result += refractive * lightRefracted;
} }
return result; return returnFactor * result;
} }
} }

View file

@ -1,20 +1,15 @@
package xyz.marsavic.gfxlab.graphics3d.scene; package xyz.marsavic.gfxlab.graphics3d.scene;
import javafx.util.Pair;
import xyz.marsavic.functions.interfaces.F1; import xyz.marsavic.functions.interfaces.F1;
import xyz.marsavic.geometry.Vector; import xyz.marsavic.geometry.Vector;
import xyz.marsavic.gfxlab.Color;
import xyz.marsavic.gfxlab.Spectrum;
import xyz.marsavic.gfxlab.SplineSpectrum; import xyz.marsavic.gfxlab.SplineSpectrum;
import xyz.marsavic.gfxlab.Vec3; import xyz.marsavic.gfxlab.Vec3;
import xyz.marsavic.gfxlab.graphics3d.Light; import xyz.marsavic.gfxlab.graphics3d.*;
import xyz.marsavic.gfxlab.graphics3d.Material;
import xyz.marsavic.gfxlab.graphics3d.Scene;
import xyz.marsavic.gfxlab.graphics3d.Solid;
import xyz.marsavic.gfxlab.graphics3d.solids.Ball; import xyz.marsavic.gfxlab.graphics3d.solids.Ball;
import xyz.marsavic.gfxlab.graphics3d.solids.Group; import xyz.marsavic.gfxlab.graphics3d.solids.Group;
import xyz.marsavic.gfxlab.graphics3d.solids.HalfSpace; import xyz.marsavic.gfxlab.graphics3d.solids.HalfSpace;
import xyz.marsavic.gfxlab.graphics3d.solids.Parallelepiped;
import xyz.marsavic.gfxlab.graphics3d.textures.Grid; import xyz.marsavic.gfxlab.graphics3d.textures.Grid;
import java.util.ArrayList; import java.util.ArrayList;
@ -25,28 +20,42 @@ public class SpectrumTest extends Scene.Base {
public SpectrumTest() { public SpectrumTest() {
var materialUVWalls = (F1<Material, Vector>) (uv -> Material.matte(1.0)); var materialUVWalls = (F1<Material, Vector>) (uv -> Material.matte(1.0));
SplineSpectrum s; var materialBlocks = (F1<Material, Vector>) (uv -> Material.matte(0.0));
var materialUVWallsL = Grid.standard(w -> 1.0); var materialUVWallsL = Grid.standard(w -> 1.0);
var materialUVWallsB = Grid.standard(w -> 1.0);
var materialUVWallsR = Grid.standard(w -> 0.1); var materialUVWallsR = (F1<Material, Vector>) (uv -> Material.MIRROR);
var materialGlass = (F1<Material, Vector>) (uv -> Material.GLASS
.refractiveIndex(w -> 5.6 + (w-400)/(800-400) * (1.55 - 5.6)));
var materialMirror = (F1<Material, Vector>) (uv -> Material.MIRROR);
var materialLight = (F1<Material, Vector>) (uv -> Material.light(w -> 1.0));
Collection<Solid> solids = new ArrayList<>(); Collection<Solid> solids = new ArrayList<>();
Collections.addAll(solids, Collections.addAll(solids,
HalfSpace.pn(Vec3.xyz(1, 0, 10), Vec3.xyz( 1, 0, -1), materialUVWallsL), HalfSpace.pn(Vec3.xyz(-1, 0, 0), Vec3.xyz( 1, 0, 0), materialUVWallsL),
HalfSpace.pn(Vec3.xyz( 0, 0, 10), Vec3.xyz(-1, 0, -1), materialUVWallsR), HalfSpace.pn(Vec3.xyz( 1, 0, 0), Vec3.xyz(-1, 0, 0), materialUVWallsR),
HalfSpace.pn(Vec3.xyz(0, 2, 0), Vec3.xyz( 0, -1, 0), materialUVWallsL), HalfSpace.pn(Vec3.xyz( 0, -1, 0), Vec3.xyz( 0, 1, 0), materialUVWalls),
HalfSpace.pn(Vec3.xyz(0, -2, 10), Vec3.xyz( 0, 1, 0), materialUVWallsL), // HalfSpace.pn(Vec3.xyz( 0, 1, 0), Vec3.xyz( 0, -1, 0), materialUVWalls),
HalfSpace.pn(Vec3.xyz( 0, 0, 1), Vec3.xyz( 0, 0, -1), materialUVWallsB),
HalfSpace.pn(Vec3.xyz( 0, 0, 8), Vec3.xyz(0.3, 1, -1), uv -> Material.GLASS.refractiveIndex( HalfSpace.pn(Vec3.xyz( 0, 0, -6), Vec3.xyz( 0, 0, 1), materialUVWallsB),
w -> 5.6 + (w-400)/(800-400) * (1.55 - 5.6) Ball.cr(Vec3.xyz(0, 3, 0), 0.8, materialLight),
Parallelepiped.pabc(Vec3.xyz(0, 0, 0),
)), Vec3.xyz(0.4, 0, 0),
HalfSpace.pn(Vec3.xyz( 0, 0, -6), Vec3.xyz( 0, 0, 1), materialUVWallsR) Vec3.xyz(0, 0.4, 0),
); Vec3.xyz(0, 0, 6),
materialGlass),
Collections.addAll(lights, Parallelepiped.pabc(Vec3.xyz(-5.5 , 1, 1),
Light.ps(Vec3.xyz(0, 1.0, -1.0), Spectrum.WHITE), Vec3.xyz(4.5, 0, 0),
Light.ps(Vec3.xyz(0, -1.0, 8.0), Spectrum.WHITE) Vec3.xyz(0, 0.4, 0),
Vec3.xyz(0, 0, -6),
materialGlass),
Parallelepiped.pabc(Vec3.xyz(0.5, 1, 1),
Vec3.xyz(4.5, 0, 0),
Vec3.xyz(0, 0.4, 0),
Vec3.xyz(0, 0, -6),
materialGlass)
); );
solid = Group.of(solids); solid = Group.of(solids);