Poor Man's Caustics in openFrameworks

Recently I did a project where we were asked to program "caustics". I came up with this, it's not really caustics, but a nice effect in any case. Grab the code on github.

1. Particle System


Create any old particle system. I made one that has particles that start with an initial random velocity and bounces off the sides of the window. Pretty straight forward.

class Particle: public ofVec2f {
public:
	ofVec2f vel;
	Particle() {
		x = ofRandomWidth();
		y = ofRandomHeight();
		vel.set(ofRandom(0.3, 2), 0);
		vel.rotate(ofRandom(0, 360));
	}
 
	void update() {
		*this += vel;
 
		if(x<0) {
			x = 0;
			vel.x *= -1;
		}
 
		if(y<0) {
			y = 0;
			vel.y *= -1;
		}
 
		if(x>ofGetWidth()) {
			x = ofGetWidth();
			vel.x *= -1;
		}
 
		if(y>ofGetHeight()) {
			y = ofGetHeight();
			vel.y *= -1;
		}
	}
	void draw() {
		ofCircle(*this, 3);
	}
};

2. Triangulation

Looking at caustic images on the internet, it looks like a load of wavy lines joined to eachother, (e.g. https://www.google.co.uk/search?q=caustics&tbm=isch), so I thought to draw lines between all the points. The best way to do this is use a triangulation algorithm. Triangulating means, very simply, drawing lines in between the points so that it resembles a mesh of triangles.

For this, I used ofxDelaunay, which is a wrapper for Gilles Dumoulin's C++ implementation of Paul Bourke's Delaunay Triangulation algorithm.

It's easy enough to use:

// declaration
ofxDelaunay triangulator;
 
...
 
// initialization
triangulator.init(maxNumPoints);
 
...
 
// every frame
triangulator.reset();
for(int i = 0; i < particles.size(); i++) {
	triangulator.addPoint(particles[i].x, particles[i].y);
}
triangulator.triangulate();
 
...
 
// to draw it.
int numTris = triangulator.getNumTriangles();
ITRIANGLE *tris = triangulator.getTriangles();
XYZ *points = triangulator.getPoints();
 
for(int i = 0; i < numTris; i++) {
	ofVec2f a = ofVec2f(points[tris[i].p1].x, points[tris[i].p1].y);
	ofVec2f b = ofVec2f(points[tris[i].p2].x, points[tris[i].p2].y);
	ofVec2f c = ofVec2f(points[tris[i].p3].x, points[tris[i].p3].y);
 
	ofLine(a, b);
	ofLine(b, c);
	ofLine(c, a);
}

Note that the triangulator actually returns an array of triangles that are defined by coordinate indexes instead of actual points. i.e. each triangle is specified by 3 numbers which are the array positions of its corners. You can just copy/paste it if you don't get it.

3. Waviness

So now we have a triangular mesh. What we need to do is make the lines in the triangle wave about. The way we do this is by drawing a sine wave along the line. To make it actually animate, we offset the sinewave by the current time. We also have to taper the ends. The way to do this is to multiply your sinewave by a windowing function - one that starts at zero, goes slowly to one as it approaches the middle, and back down to zero. The actual function is y = 1-4*(x-0.5)*(x-0.5); I worked it out using grapher (the graphing program that comes with OS X.


Sine

x

Window

=

Windowed sine


Now we have to do some maths to draw it along the line. For this, we simply step along the line in increments, and add to the current vector, the normalized normal of the line's vector by this sine wave. Below is how to do this.
void drawWavyLine(ofVec2f a, ofVec2f b) {
 
	ofVec2f diff = (b-a);
	float length = diff.length();
 
 
	// now calculate the normal, normalized
	ofVec2f n = diff/length;
 
	// turn (x, y) into (y, -x) - this flips the vector 90 degrees
	float ny = n.y;
	n.y = -n.x;
	n.x = ny;
 
 
	glBegin(GL_LINE_STRIP);
 
 
	for(float f = 0; f < PI*2; f+=PI/5.f) {
		float d = f/TWO_PI;
 
		float window = 1-4*(d-0.5)*(d-0.5);
		ofVec2f sine = n * sin(f+currTime)*ofMap(length, 0, 200, 0, 8)*window;
		ofVec2f p = sine + a + diff * d;
 
		glVertex2f(p.x, p.y);
	}
	glVertex2f(b.x, b.y);
 
	glEnd();
}

4. Accumulation

Now the lines are wavy and animated, we need to draw a sort of trail of where the wave has waved, like an echo. You can simply do this by turning off background auto (ofSetBackgroundAuto(false)) and drawing a slightly opaque black rectangle over the frame every screen. But I wanted it drawn to a texture, so I used ofFbo.

The idea to draw the previous frame to the screen, with an alpha of 95% or something, then draw the triangulation. In order to this you need 2 fbo's and you need to swap them every time:

// frame 1
start drawing into FBO (A)
draw FBO (B) with alpha 95%
draw Triangulation
end drawing into FBO (A)
 
// FBO (A) now has the current frame in it.
 
// frame 2
start drawing into FBO(B)
draw FBO (A) with alpha 95%
draw Triangulation
end drawing into FBO (B)
 
// now FBO (B) has the current frame in it.

This is called ping-pong. If you draw the previous frame slightly scaled up (100.1%) you get a sort of zoom blur:

I think that's it. Here's the code




Comments

and fast! also a great explanation of ping-ponging - thanks!

Thanks a lot for sharing such great tutorial

Add new comment