added support for refraction in raytracer

This commit is contained in:
Sven Vogel 2023-02-28 00:20:45 +01:00
parent bb03af40e9
commit 4d3c45c111
21 changed files with 175 additions and 38 deletions

3
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

13
.idea/compiler.xml Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="JPath" />
</profile>
</annotationProcessing>
</component>
</project>

7
.idea/encodings.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>

20
.idea/jarRepositories.xml Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://repo.maven.apache.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
</component>
</project>

12
.idea/misc.xml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_19" default="true" project-jdk-name="openjdk-19" project-jdk-type="JavaSDK" />
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -100,6 +100,16 @@ public class Vector {
return this.sub(normal.scale(this.dot(normal) * 2.0)); 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) { public Vector lerp(Vector other, double k) {
return this.scale(k).add(other.scale(1.0 - k)); return this.scale(k).add(other.scale(1.0 - k));
} }
@ -108,6 +118,7 @@ public class Vector {
return this.sub(vector).length(); return this.sub(vector).length();
} }
public enum SwizzleMask { public enum SwizzleMask {
XYZ, XYZ,
YXZ, YXZ,

View File

@ -9,7 +9,7 @@ public class Main {
public static void main(String[] args) { public static void main(String[] args) {
Resolution resolution = new Resolution(800, 800, 2); Resolution resolution = new Resolution(800, 800, 3);
PlayerController controller = new PlayerController(); PlayerController controller = new PlayerController();

View File

@ -9,8 +9,10 @@ import optics.light.Point;
import raytracing.BasicMaterial; import raytracing.BasicMaterial;
import renderer.mesh.BasicMesh; import renderer.mesh.BasicMesh;
import renderer.mesh.Mesh; import renderer.mesh.Mesh;
import renderer.primitive.Disk;
import renderer.primitive.Plane; import renderer.primitive.Plane;
import renderer.primitive.Sphere; import renderer.primitive.Sphere;
import shading.Material;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -30,14 +32,27 @@ public class Scene {
public static Scene generateExampleScene() { public static Scene generateExampleScene() {
Scene scene = new Scene(new LinearList()); 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))); BasicMaterial mirror = new BasicMaterial(Color.BLACK, 1.0, Color.WHITE, true);
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))); BasicMaterial white = new BasicMaterial(new Color(0.8, 0.8, 0.8), 0.1, new Color(0.8, 0.8, 0.8), false);
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))); BasicMaterial red = new BasicMaterial(new Color(0.8, 0.0, 0.0), 0.1, new Color(0.8, 0.8, 0.8), false);
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 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.addMesh(new BasicMesh(mirror, new Sphere(new Vector(0,1.5,0), 1.0)));
scene.addLight(new Point(new Color(0,0.8,0), new Vector(4,3,2))); scene.addMesh(new BasicMesh(white, new Sphere(new Vector(3,2,0), 0.25)));
//scene.addLight(new Directional(new Color(1), new Vector(1, 1, 0).normalize())); 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; return scene;
} }

View File

