Feedback shader

hey there!
im trying to port this feedback shader https://www.shadertoy.com/view/ldsczf
from shadertoy to openframeworks. but it seems that i have a problem to get the current buffer and pass it to the shader as a texture uniform.

what im doing wrong?

fbo.begin();
    ofClear(0);
    cam.begin();
    ofClear(0,0,0,0);
    sphere.setPosition(0, 0, 0);
    sphere.draw();
    cam.end();
    fbo.end();
    
    glDisable(GL_DEPTH_TEST);
    
    fbo1.begin();
    ofClear(0,0,0);
    shader.begin();
     shader.setUniformTexture("feedback", fbo1.getTexture(), 1);
    shader.setUniform2f("resolution", fbo.getWidth(), fbo.getHeight());
    shader.setUniform1f("time", ofGetElapsedTimef());
    fbo.draw(0,0);
//        ofRect(0,0, 500, 500);
    shader.end();
    fbo1.end();
    

    ofSetRectMode(OF_RECTMODE_CORNER);
 
    
    fbo1.draw(0, 0, ofGetWidth(), ofGetHeight());

if i draw the fbo 0 into the fbo1 i get the texture using sampler2D tex0 in the shader. but i cant recive the feedback texture ( the same buffer) .

try this other example and also doesn’t work

#include "ofApp.h"
void ofApp::setup(){
	ofSetWindowShape(1024, 768);
	sh.load("","sh11.frag");
	render.allocate(1024, 768);
	
}

void ofApp::update() {

}
void ofApp::draw() {
    
    render.begin();
    sh.begin();
    sh.setUniformTexture("tex0", render.getTexture(), 0);
    sh.setUniform2f("mouse", ofMap(ofGetMouseX(), 0, ofGetWidth(), 0, 1), ofMap(ofGetMouseY(), 0, ofGetHeight(), 0, 1));
    
    ofRect(0, 0, ofGetWidth(), ofGetHeight());
    sh.end();
    
    render.end();
    
	ofSetColor(255);
	render.draw(0, 0);
	ofSetColor(200, 0, 0);
	ofRect(ofGetMouseX(), ofGetMouseY(), 40, 50);
}
void ofApp::windowResized(int w, int h) {


	render.clear();
	render.allocate(w, h);

	sh.begin();
	sh.setUniform2f("resolution", ofGetWidth(), ofGetHeight());
	sh.end();
}

.frag:

uniform sampler2DRect tex0;
uniform vec2 resolution;
uniform vec2 mouse;

float cir(vec2 uv,vec2 p, float s,float d);
void main()
{	
	vec2 uv = gl_FragCoord.xy / resolution;

	vec2 coords = gl_FragCoord.xy ;
	//coords.y = resolution.y -coords.y;
	vec4 texture =  texture2DRect(tex0, coords);
	
	
	gl_FragColor = texture;
	
	
	//vec4 fin = vec4(e);
	float e = cir(uv,mouse,0.01,0.1);
	gl_FragColor = vec4(e,e,e,1.0)+texture*0.99; 
}
float cir(vec2 uv,vec2 p,float s, float d){
	
	vec2 p2 = p - uv;
	float a = atan(p2.x,p2.y);
	float r = length(p2);
	
	float e = 1.-smoothstep(s,s+d,r);
	return e;
}

Hi, you can not pass an fbo’s texture to a shader while you are still rendering the fbo (befor calling end() on the fbo).

1 Like

:confused: ok. so what is the way to do it? how can i pass the current buffer to a shader? i can do it in processing just drawing the buffer between begin(); and end();

Can you post that processing code?
I doubt that you can because it makes no sense to pass the buffer where you are drawing to into the shader.
Usually you would use what’s called a ping pong buffer. Just 2 fbos in which one is the one where you are drawing and the other is the one you are passing to the shader, then in the next run it is the opposite order.

I found this simple and straightforwards class for a pingpong fbo.

file FboPingPong.h

//
//  FboPingPong.h
//  emptyExample
//
//  Created by Andreas Müller on 12/08/2013.
//
//

#pragma once

#include "ofMain.h"

class FboPingPong
{
	public:

		void allocate( int _w, int _h, int internalformat = GL_RGB, ofColor _clearColor = ofColor(255,255,255) );
		void allocate( ofFbo::Settings _settings, ofColor _clearColor = ofColor(255,255,255) );
	
		ofFbo* source() { return sourceBuffer;	}
		ofFbo* dest()	{ return destBuffer;	}
	
		void draw( ofPoint _pos, float _width, bool _drawBack = false );
	
		void clearBoth();
		void clearBoth( ofColor _clearColor );
	
		void clearSource();
		void clearDest();
	
		void clearSource( ofColor _clearColor );
		void clearDest( ofColor _clearColor );
	
		void setClearColor( ofColor _color );
	
		void swap();
		
	private:
	
		ofFbo* sourceBuffer;
		ofFbo* destBuffer;
	
		ofFbo fbo1;
		ofFbo fbo2;
		
		ofColor clearColor;
};

file FboPingPong.cpp


//
//  FboPingPong.cpp
//  emptyExample
//
//  Created by Andreas Müller on 12/08/2013.
//
//

#include "FboPingPong.h"

// ------------------------------------------------------------------------------------
//
void FboPingPong::allocate( int _w, int _h, int _internalformat, ofColor _clearColor )
{
	ofFbo::Settings settings = ofFbo::Settings();
	settings.width  = _w;		
	settings.height = _h;
	settings.useDepth = true;
	settings.internalformat = _internalformat;
	
	allocate( settings, _clearColor );
}

// ------------------------------------------------------------------------------------
//
void FboPingPong::allocate( ofFbo::Settings _settings, ofColor _clearColor )
{
	clearColor = _clearColor;
	
	fbo1.allocate( _settings);
	fbo2.allocate( _settings );
	
	sourceBuffer = &fbo1;
	destBuffer = &fbo2;
	
	clearSource();
	clearDest();
}

// ------------------------------------------------------------------------------------
//
void FboPingPong::draw( ofPoint _pos, float _width, bool _drawBack )
{
	float desWidth = _width;
	float desHeight = (source()->getWidth() / source()->getHeight()) * desWidth;
	
	source()->draw( _pos, desWidth, desHeight );
	
	if( _drawBack )
	{
		dest()->draw( _pos + ofVec2f(desWidth,0), desWidth, desHeight );
	}
}

// ------------------------------------------------------------------------------------
//
void FboPingPong::clearBoth()
{
	clearSource();
	clearDest();
}

// ------------------------------------------------------------------------------------
//
void FboPingPong::clearBoth( ofColor _clearColor )
{
	clearSource( _clearColor );
	clearDest( _clearColor );
}

// ------------------------------------------------------------------------------------
//
void FboPingPong::clearSource()
{
	clearSource( clearColor );
}

// ------------------------------------------------------------------------------------
//
void FboPingPong::clearDest()
{
	clearDest( clearColor );
}

// ------------------------------------------------------------------------------------
//
void FboPingPong::clearSource( ofColor _clearColor )
{
	source()->begin();
		ofClear( _clearColor );
	source()->end();
}

// ------------------------------------------------------------------------------------
//
void FboPingPong::clearDest( ofColor _clearColor )
{
	dest()->begin();
		ofClear( _clearColor );
	dest()->end();
}

// ------------------------------------------------------------------------------------
//
void FboPingPong::setClearColor( ofColor _color )
{
	clearColor = _color;
}

// ------------------------------------------------------------------------------------
//
void FboPingPong::swap()
{
	std::swap(sourceBuffer, destBuffer);
}

Add this class to your project and declare in the ofApp.h file and allocate properly in the setup, just as you would do with any fbo.

FboPingPong pingpong;

and remember to add #include “FboPingPong.h” at the top of ofApp.h file just below the other #include statements.

So your code should look something like the following, which I have not tested btw.

    ofClear(0);
    cam.begin();
    ofClear(0,0,0,0);
    sphere.setPosition(0, 0, 0);
    sphere.draw();
    cam.end();
    pingpong.source()->end();
    
//    glDisable(GL_DEPTH_TEST); dont call this directly, instead prefer ofEnableDepthTest() and ofDisableDepthTest() as it will deal properly with any renderer you're using.
ofDisableDepthTest();    

    pingpong.dest()->begin();
    ofClear(0,0,0);
    shader.begin();
     shader.setUniformTexture("feedback", pingpong.source()->getTexture(), 1);
    shader.setUniform2f("resolution", pingpong.source()->getWidth(), pingpong.source()->getHeight());
    shader.setUniform1f("time", ofGetElapsedTimef());
    pingpong.source()->draw(0,0);
    shader.end();
    pingpong.dest()->end();
    
    pingpong.dest()->draw(0, 0, ofGetWidth(), ofGetHeight());


