固定機能:トゥーンレンダリング

※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。

床井研究室 - トゥーンシェーディング
http://marina.sys.wakayama-u.ac.jp/~tokoi/?date=20080218

こちらのプログラムを参考にして更に輪郭線と陰影の階層を増やしています。

ファイル
main.cpp
bunny.obj(上記リンク先からダウンロードして下さい)

toon.png

main.cpp

#pragma comment(linker, "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup")

#include <stdio.h>
#include <GL/freeglut/freeglut.h>
#include <GL/glext.h>
#include "PNG.h"

/*
** トラックボール処理用関数の宣言
*/
/*
** 簡易トラックボール処理
*/
extern void trackballInit(void);
extern void trackballRegion(int w, int h);
extern void trackballStart(int x, int y);
extern void trackballMotion(int x, int y);
extern void trackballStop(int x, int y);
extern const double *trackballRotation(void);

/*
** 簡易トラックボール処理
*/

#ifndef M_PI
#  define M_PI 3.14159265358979323846
#endif

/* ドラッグ開始位置 */
static int cx, cy;

/* マウスの絶対位置→ウィンドウ内での相対位置の換算係数 */
static double sx, sy;

/* マウスの相対位置→回転角の換算係数 */
#define SCALE (2.0 * M_PI)

/* 回転の初期値 (クォータニオン) */
static double cq[4] = { 1.0, 0.0, 0.0, 0.0 };

/* ドラッグ中の回転 (クォータニオン) */
static double tq[4];

/* ドラッグ中か否か */
static int drag = 0;

/*
** r <- p x q
*/
static void qmul(double r[], const double p[], const double q[])
{
  r[0] = p[0] * q[0] - p[1] * q[1] - p[2] * q[2] - p[3] * q[3];
  r[1] = p[0] * q[1] + p[1] * q[0] + p[2] * q[3] - p[3] * q[2];
  r[2] = p[0] * q[2] - p[1] * q[3] + p[2] * q[0] + p[3] * q[1];
  r[3] = p[0] * q[3] + p[1] * q[2] - p[2] * q[1] + p[3] * q[0];
}

/*
** 回転変換行列 r <- クォータニオン q
*/
static void qrot(double r[], double q[])
{
  double x2 = q[1] * q[1] * 2.0;
  double y2 = q[2] * q[2] * 2.0;
  double z2 = q[3] * q[3] * 2.0;
  double xy = q[1] * q[2] * 2.0;
  double yz = q[2] * q[3] * 2.0;
  double zx = q[3] * q[1] * 2.0;
  double xw = q[1] * q[0] * 2.0;
  double yw = q[2] * q[0] * 2.0;
  double zw = q[3] * q[0] * 2.0;
  
  r[ 0] = 1.0 - y2 - z2;
  r[ 1] = xy + zw;
  r[ 2] = zx - yw;
  r[ 4] = xy - zw;
  r[ 5] = 1.0 - z2 - x2;
  r[ 6] = yz + xw;
  r[ 8] = zx + yw;
  r[ 9] = yz - xw;
  r[10] = 1.0 - x2 - y2;
  r[ 3] = r[ 7] = r[11] = r[12] = r[13] = r[14] = 0.0;
  r[15] = 1.0;
}

/*
** トラックボール処理の初期化
** プログラムの初期化処理のところで実行する
*/
void trackballInit(void)
{
  /* 単位クォーターニオン */
  cq[0] = 1.0;
  cq[1] = 0.0;
  cq[2] = 0.0;
  cq[3] = 0.0;

  /* ドラッグ中ではない */
  drag = 0;
}

/*
** トラックボールする領域
** Reshape コールバック (resize) の中で実行する
*/
void trackballRegion(int w, int h)
{
  /* マウスポインタ位置のウィンドウ内の相対的位置への換算用 */
  sx = 1.0 / (double)w;
  sy = 1.0 / (double)h;
}

/*
** ドラッグ開始
** マウスボタンを押したときに実行する
*/
void trackballStart(int x, int y)
{
  /* ドラッグ開始 */
  drag = 1;

  /* ドラッグ開始点を記録 */
  cx = x;
  cy = y;
}

/*
** ドラッグ中
** マウスのドラッグ中に実行する
*/
void trackballMotion(int x, int y)
{
  if (drag) {
    double dx, dy, a;
    
    /* マウスポインタの位置のドラッグ開始位置からの変位 */
    dx = (x - cx) * sx;
    dy = (y - cy) * sy;
    
    /* マウスポインタの位置のドラッグ開始位置からの距離 */
    a = sqrt(dx * dx + dy * dy);
    
    if (a != 0.0) {
      double ar = a * SCALE * 0.5;
      double as = sin(ar) / a;
      double dq[4] = { cos(ar), dy * as, dx * as, 0.0 };
      
      /* クォータニオンを掛けて回転を合成 */
      qmul(tq, dq, cq);
    }
  }
}

/*
** 停止
** マウスボタンを離したときに実行する
*/
void trackballStop(int x, int y)
{
  /* ドラッグ終了点における回転を求める */
  trackballMotion(x, y);

  /* 回転の保存 */
  cq[0] = tq[0];
  cq[1] = tq[1];
  cq[2] = tq[2];
  cq[3] = tq[3];

  /* ドラッグ終了 */
  drag = 0;
}

/*
** 回転の変換行列を戻す
** 戻り値を glMultMatrixd() などで使用してオブジェクトを回転する
*/
const double *trackballRotation(void)
{
  /* 回転の変換行列 */
  static double rt[16];

  /* クォータニオンから回転の変換行列を求める */
  qrot(rt, tq);

  return rt;
}


/*
** 形状データ
*/


typedef float vec[3];
typedef float tex[2];
typedef unsigned int idx[3];

class Obj {
  int nv, nf;
  vec *vert, *norm, *fnorm;
  tex *texc;
  idx *face;
  void copy(const Obj &);
public:
  Obj(void);
  Obj(const Obj &);
  virtual ~Obj();
  Obj &operator=(const Obj &);
  int load(char *);
  void calcTexCoord(const GLfloat *, const GLfloat *);
  void draw(void);
  void line_draw(void);
};

#include <iostream>
#include <fstream>
#include <cstdio>
#include <cmath>

 

/*
** オブジェクトのコピー
*/
void Obj::copy(const Obj &o)
{
  nv = o.nv;
  nf = o.nf;

  try {
    if (nv > 0) {
      vert = new vec[nv];
      norm = new vec[nv];
      texc = new tex[nv];

      memcpy(vert, o.vert, sizeof(vec) * nv);
      memcpy(norm, o.norm, sizeof(vec) * nv);
      memcpy(texc, o.texc, sizeof(tex) * nv);
    }
    else {
      vert = norm = 0;
      texc = 0;
    }
    if (nf > 0) {
      fnorm = new vec[nf];
      face = new idx[nf];

      memcpy(fnorm, o.fnorm, sizeof(vec) * nf);
      memcpy(face, o.face, sizeof(idx) * nf);
    }
    else {
      fnorm = 0;
      face = 0;
    }
  }
  catch (std::bad_alloc e) {
    std::cerr << "メモリが足りません"<< std::endl;
    abort();
  }
}

/*
** コンストラクタ
*/
Obj::Obj(void)
{
  nv = nf = 0;
  vert = norm = fnorm = 0;
  texc = 0;
  face = 0;
}

/*
** コピーコンストラクタ
*/
Obj::Obj(const Obj &o)
{
  copy(o);
}

/*
** デストラクタ
*/
Obj::~Obj()
{
  if (vert) delete[] vert;
  if (norm) delete[] norm;
  if (texc) delete[] texc;
  if (fnorm) delete[] fnorm;
  if (face) delete[] face;
}

/*
** 代入演算子
*/
Obj &Obj::operator=(const Obj &o)
{
  if (this != &o) {
    this->~Obj();
    copy(o);
  }
  return *this;
}

