はじめに
前回の記事でVertexShaderとFragmentShaderについて書きましたが、
実はShaderは、VertexShader→GeometryShader(省略可)→FragmentShaderの順番で処理されています。
今回は、前回に省略したGeometryShaderを使ってみたいと思います。
GeometryShaderを使用することによって、次のようなことができます。
・VertexShaderでは1頂点ごとの処理に対し、GeometryShaderでは面ごとの処理が可能。
・頂点を増減させることができ、プリミティブを変更することもできる。
・隣接する頂点を参照することができる。
→このことにより、下記のようなVertexShaderとFragmentShaderだけでは難しい表現などが実現可能です。
・オブジェクトをポリゴンごとに分解してバラバラになる表現
・点座標を基にポリゴンを作り、テクスチャを貼ることで大量のパーティクルの表現
今回、以下の画像の3次元グラフのようなものを作成してGeometryShaderを試してみました。
環境
・macOS Sierra 10.12.6
・openFrameworks v0.9.8
・GLSLバージョン 150
Shaderソースの実装
・shader.vert
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#version 150 precision mediump float; in vec3 normal; in vec4 color; in vec3 position; out vec3 vNormal; out vec4 vColor; out vec3 vPosition; void main() { vNormal = normal; vColor = color; vPosition = position; } |
VertexShaderは位置、法線、色の情報をGeometryShaderにそのまま渡します。
・shader.geom
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 |
#version 150 precision mediump float; layout (triangles) in; layout (triangle_strip, max_vertices = 6) out; uniform mat4 modelViewProjectionMatrix; uniform int height; in vec3 vNormal[]; in vec4 vColor[]; in vec3 vPosition[]; out vec4 gsColor; void main(){ //平面 for(int i=0; i<gl_in.length(); i++){ gl_Position = modelViewProjectionMatrix * vec4(vPosition[i], 1.0); gsColor = vColor[i]; EmitVertex(); } EndPrimitive(); //凹凸面 vec3 offset; for(int i=0; i<gl_in.length(); i++){ offset = vNormal[i] * height * vColor[i].r; gl_Position = modelViewProjectionMatrix * vec4(vPosition[i] + (offset.xyz), 1.0); gsColor = vColor[i]; EmitVertex(); } EndPrimitive(); } |
GeometryShaderでは、頂点情報を設定したらEmitVertex()を呼びます。
今回は、三角形ポリゴンなので3頂点の設定が全て終わったらEndPrimitive()を呼び三角形ポリゴンの設定を完了します。上記では3頂点の入力に2つのプリミティブ(平面と凹凸面)の計6頂点を出力しています。
また、凹凸面に関しては色情報が赤い部分ほど高くなるようにしています。
・shader.frag
1 2 3 4 5 6 7 8 9 10 |
#version 150 precision mediump float; in vec4 gsColor; out vec4 outputColor; void main(){ outputColor = gsColor; } |
FragmentShaderは、頂点の色をそのまま出力します。
openFrameworksの実装
・ofApp.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#pragma once #include "ofMain.h" class ofApp : public ofBaseApp{ public: void setup(); void update(); void draw(); //中略 ofEasyCam cam; ofShader shader; ofVboMesh vboMesh; ofPlanePrimitive plane; ofColor vColor; int red; int height = 0; }; |
・ofApp.cpp
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 |
#include "ofApp.h" //-------------------------------------------------------------- void ofApp::setup(){ ofEnableAlphaBlending(); ofEnableDepthTest(); //shader shader.setGeometryInputType(GL_TRIANGLES); shader.setGeometryOutputType(GL_TRIANGLE_STRIP); shader.setGeometryOutputCount(6); shader.load("shaders/shader.vert", "shaders/shader.frag", "shaders/shader.geom"); //vboMesh vboMesh.setMode(OF_PRIMITIVE_TRIANGLES); plane.setPosition(ofVec3f(0, 0, 0)); plane.set(500, 500, 15, 15); vboMesh = plane.getMesh(); for(int i=0; i<vboMesh.getVertices().size(); i++){ red = ofRandom(0, vboMesh.getVertices().size()); vColor = ofColor(red * (255.0 / vboMesh.getVertices().size()), 255.0 - red * (255.0 / vboMesh.getVertices().size()), 0.0, 255.0); vboMesh.addColor(vColor); } } //-------------------------------------------------------------- void ofApp::update(){ } //-------------------------------------------------------------- void ofApp::draw(){ ofBackground(0); cam.begin(); shader.begin(); shader.setUniform1i("height", height); vboMesh.draw(); shader.end(); cam.end(); } //-------------------------------------------------------------- void ofApp::keyPressed(int key){ if(key == OF_KEY_UP) { height += 5; } else if(key == OF_KEY_DOWN) { height -= 5; } } //以下省略 |
平面のMeshの情報をShaderに渡してCam上で表示しています。
今回は、keyイベントで高さを変えられるようにしています。