//this line is super important as it will swap the buffers so what was the source now will be the destination and vice versa.
    pingpong.swap();

hope this helps.

4 Likes

in processing i do :

      ths.beginDraw();
      ths.shader(shader); 
      shader.set("thiss", ths); // current fbo
      ths.image(texture, 0, 0); //another fbo/texture

      ths.endDraw();

probably Processing has some ping pong buffers running under the hood, which would allow you to do so.

1 Like

Maybe. i think @Julian_Puppo also could pass the current FBO into a shader. (in openFrameworks) right? but for some reason his example doesnt work on osX sierra. thanks for explain the ping pong technic!

which example?

Im sorry, my second comment is his example

Interesting.
Well it kinda works. Although if you allocate the fbo with GL_RGBA32F_ARB(in order to avoid the trail that is left; look at examples/gl/fboTrailsExample) as its third parameter and in the shader you change the line that reads

gl_FragColor = vec4(e,e,e,1.0)+texture*0.99; 

for

gl_FragColor = vec4(e,e,e,1.0)+texture*0.99999999; 

you get some weird effects, which look like the fbo is writting the texture and reading at the same time, which is why the pingpong method is preferred.

I still have problems to get it working… take a look this example:
the shader is very basic one:

https://www.shadertoy.com/view/ldsczf

buffer A makes the shape. buffer B make the feedback, taken the buffer A as a texture and buffer B (itself) to make feedback. in processing this example works fine, passing the buffer as i said before.

using the pingPong class that you post…

void ofApp::setup(){
    shader.load("passV.glsl", "feedbackLoop.glsl");
    pingpong.allocate(1000, 1000, GL_RGBA32F_ARB);
}
void ofApp::draw(){
    ofClear(0);

    pingpong.source()->begin();
    ofClear(0);
    sphere.setRadius(sin(ofGetElapsedTimef())*50);

    sphere.setPosition(500, 500, 0);
    sphere.draw();
    pingpong.source()->end();

    ofDisableDepthTest();
//
    pingpong.dest()->begin();
    shader.begin();
    
    pingpong.source()->draw(0,0);
    shader.setUniformTexture("feedback", pingpong.source()->getTexture(), 1);
    shader.setUniform2f("resolution", pingpong.source()->getWidth(), pingpong.source()->getHeight());
    shader.setUniform1f("u_time", ofGetElapsedTimef());
     shader.setUniform1i("frameCount",ofGetFrameNum());

    shader.end();
    
    pingpong.dest()->end();

    pingpong.dest()->draw(0, 0, ofGetWidth(), ofGetHeight());
    
    
    //this line is super important as it will swap the buffers so what was the source now will be the destination and vice versa.
    pingpong.swap();
}

this is my .frag:

#version 150
precision highp float;

uniform float u_time;
uniform vec2 resolution = vec2(1000,1000);
in vec2 TexCoord;

out vec4 fragColor;

uniform sampler2DRect tex0;
uniform sampler2DRect feedback;
uniform int frameCount;

float hash( float n )
{
    return fract(sin(n)*43758.5453123);
}

float noise( in vec2 x )
{
    vec2 p = floor(x);
    vec2 f = fract(x);
    
    f = f*f*(3.0-2.0*f);
    
    float n = p.x + p.y*157.0;
    
    return mix(mix( hash(n+  0.0), hash(n+  1.0),f.x),
               mix( hash(n+157.0), hash(n+158.0),f.x),f.y);
}

// hue by netgrind(?)

vec3 hue(vec3 color, float shift) {
    
    const vec3  kRGBToYPrime = vec3 (0.299, 0.587, 0.114);
    const vec3  kRGBToI     = vec3 (0.596, -0.275, -0.321);
    const vec3  kRGBToQ     = vec3 (0.212, -0.523, 0.311);
    
    const vec3  kYIQToR   = vec3 (1.0, 0.956, 0.621);
    const vec3  kYIQToG   = vec3 (1.0, -0.272, -0.647);
    const vec3  kYIQToB   = vec3 (1.0, -1.107, 1.704);
    
    // Convert to YIQ
    float   YPrime  = dot (color, kRGBToYPrime);
    float   I      = dot (color, kRGBToI);
    float   Q      = dot (color, kRGBToQ);
    
    // Calculate the hue and chroma
    float   hue     = atan (Q, I);
    float   chroma  = sqrt (I * I + Q * Q);
    
    // Make the user's adjustments
    hue += shift;
    
    // Convert back to YIQ
    Q = chroma * sin (hue);
    I = chroma * cos (hue);
    
    // Convert back to RGB
    vec3    yIQ   = vec3 (YPrime, I, Q);
    color.r = dot (yIQ, kYIQToR);
    color.g = dot (yIQ, kYIQToG);
    color.b = dot (yIQ, kYIQToB);
    
    return color;
}

float fractalNoise(vec2 pos) {
    float n = 0.;
    float scale = 1. / 1.5;
    for (int i = 0; i < 5; i += 1) {
        n += noise(pos) * scale;
        scale *= 0.5;
        pos *= 2.;
    }
    return n;
}
void main( )
{
   	vec2 uv = gl_FragCoord.xy;
    vec2 uv2 = gl_FragCoord.xy;

    vec2 polarUv = (uv * 2.0 - 1.0);
    float angle = atan(polarUv.y, polarUv.x);
    
    // Scale up the length of the vector by a noise function feeded by the angle and length of the vector
    float ll = length(polarUv)*0.5 - fractalNoise(vec2(sin(angle*4. + u_time*2.) + length(uv)*10., length(uv)*20. + sin(angle*4.)))*0.005 ;
    
    vec3 base = texture(tex0, uv).rgb;
    
    // Convert the scaled coordinates back to cartesian
    vec2 offs = vec2(cos(angle)*ll + 0.5, sin(angle)*ll + 0.5);
    
    // sample the last texture with uv's slightly scaled up
    vec3 overlay = texture(feedback, offs.xy).rgb;
    
    // Since the colors of the iChannel0 are monochrome, set a color channel to zero to do a hue shift
    base.b = 0.;
    
    // Apply a hue shift to the overlaid image so it cascades in the feedback loop
    overlay = hue(overlay, .3);
    
    // Additively blend the colors together
    vec4 col = vec4(clamp(vec3(0.),vec3(1.),base + overlay*0.86), 1.0);
    
    fragColor = col;
}

and also using a .vert

#version 150 core

in vec3 position;
in vec2 texcoord;
uniform mat4 modelViewProjectionMatrix;

out vec2 TexCoord;

void main()
{
    gl_Position = modelViewProjectionMatrix * vec4(position, 1.0f);
    TexCoord = texcoord;
}

and…

ofGLFWWindowSettings settings;
    
    settings.setGLVersion(3, 2);
    settings.setSize(1000, 1000);
    ofCreateWindow(settings);

thanks for reading. what im doing wrong? im only see a yellow sphere

do you get the feedback to work?
there seems to be an isuee with RGBA32F_ARB, try instead RGBA.
I am not sure if it matters but I’d rather set all the shader’s uniforms before drawing anithing.

try with RGBA and nothing. have you tested the example that i posted? just to check if is the shader are ok . And nop, i dont get the feedback in the shader… and i dont know why

Hey @pxt & @roymacdonald!

I was interested in your shader ping pong problem.

I think in your case, you need to not swap the Fbos.
Try removing

pingpong.swap();

If you leave it, then the source Fbo gets replaced by the destination Fbo with the first step of the shader and it gets replaced by

 ofClear(0);

so in essence it wouldn’t apply the effect.

So to resume :

