From 4d3c45c111b8ea4549ed45d27f6f488024294634 Mon Sep 17 00:00:00 2001 From: teridax Date: Tue, 28 Feb 2023 00:20:45 +0100 Subject: [PATCH] added support for refraction in raytracer --- .idea/.gitignore | 3 ++ .idea/compiler.xml | 13 +++++++ .idea/encodings.xml | 7 ++++ .idea/jarRepositories.xml | 20 +++++++++++ .idea/misc.xml | 12 +++++++ .idea/vcs.xml | 6 ++++ src/main/java/basics/math/algebra/Vector.java | 11 ++++++ src/main/java/entry/Main.java | 2 +- src/main/java/geometry/scene/Scene.java | 29 ++++++++++++---- src/main/java/optics/light/Color.java | 1 + src/main/java/raytracing/BasicMaterial.java | 9 ++++- src/main/java/raytracing/Raytracer.java | 24 +++++++++---- src/main/java/renderer/Display.java | 2 +- .../renderer/canvas/ContributionBuffer.java | 13 ++----- .../java/renderer/canvas/RenderTarget.java | 2 +- src/main/java/renderer/mesh/BasicMesh.java | 4 +-- src/main/java/renderer/mesh/Mesh.java | 2 +- src/main/java/renderer/primitive/Disk.java | 34 +++++++++++++++++++ src/main/java/renderer/primitive/Plane.java | 4 +-- .../java/renderer/primitive/Primitive.java | 2 +- src/main/java/renderer/primitive/Sphere.java | 13 ++++--- 21 files changed, 175 insertions(+), 38 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 src/main/java/renderer/primitive/Disk.java diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..72f98fa --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..2738f8e --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main/java/basics/math/algebra/Vector.java b/src/main/java/basics/math/algebra/Vector.java index f71e133..4717086 100644 --- a/src/main/java/basics/math/algebra/Vector.java +++ b/src/main/java/basics/math/algebra/Vector.java @@ -100,6 +100,16 @@ public class Vector { return this.sub(normal.scale(this.dot(normal) * 2.0)); } + public Vector refract(Vector normal, Vector incoming, double ior) { + double len2 = normal.dot(incoming); + double k = 1.0 - ior * ior * (1.0 - len2 * len2); + + if (k < 0) + return Vector.origin(); + + return incoming.scale(ior).sub(normal.scale(ior * len2 + Math.sqrt(k))); + } + public Vector lerp(Vector other, double k) { return this.scale(k).add(other.scale(1.0 - k)); } @@ -108,6 +118,7 @@ public class Vector { return this.sub(vector).length(); } + public enum SwizzleMask { XYZ, YXZ, diff --git a/src/main/java/entry/Main.java b/src/main/java/entry/Main.java index 34d0938..808f7a6 100644 --- a/src/main/java/entry/Main.java +++ b/src/main/java/entry/Main.java @@ -9,7 +9,7 @@ public class Main { public static void main(String[] args) { - Resolution resolution = new Resolution(800, 800, 2); + Resolution resolution = new Resolution(800, 800, 3); PlayerController controller = new PlayerController(); diff --git a/src/main/java/geometry/scene/Scene.java b/src/main/java/geometry/scene/Scene.java index f192c4a..60bf6cc 100644 --- a/src/main/java/geometry/scene/Scene.java +++ b/src/main/java/geometry/scene/Scene.java @@ -9,8 +9,10 @@ import optics.light.Point; 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; @@ -30,14 +32,27 @@ public class Scene { public static Scene generateExampleScene() { Scene scene = new Scene(new LinearList()); - scene.addMesh(new BasicMesh(new BasicMaterial(new Color(0.8, 0.4, 0.2), 1.0, new Color(1,0.4,0.1)), new Sphere(new Vector(2,0,0), 1.0))); - scene.addMesh(new BasicMesh(new BasicMaterial(new Color(0.8, 0.4, 0.2), 0.0, new Color(1,0.4,0.1)), new Sphere(Vector.origin(), 1.0))); - scene.addMesh(new BasicMesh(new BasicMaterial(new Color(1, 1, 1), 0.2, new Color(1.0)), new Plane(new Vector(1, 0, 0), 1.0))); - scene.addMesh(new BasicMesh(new BasicMaterial(new Color(1, 1, 1), 0.0, new Color(1.0)), new Plane(new Vector(0, 1, 0), 1.0))); + BasicMaterial mirror = new BasicMaterial(Color.BLACK, 1.0, Color.WHITE, true); + BasicMaterial white = new BasicMaterial(new Color(0.8, 0.8, 0.8), 0.1, new Color(0.8, 0.8, 0.8), false); + BasicMaterial red = new BasicMaterial(new Color(0.8, 0.0, 0.0), 0.1, new Color(0.8, 0.8, 0.8), false); + BasicMaterial green = new BasicMaterial(new Color(0.0, 0.8, 0.0), 0.1, new Color(0.8, 0.8, 0.8), false); + BasicMaterial blue = new BasicMaterial(new Color(0.0, 0.0, 0.8), 0.1, new Color(0.8, 0.8, 0.8), false); - scene.addLight(new Point(new Color(0.9, 0, 0), new Vector(8,3,-2))); - scene.addLight(new Point(new Color(0,0.8,0), new Vector(4,3,2))); - //scene.addLight(new Directional(new Color(1), new Vector(1, 1, 0).normalize())); + scene.addMesh(new BasicMesh(mirror, new Sphere(new Vector(0,1.5,0), 1.0))); + scene.addMesh(new BasicMesh(white, new Sphere(new Vector(3,2,0), 0.25))); + scene.addMesh(new BasicMesh(red, new Sphere(new Vector(2.5,2,-3), 0.33))); + + scene.addMesh(new BasicMesh(white, new Plane(new Vector(0, 1, 0), 1))); + scene.addMesh(new BasicMesh(white, new Plane(new Vector(0, 1, 0), -6))); + + scene.addMesh(new BasicMesh(red, new Plane(new Vector(1, 0, 0), -4))); + scene.addMesh(new BasicMesh(green, new Plane(new Vector(1, 0, 0), 4))); + + scene.addMesh(new BasicMesh(blue, new Plane(new Vector(0, 0, 1), 5))); + scene.addMesh(new BasicMesh(white, new Plane(new Vector(0, 0, 1), -4))); + + scene.addLight(new Point(Color.WHITE.scale(0.5), new Vector(0, 4, 0))); + scene.addLight(new Point(Color.WHITE.scale(0.5), new Vector(1, 3, 1))); return scene; } diff --git a/src/main/java/optics/light/Color.java b/src/main/java/optics/light/Color.java index cf8f764..0e83332 100644 --- a/src/main/java/optics/light/Color.java +++ b/src/main/java/optics/light/Color.java @@ -5,6 +5,7 @@ import basics.math.algebra.Vector; public class Color { public static final Color BLACK = new Color(0,0,0); + public static final Color WHITE = Color.diagonal(1.0); public double r; public double g; diff --git a/src/main/java/raytracing/BasicMaterial.java b/src/main/java/raytracing/BasicMaterial.java index 5e9311b..4f9a4d1 100644 --- a/src/main/java/raytracing/BasicMaterial.java +++ b/src/main/java/raytracing/BasicMaterial.java @@ -9,10 +9,13 @@ public class BasicMaterial implements Material { private double glossieness; private Color reflectionTint; - public BasicMaterial(Color diffuse, double glossieness, Color reflectionTint) { + private boolean refracts; + + public BasicMaterial(Color diffuse, double glossieness, Color reflectionTint, boolean refracts) { this.diffuse = diffuse; this.glossieness = glossieness; this.reflectionTint = reflectionTint; + this.refracts = refracts; } public Color getDiffuse() { @@ -26,4 +29,8 @@ public class BasicMaterial implements Material { public Color getReflectionTint() { return reflectionTint; } + + public boolean refracts() { + return this.refracts; + } } diff --git a/src/main/java/raytracing/Raytracer.java b/src/main/java/raytracing/Raytracer.java index e4ff27f..0d22992 100644 --- a/src/main/java/raytracing/Raytracer.java +++ b/src/main/java/raytracing/Raytracer.java @@ -30,7 +30,7 @@ public class Raytracer implements Renderer { } private Color traceDirect(Ray directRay, int depth) { - if (depth == 4) { + if (depth == 8) { return Color.BLACK; } @@ -42,13 +42,17 @@ public class Raytracer implements Renderer { } Vector intersection = directRay.travel(result.get().getDistance()); - Vector normal = result.get().getMesh().normalAt(intersection); + Vector normal = result.get().getMesh().normalAt(intersection, directRay.getDirection()); BasicMaterial material = (BasicMaterial) result.get().getMesh().getMaterial(); // indirect light sum Color incoming = new Color(0,0,0); + if (material.refracts()) { + return refractedLight(directRay, material, normal, intersection, depth); + } + Color reflectedLight = reflectedLight(directRay, material, normal, intersection, depth); for (LightSource light : scene.getLights()) { @@ -56,20 +60,26 @@ public class Raytracer implements Renderer { Color directLight = material.getDiffuse().scale(directInfluence).mul(light.getColor()); - incoming = incoming.add(directLight).add(reflectedLight); + incoming = incoming.add(directLight); } - return incoming.add(reflectedLight); + return reflectedLight.lerp(incoming, material.getGlossieness()); } private Color reflectedLight(Ray incomingRay, BasicMaterial material, Vector normal, Vector point, int depth) { Vector reflected = incomingRay.getDirection().reflect(normal); - Color reflectedLight = traceDirect(new Ray(point, reflected, 1e-3, 1e3), depth + 1); + Color reflectedLight = traceDirect(new Ray(point, reflected, 1e-3, incomingRay.getFar()), depth + 1); - double highlightFactor = reflected.dot(normal); + return reflectedLight.mul(material.getReflectionTint()); + } - return reflectedLight.add(new Color(highlightFactor)).mul(material.getReflectionTint()); + private Color refractedLight(Ray incomingRay, BasicMaterial material, Vector normal, Vector point, int depth) { + Vector refracted = incomingRay.getDirection().refract(normal, incomingRay.getDirection(), 1.45); + + Color refractedLight = traceDirect(new Ray(point, refracted, 1e-3, incomingRay.getFar()), depth + 1); + + return refractedLight; } private double castShadow(LightSource target, Vector point, Vector surfaceNormal) { diff --git a/src/main/java/renderer/Display.java b/src/main/java/renderer/Display.java index f134337..e55d94b 100644 --- a/src/main/java/renderer/Display.java +++ b/src/main/java/renderer/Display.java @@ -80,7 +80,7 @@ public class Display { }; this.window.setContentPane(target); - this.window.setLocationRelativeTo(null); + this.window.setLocationByPlatform(true); this.window.setVisible(true); try (Scheduler scheduler = Scheduler.getInstance()) { diff --git a/src/main/java/renderer/canvas/ContributionBuffer.java b/src/main/java/renderer/canvas/ContributionBuffer.java index e360104..81ccfee 100644 --- a/src/main/java/renderer/canvas/ContributionBuffer.java +++ b/src/main/java/renderer/canvas/ContributionBuffer.java @@ -48,19 +48,12 @@ public class ContributionBuffer { } } - private double tonemap(double x) { - final double g = 5; - final double k = 4; - - return x < g ? 1 - 1 / Math.pow(g, k) * Math.pow(x - g, k) : 1.0; - } - private int getRGB(int x, int y) { Color linearRGB = buffer[x][y].scale(1.0 / samples[x][y]); - int red = (int) (tonemap(linearRGB.r) * 255.0); - int green = (int) (tonemap(linearRGB.g) * 255.0); - int blue = (int) (tonemap(linearRGB.b) * 255.0); + int red = (int) (linearRGB.r* 255.0); + int green = (int) (linearRGB.g * 255.0); + int blue = (int) (linearRGB.b * 255.0); red = Math.max(Math.min(red, 255), 0); green = Math.max(Math.min(green, 255), 0); diff --git a/src/main/java/renderer/canvas/RenderTarget.java b/src/main/java/renderer/canvas/RenderTarget.java index 0302cbf..9f6ff16 100644 --- a/src/main/java/renderer/canvas/RenderTarget.java +++ b/src/main/java/renderer/canvas/RenderTarget.java @@ -17,7 +17,7 @@ public class RenderTarget extends JPanel { private ContributionBuffer buffer; private DebugOverlay overlay; - private boolean visualizeThreads = true; + private boolean visualizeThreads = false; public RenderTarget(Resolution resolution) { super(); diff --git a/src/main/java/renderer/mesh/BasicMesh.java b/src/main/java/renderer/mesh/BasicMesh.java index 768e73c..a444ce6 100644 --- a/src/main/java/renderer/mesh/BasicMesh.java +++ b/src/main/java/renderer/mesh/BasicMesh.java @@ -28,7 +28,7 @@ public class BasicMesh extends Mesh { } @Override - public Vector normalAt(Vector surfacePoint) { - return shape.normalAt(surfacePoint); + public Vector normalAt(Vector surfacePoint, Vector incoming) { + return shape.normalAt(surfacePoint, incoming); } } diff --git a/src/main/java/renderer/mesh/Mesh.java b/src/main/java/renderer/mesh/Mesh.java index 25e6a2f..bc12761 100644 --- a/src/main/java/renderer/mesh/Mesh.java +++ b/src/main/java/renderer/mesh/Mesh.java @@ -8,7 +8,7 @@ public abstract class Mesh { public abstract double intersect(Ray ray); - public abstract Vector normalAt(Vector surfacePoint); + public abstract Vector normalAt(Vector surfacePoint, Vector incoming); public abstract Material getMaterial(); } diff --git a/src/main/java/renderer/primitive/Disk.java b/src/main/java/renderer/primitive/Disk.java new file mode 100644 index 0000000..5ef66d7 --- /dev/null +++ b/src/main/java/renderer/primitive/Disk.java @@ -0,0 +1,34 @@ +package renderer.primitive; + +import basics.math.algebra.Ray; +import basics.math.algebra.Vector; + +public class Disk implements Primitive { + + private double offset; + private double radius; + private Vector normal; + + public Disk(Vector normal, double offset, double radius) { + this.offset = offset; + this.radius = radius; + this.normal = normal; + } + + @Override + public double intersect(Ray ray) { + double distance = -(ray.getOrigin().dot(normal) + offset) / ray.getDirection().dot(normal); + Vector intersection = ray.travel(distance); + + if (intersection.dot(intersection) > this.radius * this.radius) { + return -1.0; + } + + return distance; + } + + @Override + public Vector normalAt(Vector surfacePoint, Vector incoming) { + return normal.scale(-Math.signum(normal.dot(incoming))); + } +} diff --git a/src/main/java/renderer/primitive/Plane.java b/src/main/java/renderer/primitive/Plane.java index 26bae37..07ded69 100644 --- a/src/main/java/renderer/primitive/Plane.java +++ b/src/main/java/renderer/primitive/Plane.java @@ -19,7 +19,7 @@ public class Plane implements Primitive { } @Override - public Vector normalAt(Vector surfacePoint) { - return normal; + public Vector normalAt(Vector surfacePoint, Vector incoming) { + return normal.scale(-Math.signum(normal.dot(incoming))); } } diff --git a/src/main/java/renderer/primitive/Primitive.java b/src/main/java/renderer/primitive/Primitive.java index b95d09b..7c1b631 100644 --- a/src/main/java/renderer/primitive/Primitive.java +++ b/src/main/java/renderer/primitive/Primitive.java @@ -7,5 +7,5 @@ public interface Primitive { double intersect(Ray ray); - Vector normalAt(Vector surfacePoint); + Vector normalAt(Vector surfacePoint, Vector incoming); } diff --git a/src/main/java/renderer/primitive/Sphere.java b/src/main/java/renderer/primitive/Sphere.java index 6cf56e1..793853d 100644 --- a/src/main/java/renderer/primitive/Sphere.java +++ b/src/main/java/renderer/primitive/Sphere.java @@ -17,18 +17,23 @@ public class Sphere implements Primitive { public double intersect(Ray ray) { Vector oc = ray.getOrigin().sub(this.center); - double b = ray.getDirection().dot(oc); + double b = ray.getDirection().scale(2).dot(oc); double c = oc.dot(oc) - this.radius * this.radius; - double h = b * b - c; + double h = b * b - 4.0 * c; if (h < 0.0) return -1.0; - return -b - h; + h = Math.sqrt(h); + + double u = -b - h; + double v = -b + h; + + return (u > ray.getNear() ? u : u > ray.getNear() ? v : 0) * 0.5; } @Override - public Vector normalAt(Vector surfacePoint) { + public Vector normalAt(Vector surfacePoint, Vector incoming) { return surfacePoint.sub(center).normalize(); } }