/*
** ファイルの読み込み
*/
int Obj::load(char *name)
{
  std::ifstream file(name, std::ios::in | std::ios::binary);
  char buf[1024];
  int i, v, f;

  if (!file) {
    std::cerr << name << " が開けません" << std::endl;
    return 1;
  }

  /* データの数を調べる */
  v = f = 0;
  while (file.getline(buf, sizeof buf)) {
    if (buf[0] == 'v' && buf[1] == ' ') {
      ++v;
    }
    else if (buf[0] == 'f' && buf[1] == ' ') {
      ++f;
    }
  }

  nv = v;
  nf = f;

  try {
    vert = new vec[v];
    norm = new vec[v];
    texc = new tex[v];
    fnorm = new vec[f];
    face = new idx[f];
  }
  catch (std::bad_alloc e) {
    std::cerr << "メモリが足りません" << std::endl;
    abort();
  }

  /* ファイルの巻き戻し */
  file.clear();
  file.seekg(0L, std::ios::beg);

  /* データの読み込み */
  v = f = 0;
  while (file.getline(buf, sizeof buf)) {
    if (buf[0] == 'v' && buf[1] == ' ') {
      sscanf(buf, "%*s %f %f %f", vert[v], vert[v] + 1, vert[v] + 2);
      ++v;
    }
    else if (buf[0] == 'f' && buf[1] == ' ') {
      if (sscanf(buf + 2, "%d/%*d/%*d %d/%*d/%*d %d/%*d/%*d", face[f], face[f] + 1, face[f] + 2) != 3) {
        if (sscanf(buf + 2, "%d//%*d %d//%*d %d//%*d", face[f], face[f] + 1, face[f] + 2) != 3) {
          sscanf(buf + 2, "%d %d %d", face[f], face[f] + 1, face[f] + 2);
        }
      }
      --face[f][0];
      --face[f][1];
      --face[f][2];
      ++f;
    }
  }

  /* 面法線ベクトルの算出 */
  for (i = 0; i < f; ++i) {
    float dx1 = vert[face[i][1]][0] - vert[face[i][0]][0];
    float dy1 = vert[face[i][1]][1] - vert[face[i][0]][1];
    float dz1 = vert[face[i][1]][2] - vert[face[i][0]][2];
    float dx2 = vert[face[i][2]][0] - vert[face[i][0]][0];
    float dy2 = vert[face[i][2]][1] - vert[face[i][0]][1];
    float dz2 = vert[face[i][2]][2] - vert[face[i][0]][2];

    fnorm[i][0] = dy1 * dz2 - dz1 * dy2;
    fnorm[i][1] = dz1 * dx2 - dx1 * dz2;
    fnorm[i][2] = dx1 * dy2 - dy1 * dx2;
  }

  /* 頂点の仮想法線ベクトルの算出 */
  for (i = 0; i < v; ++i) {
    norm[i][0] = norm[i][1] = norm[i][2] = 0.0;
  }
  
  for (i = 0; i < f; ++i) {
    norm[face[i][0]][0] += fnorm[i][0];
    norm[face[i][0]][1] += fnorm[i][1];
    norm[face[i][0]][2] += fnorm[i][2];

    norm[face[i][1]][0] += fnorm[i][0];
    norm[face[i][1]][1] += fnorm[i][1];
    norm[face[i][1]][2] += fnorm[i][2];

    norm[face[i][2]][0] += fnorm[i][0];
    norm[face[i][2]][1] += fnorm[i][1];
    norm[face[i][2]][2] += fnorm[i][2];
  }

  /* 頂点の仮想法線ベクトルの正規化 */
  for (i = 0; i < v; ++i) {
    float a = sqrt(norm[i][0] * norm[i][0]
                 + norm[i][1] * norm[i][1]
                 + norm[i][2] * norm[i][2]);

    if (a != 0.0) {
      norm[i][0] /= a;
      norm[i][1] /= a;
      norm[i][2] /= a;
    }
  }

  return 0;
}

/*
** テクスチャ座標の計算
*/
void Obj::calcTexCoord(const GLfloat *lpos, const GLfloat *epos)
{
  GLfloat lp[3], lv[3], ep[3], ev[3];

  /*
  ** 光源位置/ベクトルの算出
  */
  if (lpos[3] != 0.0) {
    /* 実座標を求める */
    lp[0] = lpos[0] / lpos[3];
    lp[1] = lpos[1] / lpos[3];
    lp[2] = lpos[2] / lpos[3];
  }
  else {
    /* 光源方向の単位ベクトルを求める */
    lv[0] = lpos[0];
    lv[1] = lpos[1];
    lv[2] = lpos[2];
    GLfloat a = sqrtf(lv[0] * lv[0] + lv[1] * lv[1] + lv[2] * lv[2]);
    if (a != 0.0) {
      lv[0] /= a;
      lv[1] /= a;
      lv[2] /= a;
    }
  }

  /*
  ** 視点位置/ベクトルの算出
  */
  if (epos[3] != 0.0) {
    /* 実座標を求める */
    ep[0] = epos[0] / epos[3];
    ep[1] = epos[1] / epos[3];
    ep[2] = epos[2] / epos[3];
  }
  else {
    /* 視線方向の単位ベクトルを求める */
    ev[0] = epos[0];
    ev[1] = epos[1];
    ev[2] = epos[2];
    GLfloat a = sqrtf(ev[0] * ev[0] + ev[1] * ev[1] + ev[2] * ev[2]);
    if (a != 0.0) {
      ev[0] /= a;
      ev[1] /= a;
      ev[2] /= a;
    }
  }

  /*
  ** テクスチャ座標の算出
  */
  for (int i = 0; i < nv; ++i) {

    if (lpos[3] != 0.0) {
      /* 頂点ごとに光源方向の単位ベクトルを求める */
      lv[0] = lp[0] - vert[i][0];
      lv[1] = lp[1] - vert[i][1];
      lv[2] = lp[2] - vert[i][2];
      GLfloat a = sqrtf(lv[0] * lv[0] + lv[1] * lv[1] + lv[2] * lv[2]);
      if (a != 0.0) {
        lv[0] /= a;
        lv[1] /= a;
        lv[2] /= a;
      }
    }

    if (epos[3] != 0.0) {
      /* 頂点ごとに視線方向の単位ベクトルを求める */
      ev[0] = ep[0] - vert[i][0];
      ev[1] = ep[1] - vert[i][1];
      ev[2] = ep[2] - vert[i][2];
      GLfloat a = sqrtf(ev[0] * ev[0] + ev[1] * ev[1] + ev[2] * ev[2]);
      if (a != 0.0) {
        ev[0] /= a;
        ev[1] /= a;
        ev[2] /= a;
      }
    }

    /* テクスチャの s 座標は明度 */
    texc[i][0] = norm[i][0] * lv[0] + norm[i][1] * lv[1] + norm[i][2] * lv[2];

    /* テクスチャの t 座標はエッジ */
    texc[i][1] = norm[i][0] * ev[0] + norm[i][1] * ev[1] + norm[i][2] * ev[2];
  }
}

/*
** 図形の表示
*/
void Obj::draw(void)
{
  glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
  glEnableClientState(GL_TEXTURE_COORD_ARRAY);
  
    glNormalPointer(GL_FLOAT, 0, norm);
  glVertexPointer(3, GL_FLOAT, 0, vert);
  glTexCoordPointer(2, GL_FLOAT, 0, texc);
  
  glEnable(GL_TEXTURE_2D);

  glDrawElements(GL_TRIANGLES, nf * 3, GL_UNSIGNED_INT, face);

  glDisable(GL_TEXTURE_2D);

  glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
  glDisableClientState(GL_VERTEX_ARRAY);
}

void Obj::line_draw(void)
{
  glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);

    glNormalPointer(GL_FLOAT, 0, norm);
  glVertexPointer(3, GL_FLOAT, 0, vert);
  glDisable(GL_TEXTURE_2D);

  glDrawElements(GL_TRIANGLES, nf * 3, GL_UNSIGNED_INT, face);
  glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
  glDisableClientState(GL_VERTEX_ARRAY);
}


Obj *data;

/*
** 視点の距離
*/
static double offset = -200.0;

/*
** 光源
*/
static const GLfloat lightpos[] = { 0.0, 0.0, 1.0, 0.0 };  /* 位置 */


GLuint texName;
TEXTURE *texture;
#define TEX_WIDTH 256
#define TEX_HEIGHT 256
void TexCreate(char* FileName){
  glGenTextures(1, &texName);
  texture = new TEXTURE(FileName);
  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
  //テクスチャオブジェクトの作成
  glBindTexture(GL_TEXTURE_2D, texName);
  //テクスチャの指定
  glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,texture->Width,texture->Height,0,GL_RGBA,GL_UNSIGNED_BYTE,texture->image);
  //テクスチャの繰り返し方法の指定
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);//GL_CLAMP);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);//GL_CLAMP);
  //テクスチャを拡大・縮小する方法の指定
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);//NEAREST);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//NEAREST);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
  glBindTexture(GL_TEXTURE_2D, 0);
}

/*
** 初期化
*/
static void init(void)
{
  TexCreate("toon.png");
  /* 初期設定 */
  glClearColor(0.3, 0.3, 1.0, 1.0);
  glEnable(GL_DEPTH_TEST);
  glDisable(GL_CULL_FACE);
  
  /* 光源の初期設定 */
  glDisable(GL_LIGHTING);

  /* 形状データオブジェクトの作成 */
  data = new Obj;

  /* 形状データの読み込み */
  data->load("bunny.obj");
}

/*
** シーンの描画
*/
void scene(void)
{
  GLfloat eyepos[] = { 0.0f, 0.0f, (GLfloat)-offset, 1.0f };
  GLfloat lpos[4], epos[4];
  const double *rt;
  int i, j;

  /*
  ** 回転の変換行列を取り出す
  */
  rt = trackballRotation();

  /*
  ** 光源を物体の回転と逆方向に回転する(回転行列の転置行列をかける)
  */
  for (i = 0; i < 4; ++i) {
    lpos[i] = 0.0;
    for (j = 0; j < 4; ++j) {
      lpos[i] += lightpos[j] * rt[i * 4 + j];
    }
  }

  /*
  ** 視点を物体の回転と逆方向に回転する(回転行列の転置行列をかける)
  */
  for (i = 0; i < 4; ++i) {
    epos[i] = 0.0;
    for (j = 0; j < 4; ++j) {
      epos[i] += eyepos[j] * rt[i * 4 + j];
    }
  }

  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, texName);
  glEnable(GL_CULL_FACE);
  glCullFace(GL_BACK);
  data->calcTexCoord(lpos, epos);
  data->draw();
  float size =1.02;
  GLfloat black[] = { 0.0, 0.0, 0.0, 1.0 };
  glMaterialfv(GL_FRONT, GL_DIFFUSE, black);
  glScalef(size,size,size);
  glColorMaterial(GL_FRONT_AND_BACK,GL_DIFFUSE);
  glEnable(GL_COLOR_MATERIAL);
  glColor3f(0.0f,0.0f,0.0f);
  glCullFace(GL_FRONT);
  data->line_draw();
}

/****************************
** GLUT のコールバック関数 **
****************************/

static void display(void)
{
  /* モデルビュー変換行列の設定 */
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  
  /* 視点の移動(物体の方を奥に移動)*/
  glTranslated(0.0, 0.0, offset);
  
  /* 光源の位置を設定 */
  glLightfv(GL_LIGHT0, GL_POSITION, lightpos);
  
  /* トラックボール処理による回転 */
  glMultMatrixd(trackballRotation());
  
  /* 画面クリア */
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  
  /* シーンの描画 */
  scene();
  
  /* ダブルバッファリング */
  glutSwapBuffers();
}

static void resize(int w, int h)
{
  /* トラックボールする範囲 */
  trackballRegion(w, h);
  
  /* ウィンドウ全体をビューポートにする */
  glViewport(0, 0, w, h);
  
  /* 透視変換行列の指定 */
  glMatrixMode(GL_PROJECTION);
  
  /* 透視変換行列の初期化 */
  glLoadIdentity();
  gluPerspective(60.0, (double)w / (double)h, 1.0, 500.0);
}

static void idle(void)
{
  /* 画面の描き替え */
  glutPostRedisplay();
}

static void mouse(int button, int state, int x, int y)
{
  switch (button) {
  case GLUT_LEFT_BUTTON:
    switch (state) {
    case GLUT_DOWN:
      /* トラックボール開始 */
      trackballStart(x, y);
      glutIdleFunc(idle);
      break;
    case GLUT_UP:
      /* トラックボール停止 */
      glutIdleFunc(0);
      trackballStop(x, y);
      break;
    default:
      break;
    }
    break;
    default:
      break;
  }
}

static void motion(int x, int y)
{
  /* トラックボール移動 */
  trackballMotion(x, y);
}

static void keyboard(unsigned char key, int x, int y)
{
  switch (key) {
  case '+':
    offset += 10.0;
    glutPostRedisplay();
    break;
  case '-':
    offset -= 10.0;
    glutPostRedisplay();
    break;
  case 'q':
  case 'Q':
  case '\033':
    /* ESC か q か Q をタイプしたら終了 */
    exit(0);
  default:
    break;
  }
}

/*
** メインプログラム
*/
int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);
  glutInitWindowSize(640, 480);
  glutCreateWindow("トゥーンレンダリング");
  glutDisplayFunc(display);
  glutReshapeFunc(resize);
  glutMouseFunc(mouse);
  glutMotionFunc(motion);
  glutKeyboardFunc(keyboard);
  init();
  glutMainLoop();
  return 0;
}

 

 

最終更新:2015年05月24日 16:21
添付ファイル