ofApp.cpp::draw(){
    ofClear(0);
    
    pingpong.source()->begin();
    ofClear(0);
    sphere.setRadius(sin(ofGetElapsedTimef())*50);
    
    sphere.setPosition(500, 500, 0);
    sphere.draw();
    pingpong.source()->end();
    
    ofDisableDepthTest();
    //
    pingpong.dest()->begin();
    shaderBufferB.begin();
    
    shaderBufferB.setUniformTexture("feedback", pingpong.dest()->getTexture(), 1);
    shaderBufferB.setUniform2f("resolution", pingpong.source()->getWidth(), pingpong.source()->getHeight());
    shaderBufferB.setUniform1f("u_time", ofGetElapsedTimef());
    shaderBufferB.setUniform1i("frameCount",ofGetFrameNum());
    
    
    pingpong.source()->draw(0,0);
    
    shaderBufferB.end();
    
    pingpong.dest()->end();
    
    pingpong.dest()->draw(0, 0, ofGetWidth(), ofGetHeight());
    
    
    //this line is super important as it will swap the buffers so what was the source now will be the destination and vice versa.
//    pingpong.swap();

};

(tested on Mac of10.0).

Hope it works for you too.

++

P

Hi @pierre_tardif00,
The whole point of the pingpong fbo is swaping it. If you dont swap it you will not get proper feedback.
Also, the implementation of it is wrong ( I am guilty for it).
So I was testing last night, and the only thing that seemed to fix the problem was to use GL_RGBA as the fbo’s format.

I’ve modified slightly the FboPingPong class so I’ve added it again here.
So this is working for me
ofApp.h

#pragma once

#include "ofMain.h"
#include "FboPingPong.h"
#include "ofxGui.h"



class ofApp : public ofBaseApp{

	public:
		void setup();
//		void update();
		void draw();

	void keyReleased(int key);
	
		//void windowResized(int w, int h);
		
		FboPingPong pingpong;
	ofShader shader;

	string frag, vert, fragTest, fragFeedback;
	
	ofFbo fbo;

	
	ofxPanel gui;
	ofParameter<float> feedback;
};

ofApp.cpp


#include "ofApp.h"
void ofApp::setup(){
	ofSetLogLevel(OF_LOG_VERBOSE);
	
	gui.setup();
	gui.add(feedback.set("feedback", 0.9, 0, 1));
	
	vert = "passV.glsl";
	fragTest = "test.frag";
	fragFeedback = "feedbackLoop.glsl";
	frag = fragFeedback;
	shader.load(vert, frag);
	pingpong.allocate(500,500, GL_RGBA);
	fbo.allocate(500,500, GL_RGBA);
}
void ofApp::draw(){
	
	fbo.begin();
	ofClear(0, 0);
	ofSetColor(255,0,0);
	ofDrawSphere(ofGetMouseX(), ofGetMouseY(), 0,sin(ofGetElapsedTimef())*50); 
	fbo.end();
	
	
	pingpong.dest().begin();
	ofClear(0,0);
	shader.begin();
	shader.setUniformTexture("tex0", fbo.getTexture(), 0);
	shader.setUniformTexture("feedback", pingpong.source().getTexture(), 1);
	shader.setUniform1f("feedbackAmt",feedback.get());

	shader.setUniform1f("u_time", ofGetElapsedTimef());
	
	
	ofSetColor(255);
	fbo.draw(0,0);
	shader.end();
	pingpong.dest().end();
	
	ofSetColor(255);
	
	pingpong.dest().draw(0, 0);
	ofDrawBitmapStringHighlight("pingpong dest", 20, 20);
	
	pingpong.source().draw(500, 0);
	ofDrawBitmapStringHighlight("pingpong source", 520, 20);
	
	fbo.draw(0,500);
	ofDrawBitmapStringHighlight("fbo", 20, 520);
	
	
	ofBitmapFont bf;
	string msg = "Using frag shader: " + frag;
	auto r = bf.getBoundingBox(msg, 0, 0);
	ofDrawBitmapStringHighlight(msg, ofGetWidth() - r.width - 10, ofGetHeight() - 10);
	
	//this line is super important as it will swap the buffers so what was the source now will be the destination and vice versa.
	pingpong.swap();
	
	
	gui.draw();
	
}
void ofApp::keyReleased(int key){
	if(key == '1'){
		frag = fragTest;
		shader.load(vert, frag);
	}else if(key == '2'){
		frag = fragFeedback;
		shader.load(vert, frag);
	}else if(key == ' '){
		shader.load(vert, frag);
	}
}

FboPingPong.h


//
//  FboPingPong.h
//  emptyExample
//
//  Created by Andreas Müller on 12/08/2013.
//
//

#pragma once

#include "ofMain.h"

class FboPingPong
{
public:
	
