diff --git a/src/xyz/marsavic/gfxlab/graphics3d/Material.java b/src/xyz/marsavic/gfxlab/graphics3d/Material.java index 1d00e3c..b54f58d 100644 --- a/src/xyz/marsavic/gfxlab/graphics3d/Material.java +++ b/src/xyz/marsavic/gfxlab/graphics3d/Material.java @@ -8,21 +8,23 @@ import xyz.marsavic.gfxlab.SplineSpectrum; public record Material( Spectrum diffuse, Spectrum specular, + Spectrum emissive, double shininess, Spectrum reflective, Spectrum refractive, Spectrum refractiveIndex ) { - public Material diffuse (Spectrum diffuse ) { return new Material(diffuse, specular, shininess, reflective, refractive, refractiveIndex); } - public Material specular (Spectrum specular ) { return new Material(diffuse, specular, shininess, reflective, refractive, refractiveIndex); } - public Material shininess (double shininess ) { return new Material(diffuse, specular, shininess, reflective, refractive, refractiveIndex); } - public Material reflective (Spectrum reflective ) { return new Material(diffuse, specular, shininess, reflective, refractive, refractiveIndex); } - public Material refractive (Spectrum refractive ) { return new Material(diffuse, specular, shininess, reflective, refractive, refractiveIndex); } - public Material refractiveIndex(Spectrum refractiveIndex) { 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, emissive, shininess, reflective, refractive, refractiveIndex); } + public Material emissive (Spectrum emissive ) { return new Material(diffuse, specular, emissive, shininess, reflective, refractive, refractiveIndex); } + public Material shininess (double shininess ) { return new Material(diffuse, specular, emissive, shininess, reflective, refractive, refractiveIndex); } + public Material reflective (Spectrum reflective ) { return new Material(diffuse, specular, emissive, 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 - - 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 (double k) { return matte(w -> k); } @@ -39,4 +41,6 @@ public record Material( 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 of BaK4 crown glass*/ + + public static Material light (Spectrum s) { return BLACK.emissive(s); } } diff --git a/src/xyz/marsavic/gfxlab/graphics3d/raytracers/RayTracerSimple.java b/src/xyz/marsavic/gfxlab/graphics3d/raytracers/RayTracerSimple.java index e391fce..3f995a0 100644 --- a/src/xyz/marsavic/gfxlab/graphics3d/raytracers/RayTracerSimple.java +++ b/src/xyz/marsavic/gfxlab/graphics3d/raytracers/RayTracerSimple.java @@ -1,9 +1,11 @@ package xyz.marsavic.gfxlab.graphics3d.raytracers; +import xyz.marsavic.geometry.Vec; import xyz.marsavic.gfxlab.Color; import xyz.marsavic.gfxlab.Vec3; import xyz.marsavic.gfxlab.graphics3d.*; import xyz.marsavic.random.RNG; +import xyz.marsavic.utils.Numeric; public class RayTracerSimple extends RayTracer { @@ -11,6 +13,8 @@ public class RayTracerSimple extends RayTracer { private static final double minWavelength = 380; private static final double maxWavelength = 780; 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) { super(scene, camera); @@ -20,86 +24,70 @@ public class RayTracerSimple extends RayTracer { @Override protected Color sample(Ray ray) { - Color s = Color.BLACK; - int n = 5; - for (int i = 0; i < n; i++) { - s = s.add(sample(ray, 64)); - } - return s.div(n); + Color result = Color.BLACK; + for (int i = 0; i < sampleNumber; i++) { + double x = 0.0, y = 0.0, z = 0.0; + for (int j = 0; j < spectrumSamples; j++) { + double wavelength = minWavelength + (((double) j + rng.nextDouble()) / spectrumSamples) * (maxWavelength - minWavelength); + 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) { - double x = 0.0, y = 0.0, z = 0.0; - for (int i = 0; i < spectrumSamples; i++) { - double wavelength = minWavelength + (((double) i + rng.nextDouble())/spectrumSamples)*(maxWavelength - minWavelength); - double intensity = sample(ray, depthRemaining, wavelength); - - x += intensity * Xyz.x[(int) wavelength]; - 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; + protected double sample(Ray ray, double wavelength, int depthRemaining) { + double returnFactor = 1.0; + if (depthRemaining <= 0) { + if (rng.nextDouble(1.0) <= stopProbability) { + return 0.0; + } else { + returnFactor = 1/(1-stopProbability); + } } Hit hit = scene.solid().firstHit(ray, EPSILON); if (hit == null) { return scene.backgroundSpectrum.at(wavelength); } - + Vec3 p = ray.at(hit.t()); // The hit point Vec3 n_ = hit.n_(); // Normalized normal to the body surface at the hit point Vec3 i = ray.d().inverse(); // Incoming direction double lI = i.length(); Vec3 r = GeometryUtils.reflectedN(n_, i); // 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(); - - for (Light light : scene.lights()) { - Vec3 l = light.p().sub(p); // Vector from p to the light; - - Ray rayToLight = Ray.pd(p, l); - if (scene.solid().hitBetween(rayToLight, EPSILON, 1)) continue; - - double lLSqr = l.lengthSquared(); // Distance from p to the light squared - double lL = Math.sqrt(lLSqr); // Distance from p to the light - double cosLN = n_.dot(l) / lL; // Cosine of the angle between l and n_ - - if (cosLN > 0) { // If the light is above the surface - double irradiance = light.s().at(wavelength) * cosLN / lLSqr; - // The irradiance represents how much light is received by a unit area of the surface. It is - // proportional to the cosine of the incoming angle and inversely proportional to the distance squared - // (inverse-square law). - 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 += material.diffuse().at(wavelength) * lightDiffuse; - result += material.specular().at(wavelength) * lightSpecular; + Material material = hit.material(); + + double lightDiffuse = 0.0; // The diffuse contribution of the generated path + 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 + + double result = lightEmissive; + + double diffuse = material.diffuse().at(wavelength); + if (diffuse != 0.0) { + 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_ + Vec3 u_ = GeometryUtils.normal(n_).normalized_(); // One of the normalized vectors normal to n + Vec3 v_ = n_.cross(u_); // Doesn't need to be normalized because n_ and u_ are normalized and normal + Vec3 o = u_.mul(Numeric.sinT(r1)).add(v_.mul(Numeric.cosT(r1))) + .mul(Numeric.cosT(r2)).add(n_.mul(Numeric.sinT(r2))); // Outgoing sample vector + lightDiffuse = diffuse * sample(Ray.pd(p, o), wavelength, depthRemaining - 1); + } + + result += lightDiffuse; double reflective = material.reflective().at(wavelength); if (reflective != 0.0) { - // When material has reflective properties we recursively find the color visible along the ray (p, r). - double lightReflected = sample(Ray.pd(p, r), depthRemaining - 1, wavelength); + lightReflected = sample(Ray.pd(p, r), wavelength, depthRemaining - 1); result += reflective * lightReflected; } @@ -126,10 +114,10 @@ public class RayTracerSimple extends RayTracer { } 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; } - - return result; + + return returnFactor * result; } } diff --git a/src/xyz/marsavic/gfxlab/graphics3d/scene/SpectrumTest.java b/src/xyz/marsavic/gfxlab/graphics3d/scene/SpectrumTest.java index f10bfbe..6d6b5ab 100644 --- a/src/xyz/marsavic/gfxlab/graphics3d/scene/SpectrumTest.java +++ b/src/xyz/marsavic/gfxlab/graphics3d/scene/SpectrumTest.java @@ -1,20 +1,15 @@ package xyz.marsavic.gfxlab.graphics3d.scene; -import javafx.util.Pair; import xyz.marsavic.functions.interfaces.F1; import xyz.marsavic.geometry.Vector; -import xyz.marsavic.gfxlab.Color; -import xyz.marsavic.gfxlab.Spectrum; import xyz.marsavic.gfxlab.SplineSpectrum; import xyz.marsavic.gfxlab.Vec3; -import xyz.marsavic.gfxlab.graphics3d.Light; -import xyz.marsavic.gfxlab.graphics3d.Material; -import xyz.marsavic.gfxlab.graphics3d.Scene; -import xyz.marsavic.gfxlab.graphics3d.Solid; +import xyz.marsavic.gfxlab.graphics3d.*; import xyz.marsavic.gfxlab.graphics3d.solids.Ball; import xyz.marsavic.gfxlab.graphics3d.solids.Group; import xyz.marsavic.gfxlab.graphics3d.solids.HalfSpace; +import xyz.marsavic.gfxlab.graphics3d.solids.Parallelepiped; import xyz.marsavic.gfxlab.graphics3d.textures.Grid; import java.util.ArrayList; @@ -25,28 +20,42 @@ public class SpectrumTest extends Scene.Base { public SpectrumTest() { var materialUVWalls = (F1) (uv -> Material.matte(1.0)); - SplineSpectrum s; + var materialBlocks = (F1) (uv -> Material.matte(0.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) (uv -> Material.MIRROR); + + var materialGlass = (F1) (uv -> Material.GLASS + .refractiveIndex(w -> 5.6 + (w-400)/(800-400) * (1.55 - 5.6))); + var materialMirror = (F1) (uv -> Material.MIRROR); + + var materialLight = (F1) (uv -> Material.light(w -> 1.0)); Collection solids = new ArrayList<>(); Collections.addAll(solids, - HalfSpace.pn(Vec3.xyz(1, 0, 10), Vec3.xyz( 1, 0, -1), materialUVWallsL), - HalfSpace.pn(Vec3.xyz( 0, 0, 10), Vec3.xyz(-1, 0, -1), materialUVWallsR), - HalfSpace.pn(Vec3.xyz(0, 2, 0), Vec3.xyz( 0, -1, 0), materialUVWallsL), - HalfSpace.pn(Vec3.xyz(0, -2, 10), Vec3.xyz( 0, 1, 0), materialUVWallsL), - - HalfSpace.pn(Vec3.xyz( 0, 0, 8), Vec3.xyz(0.3, 1, -1), uv -> Material.GLASS.refractiveIndex( - w -> 5.6 + (w-400)/(800-400) * (1.55 - 5.6) - - )), - HalfSpace.pn(Vec3.xyz( 0, 0, -6), Vec3.xyz( 0, 0, 1), materialUVWallsR) - ); - - Collections.addAll(lights, - Light.ps(Vec3.xyz(0, 1.0, -1.0), Spectrum.WHITE), - Light.ps(Vec3.xyz(0, -1.0, 8.0), Spectrum.WHITE) + HalfSpace.pn(Vec3.xyz(-1, 0, 0), Vec3.xyz( 1, 0, 0), materialUVWallsL), + HalfSpace.pn(Vec3.xyz( 1, 0, 0), Vec3.xyz(-1, 0, 0), materialUVWallsR), + HalfSpace.pn(Vec3.xyz( 0, -1, 0), Vec3.xyz( 0, 1, 0), materialUVWalls), +// 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, -6), Vec3.xyz( 0, 0, 1), materialUVWallsB), + Ball.cr(Vec3.xyz(0, 3, 0), 0.8, materialLight), + Parallelepiped.pabc(Vec3.xyz(0, 0, 0), + Vec3.xyz(0.4, 0, 0), + Vec3.xyz(0, 0.4, 0), + Vec3.xyz(0, 0, 6), + materialGlass), + Parallelepiped.pabc(Vec3.xyz(-5.5 , 1, 1), + Vec3.xyz(4.5, 0, 0), + 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);