zbuffer法はzバッファというメモリを用意し、ビューポート変換まで終わった図形をラスタライズする際に、走査変換などでピクセルをレンダーバッファに書き込む際にそのz値をzバッファに書き込むことで陰面消去を行う方法。ここで、ではどうやってz値を計算するのかというと、調べたところ面方程式を使うらしい。
つまり、三角形の三点の座標(整数ピクセル座標のx,yと実数のz)がわかっているので、その三点が存在する面の方程式を求めることができる。
面方程式がわかれば、問題は「ax+by+cz+d=0 で表される平面上のx,yがわかっているときのzを求めよ」という問いになり、zについて解けばいいという事になる。
以前作った線分の交差判定をするコードと一緒に書いたPoint型に色を付けられるようにし、かつzを保持できるように変更する。
走査変換の際に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は前に作った物を使う。
#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); }