Compare commits
3 commits
c4dfbfd3ed
...
32de052a6c
Author | SHA1 | Date | |
---|---|---|---|
32de052a6c | |||
876586ee9d | |||
9ac3ce01b8 |
8 changed files with 162 additions and 19 deletions
2
.idea/kotlinc.xml
generated
2
.idea/kotlinc.xml
generated
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="KotlinJpsPluginSettings">
|
<component name="KotlinJpsPluginSettings">
|
||||||
<option name="version" value="2.0.21" />
|
<option name="version" value="2.1.0" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
|
@ -1,5 +1,5 @@
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "2.0.21"
|
kotlin("jvm") version "2.1.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "org.example"
|
group = "org.example"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import lights.PointLight
|
import lights.PointLight
|
||||||
import materials.MaterialColor
|
import materials.MaterialColor
|
||||||
|
import materials.PathTracedMaterial
|
||||||
import materials.ReflectiveMaterial
|
import materials.ReflectiveMaterial
|
||||||
import materials.WhateverMaterial
|
import materials.WhateverMaterial
|
||||||
import materials.times
|
import materials.times
|
||||||
|
@ -15,19 +16,46 @@ fun main() {
|
||||||
val bmp = Bitmap(1000, 1000)
|
val bmp = Bitmap(1000, 1000)
|
||||||
val origin = Point(500f, 500f, -500f)
|
val origin = Point(500f, 500f, -500f)
|
||||||
|
|
||||||
val sphere = Sphere(Point(500f, 500f, 500f), radius = 500f, material = ReflectiveMaterial(reflectivity = 0.2f, MaterialColor(0f, 1.0f, 0f)))
|
val material1 = PathTracedMaterial(
|
||||||
val sphere2 = Sphere(Point(0f, 500f, 50f), radius = 50f, material = ReflectiveMaterial(reflectivity = 0.2f, MaterialColor(1.0f, 0.0f, 0.1f)))
|
MaterialColor(1f, 0f, 0f),
|
||||||
|
specularProbability = 0.1f,
|
||||||
|
specularColour = MaterialColor.White,
|
||||||
|
smoothness = 1.0f,
|
||||||
|
)
|
||||||
|
|
||||||
|
val materialFloor = PathTracedMaterial(
|
||||||
|
MaterialColor(1f, 0f, 1f),
|
||||||
|
specularProbability = 0.1f,
|
||||||
|
specularColour = MaterialColor.White,
|
||||||
|
smoothness = 1.0f,
|
||||||
|
)
|
||||||
|
|
||||||
|
val lightMaterial = PathTracedMaterial(
|
||||||
|
color = MaterialColor.Black,
|
||||||
|
emissionColor = MaterialColor.White,
|
||||||
|
emissionStrength = 0.2f,
|
||||||
|
specularColour = MaterialColor.White
|
||||||
|
)
|
||||||
|
|
||||||
|
val material2 = ReflectiveMaterial(reflectivity = 0.2f, MaterialColor(0f, 1.0f, 0f))
|
||||||
|
val material3 = ReflectiveMaterial(reflectivity = 0.2f, MaterialColor(1.0f, 0.0f, 0.1f))
|
||||||
|
|
||||||
|
val sphere = Sphere(Point(500f, 500f, 500f), radius = 500f, material = material1)
|
||||||
|
val sphere2 = Sphere(Point(0f, 500f, 50f), radius = 50f, material = material1)
|
||||||
|
val sphere3 = Sphere(Point(1000f, 1000f, -500f), radius = 100f, material = lightMaterial)
|
||||||
|
|
||||||
|
val material4 = WhateverMaterial(MaterialColor(1f, 0.0f, 0.0f))
|
||||||
val plane = Plane(
|
val plane = Plane(
|
||||||
Point(0f, -1000f, 0f),
|
Point(0f, -1000f, 0f),
|
||||||
normal = Vector(0f, 1f, 0f),
|
normal = Vector(0f, 1f, 0f),
|
||||||
material = WhateverMaterial(MaterialColor(1f, 0.0f, 0.0f))
|
material = materialFloor
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val material5 = WhateverMaterial(MaterialColor(0.0f, 1f, 0.0f))
|
||||||
val leftPlane = Plane(
|
val leftPlane = Plane(
|
||||||
Point(-1000f, 0f, 0f),
|
Point(-1000f, 0f, 0f),
|
||||||
Vector(1f, 0f, 0f),
|
Vector(1f, 0f, 0f),
|
||||||
material = WhateverMaterial(MaterialColor(0.0f, 1f, 0.0f))
|
material = materialFloor
|
||||||
)
|
)
|
||||||
|
|
||||||
val light = PointLight(Point(1000f, 1000f, -500f), color = 0.2f * MaterialColor(0.9f, 0.9f, 0.9f))
|
val light = PointLight(Point(1000f, 1000f, -500f), color = 0.2f * MaterialColor(0.9f, 0.9f, 0.9f))
|
||||||
|
@ -36,20 +64,30 @@ fun main() {
|
||||||
sphere,
|
sphere,
|
||||||
sphere2,
|
sphere2,
|
||||||
plane,
|
plane,
|
||||||
leftPlane
|
leftPlane,
|
||||||
), light = light)
|
sphere3
|
||||||
|
), light = light, ambientLight = MaterialColor.White * 0.1f)
|
||||||
|
|
||||||
|
|
||||||
for (y in 0 until bmp.height) {
|
for (y in 0 until bmp.height) {
|
||||||
for (x in 0 until bmp.width) {
|
for (x in 0 until bmp.width) {
|
||||||
val point = Point(x.toFloat(), y.toFloat(), 0f)
|
var color = MaterialColor.Black
|
||||||
val ray = origin.rayTo(point)
|
|
||||||
|
|
||||||
val color = scene.colorForRay(ray)
|
repeat(samplesPerPixel) {
|
||||||
if (color != null) {
|
val point = Point(x.toFloat(), y.toFloat(), 0f) + Vector.randomInCircleXY() * blurriness
|
||||||
bmp.setPixel(x, y, color.toColor())
|
val ray = origin.rayTo(point)
|
||||||
|
|
||||||
|
color += scene.colorForRay(ray)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bmp.setPixel(
|
||||||
|
x, y,
|
||||||
|
(color * (1.0f / samplesPerPixel)).toColor()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File("test.tga").writeBitmap(bmp)
|
File("test.tga").writeBitmap(bmp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const val samplesPerPixel = 64
|
||||||
|
const val blurriness = 1f
|
|
@ -11,9 +11,11 @@ data class MaterialColor(val r: Float, val g: Float, val b: Float) {
|
||||||
|
|
||||||
operator fun times(rhs: MaterialColor) = MaterialColor(r * rhs.r, g * rhs.g, b * rhs.b)
|
operator fun times(rhs: MaterialColor) = MaterialColor(r * rhs.r, g * rhs.g, b * rhs.b)
|
||||||
operator fun plus(rhs: MaterialColor) = MaterialColor(r + rhs.r, g + rhs.g, b + rhs.b)
|
operator fun plus(rhs: MaterialColor) = MaterialColor(r + rhs.r, g + rhs.g, b + rhs.b)
|
||||||
|
operator fun times(rhs: Float) = MaterialColor(rhs * r, rhs * g, rhs * b)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val Black: MaterialColor = MaterialColor(0f, 0f, 0f)
|
val Black: MaterialColor = MaterialColor(0f, 0f, 0f)
|
||||||
|
val White = MaterialColor(1f, 1f, 1f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
85
src/main/kotlin/materials/PathTracedMaterial.kt
Normal file
85
src/main/kotlin/materials/PathTracedMaterial.kt
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
package materials
|
||||||
|
|
||||||
|
import Hit
|
||||||
|
import math.Ray
|
||||||
|
import math.Vector
|
||||||
|
import things.Scene
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.ln
|
||||||
|
import kotlin.math.log
|
||||||
|
import kotlin.math.sqrt
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
data class PathTracedMaterial(
|
||||||
|
val color: MaterialColor,
|
||||||
|
val emissionColor: MaterialColor = MaterialColor(0f, 0f, 0f),
|
||||||
|
val specularProbability: Float = 0f,
|
||||||
|
val smoothness: Float = 0.5f,
|
||||||
|
val emissionStrength: Float = 0f,
|
||||||
|
val specularColour: MaterialColor,
|
||||||
|
) : Material {
|
||||||
|
override fun shade(ray: Ray, hit: Hit, scene: Scene): MaterialColor {
|
||||||
|
// source: https://github.com/SebLague/Ray-Tracing/blob/main/Assets/Scripts/Shaders/RayTracer.shader#L328
|
||||||
|
|
||||||
|
var incomingLight = MaterialColor.Black
|
||||||
|
var rayColor = MaterialColor.White
|
||||||
|
|
||||||
|
var hit: Hit? = hit
|
||||||
|
var ray = ray
|
||||||
|
|
||||||
|
repeat(maxBounces) {
|
||||||
|
if (hit != null && hit.material is PathTracedMaterial) {
|
||||||
|
val isSpecular = hit.material.specularProbability >= Random.Default.nextFloat()
|
||||||
|
|
||||||
|
val diffuseDir = (hit.normal + Random.Default.direction()).normalized()
|
||||||
|
val specularDir = reflect(ray.direction, hit.normal)
|
||||||
|
|
||||||
|
val rayDir = if (isSpecular) {
|
||||||
|
lerp(diffuseDir, specularDir, hit.material.smoothness).normalized()
|
||||||
|
} else {
|
||||||
|
diffuseDir
|
||||||
|
}
|
||||||
|
|
||||||
|
val emittedLight = hit.material.emissionColor * hit.material.emissionStrength;
|
||||||
|
incomingLight += emittedLight * rayColor;
|
||||||
|
rayColor *= if (isSpecular) hit.material.specularColour else hit.material.color
|
||||||
|
|
||||||
|
val p = maxOf(rayColor.r, rayColor.g, rayColor.b)
|
||||||
|
if (Random.Default.nextFloat() >= p) {
|
||||||
|
return@repeat
|
||||||
|
}
|
||||||
|
|
||||||
|
rayColor *= 1.0f / p;
|
||||||
|
|
||||||
|
ray = Ray(hit.point, rayDir)
|
||||||
|
|
||||||
|
} else if (hit != null) {
|
||||||
|
incomingLight += hit.material.shade(ray, hit, scene) * rayColor
|
||||||
|
return@repeat
|
||||||
|
} else {
|
||||||
|
incomingLight += scene.ambientLight * rayColor
|
||||||
|
return@repeat
|
||||||
|
}
|
||||||
|
|
||||||
|
hit = scene.intersects(ray)
|
||||||
|
}
|
||||||
|
|
||||||
|
return incomingLight
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const val maxBounces = 4
|
||||||
|
|
||||||
|
fun Random.normalDistributedFloat(): Float {
|
||||||
|
val theta = nextFloat() * 2 * Math.PI.toFloat()
|
||||||
|
val rho = sqrt(-2 * ln(nextFloat()))
|
||||||
|
return rho * cos(theta)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Random.direction(): Vector =
|
||||||
|
Vector(normalDistributedFloat(), normalDistributedFloat(), normalDistributedFloat()).normalized()
|
||||||
|
|
||||||
|
fun lerp(a: Float, b: Float, c: Float): Float = a * (1 - c) + b * c
|
||||||
|
fun lerp(a: MaterialColor, b: MaterialColor, c: Float): MaterialColor = a * (1 - c) + b * c
|
||||||
|
fun lerp(a: Vector, b: Vector, c: Float): Vector = a * (1 - c) + b * c
|
|
@ -2,6 +2,7 @@ package materials
|
||||||
|
|
||||||
import Hit
|
import Hit
|
||||||
import math.Ray
|
import math.Ray
|
||||||
|
import math.Vector
|
||||||
import things.Scene
|
import things.Scene
|
||||||
import math.times
|
import math.times
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
@ -29,9 +30,14 @@ data class ReflectiveMaterial(
|
||||||
color += intensity * MaterialColor(0.3f, 0f, 0f)
|
color += intensity * MaterialColor(0.3f, 0f, 0f)
|
||||||
}
|
}
|
||||||
|
|
||||||
val reflectionDirection = ray.direction - 2 * (ray.direction dot hit.normal) * hit.normal
|
val reflectionDirection = reflect(ray.direction, hit.normal)
|
||||||
val reflectedColor = scene.colorForRay(Ray(offsetPoint, reflectionDirection)) ?: MaterialColor.Black
|
val reflectedColor = scene.colorForRay(Ray(offsetPoint, reflectionDirection))
|
||||||
|
|
||||||
return (1.0f - reflectivity) * color + reflectivity * reflectedColor
|
return (1.0f - reflectivity) * color + reflectivity * reflectedColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun reflect(
|
||||||
|
rayDirection: Vector,
|
||||||
|
normal: Vector
|
||||||
|
): Vector = rayDirection - 2 * (rayDirection dot normal) * normal
|
|
@ -1,6 +1,9 @@
|
||||||
package math
|
package math
|
||||||
|
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.sin
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
data class Vector(val x: Float, val y: Float, val z: Float) {
|
data class Vector(val x: Float, val y: Float, val z: Float) {
|
||||||
val length: Float get() = sqrt(squaredLength)
|
val length: Float get() = sqrt(squaredLength)
|
||||||
|
@ -21,6 +24,14 @@ data class Vector(val x: Float, val y: Float, val z: Float) {
|
||||||
)
|
)
|
||||||
|
|
||||||
infix fun dot(rhs: Vector): Float = x * rhs.x + y * rhs.y + z * rhs.z
|
infix fun dot(rhs: Vector): Float = x * rhs.x + y * rhs.y + z * rhs.z
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun randomInCircleXY(): Vector {
|
||||||
|
val r = sqrt(Random.Default.nextFloat())
|
||||||
|
val theta = Random.Default.nextFloat() * 2 * Math.PI.toFloat()
|
||||||
|
return Vector(r * sin(theta), r * cos(theta), 0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun Float.times(rhs: Vector): Vector = rhs * this
|
operator fun Float.times(rhs: Vector): Vector = rhs * this
|
||||||
|
|
|
@ -3,9 +3,10 @@ package things
|
||||||
import Hit
|
import Hit
|
||||||
import materials.MaterialColor
|
import materials.MaterialColor
|
||||||
import lights.PointLight
|
import lights.PointLight
|
||||||
|
import materials.Material
|
||||||
import math.Ray
|
import math.Ray
|
||||||
|
|
||||||
data class Scene(val things: List<Thing>, val light: PointLight): Thing {
|
data class Scene(val things: List<Thing>, val light: PointLight, val ambientLight: MaterialColor = MaterialColor.Black): Thing {
|
||||||
override fun intersects(ray: Ray): Hit? {
|
override fun intersects(ray: Ray): Hit? {
|
||||||
var closest: Hit? = null
|
var closest: Hit? = null
|
||||||
for (thing in things) {
|
for (thing in things) {
|
||||||
|
@ -18,11 +19,11 @@ data class Scene(val things: List<Thing>, val light: PointLight): Thing {
|
||||||
return closest
|
return closest
|
||||||
}
|
}
|
||||||
|
|
||||||
fun colorForRay(ray: Ray): MaterialColor? {
|
fun colorForRay(ray: Ray): MaterialColor {
|
||||||
val hit = intersects(ray)
|
val hit = intersects(ray)
|
||||||
if (hit != null) {
|
if (hit != null) {
|
||||||
return hit.material.shade(ray, hit, this)
|
return hit.material.shade(ray, hit, this)
|
||||||
}
|
}
|
||||||
return null
|
return ambientLight
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue