3D-s számítógépes geometria és alakzatrekonstrukció 3c. Tesztkörnyezet III
http://cg.iit.bme.hu/portal/node/312 https://portal.vik.bme.hu/kepzes/targyak/VIIIMA01 Dr. Várady Tamás, Salvi Péter BME, Villamosmérnöki és Informatikai Kar Irányítástechnika és Informatika Tanszék
Tartalom ●
Átlaggörbület-approximáció ● ●
Mesh-navigáció [OpenMesh] Dialógusablakok felépítése [Qt] –
●
●
Layoutok
Színezés [OpenGL]
Fényvonalak ● ● ●
Fényvonal-textúra kiszámítása Resource-ok használata [Qt] Textúrák használata [OpenGL] –
Környezeti textúrák
Harmadik fázis ●
Átlaggörbület ● ●
Színkódolás [kék→zöld→piros] Szélsőértékek meghatározása – –
●
Fényvonalak [bővebben később] ● ●
●
Tartomány szélének levágása Explicit megadás
Dinamikus textúra Kimutat apró egyenetlenségeket
Ehhez: ● ● ●
Qt [menü, dialóguskészítés] OpenGL [színezés, textúra] OpenMesh [háromszögháló bejárás]
Átlaggörbület (ismétlés) ●
Becslés háromszöglegyező alapján 1 βi∣e i∣ ∑ i 4 H p = 1 Ap 3
●
Feladatok: ●
●
●
A(p) kiszámítása (és eltárolása) βi szögek kiszámítása H(p) eltárolása
Figyelem! ● ● ●
A kódrészletek nem pontosak/teljesek Csak a lényeges részeket mutatják Gyakran kimaradnak... ● ● ● ●
●
Scope-operátorok Deklarációk #include utasítások Stb.
Teljes verzió ● ●
A honlapon levő zip fájlokban Érdemes óra után részletesen átnézni
Háromszögterületek ●
MyViewer.h: struct MyTraits : public OpenMesh::DefaultTraits { VertexTraits { double area; // total area of the surrounding triangles double mean; // approximated mean curvature }; FaceTraits { double area; }; }; typedef OpenMesh::TriMesh_ArrayKernelT<MyTraits> MyMesh;
●
MyViewer.cpp: for(ConstFaceIter i = mesh.faces_begin(), ie = mesh.faces_end(); i != ie; ++i) { HalfedgeHandle h1 = mesh.halfedge_handle(*i); HalfedgeHandle h2 = mesh.next_halfedge_handle(h1); mesh.data(*i).area = (halfedgeVector(h1) % halfedgeVector(h2)).norm() / 2; } for(VertexIter i = mesh.vertices_begin(), ie = mesh.vertices_end(); i != ie; ++i) { mesh.data(*i).area = 0; mesh.data(*i).mean = 0; for(ConstVertexFaceIter j(mesh, *i); j.is_valid(); ++j) mesh.data(*i).area += mesh.data(*j).area; }
MyViewer.cpp ●
Segédfüggvény: Vector MyViewer::halfedgeVector(HalfedgeHandle const &h) const { return mesh.point(mesh.to_vertex_handle(h)) mesh.point(mesh.from_vertex_handle(h)); }
●
Átlaggörbület-számítás: for(VertexIter i = mesh.vertices_begin(), ie = mesh.vertices_end(); i != ie; ++i) { for(ConstVertexEdgeIter j(mesh, *i); j.is_valid(); ++j) { double angle; HalfedgeHandle h1 = mesh.halfedge_handle(*j, 0); HalfedgeHandle h2 = mesh.halfedge_handle(*j, 1); i i Vector v = halfedgeVector(h1);
1 β ∣e i∣ ∑ 4 H p = 1 mesh.is_boundary(h2)) Ap 3
if(mesh.is_boundary(h1) || angle = 0.0; else { Vector n1 = mesh.normal(mesh.face_handle(h1)); Vector n2 = mesh.normal(mesh.face_handle(h2)); angle = acos(std::min(std::max(n1 | n2, -1.0f), 1.0f)); angle *= ((n1 % n2) | v) >= 0.0 ? 1.0 : -1.0; } mesh.data(*i).mean += angle * v.norm(); }
} mesh.data(*i).mean *= 3.0 / 4.0 / mesh.data(*i).area;
A program szervezése ●
MyViewer osztály ● ● ●
●
Görbületszámítás ● ● ●
●
double mean_min, mean_max, cutoff_ratio getMeanMin(), setMeanMin(double) stb. enum {COLOR_PLAIN, COLOR_MEAN} coloring updateMeanCurvature(bool update_min_max) Min/max update nem kell pl. fairing után Színezés → meanMapColor(double, double *)
GUI ● ●
Menü, dialógusablakok Billentyűleütésre (P/M) coloring változtatása
MyViewer.cpp void MyViewer::updateMeanCurvature(bool update_min_max) { ... // Mean curvature computation if(update_min_max) updateMeanMinMax(); } void MyViewer::updateMeanMinMax() { size_t n = mesh.n_vertices(); if(n == 0) return; std::vector<double> mean; mean.reserve(n); for(VertexIter i = mesh.vertices_begin(), ie = mesh.vertices_end(); i != ie; ++i) mean.push_back(mesh.data(*i).mean); std::sort(mean.begin(), mean.end()); size_t k = (double)n * cutoff_ratio; mean_min = std::min(mean[k-1], 0.0); mean_max = std::max(mean[n-k], 0.0); }
MyViewer.cpp void MyViewer::meanMapColor(double d, double *color) const { if(d <= mean_min) { color[0] = 0.0; color[1] = 0.0; color[2] = 1.0; } else if(d >= mean_max) { color[0] = 1.0; color[1] = 0.0; color[2] = 0.0; } else if(d < 0) { double alpha = d / mean_min; color[0] = 0.0; color[1] = 1.0 - alpha; color[2] = alpha; } else { double alpha = d / mean_max; color[0] = alpha; color[1] = 1.0 - alpha; color[2] = 0; } } void MyViewer::draw() { ... std::vector<double> color(3, 1.0); for(ConstFaceIter i = mesh.faces_begin(), ie = mesh.faces_end(); i != ie; ++i) { glBegin(GL_POLYGON); for(ConstFaceVertexIter j(mesh, *i); j.is_valid(); ++j) { if(coloring == COLOR_MEAN) { meanMapColor(mesh.data(*j).mean, &color[0]); glColor3dv(&color[0]); } glNormal3fv(mesh.normal(*j).data()); glVertex3fv(mesh.point(*j).data()); } glEnd(); } ...
GUI ●
MyViewer.cpp: void MyViewer::keyPressEvent(QKeyEvent *e) { ... case Qt::Key_P: coloring = COLOR_PLAIN; updateGL(); break; case Qt::Key_M: coloring = COLOR_MEAN; updateGL(); break; ... }
●
MyWindow.cpp: MyWindow::MyWindow(QApplication *parent) : QMainWindow(), parent(parent) { ... QAction *cutoffAction = new QAction(tr("Set &cutoff ratio"), this); cutoffAction->setStatusTip(tr("Set mean map cutoff ratio")); connect(cutoffAction, SIGNAL(triggered()), this, SLOT(setCutoff())); QAction *rangeAction = new QAction(tr("Set &range"), this); rangeAction->setStatusTip(tr("Set mean map range")); connect(rangeAction, SIGNAL(triggered()), this, SLOT(setRange())); QMenu *visMenu = menuBar()->addMenu(tr("&Visualization")); visMenu->addAction(cutoffAction); visMenu->addAction(rangeAction); }
Kitérő – Layoutok ● ● ● ●
● ●
Widgetek elhelyezése QWidget::setLayout [a legkülső layout] QLayout::addWidget, QLayout::addLayout QBoxLayout, QGridLayout, QFormLayout
Jó alapméretek, átméretezés kezelése Saját widgetekhez sizeHint()
setCutoff() QDialog *dlg = new QDialog(this); QHBoxLayout *hb1 = new QHBoxLayout, *hb2 = new QHBoxLayout; QVBoxLayout *vb = new QVBoxLayout; QLabel *text = new QLabel(tr("Cutoff ratio:")); QDoubleSpinBox *sb = new QDoubleSpinBox; QPushButton *cancel = new QPushButton(tr("Cancel")); QPushButton *ok = new QPushButton(tr("Ok")); sb->setDecimals(3); sb->setRange(0.001, 0.5); sb->setSingleStep(0.01); sb->setValue(viewer->getCutoffRatio()); connect(cancel, SIGNAL(pressed()), dlg, SLOT(reject())); connect(ok, SIGNAL(pressed()), dlg, SLOT(accept())); ok->setDefault(true); hb1->addWidget(text); hb1->addWidget(sb); hb2->addWidget(cancel); hb2->addWidget(ok); vb->addLayout(hb1); vb->addLayout(hb2); dlg->setWindowTitle(tr("Set ratio")); dlg->setLayout(vb); if(dlg->exec() == QDialog::Accepted) { viewer->setCutoffRatio(sb->value()); viewer->updateGL(); }
setRange() QDialog *dlg = new QDialog(this); QGridLayout *grid = new QGridLayout; QLabel *text1 = new QLabel(tr("Min:")), *text2 = new QLabel(tr("Max:")); QDoubleSpinBox *sb1 = new QDoubleSpinBox, *sb2 = new QDoubleSpinBox; QPushButton *cancel = new QPushButton(tr("Cancel")); QPushButton *ok = new QPushButton(tr("Ok")); double max = std::numeric_limits<double>::max(); sb1->setDecimals(5); sb2->setDecimals(5); sb1->setRange(-max, 0.0); sb2->setRange(0.0, max); sb1->setSingleStep(0.01); sb2->setSingleStep(0.01); sb1->setValue(viewer->getMeanMin()); sb2->setValue(viewer->getMeanMax()); connect(cancel, SIGNAL(pressed()), dlg, SLOT(reject())); connect(ok, SIGNAL(pressed()), dlg, SLOT(accept())); ok->setDefault(true); grid->addWidget( text1, 1, 1, Qt::AlignRight); grid->addWidget( sb1, 1, 2); grid->addWidget( text2, 2, 1, Qt::AlignRight); grid->addWidget( sb2, 2, 2); grid->addWidget(cancel, 3, 1); grid->addWidget( ok, 3, 2); dlg->setWindowTitle(tr("Set range")); dlg->setLayout(grid); if(dlg->exec() == QDialog::Accepted) { viewer->setMeanMin(sb1->value()); viewer->setMeanMax(sb2->value()); viewer->updateGL(); }
Tükröződési vonalak ●
Struktúrált fények visszaverődései
●
Intuitív, G1/G2 hiba
●
Folytonos, sima: G2
●
Folytonos, tört: G1
Fényvonalak (Isophotes) ● ●
●
Egyszerűbben számolható Referencia pontból sugarak
Beesési szög a normálvektorral: p ref −p α = cos n ∥p ref −p∥ −1
● ●
Megjelenítés: piros/fehér 5 fokonként Probléma: ● ●
Referencia pont változik → textúra változik Új textúrát generálni lassú
OpenGL trükk ●
Környezeti térkép (environment map) ● ●
Dinamikus textúra A felületi pont → textúra pont hozzárendelés a nézőponttól és a normálvektortól függ
●
Ötlet: referenciapont = nézőpont
●
OpenGL gömb leképezés →
Gömb leképezés ●
●
(x,y,z) a visszaverődés iránya [szem-koordinátarendszerben] Ehhez rendelt textúra ([0,1]x[0,1]-ben): s , t =
●
x 1 y 1 2 2 2 , , ahol m = 2 x y 1 z m 2 m 2
Lekódolja az összes irányt ●
●
●
3 koordináta, de egységvektor → két szabadságfok Ez a kódolás “látványos” (projekció) Könnyen rátehető panoráma is
Fényvonalak kiszámítása ●
Feladat: (s,t) textúrakoordináta → α szög
●
(x,y,z) egységvektor, tehát 8 x 2 y 2 2 2 z = 1− = 8 s − s t − t −3 2 m cos−1 z α= 2
● ●
és , mivel z épp a referenciairány Ez alapján kiszínezhető a textúra Képfájlként fogjuk beolvasni
A Qt resource rendszer ● ● ●
Fájlok tárolása a futtatható állományban Tipikusan képek, szövegek (HTML stb.) XML-jellegű leíró fájl (.qrc) ● ●
Ebből adat-cpp fájl generálódik Formátum:
isophotes.png
●
Projekt fájl: ●
●
RESOURCES = sample-framework.qrc
Qt-s fájlműveleteknél “:/” elérési út
OpenGL textúra készítés ●
Textúra ID ● ● ●
●
Paraméterek (glTexParameter[if]v?) ● ●
●
Külső pontra mit csináljon (pl. csempézés) Minőségjavító min/mag filterek stb.
Környezeti beállítások (glTexEnv[if]v?) ●
●
Generálás (n db.): glGenTextures(n, addr) Hozzárendelés: glBindTexture(type, id) Törlés (n db.): glDeleteTextures(n, addr)
Textúra, árnyalás és anyag (szín) kapcsolata
Textúra készítese ●
glTexImage2D(...sok paraméter...)
OpenGL textúra koordináták ● ●
glEnable(GL_TEXTURE_2D) Megadható pontonként ● ●
●
glTexCoord[1234][dfis]v? Az aktuális (bind-olt) textúrára vonatkozik
Generált koordináták ●
glTexGen[dfi]v? : generálás paraméterei –
GL_TEXTURE_GEN_MODE : mód beállítása ● ● ●
– ●
GL_OBJECT_LINEAR GL_EYE_LINEAR GL_SPHERE_MAP
GL_OBJECT_PLANE / GL_EYE_PLANE : sík megadása
glEnable(GL_TEXTURE_GEN_S) glEnable(GL_TEXTURE_GEN_T)
Textúra inicializálás ●
MyViewer.cpp: void MyViewer::init() { glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1); QImage img(":/isophotes.png"); glGenTextures(1, &isophote_texture); glBindTexture(GL_TEXTURE_2D, isophote_texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, img.width(), img.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, img.convertToFormat(QImage::Format_ARGB32).bits()); }
●
GL_LINEAR: (lineáris interpoláció) ● ●
●
Távoli felületek [MIN] Közeli felületek [MAG]
GL_CLAMP_TO_EDGE: ●
Kívül eső pontokat a szélére húzza
Textúra a háromszöghálón ●
MyViewer.cpp: void MyViewer::draw() { ... if(coloring == COLOR_PLAIN) glColor3dv(&color[0]); else if(coloring == COLOR_ISOPHOTES) { glBindTexture(GL_TEXTURE_2D, isophote_texture); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); glEnable(GL_TEXTURE_2D); glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); } for(ConstFaceIter i = mesh.faces_begin(), ie = mesh.faces_end(); i != ie; ++i) { ... // Drawing the triangles } if(coloring == COLOR_ISOPHOTES) { glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); glDisable(GL_TEXTURE_2D); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); } ... }