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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {

View File

@ -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()) {

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) {
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);

View File

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

View File

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

View File

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

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
public Vector normalAt(Vector surfacePoint) {
return normal;
public Vector normalAt(Vector surfacePoint, Vector incoming) {
return normal.scale(-Math.signum(normal.dot(incoming)));
}
}

View File

@ -7,5 +7,5 @@ public interface Primitive {
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) {
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();
}
}