diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index bb44937..c224ad5 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 3c449f6..0aea81a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("jvm") version "2.1.0" + kotlin("jvm") version "2.0.21" } group = "org.example" diff --git a/src/main/kotlin/Ray.kt b/src/main/kotlin/Ray.kt index 7094e81..8e2a2ea 100644 --- a/src/main/kotlin/Ray.kt +++ b/src/main/kotlin/Ray.kt @@ -1,6 +1,5 @@ import lights.PointLight import materials.MaterialColor -import materials.PathTracedMaterial import materials.ReflectiveMaterial import materials.WhateverMaterial import materials.times @@ -16,46 +15,19 @@ fun main() { val bmp = Bitmap(1000, 1000) val origin = Point(500f, 500f, -500f) - val material1 = PathTracedMaterial( - MaterialColor(1f, 0f, 0f), - specularProbability = 0.1f, - specularColour = MaterialColor.White, - smoothness = 1.0f, - ) + val sphere = Sphere(Point(500f, 500f, 500f), radius = 500f, material = ReflectiveMaterial(reflectivity = 0.2f, MaterialColor(0f, 1.0f, 0f))) + val sphere2 = Sphere(Point(0f, 500f, 50f), radius = 50f, material = ReflectiveMaterial(reflectivity = 0.2f, MaterialColor(1.0f, 0.0f, 0.1f))) - 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( Point(0f, -1000f, 0f), normal = Vector(0f, 1f, 0f), - material = materialFloor + material = WhateverMaterial(MaterialColor(1f, 0.0f, 0.0f)) ) - val material5 = WhateverMaterial(MaterialColor(0.0f, 1f, 0.0f)) val leftPlane = Plane( Point(-1000f, 0f, 0f), Vector(1f, 0f, 0f), - material = materialFloor + material = WhateverMaterial(MaterialColor(0.0f, 1f, 0.0f)) ) val light = PointLight(Point(1000f, 1000f, -500f), color = 0.2f * MaterialColor(0.9f, 0.9f, 0.9f)) @@ -64,30 +36,20 @@ fun main() { sphere, sphere2, plane, - leftPlane, - sphere3 - ), light = light, ambientLight = MaterialColor.White * 0.1f) + leftPlane + ), light = light) for (y in 0 until bmp.height) { for (x in 0 until bmp.width) { - var color = MaterialColor.Black + val point = Point(x.toFloat(), y.toFloat(), 0f) + val ray = origin.rayTo(point) - repeat(samplesPerPixel) { - val point = Point(x.toFloat(), y.toFloat(), 0f) + Vector.randomInCircleXY() * blurriness - val ray = origin.rayTo(point) - - color += scene.colorForRay(ray) + val color = scene.colorForRay(ray) + if (color != null) { + bmp.setPixel(x, y, color.toColor()) } - - bmp.setPixel( - x, y, - (color * (1.0f / samplesPerPixel)).toColor() - ) } } File("test.tga").writeBitmap(bmp) } - -const val samplesPerPixel = 64 -const val blurriness = 1f \ No newline at end of file diff --git a/src/main/kotlin/materials/MaterialColor.kt b/src/main/kotlin/materials/MaterialColor.kt index ccc4063..0e192b3 100644 --- a/src/main/kotlin/materials/MaterialColor.kt +++ b/src/main/kotlin/materials/MaterialColor.kt @@ -11,11 +11,9 @@ 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 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 { val Black: MaterialColor = MaterialColor(0f, 0f, 0f) - val White = MaterialColor(1f, 1f, 1f) } } diff --git a/src/main/kotlin/materials/PathTracedMaterial.kt b/src/main/kotlin/materials/PathTracedMaterial.kt deleted file mode 100644 index 17df071..0000000 --- a/src/main/kotlin/materials/PathTracedMaterial.kt +++ /dev/null @@ -1,85 +0,0 @@ -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 diff --git a/src/main/kotlin/materials/ReflectiveMaterial.kt b/src/main/kotlin/materials/ReflectiveMaterial.kt index afa8b92..068d818 100644 --- a/src/main/kotlin/materials/ReflectiveMaterial.kt +++ b/src/main/kotlin/materials/ReflectiveMaterial.kt @@ -2,7 +2,6 @@ package materials import Hit import math.Ray -import math.Vector import things.Scene import math.times import kotlin.math.pow @@ -30,14 +29,9 @@ data class ReflectiveMaterial( color += intensity * MaterialColor(0.3f, 0f, 0f) } - val reflectionDirection = reflect(ray.direction, hit.normal) - val reflectedColor = scene.colorForRay(Ray(offsetPoint, reflectionDirection)) + val reflectionDirection = ray.direction - 2 * (ray.direction dot hit.normal) * hit.normal + val reflectedColor = scene.colorForRay(Ray(offsetPoint, reflectionDirection)) ?: MaterialColor.Black return (1.0f - reflectivity) * color + reflectivity * reflectedColor } -} - -fun reflect( - rayDirection: Vector, - normal: Vector -): Vector = rayDirection - 2 * (rayDirection dot normal) * normal \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/kotlin/math/Vector.kt b/src/main/kotlin/math/Vector.kt index d4d769d..dccb03a 100644 --- a/src/main/kotlin/math/Vector.kt +++ b/src/main/kotlin/math/Vector.kt @@ -1,9 +1,6 @@ package math -import kotlin.math.cos -import kotlin.math.sin import kotlin.math.sqrt -import kotlin.random.Random data class Vector(val x: Float, val y: Float, val z: Float) { val length: Float get() = sqrt(squaredLength) @@ -24,14 +21,6 @@ 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 - - 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 diff --git a/src/main/kotlin/things/Scene.kt b/src/main/kotlin/things/Scene.kt index 3f5a0af..974ce7c 100644 --- a/src/main/kotlin/things/Scene.kt +++ b/src/main/kotlin/things/Scene.kt @@ -3,10 +3,9 @@ package things import Hit import materials.MaterialColor import lights.PointLight -import materials.Material import math.Ray -data class Scene(val things: List, val light: PointLight, val ambientLight: MaterialColor = MaterialColor.Black): Thing { +data class Scene(val things: List, val light: PointLight): Thing { override fun intersects(ray: Ray): Hit? { var closest: Hit? = null for (thing in things) { @@ -19,11 +18,11 @@ data class Scene(val things: List, val light: PointLight, val ambientLigh return closest } - fun colorForRay(ray: Ray): MaterialColor { + fun colorForRay(ray: Ray): MaterialColor? { val hit = intersects(ray) if (hit != null) { return hit.material.shade(ray, hit, this) } - return ambientLight + return null } } \ No newline at end of file