スポンサーリンク

GLSLを試す (6) ワイヤーフレーム表示

シェーダを使ってワイヤーフレーム表示します。

原理

●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 ) 未満」 の領域を塗れば、ワイヤーフレーム表示と同じ状態になる。

ソースコード

C言語側

データ準備

//バッファとデータ
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()
    
  );

} 
  • fwidth( a ) … abs(dFdx( p )) + abs(dFdy( p )) を返す
  • step( a , b ) … 閾値a未満なら0.0,それ以外は1.0を返す
  • mix( x , y , a ) … x(1-a)+y*aを返す

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;
  }

}

実行結果

全体のソースコード(C言語側)

#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();
}

コメントを残す

メールアドレスが公開されることはありません。

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


この記事のトラックバックURL: