[orx-poisson-fill] move to orx-jvm

This commit is contained in:
Edwin Jakobs
2021-06-29 14:28:44 +02:00
parent 0ead2a440d
commit 533166f678
20 changed files with 1 additions and 1 deletions

View File

@@ -0,0 +1,10 @@
# orx-poisson-fill
Post processing effect that fills transparent parts of the image interpolating the edge pixel colors. GPU-based.
<!-- __demos__ -->
## Demos
### DemoPoissonFill01
[source code](src/demo/kotlin/DemoPoissonFill01.kt)
![DemoPoissonFill01Kt](https://raw.githubusercontent.com/openrndr/orx/media/orx-poisson-fill/images/DemoPoissonFill01Kt.png)

View File

@@ -0,0 +1,19 @@
sourceSets {
demo {
java {
srcDirs = ["src/demo/kotlin"]
compileClasspath += main.getCompileClasspath()
runtimeClasspath += main.getRuntimeClasspath()
}
}
}
dependencies {
implementation project(":orx-fx")
implementation project(":orx-noise")
implementation("org.openrndr:openrndr-filter:$openrndrVersion")
demoImplementation("org.openrndr:openrndr-application:$openrndrVersion")
demoImplementation("org.openrndr:openrndr-extensions:$openrndrVersion")
demoRuntimeOnly("org.openrndr:openrndr-gl3:$openrndrVersion")
demoRuntimeOnly("org.openrndr:openrndr-gl3-natives-$openrndrOS:$openrndrVersion")
demoImplementation(sourceSets.getByName("main").output)
}

View File

@@ -0,0 +1,92 @@
import org.openrndr.MouseButton
import org.openrndr.application
import org.openrndr.color.ColorHSVa
import org.openrndr.color.ColorRGBa
import org.openrndr.color.rgb
import org.openrndr.draw.ColorType
import org.openrndr.draw.colorBuffer
import org.openrndr.draw.isolatedWithTarget
import org.openrndr.draw.renderTarget
import org.openrndr.extensions.SingleScreenshot
import org.openrndr.extra.noise.Random
import org.openrndr.math.Polar
import org.openrndr.math.clamp
import org.openrndr.math.mix
import org.openrndr.poissonfill.PoissonFill
import org.openrndr.shape.Rectangle
import kotlin.math.sin
data class Thing(val color: ColorRGBa, var pos: Polar, val speed: Polar)
suspend fun main() {
application {
program {
// -- this block is for automation purposes only
if (System.getProperty("takeScreenshot") == "true") {
extend(SingleScreenshot()) {
this.outputFile = System.getProperty("screenshotPath")
}
}
val dry = renderTarget(width, height) {
colorBuffer(type = ColorType.FLOAT32)
}
val wet = colorBuffer(width, height)
val fx = PoissonFill()
var borderOpacity = 0.0
// Create a list of things with
// color, polar position and polar speed
val things = List(10) {
Thing(
ColorHSVa(it * 182.0,
Random.double(0.3, 0.6),
Random.double(0.1, 0.9)).toRGBa(),
Polar(Random.double0(360.0),
100.0 + it * 10.0),
Polar(Random.double(-1.0, 1.0), 0.0))
}
extend {
drawer.isolatedWithTarget(dry) {
stroke = null
clear(ColorRGBa.TRANSPARENT)
// draw color circles
things.forEach { thing ->
fill = thing.color.shade(0.9 +
0.1 * sin(thing.pos.theta * 0.3))
circle(thing.pos.cartesian + bounds.center, 5.0)
// A. Use after fix in Polar.kt
//thing.pos += thing.speed
// B. temporary solution
thing.pos = Polar(thing.pos.theta +
thing.speed.theta, thing.pos.radius)
}
// draw dark gray window border.
// hold mouse button to fade in.
borderOpacity += if (MouseButton.LEFT in mouse.pressedButtons) 0.01 else -0.01
borderOpacity = borderOpacity.clamp(0.0, 1.0)
stroke = rgb(0.2).opacify(borderOpacity)
fill = null
strokeWeight = 3.0
rectangle(bounds)
}
fx.apply(dry.colorBuffer(0), wet)
drawer.image(wet)
// draw white rectangle
drawer.stroke = ColorRGBa.WHITE.opacify(0.9)
drawer.fill = null
drawer.strokeWeight = 6.0
drawer.rectangle(Rectangle.fromCenter(drawer.bounds.center,
300.0, 300.0))
}
}
}
}

View File

@@ -0,0 +1,139 @@
package org.openrndr.poissonfill
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.*
import org.openrndr.extra.fx.blend.Passthrough
import org.openrndr.math.IntVector2
import org.openrndr.resourceUrl
import kotlin.math.ceil
import kotlin.math.log2
import kotlin.math.min
internal class Downscale(filterUrl: String = "/shaders/gl3/poisson/downscale.frag")
: Filter(filterShaderFromUrl(resourceUrl(filterUrl))) {
var h1: FloatArray by parameters
init {
h1 = floatArrayOf(0.0f, 0.0f, 0.0f, 0.0f, 0.0f)
}
}
internal class Upscale(filterUrl: String = "/shaders/gl3/poisson/upscale.frag")
: Filter(filterShaderFromUrl(resourceUrl(filterUrl))) {
var h1: FloatArray by parameters
var h2: Float by parameters
var g: FloatArray by parameters
init {
h1 = floatArrayOf(0.0f, 0.0f, 0.0f, 0.0f, 0.0f)
h2 = 0.0f
g = floatArrayOf(0.0f, 0.0f, 0.0f)
}
}
internal class Convolution(filterUrl: String = "/shaders/gl3/poisson/filter.frag")
: Filter(filterShaderFromUrl(resourceUrl(filterUrl))) {
var g: FloatArray by parameters
init {
g = floatArrayOf(0.0f, 0.0f, 0.0f)
}
}
private val passthrough by lazy { Passthrough() }
internal class ConvolutionPyramid(width: Int, height: Int,
private val padding: Int = 0, cutOff: Int = 10000,
private val downscale: Downscale = Downscale(),
private val upscale: Upscale = Upscale(),
private val filter: Convolution = Convolution(),
val type: ColorType = ColorType.FLOAT32) {
var h1 = floatArrayOf(0.0f, 0.0f, 0.0f, 0.0f, 0.0f)
var h2 = 0.0f
var g = floatArrayOf(0.0f, 0.0f, 0.0f)
private val size = 5
private val resolution = IntVector2(width + 2 * padding, height + 2 * padding)
private val minResolution = min(resolution.x, resolution.y)
private val depth = min(cutOff, ceil(log2(minResolution.toDouble())).toInt())
private val levelsIn = mutableListOf<RenderTarget>()
private val levelsOut = mutableListOf<RenderTarget>()
private val result = colorBuffer(width, height, type = type)
init {
var levelWidth = resolution.x + 2 * size
var levelHeight = resolution.y + 2 * size
for (i in 0 until depth) {
levelsIn.add(renderTarget(levelWidth, levelHeight) {
colorBuffer(type = type)
})
levelsOut.add(renderTarget(levelWidth, levelHeight) {
colorBuffer(type = type)
})
levelWidth /= 2
levelHeight /= 2
levelWidth += 2 * size
levelHeight += 2 * size
}
}
fun process(input: ColorBuffer): ColorBuffer {
for (l in levelsIn) {
l.clearColor(0, ColorRGBa.TRANSPARENT)
}
for (l in levelsOut) {
l.clearColor(0, ColorRGBa.TRANSPARENT)
}
downscale.h1 = h1
upscale.g = g
upscale.h1 = h1
upscale.h2 = h2
filter.g = g
passthrough.padding = (levelsIn[0].width - input.width) / 2
passthrough.apply(input, levelsIn[0].colorBuffer(0))
passthrough.padding = 0
for (i in 1 until levelsIn.size) {
downscale.padding = 0
downscale.apply(levelsIn[i - 1].colorBuffer(0),
levelsIn[i].colorBuffer(0)
)
}
filter.apply(levelsIn.last().colorBuffer(0), levelsOut.last().colorBuffer(0))
for (i in levelsOut.size - 2 downTo 0) {
upscale.padding = 0
upscale.apply(arrayOf(levelsIn[i].colorBuffer(0), levelsOut[i + 1].colorBuffer(0)), arrayOf(levelsOut[i].colorBuffer(0)))
}
passthrough.padding = -size - padding
passthrough.apply(levelsOut[0].colorBuffer(0), result)
passthrough.padding = 0
return result
}
fun destroy() {
result.destroy()
(levelsIn+levelsOut).forEach {
it.colorAttachments.forEach {
when(it) {
is ColorBufferAttachment -> it.colorBuffer.destroy()
is CubemapAttachment -> it.cubemap.destroy()
is ArrayTextureAttachment -> it.arrayTexture.destroy()
is ArrayCubemapAttachment -> it.arrayCubemap.destroy()
}
}
it.detachColorAttachments()
it.destroy()
}
}
}

View File

@@ -0,0 +1,46 @@
package org.openrndr.poissonfill
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.*
import org.openrndr.resourceUrl
internal class PassthroughNoAlpha : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/poisson/passthrough-noalpha.frag")))
/**
* Laplacian filter
*/
class Laplacian : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/poisson/laplacian.frag")))
class LaplacianIntegrator(width: Int, height: Int, downscaling: Int = 1, type: ColorType = ColorType.FLOAT32) {
private val pyramid = ConvolutionPyramid(2 + width / downscaling, 2 + height / downscaling, type = type)
private val h1 = floatArrayOf(0.15f, 0.5f, 0.7f, 0.5f, 0.15f)
private val h2 = 1.0f
private val g = floatArrayOf(0.175f, 0.547f, 0.175f)
private val preproc = colorBuffer(width + 2, height + 2, type = type)
private val combined = colorBuffer(width, height)
private val passthrough = PassthroughNoAlpha()
init {
pyramid.h1 = h1
pyramid.g = g
pyramid.h2 = h2
}
fun process(input: ColorBuffer): ColorBuffer {
preproc.fill(ColorRGBa.TRANSPARENT)
pyramid.h1 = h1
pyramid.g = g
pyramid.h2 = h2
passthrough.padding = 1
passthrough.apply(input, preproc)
passthrough.padding = 0
val result = pyramid.process(preproc)
passthrough.padding = -1
passthrough.apply(result, combined)
passthrough.padding = 0
return combined
}
}

View File

@@ -0,0 +1,117 @@
package org.openrndr.poissonfill
import org.openrndr.draw.*
import org.openrndr.extra.fx.blend.Passthrough
import org.openrndr.extra.fx.blend.Subtract
import org.openrndr.filter.color.delinearize
import org.openrndr.filter.color.linearize
import org.openrndr.resourceUrl
internal class BlendBoundary : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/poisson/blend-boundary.frag")))
class AlphaToBitmap : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/poisson/alpha-to-bitmap.frag")))
internal class BlendCombine : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/poisson/blend-combine.frag"))) {
var softMaskGain: Double by parameters
init {
softMaskGain = 1.0
}
}
internal class Clamp : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/poisson/clamp.frag"))) {
var minValue: Double by parameters
var maxValue: Double by parameters
}
private val passthrough by lazy { Passthrough() }
private val subtract by lazy { Subtract() }
class PoissonBlender(val width: Int, val height: Int, type: ColorType = ColorType.FLOAT32) {
private val pyramid = ConvolutionPyramid(width, height, 0, type = type)
private val preprocess = colorBuffer(width, height, type = type)
private val combined = colorBuffer(width, height, type = type)
private val fillBoundary = BlendBoundary()
private val fillCombine = BlendCombine()
private val difference = colorBuffer(width, height, type = type)
private val h1 = floatArrayOf(0.1507146f, 0.6835785f, 1.0334191f, 0.6836f, 0.1507f)
private val h2 = 0.0269546f
private val g = floatArrayOf(0.0311849f, 0.7752854f, 0.0311849f)
init {
pyramid.h1 = h1
pyramid.g = g
pyramid.h2 = h2
}
fun process(target: ColorBuffer, source: ColorBuffer, mask: ColorBuffer,
softMask: ColorBuffer = mask, softMaskGain: Double = 1.0): ColorBuffer {
subtract.apply(arrayOf(target, source), difference)
fillBoundary.apply(arrayOf(difference, mask), preprocess)
val result = pyramid.process(preprocess)
fillCombine.softMaskGain = softMaskGain
fillCombine.apply(arrayOf(result, target, source, mask, softMask), arrayOf(combined))
return combined
}
fun destroy() {
pyramid.destroy()
preprocess.destroy()
combined.destroy()
difference.destroy()
}
}
class PoissonBlend: Filter() {
private var blender: PoissonBlender? = null
val alphaToBitmap = AlphaToBitmap()
var mask: ColorBuffer? = null
override fun apply(source: Array<ColorBuffer>, target: Array<ColorBuffer>) {
if (target.isNotEmpty()) {
mask?.let {
if (it.width != target[0].width || it.height != target[0].height) {
it.destroy()
mask = null
}
}
if (mask == null) {
mask = colorBuffer(target[0].width, target[0].height)
}
blender?.let {
if (it.width != target[0].width || it.height != target[0].height) {
it.destroy()
blender = null
}
}
if (blender == null) {
blender = PoissonBlender(target[0].width, target[0].height)
}
mask?.let {
alphaToBitmap.apply(source[1], it)
}
blender?.let {
linearize.apply(source[0], source[0])
linearize.apply(source[1], source[1])
val result = it.process(source[0], source[1], mask ?: error("no mask"))
result.copyTo(target[0])
delinearize.apply(target[0], target[0])
}
}
}
}

View File

@@ -0,0 +1,65 @@
package org.openrndr.poissonfill
import org.openrndr.draw.*
import org.openrndr.resourceUrl
internal class FillBoundary : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/poisson/fill-boundary.frag")))
internal class FillCombine : Filter(filterShaderFromUrl(resourceUrl("/shaders/gl3/poisson/fill-combine.frag")))
class PoissonFiller(val width: Int, val height: Int, type: ColorType = ColorType.FLOAT32) {
private val pyramid = ConvolutionPyramid(width, height, 0, type = type)
private val preproc = colorBuffer(width, height, type = type)
private val combined = colorBuffer(width, height, type = type)
private val fillBoundary = FillBoundary()
private val fillCombine = FillCombine()
private val h1 = floatArrayOf(0.1507146f, 0.6835785f, 1.0334191f, 0.6836f, 0.1507f)
private val h2 = 0.0269546f
private val g = floatArrayOf(0.0311849f, 0.7752854f, 0.0311849f)
init {
pyramid.h1 = h1
pyramid.g = g
pyramid.h2 = h2
}
fun process(input: ColorBuffer): ColorBuffer {
fillBoundary.apply(input, preproc)
val result = pyramid.process(preproc)
fillCombine.apply(arrayOf(result, input), arrayOf(combined))
return combined
}
fun destroy() {
preproc.destroy()
combined.destroy()
}
}
/**
* Poison filling as a filter
*/
class PoissonFill : Filter() {
private var filler: PoissonFiller? = null
override fun apply(source: Array<ColorBuffer>, target: Array<ColorBuffer>) {
if (target.isNotEmpty()) {
filler?.let {
if (it.width != target[0].width || it.height != target[0].height) {
it.destroy()
filler = null
}
}
if (filler == null) {
filler = PoissonFiller(target[0].width, target[0].height)
}
filler?.let {
val result = it.process(source[0])
result.copyTo(target[0])
}
}
}
}

View File

@@ -0,0 +1,11 @@
#version 330
in vec2 v_texCoord0;
uniform sampler2D tex0;
out vec4 o_output;
void main(){
vec4 c = texture(tex0, v_texCoord0);
o_output.rgb = vec3(step(0.01, c.a));
o_output.a = 1.0;
}

View File

@@ -0,0 +1,37 @@
// adapted from https://github.com/kosua20/Rendu/blob/master/resources/common/shaders/screens/convolution-pyramid/fill-boundary.frag
#version 330
in vec2 v_texCoord0;
uniform sampler2D tex0; // input image
uniform sampler2D tex1; // mask
out vec4 o_output;
/** Output color only on the edges of the black regions in the input image, along with a 1.0 alpha. */
void main(){
o_output = vec4(0.0);
vec4 fullColor = textureLod(tex0, v_texCoord0, 0.0);
float maskColor = textureLod(tex1, v_texCoord0, 0.0).r;
float isInMask = maskColor == 1.0 ? 1.0 : 0.0;
float maskLaplacian = -4.0 * isInMask;
float mask110 = textureLodOffset(tex1, v_texCoord0, 0.0, ivec2( 1, 0)).r;
float mask101 = textureLodOffset(tex1, v_texCoord0, 0.0, ivec2( 0, 1)).r;
float mask010 = textureLodOffset(tex1, v_texCoord0, 0.0, ivec2(-1, 0)).r;
float mask001 = textureLodOffset(tex1, v_texCoord0, 0.0, ivec2( 0,-1)).r;
maskLaplacian += mask110 == 1.0 ? 1.0 : 0.0;
maskLaplacian += mask101 == 1.0 ? 1.0 : 0.0;
maskLaplacian += mask010 == 1.0 ? 1.0 : 0.0;
maskLaplacian += mask001 == 1.0 ? 1.0 : 0.0;
if(maskLaplacian > 0.0){
o_output.rgb = fullColor.rgb;
o_output.a = 1.0;
}
}

View File

@@ -0,0 +1,46 @@
#version 330
in vec2 v_texCoord0;
uniform sampler2D tex0; // membrane
uniform sampler2D tex1; // target image
uniform sampler2D tex2; // source image
uniform sampler2D tex3; // mask
uniform sampler2D tex4; // soft mask
uniform float softMaskGain;
out vec4 o_output;
void main(){
vec4 targetColor = textureLod(tex1, v_texCoord0, 0.0).rgba;
float maskColor = textureLod(tex3, v_texCoord0, 0.0).r;
float mask = maskColor == 1.0 ? 1.0 : 0.0;
float softMask = textureLod(tex4, v_texCoord0, 0.0).r;
float maskLaplacian = -4.0 * mask;
float mask110 = textureLodOffset(tex3, v_texCoord0, 0.0, ivec2( 1, 0)).r;
float mask101 = textureLodOffset(tex3, v_texCoord0, 0.0, ivec2( 0, 1)).r;
float mask010 = textureLodOffset(tex3, v_texCoord0, 0.0, ivec2(-1, 0)).r;
float mask001 = textureLodOffset(tex3, v_texCoord0, 0.0, ivec2( 0,-1)).r;
maskLaplacian += mask110 == 1.0 ? 1.0 : 0.0;
maskLaplacian += mask101 == 1.0 ? 1.0 : 0.0;
maskLaplacian += mask010 == 1.0 ? 1.0 : 0.0;
maskLaplacian += mask001 == 1.0 ? 1.0 : 0.0;
if (maskLaplacian > 0) {
mask = 1;
}
{
vec4 sourceColor = textureLod(tex2, v_texCoord0, 0.0);
vec4 membraneColor = textureLod(tex0, v_texCoord0, 0.0);
membraneColor.rgb /= membraneColor.a;
vec3 blend = membraneColor.rgb + sourceColor.rgb;
o_output.rgb = mix(targetColor.rgb, blend, mask*max(0.0,min(1.0, softMask * softMaskGain)));
o_output.a = 1.0;
}
}

View File

@@ -0,0 +1,25 @@
// from https://github.com/kosua20/Rendu/blob/master/resources/common/shaders/screens/convolution-pyramid/fill-boundary.frag
#version 330
in vec2 v_texCoord0;
uniform sampler2D tex0;
uniform int levels;
out vec4 o_output;
uniform float scale;
uniform float phase;
uniform float minValue;
uniform float maxValue;
void main(){
vec4 c = texture(tex0, v_texCoord0);
c.rgb = clamp(c.rgb, vec3(minValue), vec3(maxValue));
o_output = c;
}

View File

@@ -0,0 +1,50 @@
// from https://github.com/kosua20/Rendu/blob/master/resources/common/shaders/screens/convolution-pyramid/downscale.frag
#version 330
in vec2 v_texCoord0;
uniform vec2 targetSize;
uniform sampler2D tex0;
out vec4 o_output;
uniform vec2 padding;
uniform float h1[5]; ///< h1 filter parameters.
/** Denotes if a pixel falls outside an image.
\param pos the pixel position
\param size the image size
\return true if the pixel is outside of the image
*/
bool isOutside(ivec2 pos, ivec2 size){
return (pos.x < 0 || pos.y < 0 || pos.x > size.x || pos.y > size.y);
}
/** Apply the h1 filter and downscale the input data by a factor of 2. */
void main(){
vec4 accum = vec4(0.0);
ivec2 size = textureSize(tex0, 0).xy;
ivec2 ts = size;
//ivec2 ts = ivec2(targetSize - 2 * padding);
// Our current size is half this one, so we have to scale by 2.
ivec2 coords = ivec2(floor( targetSize * v_texCoord0)) * 2 - ivec2(10);
for(int dy = -2; dy <=2; dy++){
for(int dx = -2; dx <=2; dx++){
ivec2 newPix = coords+ivec2(dx,dy);
if(isOutside(newPix, size)){
continue;
//accum = vec4(1.0, 0.0, 0.0, 1.0);
}
accum += h1[dx+2] * h1[dy+2] * texelFetch(tex0, newPix,0);
}
}
o_output = accum;
}

