スポンサーリンク

zBuffer法で陰面消去

zbuffer法はzバッファというメモリを用意し、ビューポート変換まで終わった図形をラスタライズする際に、走査変換などでピクセルをレンダーバッファに書き込む際にそのz値をzバッファに書き込むことで陰面消去を行う方法。ここで、ではどうやってz値を計算するのかというと、調べたところ面方程式を使うらしい。

つまり、三角形の三点の座標(整数ピクセル座標のx,yと実数のz)がわかっているので、その三点が存在する面の方程式を求めることができる。

面方程式がわかれば、問題は「ax+by+cz+d=0 で表される平面上のx,yがわかっているときのzを求めよ」という問いになり、zについて解けばいいという事になる。

LineCross.hpp

線分の交差判定するコード

以前作った線分の交差判定をするコードと一緒に書いたPoint型に色を付けられるようにし、かつzを保持できるように変更する。

scanConversion.hpp

走査変換の際にdepthbufferのチェックと更新を行う。

#pragma once

#include "NByteData.hpp"
#include "LineCross.hpp"

template<typename PixelType>
struct imagedata {
  int width;
  int height;
  float* depthbuffer;// デプスバッファ
  PixelType* img;
};

//! @brief 水平な線分ともう一方の線分との交差判定を行う
//! @param [out] cross 交差していた場合の座標(ピクセル単位)
//! @param [in] scanL 水平な線分
//! @param [in] L 線分
bool isPixelHorizonalCross(
  Pointi* cross,
  const LineSegmenti& scanL,
  const LineSegmenti& L) {

  //三角形塗りつぶしようなので、scanLは必ずLの始点・終点よりも広い範囲をとる
  //そのため始点・終点のY座標が一致していたら交差しているのでtrueを返す
  if (scanL.s.y == L.s.y) {
    *cross = L.s;
    return true;
  }
  if (scanL.s.y == L.e.y) {
    *cross = L.e;
    return true;
  }

  return isCrossPixel(cross, scanL, L);

}

//! @brief 画素に色を設定
//! @param [in,out] img 画像データ
//! @param [in] P 書き込み先の座標
template<typename PixelType>
void Plot(
  imagedata<PixelType> *img,
  Pointi P,
  float z
  ) {
  
  PixelType Color{
    P.color.r,
    P.color.g, 
    P.color.b
  };

  // デプステスト
  if (img->depthbuffer[P.y * img->width + P.x] > z) {
    img->img[P.y * img->width + P.x] = Color;

    img->depthbuffer[P.y * img->width + P.x] = z;// デプスバッファ更新
  }
}


//! @brief 走査変換で頂点ABCからなる三角形を描画
//! @param [in,out] img 画像データ
//! @param [in] A 頂点1の座標
//! @param [in] B 頂点2の座標
//! @param [in] C 頂点3の座標
template<typename PixelType>
void scanConversion(
  imagedata<PixelType> *img,
  const Pointi A,
  const Pointi B,
  const Pointi C
) {
  //三角形の法線ベクトル
  std::array<double, 3> pa{ A.x,A.y,A.z };
  std::array<double, 3> pb{ B.x,B.y,B.z };
  std::array<double, 3> pc{ C.x,C.y,C.z };
  std::array<double, 3> tn,v1,v2;
  szl::mathv::vector_from_line3(v1, pa, pb);
  szl::mathv::vector_from_line3(v2, pa, pc);
  szl::mathv::outer3(tn, v1, v2);
  double nx = tn[0]; double x0 = pa[0];
  double ny = tn[1]; double y0 = pa[1];
  double nz = tn[2]; double z0 = pa[2];
  double d = -(nx * x0 + ny * y0+ nz *z0 );
  // zの求め方
  auto getZ = [&](double x,double y)->double{
    return -(nx*x + ny * y + d) / nz;
  };
  //入力された頂点をY座標が小さい順にソート
  Pointi m[3]{ A,B,C };
  std::sort(
    std::begin(m), std::end(m),
    [](const auto& s, const auto& t) {
    return s.y < t.y;
  });

  //X座標の最大・最小より少し広い範囲
  const int xmin = std::min_element(
    std::begin(m), std::end(m),
    [](const auto& s, const auto& t) {return s.x < t.x; }
  )->x - 1;
  const int xmax = std::max_element(
    std::begin(m), std::end(m),
    [](const auto& s, const auto& t) {return s.x < t.x; }
  )->x + 1;

  /*
           0
         / |
       /   |
     /     |
    1      |
     \     |
      \    |
       \   |
        \  |
         \ |
          2

  ソートにより三角形の配列mはy座標順に並んでいる


  */

  // 走査線と 01,02
  for (int y = m[0].y; y < m[1].y; y++) {
    //上半分について処理

    LineSegmenti scan(
      Pointi(xmin, y),
      Pointi(xmax, y)
    );

    Pointi cross01;
    Pointi cross02;

    LineSegmenti L01(m[0], m[1]);
    LineSegmenti L02(m[0], m[2]);

    bool ab = isPixelHorizonalCross(&cross01, scan, L01);
    bool ac = isPixelHorizonalCross(&cross02, scan, L02);

    if (ab == true && ac == true) {
      int cxmin = (std::min)(cross01.x, cross02.x);
      int cxmax = (std::max)(cross01.x, cross02.x);
      for (int x = cxmin; x <= cxmax; x++) {
        Pointi P(x, y);
        P.color = A.color;
        Plot(img, P , getZ(x,y) );
      }
    }


  }
  // 走査線と 12 , 01
  for (int y = m[1].y; y <= m[2].y; y++) {
    LineSegmenti scan(
      Pointi(xmin, y),
      Pointi(xmax, y)
    );

    Pointi cross12;
    Pointi cross02;

    LineSegmenti L12(m[1], m[2]);
    LineSegmenti L02(m[0], m[2]);

    bool bc = isPixelHorizonalCross(&cross12, scan, L12);
    bool ac = isPixelHorizonalCross(&cross02, scan, L02);

    if (bc == true && ac == true) {
      int cxmin = (std::min)(cross12.x, cross02.x);
      int cxmax = (std::max)(cross12.x, cross02.x);
      for (int x = cxmin; x <= cxmax; x++) {
        Pointi P(x, y);
        P.color = A.color;
        Plot(img, P, getZ(x, y) );
      }

    }
  }

}

