Let's begin by looking at a minimal OpenGL application written with Cinder. This barebones app simply draws a blank, black window:
#include "cinder/app/App.h"
#include "cinder/app/RendererGl.h"
#include "cinder/gl/gl.h"
using namespace ci;
using namespace ci::app;
class BasicApp : public App {
public:
void draw() override;
};
void BasicApp::draw()
{
gl::clear();
}
CINDER_APP( BasicApp, RendererGl )
In order to use OpenGL, a Cinder app instantiates CINDER_APP
macro. The most straightforward way to start to use Cinder's OpenGL API is to #include "cinder/gl/gl.h"
. While this does not include all of Cinder's OpenGL headers, it does include some of the commonly used ones.
Often you're more interested in ease of implementation than performance. Cinder's draw()
method follows:
void BasicApp::draw()
{
gl::clear();
gl::drawSolidCircle( getWindowCenter(), 200 );
}
We're making use of the
Let's make another modification to the draw()
method:
void BasicApp::draw()
{
gl::clear();
vec2 center = getWindowCenter();
float r = 100;
gl::color( Color( 1, 0, 0 ) ); // red
gl::drawSolidCircle( center + vec2( -r, r ), r );
gl::color( Color( 0, 1, 0 ) ); // green
gl::drawSolidCircle( center + vec2( r, r ), r );
gl::color( Color( 0, 0, 1 ) ); // blue
gl::drawSolidCircle( center + vec2( 0, -0.73 * r ), r );
}
The snippet above highlights the
Traditionally in computer graphics, transformations (operations like translation, scale and rotation) are implemented using 4x4 matrices. Cinder's Model
matrix is a global transformation which is applied to any draw call. Routines like Model
matrix.
void BasicApp::draw()
{
gl::clear();
// reset the matrices
gl::setMatricesWindow( getWindowSize() );
// move to the horizontal window center, down 75
gl::translate( getWindowCenter().x, 75 );
gl::color( Color( 1, 0, 0 ) );
gl::drawSolidCircle( vec2( 0 ), 70 );
// move down 150 pixels
gl::translate( 0, 150 );
gl::color( Color( 1, 1, 0 ) );
gl::drawSolidCircle( vec2( 0 ), 70 );
// move down another 150 pixels
gl::translate( 0, 150 );
gl::color( Color( 0, 1, 0 ) );
gl::drawSolidCircle( vec2( 0 ), 70 );
}
There's a couple of things to notice in this code, the first being the call to Model
matrix to Cinder's default, window-aligned configuration when passed the Window's current size (via Model
matrix indefinitely, and our circles would quickly be too far down to see. This is because the effects of Model
matrix rather than replacing it. In the case above, a call to gl::translate( 0, 150 )
translates the current Model
matrix 150 units vertically; note it does not set the translation to ( 0, 150 )
.
Also key is to note that we draw the circles with an offset of (0,0)
now, since we are relying on the Model
matrix manipulated by
For many use cases, it can be convenient to preserve, manipulate and then restore the Model
matrix. Cinder makes this simple by using a stack of matrices, coupled with the routines Model
matrix, and
void BasicApp::draw()
{
gl::clear();
// preserve the default Model matrix
gl::pushModelMatrix();
// move to the window center
gl::translate( getWindowCenter() );
int numCircles = 16;
float radius = getWindowHeight() / 2 - 30;
for( int c = 0; c < numCircles; ++c ) {
float rel = c / (float)numCircles;
float angle = rel * M_PI * 2;
vec2 offset( cos( angle ), sin( angle ) );
// preserve the Model matrix
gl::pushModelMatrix();
// move to the correct position
gl::translate( offset * radius );
// set the color using HSV color
gl::color( Color( CM_HSV, rel, 1, 1 ) );
// draw a circle relative to Model matrix
gl::drawStrokedCircle( vec2(), 30 );
// restore the Model matrix
gl::popModelMatrix();
}
// draw a white circle at window center
gl::color( Color( 1, 1, 1 ) );
gl::drawSolidCircle( vec2(), 15 );
// restore the default Model matrix
gl::popModelMatrix();
}
In this example, we preserve the default Model
matrix by calling draw()
, and calling Model
matrix and translate by this offset. Recall that because these transformations are cumulative, this translation is relative to the window center, due to our earlier call to Model
matrix. We then restore the matrix (setting it to be the window center once again) using draw()
.
If you're wondering if we could achieve the same thing simply by passing offset * radius
as the first parameter to
void BasicApp::draw()
{
gl::clear();
// preserve the default Model matrix
gl::pushModelMatrix();
// move to the window center
gl::translate( getWindowCenter() );
int numCircles = 32;
float radius = getWindowHeight() / 2 - 30;
for( int c = 0; c < numCircles; ++c ) {
float rel = c / (float)numCircles;
float angle = rel * M_PI * 2;
vec2 offset( cos( angle ), sin( angle ) );
// preserve the Model matrix
gl::pushModelMatrix();
// move to the correct position
gl::translate( offset * radius );
// rotate by current angle
gl::rotate( angle );
// non-uniform scale
gl::scale( 8, 0.25f );
// set the color using HSV color
gl::color( Color( CM_HSV, rel, 1, 1 ) );
// draw a circle based on the current Model matrix
gl::drawSolidCircle( vec2(), 20 );
// restore the Model matrix
gl::popModelMatrix();
}
// restore the default Model matrix
gl::popModelMatrix();
}
In this example, we demonstrate a couple of new things. For each circle, we start by scaling it non-uniformly, making it 8 times as wide and ¼ as tall, by passing ( 8, 0.25f )
to
void BasicApp::draw()
{
…
for( int c = 0; c < numCircles; ++c ) {
…
gl::translate( offset * radius );
// reversed scale & rotate
gl::scale( 8, 0.25f );
gl::rotate( angle );
gl::drawSolidCircle( vec2(), 20 );
…
}
…
}
Here we've only swapped the order of the calls to
In the next section we'll look at how to take these concepts into 3D.