	void allocate( int _w, int _h, int internalformat = GL_RGB, ofColor _clearColor = ofColor(255,255,255) );
	void allocate( ofFbo::Settings _settings, ofColor _clearColor = ofColor(255,255,255) );
	
	ofFbo& source() { return fbos[sourceIndex];	}
	ofFbo& dest()	{ return fbos[destIndex];	}
	
	void draw( const glm::vec2& _pos, float _width, bool _drawBack = false );
	
	void clearBoth();
	void clearBoth( ofColor _clearColor );
	
	void clearSource();
	void clearDest();
	
	void clearSource( ofColor _clearColor );
	void clearDest( ofColor _clearColor );
	
	void setClearColor( ofColor _color );
	
	void swap();
	
private:
	
//	ofFbo* sourceBuffer;
//	ofFbo* destBuffer;
	
	ofFbo fbos [2];
//	ofFbo fbo2;
	
	ofColor clearColor;
	
	int sourceIndex = 0;
	int destIndex = 1;	
};

FboPingPong.cpp


//
//  FboPingPong.cpp
//  emptyExample
//
//  Created by Andreas Müller on 12/08/2013.
//
//

#include "FboPingPong.h"

// ------------------------------------------------------------------------------------
//
void FboPingPong::allocate( int _w, int _h, int _internalformat, ofColor _clearColor )
{
	ofFbo::Settings settings = ofFbo::Settings();
	settings.width  = _w;		
	settings.height = _h;
	settings.useDepth = true;
	settings.internalformat = _internalformat;
	
	allocate( settings, _clearColor );
}

// ------------------------------------------------------------------------------------
//
void FboPingPong::allocate( ofFbo::Settings _settings, ofColor _clearColor )
{
	clearColor = _clearColor;
	
	fbos[0].allocate( _settings);
	fbos[1].allocate( _settings );
		
	clearSource();
	clearDest();
}

// ------------------------------------------------------------------------------------
//
void FboPingPong::draw( const glm::vec2& _pos, float _width, bool _drawBack )
{
	float desWidth = _width;
	float desHeight = (source().getWidth() / source().getHeight()) * desWidth;
	
	source().draw( _pos.x, _pos.y, desWidth, desHeight );
	
	if( _drawBack ){
		dest().draw( _pos.x + desWidth, _pos.y, desWidth, desHeight );
	}
}

// ------------------------------------------------------------------------------------
//
void FboPingPong::clearBoth()
{
	clearSource();
	clearDest();
}

// ------------------------------------------------------------------------------------
//
void FboPingPong::clearBoth( ofColor _clearColor )
{
	clearSource( _clearColor );
	clearDest( _clearColor );
}

// ------------------------------------------------------------------------------------
//
void FboPingPong::clearSource()
{
	clearSource( clearColor );
}

// ------------------------------------------------------------------------------------
//
void FboPingPong::clearDest()
{
	clearDest( clearColor );
}

// ------------------------------------------------------------------------------------
//
void FboPingPong::clearSource( ofColor _clearColor )
{
	source().begin();
	ofClear( _clearColor );
	source().end();
}

// ------------------------------------------------------------------------------------
//
void FboPingPong::clearDest( ofColor _clearColor )
{
	dest().begin();
	ofClear( _clearColor );
	dest().end();
}

// ------------------------------------------------------------------------------------
//
void FboPingPong::setClearColor( ofColor _color )
{
	clearColor = _color;
}

// ------------------------------------------------------------------------------------
//
void FboPingPong::swap()
{
	(++sourceIndex)%=2;
	destIndex = (sourceIndex + 1)%2;
//	std::swap(sourceBuffer, destBuffer);
}

and the shaders

test.frag


#version 150
uniform sampler2DRect tex0;
uniform sampler2DRect feedback;

uniform float feedbackAmt;

out vec4 outColor;


void main(){	

	vec2 tc = gl_FragCoord.xy;	

	vec4 tx = texture(tex0, tc) ;

	vec4 fc = texture(feedback, tc); 
	fc.a *= feedbackAmt;
	
	outColor = fc  + tx;
	
}

feedbackLoop.glsl

#version 150
uniform sampler2DRect tex0;
uniform sampler2DRect feedback;

uniform float u_time;

out vec4 fragColor;

uniform float feedbackAmt;


float hash( float n )
{
    return fract(sin(n)*43758.5453123);
}

