HalfSpace, Group, Material, uv mapping, Light, Scene, diffuse lighting

This commit is contained in:
Marko Savić 2022-11-21 23:51:12 +01:00
parent 7f8dbce165
commit 5e56c197f2
15 changed files with 314 additions and 61 deletions

View file

@ -0,0 +1,3 @@
<component name="DependencyValidationManager">
<scope name="Raytracing" pattern="(!lib:*..*&amp;&amp;!test:*..*&amp;&amp;!src:*..*&amp;&amp;!file[GfxLab]:*/&amp;&amp;!ext:*/||src:xyz.marsavic.gfxlab.graphics3d..*||src:xyz.marsavic.gfxlab.playground..*)&amp;&amp;!src:xyz.marsavic.gfxlab.playground.colorfunctions..*" />
</component>

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="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View file

@ -7,12 +7,18 @@ import xyz.marsavic.gfxlab.Vec3;
/** Interaction of a ray with a solid.*/
public interface Hit {
/** The time of the hit */
/** The time of the hit. */
double t();
/** The normal at the point of the hit */
/** The normal at the hit point. */
Vec3 n();
/** Surface material at the hit point. */
Material material();
/** 2D coordinates in the internal coordinate system of the surface. */
Vector uv();
/** The normalized normal at the point of the hit */
default Vec3 n_() {
return n().normalized_();

View file

@ -0,0 +1,20 @@
package xyz.marsavic.gfxlab.graphics3d;
import xyz.marsavic.gfxlab.Color;
import xyz.marsavic.gfxlab.Vec3;
/** Point light. */
public record Light(
Vec3 p,
Color c
) {
public static Light pc(Vec3 p, Color c) {
return new Light(p, c);
}
public static Light p(Vec3 p) {
return pc(p, Color.WHITE);
}
}

View file

@ -0,0 +1,16 @@
package xyz.marsavic.gfxlab.graphics3d;
import xyz.marsavic.gfxlab.Color;
public record Material(
Color diffuse
) {
public Material diffuse(Color diffuse) { return new Material(diffuse); }
public static final Material BLACK = new Material(Color.BLACK);
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();
}

View file

@ -0,0 +1,44 @@
package xyz.marsavic.gfxlab.graphics3d;
import xyz.marsavic.gfxlab.Color;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public interface Scene {
Solid solid();
Collection<Light> lights();
default Color colorBackground() {
return Color.BLACK;
}
class Base implements Scene {
protected Solid solid;
protected final List<Light> lights = new ArrayList<>();
protected final Color colorBackground = Color.BLACK;
@Override
public Solid solid() {
return solid;
}
@Override
public Collection<Light> lights() {
return lights;
}
@Override
public Color colorBackground() {
return colorBackground;
}
}
}

View file

@ -0,0 +1,29 @@
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.Ray;
import xyz.marsavic.gfxlab.graphics3d.Scene;
public abstract class RayTracer implements ColorFunctionT {
protected final Scene scene;
public RayTracer(Scene scene) {
this.scene = scene;
}
protected abstract Color sample(Ray r);
@Override
public Color at(double t, Vector p) {
Ray ray = Ray.pd(Vec3.xyz(0, 0, -2.6), Vec3.zp(1.6, p));
return sample(ray);
}
}

View file

@ -0,0 +1,44 @@
package xyz.marsavic.gfxlab.graphics3d.raytracers;
import xyz.marsavic.gfxlab.Color;
import xyz.marsavic.gfxlab.Vec3;
import xyz.marsavic.gfxlab.graphics3d.*;
public class RayTracerSimple extends RayTracer {
public RayTracerSimple(Scene scene) {
super(scene);
}
@Override
protected Color sample(Ray r) {
Hit hit = scene.solid().firstHit(r);
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
Color lightDiffuse = Color.BLACK; // The sum of diffuse contributions from all the lights
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_
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);
}
}
return hit.material().diffuse().mul(lightDiffuse);
}
}

View file

@ -1,30 +0,0 @@
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.Hit;
import xyz.marsavic.gfxlab.graphics3d.Ray;
import xyz.marsavic.gfxlab.graphics3d.solids.Ball;
import xyz.marsavic.gfxlab.graphics3d.solids.HalfSpace;
public class RayTracingTest implements ColorFunctionT {
Ball ball = Ball.cr(Vec3.xyz(0, 0, 2), 1);
HalfSpace halfSpace = HalfSpace.pn(Vec3.xyz(0, -1, 0), Vec3.xyz(0, 1, 0));
@Override
public Color at(double t, Vector p) {
Ray ray = Ray.pq(Vec3.ZERO, Vec3.zp(1, p));
Hit hit1 = ball.firstHit(ray);
Hit hit2 = halfSpace.firstHit(ray);
double tMin = Math.min(Hit.t(hit1), Hit.t(hit2));
return Color.gray(1.0 / (1.0 + tMin));
}
}

View file

