Some notes about the idea and the process behind this demo. I’ve started to work thinking that it could be nice to put some rings that move up and down on the trees, maybe in sync whith music. draw Using ofxLsystem was pretty easy to have a mesh containing a forest of ~80 trees, all I need to do is to put rings on them.

The rings

After reading the wonderful The book of shaders, and especially the chapter dedicated to the shaping functions, I’ve decided to use a shader to draw the ring on the surface of the trees. In the book was linked this example, that uses a function called cubicPulse by Iñigo Quiles to create a smooth effect.

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;

//  Function from Iñigo Quiles 
//  www.iquilezles.org/www/articles/functions/functions.htm
float cubicPulse( float c, float w, float x ){
    x = abs(x - c);
    if( x>w ) return 0.0;
    x /= w;
    return 1.0 - x*x*(3.0-2.0*x);
}

void main() {
    vec2 st = gl_FragCoord.xy/u_resolution;
    // now the ring is moving following the time, it has to move following the sound!
    float y = cubicPulse(mod(u_time,0.99),0.2,st.y);
    vec3 color = vec3(y);
    color = color*vec3(0.0,1.0,0.0);
    gl_FragColor = vec4(color,1.0);
}

The mesh containing the trees

I did not want to apply a shader for each tree because it could have been slow, also considering that I will need to continuously update the uniform containing the current sound amplitude to have the rings moving in sync with the music. That’s why, in openFrameworks, I’ve merged all the trees in a single mesh.This mesh will be loaded later in the threeJS sketch.

for(int i = 0; i<nTree; i++){
    ofxLSystem tree;
    tree.setAxiom("A(100)");
    tree.setRules({"A(s) -> F(s)[+A(s/R)][-A(s/R)]"});
    tree.setRandomYRotation(true);
    tree.setConstants(constants);
    tree.setStep(abs(ofRandom(5, 10)));
    tree.setScaleWidth(true);
    tree.setStepWidth(abs(ofRandom(30.5, 40.5)));
    tree.setStepLength(abs(ofRandom(150,300)));
    tree.build();
    tree.pan(ofRandom(30.00, 330.00));
    auto pos = ofVec2f(ofRandom(-halfScreen, halfScreen),
            ofRandom(-halfScreen, halfScreen));

    tree.setPosition(ofVec3f(pos.x, 0, pos.y));
    for (int i=0; i<tree.getMesh().getNumVertices(); i++) {
        auto actualVert =tree.getMesh().getVerticesPointer()[i];
        auto newVert = actualVert * tree.getGlobalTransformMatrix();
        tree.getMesh().getVerticesPointer()[i] = newVert;
    }
    forest.append(tree.getMesh());
}

In this repository, in the folder “example-rings” you can find the whole openFrameworks application, shaders and onset detection included. The mesh was still pretty big, as I do not need high resolution, I’ve used meshLab to simplyfy the mesh.

The sound

Maximilian is not only a c++ library that can be easily integrated with openFrameworks using ofxMaxim, but it has also a javascript port that simplifies a lot working with sound in the browser.

To play a sample in with Maximilian is super easy.

var maxiAudio = new maximJs.maxiAudio(); // the maxi audio object
var sample = new maximJs.maxiSample(); // object that will contain the song loaded
var ctx = new AudioContext(); // the audio context. see http://www.html5rocks.com/en/tutorials/webaudio/intro/

//loading the sound and the mesh
$.when(
        maxiAudio.loadSample('beat.wav', sample, ctx),
        loadPly('tree.ply')
      ).then(
        function (_, treePly) {
            init(treePly);
            animate();
        },
        function (error) {
            console.log(error);
        }
);

In the init method I’ve added a method called initAudio that plays the sample


function initAudio(){
    maxiAudio.init();
    // I need the output as an array later
    maxiAudio.outputIsArray(true, 2);
    maxiAudio.play = function(scenography) {
        var wave1 = sample.play();
        var mix = wave1 * config.volume; // in case you have other samples, just add them here: var mix =  wave1 +wave2;
        this.output[0] = mix;
        this.output[1] = this.output[0];
        var left = this.output[0];
        var right = this.output[1];
    }
}

The function maxiAudio.loadSample sets the variable sample. The third parameter ctx is the AudioContext created at the beginning. In the init method of the threejs sketch I call initAudio. This function get called only once, but the content of the loop maxiAudio.play = function() { get executed until we close the page. This is related to the nature of the AudioContext. There is a good introduction of Web Audio API at this page

Beat detection

What i wanted to do is to move the rings up and down depending on the music. The easiest way is to implement an onset detection, a way to find “the beat”, the moment when the sound “kicks”. I’ve used Root Mean Square calculation (RMS), there is a nice explanation of it in the ofBook. I’ve edited the initAudio method setting the value of rms as follow:

function initAudio(){
    maxiAudio.init(); //initialize the maxiAudio
    maxiAudio.outputIsArray(true, 2); // the output is an array containing 2 channels, left and right
    maxiAudio.play = function() {
        bufferCount++;
        var wave1 = sample.play(); // play the sample
        var mix = wave1 * config.volume; // in case you have other samples, just add them here: var mix =  wave1 +wave2;
        this.output[0] = mix;
        this.output[1] = this.output[0];
        // in case we want to do smth else in the future with the current bufferOut
        bufferOut[bufferCount % 1024] = mix;
        var left = this.output[0];
        var right = this.output[1];
        rms += left * left;
        rms += right * right;
        examplesCounted += 2;
    }
}

later, in the animate function of the threejs sketch i’ve smoothed the rms value, and updated the related uniform.

function calcRms(bufferOut) {
    if (bufferOut.length === 1024) {
        rms /= examplesCounted;
        rms = Math.sqrt(rms);

        threshold = lerp(threshold, this.config.minTreshold, this.config.decayRate);
        if (rms > threshold) {
            threshold = rms;
        }
        treeMaterial.uniforms.rms.value = threshold;
    }
}

Sound and shader

Unsing a sin(u_time) in the previous shader, the rings are moving like this:

rings

To move the the ring up and down I’ve first to identify a 0 value on the y axis, then add a uniform containing the previously calculated rms value, decide how tick the ring will be , calculating the padding between the rings and finally draw as many ring as desidered. To simply draw a ring at the base of the mesh, that moves up and down following the rms value, i can modify the previuos shader like this:

vec4 tmpCol = vec4(treeColor, 1.0);
float y = cubicPulse(mod(rms * scaleRing, 1.0),ringThickness,screenY);
tmpCol += vec4( vec3( dProd ) *vec3( y ) * vec3(ringColor), y);

treeColor is the color of the tree, rms is the currently calculated rms, scaleRing is a value used to amplify the movement of the ring, ringThickness is a value that decide the thickness of the ring, and screenY is a value calculated in the vertex shader with screenY = position.y/uResolution.y, that determinates the initial position of the ring. You can change all these uniforms in the demo, pressing g to see how they affect it.

To draw more than one ring I’ve used a loop. Compared to OpenGL, WebGL has some limitations for loops. In webGL, the upper limit of the loop has to be a declared constant, MAX_NUM_RINGS in my shader. When the shader compile, this loop is unrolled as many time as defined in that constant. To execute the instruction contained in the loop as many time as defined in the uniform ringNumber, I’ve to break the loop at the desidered value.

#define MAX_NUM_RINGS 18
for (int i = 0; i< MAX_NUM_RINGS; i++) {
  if (i == ringNumber ) break;
  //draw the ring!
end

The distance between each ring is stored in the variable padding and it depends on how many rings are currently drawn. It is calculated like this:

float inc = 1.0/float(ringNumber);
float padding = 0.0;
//arbitrary light position
vec3 lightPos = vec3(0.5, 0.2, 0.5);
vec3 lightDirection = normalize(vecPos.xyz - lightPos);
float dProd = max(0.6,dot(vecNormal, lightDirection));

vec4 tmpCol = vec4(treeColor, 1.0);
for (int i = 0; i< MAX_NUM_RINGS; i++) {
  if (i == ringNumber ) break;
  float y = cubicPulse(mod(rms * scaleRing, 1.0),ringThickness,screenY - padding);
  //apply light only to the rings
  tmpCol += vec4( vec3( dProd ) *vec3( y ) * vec3(ringColor), y);
  padding +=inc;
}
gl_fragColor = tmpCol;

Fragment and Vertex shaders are both available in the repository

draw