@ -5,6 +5,7 @@ import basics.math.algebra.Vector;
public class Color { public class Color {
public static final Color BLACK = new Color(0,0,0); public static final Color BLACK = new Color(0,0,0);
public static final Color WHITE = Color.diagonal(1.0);
public double r; public double r;
public double g; public double g;

View File

@ -9,10 +9,13 @@ public class BasicMaterial implements Material {
private double glossieness; private double glossieness;
private Color reflectionTint; 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.diffuse = diffuse;
this.glossieness = glossieness; this.glossieness = glossieness;
this.reflectionTint = reflectionTint; this.reflectionTint = reflectionTint;
this.refracts = refracts;
} }
public Color getDiffuse() { public Color getDiffuse() {
@ -26,4 +29,8 @@ public class BasicMaterial implements Material {
public Color getReflectionTint() { public Color getReflectionTint() {
return reflectionTint; return reflectionTint;
} }
public boolean refracts() {
return this.refracts;
}
} }

View File

@ -30,7 +30,7 @@ public class Raytracer implements Renderer {
} }
private Color traceDirect(Ray directRay, int depth) { private Color traceDirect(Ray directRay, int depth) {
if (depth == 4) { if (depth == 8) {
return Color.BLACK; return Color.BLACK;
} }
@ -42,13 +42,17 @@ public class Raytracer implements Renderer {
} }
Vector intersection = directRay.travel(result.get().getDistance()); 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(); BasicMaterial material = (BasicMaterial) result.get().getMesh().getMaterial();
// indirect light sum // indirect light sum
Color incoming = new Color(0,0,0); 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); Color reflectedLight = reflectedLight(directRay, material, normal, intersection, depth);
for (LightSource light : scene.getLights()) { for (LightSource light : scene.getLights()) {
@ -56,20 +60,26 @@ public class Raytracer implements Renderer {
Color directLight = material.getDiffuse().scale(directInfluence).mul(light.getColor()); 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) { private Color reflectedLight(Ray incomingRay, BasicMaterial material, Vector normal, Vector point, int depth) {
Vector reflected = incomingRay.getDirection().reflect(normal); 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) { private double castShadow(LightSource target, Vector point, Vector surfaceNormal) {

View File

@ -80,7 +80,7 @@ public class Display {
}; };
this.window.setContentPane(target); this.window.setContentPane(target);
this.window.setLocationRelativeTo(null); this.window.setLocationByPlatform(true);
this.window.setVisible(true); this.window.setVisible(true);
try (Scheduler scheduler = Scheduler.getInstance()) { try (Scheduler scheduler = Scheduler.getInstance()) {

View File

@ -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) { private int getRGB(int x, int y) {
Color linearRGB = buffer[x][y].scale(1.0 / samples[x][y]); Color linearRGB = buffer[x][y].scale(1.0 / samples[x][y]);
int red = (int) (tonemap(linearRGB.r) * 255.0); int red = (int) (linearRGB.r* 255.0);
int green = (int) (tonemap(linearRGB.g) * 255.0); int green = (int) (linearRGB.g * 255.0);
int blue = (int) (tonemap(linearRGB.b) * 255.0); int blue = (int) (linearRGB.b * 255.0);
red = Math.max(Math.min(red, 255), 0); red = Math.max(Math.min(red, 255), 0);
green = Math.max(Math.min(green, 255), 0); green = Math.max(Math.min(green, 255), 0);

View File

@ -17,7 +17,7 @@ public class RenderTarget extends JPanel {
private ContributionBuffer buffer; private ContributionBuffer buffer;
private DebugOverlay overlay; private DebugOverlay overlay;
private boolean visualizeThreads = true; private boolean visualizeThreads = false;
public RenderTarget(Resolution resolution) { public RenderTarget(Resolution resolution) {
super(); super();

View File

@ -28,7 +28,7 @@ public class BasicMesh extends Mesh {
} }
@Override @Override
public Vector normalAt(Vector surfacePoint) { public Vector normalAt(Vector surfacePoint, Vector incoming) {
return shape.normalAt(surfacePoint); return shape.normalAt(surfacePoint, incoming);
} }
} }

View File

@ -8,7 +8,7 @@ public abstract class Mesh {
public abstract double intersect(Ray ray); public abstract double intersect(Ray ray);
public abstract Vector normalAt(Vector surfacePoint); public abstract Vector normalAt(Vector surfacePoint, Vector incoming);
public abstract Material getMaterial(); public abstract Material getMaterial();
} }

View File

@ -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)));
}
}

View File

@ -19,7 +19,7 @@ public class Plane implements Primitive {
} }
@Override @Override
public Vector normalAt(Vector surfacePoint) { public Vector normalAt(Vector surfacePoint, Vector incoming) {
return normal; return normal.scale(-Math.signum(normal.dot(incoming)));
} }
} }

View File

@ -7,5 +7,5 @@ public interface Primitive {
double intersect(Ray ray); double intersect(Ray ray);
Vector normalAt(Vector surfacePoint); Vector normalAt(Vector surfacePoint, Vector incoming);
} }

View File

@ -17,18 +17,23 @@ public class Sphere implements Primitive {
public double intersect(Ray ray) { public double intersect(Ray ray) {
Vector oc = ray.getOrigin().sub(this.center); 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 c = oc.dot(oc) - this.radius * this.radius;
double h = b * b - c; double h = b * b - 4.0 * c;
if (h < 0.0) if (h < 0.0)
return -1.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 @Override
public Vector normalAt(Vector surfacePoint) { public Vector normalAt(Vector surfacePoint, Vector incoming) {
return surfacePoint.sub(center).normalize(); return surfacePoint.sub(center).normalize();
} }
} }