diff --git a/README.md b/README.md index 53149ff1..8aaf3ddd 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ A growing library of assorted data structures, algorithms and utilities. - [`orx-olive`](orx-olive/README.md), extensions that turns OPENRNDR in to a live coding environment - [`orx-osc`](orx-osc/README.md), open sound control interface - [`orx-palette`](orx-palette/README.md), manage color palettes - +- [`orx-temporal-blur`](orx-temporal-blur/README.md), temporal (motion) blur for video production. # Developer notes ## Create and use local builds of the library diff --git a/orx-temporal-blur/README.md b/orx-temporal-blur/README.md new file mode 100644 index 00000000..dd48cb31 --- /dev/null +++ b/orx-temporal-blur/README.md @@ -0,0 +1,44 @@ +# orx-temporal-blur + +`orx-temporal-blur` is a an extension intended for off-line production; +i.e. videos made using the `ScreenRecorder` extension. This extension uses multi-sampling +to accumulate and average the final image. Multi-sampling is performed by modifying `Program.clock` +while processing the tail-end of the extension chain. This multi-sampling strategy is slow and not +entirely suited in real-time and/or interactive settings. + +`orx-temporal-blur` works well with programs that use `seconds` for their animation input. +This includes `Animatables`, but only after the `Animatable` clock is synchronized with the `Program` clock. +(Which you should already have done when using `ScreenRecorder`) + +Synchronizing clocks in OPENRNDR 0.3.36 (current release): +``` +Animatable.clock(object: Clock { + override val time: Long + get() = (clock() * 1E3).toLong() + }) +``` + +Synchronizing high precision clocks in OPENRNDR 0.3.37 (future release) +``` +Animatable.clock(object: Clock { + override val time: Long + get() = timeNanos / 1000 + override val timeNanos: Long + get() = (clock() * 1E6).toLong() + + }) +``` + +Note that time-step-based simulations or integrations will likely break because your drawing code will be executed multiple times +per frame. + +## Configuration + +```kotlin +extend(TemporalBlur()) { + duration = 0.9 + samples = 30 + fps = 60.0 + jitter = 1.0 +} +``` \ No newline at end of file diff --git a/orx-temporal-blur/src/main/kotlin/TemporalBlur.kt b/orx-temporal-blur/src/main/kotlin/TemporalBlur.kt index c1802224..06ee3f40 100644 --- a/orx-temporal-blur/src/main/kotlin/TemporalBlur.kt +++ b/orx-temporal-blur/src/main/kotlin/TemporalBlur.kt @@ -1,3 +1,5 @@ +package org.openrndr.extra.temporalblur + import org.openrndr.Extension import org.openrndr.Program import org.openrndr.color.ColorRGBa @@ -7,6 +9,7 @@ import org.openrndr.filter.blend.add import org.openrndr.filter.color.delinearize import org.openrndr.filter.color.linearize import org.openrndr.math.Matrix44 +import org.openrndr.math.Matrix55 import org.openrndr.math.Vector2 import org.openrndr.math.transforms.translate @@ -27,6 +30,7 @@ class TemporalBlur : Extension { * number of samples to take, more is slower */ var samples = 30 + /** * duration in frames, shouldn't be 1.0 or larger when using Animatables */ @@ -121,20 +125,22 @@ class TemporalBlur : Extension { drawer.background(ColorRGBa.BLACK) } } + val oldClock = program.clock + val oldClockValue = oldClock() for (i in samples - 1 downTo 1) { image?.bind() drawer.background(ColorRGBa.BLACK) - - val oldClock = program.clock - program.clock = { oldClock() - (i * duration) / (fps * samples) } + program.clock = { oldClockValue - (i * duration) / (fps * samples) } // I guess we need something better here. val fsf = Program::class.java.getDeclaredField("frameSeconds") fsf.isAccessible = true fsf.setDouble(program, program.clock()) + drawer.drawStyle.blendMode = BlendMode.OVER + drawer.drawStyle.colorMatrix = Matrix55.IDENTITY drawer.isolated { val offset = Vector2.uniformRing(0.0, jitter) drawer.projection = Matrix44.translate(offset.x * (1.0 / program.width), offset.y * (1.0 / program.height), 0.0) * drawer.projection @@ -181,6 +187,8 @@ class TemporalBlur : Extension { drawer.view = Matrix44.IDENTITY drawer.isolatedWithTarget(result!!) { + drawer.drawStyle.blendMode = BlendMode.OVER + drawer.background(ColorRGBa.BLACK) drawer.drawStyle.colorMatrix = tint(ColorRGBa.WHITE.shade(1.0 / samples)) drawer.image(accumulator!!.colorBuffer(0)) @@ -188,6 +196,11 @@ class TemporalBlur : Extension { if (delinearizeOutput) { delinearize.apply(result!!.colorBuffer(0), result!!.colorBuffer(0)) } + drawer.drawStyle.blendMode = BlendMode.OVER + drawer.drawStyle.colorMatrix = Matrix55.IDENTITY + drawer.drawStyle.depthTestPass = DepthTestPass.ALWAYS + + drawer.background(ColorRGBa.BLACK) drawer.image(result!!.colorBuffer(0)) } }