float noise( in vec2 x )
{
    vec2 p = floor(x);
    vec2 f = fract(x);
    
    f = f*f*(3.0-2.0*f);
    
    float n = p.x + p.y*157.0;
    
    return mix(mix( hash(n+  0.0), hash(n+  1.0),f.x),
               mix( hash(n+157.0), hash(n+158.0),f.x),f.y);
}

// hue by netgrind(?)

vec3 hue(vec3 color, float shift) {
    
    const vec3  kRGBToYPrime = vec3 (0.299, 0.587, 0.114);
    const vec3  kRGBToI     = vec3 (0.596, -0.275, -0.321);
    const vec3  kRGBToQ     = vec3 (0.212, -0.523, 0.311);
    
    const vec3  kYIQToR   = vec3 (1.0, 0.956, 0.621);
    const vec3  kYIQToG   = vec3 (1.0, -0.272, -0.647);
    const vec3  kYIQToB   = vec3 (1.0, -1.107, 1.704);
    
    // Convert to YIQ
    float   YPrime  = dot (color, kRGBToYPrime);
    float   I      = dot (color, kRGBToI);
    float   Q      = dot (color, kRGBToQ);
    
    // Calculate the hue and chroma
    float   hue     = atan (Q, I);
    float   chroma  = sqrt (I * I + Q * Q);
    
    // Make the user's adjustments
    hue += shift;
    
    // Convert back to YIQ
    Q = chroma * sin (hue);
    I = chroma * cos (hue);
    
    // Convert back to RGB
    vec3    yIQ   = vec3 (YPrime, I, Q);
    color.r = dot (yIQ, kYIQToR);
    color.g = dot (yIQ, kYIQToG);
    color.b = dot (yIQ, kYIQToB);
    
    return color;
}

float fractalNoise(vec2 pos) {
    float n = 0.;
    float scale = 1. / 1.5;
    for (int i = 0; i < 5; i += 1) {
        n += noise(pos) * scale;
        scale *= 0.5;
        pos *= 2.;
    }
    return n;
}
void main(){
    vec2 uv = gl_FragCoord.xy;

    vec2 polarUv = (uv * 2.0 - 1.0);
    float angle = atan(polarUv.y, polarUv.x);
    
    // // Scale up the length of the vector by a noise function feeded by the angle and length of the vector
    float ll = length(polarUv)*0.5 - fractalNoise(vec2(sin(angle*4. + u_time*2.) + length(uv)*10., length(uv)*20. + sin(angle*4.)))*0.005 ;
    
    vec3 base = texture(tex0, uv).rgb;
    
    // // Convert the scaled coordinates back to cartesian
    vec2 offs = vec2(cos(angle)*ll + 0.5, sin(angle)*ll + 0.5);
    
    // // sample the last texture with uv's slightly scaled up
    vec3 overlay = texture(feedback, offs).rgb;
    
    // // Since the colors of the iChannel0 are monochrome, set a color channel to zero to do a hue shift
    base.b = 0.;
    
    // // Apply a hue shift to the overlaid image so it cascades in the feedback loop
    overlay = hue(overlay, .3);
    
    // // Additively blend the colors together
    vec4 col = vec4(clamp(base + overlay*feedbackAmt, vec3(0.),vec3(1.)), 1.0);
    // vec4 col = vec4(base + overlay*feedbackAmt, 1.0);

    fragColor = col;
    
    // fragColor = texture(tex0, uv);
}
1 Like

there something wrong with me…

EDIT. my mystake, the shader was wrong. now is working! Thanks!

good to know.

So in short, the idea is that you use the ping pong fbos and its related shader as a “image processing module”, to which you have to pass some image, in this case it is the other fbo into which we draw. Then as you run the app the ping pong fbos keep on swapping so you get what was being drawn in the previous frame.
There are a few really good addons that implement this idea, so you dont have to manage the pingpongs and fbos and such. You should check these. ofxFX, ofxPostProcessing and ofxDeferredRendering.
Cheers

1 Like

Ah yeah!

I did try to get the swapping to work, but for some reasons I got mixed up with thinking that the ofClear(0,0) was erasing it…

Thanks!

++

Just for the sake of comparison, I’ve been using feedback loops in Processing and OPENRNDR for a while: