シェーダを使ってワイヤーフレーム表示します。
●Vertex Shaderは各頂点ごとに呼び出される。
●Fragment Shaderはピクセルごとに呼び出される
つまり処理単位が違う。では、
#version 460 core layout (location = 0) in vec3 aPos; layout (location = 2) in vec3 color;//頂点ごとの入力 out vec3 vColor; uniform mat4 gl_ModelViewMatrix; uniform mat4 gl_ProjectionMatrix; void main() { gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * vec4(aPos, 1.0); vColor = color;//フラグメントシェーダに出力 }
というVertex Shaderがあったとき、頂点毎にoutされたvColorはFragment Shader側でどうなっているのか?
答えは、各頂点でoutされたvColor、つまり三つのvColorの値が、重心座標系で補間されて入力される。
描画したい三角形の各頂点にそれぞれ(1,0,0),(0,1,0),(0,0,1)のデータを付加し、それを頂点シェーダで読み取り、そのままフラグメントシェーダに渡すと、勝手に重心座標系で補間された座標値になる。
つまりフラグメントシェーダ側で、重心座標系で「( 0.01 , 0.0 , 0.0 )未満 or ( 0.0 , 0.01 , 0.0 ) 未満 or ( 0.0 , 0.0 , 0.01 ) 未満」 の領域を塗れば、ワイヤーフレーム表示と同じ状態になる。
//バッファとデータ typedef GLfloat points_t[3]; GLuint vertexbuffer;//バッファのIDを格納する変数 GLuint colorbuffer;//バッファのIDを格納する変数 GLuint barycentricbuffer; points_t position[9]; points_t color[9]; points_t barycentric[9]; void set(GLfloat* p, GLfloat a, GLfloat b, GLfloat c) { p[0] = a; p[1] = b; p[2] = c; } ////////////////////////////////////////////////// void prepare_buffers() { // position ... 頂点座標 // barycentric ... 重心座標系用 // color ... 色 //三角形1 set(position[0], 0.0, 0.5, 0);set(barycentric[0], 1, 0, 0);set(color[0], 1, 0, 0); set(position[1], 0.5, -0.5, 0);set(barycentric[1], 0, 1, 0);set(color[1], 0, 1, 0); set(position[2], -0.5, -0.5, 0);set(barycentric[2], 0, 0, 1);set(color[2], 0, 0, 1); //三角形2 set(position[3], 0.0, 0.5, 0);set(barycentric[3], 1, 0, 0);set(color[3], 1, 0, 0); set(position[4], -1.0, 0.5, 0);set(barycentric[4], 0, 1, 0);set(color[4], 1, 1, 0); set(position[5], -0.5, -0.5, 0);set(barycentric[5], 0, 0, 1);set(color[5], 0, 0, 1); //三角形3 set(position[6], -1.0, 0.5, 0); set(barycentric[6], 1, 0, 0); set(color[6], 1, 1, 0); set(position[7], -1.5, -0.5, 0); set(barycentric[7], 0, 1, 0); set(color[7], 0, 1, 1); set(position[8], -0.5, -0.5, 0); set(barycentric[8], 0, 0, 1); set(color[8], 0, 0, 1); glGenBuffers(1, &vertexbuffer); glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer); glBufferData( GL_ARRAY_BUFFER, 3 * 9 * sizeof(GLfloat), position, GL_STATIC_DRAW); glGenBuffers(1, &colorbuffer); glBindBuffer(GL_ARRAY_BUFFER, colorbuffer); glBufferData( GL_ARRAY_BUFFER, 3 * 9 * sizeof(GLfloat), color, GL_STATIC_DRAW); glGenBuffers(1, &barycentricbuffer); glBindBuffer(GL_ARRAY_BUFFER, barycentricbuffer); glBufferData( GL_ARRAY_BUFFER, 3 * 9 * sizeof(GLfloat), barycentric, GL_STATIC_DRAW); }
void display(void) { glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); glLoadIdentity(); glTranslated(0.5, 0.0, 0.0);//平行移動 // シェーダを使う glUseProgram(ProgramID); // 頂点バッファ:頂点 glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer); glVertexAttribPointer( 0, // シェーダ内のlayoutとあわせる 3, // 1要素の要素数(x,y,z)で3要素 GL_FLOAT, // タイプ GL_FALSE, // 正規化しない(データが整数型の時) 0, // ストライド (void*)0 // 配列バッファオフセット ); // カラーバッファを有効にする glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, colorbuffer); glVertexAttribPointer( 1, // シェーダ内のlayoutとあわせる 3, // 1要素の要素数(r,g,b)で3要素 GL_FLOAT, // タイプ GL_FALSE, // 正規化しない(データが整数型の時) 0, // ストライド (void*)0 // 配列バッファオフセット ); //重心座標計算用バッファを有効にする glEnableVertexAttribArray(2); glBindBuffer(GL_ARRAY_BUFFER, barycentricbuffer); glVertexAttribPointer( 2, // シェーダ内のlayoutとあわせる 3, // 1要素の要素数 GL_FLOAT, // タイプ GL_FALSE, // 正規化しない(データが整数型の時) 0, // ストライド (void*)0 // 配列バッファオフセット ); // 三角形を描きます! glDrawArrays(GL_TRIANGLES, 0, 9); glDisableVertexAttribArray(0);//バッファを無効にする glDisableVertexAttribArray(1); glDisableVertexAttribArray(2); glFlush(); }
#version 460 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 incolor; layout (location = 2) in vec3 barycentric; out vec4 vertexColor; out vec3 baryxyz; uniform mat4 gl_ModelViewMatrix; uniform mat4 gl_ProjectionMatrix; void main() { gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * vec4(aPos, 1.0); vertexColor = vec4(incolor, 1.0); baryxyz = barycentric; }
#version 460 core out vec4 FragColor; in vec4 vertexColor; in vec3 baryxyz; void main() { if( baryxyz.x < 0.01 || baryxyz.y < 0.01 || baryxyz.z < 0.01 ){ FragColor= vec4(1,1,1,1); } else{ FragColor = vertexColor; } }
ところが重心座標系の座標値は三角形の形状に依存する。つまり、鋭角三角形などではエッジがちゃんと表示できない。
・https://tchayen.github.io/wireframes-with-barycentric-coordinates/
・http://codeflow.org/entries/2012/aug/02/easy-wireframe-display-with-barycentric-coordinates/
上記サイトではfwidthを使って解決している。(解説したいが自分自身がわかっていない)
#version 460 core out vec4 FragColor; in vec4 vertexColor; in vec3 baryxyz; const float lineWidth = 1.0; //エッジの太さ const vec3 lineColor = vec3(1.0, 1.0, 1.0); //エッジの色 float edgeFactor() { vec3 d = fwidth( baryxyz ); vec3 f = step( d * lineWidth, baryxyz ); return min( min( f.x, f.y ), f.z ); } void main() { FragColor.rgb = mix( lineColor, vertexColor.xyz, edgeFactor() ); }
・https://qiita.com/edo_m18/items/71f6064f3355be7e4f45
・https://qiita.com/gam0022/items/1342a91d0a6b16a3a9ba
上記フラグメントシェーダのedgeFactorは、そのピクセルがエッジ該当領域なら0.0を、そうでなければ1.0を返す。
0.0が帰ってきたらエッジ色で塗り、それ以外のケースではdiscardして着色を破棄するとワイヤーフレーム表示になる。
#version 460 core out vec4 FragColor; in vec4 vertexColor; in vec3 baryxyz; const float lineWidth = 3.0; //エッジの太さ const vec3 lineColor = vec3(1.0, 1.0, 1.0); //エッジの色 float edgeFactor() { vec3 d = fwidth(baryxyz); vec3 f = step(d * lineWidth, baryxyz); return min(min(f.x, f.y), f.z); } void main() { if( edgeFactor() < 0.1 ){ //0.0ならエッジ。シェーダは詳しくないが浮動小数点を==比較したくない FragColor.rgb = lineColor; } else{ discard; } }
#pragma warning(disable:4996) #pragma comment(lib,"glew32.lib") #include <gl/glew.h> #include <GL/glut.h> #include <fstream> #include <sstream> #include <vector> #include <algorithm> void init(void); void display(void); int main(int argc, char* argv[]) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGBA); glutCreateWindow(argv[0]); glutDisplayFunc(display); glewInit(); // glewの初期化 init(); glutMainLoop(); return 0; } void prepare_buffers(); void prepare_vertex_shader(); void prepare_fragment_shader(); void link_program(); void init(void) { //頂点データと色情報の作成 prepare_buffers(); //頂点シェーダの準備 prepare_vertex_shader(); //フラグメントシェーダの準備 prepare_fragment_shader(); //プログラムのリンク link_program(); } //バッファとデータ typedef GLfloat points_t[3]; GLuint vertexbuffer;//バッファのIDを格納する変数 GLuint colorbuffer;//バッファのIDを格納する変数 GLuint barycentricbuffer; points_t position[9]; points_t color[9]; points_t barycentric[9]; void set(GLfloat* p, GLfloat a, GLfloat b, GLfloat c) { p[0] = a; p[1] = b; p[2] = c; } ////////////////////////////////////////////////// void prepare_buffers() { // position ... 頂点座標 // barycentric ... 重心座標系用 // color ... 色 //三角形1 set(position[0], 0.0, 0.5, 0);set(barycentric[0], 1, 0, 0);set(color[0], 1, 0, 0); set(position[1], 0.5, -0.5, 0);set(barycentric[1], 0, 1, 0);set(color[1], 0, 1, 0); set(position[2], -0.5, -0.5, 0);set(barycentric[2], 0, 0, 1);set(color[2], 0, 0, 1); //三角形2 set(position[3], 0.0, 0.5, 0);set(barycentric[3], 1, 0, 0);set(color[3], 1, 0, 0); set(position[4], -1.0, 0.5, 0);set(barycentric[4], 0, 1, 0);set(color[4], 1, 1, 0); set(position[5], -0.5, -0.5, 0);set(barycentric[5], 0, 0, 1);set(color[5], 0, 0, 1); //三角形3 set(position[6], -1.0, 0.5, 0); set(barycentric[6], 1, 0, 0); set(color[6], 1, 1, 0); set(position[7], -1.5, -0.5, 0); set(barycentric[7], 0, 1, 0); set(color[7], 0, 1, 1); set(position[8], -0.5, -0.5, 0); set(barycentric[8], 0, 0, 1); set(color[8], 0, 0, 1); glGenBuffers(1, &vertexbuffer); glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer); glBufferData( GL_ARRAY_BUFFER, 3 * 9 * sizeof(GLfloat), position, GL_STATIC_DRAW); glGenBuffers(1, &colorbuffer); glBindBuffer(GL_ARRAY_BUFFER, colorbuffer); glBufferData( GL_ARRAY_BUFFER, 3 * 9 * sizeof(GLfloat), color, GL_STATIC_DRAW); glGenBuffers(1, &barycentricbuffer); glBindBuffer(GL_ARRAY_BUFFER, barycentricbuffer); glBufferData( GL_ARRAY_BUFFER, 3 * 9 * sizeof(GLfloat), barycentric, GL_STATIC_DRAW); } GLuint VertexShaderID; void prepare_vertex_shader() { ////////////////////////////////////////////// // シェーダを作ります // (作るといっても宣言みたいなもの) VertexShaderID = glCreateShader(GL_VERTEX_SHADER); //////////////////////////////////////////// // ファイルから頂点シェーダを読み込みます。 // 注意 ここはSTLのifstreamとかstringstreamの使い方の話で、 // OpenGL命令は一つも無い。 const char* vertex_file_path = ".\\vertex.shader"; std::string VertexShaderCode; std::ifstream VertexShaderStream(vertex_file_path, std::ios::in); if (VertexShaderStream.is_open()) { std::stringstream sstr; sstr << VertexShaderStream.rdbuf(); VertexShaderCode = sstr.str(); VertexShaderStream.close(); } //////////////////////////////////////////// // 頂点シェーダをコンパイルします。 printf("Compiling shader : %s\n", vertex_file_path); char const* VertexSourcePointer = VertexShaderCode.c_str(); glShaderSource(VertexShaderID, 1, &VertexSourcePointer, NULL); glCompileShader(VertexShaderID); //////////////////////////////////////////// // エラーチェック GLint Result = GL_FALSE; int InfoLogLength; // 頂点シェーダをチェックします。 glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result); glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength); if (Result == FALSE) { std::vector<char> VertexShaderErrorMessage(InfoLogLength); glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]); fprintf(stdout, "%s\n", &VertexShaderErrorMessage[0]); } } GLuint FragmentShaderID; void prepare_fragment_shader() { ///////////////////////////////////////////// // シェーダを作ります。 FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER); ///////////////////////////////////////////// // ファイルからフラグメントシェーダを読み込みます。 const char* fragment_file_path = ".\\fragment.shader"; std::string FragmentShaderCode; std::ifstream FragmentShaderStream(fragment_file_path, std::ios::in); if (FragmentShaderStream.is_open()) { std::stringstream sstr; sstr << FragmentShaderStream.rdbuf(); FragmentShaderCode = sstr.str(); FragmentShaderStream.close(); } ///////////////////////////////////////////// // フラグメントシェーダをコンパイルします。 printf("Compiling shader : %s\n", fragment_file_path); char const* FragmentSourcePointer = FragmentShaderCode.c_str(); glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer, NULL); glCompileShader(FragmentShaderID); GLint Result = GL_FALSE; int InfoLogLength; ///////////////////////////////////////////// // フラグメントシェーダをチェックします。 glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result); glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength); if (Result == GL_FALSE) { std::vector<char> FragmentShaderErrorMessage(InfoLogLength); glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]); fprintf(stdout, "%s\n", &FragmentShaderErrorMessage[0]); } } GLuint ProgramID; void link_program() { GLint Result = GL_FALSE; int InfoLogLength; //////////////////////////////////////// // プログラムをリンクします。 fprintf(stdout, "Linking program\n"); ProgramID = glCreateProgram(); glAttachShader(ProgramID, VertexShaderID); glAttachShader(ProgramID, FragmentShaderID); glLinkProgram(ProgramID); //////////////////////////////////////// // プログラムをチェックします。 glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result); glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength); std::vector<char> ProgramErrorMessage((std::max)(InfoLogLength, int(1))); glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]); fprintf(stdout, "%s\n", &ProgramErrorMessage[0]); } void display(void) { glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); glLoadIdentity(); glTranslated(0.5, 0.0, 0.0);//平行移動 // シェーダを使う glUseProgram(ProgramID); // 頂点バッファ:頂点 glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer); glVertexAttribPointer( 0, // シェーダ内のlayoutとあわせる 3, // 1要素の要素数(x,y,z)で3要素 GL_FLOAT, // タイプ GL_FALSE, // 正規化しない(データが整数型の時) 0, // ストライド (void*)0 // 配列バッファオフセット ); // カラーバッファを有効にする glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, colorbuffer); glVertexAttribPointer( 1, // シェーダ内のlayoutとあわせる 3, // 1要素の要素数(r,g,b)で3要素 GL_FLOAT, // タイプ GL_FALSE, // 正規化しない(データが整数型の時) 0, // ストライド (void*)0 // 配列バッファオフセット ); //重心座標計算用バッファを有効にする glEnableVertexAttribArray(2); glBindBuffer(GL_ARRAY_BUFFER, barycentricbuffer); glVertexAttribPointer( 2, // シェーダ内のlayoutとあわせる 3, // 1要素の要素数 GL_FLOAT, // タイプ GL_FALSE, // 正規化しない(データが整数型の時) 0, // ストライド (void*)0 // 配列バッファオフセット ); // 三角形を描きます! glDrawArrays(GL_TRIANGLES, 0, 9); glDisableVertexAttribArray(0);//バッファを無効にする glDisableVertexAttribArray(1); glFlush(); }