Files
orx/orx-temporal-blur/src/main/kotlin/TemporalBlur.kt
2021-07-25 12:09:09 +02:00

268 lines
8.3 KiB
Kotlin

package org.openrndr.extra.temporalblur
import org.intellij.lang.annotations.Language
import org.openrndr.Extension
import org.openrndr.Program
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.*
import org.openrndr.extra.noise.uniformRing
import org.openrndr.extra.fx.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
private fun glslFull(@Language("GLSL") glsl: String) = glsl
class PlainAdd : Filter(filterShaderFromCode(glslFull("""
#version 330
uniform sampler2D tex0;
uniform sampler2D tex1;
in vec2 v_texCoord0;
out vec4 o_output;
void main() {
vec4 a = texture(tex0, v_texCoord0);
vec4 b = texture(tex1, v_texCoord0);
o_output = a + b;
}
"""), "plain-add"))
private val add by lazy { PlainAdd() }
/**
* Temporal blur extension.
* This works best in video rendering applications as it heavily relies on rendering
* the scene as many times as you have samples.
*/
class TemporalBlur : Extension {
private var oldClock: () -> Double = { 0.0 }
override var enabled: Boolean = true
private var accumulator: RenderTarget? = null
private var result: RenderTarget? = null
private var image: RenderTarget? = null
private var imageResolved: RenderTarget? = null
// modifier for final stage averager, higher gain results in brighter images
var gain = 1.0
/**
* number of samples to take, more is slower
*/
var samples = 30
/**
* duration in frames, shouldn't be 1.0 or larger when using Animatables
*/
var duration = 0.5
/**
* reference frame rate
*/
var fps = 60.0
/**
* spatial jitter in pixels, 0 is no jitter
*/
var jitter = 1.0
/**
* should the accumulator linearize the input. this should be true when rendering in sRGB
*/
var linearizeInput = true
/**
* should the accumulator delinearize the output. this should be true when rendering in sRGB
*/
var delinearizeOutput = true
/**
* multisampling setting
* */
var multisample: BufferMultisample = BufferMultisample.SampleCount(8)
var colorMatrix: (Double)->Matrix55 = { Matrix55.IDENTITY }
var beforeDrawAccumulated : TemporalBlur.() -> Unit = {}
override fun beforeDraw(drawer: Drawer, program: Program) {
val extensionOffset = program.extensions.indexOf(this)
val extensionTail = program.extensions.drop(extensionOffset + 1)
accumulator?.let { a ->
if (a.width != program.width || a.height != program.height) {
a.colorBuffer(0).destroy()
a.detachColorAttachments()
a.destroy()
}
}
result?.let { r ->
if (r.width != program.width || r.height != program.height) {
r.colorBuffer(0).destroy()
r.detachColorAttachments()
r.destroy()
}
}
image?.let { i ->
if (i.width != program.width || i.height != program.height) {
i.colorBuffer(0).destroy()
i.depthBuffer?.destroy()
i.detachColorAttachments()
i.detachDepthBuffer()
i.destroy()
}
}
imageResolved?.let { i ->
if (i.width != program.width || i.height != program.height) {
i.colorBuffer(0).destroy()
i.detachColorAttachments()
i.destroy()
}
}
if (accumulator == null) {
accumulator = renderTarget(program.width, program.height) {
colorBuffer(type = ColorType.FLOAT32)
}
}
if (result == null) {
result = renderTarget(program.width, program.height) {
colorBuffer(type = ColorType.FLOAT32)
}
}
if (image == null) {
image = renderTarget(program.width, program.height, multisample = multisample) {
depthBuffer()
colorBuffer(type = ColorType.FLOAT32)
}
}
if (imageResolved == null) {
imageResolved = renderTarget(program.width, program.height) {
depthBuffer()
colorBuffer(type = ColorType.FLOAT32)
}
}
accumulator?.let {
drawer.withTarget(it) {
drawer.clear(ColorRGBa.BLACK)
}
}
oldClock = program.clock
val oldClockValue = oldClock()
for (i in samples - 1 downTo 1) {
image?.bind()
drawer.clear(ColorRGBa.BLACK)
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 {
if (jitter > 0.0){
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
}
for (extension in extensionTail) {
extension.beforeDraw(drawer, program)
}
for (extension in extensionTail.reversed())
extension.afterDraw(drawer, program)
}
image?.unbind()
image!!.colorBuffer(0).copyTo(imageResolved!!.colorBuffer(0))
if (linearizeInput) {
imageResolved?.let {
linearize.apply(it.colorBuffer(0), it.colorBuffer(0))
}
}
val activeColorMatrix = colorMatrix(i / (samples - 1.0))
if (activeColorMatrix !== Matrix55.IDENTITY) {
drawer.isolatedWithTarget(imageResolved!!) {
drawer.drawStyle.colorMatrix = activeColorMatrix
drawer.drawStyle.blendMode = BlendMode.REPLACE
drawer.image(imageResolved!!.colorBuffer(0))
}
}
add.apply(arrayOf(imageResolved!!.colorBuffer(0), accumulator!!.colorBuffer(0)), accumulator!!.colorBuffer(0))
fsf.setDouble(program, program.clock())
}
image?.let {
drawer.withTarget(it) {
drawer.clear(ColorRGBa.BLACK)
}
}
image?.bind()
}
override fun afterDraw(drawer: Drawer, program: Program) {
// restore clock
program.clock = oldClock
// -- we receive one last frame here
image?.unbind()
image!!.colorBuffer(0).copyTo(imageResolved!!.colorBuffer(0))
val activeColorMatrix = colorMatrix(0.0)
if (activeColorMatrix !== Matrix55.IDENTITY) {
drawer.isolatedWithTarget(imageResolved!!) {
drawer.drawStyle.colorMatrix = activeColorMatrix
drawer.drawStyle.blendMode = BlendMode.REPLACE
drawer.image(imageResolved!!.colorBuffer(0))
}
}
add.apply(arrayOf(imageResolved!!.colorBuffer(0), accumulator!!.colorBuffer(0)), accumulator!!.colorBuffer(0))
beforeDrawAccumulated()
// -- render accumulated result
drawer.isolated {
drawer.defaults()
drawer.ortho(result!!)
drawer.isolatedWithTarget(result!!) {
drawer.drawStyle.blendMode = BlendMode.OVER
drawer.clear(ColorRGBa.BLACK)
drawer.drawStyle.colorMatrix = tint(ColorRGBa.WHITE.shade((1.0 / samples) * gain))
drawer.image(accumulator!!.colorBuffer(0))
}
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.clear(ColorRGBa.BLACK)
drawer.image(result!!.colorBuffer(0))
}
}
}