├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── renders ├── a - first renders │ ├── 1 - hello world.png │ ├── 2 - sphere.png │ ├── 3 - spheres.png │ ├── 4 - colors.png │ ├── 5 - reflections.png │ ├── 6 - aspect ratio.png │ ├── 7 - environment.png │ ├── 8 - samples.png │ └── 9 - roughness.png ├── b - mandelbulb reflections │ ├── 10 - red.png │ ├── 11 - reflection.png │ ├── 12 - scaled up.png │ ├── 13 - dark ground.png │ └── 14 - diffuse attempt.png ├── c - mandelbulb wallpapers │ ├── 15 - purple.png │ └── 16 - green.png ├── d - specular pbr │ ├── 17 - specular.png │ ├── 18 - sphere and bulb.png │ ├── 19 - sphere and bulb close.png │ ├── 20 - mandelbulb close.png │ └── 21 - rough specular.png ├── e - material grids │ ├── 22 - purple specular rough.png │ ├── 23 - cmy specular rough.png │ ├── 24 - cmy no fresnel.png │ ├── 25 - cmy metal.png │ └── 26 - cmy metal specular.png └── f - multithreaded renders │ └── 27 - mandelbulb.png └── src ├── demo.rs ├── main.rs ├── objects ├── mandelbulb.rs ├── march.rs ├── mod.rs ├── plane.rs ├── sphere.rs ├── trace.rs └── triangle.rs ├── render.rs ├── structures ├── camera.rs ├── cast.rs ├── material.rs ├── mod.rs ├── ray.rs ├── scene.rs └── vec3.rs └── write.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | **/.DS_Store 4 | 5 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "adler32" 5 | version = "1.0.4" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | 8 | [[package]] 9 | name = "arrayvec" 10 | version = "0.4.12" 11 | source = "registry+https://github.com/rust-lang/crates.io-index" 12 | dependencies = [ 13 | "nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 14 | ] 15 | 16 | [[package]] 17 | name = "autocfg" 18 | version = "0.1.6" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | 21 | [[package]] 22 | name = "bitflags" 23 | version = "1.2.1" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | 26 | [[package]] 27 | name = "byteorder" 28 | version = "1.3.2" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | 31 | [[package]] 32 | name = "cfg-if" 33 | version = "0.1.10" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | 36 | [[package]] 37 | name = "cloudabi" 38 | version = "0.0.3" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | dependencies = [ 41 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 42 | ] 43 | 44 | [[package]] 45 | name = "color_quant" 46 | version = "1.0.1" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | 49 | [[package]] 50 | name = "crc32fast" 51 | version = "1.2.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | dependencies = [ 54 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 55 | ] 56 | 57 | [[package]] 58 | name = "crossbeam-deque" 59 | version = "0.7.1" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | dependencies = [ 62 | "crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 63 | "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", 64 | ] 65 | 66 | [[package]] 67 | name = "crossbeam-epoch" 68 | version = "0.7.2" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | dependencies = [ 71 | "arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 72 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 73 | "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", 74 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 75 | "memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 76 | "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 77 | ] 78 | 79 | [[package]] 80 | name = "crossbeam-queue" 81 | version = "0.1.2" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | dependencies = [ 84 | "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", 85 | ] 86 | 87 | [[package]] 88 | name = "crossbeam-utils" 89 | version = "0.6.6" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | dependencies = [ 92 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 93 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 94 | ] 95 | 96 | [[package]] 97 | name = "deflate" 98 | version = "0.7.20" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | dependencies = [ 101 | "adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 102 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 103 | ] 104 | 105 | [[package]] 106 | name = "either" 107 | version = "1.5.3" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | 110 | [[package]] 111 | name = "fuchsia-cprng" 112 | version = "0.1.1" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | 115 | [[package]] 116 | name = "gif" 117 | version = "0.10.3" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | dependencies = [ 120 | "color_quant 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 121 | "lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 122 | ] 123 | 124 | [[package]] 125 | name = "image" 126 | version = "0.22.3" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | dependencies = [ 129 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 130 | "gif 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)", 131 | "jpeg-decoder 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 132 | "num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 133 | "num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 134 | "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 135 | "png 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", 136 | "scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 137 | "tiff 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 138 | ] 139 | 140 | [[package]] 141 | name = "inflate" 142 | version = "0.4.5" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | dependencies = [ 145 | "adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 146 | ] 147 | 148 | [[package]] 149 | name = "jpeg-decoder" 150 | version = "0.1.16" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | dependencies = [ 153 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 154 | "rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 155 | ] 156 | 157 | [[package]] 158 | name = "keikan" 159 | version = "0.1.0" 160 | dependencies = [ 161 | "image 0.22.3 (registry+https://github.com/rust-lang/crates.io-index)", 162 | "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 163 | "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 164 | ] 165 | 166 | [[package]] 167 | name = "lazy_static" 168 | version = "1.4.0" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | 171 | [[package]] 172 | name = "libc" 173 | version = "0.2.71" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | 176 | [[package]] 177 | name = "lzw" 178 | version = "0.10.0" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | 181 | [[package]] 182 | name = "memoffset" 183 | version = "0.5.1" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | dependencies = [ 186 | "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 187 | ] 188 | 189 | [[package]] 190 | name = "nodrop" 191 | version = "0.1.14" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | 194 | [[package]] 195 | name = "num-derive" 196 | version = "0.2.5" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | dependencies = [ 199 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 200 | "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 201 | "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", 202 | ] 203 | 204 | [[package]] 205 | name = "num-integer" 206 | version = "0.1.41" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | dependencies = [ 209 | "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 210 | "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 211 | ] 212 | 213 | [[package]] 214 | name = "num-iter" 215 | version = "0.1.39" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | dependencies = [ 218 | "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 219 | "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 220 | "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 221 | ] 222 | 223 | [[package]] 224 | name = "num-rational" 225 | version = "0.2.2" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | dependencies = [ 228 | "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 229 | "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 230 | "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 231 | ] 232 | 233 | [[package]] 234 | name = "num-traits" 235 | version = "0.2.8" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | dependencies = [ 238 | "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 239 | ] 240 | 241 | [[package]] 242 | name = "num_cpus" 243 | version = "1.10.1" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | dependencies = [ 246 | "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", 247 | ] 248 | 249 | [[package]] 250 | name = "png" 251 | version = "0.15.0" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | dependencies = [ 254 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 255 | "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 256 | "deflate 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)", 257 | "inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 258 | ] 259 | 260 | [[package]] 261 | name = "proc-macro2" 262 | version = "0.4.30" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | dependencies = [ 265 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 266 | ] 267 | 268 | [[package]] 269 | name = "quote" 270 | version = "0.6.13" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | dependencies = [ 273 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 274 | ] 275 | 276 | [[package]] 277 | name = "rand" 278 | version = "0.6.5" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | dependencies = [ 281 | "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 282 | "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", 283 | "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 284 | "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 285 | "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 286 | "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 287 | "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 288 | "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 289 | "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 290 | "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 291 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 292 | ] 293 | 294 | [[package]] 295 | name = "rand_chacha" 296 | version = "0.1.1" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | dependencies = [ 299 | "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 300 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 301 | ] 302 | 303 | [[package]] 304 | name = "rand_core" 305 | version = "0.3.1" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | dependencies = [ 308 | "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 309 | ] 310 | 311 | [[package]] 312 | name = "rand_core" 313 | version = "0.4.2" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | 316 | [[package]] 317 | name = "rand_hc" 318 | version = "0.1.0" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | dependencies = [ 321 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 322 | ] 323 | 324 | [[package]] 325 | name = "rand_isaac" 326 | version = "0.1.1" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | dependencies = [ 329 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 330 | ] 331 | 332 | [[package]] 333 | name = "rand_jitter" 334 | version = "0.1.4" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | dependencies = [ 337 | "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", 338 | "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 339 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 340 | ] 341 | 342 | [[package]] 343 | name = "rand_os" 344 | version = "0.1.3" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | dependencies = [ 347 | "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 348 | "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 349 | "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", 350 | "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 351 | "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 352 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 353 | ] 354 | 355 | [[package]] 356 | name = "rand_pcg" 357 | version = "0.1.2" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | dependencies = [ 360 | "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 361 | "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 362 | ] 363 | 364 | [[package]] 365 | name = "rand_xorshift" 366 | version = "0.1.1" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | dependencies = [ 369 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 370 | ] 371 | 372 | [[package]] 373 | name = "rayon" 374 | version = "1.2.0" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | dependencies = [ 377 | "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 378 | "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 379 | "rayon-core 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 380 | ] 381 | 382 | [[package]] 383 | name = "rayon-core" 384 | version = "1.6.0" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | dependencies = [ 387 | "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 388 | "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 389 | "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", 390 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 391 | "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 392 | ] 393 | 394 | [[package]] 395 | name = "rdrand" 396 | version = "0.4.0" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | dependencies = [ 399 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 400 | ] 401 | 402 | [[package]] 403 | name = "rustc_version" 404 | version = "0.2.3" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | dependencies = [ 407 | "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 408 | ] 409 | 410 | [[package]] 411 | name = "scoped_threadpool" 412 | version = "0.1.9" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | 415 | [[package]] 416 | name = "scopeguard" 417 | version = "1.0.0" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | 420 | [[package]] 421 | name = "semver" 422 | version = "0.9.0" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | dependencies = [ 425 | "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 426 | ] 427 | 428 | [[package]] 429 | name = "semver-parser" 430 | version = "0.7.0" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | 433 | [[package]] 434 | name = "syn" 435 | version = "0.15.44" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | dependencies = [ 438 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 439 | "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 440 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 441 | ] 442 | 443 | [[package]] 444 | name = "tiff" 445 | version = "0.3.1" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | dependencies = [ 448 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 449 | "lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 450 | "num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", 451 | "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 452 | ] 453 | 454 | [[package]] 455 | name = "unicode-xid" 456 | version = "0.1.0" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | 459 | [[package]] 460 | name = "winapi" 461 | version = "0.3.8" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | dependencies = [ 464 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 465 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 466 | ] 467 | 468 | [[package]] 469 | name = "winapi-i686-pc-windows-gnu" 470 | version = "0.4.0" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | 473 | [[package]] 474 | name = "winapi-x86_64-pc-windows-gnu" 475 | version = "0.4.0" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | 478 | [metadata] 479 | "checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" 480 | "checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" 481 | "checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875" 482 | "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 483 | "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" 484 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 485 | "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 486 | "checksum color_quant 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd" 487 | "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" 488 | "checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71" 489 | "checksum crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fedcd6772e37f3da2a9af9bf12ebe046c0dfe657992377b4df982a2b54cd37a9" 490 | "checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" 491 | "checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" 492 | "checksum deflate 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)" = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4" 493 | "checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" 494 | "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 495 | "checksum gif 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)" = "471d90201b3b223f3451cd4ad53e34295f16a1df17b1edf3736d47761c3981af" 496 | "checksum image 0.22.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4be8aaefbe7545dc42ae925afb55a0098f226a3fe5ef721872806f44f57826" 497 | "checksum inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" 498 | "checksum jpeg-decoder 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "c1aae18ffeeae409c6622c3b6a7ee49792a7e5a062eea1b135fbb74e301792ba" 499 | "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 500 | "checksum libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" 501 | "checksum lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" 502 | "checksum memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f" 503 | "checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" 504 | "checksum num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "eafd0b45c5537c3ba526f79d3e75120036502bebacbb3f3220914067ce39dbf2" 505 | "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" 506 | "checksum num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "76bd5272412d173d6bf9afdf98db8612bbabc9a7a830b7bfc9c188911716132e" 507 | "checksum num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2885278d5fe2adc2f75ced642d52d879bffaceb5a2e0b1d4309ffdfb239b454" 508 | "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" 509 | "checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" 510 | "checksum png 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8422b27bb2c013dd97b9aef69e161ce262236f49aaf46a0489011c8ff0264602" 511 | "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 512 | "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" 513 | "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" 514 | "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" 515 | "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 516 | "checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 517 | "checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" 518 | "checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" 519 | "checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" 520 | "checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" 521 | "checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" 522 | "checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" 523 | "checksum rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "83a27732a533a1be0a0035a111fe76db89ad312f6f0347004c220c57f209a123" 524 | "checksum rayon-core 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "98dcf634205083b17d0861252431eb2acbfb698ab7478a2d20de07954f47ec7b" 525 | "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 526 | "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 527 | "checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" 528 | "checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" 529 | "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 530 | "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 531 | "checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" 532 | "checksum tiff 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d7b7c2cfc4742bd8a32f2e614339dd8ce30dbcf676bb262bd63a2327bc5df57d" 533 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 534 | "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 535 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 536 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 537 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "keikan" 3 | version = "0.1.0" 4 | authors = ["Tloru (Isaac C.) "] 5 | edition = "2018" 6 | publish = false 7 | repository = "https://github.com/Tloru/keikan" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | image = "*" 13 | rand = "0.6.5" 14 | num_cpus = "1.0" 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Veritable 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Keikan (警官) 2 | *An elegant (imo) rendering engine written in Rust.* 3 | 4 | **Keikan** is a multithreaded photorealistic rendering engine that supports 5 | path-tracing, ray-marching, Photo Based Rendering 6 | (through a principled implementation), and more. 7 | 8 | I've tried my hand a few toy path-tracers and ray-marchers in the past, 9 | and I wanted to write something more than just a toy. 10 | Having just stumbled upon Rust, I decided to try my hand at it by 11 | implementing a rendering engine from scratch. 12 | 13 | This project was originally inspired by 14 | [*Ray Tracing in One Weekend*](https://raytracing.github.io/books/RayTracingInOneWeekend.html), 15 | but after I knocked out the core engine, I've pulled from a number of different 16 | sources and my own experience to develop it further. 17 | 18 | ## Results 19 | Here's a recent render of a ray-marched *Mandelbulb* (a type of fractal): 20 | 21 | ![A Mandelbulb fractal](https://raw.githubusercontent.com/slightknack/keikan/master/renders/f%20-%20multithreaded%20renders/27%20-%20mandelbulb.png) 22 | > Rendered in a little over 2 minutes on a potato. 23 | 24 | There are some images bundles in this repository under the `keikan/renders/` 25 | folder, ordered chronologically. Go check them out! 26 | 27 | ## Design Goals 28 | Keikan is able to render a large variety of objects, including fractals to 29 | near-infinite precision, due to its support of both path-tracing 30 | and ray-marching. 31 | 32 | Ray-marching is rendering through the use of a 33 | [signed distance field](https://iquilezles.org/www/articles/distfunctions/distfunctions.htm). 34 | I personally got interested in computer graphics through writing ray-marching 35 | renderer, so I decided that if I wrote another rendering engine, 36 | I'd have include ray-marching as a technique. 37 | 38 | ## Getting Started 39 | To render a small demo, clone this repository and run Keikan: 40 | 41 | ```bash 42 | git clone https://github.com/slightknack/keikan.git 43 | cd keikan 44 | cargo run --release -- ~/Desktop/demo.png # where to save the image 45 | ``` 46 | 47 | You should see some output right away. Keikan will spawn as many threads as 48 | detected CPU cores, so it should be ~pretty~ relatively fast 49 | (for non-GPU-based rendering code, haha). 50 | 51 | I'd like to use GPU features, as I've made some ray marchers in GLSL, 52 | but it seems like Rust doesn't have standardized GPU support. 53 | If you know how to run Rust on the GPU, open an issue so we can discuss it 54 | further! 55 | 56 | ## Why (the Name) Keikan? 57 | It's Japanese for policeman. 58 | -------------------------------------------------------------------------------- /renders/a - first renders/1 - hello world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/a - first renders/1 - hello world.png -------------------------------------------------------------------------------- /renders/a - first renders/2 - sphere.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/a - first renders/2 - sphere.png -------------------------------------------------------------------------------- /renders/a - first renders/3 - spheres.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/a - first renders/3 - spheres.png -------------------------------------------------------------------------------- /renders/a - first renders/4 - colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/a - first renders/4 - colors.png -------------------------------------------------------------------------------- /renders/a - first renders/5 - reflections.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/a - first renders/5 - reflections.png -------------------------------------------------------------------------------- /renders/a - first renders/6 - aspect ratio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/a - first renders/6 - aspect ratio.png -------------------------------------------------------------------------------- /renders/a - first renders/7 - environment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/a - first renders/7 - environment.png -------------------------------------------------------------------------------- /renders/a - first renders/8 - samples.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/a - first renders/8 - samples.png -------------------------------------------------------------------------------- /renders/a - first renders/9 - roughness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/a - first renders/9 - roughness.png -------------------------------------------------------------------------------- /renders/b - mandelbulb reflections/10 - red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/b - mandelbulb reflections/10 - red.png -------------------------------------------------------------------------------- /renders/b - mandelbulb reflections/11 - reflection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/b - mandelbulb reflections/11 - reflection.png -------------------------------------------------------------------------------- /renders/b - mandelbulb reflections/12 - scaled up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/b - mandelbulb reflections/12 - scaled up.png -------------------------------------------------------------------------------- /renders/b - mandelbulb reflections/13 - dark ground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/b - mandelbulb reflections/13 - dark ground.png -------------------------------------------------------------------------------- /renders/b - mandelbulb reflections/14 - diffuse attempt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/b - mandelbulb reflections/14 - diffuse attempt.png -------------------------------------------------------------------------------- /renders/c - mandelbulb wallpapers/15 - purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/c - mandelbulb wallpapers/15 - purple.png -------------------------------------------------------------------------------- /renders/c - mandelbulb wallpapers/16 - green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/c - mandelbulb wallpapers/16 - green.png -------------------------------------------------------------------------------- /renders/d - specular pbr/17 - specular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/d - specular pbr/17 - specular.png -------------------------------------------------------------------------------- /renders/d - specular pbr/18 - sphere and bulb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/d - specular pbr/18 - sphere and bulb.png -------------------------------------------------------------------------------- /renders/d - specular pbr/19 - sphere and bulb close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/d - specular pbr/19 - sphere and bulb close.png -------------------------------------------------------------------------------- /renders/d - specular pbr/20 - mandelbulb close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/d - specular pbr/20 - mandelbulb close.png -------------------------------------------------------------------------------- /renders/d - specular pbr/21 - rough specular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/d - specular pbr/21 - rough specular.png -------------------------------------------------------------------------------- /renders/e - material grids/22 - purple specular rough.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/e - material grids/22 - purple specular rough.png -------------------------------------------------------------------------------- /renders/e - material grids/23 - cmy specular rough.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/e - material grids/23 - cmy specular rough.png -------------------------------------------------------------------------------- /renders/e - material grids/24 - cmy no fresnel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/e - material grids/24 - cmy no fresnel.png -------------------------------------------------------------------------------- /renders/e - material grids/25 - cmy metal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/e - material grids/25 - cmy metal.png -------------------------------------------------------------------------------- /renders/e - material grids/26 - cmy metal specular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/e - material grids/26 - cmy metal specular.png -------------------------------------------------------------------------------- /renders/f - multithreaded renders/27 - mandelbulb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightknack/keikan/002603ec16e64b3d92113835b384d980376cb711/renders/f - multithreaded renders/27 - mandelbulb.png -------------------------------------------------------------------------------- /src/demo.rs: -------------------------------------------------------------------------------- 1 | use crate::structures::material::Material; 2 | use crate::structures::camera::Camera; 3 | use crate::structures::scene::Scene; 4 | use crate::structures::vec3::Vec3; 5 | use crate::objects::sphere::Sphere; 6 | use crate::objects::plane::Plane; 7 | use crate::objects::mandelbulb::Mandelbulb; 8 | use crate::objects::triangle::Triangle; 9 | 10 | // const RESOLUTION: (usize, usize) = (1920, 1080); 11 | // const RESOLUTION: (usize, usize) = (1440, 900); 12 | const RESOLUTION: (usize, usize) = (720, 450); 13 | 14 | pub fn mandelbulb() -> (Scene, Camera) { 15 | let camera = Camera::new( 16 | Vec3::new(-2.0, 0.6, 4.0), 17 | Vec3::new(0.0, 0.0, 1.0), 18 | Vec3::new(0.0, 1.0, 0.0), 19 | 60.0, 20 | RESOLUTION, 21 | 12, 1, 3, 22 | ); 23 | 24 | let mut scene = Scene::empty(); 25 | 26 | let plastic = Material::dielectric(Vec3::new(0.2, 0.2, 0.2), 0.7, 0.05); 27 | let light = Material::emissive(Vec3::new(1.0, 1.0, 0.9), 3.0); 28 | let metal = Material::metal(Vec3::new(1.0, 1.0, 1.0), 0.0); 29 | 30 | let sphere = Sphere::new( 31 | Vec3::new(4.0, 4.0, 4.0), 32 | 4.0, 33 | light 34 | ); 35 | 36 | let bulb = Mandelbulb::new(Vec3::new(0.0, 0.0, 0.0), 8.0, 10, metal); 37 | 38 | let plane = Plane::new( 39 | Vec3::new(0.0, -1.0, 0.0), 40 | Vec3::new(0.0, 1.0, 0.0), 41 | plastic, 42 | ); 43 | 44 | scene.add_trace(Box::new(sphere)); 45 | scene.add_march(Box::new(bulb)); 46 | scene.add_trace(Box::new(plane)); 47 | 48 | return (scene, camera); 49 | } 50 | 51 | pub fn specular() -> (Scene, Camera) { 52 | let camera = Camera::new( 53 | Vec3::new(5.0, 2.2, 5.0), 54 | Vec3::new(0.0, 1.2, 0.0), 55 | Vec3::new(0.0, 1.0, 0.0), 56 | 60.0, 57 | RESOLUTION, 58 | 64, 1, 3, 59 | ); 60 | 61 | let mut scene = Scene::empty(); 62 | scene.bg = Material::emissive(Vec3::new(0.5, 0.5, 1.0), 0.2); 63 | 64 | let light = Material::emissive(Vec3::new(1.0, 1.0, 1.0), 5.0); 65 | let chalk = Material::dielectric(Vec3::new(0.5, 0.5, 0.5), 1.0, 0.0); 66 | let mirror = Material::metal(Vec3::new(0.9, 0.5, 0.5), 0.01); 67 | 68 | let sphere = Sphere::new(Vec3::new(0.0, 1.0, 0.0), 1.0, chalk); 69 | let lamp = Sphere::new(Vec3::new(0.0, 2.0, 2.0), 0.5, light); 70 | let ground = Plane::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 1.0, 0.0), mirror); 71 | 72 | scene.add_trace(Box::new(sphere)); 73 | scene.add_trace(Box::new(lamp)); 74 | scene.add_trace(Box::new(ground)); 75 | 76 | return (scene, camera); 77 | } 78 | 79 | pub fn triangle() -> (Scene, Camera) { 80 | let camera = Camera::new( 81 | Vec3::new(5.0, 5.0, 0.0), 82 | Vec3::new(0.0, 1.0, 0.0), 83 | Vec3::new(0.0, 1.0, 0.0), 84 | 60.0, 85 | RESOLUTION, 86 | 16, 4, 2, 87 | ); 88 | 89 | let mut scene = Scene::empty(); 90 | scene.bg = Material::emissive(Vec3::new(0.0, 0.0, 0.0), 0.0); 91 | 92 | let light = Material::emissive(Vec3::new(1.0, 1.0, 1.0), 2.0); 93 | let chalk = Material::dielectric(Vec3::new(0.5, 0.5, 0.5), 0.5, 1.0); 94 | 95 | let triangle = Triangle::new( 96 | Vec3::new(0.0, 0.0, -1.0), 97 | Vec3::new(0.0, 0.0, 1.0), 98 | Vec3::new(0.0, 2.0, 0.0), 99 | chalk, 100 | ); 101 | 102 | let lamp = Sphere::new(Vec3::new(2.0, 2.0, 2.0), 1.0, light); 103 | 104 | scene.add_trace(Box::new(lamp)); 105 | scene.add_trace(Box::new(triangle)); 106 | 107 | return (scene, camera); 108 | } 109 | 110 | pub fn materials() -> (Scene, Camera) { 111 | let steps = 5.0; 112 | let half = steps / 2.0; 113 | 114 | let camera = Camera::new( 115 | Vec3::new(steps, half, half), 116 | Vec3::new(0.0, half, half), 117 | Vec3::new(0.0, 1.0, 0.0), 118 | 100.0, 119 | (RESOLUTION.1, RESOLUTION.1), 120 | 32, 1, 3, 121 | ); 122 | 123 | let mut scene = Scene::empty(); 124 | scene.bg = Material::emissive(Vec3::new(0.0, 0.0, 0.0), 0.0); 125 | 126 | for x in 0..(steps as usize) { 127 | for y in 0..(steps as usize) { 128 | let material = Material { 129 | color: Vec3::new(0.0, 1.0, 1.0), 130 | emission: 0.0, 131 | metallic: y as f64 / (steps - 1.0), 132 | specular: 1.0, 133 | roughness: 1.0 - x as f64 / (steps - 1.0), 134 | transmission: 0.0, 135 | }; 136 | 137 | let sphere = Sphere::new( 138 | Vec3::new(0.0, x as f64 + 0.5, y as f64 + 0.5), 139 | 0.5, 140 | material 141 | ); 142 | 143 | scene.add_trace(Box::new(sphere)); 144 | } 145 | } 146 | 147 | let light = Sphere::new( 148 | Vec3::new(steps, steps, steps), 149 | steps * 0.5, 150 | Material::emissive(Vec3::new(1.0, 0.0, 1.0), 5.0), 151 | ); 152 | 153 | let light_2 = Sphere::new( 154 | Vec3::new(steps, 0.0, 0.0), 155 | steps * 0.5, 156 | Material::emissive(Vec3::new(1.0, 1.0, 0.0), 5.0), 157 | ); 158 | 159 | let diffuse = Material::dielectric(Vec3::new(1.0, 1.0, 1.0), 0.0, 1.0); 160 | let surface = Plane::new(Vec3::new(-0.5, 0.0, 0.5), Vec3::new(1.0, 0.0, 0.0), diffuse); 161 | 162 | scene.add_trace(Box::new(light)); 163 | scene.add_trace(Box::new(light_2)); 164 | scene.add_trace(Box::new(surface)); 165 | 166 | return (scene, camera); 167 | } 168 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::Path; 3 | // use tokio::prelude::*; 4 | 5 | mod structures; 6 | mod objects; 7 | mod write; 8 | mod render; 9 | mod demo; 10 | 11 | // runs the demo 12 | fn main() { 13 | let output: String = match env::args().nth(1) { 14 | Some(p) => p, 15 | None => { 16 | eprintln!("Expected output path CLI argument"); 17 | return; 18 | }, 19 | }; 20 | 21 | let (scene, camera) = demo::mandelbulb(); 22 | let image = camera.render(scene); 23 | 24 | match write::png(image, Path::new(&output.to_string())) { 25 | Ok(()) => (), 26 | Err(_) => eprintln!("Could not save image!"), 27 | } 28 | } 29 | 30 | // TODO: write tests 31 | -------------------------------------------------------------------------------- /src/objects/mandelbulb.rs: -------------------------------------------------------------------------------- 1 | use std::f64; 2 | 3 | use crate::structures::vec3::Vec3; 4 | use crate::structures::material::Material; 5 | use crate::objects::march::March; 6 | 7 | pub struct Mandelbulb { 8 | pub position: Vec3, 9 | pub power: f64, 10 | pub iterations: usize, 11 | pub material: Material, 12 | } 13 | 14 | fn length(x: f64, y: f64) -> f64 { 15 | (x * x + y * y).sqrt() 16 | } 17 | 18 | impl Mandelbulb { 19 | pub fn new(position: Vec3, power: f64, iterations: usize, material: Material) -> Mandelbulb { 20 | Mandelbulb { 21 | position: position, 22 | power: power, 23 | iterations: iterations, 24 | material: material, 25 | } 26 | } 27 | } 28 | 29 | impl March for Mandelbulb { 30 | fn material(&self) -> Material { self.material } 31 | 32 | fn march(&self, point: Vec3) -> f64 { 33 | let mut zn = point.clone() - self.position; 34 | let mut rad = zn.length(); 35 | let mut d = 1.0; 36 | let sphere = rad - 2.0; 37 | 38 | // approximate bulb as sphere if too far away 39 | if rad > 2.5 { return sphere; } 40 | 41 | for _ in 0..self.iterations { 42 | rad = zn.length(); 43 | if rad > 2.0 { break; } 44 | 45 | let th = length(zn.x, zn.y).atan2(zn.z); 46 | let phi = zn.y.atan2(zn.x); 47 | let rado = rad.powi(8); 48 | d = rad.powi(7) * 7.0 * d + 1.0; 49 | 50 | let sint = (th * self.power).sin(); 51 | zn.x = rado * sint * (phi * self.power).cos(); 52 | zn.y = rado * sint * (phi * self.power).sin(); 53 | zn.z = rado * (th * self.power).cos(); 54 | zn = zn + (point - self.position); 55 | } 56 | 57 | return 0.5 * rad.ln() * rad / d; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/objects/march.rs: -------------------------------------------------------------------------------- 1 | use crate::structures::material::Material; 2 | use crate::structures::cast::Cast; 3 | use crate::structures::vec3::Vec3; 4 | use crate::structures::ray::Ray; 5 | 6 | use crate::render::EPSILON; 7 | pub const MAX_STEPS: usize = 64; 8 | pub const MAX_DEPTH: f64 = 40.0; 9 | 10 | pub trait March: Send + Sync { 11 | fn material(&self) -> Material; 12 | fn march(&self, point: Vec3) -> f64; // distance to nearest point 13 | } 14 | 15 | impl dyn March { 16 | fn sdf(point: Vec3, march: &Vec>) -> (f64, Material) { 17 | let mut min = f64::MAX; 18 | let mut mat = Material::sky(); 19 | 20 | for object in march.iter() { 21 | let distance = object.march(point); 22 | 23 | if distance <= min { 24 | min = distance; 25 | mat = object.material(); 26 | } 27 | } 28 | 29 | return (min, mat); 30 | } 31 | 32 | // TODO: replace with faster normal epsilon sample technique 33 | fn normal(p: Vec3, march: &Vec>) -> Vec3 { 34 | Vec3::new( 35 | March::sdf(Vec3::new(p.x + EPSILON, p.y, p.z), march).0 - March::sdf(Vec3::new(p.x - EPSILON, p.y, p.z), march).0, 36 | March::sdf(Vec3::new(p.x, p.y + EPSILON, p.z), march).0 - March::sdf(Vec3::new(p.x, p.y - EPSILON, p.z), march).0, 37 | March::sdf(Vec3::new(p.x, p.y, p.z + EPSILON), march).0 - March::sdf(Vec3::new(p.x, p.y, p.z - EPSILON), march).0, 38 | ).unit() 39 | } 40 | 41 | pub fn hit(march: &Vec>, ray: Ray) -> Option { 42 | let mut depth = EPSILON; 43 | 44 | for _step in 0..MAX_STEPS { 45 | let point = ray.point_at(&depth); 46 | let (distance, material) = March::sdf(point, march); 47 | 48 | if distance <= EPSILON { 49 | let normal = March::normal(point, march); 50 | return Some(Cast { distance: depth, normal, material }); 51 | } 52 | 53 | if distance >= MAX_DEPTH { break; } 54 | depth += distance; 55 | } 56 | return None; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/objects/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod march; 2 | pub mod trace; 3 | 4 | pub mod sphere; 5 | pub mod plane; 6 | pub mod mandelbulb; 7 | pub mod triangle; 8 | -------------------------------------------------------------------------------- /src/objects/plane.rs: -------------------------------------------------------------------------------- 1 | use crate::structures::vec3::Vec3; 2 | use crate::structures::ray::Ray; 3 | use crate::structures::material::Material; 4 | use crate::objects::trace::Trace; 5 | 6 | #[derive(Debug, Copy, Clone)] 7 | pub struct Plane { 8 | pub position: Vec3, 9 | pub normal: Vec3, 10 | pub material: Material, 11 | } 12 | 13 | impl Plane { 14 | pub fn new(position: Vec3, normal: Vec3, material: Material) -> Plane { 15 | Plane { 16 | position: position, 17 | normal: normal, 18 | material: material, 19 | } 20 | } 21 | } 22 | 23 | impl Trace for Plane { 24 | fn material(&self) -> Material { self.material } 25 | 26 | fn trace(&self, ray: Ray) -> Option<(f64, Vec3)> { 27 | let denom = self.normal.dot(&ray.direction); 28 | if denom.abs() > 0.0 { 29 | let t = (self.position - ray.origin).dot(&self.normal) / denom; 30 | 31 | if t >= 0.0 { 32 | return Some((t, self.normal)); 33 | } 34 | } 35 | 36 | return None; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/objects/sphere.rs: -------------------------------------------------------------------------------- 1 | use crate::structures::vec3::Vec3; 2 | use crate::structures::ray::Ray; 3 | use crate::structures::material::Material; 4 | use crate::objects::march::March; 5 | use crate::objects::trace::Trace; 6 | 7 | #[derive(Debug, Copy, Clone)] 8 | pub struct Sphere { 9 | pub position: Vec3, 10 | pub radius: f64, 11 | pub material: Material, 12 | } 13 | 14 | impl Sphere { 15 | pub fn new(position: Vec3, radius: f64, material: Material) -> Sphere { 16 | Sphere { 17 | position: position, 18 | radius: radius, 19 | material: material, 20 | } 21 | } 22 | } 23 | 24 | impl Trace for Sphere { 25 | fn material(&self) -> Material { self.material } 26 | 27 | fn trace(&self, ray: Ray) -> Option<(f64, Vec3)> { 28 | let oc = ray.origin - self.position; 29 | 30 | let a = ray.direction.dot(&ray.direction); 31 | let b = oc.dot(&ray.direction); 32 | let c = oc.dot(&oc) - self.radius * self.radius; 33 | let disc = (b * b) - (a * c); 34 | 35 | let distance = ((0.0 - b - disc.sqrt()) / a).min((0.0 - b + disc.sqrt()) / a); 36 | let normal = (ray.point_at(&distance) - self.position).unit(); 37 | 38 | return if disc > 0.0 { Some((distance, normal)) } else { None }; 39 | } 40 | } 41 | 42 | impl March for Sphere { 43 | fn material(&self) -> Material { self.material } 44 | 45 | fn march(&self, point: Vec3) -> f64 { 46 | (point - self.position).length() - self.radius // TODO modulo with 6 for infinite rep. 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/objects/trace.rs: -------------------------------------------------------------------------------- 1 | use crate::structures::vec3::Vec3; 2 | use crate::structures::ray::Ray; 3 | use crate::structures::material::Material; 4 | use crate::structures::cast::Cast; 5 | use crate::render::EPSILON; 6 | 7 | pub trait Trace: Send + Sync { 8 | fn material(&self) -> Material; 9 | fn trace(&self, ray: Ray) -> Option<(f64, Vec3)>; // distance, normal 10 | } 11 | 12 | impl dyn Trace { 13 | pub fn hit(trace: &Vec>, ray: Ray) -> Option { 14 | let mut best: Option = None; 15 | 16 | for object in trace.iter() { 17 | let (distance, normal) = match object.trace(ray) { 18 | Some(v) => v, 19 | None => continue, 20 | }; 21 | 22 | let visible = distance > EPSILON; 23 | let closer = if let Some(cast) = best { distance < cast.distance } else { true }; 24 | let cast = Cast { distance, normal, material: object.material() }; 25 | 26 | if visible && closer { 27 | best = Some(cast); 28 | } 29 | } 30 | 31 | return best; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/objects/triangle.rs: -------------------------------------------------------------------------------- 1 | use crate::structures::vec3::Vec3; 2 | use crate::structures::ray::Ray; 3 | use crate::structures::material::Material; 4 | use crate::objects::trace::Trace; 5 | 6 | use crate::render::EPSILON; 7 | 8 | // TODO: maybe make material a wrapper around marches and traces? 9 | pub struct Triangle { 10 | a: Vec3, 11 | b: Vec3, 12 | c: Vec3, 13 | material: Material, 14 | } 15 | 16 | impl Triangle { 17 | pub fn new(a: Vec3, b: Vec3, c: Vec3, material: Material) -> Triangle { 18 | Triangle { a, b, c, material } 19 | } 20 | } 21 | 22 | impl Trace for Triangle { 23 | fn material(&self) -> Material { 24 | self.material 25 | } 26 | 27 | fn trace(&self, ray: Ray) -> Option<(f64, Vec3)> { 28 | // triangle degined by vertices self.a, self.b and self.c 29 | let ab = self.b - self.a; 30 | let ac = self.c - self.a; 31 | let rov0 = ray.origin - self.a; 32 | let n = ab.cross(&ac); 33 | let q = rov0.cross(&ray.direction); 34 | let d = 1.0 / (ray.direction.dot(&n)); 35 | let u = d * (0.0 - q).dot(&ac); 36 | let v = d * q.dot(&ab); 37 | let t = d * (0.0 - n).dot(&rov0); 38 | let hit = u.min(v.min(t.min(1.0 - (u + v)))) > EPSILON; 39 | 40 | return if hit { 41 | Some(((Vec3::new(t, u, v) - ray.origin).length(), n.unit())) 42 | } else { 43 | None 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/render.rs: -------------------------------------------------------------------------------- 1 | use std::f64; 2 | use rand::Rng; 3 | 4 | use crate::structures::vec3::Vec3; 5 | use crate::structures::ray::Ray; 6 | use crate::structures::material::Material; 7 | use crate::structures::scene::Scene; 8 | use crate::structures::cast::Cast; 9 | use crate::structures::camera::Camera; 10 | use crate::objects::march::March; 11 | use crate::objects::trace::Trace; 12 | 13 | pub const EPSILON: f64 = 0.0005; 14 | 15 | fn cast_ray(scene: &Scene, ray: Ray) -> Option { 16 | let march = March::hit(&scene.march, ray); 17 | let trace = Trace::hit(&scene.trace, ray); 18 | 19 | match (march, trace) { 20 | (None, None) => None, 21 | (None, t @ Some(_)) => t, 22 | (m @ Some(_), None) => m, 23 | // trace results are more exact, so favor in a tie. 24 | (Some(m), Some(t)) => Some(if m.distance < t.distance { m } else { t }), 25 | } 26 | } 27 | 28 | fn sample_sphere() -> Vec3 { 29 | let mut rng = rand::thread_rng(); 30 | let mut point: Vec3 = Vec3::max(); 31 | 32 | // sample point in unit cube, check if in unit sphere 33 | while point.length_squared() >= 1.0 { 34 | point = Vec3::new( 35 | rng.gen::() * 2.0 - 1.0, 36 | rng.gen::() * 2.0 - 1.0, 37 | rng.gen::() * 2.0 - 1.0, 38 | ); 39 | } 40 | 41 | return point; 42 | } 43 | 44 | fn sample_sphere_surface() -> Vec3 { 45 | sample_sphere().unit() 46 | } 47 | 48 | fn reflect(v: Vec3, n: Vec3) -> Vec3 { 49 | return v - 2.0 * v.dot(&n) * n; 50 | } 51 | 52 | // adapted from Casual Shadertoy Path Tracing Part III (demofox.org) 53 | fn fresnel(ior: f64, normal: Vec3, ray: Ray) -> f64 { 54 | let mut r0: f64 = (1.0 - ior)/(1.0 + ior); 55 | let cosine = -normal.dot(&ray.direction); 56 | r0 = r0*r0; 57 | let unclamped = r0 + (1.0-r0) * (1.0-cosine).powi(5); 58 | return (0.0 as f64).max(unclamped.min(1.0)); 59 | } 60 | 61 | fn refract(v: &Vec3, n: &Vec3, ni_over_nt: f64, refracted: &mut Vec3) -> bool { 62 | let uv: Vec3 = v.unit(); 63 | let dt: f64 = uv.dot(&n); 64 | 65 | let discriminant: f64 = 1.0 - ni_over_nt*ni_over_nt*(1.0 - dt*dt); 66 | if discriminant > 0.0 { 67 | *refracted = ni_over_nt*(uv - *n*dt) - *n*((discriminant).sqrt()); 68 | return true; 69 | } else { 70 | return false; 71 | } 72 | } 73 | 74 | // simplify 75 | fn color(scene: &Scene, ray: Ray, bounce: usize, branches: usize) -> Vec3 { 76 | let (distance, normal, material) = match cast_ray(&scene, ray) { 77 | Some(cast) if bounce != 0 => (cast.distance, cast.normal, cast.material), 78 | // hit the sky or traced for too long 79 | Some(_) => return Vec3::new(0.0, 0.0, 0.0), 80 | _ => return scene.bg.color * scene.bg.emission, 81 | }; 82 | 83 | // uncomment to debug depth map: 84 | // return Vec3::new(1.0/distance, 1.0/distance, 1.0/distance); 85 | 86 | // uncomment to debug normal map: 87 | // return (normal + 1.0) * 0.5; 88 | 89 | let position = ray.point_at(&distance); 90 | let mut diffuse = Vec3::new(0.0, 0.0, 0.0); 91 | let mut specular = Vec3::new(0.0, 0.0, 0.0); 92 | let transmission = Vec3::new(0.0, 0.0, 0.0); 93 | 94 | // diffuse 95 | for _ in 0..branches { 96 | let scatter = Ray::new(position, (sample_sphere_surface() + normal).unit()); 97 | let sample = color(&scene, scatter, bounce - 1, 1); // (samples / 2).max(1)); // only take one sample 98 | 99 | diffuse = diffuse + material.color * sample; 100 | } 101 | 102 | diffuse = diffuse / (branches as f64); 103 | 104 | // specular 105 | for _ in 0..branches { 106 | // let scatter = Ray::new(position, reflect(ray.direction, normal).unit()); 107 | let scatter = Ray::new( 108 | position, 109 | reflect(ray.direction, normal + (sample_sphere() * material.roughness)), 110 | ); 111 | 112 | let sample = color(&scene, scatter, bounce - 1, (branches / 2).max(1)); 113 | specular = specular + sample; 114 | } 115 | 116 | specular = specular / (branches as f64); 117 | 118 | // this calculation of IOR looks fine, 119 | // but specular is defined as a percent, 120 | // so this might not be correct 121 | let sqrtm = material.specular.sqrt(); 122 | let ior = (1.0 - sqrtm * 0.28) / (sqrtm * 0.28 + 1.0); // 0.28 is ~ sqrt(0.08) 123 | let f = fresnel(ior, normal, ray); 124 | // return Vec3::new(f, f, f); 125 | 126 | return pbr(material, transmission, diffuse, specular, f); 127 | } 128 | 129 | // combine samples in a PBR manner 130 | pub fn pbr( 131 | material: Material, 132 | transmission: Vec3, 133 | diffuse: Vec3, 134 | specular: Vec3, 135 | fresnel: f64, 136 | ) -> Vec3 { 137 | // TODO: transmission 138 | 139 | // mix transparent and diffuse 140 | let base = (transmission * material.transmission) + diffuse * (1.0 - material.transmission); 141 | 142 | // with a specular layer on top 143 | let dielectric = (specular * fresnel) + base * (1.0 - fresnel); 144 | // for metallic materials 145 | let electric = specular * material.color; 146 | 147 | // lerp electric and dielectric 148 | let non_emmisive = (electric * material.metallic) + dielectric * (1.0 - material.metallic); 149 | let combined = non_emmisive + material.color * material.emission; 150 | 151 | // final color. 152 | return combined; 153 | } 154 | 155 | pub fn sample( 156 | scene: &Scene, 157 | camera: &Camera, 158 | rng: &mut impl Rng, 159 | u: f64, v: f64 160 | ) -> Vec3 { 161 | let mut aliased = Vec3::new(0.0, 0.0, 0.0); 162 | 163 | for _s in 0..camera.aa { 164 | // shake pixel around 165 | let (x, y) = (u + rng.gen::(), v + rng.gen::()); 166 | let ray = camera.make_ray(x, y); 167 | 168 | // cast ray 169 | aliased = aliased + color(&scene, ray, camera.bounces, camera.branch); 170 | } 171 | 172 | return aliased / (camera.aa as f64); 173 | } 174 | -------------------------------------------------------------------------------- /src/structures/camera.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | use std::thread; 3 | use std::sync::Arc; 4 | use num_cpus; 5 | 6 | use crate::structures::vec3::Vec3; 7 | use crate::structures::ray::Ray; 8 | use crate::structures::scene::Scene; 9 | use crate::render::sample; 10 | 11 | #[derive(Debug, Copy, Clone)] 12 | pub struct Camera { 13 | pub ray: Ray, // position and direction of camera 14 | pub up: Vec3, // up vector of camera 15 | pub fov: f64, // frame of view, in degrees 16 | pub reso: (usize, usize), // resolution of camera, in pixels 17 | 18 | pub aa: usize, // samples per pixel 19 | pub branch: usize, // branches per bounce (tree-based path tracing) 20 | pub bounces: usize, // (maximum) number of bounces 21 | } 22 | 23 | impl Camera { 24 | pub fn new( 25 | from: Vec3, to: Vec3, up: Vec3, 26 | fov: f64, reso: (usize, usize), 27 | aa: usize, branch: usize, bounces: usize, 28 | ) -> Camera { 29 | let f = (to - from).unit(); 30 | Camera { 31 | ray: Ray::new(from, f), 32 | up, fov, reso, 33 | aa, branch, bounces, 34 | } 35 | } 36 | 37 | fn ratio(&self) -> f64 { 38 | (self.width() as f64) / (self.height() as f64) 39 | } 40 | 41 | pub fn width(&self) -> usize { self.reso.0 } 42 | pub fn height(&self) -> usize { self.reso.1 } 43 | 44 | // uv is resolution coordinate + noise 45 | // x, y is pixel location on camera sensor 46 | pub fn make_ray(&self, x: f64, y: f64) -> Ray { 47 | // normalize coordinates 48 | let (mut u, v) = (x / (self.width() as f64), y / (self.height() as f64)); 49 | u *= self.ratio(); 50 | let (u, v) = (u - self.ratio() * 0.5, v - 0.5); 51 | 52 | // find direction 53 | let z = 1.0 / (self.fov.to_radians() / 2.0).tan(); 54 | let dir = Vec3::new(u, v, -z).unit(); 55 | 56 | // translate the ray 57 | let f = self.ray.direction; 58 | let s = (f.cross(&self.up)).unit(); 59 | let u = s.cross(&f); 60 | let r = dir; 61 | 62 | // return the new ray. 63 | return Ray::new( 64 | self.ray.origin, 65 | // cross product 66 | Vec3::new( 67 | (r.x * s.x) + (r.y * u.x) + (r.z * -f.x), 68 | (r.x * s.y) + (r.y * u.y) + (r.z * -f.y), 69 | (r.x * s.z) + (r.y * u.z) + (r.z * -f.z), 70 | ), 71 | ) 72 | } 73 | 74 | pub fn render(self, scene: Scene) -> Vec> { 75 | // display rendering information 76 | println!("Render Information\n"); 77 | 78 | println!("rendering {} pixel(s):", self.reso.0 * self.reso.1); 79 | println!(" - {} row(s)", self.reso.1); 80 | println!(" - {} column(s)\n", self.reso.0); 81 | 82 | println!("taking {} samples(s) per pixel:", self.aa * self.branch.pow(self.bounces as u32)); 83 | println!(" - {} base sample(s) for AA", self.aa); 84 | println!(" - {} bounce(s) per sample", self.bounces); 85 | println!(" - {} branch(es) per bounce\n", self.branch); 86 | 87 | println!("scene has {} object(s):", scene.trace.len() + scene.march.len()); 88 | println!(" - {} traced object(s)", scene.trace.len()); 89 | println!(" - {} marched object(s)\n", scene.march.len()); 90 | 91 | let camera = Arc::new(self); 92 | let scene = Arc::new(scene); 93 | 94 | let num_workers = num_cpus::get(); 95 | println!("automatically detected {} cpu core(s):", num_workers); 96 | 97 | let mut workers = vec![]; 98 | for worker in 0..num_workers { 99 | let start = self.height() * worker / num_workers; 100 | let stop = self.height() * (worker + 1) / num_workers; 101 | 102 | let camera_clone = Arc::clone(&camera); 103 | let scene_clone = Arc::clone(&scene); 104 | 105 | workers.push(thread::spawn(move || { 106 | Camera::section(camera_clone, scene_clone, start, stop, worker + 1) 107 | })); 108 | } 109 | 110 | let mut result = vec![]; 111 | 112 | for worker in workers { 113 | result.append(&mut worker.join().expect("thread failed to return value")) 114 | } 115 | 116 | println!(); 117 | return result; 118 | } 119 | 120 | pub fn section( 121 | self: Arc, scene: Arc, 122 | start: usize, stop: usize, id: usize, 123 | ) -> Vec> { 124 | let mut rng = rand::thread_rng(); 125 | let mut image = vec![]; 126 | 127 | for y in start..stop { 128 | let mut row = vec![]; 129 | 130 | for x in 0..self.width() { 131 | row.push( 132 | sample(&scene, &self, &mut rng, x as f64, (self.height() - y) as f64) 133 | ); 134 | } 135 | 136 | image.push(row); 137 | print!("\r - worker {} is {}% done", id, (y - start + 1) * 100 / (stop - start)); 138 | std::io::stdout().flush().ok().expect("Could not flush stdout"); 139 | } 140 | 141 | println!(); 142 | return image; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/structures/cast.rs: -------------------------------------------------------------------------------- 1 | use std::f64; 2 | 3 | use crate::structures::vec3::Vec3; 4 | use crate::structures::material::Material; 5 | 6 | #[derive(Debug, Copy, Clone)] 7 | pub struct Cast { 8 | pub distance: f64, 9 | pub normal: Vec3, 10 | pub material: Material, 11 | } 12 | -------------------------------------------------------------------------------- /src/structures/material.rs: -------------------------------------------------------------------------------- 1 | use crate::structures::vec3::Vec3; 2 | 3 | // TODO: derive debug.. etc. for other structs 4 | #[derive(Debug, Copy, Clone)] 5 | pub struct Material { 6 | pub color: Vec3, // color 7 | pub emission: f64, // how strong? 8 | 9 | pub metallic: f64, 10 | pub specular: f64, 11 | pub roughness: f64, 12 | 13 | pub transmission: f64, 14 | // pub ior: f64, 15 | } 16 | 17 | // ior and specular are correlated, remove one or the other? 18 | 19 | impl Material { 20 | // TODO 21 | 22 | pub fn sky() -> Material { 23 | Material::emissive(Vec3::new(0.2, 1.0, 0.8), 0.6) 24 | } 25 | 26 | pub fn emissive(color: Vec3, emission: f64) -> Material { 27 | Material { 28 | color, 29 | emission, 30 | 31 | metallic: 0.0, 32 | specular: 0.0, 33 | roughness: 0.0, 34 | 35 | transmission: 0.0, 36 | // ior: 1.0, 37 | } 38 | } 39 | 40 | pub fn metal(color: Vec3, roughness: f64) -> Material { 41 | Material { 42 | color, 43 | emission: 0.0, 44 | 45 | metallic: 1.0, 46 | specular: 1.0, 47 | roughness, 48 | 49 | transmission: 0.0, 50 | } 51 | } 52 | 53 | pub fn dielectric(color: Vec3, specular: f64, roughness: f64) -> Material { 54 | Material { 55 | color, 56 | emission: 0.0, 57 | 58 | metallic: 0.0, 59 | specular, 60 | roughness, 61 | 62 | transmission: 0.0, 63 | } 64 | } 65 | 66 | // TODO: transparent 67 | // convert ior to specular using polynomial approx 68 | } 69 | -------------------------------------------------------------------------------- /src/structures/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod vec3; 2 | pub mod ray; 3 | pub mod material; 4 | pub mod camera; 5 | pub mod scene; 6 | pub mod cast; 7 | -------------------------------------------------------------------------------- /src/structures/ray.rs: -------------------------------------------------------------------------------- 1 | use crate::structures::vec3::Vec3; 2 | 3 | #[derive(Debug, Copy, Clone)] 4 | pub struct Ray { 5 | pub origin: Vec3, 6 | pub direction: Vec3, 7 | } 8 | 9 | impl Ray { 10 | pub fn new(origin: Vec3, direction: Vec3) -> Ray { 11 | Ray { 12 | origin: origin, 13 | direction: direction, 14 | } 15 | } 16 | 17 | pub fn through(origin: Vec3, to: Vec3) -> Ray { 18 | Ray { 19 | origin: origin, 20 | direction: (origin - to).unit() 21 | } 22 | } 23 | 24 | pub fn point_at(&self, distance: &f64) -> Vec3 { 25 | self.origin + self.direction * (*distance) 26 | } 27 | } 28 | 29 | impl PartialEq for Ray { 30 | fn eq(&self, other: &Ray) -> bool { 31 | (self.origin == other.origin) && (self.direction == other.direction) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/structures/scene.rs: -------------------------------------------------------------------------------- 1 | use crate::structures::material::Material; 2 | use crate::objects::march::March; 3 | use crate::objects::trace::Trace; 4 | 5 | pub struct Scene { 6 | pub march: Vec>, 7 | pub trace: Vec>, 8 | pub bg: Material, 9 | } 10 | 11 | impl Scene { 12 | pub fn empty() -> Scene { 13 | Scene { march: vec![], trace: vec![], bg: Material::sky() } 14 | } 15 | 16 | pub fn add_march(&mut self, march: Box) { 17 | self.march.push(march); 18 | } 19 | 20 | pub fn add_trace(&mut self, trace: Box) { 21 | self.trace.push(trace); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/structures/vec3.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Sub, Mul, Div}; 2 | use std::f64; 3 | 4 | #[derive(Debug, Copy, Clone)] 5 | pub struct Vec3 { 6 | pub x: f64, 7 | pub y: f64, 8 | pub z: f64, 9 | } 10 | 11 | // here's everything Vec3 should implement: 12 | // - dot and cross product 13 | // - length, squared length, and unit vector 14 | // - ... 15 | 16 | impl Vec3 { 17 | pub fn new(x: f64, y: f64, z: f64) -> Vec3 { 18 | Vec3 { x: x, y: y, z: z } 19 | } 20 | 21 | pub fn max() -> Vec3 { 22 | Vec3 { 23 | x: f64::MAX, 24 | y: f64::MAX, 25 | z: f64::MAX, 26 | } 27 | } 28 | 29 | pub fn dot(&self, other: &Vec3) -> f64 { 30 | (self.x * other.x) + (self.y * other.y) + (self.z * other.z) 31 | } 32 | 33 | pub fn cross(&self, other: &Vec3) -> Vec3 { 34 | Vec3 { 35 | x: (self.y * other.z) - (self.z * other.y), 36 | y: (self.z * other.x) - (self.x * other.z), 37 | z: (self.x * other.y) - (self.y * other.x), 38 | } 39 | } 40 | 41 | pub fn length_squared(&self) -> f64 { 42 | (self.x * self.x) + (self.y * self.y) + (self.z * self.z) 43 | } 44 | 45 | pub fn length(&self) -> f64 { 46 | self.length_squared().sqrt() 47 | } 48 | 49 | pub fn unit(&self) -> Vec3 { 50 | let length = self.length(); 51 | 52 | Vec3 { 53 | x: self.x / length, 54 | y: self.y / length, 55 | z: self.z / length, 56 | } 57 | } 58 | 59 | pub fn total(&self) -> f64 { 60 | self.x + self.y + self.z 61 | } 62 | 63 | pub fn abs(&self) -> Vec3 { 64 | Vec3 { 65 | x: self.x.abs(), 66 | y: self.y.abs(), 67 | z: self.z.abs(), 68 | } 69 | } 70 | 71 | pub fn colorize(&self, exposure: f64) -> [u8; 3] { 72 | // TODO: simplify 73 | 74 | // remove colors less than 0 75 | // this shouldn't happen, but just in case 76 | let mut color = Vec3 { 77 | x: self.x.max(0.0), 78 | y: self.y.max(0.0), 79 | z: self.z.max(0.0), 80 | }; 81 | 82 | // compress colorspace 83 | // note, in the future, k should be average color across all channels 84 | 85 | color = (3.0 * color) / (2.0 * exposure + color); 86 | 87 | { 88 | let away = (color.y - 1.0).max(0.0) + (color.z - 1.0).max(0.0); 89 | color.x = (color.x.min(1.0)) + (away - (away - (1.0 - color.x).max(0.0)).max(0.0)); 90 | } 91 | 92 | { 93 | let away = (color.x - 1.0).max(0.0) + (color.z - 1.0).max(0.0); 94 | color.y = (color.y.min(1.0)) + (away - (away - (1.0 - color.y).max(0.0)).max(0.0)); 95 | } 96 | 97 | { 98 | let away = (color.x - 1.0).max(0.0) + (color.y - 1.0).max(0.0); 99 | color.z = (color.z.min(1.0)) + (away - (away - (1.0 - color.z).max(0.0)).max(0.0)); 100 | } 101 | 102 | // gamma correction and range normalization 103 | color.x = color.x.sqrt() * 255.9; 104 | color.y = color.y.sqrt() * 255.9; 105 | color.z = color.z.sqrt() * 255.9; 106 | 107 | return [color.x as u8, color.y as u8, color.z as u8]; 108 | } 109 | 110 | pub fn print(&self) { 111 | println!("{:?}", (self.x, self.y, self.z)) 112 | } 113 | } 114 | 115 | // - ... 116 | // - Vec3 adding Vec3s and scalars 117 | // - ... 118 | 119 | impl Add for Vec3 { 120 | type Output = Vec3; 121 | 122 | fn add(self, other: Vec3) -> Vec3 { 123 | Vec3 { 124 | x: self.x + other.x, 125 | y: self.y + other.y, 126 | z: self.z + other.z, 127 | } 128 | } 129 | } 130 | 131 | impl Add for Vec3 { 132 | type Output = Vec3; 133 | 134 | fn add(self, other: f64) -> Vec3 { 135 | Vec3 { 136 | x: self.x + other, 137 | y: self.y + other, 138 | z: self.z + other, 139 | } 140 | } 141 | } 142 | 143 | impl Add for f64 { 144 | type Output = Vec3; 145 | 146 | fn add(self, other: Vec3) -> Vec3 { 147 | Vec3 { 148 | x: self + other.x, 149 | y: self + other.y, 150 | z: self + other.z, 151 | } 152 | } 153 | } 154 | 155 | // - ... 156 | // - Vec3 subtracting Vec3s and scalars 157 | // - ... 158 | 159 | impl Sub for Vec3 { 160 | type Output = Vec3; 161 | 162 | fn sub(self, other: Vec3) -> Vec3 { 163 | Vec3 { 164 | x: self.x - other.x, 165 | y: self.y - other.y, 166 | z: self.z - other.z, 167 | } 168 | } 169 | } 170 | 171 | impl Sub for Vec3 { 172 | type Output = Vec3; 173 | 174 | fn sub(self, other: f64) -> Vec3 { 175 | Vec3 { 176 | x: self.x - other, 177 | y: self.y - other, 178 | z: self.z - other, 179 | } 180 | } 181 | } 182 | 183 | impl Sub for f64 { 184 | type Output = Vec3; 185 | 186 | fn sub(self, other: Vec3) -> Vec3 { 187 | Vec3 { 188 | x: self - other.x, 189 | y: self - other.y, 190 | z: self - other.z, 191 | } 192 | } 193 | } 194 | 195 | // - ... 196 | // - Vec3 piecewise multiplication for Vec3s and scalars 197 | // - ... 198 | 199 | impl Mul for Vec3 { 200 | type Output = Vec3; 201 | 202 | fn mul(self, other: Vec3) -> Vec3 { 203 | Vec3 { 204 | x: self.x * other.x, 205 | y: self.y * other.y, 206 | z: self.z * other.z, 207 | } 208 | } 209 | } 210 | 211 | impl Mul for Vec3 { 212 | type Output = Vec3; 213 | 214 | fn mul(self, other: f64) -> Vec3 { 215 | Vec3 { 216 | x: self.x * other, 217 | y: self.y * other, 218 | z: self.z * other, 219 | } 220 | } 221 | } 222 | 223 | impl Mul for f64 { 224 | type Output = Vec3; 225 | 226 | fn mul(self, other: Vec3) -> Vec3 { 227 | Vec3 { 228 | x: self * other.x, 229 | y: self * other.y, 230 | z: self * other.z, 231 | } 232 | } 233 | } 234 | 235 | // - ... 236 | // - divide vec3 by scalar 237 | // - ... 238 | 239 | fn clamp(number: f64) -> f64 { 240 | // get rid of infinities? 241 | return if number.abs() == (1.0 / 0.0) { f64::MAX } else { number } 242 | } 243 | 244 | impl Div for Vec3 { 245 | type Output = Vec3; 246 | 247 | fn div(self, other: Vec3) -> Vec3 { 248 | Vec3 { 249 | x: clamp(self.x / other.x), 250 | y: clamp(self.y / other.y), 251 | z: clamp(self.z / other.z), 252 | } 253 | } 254 | } 255 | 256 | impl Div for Vec3 { 257 | type Output = Vec3; 258 | 259 | fn div(self, other: f64) -> Vec3 { 260 | Vec3 { 261 | x: clamp(self.x / other), 262 | y: clamp(self.y / other), 263 | z: clamp(self.z / other), 264 | } 265 | } 266 | } 267 | 268 | // - ... 269 | // - equality 270 | 271 | impl PartialEq for Vec3 { 272 | fn eq(&self, other: &Vec3) -> bool { 273 | (self.x == other.x) && (self.y == other.y) && (self.z == other.z) 274 | } 275 | } 276 | 277 | // and now, some tests 278 | 279 | #[cfg(test)] 280 | pub mod test { 281 | use super::Vec3; 282 | 283 | #[test] 284 | fn test_new() { 285 | let vec = Vec3::new(1.0, -2.0, 0.3); 286 | vec.print(); 287 | } 288 | 289 | #[test] 290 | fn test_xyz() { 291 | let vec = Vec3::new(1.0, -2.0, 0.3); 292 | assert_eq!(vec.x, 1.0); 293 | assert_eq!(vec.y, -2.0); 294 | assert_eq!(vec.z, 0.3); 295 | } 296 | 297 | #[test] 298 | fn test_add_vec() { 299 | let vec = Vec3::new(1.0, -2.0, 0.3); 300 | let other = Vec3::new(-2.0, -7.3, 1.2); 301 | 302 | let test = Vec3::new(-1.0, -9.3, 1.5); 303 | 304 | // Vec3 and Vec3 305 | assert_eq!( 306 | vec + other, 307 | test, 308 | ); 309 | } 310 | 311 | #[test] 312 | fn test_add_scalar() { 313 | let vec = Vec3::new(1.0, -2.0, 0.3); 314 | let scalar = 93.8; 315 | 316 | let test = Vec3::new(94.8, 91.8, 94.1); 317 | 318 | // Vec3 and scalar 319 | assert_eq!( 320 | vec + scalar, 321 | test, 322 | ); 323 | } 324 | 325 | #[test] 326 | fn test_sub_vec() { 327 | let vec = Vec3::new(1.0, -2.0, 0.3); 328 | let other = Vec3::new(-2.0, -7.3, 1.2); 329 | 330 | let test = Vec3::new(3.0, 5.3, -0.8999999999999999); 331 | 332 | // Vec3 and Vec3 333 | assert_eq!( 334 | vec - other, 335 | test, 336 | ); 337 | } 338 | 339 | #[test] 340 | fn test_sub_scalar() { 341 | let vec = Vec3::new(1.0, -2.0, 0.3); 342 | let scalar = 93.8; 343 | 344 | let test = Vec3::new(-92.8, -95.8, -93.5); 345 | 346 | // Vec3 and scalar 347 | assert_eq!( 348 | vec - scalar, 349 | test, 350 | ); 351 | } 352 | 353 | #[test] 354 | fn test_mul_vec() { 355 | let vec = Vec3::new(1.0, -2.0, 0.3); 356 | let other = Vec3::new(-2.0, -7.3, 1.2); 357 | 358 | let test = Vec3::new(-2.0, 14.6, 0.36); 359 | 360 | // Vec3 and Vec3 361 | assert_eq!( 362 | vec * other, 363 | test, 364 | ); 365 | } 366 | 367 | #[test] 368 | fn test_mul_scalar() { 369 | let vec = Vec3::new(1.0, -2.0, 0.3); 370 | let scalar = 93.8; 371 | 372 | let test = Vec3::new(93.8, -187.6, 28.139999999999997); 373 | 374 | // Vec3 and scalar 375 | assert_eq!(vec * scalar, test); 376 | } 377 | 378 | #[test] 379 | fn test_div_vec() { 380 | let vec = Vec3::new(1.0, -2.0, 0.3); 381 | let other = Vec3::new(-2.0, -7.3, 1.2); 382 | 383 | let test = Vec3::new(-0.5, 0.273972602739726, 0.25); 384 | 385 | // Vec3 and Vec3 386 | assert_eq!(vec / other, test); 387 | } 388 | 389 | #[test] 390 | fn test_div_scalar() { 391 | // TODO: div by 0 392 | let vec = Vec3::new(1.0, -2.0, 0.3); 393 | let scalar = 4.0; 394 | 395 | let test = Vec3::new(0.25, -0.5, 0.075); 396 | 397 | // Vec3 and scalar 398 | assert_eq!( 399 | vec / scalar, 400 | test, 401 | ); 402 | } 403 | 404 | #[test] 405 | fn test_dot() { 406 | let vec: Vec3 = Vec3::new(0.2, 0.4, 0.7); 407 | let other: Vec3 = Vec3::new(0.1, 0.3, 0.3); 408 | 409 | assert_eq!(vec.dot(&other), 0.35); 410 | } 411 | 412 | #[test] 413 | fn test_cross() { 414 | let vec: Vec3 = Vec3::new(3.0, -3.0, 1.0); 415 | let other: Vec3 = Vec3::new(4.0, 9.0, 2.0); 416 | 417 | let test: Vec3 = Vec3::new(-15.0, -2.0, 39.0); 418 | 419 | assert_eq!( 420 | vec.cross(&other), 421 | test, 422 | ); 423 | } 424 | 425 | #[test] 426 | fn test_length() { 427 | let vec = Vec3::new(0.0, -3.0, 4.0); 428 | assert_eq!(vec.length(), 5.0); 429 | } 430 | 431 | #[test] 432 | fn test_length_squared() { 433 | let vec = Vec3::new(5.0, -3.0, 4.0); 434 | assert_eq!(vec.length_squared(), 50.0); 435 | } 436 | 437 | #[test] 438 | fn test_unit() { 439 | let vec = Vec3::new(0.0, -3.0, 4.0); 440 | let test = Vec3::new(0.0, -0.6, 0.8); 441 | 442 | assert_eq!( 443 | vec.unit(), 444 | test, 445 | ); 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /src/write.rs: -------------------------------------------------------------------------------- 1 | use image::{ ImageBuffer, Rgb, ImageRgb8 }; 2 | use std::path::Path; 3 | use std::io; 4 | 5 | use crate::structures::vec3::Vec3; 6 | 7 | pub fn png(image: Vec>, path: &Path) -> io::Result<()> { 8 | // TODO: error handling 9 | 10 | // new buffer the width and height of the render 11 | let mut buffer = ImageBuffer::new( 12 | image[0].len() as u32, 13 | image.len() as u32, 14 | ); 15 | 16 | for (y, row) in image.iter().enumerate() { 17 | for (x, pixel) in row.iter().enumerate() { 18 | buffer.put_pixel(x as u32, y as u32, Rgb(pixel.colorize(1.0))); 19 | } 20 | } 21 | 22 | ImageRgb8(buffer).save(&path)?; 23 | println!("Render saved to {}", path.display()); 24 | return Ok(()); 25 | } 26 | --------------------------------------------------------------------------------