呼び出し例

mygl-transform.hpp,NByteData.hppは前に作った物を使う。

NBytesのデータを扱うクラス

OpenGLの座標変換を時前でやってみる

#pragma warning(disable:4996)

#include <GL/glut.h>

#pragma comment(lib,"opengl32.lib")

#include "NByteData.hpp"

#include "mygl-transform.hpp"

#include "scanConversion.hpp"

#include<vector>


std::vector < NByteData<3> > myimage;
std::vector < float > mydepthbuffer;

void my_transformation(
  std::array<double, 4>& wndcoord,
  const std::array<double, 4>& obj,
  const std::array<double, 4>& norm
) {

  std::array<double, 16> model;
  std::array<double, 16> proj;
  std::array<double, 2> depthrange{ 0, 1 };
  std::array<int, 4> viewport;

  glGetDoublev(GL_MODELVIEW_MATRIX, model.data());//行列とビューポート取得
  glGetDoublev(GL_PROJECTION_MATRIX, proj.data());
  glGetIntegerv(GL_VIEWPORT, viewport.data());


  opengltransformation(
    wndcoord,//結果のピクセル座標
    obj,//モデルの頂点座標
    norm,
    model,
    proj,
    depthrange,
    viewport
  );
}

//保存の時に画像を上下反転する
void reverse_Y(const int width, const int height, NByteData<3>* p);

void pnmP3_Write(const char* const fname, const int vmax, const int width, const int height, const unsigned char* const p);

void my_draw(
  RGB triangle_color,
  const std::array<double, 4>& A,
  const std::array<double, 4>& B,
  const std::array<double, 4>& C
) {
  std::array<double, 4> wA;
  std::array<double, 4> wB;
  std::array<double, 4> wC;

  //法線は今回は使用しない
  std::array<double, 4> dmy{ 1,1,1,1 };

  my_transformation(wA, A, dmy);//三次元座標をピクセル座標に変換
  my_transformation(wB, B, dmy);
  my_transformation(wC, C, dmy);

  //////////////////////////////
  int width = glutGet(GLUT_WINDOW_WIDTH);
  int height = glutGet(GLUT_WINDOW_HEIGHT);


  imagedata< NByteData<3> > imgd;
  imgd.width = width;
  imgd.height = height;
  imgd.img = &myimage[0];
  imgd.depthbuffer = &mydepthbuffer[0];

  //三角形の描画
  Pointi pA(wA[0], wA[1], wA[2]); pA.color = triangle_color;
  Pointi pB(wB[0], wB[1], wB[2]); pB.color = triangle_color;
  Pointi pC(wC[0], wC[1], wC[2]); pC.color = triangle_color;
  scanConversion(&imgd, pA, pB, pC);

}

