268 lines
8.3 KiB
Kotlin
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))
|
|
}
|
|
}
|
|
} |