View File

@@ -0,0 +1,63 @@
// from https://github.com/kosua20/Rendu/blob/master/resources/common/shaders/screens/convolution-pyramid/fill-boundary.frag
#version 330
in vec2 v_texCoord0;
uniform sampler2D tex0;
//layout(binding = 0) uniform sampler2D screenTexture; ///< Image to process.
//layout(location = 0) out vec4 fragColor; ///< Color.
out vec4 o_output;
/** Denotes if a pixel falls outside an image.
\param pos the pixel position
\param size the image size
\return true if the pixel is outside of the image
*/
/** Output color only on the edges of the black regions in the input image, along with a 1.0 alpha. */
void main(){
o_output = vec4(0.0);
vec4 fullColor = textureLod(tex0, v_texCoord0, 0.0);
float isInMask = fullColor.a == 1.0 ? 0.0 : 1.0; //float(all(equal(fullColor.rgb, vec3(0.0))));
float maskLaplacian = -4.0*(1.0-fullColor.a);
// float maskLaplacian = -4.0 * isInMask;
vec4 cola110 = textureLodOffset(tex0, v_texCoord0, 0.0, ivec2( 1, 0));
vec4 cola101 = textureLodOffset(tex0, v_texCoord0, 0.0, ivec2( 0, 1));
vec4 cola010 = textureLodOffset(tex0, v_texCoord0, 0.0, ivec2(-1, 0));
vec4 cola001 = textureLodOffset(tex0, v_texCoord0, 0.0, ivec2( 0,-1));
vec3 col110 = cola110.rgb; // cola110.a;
vec3 col101 = cola101.rgb; // cola101.a;
vec3 col010 = cola010.rgb; // cola010.a;
vec3 col001 = cola001.rgb; // cola001.a;
// maskLaplacian += float(all(equal(col110, vec3(0.0))));
// maskLaplacian += float(all(equal(col101, vec3(0.0))));
// maskLaplacian += float(all(equal(col010, vec3(0.0))));
// maskLaplacian += float(all(equal(col001, vec3(0.0))));
// maskLaplacian += cola110.a == 1.0 ? 0.0 : 1.0;
// maskLaplacian += cola101.a == 1.0 ? 0.0 : 1.0;
// maskLaplacian += cola010.a == 1.0 ? 0.0 : 1.0;
// maskLaplacian += cola001.a == 1.0 ? 0.0 : 1.0;
maskLaplacian += (1.0-cola110.a);
maskLaplacian += (1.0-cola101.a);
maskLaplacian += (1.0-cola010.a);
maskLaplacian += (1.0-cola001.a);
if(maskLaplacian > 0.0){
o_output.rgb = fullColor.rgb;///fullColor.a;;
o_output.a = fullColor.a;
}
}

View File

@@ -0,0 +1,23 @@
// from https://github.com/kosua20/Rendu/blob/master/resources/common/shaders/screens/convolution-pyramid/fill-combine.frag
#version 330
in vec2 v_texCoord0;
uniform sampler2D tex0; // result of pyramid convolution
uniform sampler2D tex1; // input image
out vec4 o_output;
/** Composite the initial image and the filled image in the regions where the initial image is black. */
void main(){
vec4 inputColor = textureLod(tex1, v_texCoord0, 0.0);
float mask = 1.0 - inputColor.a;
vec4 fillColor = textureLod(tex0, v_texCoord0, 0.0);
fillColor.rgb /= fillColor.a;
o_output.rgb = fillColor.rgb * (mask) + inputColor.rgb; //mix(inputColor.rgb, fillColor.rgb, mask);
o_output.a = 1.0;
}

View File

@@ -0,0 +1,41 @@
// from https://github.com/kosua20/Rendu/blob/master/resources/common/shaders/screens/convolution-pyramid/filter.frag
#version 330
//layout(binding = 0) uniform sampler2D screenTexture; ///< Level to filter.
uniform sampler2D tex0;
in vec2 v_texCoord0;
out vec4 o_output;
uniform float g[3]; ///< g filter parameters.
/** Denotes if a pixel falls outside an image.
\param pos the pixel position
\param size the image size
\return true if the pixel is outside of the image
*/
bool isOutside(ivec2 pos, ivec2 size){
return (pos.x < 0 || pos.y < 0 || pos.x >= size.x || pos.y >= size.y);
}
/** Apply the g filter to the input data. */
void main(){
vec4 accum = vec4(0.0);
ivec2 size = textureSize(tex0, 0).xy;
ivec2 coords = ivec2(v_texCoord0 * size);
for(int dy = -1; dy <=1; dy++){
for(int dx = -1; dx <=1; dx++){
ivec2 newPix = coords + ivec2(dx,dy);
if(isOutside(newPix, size)){
continue;
}
accum += g[dx+1] * g[dy+1] * texelFetch(tex0, newPix,0 );
}
}
o_output = accum;
}

View File

@@ -0,0 +1,52 @@
// adapted from https://github.com/kosua20/Rendu/blob/master/resources/common/shaders/screens/convolution-pyramid/laplacian.frag
#version 330
in vec2 v_texCoord0;
uniform sampler2D tex0;
out vec4 o_output;
/** Denotes if UV coordinates falls outside an image.
\param pos the UV coordinates
\return true if the UV are outside of the image
*/
bool isOutside(vec2 pos){
return (pos.x < 0.0 || pos.y < 0.0 || pos.x > 1.0 || pos.y > 1.0);
}
/** Compute the Laplacian field of an input RGB image, adding a 1px black border around it before computing the gradients and divergence. */
void main(){
vec3 div = vec3(0.0);
ivec2 size = textureSize(tex0, 0).xy;
vec3 pixelShift = vec3(0.0);
pixelShift.xy = 1.0/vec2(size);
vec2 uvs = v_texCoord0;
if(!isOutside(uvs)){
vec3 col = textureLod(tex0, uvs, 0.0).rgb;
div = 4.0 * col;
}
vec2 uvs110 = uvs + pixelShift.xz;
if(!isOutside(uvs110)){
vec3 col110 = textureLod(tex0, uvs110, 0.0).rgb;
div -= col110;
}
vec2 uvs101 = uvs + pixelShift.zy;
if(!isOutside(uvs101)){
vec3 col101 = textureLod(tex0, uvs101, 0.0).rgb;
div -= col101;
}
vec2 uvs010 = uvs - pixelShift.xz;
if(!isOutside(uvs010)){
vec3 col010 = textureLod(tex0, uvs010, 0.0).rgb;
div -= col010;
}
vec2 uvs001 = uvs - pixelShift.zy;
if(!isOutside(uvs001)){
vec3 col001 = textureLod(tex0, uvs001, 0.0).rgb;
div -= col001;
}
o_output.rgb = div;
o_output.a = 1.0f;
}

View File

@@ -0,0 +1,20 @@
#version 330
in vec2 v_texCoord0;
uniform sampler2D tex0;
out vec4 o_output;
/** Denotes if a pixel falls outside an image.
\param pos the pixel position
\param size the image size
\return true if the pixel is outside of the image
*/
/** Output an image translated by a fixed number of pixels on each axis. useful for padding when rendering in a larger framebuffer. */
void main(){
vec4 c = texture(tex0, v_texCoord0);
o_output.rgb = c.rgb;
o_output.a = 1.0;
}

View File

@@ -0,0 +1,18 @@
// from https://github.com/kosua20/Rendu/blob/master/resources/common/shaders/screens/convolution-pyramid/fill-boundary.frag
#version 330
in vec2 v_texCoord0;
uniform sampler2D tex0;
out vec4 o_output;
void main(){
o_output = vec4(0.0);
vec4 fullColor = textureLod(tex0, v_texCoord0, 0.0);
if (fullColor.a == 1.0) {
o_output = fullColor;
}
}

View File

@@ -0,0 +1,60 @@
// from https://github.com/kosua20/Rendu/blob/master/resources/common/shaders/screens/convolution-pyramid/upscale.frag
#version 330
in vec2 v_texCoord0;
uniform sampler2D tex0; ///< Current h1 filtered level.
uniform sampler2D tex1; ///< Previous h1+g filtered level.
out vec4 o_output; ///< Color.
uniform float h1[5]; ///< h1 filter parameters.
uniform float h2; ///< h2 scaling parameter.
uniform float g[3]; ///< g filter parameters.
/** Denotes if a pixel falls outside an image.
\param pos the pixel position
\param size the image size
\return true if the pixel is outside of the image
*/
bool isOutside(ivec2 pos, ivec2 size){
return (pos.x < 0 || pos.y < 0 || pos.x >= size.x || pos.y >= size.y);
}
/** Combine previous level filtered with h2 (applying a 0-filled upscaling) and the current level filtered with g.
*/
void main(){
vec4 accum = vec4(0.0);
ivec2 size = textureSize(tex0, 0).xy;
ivec2 coords = ivec2(v_texCoord0 * size);
for(int dy = -1; dy <=1; dy++){
for(int dx = -1; dx <=1; dx++){
ivec2 newPix = coords+ivec2(dx,dy);
if(isOutside(newPix, size)){
continue;
}
accum += g[dx+1] * g[dy+1] * texelFetch(tex0, newPix,0);
}
}
ivec2 sizeSmall = textureSize(tex1, 0).xy;
for(int dy = -2; dy <=2; dy++){
for(int dx = -2; dx <=2; dx++){
ivec2 newPix = coords+ivec2(dx,dy);
// The filter is applied to a texture upscaled by inserting zeros.
if(newPix.x%2 != 0 || newPix.y%2 != 0){
continue;
}
newPix /= 2;
newPix += 5;
if(isOutside(newPix, sizeSmall)){
accum = vec4(0.0, 0.0, 1.0, 1.0);
}
accum += h2 * h1[dx+2] * h1[dy+2] * texelFetch(tex1, newPix, 0);
}
}
o_output = accum;
}