void display(void)
{
  glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
  glEnable(GL_DEPTH_TEST);

  glMatrixMode(GL_PROJECTION);//透視投影行列
  glLoadIdentity();
  gluPerspective(60, GLUT_SCREEN_WIDTH / (double)GLUT_SCREEN_HEIGHT, 0.01, 10);
  glMatrixMode(GL_MODELVIEW);//モデルビュー変換行列
  glLoadIdentity();
  glPushMatrix();

  int width = glutGet(GLUT_WINDOW_WIDTH);
  int height = glutGet(GLUT_WINDOW_HEIGHT);
  myimage.resize(width* height);
  std::fill(myimage.begin(), myimage.end(), NByteData<3>{255, 255, 255});
  
  //デプスバッファ初期化
  mydepthbuffer.resize(width* height);
  std::fill(mydepthbuffer.begin(), mydepthbuffer.end(), 1.0f);

  glTranslated(0.0, 0.0, -1);
  glRotated(30, 1, 1, 1);

  //赤い三角形の描画
  {

    std::array<double, 4> norm{ 1,1,1,1 };
    std::array<double, 4> pa{ -0.3 ,-0.4 , 0.0 ,1 };
    std::array<double, 4> pb{  0.3 ,-0.4 , 0.2 ,1 };
    std::array<double, 4> pc{  0.1 , 0.2 ,-0.2 ,1 };

    RGB red;
    red.r = 255; red.g = 0; red.b = 0;
    my_draw(red,pa, pb, pc);//座標変換のテスト

    glColor3d(1, 0, 0);
    glPointSize(5);
    glBegin(GL_TRIANGLES);
    glVertex3dv(pa.data());
    glVertex3dv(pb.data());
    glVertex3dv(pc.data());
    glEnd();
  }

  //緑の三角形の描画
  {

    std::array<double, 4> norm{ 1,1,1,1 };
    std::array<double, 4> pa{ 0.3 ,-0.4 , 0.0 , 1 };
    std::array<double, 4> pb{ -0.3, 0.4 , 0.2 , 1 };
    std::array<double, 4> pc{ 0.1 ,-1.0 ,-0.2 , 1 };

    RGB green;
    green.r = 0; green.g = 255; green.b = 0;
    my_draw(green,pa, pb, pc);//座標変換のテスト

    glColor3d(0, 1, 0);
    glPointSize(5);
    glBegin(GL_TRIANGLES);
    glVertex3dv(pa.data());
    glVertex3dv(pb.data());
    glVertex3dv(pc.data());
    glEnd();
  }


  glPopMatrix();

  glFlush();


  ////////////////////////////////////////////////
  // ファイル出力

  //上下反転
  reverse_Y(width, height, &myimage[0]);

  pnmP3_Write(//ファイルに出力
    R"(C:\test\data\a.ppm)",
    255, width, height, myimage.begin()->data()
  );


}

void init(void)
{
  glClearColor(0.0, 0.0, 1.0, 1.0);
}

int main(int argc, char*argv[])
{

  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGBA);
  glutCreateWindow(argv[0]);
  glutDisplayFunc(display);
  init();
  glutMainLoop();
  return 0;
}

//画像の上下を反転する
void reverse_Y(const int width, const int height, NByteData<3>* p) {

  size_t HalfHEIGHT = height / 2;
  for (int ha = 0; ha < HalfHEIGHT; ha++) {

    int hb = (height - ha) - 1;

    NByteData<3>* pha = p + ha * width;
    NByteData<3>* phb = p + hb * width;

    if (ha != hb) {
      for (size_t i = 0; i < width; i++) {
        std::swap(pha[i], phb[i]);
      }
    }

  }

}


//! @brief PPM(RGB各1byte,カラー,テキスト)を書き込む
//! @param [in] fname ファイル名
//! @param [in] vmax 全てのRGBの中の最大値
//! @param [in] width 画像の幅
//! @param [in] height 画像の高さ
//! @param [in] p 画像のメモリへのアドレス
//! @details RGBRGBRGB....のメモリを渡すと、RGBテキストでファイル名fnameで書き込む
void pnmP3_Write(const char* const fname, const int vmax, const int width, const int height, const unsigned char* const p) { // PPM ASCII


  FILE* fp = fopen(fname, "w");

  fprintf(fp, "P3\n%d %d\n%d\n", width, height, vmax);

  size_t k = 0;
  for (size_t i = 0; i < (size_t)height; i++) {
    for (size_t j = 0; j < (size_t)width; j++) {
      fprintf(fp, "%d %d %d ", p[k * 3 + 0], p[k * 3 + 1], p[k * 3 + 2]);
      k++;
    }
    fprintf(fp, "\n");
  }

  fclose(fp);


}

実行結果

OpenGLで描画
自前実装

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

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


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