@ -0,0 +1,34 @@
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.solids.Ball;
import xyz.marsavic.gfxlab.graphics3d.solids.Group;
import xyz.marsavic.gfxlab.graphics3d.solids.HalfSpace;
import java.util.Collections;
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()))
),
HalfSpace.pn(Vec3.xyz(0, -1, 0), Vec3.xyz(0, 1, 0),
uv -> new Material(Color.hsb(uv.x(), 0.8, 0.8))
)
);
Collections.addAll(lights,
Light.pc(Vec3.xyz(-1, 1, -1), Color.hsb(0.0, 1.0, 0.6)),
Light.pc(Vec3.xyz( 2, 0, 0), Color.gray(0.6)),
Light.pc(Vec3.xyz( 0, 0, -2), Color.gray(0.1))
);
}
}

View file

@ -1,8 +1,10 @@
package xyz.marsavic.gfxlab.graphics3d.solids;
import xyz.marsavic.functions.interfaces.F1;
import xyz.marsavic.geometry.Vector;
import xyz.marsavic.gfxlab.Vec3;
import xyz.marsavic.gfxlab.graphics3d.Hit;
import xyz.marsavic.gfxlab.graphics3d.Material;
import xyz.marsavic.gfxlab.graphics3d.Ray;
import xyz.marsavic.gfxlab.graphics3d.Solid;
import xyz.marsavic.utils.Numeric;
@ -12,21 +14,22 @@ public class Ball implements Solid {
private final Vec3 c;
private final double r;
private final F1<Material, Vector> mapMaterial;
// transient
private final double rSqr;
private Ball(Vec3 c, double r) {
private Ball(Vec3 c, double r, F1<Material, Vector> mapMaterial) {
this.c = c;
this.r = r;
rSqr = r * r;
this.mapMaterial = mapMaterial;
}
public static Ball cr(Vec3 c, double r) {
return new Ball(c, r);
public static Ball cr(Vec3 c, double r, F1<Material, Vector> mapMaterial) {
return new Ball(c, r, mapMaterial);
}
@ -69,6 +72,20 @@ public class Ball implements Solid {
return ray().at(t()).sub(c());
}
@Override
public Material material() {
return Ball.this.mapMaterial.at(uv());
}
@Override
public Vector uv() {
Vec3 n = n();
return Vector.xy(
Numeric.atan2T(n.z(), n.x()),
-2 * Numeric.asinT(n.y() / r) + 0.5
);
}
@Override
public Vec3 n_() {
return n().div(r);

View file

@ -0,0 +1,42 @@
package xyz.marsavic.gfxlab.graphics3d.solids;
import xyz.marsavic.gfxlab.graphics3d.Hit;
import xyz.marsavic.gfxlab.graphics3d.Ray;
import xyz.marsavic.gfxlab.graphics3d.Solid;
import java.util.Collection;
public class Group implements Solid {
private final Solid[] solids;
private Group(Solid... solids) {
this.solids = solids.clone();
}
public static Group of(Solid... solids) {
return new Group(solids);
}
public static Group of(Collection<Solid> solids) {
return new Group(solids.toArray(Solid[]::new));
}
@Override
public Hit firstHit(Ray ray, double afterTime) {
double minT = Double.POSITIVE_INFINITY;
Hit minHit = null;
for (Solid s : solids) {
Hit hit = s.firstHit(ray, afterTime);
double t = Hit.t(hit);
if (t < minT) {
minT = t;
minHit = hit;
}
}
return minHit;
}
}

View file

@ -1,10 +1,9 @@
package xyz.marsavic.gfxlab.graphics3d.solids;
import xyz.marsavic.functions.interfaces.F1;
import xyz.marsavic.geometry.Vector;
import xyz.marsavic.gfxlab.Vec3;
import xyz.marsavic.gfxlab.graphics3d.GeometryUtils;
import xyz.marsavic.gfxlab.graphics3d.Hit;
import xyz.marsavic.gfxlab.graphics3d.Ray;
import xyz.marsavic.gfxlab.graphics3d.Solid;
import xyz.marsavic.gfxlab.graphics3d.*;
public class HalfSpace implements Solid {
@ -12,6 +11,7 @@ public class HalfSpace implements Solid {
private final Vec3 p; // A point on the boundary plane
private final Vec3 e; // A vector parallel to the boundary plane.
private final Vec3 f; // A vector parallel to the boundary plane, not parallel to e.
private final F1<Material, Vector> mapMaterial;
// transient
private final Vec3 n; // A normal vector to the boundary plane
@ -20,10 +20,11 @@ public class HalfSpace implements Solid {
private HalfSpace(Vec3 p, Vec3 e, Vec3 f) {
private HalfSpace(Vec3 p, Vec3 e, Vec3 f, F1<Material, Vector> mapMaterial) {
this.p = p;
this.e = e;
this.f = f;
this.mapMaterial = mapMaterial;
this.n = e.cross(f);
n_ = n.normalized_();
@ -37,21 +38,21 @@ public class HalfSpace implements Solid {
}
public static HalfSpace pef(Vec3 p, Vec3 e, Vec3 f) {
return new HalfSpace(p, e, f);
public static HalfSpace pef(Vec3 p, Vec3 e, Vec3 f, F1<Material, Vector> mapMaterial) {
return new HalfSpace(p, e, f, mapMaterial);
}
public static HalfSpace pqr(Vec3 p, Vec3 q, Vec3 r) {
return pef(p, q.sub(p), r.sub(p));
public static HalfSpace pqr(Vec3 p, Vec3 q, Vec3 r, F1<Material, Vector> mapMaterial) {
return pef(p, q.sub(p), r.sub(p), mapMaterial);
}
public static HalfSpace pn(Vec3 p, Vec3 n) {
public static HalfSpace pn(Vec3 p, Vec3 n, F1<Material, Vector> mapMaterial) {
double nl = n.length();
Vec3 e = GeometryUtils.normal(n).normalizedTo(nl);
Vec3 f = n.cross(e).normalizedTo(nl);
return new HalfSpace(p, e, f);
return new HalfSpace(p, e, f, mapMaterial);
}
@ -113,11 +114,30 @@ public class HalfSpace implements Solid {
return n;
}
@Override
public Material material() {
return HalfSpace.this.mapMaterial.at(uv());
}
@Override
public Vec3 n_() {
return n_;
}
@Override
public Vector uv() {
Vec3 b = ray().at(t()).sub(p);
double b_e = b.dot(e) / eLSqr;
double b_f = b.dot(f) / fLSqr;
return Vector.xy(
(b_e - b_f * f_e) / sinSqr,
(b_f - b_e * e_f) / sinSqr
);
}
}
}

View file

@ -15,10 +15,9 @@ import xyz.marsavic.gfxlab.Color;
import xyz.marsavic.gfxlab.Matrix;
import xyz.marsavic.gfxlab.MatrixData;
import xyz.marsavic.gfxlab.MatrixInts;
import xyz.marsavic.resources.ResourceManagerMap;
import xyz.marsavic.random.RNG;
import xyz.marsavic.resources.ResourceManagerMap;
import xyz.marsavic.time.Profiler;
import xyz.marsavic.utils.Utils;
import javax.imageio.ImageIO;
import java.io.File;
@ -139,13 +138,13 @@ public class UtilsGL {
static {
int p = ForkJoinPool.getCommonPoolParallelism();
try {
boolean obsRunning = false;
obsRunning |= ProcessHandle.allProcesses().anyMatch(ph -> ph.info().command().orElse("").contains("obs64")); // Windows
obsRunning |= !Utils.runCommand("top -b -n 1 | grep \" obs\"").isEmpty(); // Linux
obsRunning |= true;
if (obsRunning) {
p -= 3;
}
// boolean obsRunning = false;
// obsRunning |= ProcessHandle.allProcesses().anyMatch(ph -> ph.info().command().orElse("").contains("obs64")); // Windows
// obsRunning |= !Utils.runCommand("top -b -n 1 | grep \" obs\"").isEmpty(); // Linux
// obsRunning |= true;
// if (obsRunning) {
p = 1 + p/2;
// }
} catch (Exception e) {
e.printStackTrace();
}

View file

@ -4,11 +4,12 @@ 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.raytracers.RayTracingTest;
import xyz.marsavic.gfxlab.graphics3d.raytracers.RayTracerSimple;
import xyz.marsavic.gfxlab.graphics3d.scene.SceneTest1;
import xyz.marsavic.gfxlab.gui.UtilsGL;
import xyz.marsavic.gfxlab.tonemapping.ColorTransform;
import xyz.marsavic.gfxlab.tonemapping.ToneMapping;
import xyz.marsavic.gfxlab.tonemapping.colortransforms.Identity;
import xyz.marsavic.gfxlab.tonemapping.colortransforms.Multiply;
import static xyz.marsavic.gfxlab.elements.ElementF.e;
import static xyz.marsavic.gfxlab.elements.Output.val;
@ -28,13 +29,15 @@ public class GfxLab {
e(Fs::aFillFrameColor,
e(Fs::transformedColorFunction,
// e(Blobs::new, val(5), val(0.1), val(0.2)),
e(RayTracingTest::new),
e(RayTracerSimple::new,
e(SceneTest1::new)
),
e(TransformationsFromSize.toGeometric, eSize)
)
),
e(Fs::toneMapping,
e(ColorTransform::asColorTransformFromMatrixColor,
e(Identity::new)
e(Multiply::new, val(1.0))
)
)
)
@ -70,7 +73,7 @@ class Fs {
public static ToneMapping toneMapping(F1<ColorTransform, Matrix<Color>> f_ColorTransform_MatrixColor) {
return (input, output) -> {
ColorTransform f = f_ColorTransform_MatrixColor.at(input);
output.fill(p -> f.at(input.get(p)).code());
output.fill(p -> f.at(input.get(p)).codeClamp());
};
}