掲題の件、意外にもダイレクトな方法の紹介がネットの大海に無かったので備忘としてポストします。
(クリエイティブコーディングのよろず屋 yoppa.org にも方法が載ってなかった…。)
ofShaderインスタンス内メソッドのbegin()とend()の間に物体を描くと、その物体にシェーダマテリアルを貼り付けることができます。
1 2 3 4 5 6 7 8 9 |
// シェーダマテリアル // setup() 内でシェーダをロード ofShader shader; shader.load("shader/shader.vert", "shader/shader.frag"); // draw() 内でオブジェクトを描く shader.begin(); ofDrawBox(0, 0, 0, 100, 100, 100); shader.end(); |
ofMatrialと使い方は一緒で便利でる。
しかし今回やりたいのは、シェーダマテリアルではなく、ポストエフェクトです。
一度レンダリングした画面の全体にエフェクトをかけるというものです。
手順としてはこんな感じです。
1) FBO(Frame Buffer Object)に、一度絵を描き込む
2) 1)で描かれたFBOをシェーダにTextureとして渡し、出力する色を計算
3) 2)の計算結果を画面にぴったりあった四角形にシェーダマテリアルとして貼り付け
以下、順番に概観します。
手順
1) FBO(Frame Buffer Object)に、一度絵を描き込む
Frame Buffer Object(以下FBO)とは1つの描画結果としてのframeを、一時的に保管して持っておくためのオブジェクトです。
px単位で、色情報(color buffer)、深度情報(depth buffer)など、描画に必要な情報を持っています。
FBOについての解説は、こちら が詳しいです。
openFrameworksでの使い方は、ありがたいことにとても単純化されており超簡単です。
1 2 3 4 5 6 7 8 |
// setup() 内でFBOを準備 ofFbo fbo; fbo.allocate(ofGetWidth(), ofGetHeight()); // draw() または update() 内でfboに描画結果をためこむ fbo.begin(); ofDrawBox(0, 0, 0, 100, 100, 100); fbo.end(); |
この書き方どこかで見たことがありますね。
begin()とend()ではさむというoFにおける単純化のパターンですね。
このように書き方を単純化しつつ統一性をもたせてくれるのはラッパーライブラリとしてのoFの素晴らしいところです。
2) 1)で描かれたFBOをシェーダにTextureとして渡し、出力する色を計算
1)で描いたFBOはいわば一枚の絵です。これをTextureとしてシェーダプログラムに渡します。
シェーダへの渡し方はおまじない的に覚えてしまった方が良いかもしれません。
1 |
shader.setUniformTexture("texture", fbo, 0); |
ofShaderインスタンスのsetUniformTexture()メソッドで渡します。
第一引数は、シェーダ側での変数名と同じにし、第二引数にTextureとなるオブジェクトを渡します。
今回はofFboのインスタンスですが、ofBaseHasTexture
を継承しているものであれば指定可能です。(ofImageもgetTexture()を使えば、同じように ofBaseHasTexture を継承した ofTexture にアクセスでき、shader に渡せます。)
フラグメントシェーダ側では以下のように渡したFboのTextureから色情報を取り出します。
1 2 3 4 5 6 7 8 9 10 11 12 |
#version 120 uniform sampler2DRect texture; // 型に注意。sampler2Dではダメ。 varying vec2 vTexCoord; void main() { // texture2DRect()関数で色情報にアクセス // vTexCoordは、描画する四角形のテクスチャのuv座標がわたれば良い。 // 頂点シェーダ側で、vTexCoord = gl_Vertex.xy; とするのが無難。 // (フラグメントシェーダ側ではvarying変数として線形補間された値が入る) vec4 samplerColor = texture2DRect(texture, vTexCoord); ... } |
取り出したらあとは適当にいじくります。いじくったあとは、gl_FragColor(vec4型。rgbaを意味する4つのfloat値の集合)に代入すれば、描画結果として得ることができます。
関数名については、GLSLのバージョンによって変わったりしますので注意が必要です。
GLSLのバージョンについては こちらの記事 がとても参考になります。
3) 2)の計算結果を画面にぴったりあった四角形にシェーダマテリアルとして貼り付け
2)で計算した色は、シェーダマテリアルとして画面をぴったりと覆う四角形に貼り付けます。
図形へのシェーダマテリアルのはりつけは冒頭に紹介したとおりですね。
ofShaderのインスタンスのbegin()とend()で挟めばOKです。
サンプル
最後に冒頭の動画の完成版のコードを掲載します。
このサンプルのポストエフェクトはグラデーションを時間差で強弱をつけながら上乗せするという単純なものです。
oF C++ヘッダーファイル
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#include "ofMain.h" class ofApp : public ofBaseApp{ public: void setup(); void update(); void draw(); void keyPressed(int key); void keyReleased(int key); void mouseMoved(int x, int y ); void mouseDragged(int x, int y, int button); void mousePressed(int x, int y, int button); void mouseReleased(int x, int y, int button); void mouseEntered(int x, int y); void mouseExited(int x, int y); void windowResized(int w, int h); void dragEvent(ofDragInfo dragInfo); void gotMessage(ofMessage msg); ofEasyCam cam; ofLight light; ofShader shader; ofFbo fbo; }; |
oF C++ファイル
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
#include "ofApp.h" //-------------------------------------------------------------- void ofApp::setup(){ ofEnableDepthTest(); ofBackground(0); // setup light light.enable(); light.setSpotlight(); light.setPosition(-150, 150, 150); light.lookAt(ofVec3f(0.0, 0.0, 0.0)); light.setAmbientColor(ofFloatColor(0.0, 0.0, 0.0)); light.setDiffuseColor(ofFloatColor(1.0, 1.0, 1.0)); shader.load("shader/shader.vert", "shader/shader.frag"); fbo.allocate(ofGetWidth(), ofGetHeight()); } //-------------------------------------------------------------- void ofApp::update(){ } //-------------------------------------------------------------- void ofApp::draw(){ // FBO fbo.begin(); ofClear(0, 0, 0, 0); ofPushMatrix(); ofTranslate(ofGetWidth()/2, ofGetHeight()/2); cam.begin(); light.draw(); ofDrawBox(50, 0, 0, 100, 100, 100); ofDrawSphere(-50, 0, 0, 50); ofDrawBox(0, -100, 0, 800, 10, 800); cam.end(); ofPopMatrix(); fbo.end(); // PostEffect shader.begin(); shader.setUniformTexture("texture", fbo, 0); shader.setUniform2f("resolution", ofGetWidth(), ofGetHeight()); shader.setUniform1f("time", ofGetElapsedTimef()); ofDrawRectangle(0, 0, ofGetWidth(), ofGetHeight()); shader.end(); } |
頂点シェーダ
1 2 3 4 5 6 |
varying vec2 vTexCoord; void main() { vTexCoord = gl_Vertex.xy; gl_Position = ftransform(); } |
フラグメントシェーダ
1 2 3 4 5 6 7 8 9 10 11 12 13 |
uniform vec2 resolution; uniform sampler2DRect texture; uniform float time; varying vec2 vTexCoord; void main() { vec4 samplerColor = texture2DRect(texture, vTexCoord); vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / resolution; float t = (sin(time) + 1.0) * 0.5; vec2 tvec = p * 0.5 + vec2(t) * 0.5; gl_FragColor = vec4(samplerColor.r + tvec.x, samplerColor.g, samplerColor.b + tvec.y, 1.0); } |
参考
- ofxPostGlitch: Maxillaさんの素晴らしいAddon。もはやoFのポストエフェクトはこれで十分すぎるくらいです。こちらのソースを拝見し、参考にさせていただきました。
- Introducing Shader: openFrameworksの公式チュートリアル。具体的なエフェクトを説明しておりかなり充実した内容です。。