Skip to content

Commit

Permalink
Animation (#4)
Browse files Browse the repository at this point in the history
* Implement mini physics+collision engine for animated scene

* Create animated scene

* Rework PPM export

* Add MP4 Muxer

* Add a hack h264 encoder

* Streamline the H264Encoder

* Add color conversion RGB -> Y'CbCr

* Mix it all together for video animation
  • Loading branch information
mratsim committed May 26, 2020
1 parent e80b3ed commit 26ef9ed
Show file tree
Hide file tree
Showing 14 changed files with 4,924 additions and 13 deletions.
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,31 @@ a rendering project (raytracing or otherwise) from scratch, Nim is the best lang
- approachability & development agility

<p style="text-align: center;">
<img width="384" height="216" src="media/in_one_weekend.png">
<img width="256" height="144" src="media/animation.gif">
</p>

## Changelog

The writeup below corresponds to v0.1.0. Since then the following updates (not in Shirley's book) have been made:

- Parallelization via [Weave](https://github.com/mratsim/weave), my own state-of-the-art multithreading runtime.
- Rework of the RNG and sampler for parallel Monte-Carlo simulations
- Simple animation (i.e. no Motion Blur ... yet, only vertical axis, ...)
- Simple physics engine that can simulate gravity and bounce
- Export animation to series of PPM
- Export animation to MP4 video:
- RGB to Y'CbCr 420 color conversion (often called YUV)
- Poor Man's H264 encoder
- MP4 Muxer
- ⚠️ Warning: very slow as the engine as no
acceleration structures (Bounding Volume Hierarchy, Kd-Trees, ...)
- Credits to https://github.com/nwtgck/ray-tracing-iow-scala for the animation idea.

## Table of Contents

- [Trace of Radiance](#trace-of-radiance)
- [Changelog](#changelog)
- [Table of Contents](#table-of-contents)
- [Reproducing](#reproducing)
- [Speed](#speed)
- [Raytracing in one Weekend](#raytracing-in-one-weekend)
Expand All @@ -39,6 +60,7 @@ a rendering project (raytracing or otherwise) from scratch, Nim is the best lang
```
git clone https://github.com/mratsim/trace-of-radiance
cd trace-of-radiance
git checkout v0.1.0
nim c -d:danger --outdir:build trace_of_radiance.nim
./build/trace_of_radiance > image.ppm
```
Expand All @@ -49,6 +71,8 @@ Rendering is an extremely time-consuming task. Nim is fast, very fast, even fast

### Raytracing in one Weekend

> This applies to v0.1.0 of the repo, the repo has been reworked and in particular adds parallelism.
In the RayTracing in One Weekend first book

- Nim 1.2 (GCC 10.1), flag `-d:danger`
Expand Down
Binary file added media/animation.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
190 changes: 188 additions & 2 deletions trace_of_radiance.nim
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.

import
# Standard library
std/[os, strformat, monotimes],
# 3rd party
weave,
# Internals
./trace_of_radiance/[
primitives,
Expand All @@ -15,8 +19,18 @@ import
scenes,
sampling,
io/ppm
],
# Extra
./trace_of_radiance/scenes_animated,
./trace_of_radiance/io/[
color_conversions,
h264,
mp4,
rgb
]

import times except Time

proc main() =
const aspect_ratio = 16.0 / 9.0
const image_width = 384
Expand Down Expand Up @@ -50,10 +64,182 @@ proc main() =
)
defer: canvas.delete()

init(Weave)
canvas.render(cam, world.list(), max_depth)
exit(Weave)

stdout.exportToPPM canvas
canvas.exportToPPM stdout
stderr.write "\nDone.\n"

proc main_animation_ppm() =
const aspect_ratio = 16.0 / 9.0
const image_width = 384
const image_height = int32(image_width / aspect_ratio)
const samples_per_pixel = 100
const gamma_correction = 2.2
const max_depth = 50

const
dt = 0.005
t_min = 0.0
t_max = 2.0
skip = 6 # Render every 6 physics update

const
destDir = "build"/"rendered"
series = "animation"

var worldRNG: Rng
worldRNG.seed 0xFACADE

var animation = worldRNG.random_moving_spheres(
image_height, image_width, dt.Time, t_min.Time, t_max.Time
)

var canvas = newCanvas(
image_height, image_width,
samples_per_pixel,
gamma_correction
)
defer: canvas.delete()

createDir(destDir)

let totalScenes = int((t_max - t_min)/(dt*skip))
stderr.write &"Total scenes: {totalScenes}"

init(Weave)
var sceneID = 0
var elapsed: Duration
for camera, scene in scenes(animation, skip = 6):
let remaining = totalScenes-sceneID
let timeSpent = inSeconds(elapsed)
let timeLeft = remaining * timeSpent
stderr.write &"\rScenes remaining: {remaining:>5}, {timeSpent:>2} seconds/scene, estimated time left {timeLeft:>4} seconds"
stderr.flushFile()
let start = getMonoTime()
canvas.render(camera, scene.list(), maxdepth)
syncRoot(Weave)

canvas.exportToPPM destDir, series, sceneID

inc sceneID
elapsed = getMonoTime() - start

exit(Weave)

proc main_animation_mp4() =

when true: # Fast test
const aspect_ratio = 16.0 / 9.0
const image_width = 256 # so that we have multiples of 16 everywhere
const image_height = int32(image_width / aspect_ratio)
const samples_per_pixel = 10
const gamma_correction = 2.2
const max_depth = 50

const
dt = 0.005
t_min = 0.0
t_max = 1.0
skip = 6 # Render every 6 physics update

else: # Full render
const aspect_ratio = 16.0 / 9.0
const image_width = 576 # so that we have multiples of 16 everywhere
const image_height = int32(image_width / aspect_ratio)
const samples_per_pixel = 300
const gamma_correction = 2.2
const max_depth = 50

const
dt = 0.005
t_min = 0.0
t_max = 6.0
skip = 6 # Render every 6 physics update

const
destDir = "build"/"rendered"
series = "animation"

var worldRNG: Rng
worldRNG.seed 0xFACADE

var animation = worldRNG.random_moving_spheres(
image_height, image_width, dt.Time, t_min.Time, t_max.Time
)

var canvas = newCanvas(
image_height, image_width,
samples_per_pixel,
gamma_correction
)
defer: canvas.delete()

createDir(destDir)

# Encoding
# ----------------------------------------------------------------------
let tmp264 = destDir/(series & ".264")
let out264 = open(tmp264, fmWrite)
var encoder = H264Encoder.init(image_width, image_height, out264)
let (Y, Cb, Cr) = encoder.getFrameBuffers()

let yD = Y.initChannelDesc(image_width, subsampled = false)
let uD = Cb.initChannelDesc(image_width, subsampled = true)
let vD = Cr.initChannelDesc(image_width, subsampled = true)

# Rendering
# ----------------------------------------------------------------------
let totalScenes = int((t_max - t_min)/(dt*skip))
stderr.write &"Total scenes: {totalScenes}"

init(Weave)
var sceneID = 0
var elapsed: Duration
for camera, scene in scenes(animation, skip = 6):
let remaining = totalScenes-sceneID
let timeSpent = inMicroSeconds(elapsed)
let timeLeft = remaining.float64 * timeSpent.float64 * 1e-6
let throughput = 1e6/timeSpent.float64
stderr.write &"\rScenes remaining: {remaining:>5}, {throughput:>7.4f} scene(s)/second, estimated time left {timeLeft:>10.3f} seconds"
stderr.flushFile()
let start = getMonoTime()
canvas.render(camera, scene.list(), maxdepth)
syncRoot(Weave)


# Video
let rgb = canvas.toRGB_Raw()
let rgbD = rgb[0].unsafeAddr.initChannelDesc(image_width, subsampled = false)
rgbRaw_to_ycbcr420(
image_width, image_height,
rgb = rgbD,
luma = yD,
chromaBlue = uD,
chromaRed = vD,
BT601
)
encoder.flushFrame()

inc sceneID
elapsed = getMonoTime() - start

exit(Weave)
encoder.finish()
out264.close()
echo "\nFinished writing temporary \"", destDir/(series & ".264"),"\".\nMuxing into MP4"

var mP4Muxer: MP4Muxer
let mp4 = open(destDir/(series & ".mp4"), fmWrite)
mP4Muxer.initialize(mp4, image_width, image_height)
mP4Muxer.writeMP4_from(tmp264)

mP4Muxer.close()
mp4.close()
# removeFile(tmp264)
echo "Finished! Rendering available at \"", destDir/(series & ".mp4"),"\""

main()
# main()
# main_animation_ppm()
main_animation_mp4()
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ description = "An educational raytracer"
license = "MIT or Apache License 2.0"

# Dependencies
requires "nim >= 1.2.0", "weave"
requires "nim >= 1.2.0",
"weave"
Loading

0 comments on commit 26ef9ed

Please sign in to comment.