Created
July 16, 2015 04:11
-
-
Save paulhoux/668ffb8074d1799bc605 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#version 410 | |
// From our C++ code, we will tell the | |
// shader how many seconds have elapsed. | |
uniform float uTime = 0.0; | |
// Adjust for different aspect ratios. | |
uniform float uAspectRatio = 1.0; | |
// The GPU rasterizer has interpolated the | |
// texture coordinates, which now have the | |
// correct values for this pixel (fragment). | |
in vec2 vertTexCoord0; | |
// The output is an RGBA color, even if we | |
// only want grayscale. | |
out vec4 fragColor; | |
void main( void ) | |
{ | |
// Start with black. | |
fragColor = vec4( 0, 0, 0, 1 ); | |
// Be creative :) | |
vec2 uv = vertTexCoord0 * 2.0 - 1.0; | |
uv.x *= uAspectRatio; | |
float circular = dot( uv, uv ); | |
float polar = atan( uv.y, uv.x ); | |
fragColor.r += 0.5 + 0.5 * cos( 4.0 * circular + 0.2 * uTime ); | |
fragColor.r *= 0.5 + 0.5 * cos( 5.0 * polar - 0.5 * uTime ); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#version 410 | |
// Automatically provided by Cinder. | |
uniform mat4 ciModelViewProjection; | |
// Input attributes automatically provided by Cinder. | |
in vec4 ciPosition; | |
in vec2 ciTexCoord0; | |
// Output attributes which we will have to provide ourselves. | |
out vec2 vertTexCoord0; | |
void main( void ) | |
{ | |
// Output the vertex coordinate. The GPU will | |
// interpolate it for us, so that it has the | |
// correct value for every pixel. | |
vertTexCoord0 = ciTexCoord0; | |
// OpenGL requires us to transform every vertex | |
// to so-called normalized device coordinates. | |
// This sounds harder than it is: | |
gl_Position = ciModelViewProjection * ciPosition; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#version 410 | |
// The output is an RGBA color. | |
out vec4 fragColor; | |
void main(void) | |
{ | |
// Make the particles green. | |
fragColor.rgb = vec3( 0, 0.25, 0.1 ); | |
// With a little trick, we can make them round. | |
vec2 uv = gl_PointCoord.xy * 2.0 - 1.0; | |
float r = dot( uv, uv ); | |
fragColor.a = 1.0 - smoothstep( 0.45, 0.55, r ); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#version 410 | |
// Automatically provided by Cinder. | |
uniform mat4 ciModelViewProjection; | |
layout(location = 0) in vec4 iPositionVelocity; // xy = position, zw = velocity | |
void main(void) | |
{ | |
// Just output the position and a size. | |
gl_PointSize = 8.0; | |
gl_Position = ciModelViewProjection * vec4( iPositionVelocity.xy, 0.0, 1.0 ); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#version 410 | |
// We'd like to know the size of the window. | |
uniform vec2 uWindowSize; | |
// We'd like to sample the dynamic background. | |
uniform sampler2D uTexBackground; | |
// Automatically provided by Cinder. | |
uniform mat4 ciModelViewProjection; | |
// Input attributes: our position and velocity. | |
layout(location = 0) in vec4 iPositionVelocity; | |
// Output attributes: should be the same. | |
layout(location = 0) out vec4 oPositionVelocity; | |
void main( void ) | |
{ | |
// Start by copying the current data. | |
oPositionVelocity = iPositionVelocity; | |
// Convert particle position to a normalized texture coordinate, | |
// so that we can sample the background texture. This is simple | |
// in our case, because the texture has the same size as the window. | |
vec2 coord = oPositionVelocity.xy / uWindowSize; | |
// Sample the background image four times and | |
// determine the slope. | |
float top = textureOffset( uTexBackground, coord, ivec2(0, -1) ).r; | |
float left = textureOffset( uTexBackground, coord, ivec2(-1, 0) ).r; | |
float bottom = textureOffset( uTexBackground, coord, ivec2(0, 1) ).r; | |
float right = textureOffset( uTexBackground, coord, ivec2(1, 0) ).r; | |
vec2 slope = vec2( right - left, bottom - top ); | |
// Update velocity. Particle will slow down when going uphill. | |
oPositionVelocity.zw = 0.998 * oPositionVelocity.zw - 1.0 * slope; | |
// Update position. | |
oPositionVelocity.xy += oPositionVelocity.zw; | |
// Make sure the particle does not leave the window. | |
if( oPositionVelocity.x < 0 ) | |
oPositionVelocity.x += uWindowSize.x; | |
else if( oPositionVelocity.x >= uWindowSize.x ) | |
oPositionVelocity.x -= uWindowSize.x; | |
if( oPositionVelocity.y < 0 ) | |
oPositionVelocity.y += uWindowSize.y; | |
else if( oPositionVelocity.y >= uWindowSize.y ) | |
oPositionVelocity.y -= uWindowSize.y; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "cinder/app/App.h" | |
#include "cinder/app/RendererGl.h" | |
#include "cinder/gl/gl.h" | |
#include "cinder/Rand.h" | |
using namespace ci; | |
using namespace ci::app; | |
using namespace std; | |
class VivekSampleApp : public App { | |
public: | |
// Allows us to override default window size, among other things. | |
static void prepare( Settings *settings ); | |
void setup() override; | |
void update() override; | |
void draw() override; | |
//! Called when the window is resized. | |
void resize() override; | |
void keyDown( KeyEvent event ) override { setupShaders(); } | |
private: | |
void setupShaders(); | |
void updateBackground(); | |
void renderBackground(); | |
void setupParticles(); | |
void updateParticles(); | |
void renderParticles(); | |
private: | |
gl::FboRef mBackgroundFbo; | |
gl::GlslProgRef mBackgroundShader; | |
gl::GlslProgRef mParticleTransformShader; | |
gl::GlslProgRef mParticleShader; | |
typedef struct { | |
ci::gl::VaoRef mVao; | |
ci::gl::VboRef mVbo; | |
ci::gl::TransformFeedbackObjRef mTransformFeedback; | |
} ParticleBuffer; | |
// For our particle system, we need a set of two buffers: | |
// one buffer to read from, one buffer to write to. | |
std::array<ParticleBuffer, 2> mBuffers; | |
// We will alternate these buffers, also called ping-ponging. | |
uint8_t mBufferReadIndex = 0; | |
uint8_t mBufferWriteIndex = 1; | |
// Define the number of particles. | |
const uint32_t kNumParticles = 16384; | |
}; | |
void VivekSampleApp::prepare( Settings * settings ) | |
{ | |
settings->setWindowSize( 1024, 768 ); | |
} | |
void VivekSampleApp::setup() | |
{ | |
// Load the shaders. | |
setupShaders(); | |
// Initialize our particles. | |
setupParticles(); | |
// Allow the application to run at the same | |
// frame rate as your monitor. | |
gl::enableVerticalSync( true ); | |
disableFrameRate(); | |
} | |
void VivekSampleApp::update() | |
{ | |
// Use a fixed time step for a steady 60 updates per second, | |
// regardless of our frame rate. Feel free to change this. | |
static const double timestep = 1.0 / 60.0; | |
// Keep track of time. | |
static double time = getElapsedSeconds(); | |
static double accumulator = 0.0; | |
// Calculate elapsed time since last frame. | |
double elapsed = getElapsedSeconds() - time; | |
time += elapsed; | |
// Update stuff. | |
accumulator += math<double>::min( elapsed, 0.1 ); // prevents 'spiral of death' | |
while( accumulator >= timestep ) { | |
// Update our background. | |
updateBackground(); | |
// Update our particles. | |
updateParticles(); | |
accumulator -= timestep; | |
} | |
} | |
void VivekSampleApp::draw() | |
{ | |
// Clear the main buffer (our window). | |
gl::clear(); | |
// Render the dynamic background. | |
renderBackground(); | |
// Render the particles. | |
renderParticles(); | |
} | |
void VivekSampleApp::resize() | |
{ | |
// Tell OpenGL we only want to use a single channel (GL_RED), | |
// because it's meant to be a grayscale background. | |
// The swizzleMask tells OpenGL that the green and blue channels | |
// use the information in the red channel. | |
auto tfmt = gl::Texture2d::Format().internalFormat( GL_RED ).swizzleMask( GL_RED, GL_RED, GL_RED, GL_ONE ); | |
auto fmt = gl::Fbo::Format().colorTexture( tfmt ); | |
// Create a frame buffer object for our dynamic background. | |
// It will have the same size as the window. | |
mBackgroundFbo = gl::Fbo::create( getWindowWidth(), getWindowHeight(), fmt ); | |
} | |
void VivekSampleApp::setupShaders() | |
{ | |
// Load the background shader, which creates the dynamic | |
// height map. Always use a try-catch block, so we know | |
// if something went wrong. | |
try { | |
auto vertexFile = loadAsset( "background.vert" ); | |
auto fragmentFile = loadAsset( "background.frag" ); | |
mBackgroundShader = gl::GlslProg::create( vertexFile, fragmentFile ); | |
} | |
catch( const std::exception &exc ) { | |
console() << "Failed to load background shader: " << exc.what() << std::endl; | |
} | |
// Load the particle transform shader, which takes care of | |
// animating the particles on the GPU. | |
try { | |
auto vertexFile = loadAsset( "transform.vert" ); | |
auto fmt = gl::GlslProg::Format() | |
.vertex( vertexFile ) | |
.attribLocation( "iPositionVelocity", 0 ) | |
.feedbackVaryings( { "oPositionVelocity" } ) | |
.feedbackFormat( GL_INTERLEAVED_ATTRIBS ); | |
mParticleTransformShader = gl::GlslProg::create( fmt ); | |
} | |
catch( const std::exception &exc ) { | |
console() << "Failed to load transform shader: " << exc.what() << std::endl; | |
} | |
// Load the particle shader, which draws the particles as | |
// point sprites. | |
try { | |
auto vertexFile = loadAsset( "particles.vert" ); | |
auto fragmentFile = loadAsset( "particles.frag" ); | |
mParticleShader = gl::GlslProg::create( vertexFile, fragmentFile ); | |
} | |
catch( const std::exception &exc ) { | |
console() << "Failed to load particle shader: " << exc.what() << std::endl; | |
} | |
} | |
void VivekSampleApp::updateBackground() | |
{ | |
// First, we tell OpenGL that we'd like to render to the frame buffer, | |
// instead of to our window. This is called 'binding the frame buffer'. | |
// We use a helper that will automatically unbind the buffer when we | |
// exit this function. | |
gl::ScopedFramebuffer fbo( mBackgroundFbo ); | |
// Next, we will have to make sure that our viewport has the same | |
// size as the frame buffer. | |
gl::ScopedViewport viewport( ivec2( 0 ), mBackgroundFbo->getSize() ); | |
// Next, we tell OpenGL we want to draw in 2D, so we disable the | |
// depth buffer and make sure our view and projection matrices | |
// are setup for 2D drawing. Again, we use helpers that restore | |
// the previous settings when we exit this function. | |
gl::ScopedDepth depth( false, false ); | |
gl::ScopedMatrices matrices; | |
gl::setMatricesWindow( mBackgroundFbo->getSize(), true ); | |
// Next, we activate the shader. For every pixel, it will | |
// evaluate its position and the current time to render a | |
// dynamic height map. So we need to tell it what the current | |
// time is, by passing it as a uniform variable. | |
gl::ScopedGlslProg shader( mBackgroundShader ); | |
mBackgroundShader->uniform( "uTime", float( getElapsedSeconds() ) ); | |
// We will also adjust for the window's aspect ratio, so our | |
// circles are indeed circles and not ellipses. | |
mBackgroundShader->uniform( "uAspectRatio", getWindowAspectRatio() ); | |
// Finally, we simply run the shader for every pixel in the | |
// frame buffer. We do this by drawing a rectangle the size | |
// of the full buffer. | |
gl::drawSolidRect( mBackgroundFbo->getBounds(), vec2( 0 ), vec2( 1 ) ); | |
// Thanks to the gl::Scoped* helpers, we don't have to reset | |
// anything manually, the OpenGL state will be restored to | |
// how it was before we called this function. | |
} | |
void VivekSampleApp::renderBackground() | |
{ | |
// Draw the contents of the frame buffer. | |
// First, activate a default shader that simply samples a texture. | |
gl::ScopedGlslProg shader( gl::getStockShader( gl::ShaderDef().texture() ) ); | |
// Bind the texture and render a full screen rectangle to run the shader | |
// for every pixel. | |
gl::ScopedTextureBind tex0( mBackgroundFbo->getColorTexture(), 0 ); | |
gl::drawSolidRect( getWindowBounds(), vec2( 0 ), vec2( 1 ) ); | |
} | |
void VivekSampleApp::setupParticles() | |
{ | |
// Our particle system will use transform feedback. Every frame, we will | |
// read the current particle data from the read buffer, transform it in a | |
// vertex shader and then write it to the write buffer. | |
// Each particle will have a 2D position and a 2D velocity. | |
// Since shaders prefer to use data in groups of 4 floats, we'll | |
// pack the information into a single vec4, where xy = position | |
// and zw = velocity. | |
// Create the initial data. | |
std::vector<vec4> initialData( kNumParticles ); | |
for( size_t i = 0; i < kNumParticles; ++i ) { | |
vec2 position = vec2( Rand::randFloat() * getWindowWidth(), Rand::randFloat() * getWindowHeight() ); | |
vec2 velocity = vec2( 0 ); | |
initialData[i] = vec4( position, velocity ); | |
} | |
// Create the two buffers. | |
for( size_t i = 0; i < mBuffers.size(); i++ ) { | |
// Create a vertex array object and bind it. We use a helper to | |
// automatically unbind it at the end of the for-loop. | |
mBuffers[i].mVao = gl::Vao::create(); | |
gl::ScopedVao vao( mBuffers[i].mVao ); | |
// Store our initial data in a vertex buffer object. We use GL_STATIC_DRAW, because we don't | |
// need to access or change the data from our CPU. | |
mBuffers[i].mVbo = gl::Vbo::create( GL_ARRAY_BUFFER, initialData.size() * sizeof( vec4 ), initialData.data(), GL_STATIC_DRAW ); | |
mBuffers[i].mVbo->bind(); | |
// We only use a single attribute. It has a size of 4 floats. | |
gl::vertexAttribPointer( 0, 4, GL_FLOAT, GL_FALSE, 0, (const GLvoid *)0 ); | |
gl::enableVertexAttribArray( 0 ); | |
// Initialize the transform feedback buffer. | |
mBuffers[i].mTransformFeedback = gl::TransformFeedbackObj::create(); | |
mBuffers[i].mTransformFeedback->bind(); | |
gl::bindBufferBase( GL_TRANSFORM_FEEDBACK_BUFFER, 0, mBuffers[i].mVbo ); | |
mBuffers[i].mTransformFeedback->unbind(); | |
} | |
} | |
void VivekSampleApp::updateParticles() | |
{ | |
// Activate the shader that animates the particles. | |
gl::ScopedGlslProg shader( mParticleTransformShader ); | |
// Tell the shader the size of the window. The background texture can be | |
// found in texture unit 0. | |
mParticleTransformShader->uniform( "uWindowSize", vec2( getWindowSize() ) ); | |
mParticleTransformShader->uniform( "uTexBackground", 0 ); | |
// Bind the background texture. | |
gl::ScopedTextureBind tex0( mBackgroundFbo->getColorTexture(), 0 ); | |
// We're going to write to the write buffer. | |
gl::ScopedVao vao( mBuffers[mBufferWriteIndex].mVao.get() ); | |
// We don't need the rasterizer, because we only use a vertex shader. | |
gl::ScopedState state( GL_RASTERIZER_DISCARD, true ); | |
// Let Cinder set all default uniforms and attributes for us. | |
gl::setDefaultShaderVars(); | |
// Use the contents of the read buffer as input. | |
mBuffers[mBufferReadIndex].mTransformFeedback->bind(); | |
// Run the shader for all particles. | |
gl::beginTransformFeedback( GL_POINTS ); | |
gl::drawArrays( GL_POINTS, 0, (GLsizei)kNumParticles ); | |
gl::endTransformFeedback(); | |
// The write buffer now becomes the read buffer and vice versa. | |
swap( mBufferReadIndex, mBufferWriteIndex ); | |
} | |
void VivekSampleApp::renderParticles() | |
{ | |
// Use additive blending, because it looks so cool. | |
gl::ScopedBlendAdditive blend; | |
// Read from the right vertex array object. | |
gl::ScopedVao vao( mBuffers[mBufferReadIndex].mVao.get() ); | |
// Allow the shader to set the size of the point sprites. | |
gl::ScopedState state( GL_PROGRAM_POINT_SIZE, true ); | |
// Bind the shader. | |
gl::ScopedGlslProg shader( mParticleShader ); | |
// Let Cinder set all default uniforms and attributes for us. | |
gl::setDefaultShaderVars(); | |
// Draw the particles. | |
gl::drawArrays( GL_POINTS, 0, (GLsizei)kNumParticles ); | |
} | |
CINDER_APP( VivekSampleApp, RendererGl( RendererGl::Options().msaa( 8 ) ), &VivekSampleApp::prepare ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment