diff --git a/README.md b/README.md index abb0831..5b228f5 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ - Ako koristite IntelliJ, ovo je lako namestiti: File > Project Structure... > Project > SDK > Add SDK > Download JDK... > Vendor: BellSoft Liberica JDK 19.0.1. - Alternativno, sami preuzmite JDK sa [https://bell-sw.com/pages/downloads/](https://bell-sw.com/pages/downloads/#/java-19-current). Izaberite vaš OS, poslednju verziju, i Full JDK (jedino Full JDK uključuje JavaFX). Kada instalirate/raspakujete JDK, namestite u IDE-u da projekat koristi baš taj JDK. - Ako nećete da koristite BellSoft Liberica JDK, snađite se da preuzmete odgovarajuće biblioteke na neki način (direktni download svih potrebnih jar-fajlova, Maven, ...). Potrebni su vam javafx-base, javafx-controls, javafx-graphics, i javafx-swing. + - U nekim slučajevima JavaFX neće koristiti GPU za iscrtavanje interfejsa i sve će biti pomalo laggy (meni se to dešava uz Linux i integrisani GPU). U tom slučaju (a ni inače verovatno ne može da škodi), dodajte system property `prism.forceGPU = true`, npr. kroz VM argument `-Dprism.forceGPU=true`. ## Šta-gde diff --git a/src/xyz/marsavic/gfxlab/graphics3d/Camera.java b/src/xyz/marsavic/gfxlab/graphics3d/Camera.java new file mode 100644 index 0000000..77e3582 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/graphics3d/Camera.java @@ -0,0 +1,8 @@ +package xyz.marsavic.gfxlab.graphics3d; + +import xyz.marsavic.geometry.Vector; + + +public interface Camera { + Ray exitingRay(Vector sensorPosition); +} diff --git a/src/xyz/marsavic/gfxlab/graphics3d/GeometryUtils.java b/src/xyz/marsavic/gfxlab/graphics3d/GeometryUtils.java index e627381..453b18e 100644 --- a/src/xyz/marsavic/gfxlab/graphics3d/GeometryUtils.java +++ b/src/xyz/marsavic/gfxlab/graphics3d/GeometryUtils.java @@ -2,8 +2,6 @@ package xyz.marsavic.gfxlab.graphics3d; import xyz.marsavic.gfxlab.Vec3; -import xyz.marsavic.random.sampling.Sampler; -import xyz.marsavic.utils.Numeric; public class GeometryUtils { @@ -26,4 +24,12 @@ public class GeometryUtils { } */ + public static Vec3 reflected(Vec3 n, Vec3 d) { + return n.mul(2 * d.dot(n) / n.lengthSquared()).sub(d); + } + + public static Vec3 reflectedN(Vec3 n_, Vec3 d) { + return n_.mul(2 * d.dot(n_)).sub(d); + } + } diff --git a/src/xyz/marsavic/gfxlab/graphics3d/Material.java b/src/xyz/marsavic/gfxlab/graphics3d/Material.java index ed3adb1..08878dd 100644 --- a/src/xyz/marsavic/gfxlab/graphics3d/Material.java +++ b/src/xyz/marsavic/gfxlab/graphics3d/Material.java @@ -3,14 +3,28 @@ package xyz.marsavic.gfxlab.graphics3d; import xyz.marsavic.gfxlab.Color; public record Material( - Color diffuse + Color diffuse, + Color specular, + double shininess, + Color reflective, + Color refractive, + double refractiveIndex + ) { - public Material diffuse(Color diffuse) { return new Material(diffuse); } + public Material diffuse (Color diffuse ) { return new Material(diffuse, specular, shininess, reflective, refractive, refractiveIndex); } + public Material specular (Color 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 (Color reflective ) { return new Material(diffuse, specular, shininess, reflective, refractive, refractiveIndex); } + public Material refractive (Color refractive ) { return new Material(diffuse, specular, shininess, reflective, refractive, refractiveIndex); } + public Material refractiveIndex(double refractiveIndex) { return new Material(diffuse, specular, shininess, reflective, refractive, refractiveIndex); } - public static final Material BLACK = new Material(Color.BLACK); + public static final Material BLACK = new Material(Color.BLACK, Color.BLACK, 32, Color.BLACK, Color.BLACK, 1.5); public static Material matte (Color c) { return BLACK.diffuse(c); } public static Material matte (double k) { return matte(Color.gray(k)); } public static Material matte ( ) { return matte(Color.WHITE); } public static final Material MATTE = matte(); + + public static final Material MIRROR = BLACK.reflective(Color.WHITE); + public static final Material GLASS = BLACK.refractive(Color.WHITE).refractiveIndex(1.5); } diff --git a/src/xyz/marsavic/gfxlab/graphics3d/Solid.java b/src/xyz/marsavic/gfxlab/graphics3d/Solid.java index b8bd81b..652cb6d 100644 --- a/src/xyz/marsavic/gfxlab/graphics3d/Solid.java +++ b/src/xyz/marsavic/gfxlab/graphics3d/Solid.java @@ -1,8 +1,5 @@ package xyz.marsavic.gfxlab.graphics3d; -import java.util.ArrayList; -import java.util.List; - public interface Solid { @@ -19,4 +16,12 @@ public interface Solid { return firstHit(ray, 0); } + + default boolean hitBetween(Ray ray, double afterTime, double beforeTime) { + Hit hit = firstHit(ray); + if (hit == null) return false; + double t = hit.t(); + return (afterTime < t) && (t < beforeTime); + } + } diff --git a/src/xyz/marsavic/gfxlab/graphics3d/cameras/Perspective.java b/src/xyz/marsavic/gfxlab/graphics3d/cameras/Perspective.java new file mode 100644 index 0000000..6396664 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/graphics3d/cameras/Perspective.java @@ -0,0 +1,23 @@ +package xyz.marsavic.gfxlab.graphics3d.cameras; + +import xyz.marsavic.geometry.Vector; +import xyz.marsavic.gfxlab.Vec3; +import xyz.marsavic.gfxlab.graphics3d.Camera; +import xyz.marsavic.gfxlab.graphics3d.Ray; +import xyz.marsavic.utils.Numeric; + + +public record Perspective( + double k +) implements Camera { + + public static Perspective fov(double angle) { + return new Perspective(Numeric.tanT(angle / 2)); + } + + + @Override + public Ray exitingRay(Vector p) { + return Ray.pd(Vec3.ZERO, Vec3.zp(1/k, p)); + } +} diff --git a/src/xyz/marsavic/gfxlab/graphics3d/cameras/TransformedCamera.java b/src/xyz/marsavic/gfxlab/graphics3d/cameras/TransformedCamera.java new file mode 100644 index 0000000..07cec9f --- /dev/null +++ b/src/xyz/marsavic/gfxlab/graphics3d/cameras/TransformedCamera.java @@ -0,0 +1,20 @@ +package xyz.marsavic.gfxlab.graphics3d.cameras; + +import xyz.marsavic.geometry.Vector; +import xyz.marsavic.gfxlab.Transformation; +import xyz.marsavic.gfxlab.graphics3d.Camera; +import xyz.marsavic.gfxlab.graphics3d.Ray; + + +public record TransformedCamera ( + Camera source, + Transformation transformation +) implements Camera { + + @Override + public Ray exitingRay(Vector sensorPosition) { + Ray ray = source.exitingRay(sensorPosition); + return transformation.at(ray); + } + +} diff --git a/src/xyz/marsavic/gfxlab/graphics3d/raytracers/RayTracer.java b/src/xyz/marsavic/gfxlab/graphics3d/raytracers/RayTracer.java index d853a83..164007c 100644 --- a/src/xyz/marsavic/gfxlab/graphics3d/raytracers/RayTracer.java +++ b/src/xyz/marsavic/gfxlab/graphics3d/raytracers/RayTracer.java @@ -3,7 +3,7 @@ package xyz.marsavic.gfxlab.graphics3d.raytracers; import xyz.marsavic.geometry.Vector; import xyz.marsavic.gfxlab.Color; import xyz.marsavic.gfxlab.ColorFunctionT; -import xyz.marsavic.gfxlab.Vec3; +import xyz.marsavic.gfxlab.graphics3d.Camera; import xyz.marsavic.gfxlab.graphics3d.Ray; import xyz.marsavic.gfxlab.graphics3d.Scene; @@ -11,10 +11,12 @@ import xyz.marsavic.gfxlab.graphics3d.Scene; public abstract class RayTracer implements ColorFunctionT { protected final Scene scene; + protected final Camera camera; - public RayTracer(Scene scene) { + public RayTracer(Scene scene, Camera camera) { this.scene = scene; + this.camera = camera; } @@ -22,7 +24,7 @@ public abstract class RayTracer implements ColorFunctionT { @Override public Color at(double t, Vector p) { - Ray ray = Ray.pd(Vec3.xyz(0, 0, -2.6), Vec3.zp(1.6, p)); + Ray ray = camera.exitingRay(p); return sample(ray); } diff --git a/src/xyz/marsavic/gfxlab/graphics3d/raytracers/RayTracerSimple.java b/src/xyz/marsavic/gfxlab/graphics3d/raytracers/RayTracerSimple.java index 9e65de6..cb42d9f 100644 --- a/src/xyz/marsavic/gfxlab/graphics3d/raytracers/RayTracerSimple.java +++ b/src/xyz/marsavic/gfxlab/graphics3d/raytracers/RayTracerSimple.java @@ -7,38 +7,75 @@ import xyz.marsavic.gfxlab.graphics3d.*; public class RayTracerSimple extends RayTracer { - public RayTracerSimple(Scene scene) { - super(scene); + private static final double EPSILON = 1e-9; + + public RayTracerSimple(Scene scene, Camera camera) { + super(scene, camera); } @Override - protected Color sample(Ray r) { - Hit hit = scene.solid().firstHit(r); + protected Color sample(Ray ray) { + return sample(ray, 64); + } + + protected Color sample(Ray ray, int depthRemaining) { + if (depthRemaining == 0) { + return Color.BLACK; + } + + Hit hit = scene.solid().firstHit(ray, EPSILON); if (hit == null) { return scene.colorBackground(); } - Vec3 p = r.at(hit.t()); // The hit point - Vec3 n_ = hit.n_(); // Normalized normal to the body surface at the hit point + 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) - Color lightDiffuse = Color.BLACK; // The sum of diffuse contributions from all the lights + Color lightDiffuse = Color.BLACK; // The sum of diffuse contributions from all the lights + Color lightSpecular = Color.BLACK; // 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; - 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_ + Vec3 l = light.p().sub(p); // Vector from p to the light; - if (cosLN > 0) { // If the light is above the surface + 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 Color irradiance = light.c().mul(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.add(irradiance); + + double cosLR = l.dot(r_); + if (cosLR > 0) { // If the angle between l and r is acute + cosLR /= lL; + lightSpecular = lightSpecular.add(irradiance.mul(Math.pow(cosLR, material.shininess()))); + } } } - return hit.material().diffuse().mul(lightDiffuse); + Color result = Color.BLACK; + result = result.add(material.diffuse ().mul(lightDiffuse )); + result = result.add(material.specular().mul(lightSpecular)); + + if (material.reflective().notZero()) { + // When material has reflective properties we recursively find the color visible along the ray (p, r). + Color lightReflected = sample(Ray.pd(p, r), depthRemaining - 1); + result = result.add(material.reflective().mul(lightReflected)); + } + + return result; } } diff --git a/src/xyz/marsavic/gfxlab/graphics3d/scene/DiscoRoom.java b/src/xyz/marsavic/gfxlab/graphics3d/scene/DiscoRoom.java new file mode 100644 index 0000000..6e54fc3 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/graphics3d/scene/DiscoRoom.java @@ -0,0 +1,58 @@ +package xyz.marsavic.gfxlab.graphics3d.scene; + +import xyz.marsavic.gfxlab.Color; +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.solids.Ball; +import xyz.marsavic.gfxlab.graphics3d.solids.Group; +import xyz.marsavic.gfxlab.graphics3d.solids.HalfSpace; +import xyz.marsavic.gfxlab.graphics3d.textures.Grid; +import xyz.marsavic.random.RNG; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + + +public class DiscoRoom extends Scene.Base { + + public DiscoRoom(int nBalls, int nLights, long seed) { + RNG rngBalls = new RNG(2*seed); + + var materialUVWalls = Grid.standard(Color.WHITE); + + Collection solids = new ArrayList<>(); + Collections.addAll(solids, + HalfSpace.pn(Vec3.xyz(-1, 0, 0), Vec3.xyz( 1, 0, 0), materialUVWalls), + HalfSpace.pn(Vec3.xyz( 1, 0, 0), Vec3.xyz(-1, 0, 0), materialUVWalls), + 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), materialUVWalls) + ); + + for (int i = 0; i < nBalls; i++) { + double hue = rngBalls.nextDouble(); + Material material = rngBalls.nextDouble() < 0.8 ? + Material.matte(Color.hsb(hue, 0.9, 0.9)).specular(Color.WHITE).shininess(16) : + Material.MIRROR; + + solids.add(Ball.cr(Vec3.random(rngBalls).ZOtoMP(), 0.2, uv -> material)); + } + + solid = Group.of(solids); + + + RNG rngLights = new RNG(2*seed + 1); + + for (int i = 0; i < nLights; i++) { + lights.add(Light.pc( + Vec3.random(rngLights).ZOtoMP(), + Color.hsb(rngLights.nextDouble(), 0.75, 1)) + ); + } + } + +} diff --git a/src/xyz/marsavic/gfxlab/graphics3d/scene/Mirrors.java b/src/xyz/marsavic/gfxlab/graphics3d/scene/Mirrors.java new file mode 100644 index 0000000..145ff0b --- /dev/null +++ b/src/xyz/marsavic/gfxlab/graphics3d/scene/Mirrors.java @@ -0,0 +1,52 @@ +package xyz.marsavic.gfxlab.graphics3d.scene; + +import xyz.marsavic.geometry.Vector; +import xyz.marsavic.gfxlab.Color; +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.solids.Ball; +import xyz.marsavic.gfxlab.graphics3d.solids.Group; +import xyz.marsavic.gfxlab.graphics3d.solids.HalfSpace; +import xyz.marsavic.gfxlab.graphics3d.textures.Grid; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + + +public class Mirrors extends Scene.Base { + + public Mirrors(int nBalls) { + var materialUVWalls = Grid.standard(Color.WHITE); + var materialUVWallsL = Grid.standard(Color.hsb(0.00, 0.5, 1.0)); + var materialUVWallsR = Grid.standard(Color.hsb(0.33, 0.5, 1.0)); + + Collection solids = new ArrayList<>(); + Collections.addAll(solids, + 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), materialUVWalls) + ); + + Collections.addAll(lights, + Light.pc(Vec3.xyz(-0.8, 0.8, -0.8), Color.WHITE), + Light.pc(Vec3.xyz(-0.8, 0.8, 0.8), Color.WHITE), + Light.pc(Vec3.xyz( 0.8, 0.8, -0.8), Color.WHITE), + Light.pc(Vec3.xyz( 0.8, 0.8, 0.8), Color.WHITE) + ); + + for (int i = 0; i < nBalls; i++) { + Vector c = Vector.polar(0.5, 1.0 * i / nBalls); + Ball ball = Ball.cr(Vec3.zp(0, c), 0.4, uv -> Material.MIRROR); + solids.add(ball); + } + + solid = Group.of(solids); + } + +} diff --git a/src/xyz/marsavic/gfxlab/graphics3d/scene/RefractionTest.java b/src/xyz/marsavic/gfxlab/graphics3d/scene/RefractionTest.java new file mode 100644 index 0000000..9525aa1 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/graphics3d/scene/RefractionTest.java @@ -0,0 +1,50 @@ +package xyz.marsavic.gfxlab.graphics3d.scene; + +import xyz.marsavic.gfxlab.Color; +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.solids.Ball; +import xyz.marsavic.gfxlab.graphics3d.solids.Group; +import xyz.marsavic.gfxlab.graphics3d.solids.HalfSpace; +import xyz.marsavic.gfxlab.graphics3d.textures.Grid; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + + +public class RefractionTest extends Scene.Base { + + public RefractionTest() { + var materialUVWalls = Grid.standard(Color.WHITE); + var materialUVWallsL = Grid.standard(Color.hsb(0.00, 0.5, 1.0)); + var materialUVWallsR = Grid.standard(Color.hsb(0.33, 0.5, 1.0)); + + Collection solids = new ArrayList<>(); + Collections.addAll(solids, + 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), materialUVWalls), + + Ball.cr(Vec3.xyz(-0.3, 0.3, 0.0), 0.4, uv -> Material.GLASS.refractive(Color.hsb(0.7, 0.2, 1.0))), + Ball.cr(Vec3.xyz( 0.4, -0.4, 0.0), 0.4, uv -> Material.GLASS), + Ball.cr(Vec3.xyz(-0.3, -0.4, -0.6), 0.4, uv -> Material.GLASS.refractiveIndex(2.5)), + Ball.cr(Vec3.xyz( 0.4, 0.3, 0.6), 0.4, uv -> Material.GLASS.refractiveIndex(0.6)) + ); + + Collections.addAll(lights, + Light.pc(Vec3.xyz(-0.7, 0.7, -0.7), Color.WHITE), + Light.pc(Vec3.xyz(-0.7, 0.7, 0.7), Color.WHITE), + Light.pc(Vec3.xyz( 0.7, 0.7, -0.7), Color.WHITE), + Light.pc(Vec3.xyz( 0.7, 0.7, 0.7), Color.WHITE) + ); + + solid = Group.of(solids); + } + +} diff --git a/src/xyz/marsavic/gfxlab/graphics3d/scene/SceneTest1.java b/src/xyz/marsavic/gfxlab/graphics3d/scene/SceneTest1.java index deb6e3c..9e2a434 100644 --- a/src/xyz/marsavic/gfxlab/graphics3d/scene/SceneTest1.java +++ b/src/xyz/marsavic/gfxlab/graphics3d/scene/SceneTest1.java @@ -17,10 +17,10 @@ public class SceneTest1 extends Scene.Base{ public SceneTest1() { solid = Group.of( Ball.cr(Vec3.xyz(0, 0, 0), 1, - uv -> new Material(Color.hsb(uv.x() * 6, 0.8, uv.y())) + uv -> Material.matte(Color.hsb(uv.x() * 6, 0.8, uv.y())) ), HalfSpace.pn(Vec3.xyz(0, -1, 0), Vec3.xyz(0, 1, 0), - uv -> new Material(Color.hsb(uv.x(), 0.8, 0.8)) + uv -> Material.matte(Color.hsb(uv.x(), 0.8, 0.8)) ) ); diff --git a/src/xyz/marsavic/gfxlab/graphics3d/solids/Group.java b/src/xyz/marsavic/gfxlab/graphics3d/solids/Group.java index 72361db..4b89aa9 100644 --- a/src/xyz/marsavic/gfxlab/graphics3d/solids/Group.java +++ b/src/xyz/marsavic/gfxlab/graphics3d/solids/Group.java @@ -39,4 +39,15 @@ public class Group implements Solid { return minHit; } + + @Override + public boolean hitBetween(Ray ray, double afterTime, double beforeTime) { + for (Solid s : solids) { + Hit hit = s.firstHit(ray, afterTime); + if ((hit != null) && (hit.t() < beforeTime)) { + return true; + } + } + return false; + } } diff --git a/src/xyz/marsavic/gfxlab/graphics3d/textures/Grid.java b/src/xyz/marsavic/gfxlab/graphics3d/textures/Grid.java new file mode 100644 index 0000000..0d06f2b --- /dev/null +++ b/src/xyz/marsavic/gfxlab/graphics3d/textures/Grid.java @@ -0,0 +1,54 @@ +package xyz.marsavic.gfxlab.graphics3d.textures; + + +import xyz.marsavic.functions.interfaces.F1; +import xyz.marsavic.geometry.Vector; +import xyz.marsavic.gfxlab.Color; +import xyz.marsavic.gfxlab.graphics3d.Material; + + +public class Grid implements F1 { + + private final Vector size, sizeLine; + private final Material material, materialLine; + + // transient + private final Vector sizeLineHalf; + + + + public Grid(Vector size, Vector sizeLine, Material material, Material materialLine) { + this.size = size; + this.sizeLine = sizeLine; + this.material = material; + this.materialLine = materialLine; + sizeLineHalf = sizeLine.div(2); + } + + + @Override + public Material at(Vector uv) { + Vector p = uv.add(sizeLineHalf).mod(size); + return (p.x() < sizeLine.x()) || (p.y() < sizeLine.y()) ? materialLine : material; + } + + + public static Grid standard(Color color) { + return new Grid( + Vector.xy(0.25, 0.25), + Vector.xy(0.01, 0.01), + Material.matte(color), + Material.matte(color.mul(0.75)) + ); + } + + + public static Grid standardUnit(Color color) { + return new Grid( + Vector.UNIT_DIAGONAL, + Vector.xy(1.0/64), + Material.matte(color), + Material.matte(color.mul(0.75)) + ); + } +} diff --git a/src/xyz/marsavic/gfxlab/playground/GfxLab.java b/src/xyz/marsavic/gfxlab/playground/GfxLab.java index ec749b4..f09a15f 100644 --- a/src/xyz/marsavic/gfxlab/playground/GfxLab.java +++ b/src/xyz/marsavic/gfxlab/playground/GfxLab.java @@ -4,12 +4,15 @@ import xyz.marsavic.functions.interfaces.A2; import xyz.marsavic.functions.interfaces.F1; import xyz.marsavic.gfxlab.*; import xyz.marsavic.gfxlab.elements.Output; +import xyz.marsavic.gfxlab.graphics3d.Affine; +import xyz.marsavic.gfxlab.graphics3d.cameras.Perspective; +import xyz.marsavic.gfxlab.graphics3d.cameras.TransformedCamera; import xyz.marsavic.gfxlab.graphics3d.raytracers.RayTracerSimple; -import xyz.marsavic.gfxlab.graphics3d.scene.SceneTest1; +import xyz.marsavic.gfxlab.graphics3d.scene.DiscoRoom; import xyz.marsavic.gfxlab.gui.UtilsGL; import xyz.marsavic.gfxlab.tonemapping.ColorTransform; import xyz.marsavic.gfxlab.tonemapping.ToneMapping; -import xyz.marsavic.gfxlab.tonemapping.colortransforms.Multiply; +import xyz.marsavic.gfxlab.tonemapping.matrixcolor_to_colortransforms.AutoSoft; import static xyz.marsavic.gfxlab.elements.ElementF.e; import static xyz.marsavic.gfxlab.elements.Output.val; @@ -30,22 +33,31 @@ public class GfxLab { e(Fs::transformedColorFunction, // e(Blobs::new, val(5), val(0.1), val(0.2)), e(RayTracerSimple::new, - e(SceneTest1::new) +// e(RefractionTest::new), + e(DiscoRoom::new, val(16), val(16), val(0x3361EB272FEA4C62L)), +// e(Mirrors::new, val(3)), + e(TransformedCamera::new, + e(Perspective::new, val(1.0/3)), + e(Affine.IDENTITY + .then(Affine.translation(Vec3.xyz(0, 0, -4))) +// .then(Affine.rotationAboutY(0.03)) + ) + ) ), e(TransformationsFromSize.toGeometric, eSize) ) ), e(Fs::toneMapping, - e(ColorTransform::asColorTransformFromMatrixColor, - e(Multiply::new, val(1.0)) - ) +// e(ColorTransform::asColorTransformFromMatrixColor, +// e(Multiply::new, val(0.05)) +// ) + e(AutoSoft::new, e(0x1p-5), e(1.0)) ) ) - ); + ); outRenderer = eRenderer.out(); } - } @@ -76,5 +88,6 @@ class Fs { output.fill(p -> f.at(input.get(p)).codeClamp()); }; } + } diff --git a/src/xyz/marsavic/gfxlab/tonemapping/matrixcolor_to_colortransforms/AutoSoft.java b/src/xyz/marsavic/gfxlab/tonemapping/matrixcolor_to_colortransforms/AutoSoft.java new file mode 100644 index 0000000..f48136a --- /dev/null +++ b/src/xyz/marsavic/gfxlab/tonemapping/matrixcolor_to_colortransforms/AutoSoft.java @@ -0,0 +1,71 @@ +package xyz.marsavic.gfxlab.tonemapping.matrixcolor_to_colortransforms; + +import xyz.marsavic.functions.interfaces.F1; +import xyz.marsavic.geometry.Vector; +import xyz.marsavic.gfxlab.Color; +import xyz.marsavic.gfxlab.Matrix; +import xyz.marsavic.gfxlab.gui.UtilsGL; +import xyz.marsavic.gfxlab.tonemapping.ColorTransform; + + +// TODO +public class AutoSoft implements F1> { + + private final double preFactor; + private final double power; + private final double postFactor = 1.0; + private final boolean autoPostFactor = true; + + + public AutoSoft(double preFactor, double power) { + this.preFactor = preFactor; + this.power = power; + } + + + private double lFactor(double lSrc) { + double lPre = lSrc * preFactor; + double lDst = 1 - 1 / (1 + Math.pow(lPre, power)); + + double f = lDst / lSrc; + if (Double.isNaN(f)) { + f = 0; + } + return f; + } + + + @Override + public ColorTransform at(Matrix colorMatrix) { + Vector size = colorMatrix.size(); + + double postFactor_; + + if (autoPostFactor) { + double[] maxY = new double[size.xInt()]; + + UtilsGL.parallelY(size, y -> { + maxY[y] = Double.NEGATIVE_INFINITY; + for (int x = 0; x < size.xInt(); x++) { + Color c = colorMatrix.get(x, y); + Color result = c.mul(lFactor(c.luminance())); + maxY[y] = Math.max(maxY[y], result.max()); + } + }); + + // TODO Replace with fork-join task. + + double max = Double.NEGATIVE_INFINITY; + for (int y = 0; y < size.yInt(); y++) { + max = Math.max(max, maxY[y]); + } + + postFactor_ = 1 / max; + } else { + postFactor_ = postFactor; + } + + return color -> color.mul(lFactor(color.luminance()) * postFactor_); + } + +}