Skip to content

Instantly share code, notes, and snippets.

@sortofsleepy
Last active January 13, 2017 18:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sortofsleepy/b89c78d48db7797a4541ca2adbdede48 to your computer and use it in GitHub Desktop.
Save sortofsleepy/b89c78d48db7797a4541ca2adbdede48 to your computer and use it in GitHub Desktop.
#include "cinder/app/App.h"
#include "cinder/app/RendererGl.h"
#include "cinder/Rand.h"
#include "cinder/gl/gl.h"
using namespace ci;
using namespace ci::app;
using namespace std;
/**
Particle type holds information for rendering and simulation.
Used to buffer initial simulation values.
*/
// one thing I forgot about is that using Interleaved attributes can be finicky.
// Make sure properties are declared and enabled in the order they're declared.
struct Particle
{
vec3 pos;
vec3 ppos;
vec3 home;
ColorA color;
float damping;
vec4 colorOne;
vec4 colorTwo;
};
// How many particles to create. (600k default)
#if defined( CINDER_GL_ES ) // ES devices can't handle as many particles as the desktop
const int NUM_PARTICLES = 600e2;
#else
const int NUM_PARTICLES = 600e3;
#endif
/**
Simple particle simulation with Verlet integration and mouse interaction.
A sphere of particles is deformed by mouse interaction.
Simulation is run using transform feedback on the GPU.
particleUpdate.vs defines the simulation update step.
Designed to have the same behavior as ParticleSphereCPU.
*/
class ParticleSphereGPUApp : public App {
public:
void setup() override;
void update() override;
void keyDown(KeyEvent event) override;
void draw() override;
private:
gl::GlslProgRef mRenderProg;
gl::GlslProgRef mUpdateProg;
// Descriptions of particle data layout.
gl::VaoRef mAttributes[2];
// Buffers holding raw particle data on GPU.
gl::VboRef mParticleBuffer[2];
// Current source and destination buffers for transform feedback.
// Source and destination are swapped each frame after update.
std::uint32_t mSourceIndex = 0;
std::uint32_t mDestinationIndex = 1;
// Mouse state suitable for passing as uniforms to update program
bool mMouseDown = false;
float mMouseForce = 0.0f;
vec3 mMousePos = vec3( 0, 0, 0 );
// flag for swaping colors
bool uActive = false;
};
// keydown for simplicity, not sure how you're handling the flag yourself.
// uActive will switch from true->false and vice versa on each keypress.
void ParticleSphereGPUApp::keyDown(cinder::app::KeyEvent event){
uActive = !uActive;
app::console()<<"uActive is now "<<uActive << "\n";
}
void ParticleSphereGPUApp::setup()
{
// Create initial particle layout.
vector<Particle> particles;
particles.assign( NUM_PARTICLES, Particle() );
const float azimuth = 256.0f * M_PI / particles.size();
const float inclination = M_PI / particles.size();
const float radius = 180.0f;
vec3 center = vec3( getWindowCenter() + vec2( 0.0f, 40.0f ), 0.0f );
for( int i = 0; i < particles.size(); ++i )
{ // assign starting values to particles.
float x = radius * sin( inclination * i ) * cos( azimuth * i );
float y = radius * cos( inclination * i );
float z = radius * sin( inclination * i ) * sin( azimuth * i );
auto &p = particles.at( i );
p.pos = center + vec3( x, y, z );
p.home = p.pos;
p.ppos = p.home + Rand::randVec3() * 10.0f; // random initial velocity
p.damping = Rand::randFloat( 0.965f, 0.985f );
p.color = Color( CM_HSV, lmap<float>( i, 0.0f, particles.size(), 0.0f, 0.66f ), 1.0f, 1.0f );
// add two new colors here - yellow for true and magenta for false.
p.colorOne = vec4(1.0,1.0,0.0,1.0);
p.colorTwo = vec4(1.0,0.0,1.0,1.0);
}
// Create particle buffers on GPU and copy data into the first buffer.
// Mark as static since we only write from the CPU once.
mParticleBuffer[mSourceIndex] = gl::Vbo::create( GL_ARRAY_BUFFER, particles.size() * sizeof(Particle), particles.data(), GL_STATIC_DRAW );
mParticleBuffer[mDestinationIndex] = gl::Vbo::create( GL_ARRAY_BUFFER, particles.size() * sizeof(Particle), nullptr, GL_STATIC_DRAW );
for( int i = 0; i < 2; ++i )
{ // Describe the particle layout for OpenGL.
mAttributes[i] = gl::Vao::create();
gl::ScopedVao vao( mAttributes[i] );
// Define attributes as offsets into the bound particle buffer
// remember ordering is important with interleaved attributes
gl::ScopedBuffer buffer( mParticleBuffer[i] );
gl::enableVertexAttribArray( 0 );
gl::enableVertexAttribArray( 1 );
gl::enableVertexAttribArray( 2 );
gl::enableVertexAttribArray( 3 );
gl::enableVertexAttribArray( 4 );
gl::enableVertexAttribArray( 5 );
gl::enableVertexAttribArray(6);
gl::vertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, sizeof(Particle), (const GLvoid*)offsetof(Particle, pos) );
gl::vertexAttribPointer( 1, 4, GL_FLOAT, GL_FALSE, sizeof(Particle), (const GLvoid*)offsetof(Particle, color) );
gl::vertexAttribPointer( 2, 3, GL_FLOAT, GL_FALSE, sizeof(Particle), (const GLvoid*)offsetof(Particle, ppos) );
gl::vertexAttribPointer( 3, 3, GL_FLOAT, GL_FALSE, sizeof(Particle), (const GLvoid*)offsetof(Particle, home) );
gl::vertexAttribPointer( 4, 1, GL_FLOAT, GL_FALSE, sizeof(Particle), (const GLvoid*)offsetof(Particle, damping) );
gl::vertexAttribPointer( 5, 4, GL_FLOAT, GL_FALSE, sizeof(Particle), (const GLvoid*)offsetof(Particle, colorOne) );
gl::vertexAttribPointer( 6, 4, GL_FLOAT, GL_FALSE, sizeof(Particle), (const GLvoid*)offsetof(Particle, colorTwo) );
}
// Load our update program.
// Match up our attribute locations with the description we gave.
mRenderProg = gl::getStockShader( gl::ShaderDef().color() );
mUpdateProg = gl::GlslProg::create( gl::GlslProg::Format().vertex( loadAsset( "particleUpdate.vs" ) )
.feedbackFormat( GL_INTERLEAVED_ATTRIBS )
.feedbackVaryings( { "position", "pposition", "home", "color", "damping","colorOne","colorTwo" } )
.attribLocation( "iPosition", 0 )
.attribLocation( "iColor", 1 )
.attribLocation( "iPPosition", 2 )
.attribLocation( "iHome", 3 )
.attribLocation( "iDamping", 4 )
.attribLocation("iColorOne", 5)
.attribLocation("iColorTwo", 6)
);
// Listen to mouse events so we can send data as uniforms.
getWindow()->getSignalMouseDown().connect( [this]( MouseEvent event )
{
mMouseDown = true;
mMouseForce = 500.0f;
mMousePos = vec3( event.getX(), event.getY(), 0.0f );
} );
getWindow()->getSignalMouseDrag().connect( [this]( MouseEvent event )
{
mMousePos = vec3( event.getX(), event.getY(), 0.0f );
} );
getWindow()->getSignalMouseUp().connect( [this]( MouseEvent event )
{
mMouseForce = 0.0f;
mMouseDown = false;
} );
}
void ParticleSphereGPUApp::update()
{
// Update particles on the GPU
gl::ScopedGlslProg prog( mUpdateProg );
gl::ScopedState rasterizer( GL_RASTERIZER_DISCARD, true ); // turn off fragment stage
mUpdateProg->uniform( "uMouseForce", mMouseForce );
mUpdateProg->uniform( "uMousePos", mMousePos );
mUpdateProg->uniform("uActive", uActive);
// Bind the source data (Attributes refer to specific buffers).
gl::ScopedVao source( mAttributes[mSourceIndex] );
// Bind destination as buffer base.
gl::bindBufferBase( GL_TRANSFORM_FEEDBACK_BUFFER, 0, mParticleBuffer[mDestinationIndex] );
gl::beginTransformFeedback( GL_POINTS );
// Draw source into destination, performing our vertex transformations.
gl::drawArrays( GL_POINTS, 0, NUM_PARTICLES );
gl::endTransformFeedback();
// Swap source and destination for next loop
std::swap( mSourceIndex, mDestinationIndex );
// Update mouse force.
if( mMouseDown ) {
mMouseForce = 150.0f;
}
}
void ParticleSphereGPUApp::draw()
{
gl::clear( Color( 0, 0, 0 ) );
gl::setMatricesWindowPersp( getWindowSize(), 60.0f, 1.0f, 10000.0f );
gl::enableDepthRead();
gl::enableDepthWrite();
gl::ScopedGlslProg render( mRenderProg );
gl::ScopedVao vao( mAttributes[mSourceIndex] );
gl::context()->setDefaultShaderVars();
gl::drawArrays( GL_POINTS, 0, NUM_PARTICLES );
}
CINDER_APP( ParticleSphereGPUApp, RendererGl, [] ( App::Settings *settings ) {
settings->setWindowSize( 1280, 720 );
settings->setMultiTouchEnabled( false );
} )
#version 150 core
uniform float uMouseForce;
uniform vec3 uMousePos;
uniform bool uActive;
in vec3 iPosition;
in vec3 iPPostion;
in vec3 iHome;
in float iDamping;
in vec4 iColor;
in vec4 iColorOne;
in vec4 iColorTwo;
out vec3 position;
out vec3 pposition;
out vec3 home;
out float damping;
out vec4 color;
out vec4 colorOne;
out vec4 colorTwo;
const float dt2 = 1.0 / (60.0 * 60.0);
const float force = 100.5;
void main()
{
position = iPosition;
pposition = iPPostion;
damping = iDamping;
home = iHome;
color = iColor;
colorOne = iColorOne;
colorTwo = iColorTwo;
// I was lazy so I just set base to be 0.5
vec3 base = vec3(0.5);
// also lazy so I re-aliased iColorOne and iColorTwo to your
// colorActive and colorInactive variables.
vec4 colorActive = iColorOne;
vec4 colorInactive = iColorTwo;
// should be exactly the same as your code from here
if(uActive)
{
vec3 dir = base - position;
float d2 = length( dir );
d2 *= d2;
float currForce = d2 < 20 ? 0 : (d2 < 40 ? force/8.0 : (d2 < 75 ? force/4.0 : (d2 < 120 ? force/2.0 : force )));
position += currForce * dir / d2;
vec3 vel = (position - pposition) * damping;
pposition = position;
vec3 acc = (base - position) * 32.0f;
position += vel + acc * dt2;
}
else
{
vec3 vel = (position - pposition) * (damping + 0.05f);
pposition = position;
vec3 acc = (home - position) * 32.0f;
position += vel + acc * dt2;
}
color = vec4(((length(position - home)*(colorInactive.r - colorActive.r)/length(base - home)) + colorActive.r), ((length(position - home)*(colorInactive.g - colorActive.g)/length(base - home)) + colorActive.g), ((length(position - home)*(colorInactive.b - colorActive.b)/length(base - home)) + colorActive.b), 1);
}
#version 150 core
uniform float uMouseForce;
uniform vec3 uMousePos;
uniform bool uActive;
in vec3 iPosition;
in vec3 iPPostion;
in vec3 iHome;
in float iDamping;
in vec4 iColor;
in vec4 iColorOne;
in vec4 iColorTwo;
out vec3 position;
out vec3 pposition;
out vec3 home;
out float damping;
out vec4 color;
out vec4 colorOne;
out vec4 colorTwo;
const float dt2 = 1.0 / (60.0 * 60.0);
void main()
{
position = iPosition;
pposition = iPPostion;
damping = iDamping;
home = iHome;
color = iColor;
colorOne = iColorOne;
colorTwo = iColorTwo;
if(uActive){
color = colorOne;
}else{
color = colorTwo;
}
/// everything from here is the same as the example
// mouse interaction
if( uMouseForce > 0.0 ) {
vec3 dir = position - uMousePos;
float d2 = length( dir );
d2 *= d2;
position += uMouseForce * dir / d2;
}
vec3 vel = (position - pposition) * damping;
pposition = position;
vec3 acc = (home - position) * 32.0f;
position += vel + acc * dt2;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment