├── .gitignore ├── CMakeLists.txt ├── README.md ├── result └── result.jpg └── sources ├── CMakeLists.txt ├── arcballcontroller.cpp ├── arcballcontroller.h ├── data └── dragon.obj ├── main.cpp ├── maingui.cpp ├── maingui.h ├── openglviewer.cpp ├── openglviewer.h ├── settings.h.in ├── shaders ├── dipole.fs ├── dipole.gs ├── dipole.vs ├── gbuffers.fs ├── gbuffers.vs ├── render.fs └── render.vs └── tiny_obj_loader.h /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | settings.h 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0.0) 2 | project(FastTranslucentShader) 3 | 4 | # ----------------------------------------------------------------------------- 5 | # Required packages 6 | # ----------------------------------------------------------------------------- 7 | if (WIN32) 8 | set(QT5_ROOT "QT5-NOT_FOUND" CACHE PATH "") 9 | if (NOT EXISTS "${QT5_ROOT}/lib/cmake") 10 | message(FATAL_ERROR "[ERROR] Qt5 not found!!") 11 | endif() 12 | set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${QT5_ROOT}) 13 | endif() 14 | 15 | find_package(OpenCV REQUIRED) 16 | find_package(Qt5Widgets REQUIRED) 17 | find_package(Qt5OpenGL REQUIRED) 18 | find_package(Qt5Xml REQUIRED) 19 | find_package(OpenGL REQUIRED) 20 | 21 | # ----------------------------------------------------------------------------- 22 | # Process source directory 23 | # ----------------------------------------------------------------------------- 24 | add_subdirectory(sources) 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FastTranslucentShader 2 | === 3 | 4 | > Fast translucent object rendering with GLSL. This program is an implementation of the paper, "Real-time rendering of deformable heterogeneous translucent objects using multiresolution splatting" [1]. 5 | 6 | [[1] Chen et al., "Real-time rendering of deformable heterogeneous translucent objects using multiresolution splatting", The Visual Computer, vol. 28, No. 6, pp. 701-711, 2012.](http://link.springer.com/article/10.1007/s00371-012-0704-1) 7 | 8 | ## Build 9 | 10 | This program can be easily build with CMake v3.0.0 or higher. The dependencies of this project is listed below. 11 | 12 | * OpenCV 3.x 13 | * Qt 5.x 14 | 15 | After preparing above dependencies, you can build the progam by typing the following commands. 16 | 17 | ```shell 18 | $ git clone https://qithub.com/tatsy/FastTranslucentShader.git 19 | $ mkdir build 20 | $ cd build 21 | $ cmake [-D QT5_ROOT=(Your Qt folder) -D OpenCV_DIR=(Your OpenCV folder)] .. 22 | $ cmake --build . 23 | ``` 24 | 25 | If you prefer to use CMake GUI, please specify "QT5_ROOT" and "OpenCV_DIR" properties with your GUI. 26 | 27 | ## Screen shot 28 | 29 | Screen shot 30 | 31 | ## License 32 | 33 | MIT License 2016 (c) Tatsuya Yatagawa (tatsy). 34 | -------------------------------------------------------------------------------- /result/result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tatsy/FastTranslucentShader/f0acebd1939aebede289c1f89b249e014ff0c2da/result/result.jpg -------------------------------------------------------------------------------- /sources/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Build settings 2 | set(BUILD_TARGET "FastTranslucentShader") 3 | 4 | set(CMAKE_AUTOMOC ON) 5 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 6 | 7 | configure_file("${CMAKE_CURRENT_LIST_DIR}/settings.h.in" 8 | "${CMAKE_CURRENT_LIST_DIR}/settings.h" @ONLY) 9 | 10 | set(SOURCES main.cpp 11 | maingui.cpp maingui.h 12 | openglviewer.cpp openglviewer.h 13 | arcballcontroller.cpp arcballcontroller.h 14 | tiny_obj_loader.h settings.h) 15 | 16 | set(SHADERS shaders/render.vs shaders/render.fs 17 | shaders/gbuffers.vs shaders/gbuffers.fs) 18 | 19 | include_directories(${spica_INCLUDE_DIRS}) 20 | include_directories(${OpenCV_INCLIDE_DIRS}) 21 | add_executable(${BUILD_TARGET} ${SOURCES} ${SHADERS}) 22 | qt5_use_modules(${BUILD_TARGET} Widgets OpenGL) 23 | 24 | source_group("Source Files" FILES ${SOURCES}) 25 | source_group("Shader Files" FILES ${SHADERS}) 26 | 27 | if (MSVC) 28 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi") 29 | set_property(TARGET ${BUILD_TARGET} APPEND PROPERTY 30 | LINK_FLAGS "/DEBUG /PROFILE") 31 | endif() 32 | 33 | target_link_libraries(${BUILD_TARGET} ${QT_LIBRARIES}) 34 | target_link_libraries(${BUILD_TARGET} ${OPENGL_LIBRARIES}) 35 | target_link_libraries(${BUILD_TARGET} ${Boost_LIBRARIES}) 36 | target_link_libraries(${BUILD_TARGET} ${OpenCV_LIBS}) 37 | -------------------------------------------------------------------------------- /sources/arcballcontroller.cpp: -------------------------------------------------------------------------------- 1 | #include "arcballcontroller.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | static const double PI = 4.0 * std::atan(1.0); 8 | 9 | ArcballController::ArcballController(QWidget* parent) 10 | : parent_(parent) { 11 | } 12 | 13 | void ArcballController::initModelView(const QMatrix4x4 &mMat, const QMatrix4x4 &vMat) { 14 | rotMat_ = mMat; 15 | lookMat_ = vMat; 16 | update(); 17 | } 18 | 19 | void ArcballController::update() { 20 | switch (mode_) { 21 | case ArcballMode::Translate: 22 | updateTranslate(); 23 | break; 24 | 25 | case ArcballMode::Rotate: 26 | updateRotate(); 27 | break; 28 | 29 | case ArcballMode::Scale: 30 | updateScale(); 31 | break; 32 | 33 | default: 34 | break; 35 | } 36 | 37 | modelMat_ = rotMat_; 38 | modelMat_.scale(1.0 - scroll_ * 0.1); 39 | 40 | viewMat_ = lookMat_; 41 | viewMat_.translate(translate_); 42 | } 43 | 44 | void ArcballController::updateTranslate() { 45 | const QVector4D u(1.0f, 0.0f, 0.0f, 0.0f); 46 | const QVector4D v(0.0f, 1.0f, 0.0f, 0.0f); 47 | const QMatrix4x4 camera2objMat = lookMat_.inverted(); 48 | 49 | const QVector3D objspaceU = (camera2objMat * u).toVector3D().normalized(); 50 | const QVector3D objspaceV = (camera2objMat * v).toVector3D().normalized(); 51 | 52 | const double dx = 10.0 * (newPoint_.x() - oldPoint_.x()) / parent_->width(); 53 | const double dy = 10.0 * (newPoint_.y() - oldPoint_.y()) / parent_->height(); 54 | 55 | translate_ += (objspaceU * dx - objspaceV * dy); 56 | } 57 | 58 | void ArcballController::updateRotate() { 59 | const QVector3D u = getVector(newPoint_.x(), newPoint_.y()); 60 | const QVector3D v = getVector(oldPoint_.x(), oldPoint_.y()); 61 | 62 | const double angle = std::acos(std::min(1.0f, QVector3D::dotProduct(u, v))); 63 | 64 | const QVector3D rotAxis = QVector3D::crossProduct(v, u); 65 | const QMatrix4x4 camera2objMat = rotMat_.inverted(); 66 | 67 | const QVector3D objSpaceRotAxis = camera2objMat * rotAxis; 68 | 69 | QMatrix4x4 temp; 70 | double angleByDegree = 180.0 * angle / PI; 71 | temp.rotate(4.0 * angleByDegree, objSpaceRotAxis); 72 | 73 | rotMat_ = rotMat_ * temp; 74 | } 75 | 76 | void ArcballController::updateScale() { 77 | const double dy = 20.0 * (newPoint_.y() - oldPoint_.y()) / parent_->height(); 78 | scroll_ += dy; 79 | } 80 | 81 | QVector3D ArcballController::getVector(int x, int y) const { 82 | QVector3D pt( 2.0 * x / parent_->width() - 1.0, 83 | -2.0 * y / parent_->height() + 1.0, 84 | 0.0); 85 | 86 | const double xySquared = pt.x() * pt.x() + pt.y() * pt.y(); 87 | if (xySquared) { 88 | pt.setZ(std::sqrt(1.0 - xySquared)); 89 | } else { 90 | pt.normalized(); 91 | } 92 | 93 | return pt; 94 | } 95 | -------------------------------------------------------------------------------- /sources/arcballcontroller.h: -------------------------------------------------------------------------------- 1 | #ifndef _ARCBALL_CONTROLLER_H_ 2 | #define _ARCBALL_CONTROLLER_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | enum class ArcballMode : int { 9 | None = 0x00, 10 | Translate = 0x01, 11 | Rotate = 0x02, 12 | Scale = 0x04 13 | }; 14 | 15 | class ArcballController { 16 | public: 17 | // Public methods 18 | ArcballController(QWidget* parent); 19 | 20 | void initModelView(const QMatrix4x4& mMat, const QMatrix4x4& vMat); 21 | 22 | void update(); 23 | 24 | inline QMatrix4x4 modelMat() const { return modelMat_; } 25 | inline QMatrix4x4 viewMat() const { return viewMat_; } 26 | inline QMatrix4x4 modelViewMat() const { return viewMat_ * modelMat_; } 27 | 28 | inline double scroll() const { return scroll_; } 29 | 30 | inline void setMode(ArcballMode mode) { mode_ = mode; } 31 | inline void setOldPoint(const QPoint& pos) { oldPoint_ = pos; } 32 | inline void setNewPoint(const QPoint& pos) { newPoint_ = pos; } 33 | inline void setScroll(double scroll) { scroll_ = scroll; } 34 | 35 | private: 36 | // Private methods 37 | QVector3D getVector(int x, int y) const; 38 | void updateTranslate(); 39 | void updateRotate(); 40 | void updateScale(); 41 | 42 | // Private parameters 43 | QWidget* parent_; 44 | QMatrix4x4 modelMat_; 45 | QMatrix4x4 viewMat_; 46 | double scroll_ = 0.0; 47 | QPoint oldPoint_ = QPoint(0, 0); 48 | QPoint newPoint_ = QPoint(0, 0); 49 | 50 | ArcballMode mode_ = ArcballMode::None; 51 | QVector3D translate_ = QVector3D(0.0f, 0.0f, 0.0f); 52 | QMatrix4x4 lookMat_; 53 | QMatrix4x4 rotMat_; 54 | }; 55 | 56 | #endif // _ARCBALL_CONTROLLER_H_ 57 | -------------------------------------------------------------------------------- /sources/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "maingui.h" 5 | 6 | int main(int argc, char** argv) { 7 | QApplication app(argc, argv); 8 | 9 | QSurfaceFormat format = QSurfaceFormat::defaultFormat(); 10 | format.setVersion(3, 3); 11 | format.setProfile(QSurfaceFormat::CoreProfile); 12 | format.setOption(QSurfaceFormat::DeprecatedFunctions, false); 13 | QSurfaceFormat::setDefaultFormat(format); 14 | 15 | MainGui gui; 16 | gui.resize(1000, 600); 17 | gui.show(); 18 | 19 | return app.exec(); 20 | } -------------------------------------------------------------------------------- /sources/maingui.cpp: -------------------------------------------------------------------------------- 1 | #include "maingui.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class MainGui::Ui : public QWidget { 12 | public: 13 | Ui(QWidget* parent = nullptr) 14 | : QWidget{ parent } { 15 | layout = new QVBoxLayout(this); 16 | layout->setAlignment(Qt::AlignTop); 17 | setLayout(layout); 18 | 19 | mtrlGroup = new QGroupBox(this); 20 | mtrlGroup->setTitle("Materials"); 21 | 22 | groupLayout = new QVBoxLayout(this); 23 | mtrlGroup->setLayout(groupLayout); 24 | milkRadio = new QRadioButton(this); 25 | milkRadio->setText("Milk"); 26 | milkRadio->setChecked(true); 27 | skinRadio = new QRadioButton(this); 28 | skinRadio->setText("Skin"); 29 | 30 | groupLayout->addWidget(milkRadio); 31 | groupLayout->addWidget(skinRadio); 32 | layout->addWidget(mtrlGroup); 33 | 34 | scaleLabel = new QLabel("Scale", this); 35 | layout->addWidget(scaleLabel); 36 | scaleEdit = new QLineEdit(this); 37 | scaleEdit->setText("50.0"); 38 | layout->addWidget(scaleEdit); 39 | 40 | reflCheckBox = new QCheckBox("Reflection", this); 41 | reflCheckBox->setChecked(true); 42 | layout->addWidget(reflCheckBox); 43 | transCheckBox = new QCheckBox("Transmission", this); 44 | transCheckBox->setChecked(true); 45 | layout->addWidget(transCheckBox); 46 | } 47 | 48 | ~Ui() { 49 | delete milkRadio; 50 | delete skinRadio; 51 | delete groupLayout; 52 | delete scaleLabel; 53 | delete scaleEdit; 54 | delete mtrlGroup; 55 | delete reflCheckBox; 56 | delete transCheckBox; 57 | delete layout; 58 | } 59 | 60 | QRadioButton* milkRadio = nullptr; 61 | QRadioButton* skinRadio = nullptr; 62 | QGroupBox* mtrlGroup = nullptr; 63 | QVBoxLayout* groupLayout = nullptr; 64 | QLabel* scaleLabel = nullptr; 65 | QLineEdit* scaleEdit = nullptr; 66 | QCheckBox* reflCheckBox = nullptr; 67 | QCheckBox* transCheckBox = nullptr; 68 | QVBoxLayout* layout = nullptr; 69 | }; 70 | 71 | MainGui::MainGui(QWidget* parent) 72 | : QMainWindow{ parent } { 73 | setFont(QFont("Meiryo UI")); 74 | 75 | mainLayout = new QHBoxLayout(); 76 | mainWidget = new QWidget(this); 77 | mainWidget->setLayout(mainLayout); 78 | setCentralWidget(mainWidget); 79 | 80 | viewer = new OpenGLViewer(this); 81 | QSizePolicy viewPolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); 82 | viewPolicy.setHorizontalStretch(3); 83 | viewer->setSizePolicy(viewPolicy); 84 | mainLayout->addWidget(viewer); 85 | 86 | ui = new Ui(this); 87 | QSizePolicy uiPolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); 88 | uiPolicy.setHorizontalStretch(1); 89 | ui->setSizePolicy(uiPolicy); 90 | mainLayout->addWidget(ui); 91 | 92 | connect(ui->milkRadio, SIGNAL(toggled(bool)), this, SLOT(OnRadioToggled(bool))); 93 | connect(ui->skinRadio, SIGNAL(toggled(bool)), this, SLOT(OnRadioToggled(bool))); 94 | connect(ui->scaleEdit, SIGNAL(editingFinished()), this, SLOT(OnScaleChanged())); 95 | 96 | connect(ui->reflCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnCheckStateChanged(int))); 97 | connect(ui->transCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnCheckStateChanged(int))); 98 | connect(viewer, SIGNAL(frameSwapped()), this, SLOT(OnFrameSwapped())); 99 | } 100 | 101 | MainGui::~MainGui() { 102 | delete viewer; 103 | delete ui; 104 | delete mainLayout; 105 | delete mainWidget; 106 | } 107 | 108 | void MainGui::OnRadioToggled(bool state) { 109 | if (ui->milkRadio->isChecked()) { 110 | viewer->setMaterial("Milk"); 111 | } else if (ui->skinRadio->isChecked()) { 112 | viewer->setMaterial("Skin"); 113 | } 114 | } 115 | 116 | void MainGui::OnScaleChanged() { 117 | viewer->setMaterialScale(ui->scaleEdit->text().toDouble()); 118 | } 119 | 120 | void MainGui::OnCheckStateChanged(int state) { 121 | bool isRefl = ui->reflCheckBox->isChecked(); 122 | bool isTrans = ui->transCheckBox->isChecked(); 123 | viewer->setRenderComponents(isRefl, isTrans); 124 | } 125 | 126 | void MainGui::OnFrameSwapped() { 127 | static bool isStarted = false; 128 | static QElapsedTimer timer; 129 | static long long lastTime; 130 | if (!isStarted) { 131 | isStarted = true; 132 | timer.start(); 133 | lastTime = timer.elapsed(); 134 | } else { 135 | long long currentTime = timer.elapsed(); 136 | double fps = 1000.0 / (currentTime - lastTime); 137 | setWindowTitle(QString("FPS: %1").arg(QString::number(fps, 'f', 2))); 138 | lastTime = currentTime; 139 | } 140 | } -------------------------------------------------------------------------------- /sources/maingui.h: -------------------------------------------------------------------------------- 1 | #ifdef _MSC_VER 2 | #pragma once 3 | #endif 4 | 5 | #ifndef _MAIN_GUI_H_ 6 | #define _MAIN_GUI_H_ 7 | 8 | #include 9 | #include 10 | 11 | #include "openglviewer.h" 12 | 13 | class MainGui : public QMainWindow { 14 | Q_OBJECT 15 | 16 | public: 17 | explicit MainGui(QWidget* parent = nullptr); 18 | virtual ~MainGui(); 19 | 20 | private slots: 21 | void OnRadioToggled(bool); 22 | void OnScaleChanged(); 23 | void OnCheckStateChanged(int); 24 | void OnFrameSwapped(); 25 | 26 | private: 27 | QHBoxLayout* mainLayout = nullptr; 28 | QWidget* mainWidget = nullptr; 29 | OpenGLViewer* viewer = nullptr; 30 | 31 | class Ui; 32 | Ui* ui = nullptr; 33 | }; 34 | 35 | #endif // _MAIN_GUI_H_ 36 | -------------------------------------------------------------------------------- /sources/openglviewer.cpp: -------------------------------------------------------------------------------- 1 | #include "openglviewer.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #define TINYOBJLOADER_IMPLEMENTATION 16 | #include "tiny_obj_loader.h" 17 | 18 | #include "settings.h" 19 | 20 | // Please activate folloring line to save intermediate results. 21 | //#define DEBUG_MODE 1 22 | 23 | static constexpr int SHADER_POSITION_LOC = 0; 24 | static constexpr int SHADER_NORMAL_LOC = 1; 25 | static constexpr int SHADER_TEXCOORD_LOC = 2; 26 | 27 | static constexpr int SAMPLE_POSITION_LOC = 0; 28 | static constexpr int SAMPLE_NORMAL_LOC = 1; 29 | static constexpr int SAMPLE_TEXCOORD_LOC = 2; 30 | static constexpr int SAMPLE_RADIUS_LOC = 3; 31 | 32 | static const QVector3D lightPos = QVector3D(-3.0f, 4.0f, 5.0f); 33 | 34 | static QVector3D sigma_a = QVector3D(0.0015333, 0.0046, 0.019933); 35 | static QVector3D sigmap_s = QVector3D(4.5513 , 5.8294, 7.136 ); 36 | static float eta = 1.3f; 37 | static float mtrlScale = 50.0f; 38 | static bool isRenderRefl = true; 39 | static bool isRenderTrans = true; 40 | 41 | struct Sample { 42 | QVector3D position; 43 | QVector3D normal; 44 | QVector2D texcoord; 45 | float radius; 46 | }; 47 | 48 | namespace { 49 | 50 | void takeFloatImage(QOpenGLFramebufferObject& fbo, cv::Mat* image, int channels, int attachmentIndex) { 51 | const int width = fbo.width(); 52 | const int height = fbo.height(); 53 | 54 | fbo.bind(); 55 | std::vector pixels(width * height * 4); 56 | glReadBuffer(GL_COLOR_ATTACHMENT0 + attachmentIndex); 57 | glReadPixels(0, 0, width, height, GL_RGBA, GL_FLOAT, &pixels[0]); 58 | 59 | *image = cv::Mat(width, height, CV_MAKETYPE(CV_32F, channels)); 60 | for (int y = 0; y < height; y++) { 61 | for (int x = 0; x < width; x++) { 62 | for (int ch = 0; ch < channels; ch++) { 63 | image->at(height - y - 1, x * channels + ch) = pixels[(y * width + x) * 4 + ch]; 64 | } 65 | } 66 | } 67 | fbo.release(); 68 | } 69 | 70 | void saveFloatImage(const std::string& filename, cv::InputArray image) { 71 | cv::Mat img = image.getMat(); 72 | cv::Mat img8u; 73 | img.convertTo(img8u, CV_MAKETYPE(CV_8U, img.channels()), 255.0); 74 | cv::imwrite(filename, img8u); 75 | } 76 | 77 | template 78 | void pyrDown(cv::InputArray lower, cv::OutputArray upper, const std::function& f) { 79 | cv::Mat low = lower.getMat(); 80 | cv::Mat& up = upper.getMatRef(); 81 | 82 | const int width = low.cols; 83 | const int height = low.rows; 84 | up = cv::Mat(height / 2, width / 2, CV_MAKETYPE(low.depth(), low.channels())); 85 | 86 | for (int y = 0; y < height / 2; y++) { 87 | for (int x = 0; x < width / 2; x++) { 88 | T& t0 = low.at(y * 2, x * 2); 89 | T& t1 = low.at(y * 2, x * 2 + 1); 90 | T& t2 = low.at(y * 2 + 1, x * 2); 91 | T& t3 = low.at(y * 2 + 1, x * 2 + 1); 92 | up.at(y, x) = f(t0, t1, t2, t3); 93 | } 94 | } 95 | } 96 | 97 | } // anonymous namespace 98 | 99 | OpenGLViewer::OpenGLViewer(QWidget* parent) 100 | : QOpenGLWidget{ parent} { 101 | arcball = std::make_unique(this); 102 | 103 | QMatrix4x4 mMat, vMat; 104 | mMat.scale(5.0f); 105 | vMat.lookAt(QVector3D(0.0f, 0.0f, 3.0f), QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0f, 0.1f, 0.0f)); 106 | arcball->initModelView(mMat,vMat); 107 | 108 | timer = std::make_unique(this); 109 | connect(timer.get(), SIGNAL(timeout()), this, SLOT(OnAnimate())); 110 | 111 | timer->start(0); 112 | } 113 | 114 | OpenGLViewer::~OpenGLViewer() { 115 | } 116 | 117 | void OpenGLViewer::setMaterial(const std::string& mtrlName) { 118 | if (mtrlName == "Milk") { 119 | sigma_a = QVector3D(0.0015333, 0.0046, 0.019933); 120 | sigmap_s = QVector3D(4.5513 , 5.8294, 7.136 ); 121 | eta = 1.3f; 122 | } else if (mtrlName == "Skin") { 123 | sigma_a = QVector3D(0.061, 0.97, 1.45); 124 | sigmap_s = QVector3D(0.18, 0.07, 0.03); 125 | eta = 1.3f; 126 | } 127 | } 128 | 129 | void OpenGLViewer::setMaterialScale(double scale) { 130 | mtrlScale = static_cast(scale); 131 | } 132 | 133 | void OpenGLViewer::setRenderComponents(bool isRef, bool isTrans) { 134 | isRenderRefl = isRef; 135 | isRenderTrans = isTrans; 136 | } 137 | 138 | void OpenGLViewer::initializeGL() { 139 | glClearColor(0.0f, 0.0f, 0.0f, 1.0f); 140 | glEnable(GL_DEPTH_TEST); 141 | glEnable(GL_CULL_FACE); 142 | glCullFace(GL_BACK); 143 | 144 | // Load .obj file. 145 | std::string filename = std::string(DATA_DIRECTORY) + "dragon.obj"; 146 | std::vector shapes; 147 | std::vector materials; 148 | std::string err; 149 | if (!tinyobj::LoadObj(shapes, materials, err, filename.c_str()), tinyobj::load_flags_t::triangulation) { 150 | if (!err.empty()) { 151 | std::cerr << err << std::endl; 152 | } 153 | } 154 | const int nVerts = shapes[0].mesh.positions.size() / 3; 155 | 156 | // Initialize VAO. 157 | vao = std::make_unique(this); 158 | vao->create(); 159 | vao->bind(); 160 | 161 | vBuffer = std::make_unique(QOpenGLBuffer::VertexBuffer); 162 | vBuffer->create(); 163 | vBuffer->setUsagePattern(QOpenGLBuffer::StaticDraw); 164 | vBuffer->bind(); 165 | vBuffer->allocate(sizeof(float) * (3 + 3 + 2) * nVerts); 166 | vBuffer->write(0, &shapes[0].mesh.positions[0], sizeof(float) * 3 * nVerts); 167 | vBuffer->write(sizeof(float) * 3 * nVerts, &shapes[0].mesh.normals[0], sizeof(float) * 3 * nVerts); 168 | vBuffer->write(sizeof(float) * 6 * nVerts, &shapes[0].mesh.texcoords[0], sizeof(float) * 2 * nVerts); 169 | 170 | auto f = QOpenGLContext::currentContext()->extraFunctions(); 171 | f->glEnableVertexAttribArray(SHADER_POSITION_LOC); 172 | f->glEnableVertexAttribArray(SHADER_NORMAL_LOC); 173 | f->glEnableVertexAttribArray(SHADER_TEXCOORD_LOC); 174 | f->glVertexAttribPointer(SHADER_POSITION_LOC, 3, GL_FLOAT, GL_FALSE, 0, (void*)0); 175 | f->glVertexAttribPointer(SHADER_NORMAL_LOC, 3, GL_FLOAT, GL_FALSE, 0, (void*)(sizeof(float) * 3 * nVerts)); 176 | f->glVertexAttribPointer(SHADER_TEXCOORD_LOC, 2, GL_FLOAT, GL_FALSE, 0, (void*)(sizeof(float) * 6 * nVerts)); 177 | 178 | iBuffer = std::make_unique(QOpenGLBuffer::IndexBuffer); 179 | iBuffer->create(); 180 | iBuffer->setUsagePattern(QOpenGLBuffer::StaticDraw); 181 | iBuffer->bind(); 182 | iBuffer->allocate(&shapes[0].mesh.indices[0], sizeof(unsigned int) * shapes[0].mesh.indices.size()); 183 | 184 | vao->release(); 185 | 186 | // Initialize texture. 187 | QImage texImage; 188 | texImage.load(QString(DATA_DIRECTORY) + "wood.jpg"); 189 | texture = std::make_unique(texImage, QOpenGLTexture::MipMapGeneration::GenerateMipMaps); 190 | texture->setMinificationFilter(QOpenGLTexture::Filter::Linear); 191 | texture->setMagnificationFilter(QOpenGLTexture::Filter::Linear); 192 | texture->setWrapMode(QOpenGLTexture::CoordinateDirection::DirectionS, QOpenGLTexture::WrapMode::ClampToEdge); 193 | texture->setWrapMode(QOpenGLTexture::CoordinateDirection::DirectionT, QOpenGLTexture::WrapMode::ClampToEdge); 194 | 195 | // Initialize shaders. 196 | shader = std::make_unique(this); 197 | shader->addShaderFromSourceFile(QOpenGLShader::Vertex, QString(SHADER_DIRECTORY) + "render.vs"); 198 | shader->addShaderFromSourceFile(QOpenGLShader::Fragment, QString(SHADER_DIRECTORY) + "render.fs"); 199 | shader->link(); 200 | if (!shader->isLinked()) { 201 | std::cerr << "Failed to link shader files!!" << std::endl; 202 | std::exit(1); 203 | } 204 | 205 | dipoleShader = std::make_unique(this); 206 | dipoleShader->addShaderFromSourceFile(QOpenGLShader::Vertex, QString(SHADER_DIRECTORY) + "dipole.vs"); 207 | dipoleShader->addShaderFromSourceFile(QOpenGLShader::Geometry, QString(SHADER_DIRECTORY) + "dipole.gs"); 208 | dipoleShader->addShaderFromSourceFile(QOpenGLShader::Fragment, QString(SHADER_DIRECTORY) + "dipole.fs"); 209 | dipoleShader->link(); 210 | if (!dipoleShader->isLinked()) { 211 | std::cerr << "Failed to link shader files!!" << std::endl; 212 | std::exit(1); 213 | } 214 | 215 | gbufShader = std::make_unique(this); 216 | gbufShader->addShaderFromSourceFile(QOpenGLShader::Vertex, QString(SHADER_DIRECTORY) + "gbuffers.vs"); 217 | gbufShader->addShaderFromSourceFile(QOpenGLShader::Fragment, QString(SHADER_DIRECTORY) + "gbuffers.fs"); 218 | gbufShader->link(); 219 | if (!gbufShader->isLinked()) { 220 | std::cerr << "Failed to link shader files!!" << std::endl; 221 | std::exit(1); 222 | } 223 | 224 | // Compute hierarchical irradiance samples. 225 | calcGBuffers(); 226 | } 227 | 228 | void OpenGLViewer::resizeGL(int width, int height) { 229 | glViewport(0, 0, this->width(), this->height()); 230 | 231 | // FBOs for G-buffers. 232 | deferFbo = std::make_unique(this->width(), this->height(), 233 | QOpenGLFramebufferObject::Attachment::Depth, GL_TEXTURE_2D, GL_RGBA32F); 234 | deferFbo->addColorAttachment(this->width(), this->height(), GL_RGBA32F); 235 | deferFbo->addColorAttachment(this->width(), this->height(), GL_RGBA32F); 236 | deferFbo->addColorAttachment(this->width(), this->height(), GL_RGBA32F); 237 | 238 | // FBO for translucent component. 239 | dipoleFbo = std::make_unique(this->width(), this->height(), 240 | QOpenGLFramebufferObject::Attachment::Depth, GL_TEXTURE_2D, GL_RGBA32F); 241 | } 242 | 243 | void OpenGLViewer::paintGL() { 244 | QMatrix4x4 mMat, vMat, pMat; 245 | mMat = arcball->modelMat(); 246 | vMat = arcball->viewMat(); 247 | pMat.perspective(45.0f, (float)width() / height(), 1.0f, 1000.0f); 248 | QMatrix4x4 mvMat = vMat * mMat; 249 | QMatrix4x4 mvpMat = pMat * mvMat; 250 | 251 | // Compute deferred shading buffers. 252 | gbufShader->bind(); 253 | deferFbo->bind(); 254 | vao->bind(); 255 | 256 | gbufShader->setUniformValue("uMVPMat", mvpMat); 257 | gbufShader->setUniformValue("isMaxDepth", 0); 258 | 259 | auto f = QOpenGLContext::currentContext()->extraFunctions(); 260 | GLenum bufs[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3 }; 261 | f->glDrawBuffers(4, bufs); 262 | 263 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 264 | 265 | glDrawElements(GL_TRIANGLES, iBuffer->size() / sizeof(unsigned int), GL_UNSIGNED_INT, 0); 266 | 267 | gbufShader->release(); 268 | deferFbo->release(); 269 | vao->release(); 270 | 271 | #if DEBUG_MODE 272 | deferFbo->toImage(true, 1).save(QString(OUTPUT_DIRECTORY) + "position.png"); 273 | deferFbo->toImage(true, 2).save(QString(OUTPUT_DIRECTORY) + "normal.png"); 274 | deferFbo->toImage(true, 3).save(QString(OUTPUT_DIRECTORY) + "texcoord.png"); 275 | #endif 276 | 277 | // Translucent part. 278 | dipoleShader->bind(); 279 | dipoleFbo->bind(); 280 | sampleVAO->bind(); 281 | 282 | f->glActiveTexture(GL_TEXTURE0); 283 | glBindTexture(GL_TEXTURE_2D, deferFbo->textures()[1]); 284 | f->glActiveTexture(GL_TEXTURE1); 285 | glBindTexture(GL_TEXTURE_2D, deferFbo->textures()[2]); 286 | f->glActiveTexture(GL_TEXTURE2); 287 | glBindTexture(GL_TEXTURE_2D, deferFbo->textures()[3]); 288 | 289 | dipoleShader->setUniformValue("uPositionMap", 0); 290 | dipoleShader->setUniformValue("uNormalMap", 1); 291 | dipoleShader->setUniformValue("uTexCoordMap", 2); 292 | 293 | dipoleShader->setUniformValue("uMVPMat", mvpMat); 294 | dipoleShader->setUniformValue("uMVMat", mvMat); 295 | dipoleShader->setUniformValue("uLightPos", lightPos); 296 | 297 | dipoleShader->setUniformValue("sigma_a", sigma_a * mtrlScale); 298 | dipoleShader->setUniformValue("sigmap_s", sigmap_s * mtrlScale); 299 | dipoleShader->setUniformValue("eta", eta); 300 | 301 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 302 | 303 | glDisable(GL_DEPTH_TEST); 304 | glEnable(GL_BLEND); 305 | glBlendFunc(GL_ONE, GL_ONE); 306 | glDrawElements(GL_POINTS, sampleIBuf->size() / sizeof(unsigned int), GL_UNSIGNED_INT, 0); 307 | glDisable(GL_BLEND); 308 | glBlendFunc(GL_ONE, GL_ZERO); 309 | glEnable(GL_DEPTH_TEST); 310 | 311 | dipoleShader->release(); 312 | dipoleFbo->release(); 313 | sampleVAO->release(); 314 | 315 | #if DEBUG_MODE 316 | dipoleFbo->toImage().save(QString(OUTPUT_DIRECTORY) + "dipole.png"); 317 | #endif 318 | 319 | // Main rendering. 320 | shader->bind(); 321 | vao->bind(); 322 | 323 | f->glActiveTexture(GL_TEXTURE0); 324 | glBindTexture(GL_TEXTURE_2D, dipoleFbo->texture()); 325 | shader->setUniformValue("uTransMap", 0); 326 | 327 | shader->setUniformValue("uMVPMat", mvpMat); 328 | shader->setUniformValue("uMVMat", mvMat); 329 | shader->setUniformValue("uLightPos", lightPos); 330 | 331 | shader->setUniformValue("refFactor", isRenderRefl ? 1.0f : 0.0f); 332 | shader->setUniformValue("transFactor", isRenderTrans ? 1.0f : 0.0f); 333 | 334 | glDrawElements(GL_TRIANGLES, iBuffer->size() / sizeof(unsigned int), GL_UNSIGNED_INT, 0); 335 | 336 | shader->release(); 337 | vao->release(); 338 | } 339 | 340 | void OpenGLViewer::calcGBuffers() { 341 | static const int bufSize = 1024; 342 | if (!gbufFbo) { 343 | gbufFbo = std::make_unique(bufSize, bufSize, 344 | QOpenGLFramebufferObject::Attachment::Depth, GL_TEXTURE_2D, GL_RGBA32F); 345 | gbufFbo->addColorAttachment(bufSize, bufSize, GL_RGBA32F); 346 | gbufFbo->addColorAttachment(bufSize, bufSize, GL_RGBA32F); 347 | gbufFbo->addColorAttachment(bufSize, bufSize, GL_RGBA32F); 348 | } 349 | 350 | // Compute G-buffers from the light source. In the following part, 351 | // G-buffers except for "Maximum depth" are computed. 352 | cv::Mat minDepthImage, posImage, normalImage, texcoordImage; 353 | { 354 | glViewport(0, 0, bufSize, bufSize); 355 | 356 | gbufShader->bind(); 357 | gbufFbo->bind(); 358 | vao->bind(); 359 | 360 | QMatrix4x4 pMat, vMat, mMat; 361 | pMat.perspective(45.0f, 1.0f, 0.1f, 100.0f); 362 | vMat.lookAt(lightPos, QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0f, 1.0f, 0.0f)); 363 | mMat.scale(7.0f); 364 | 365 | QMatrix4x4 mvpMat = pMat * vMat * mMat; 366 | gbufShader->setUniformValue("uMVPMat", mvpMat); 367 | gbufShader->setUniformValue("isMaxDepth", 0); 368 | 369 | auto f = QOpenGLContext::currentContext()->extraFunctions(); 370 | GLenum bufs[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, 371 | GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3 }; 372 | f->glDrawBuffers(4, bufs); 373 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 374 | 375 | float white[] = { 1.0f, 1.0f, 1.0f, 1.0f }; 376 | f->glClearBufferfv(GL_COLOR, 0, white); 377 | 378 | glDrawElements(GL_TRIANGLES, iBuffer->size() / sizeof(unsigned int), GL_UNSIGNED_INT, 0); 379 | 380 | gbufShader->release(); 381 | gbufFbo->release(); 382 | vao->release(); 383 | 384 | takeFloatImage(*gbufFbo.get(), &minDepthImage, 1, 0); 385 | takeFloatImage(*gbufFbo.get(), &posImage, 3, 1); 386 | takeFloatImage(*gbufFbo.get(), &normalImage, 3, 2); 387 | takeFloatImage(*gbufFbo.get(), &texcoordImage, 3, 3); 388 | } 389 | 390 | // Compute the maximum depth image from the light source. 391 | cv::Mat maxDepthImage; 392 | { 393 | gbufShader->bind(); 394 | gbufFbo->bind(); 395 | vao->bind(); 396 | 397 | gbufShader->setUniformValue("isMaxDepth", 1); 398 | 399 | auto f = QOpenGLContext::currentContext()->extraFunctions(); 400 | GLenum bufs[] = { GL_COLOR_ATTACHMENT0 }; 401 | 402 | f->glDrawBuffers(1, bufs); 403 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 404 | 405 | float white[] = { 1.0f, 1.0f, 1.0f, 1.0f }; 406 | f->glClearBufferfv(GL_COLOR, 0, white); 407 | 408 | glDrawElements(GL_TRIANGLES, iBuffer->size() / sizeof(unsigned int), GL_UNSIGNED_INT, 0); 409 | 410 | gbufShader->release(); 411 | gbufFbo->release(); 412 | vao->release(); 413 | 414 | takeFloatImage(*gbufFbo.get(), &maxDepthImage, 1, 0); 415 | } 416 | 417 | // Revert viewport. 418 | glViewport(0, 0, width(), height()); 419 | 420 | #if DEBUG_MODE 421 | saveFloatImage(std::string(OUTPUT_DIRECTORY) + "gbuf_mindepth.png", depthImage); 422 | saveFloatImage(std::string(OUTPUT_DIRECTORY) + "gbuf_maxdepth.png", maxDepthImage); 423 | saveFloatImage(std::string(OUTPUT_DIRECTORY) + "gbuf_position.png", posImage); 424 | saveFloatImage(std::string(OUTPUT_DIRECTORY) + "gbuf_normal.png", normalImage); 425 | saveFloatImage(std::string(OUTPUT_DIRECTORY) + "gbuf_texcoord.png", texcoordImage); 426 | #endif 427 | 428 | // Compute sample point hierarchy. 429 | static const int maxPyrLevels = 3; 430 | 431 | std::vector minDepthPyr(maxPyrLevels); 432 | std::vector maxDepthPyr(maxPyrLevels); 433 | std::vector positionPyr(maxPyrLevels); 434 | std::vector normalPyr(maxPyrLevels); 435 | std::vector texCoordPyr(maxPyrLevels); 436 | 437 | minDepthPyr[maxPyrLevels - 1] = minDepthImage; 438 | maxDepthPyr[maxPyrLevels - 1] = maxDepthImage; 439 | positionPyr[maxPyrLevels - 1] = posImage; 440 | normalPyr[maxPyrLevels - 1] = normalImage; 441 | texCoordPyr[maxPyrLevels - 1] = texcoordImage; 442 | 443 | std::function fTakeMin = 444 | [&](float f1, float f2, float f3, float f4) { 445 | return std::min(std::min(f1, f2), std::min(f3, f4)); 446 | }; 447 | std::function fTakeMax = 448 | [&](float f1, float f2, float f3, float f4) { 449 | return std::max(std::max(f1, f2), std::max(f3, f4)); 450 | }; 451 | std::function fTakeAvg = 452 | [&](cv::Vec3f v1, cv::Vec3f v2, cv::Vec3f v3, cv::Vec3f v4) { 453 | return (v1 + v2 + v3 + v4) / 4.0f; 454 | }; 455 | 456 | for (int i = maxPyrLevels - 1; i >= 1; i--) { 457 | pyrDown(minDepthPyr[i], minDepthPyr[i - 1], fTakeMin); 458 | pyrDown(maxDepthPyr[i], maxDepthPyr[i - 1], fTakeMax); 459 | pyrDown(positionPyr[i], positionPyr[i - 1], fTakeAvg); 460 | pyrDown(normalPyr[i], normalPyr[i - 1], fTakeAvg); 461 | pyrDown(texCoordPyr[i], texCoordPyr[i - 1], fTakeAvg); 462 | } 463 | 464 | static const double alpha = 30.0; 465 | static const double Rw = 1.0; 466 | static const double RPx = 0.1; 467 | static const double z0 = 0.03; 468 | double T = 256.0; 469 | 470 | std::vector samplePyr(maxPyrLevels); 471 | for (int l = 0; l < maxPyrLevels; l++) { 472 | const int subRows = minDepthPyr[l].rows; 473 | const int subCols = minDepthPyr[l].cols; 474 | samplePyr[l] = cv::Mat(subRows, subCols, CV_8UC1, cv::Scalar(0, 0, 0)); 475 | for (int i = 0; i < subRows; i++) { 476 | for (int j = 0; j < subCols; j++) { 477 | float depthGap = (maxDepthPyr[l].at(i, j) - minDepthPyr[l].at(i, j)) * 10.0f; 478 | 479 | cv::Vec3f pos = positionPyr[l].at(i, j); 480 | QVector3D L = QVector3D(lightPos.x() - pos[0], lightPos.y() - pos[1], lightPos.z() - pos[2]).normalized(); 481 | cv::Vec3f nrm = normalPyr[l].at(i, j); 482 | QVector3D N = QVector3D(nrm[0], nrm[1], nrm[2]); 483 | 484 | double Mx = alpha * Rw / (RPx * std::abs(QVector3D::dotProduct(N, L))); 485 | 486 | if (depthGap < z0 && T > Mx) { 487 | samplePyr[l].at(i, j) = 255; 488 | } else { 489 | samplePyr[l].at(i, j) = 0; 490 | } 491 | } 492 | } 493 | T *= 2.0; 494 | } 495 | 496 | for (int l = maxPyrLevels - 1; l >= 1; l--) { 497 | for (int y = 0; y < samplePyr[l].rows; y++) { 498 | for (int x = 0; x < samplePyr[l].cols; x++) { 499 | if (samplePyr[l - 1].at(y / 2, x / 2) != 0) { 500 | samplePyr[l].at(y, x) = 0; 501 | } 502 | } 503 | } 504 | } 505 | 506 | #if DEBUG_MODE 507 | cv::imwrite(std::string(OUTPUT_DIRECTORY) + "sample0.png", samplePyr[0]); 508 | cv::imwrite(std::string(OUTPUT_DIRECTORY) + "sample1.png", samplePyr[1]); 509 | cv::imwrite(std::string(OUTPUT_DIRECTORY) + "sample2.png", samplePyr[2]); 510 | #endif 511 | 512 | std::vector samples; 513 | std::vector sampleIds; 514 | for (int l = 0; l < maxPyrLevels; l++) { 515 | for (int y = 0; y < samplePyr[l].rows; y++) { 516 | for (int x = 0; x < samplePyr[l].cols; x++) { 517 | if (samplePyr[l].at(y, x) != 0) { 518 | Sample samp; 519 | 520 | cv::Vec3f pos = positionPyr[l].at(y, x); 521 | samp.position = QVector3D(pos[0], pos[1], pos[2]); 522 | cv::Vec3f nrm = normalPyr[l].at(y, x); 523 | samp.normal = QVector3D(nrm[0], nrm[1], nrm[2]); 524 | cv::Vec3f crd = texCoordPyr[l].at(y, x); 525 | samp.texcoord = QVector2D(crd[0], crd[1]); 526 | samp.radius = std::pow(0.5, l); 527 | samples.push_back(samp); 528 | sampleIds.push_back(sampleIds.size()); 529 | } 530 | } 531 | } 532 | } 533 | 534 | #if DEBUG_MODE 535 | std::ofstream ofs((std::string(SLF_OUTPUT_DIRECTORY) + "samples.obj").c_str(), std::ios::out); 536 | for (const auto& s : samples) { 537 | ofs << "v "; 538 | ofs << s.position.x() << " "; 539 | ofs << s.position.y() << " "; 540 | ofs << s.position.z() << std::endl; 541 | } 542 | ofs.close(); 543 | #endif 544 | 545 | // Prepare sample VAO. 546 | sampleVAO = std::make_unique(this); 547 | sampleVAO->create(); 548 | sampleVAO->bind(); 549 | 550 | sampleVBuf = std::make_unique(QOpenGLBuffer::VertexBuffer); 551 | sampleVBuf->create(); 552 | sampleVBuf->setUsagePattern(QOpenGLBuffer::StaticDraw); 553 | sampleVBuf->bind(); 554 | sampleVBuf->allocate(&samples[0], sizeof(Sample) * samples.size()); 555 | 556 | auto f = QOpenGLContext::currentContext()->extraFunctions(); 557 | f->glEnableVertexAttribArray(SAMPLE_POSITION_LOC); 558 | f->glEnableVertexAttribArray(SAMPLE_NORMAL_LOC); 559 | f->glEnableVertexAttribArray(SAMPLE_TEXCOORD_LOC); 560 | f->glEnableVertexAttribArray(SAMPLE_RADIUS_LOC); 561 | f->glVertexAttribPointer(SAMPLE_POSITION_LOC, 3, GL_FLOAT, GL_FALSE, sizeof(Sample), (void*)0); 562 | f->glVertexAttribPointer(SAMPLE_NORMAL_LOC, 3, GL_FLOAT, GL_FALSE, sizeof(Sample), (void*)(sizeof(float) * 3)); 563 | f->glVertexAttribPointer(SAMPLE_TEXCOORD_LOC, 2, GL_FLOAT, GL_FALSE, sizeof(Sample), (void*)(sizeof(float) * 6)); 564 | f->glVertexAttribPointer(SAMPLE_RADIUS_LOC, 1, GL_FLOAT, GL_FALSE, sizeof(Sample), (void*)(sizeof(float) * 8)); 565 | 566 | sampleIBuf = std::make_unique(QOpenGLBuffer::IndexBuffer); 567 | sampleIBuf->create(); 568 | sampleIBuf->setUsagePattern(QOpenGLBuffer::StaticDraw); 569 | sampleIBuf->bind(); 570 | sampleIBuf->allocate(&sampleIds[0], sampleIds.size() * sizeof(unsigned int)); 571 | 572 | sampleVAO->release(); 573 | } 574 | 575 | void OpenGLViewer::mousePressEvent(QMouseEvent* ev) { 576 | // Arcball 577 | arcball->setOldPoint(ev->pos()); 578 | arcball->setNewPoint(ev->pos()); 579 | if (ev->button() == Qt::LeftButton) { 580 | arcball->setMode(ArcballMode::Rotate); 581 | } else if (ev->button() == Qt::RightButton) { 582 | arcball->setMode(ArcballMode::Translate); 583 | } else if (ev->button() == Qt::MiddleButton) { 584 | arcball->setMode(ArcballMode::Scale); 585 | } 586 | } 587 | 588 | void OpenGLViewer::mouseMoveEvent(QMouseEvent* ev) { 589 | // Arcball 590 | arcball->setNewPoint(ev->pos()); 591 | arcball->update(); 592 | arcball->setOldPoint(ev->pos()); 593 | } 594 | 595 | void OpenGLViewer::mouseReleaseEvent(QMouseEvent* ev) { 596 | // Arcball 597 | arcball->setMode(ArcballMode::None); 598 | } 599 | 600 | 601 | void OpenGLViewer::wheelEvent(QWheelEvent* ev) { 602 | arcball->setScroll(arcball->scroll() + ev->delta() / 1000.0); 603 | arcball->update(); 604 | } 605 | 606 | void OpenGLViewer::OnAnimate() { 607 | update(); 608 | } 609 | -------------------------------------------------------------------------------- /sources/openglviewer.h: -------------------------------------------------------------------------------- 1 | #ifdef _MSC_VER 2 | #pragma once 3 | #endif 4 | 5 | #ifndef _OPENGL_VIEWER_H_ 6 | #define _OPENGL_VIEWER_H_ 7 | 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "arcballcontroller.h" 24 | 25 | class OpenGLViewer : public QOpenGLWidget { 26 | Q_OBJECT 27 | public: 28 | explicit OpenGLViewer(QWidget* parent = nullptr); 29 | virtual ~OpenGLViewer(); 30 | 31 | void setMaterial(const std::string& mtrlName); 32 | void setMaterialScale(double scale); 33 | void setRenderComponents(bool isRefl, bool isTrans); 34 | 35 | protected: 36 | void initializeGL() override; 37 | void paintGL() override; 38 | void resizeGL(int width, int height) override; 39 | 40 | void mousePressEvent(QMouseEvent* ev) override; 41 | void mouseMoveEvent(QMouseEvent* ev) override; 42 | void mouseReleaseEvent(QMouseEvent* ev) override; 43 | void wheelEvent(QWheelEvent* ev) override; 44 | 45 | private slots: 46 | void OnAnimate(); 47 | 48 | private: 49 | void calcGBuffers(); 50 | 51 | std::unique_ptr shader = nullptr; 52 | std::unique_ptr dipoleShader = nullptr; 53 | std::unique_ptr gbufShader = nullptr; 54 | 55 | std::unique_ptr vao = nullptr; 56 | std::unique_ptr vBuffer = nullptr; 57 | std::unique_ptr iBuffer = nullptr; 58 | 59 | std::unique_ptr sampleVAO = nullptr; 60 | std::unique_ptr sampleVBuf = nullptr; 61 | std::unique_ptr sampleIBuf = nullptr; 62 | 63 | std::unique_ptr dipoleFbo = nullptr; 64 | std::unique_ptr deferFbo = nullptr; 65 | std::unique_ptr gbufFbo = nullptr; 66 | 67 | std::unique_ptr texture = nullptr; 68 | 69 | std::unique_ptr timer = nullptr; 70 | std::unique_ptr arcball = nullptr; 71 | }; 72 | 73 | #endif // _OPENGL_VIEWER_H_ 74 | -------------------------------------------------------------------------------- /sources/settings.h.in: -------------------------------------------------------------------------------- 1 | #ifdef _MSC_VER 2 | #pragma once 3 | #endif 4 | 5 | #ifndef _SETTINGS_H_ 6 | #define _SETTINGS_H_ 7 | 8 | const char* SOURCE_DIRECTORY = "@CMAKE_CURRENT_LIST_DIR@/"; 9 | const char* SHADER_DIRECTORY = "@CMAKE_CURRENT_LIST_DIR@/shaders/"; 10 | const char* DATA_DIRECTORY = "@CMAKE_CURRENT_LIST_DIR@/data/"; 11 | 12 | #endif // _SETTINGS_H_ 13 | -------------------------------------------------------------------------------- /sources/shaders/dipole.fs: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | in vec3 fPosWorld; 4 | in vec4 fPosScreen; 5 | in vec3 fNrmWorld; 6 | in vec2 fTexCoord; 7 | in float fRadius; 8 | 9 | out vec4 outColor; 10 | 11 | uniform sampler2D uPositionMap; 12 | uniform sampler2D uNormalMap; 13 | uniform sampler2D uTexCoordMap; 14 | 15 | uniform vec3 uLightPos; 16 | 17 | float Pi = 4.0 * atan(1.0); 18 | 19 | uniform float eta; 20 | uniform vec3 sigma_a; 21 | uniform vec3 sigmap_s; 22 | 23 | float Fdr() { 24 | if (eta >= 1.0) { 25 | return -1.4399 / (eta * eta) + 0.7099 / eta + 0.6681 + 0.0636 * eta; 26 | } else { 27 | return -0.4399 + 0.7099 / eta - 0.3319 / (eta * eta) + 0.0636 / (eta * eta * eta); 28 | } 29 | } 30 | 31 | vec3 diffRef(vec3 p0, vec3 p1) { 32 | float A = (1.0 + Fdr()) / (1.0 - Fdr()); 33 | vec3 sigmapt = sigma_a + sigmap_s; 34 | vec3 sigma_tr = sqrt(3.0 * sigma_a * sigmapt); 35 | vec3 alphap = sigmap_s / sigmapt; 36 | vec3 zpos = 1.0 / sigmapt; 37 | vec3 zneg = zpos * (1.0 + (4.0 / 3.0) * A); 38 | 39 | float dist = distance(p0, p1); 40 | float d2 = dist * dist; 41 | 42 | vec3 dpos = sqrt(d2 + zpos * zpos); 43 | vec3 dneg = sqrt(d2 + zneg * zneg); 44 | vec3 posterm = zpos * (dpos * sigma_tr + 1.0) * exp(-sigma_tr * dpos) / (dpos * dpos * dpos); 45 | vec3 negterm = zneg * (dneg * sigma_tr + 1.0) * exp(-sigma_tr * dneg) / (dneg * dneg * dneg); 46 | vec3 rd = (alphap / (4.0 * Pi)) * (posterm + negterm); 47 | return max(vec3(0.0, 0.0, 0.0), rd); 48 | } 49 | 50 | void main(void) { 51 | vec2 texCoord = fPosScreen.xy / fPosScreen.w * 0.5 + 0.5; 52 | vec3 pos = texture(uPositionMap, texCoord).xyz; 53 | 54 | vec3 L = normalize(uLightPos - fPosWorld); 55 | vec3 N = normalize(fNrmWorld); 56 | float E = max(0.0, dot(N, L)); 57 | 58 | float dA = fRadius * fRadius * Pi * 0.001; 59 | vec3 Mo = diffRef(pos, fPosWorld) * E * dA; 60 | 61 | outColor = vec4(Mo, 1.0); 62 | } 63 | -------------------------------------------------------------------------------- /sources/shaders/dipole.gs: -------------------------------------------------------------------------------- 1 | #version 330 2 | #extension GL_EXT_geometry_shader4 : enable 3 | 4 | layout(points) in; 5 | layout(triangle_strip, max_vertices=12) out; 6 | 7 | in vec3 gPosition[]; 8 | in vec3 gNormal[]; 9 | in vec2 gTexCoord[]; 10 | in float gRadius[]; 11 | 12 | out vec3 fPosWorld; 13 | out vec4 fPosScreen; 14 | out vec3 fNrmWorld; 15 | out vec2 fTexCoord; 16 | out float fRadius; 17 | 18 | uniform mat4 uMVPMat; 19 | uniform mat4 uMVMat; 20 | 21 | void processVertex(vec3 pos) { 22 | gl_Position = uMVPMat * vec4(pos, 1.0); 23 | fPosScreen = gl_Position; 24 | fPosWorld = gPosition[0]; 25 | EmitVertex(); 26 | } 27 | 28 | void main(void) { 29 | vec3 uAxis, vAxis, wAxis; 30 | wAxis = normalize(gNormal[0]); 31 | if (abs(wAxis.y) < 0.1) { 32 | vAxis = vec3(0.0, 1.0, 0.0); 33 | } else { 34 | vAxis = vec3(1.0, 0.0, 0.0); 35 | } 36 | uAxis = cross(vAxis, wAxis); 37 | vAxis = cross(uAxis, wAxis); 38 | 39 | float r = gRadius[0] * 0.1; 40 | vec3 p00 = gPosition[0] - uAxis * r - vAxis * r; 41 | vec3 p01 = gPosition[0] - uAxis * r + vAxis * r; 42 | vec3 p10 = gPosition[0] + uAxis * r - vAxis * r; 43 | vec3 p11 = gPosition[0] + uAxis * r + vAxis * r; 44 | 45 | fTexCoord = gTexCoord[0]; 46 | fNrmWorld = gNormal[0]; 47 | fRadius = gRadius[0] * 0.1; 48 | 49 | processVertex(p00); 50 | processVertex(p01); 51 | processVertex(p10); 52 | processVertex(p11); 53 | EndPrimitive(); 54 | } 55 | -------------------------------------------------------------------------------- /sources/shaders/dipole.vs: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout(location = 0) in vec3 vPosition; 4 | layout(location = 1) in vec3 vNormal; 5 | layout(location = 2) in vec2 vTexCoord; 6 | layout(location = 3) in float vRadius; 7 | 8 | out vec3 gPosition; 9 | out vec3 gNormal; 10 | out vec2 gTexCoord; 11 | out float gRadius; 12 | 13 | void main(void) { 14 | gPosition = vPosition; 15 | gNormal = vNormal; 16 | gTexCoord = vTexCoord; 17 | gRadius = vRadius; 18 | } 19 | -------------------------------------------------------------------------------- /sources/shaders/gbuffers.fs: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | in vec4 fPosScreen; 4 | in vec3 fPosWorld; 5 | in vec3 fNormal; 6 | in vec2 fTexCoord; 7 | 8 | layout(location = 0) out vec4 outDepth; 9 | layout(location = 1) out vec4 outPosition; 10 | layout(location = 2) out vec4 outNormal; 11 | layout(location = 3) out vec4 outTexCoord; 12 | 13 | uniform int isMaxDepth; 14 | 15 | void main(void) { 16 | float depth = fPosScreen.z / fPosScreen.w; 17 | outDepth = vec4(depth, depth, depth, 1.0); 18 | outPosition = vec4(fPosWorld, 1.0); 19 | outNormal = vec4(normalize(fNormal) * 0.5 + 0.5, 1.0); 20 | outTexCoord = vec4(fTexCoord, 1.0, 1.0); 21 | if (isMaxDepth != 0) { 22 | gl_FragDepth = 1.0 - depth; 23 | } else { 24 | gl_FragDepth = depth; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sources/shaders/gbuffers.vs: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout(location = 0) in vec3 vPosition; 4 | layout(location = 1) in vec3 vNormal; 5 | layout(location = 2) in vec2 vTexCoord; 6 | 7 | out vec4 fPosScreen; 8 | out vec3 fPosWorld; 9 | out vec3 fNormal; 10 | out vec2 fTexCoord; 11 | 12 | uniform mat4 uMVPMat; 13 | 14 | void main(void) { 15 | gl_Position = uMVPMat * vec4(vPosition, 1.0); 16 | fPosScreen = gl_Position; 17 | fPosWorld = vPosition; 18 | fNormal = vNormal; 19 | fTexCoord = vTexCoord; 20 | } 21 | -------------------------------------------------------------------------------- /sources/shaders/render.fs: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | in vec4 fPosScreen; 4 | in vec3 fPosCamera; 5 | in vec3 fNrmCamera; 6 | in vec2 fTexCoord; 7 | in vec3 fLightPos; 8 | 9 | out vec4 outColor; 10 | 11 | uniform sampler2D uTransMap; 12 | 13 | uniform float refFactor; 14 | uniform float transFactor; 15 | 16 | vec2 reflectRatio(vec3 V, vec3 N) { 17 | float etaO = 1.0; 18 | float etaI = 1.3; 19 | float nnt = etaO / etaI; 20 | float ddn = dot(-V, N); 21 | float cos2t = 1.0 - nnt * nnt * (1.0 - ddn * ddn); 22 | if (cos2t < 0.0) { 23 | return vec2(1.0, 0.0); 24 | } 25 | 26 | float a = etaO - etaI; 27 | float b = etaO + etaI; 28 | float R0 = (a * a) / (b * b); 29 | float c = 1.0 + ddn; 30 | float Re = R0 + (1.0 - R0) * pow(c, 5.0); 31 | float nnt2 = pow(etaI / etaO, 2.0); 32 | float Tr = (1.0 - Re) * nnt2; 33 | return vec2(Re, Tr); 34 | } 35 | 36 | void main(void) { 37 | vec3 V = normalize(-fPosCamera); 38 | vec3 N = normalize(fNrmCamera); 39 | vec3 L = normalize(fLightPos - fPosCamera); 40 | vec3 H = normalize(V + L); 41 | 42 | vec2 texCoord = fPosScreen.xy / fPosScreen.w * 0.5 + 0.5; 43 | vec3 trans = texture(uTransMap, texCoord).xyz; 44 | 45 | float NdotL = max(0.0, dot(N, L)); 46 | float NdotH = max(0.0, dot(N, H)); 47 | 48 | vec3 diffuse = vec3(1.0, 1.0, 1.0) * NdotL; 49 | vec3 specular = vec3(1.0, 1.0, 1.0) * pow(NdotH, 128.0) * 0.2; 50 | vec2 Re = vec2(0.5, 0.5); 51 | 52 | vec3 rgb = transFactor * Re.y * trans + 53 | refFactor * Re.x * (diffuse + specular); 54 | outColor = vec4(rgb, 1.0); 55 | } 56 | -------------------------------------------------------------------------------- /sources/shaders/render.vs: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout(location = 0) in vec3 vPosition; 4 | layout(location = 1) in vec3 vNormal; 5 | layout(location = 2) in vec2 vTexCoord; 6 | 7 | out vec4 fPosScreen; 8 | out vec3 fPosCamera; 9 | out vec3 fNrmCamera; 10 | out vec2 fTexCoord; 11 | out vec3 fLightPos; 12 | 13 | uniform mat4 uMVPMat; 14 | uniform mat4 uMVMat; 15 | uniform vec3 uLightPos; 16 | 17 | void main(void) { 18 | gl_Position = uMVPMat * vec4(vPosition, 1.0); 19 | fPosScreen = gl_Position; 20 | 21 | fPosCamera = (uMVMat * vec4(vPosition, 1.0)).xyz; 22 | fNrmCamera = (transpose(inverse(uMVMat)) * vec4(vNormal, 0.0)).xyz; 23 | fTexCoord = vTexCoord; 24 | fLightPos = (uMVMat * vec4(uLightPos, 1.0)).xyz; 25 | } 26 | -------------------------------------------------------------------------------- /sources/tiny_obj_loader.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2012-2016, Syoyo Fujita. 3 | // 4 | // Licensed under 2-clause BSD license. 5 | // 6 | 7 | // 8 | // version 0.9.22: Introduce `load_flags_t`. 9 | // version 0.9.20: Fixes creating per-face material using `usemtl`(#68) 10 | // version 0.9.17: Support n-polygon and crease tag(OpenSubdiv extension) 11 | // version 0.9.16: Make tinyobjloader header-only 12 | // version 0.9.15: Change API to handle no mtl file case correctly(#58) 13 | // version 0.9.14: Support specular highlight, bump, displacement and alpha 14 | // map(#53) 15 | // version 0.9.13: Report "Material file not found message" in `err`(#46) 16 | // version 0.9.12: Fix groups being ignored if they have 'usemtl' just before 17 | // 'g' (#44) 18 | // version 0.9.11: Invert `Tr` parameter(#43) 19 | // version 0.9.10: Fix seg fault on windows. 20 | // version 0.9.9 : Replace atof() with custom parser. 21 | // version 0.9.8 : Fix multi-materials(per-face material ID). 22 | // version 0.9.7 : Support multi-materials(per-face material ID) per 23 | // object/group. 24 | // version 0.9.6 : Support Ni(index of refraction) mtl parameter. 25 | // Parse transmittance material parameter correctly. 26 | // version 0.9.5 : Parse multiple group name. 27 | // Add support of specifying the base path to load material 28 | // file. 29 | // version 0.9.4 : Initial support of group tag(g) 30 | // version 0.9.3 : Fix parsing triple 'x/y/z' 31 | // version 0.9.2 : Add more .mtl load support 32 | // version 0.9.1 : Add initial .mtl load support 33 | // version 0.9.0 : Initial 34 | // 35 | 36 | // 37 | // Use this in *one* .cc 38 | // #define TINYOBJLOADER_IMPLEMENTATION 39 | // #include "tiny_obj_loader.h" 40 | // 41 | 42 | #ifndef TINY_OBJ_LOADER_H_ 43 | #define TINY_OBJ_LOADER_H_ 44 | 45 | #include 46 | #include 47 | #include 48 | #include 49 | 50 | namespace tinyobj { 51 | 52 | typedef struct { 53 | std::string name; 54 | 55 | float ambient[3]; 56 | float diffuse[3]; 57 | float specular[3]; 58 | float transmittance[3]; 59 | float emission[3]; 60 | float shininess; 61 | float ior; // index of refraction 62 | float dissolve; // 1 == opaque; 0 == fully transparent 63 | // illumination model (see http://www.fileformat.info/format/material/) 64 | int illum; 65 | 66 | int dummy; // Suppress padding warning. 67 | 68 | std::string ambient_texname; // map_Ka 69 | std::string diffuse_texname; // map_Kd 70 | std::string specular_texname; // map_Ks 71 | std::string specular_highlight_texname; // map_Ns 72 | std::string bump_texname; // map_bump, bump 73 | std::string displacement_texname; // disp 74 | std::string alpha_texname; // map_d 75 | std::map unknown_parameter; 76 | } material_t; 77 | 78 | typedef struct { 79 | std::string name; 80 | 81 | std::vector intValues; 82 | std::vector floatValues; 83 | std::vector stringValues; 84 | } tag_t; 85 | 86 | typedef struct { 87 | std::vector positions; 88 | std::vector normals; 89 | std::vector texcoords; 90 | std::vector indices; 91 | std::vector 92 | num_vertices; // The number of vertices per face. Up to 255. 93 | std::vector material_ids; // per-face material ID 94 | std::vector tags; // SubD tag 95 | } mesh_t; 96 | 97 | typedef struct { 98 | std::string name; 99 | mesh_t mesh; 100 | } shape_t; 101 | 102 | typedef enum 103 | { 104 | triangulation = 1, // used whether triangulate polygon face in .obj 105 | calculate_normals = 2, // used whether calculate the normals if the .obj normals are empty 106 | // Some nice stuff here 107 | } load_flags_t; 108 | 109 | class float3 110 | { 111 | public: 112 | float3() 113 | : x( 0.0f ) 114 | , y( 0.0f ) 115 | , z( 0.0f ) 116 | { 117 | } 118 | 119 | float3(float coord_x, float coord_y, float coord_z) 120 | : x( coord_x ) 121 | , y( coord_y ) 122 | , z( coord_z ) 123 | { 124 | } 125 | 126 | float3(const float3& from, const float3& to) 127 | { 128 | coord[0] = to.coord[0] - from.coord[0]; 129 | coord[1] = to.coord[1] - from.coord[1]; 130 | coord[2] = to.coord[2] - from.coord[2]; 131 | } 132 | 133 | float3 crossproduct ( const float3 & vec ) 134 | { 135 | float a = y * vec.z - z * vec.y ; 136 | float b = z * vec.x - x * vec.z ; 137 | float c = x * vec.y - y * vec.x ; 138 | return float3( a , b , c ); 139 | } 140 | 141 | void normalize() 142 | { 143 | const float length = std::sqrt( ( coord[0] * coord[0] ) + 144 | ( coord[1] * coord[1] ) + 145 | ( coord[2] * coord[2] ) ); 146 | if( length != 1 ) 147 | { 148 | coord[0] = (coord[0] / length); 149 | coord[1] = (coord[1] / length); 150 | coord[2] = (coord[2] / length); 151 | } 152 | } 153 | 154 | private: 155 | union 156 | { 157 | float coord[3]; 158 | struct 159 | { 160 | float x,y,z; 161 | }; 162 | }; 163 | }; 164 | 165 | class MaterialReader { 166 | public: 167 | MaterialReader() {} 168 | virtual ~MaterialReader(); 169 | 170 | virtual bool operator()(const std::string &matId, 171 | std::vector &materials, 172 | std::map &matMap, 173 | std::string &err) = 0; 174 | }; 175 | 176 | class MaterialFileReader : public MaterialReader { 177 | public: 178 | MaterialFileReader(const std::string &mtl_basepath) 179 | : m_mtlBasePath(mtl_basepath) {} 180 | virtual ~MaterialFileReader() {} 181 | virtual bool operator()(const std::string &matId, 182 | std::vector &materials, 183 | std::map &matMap, std::string &err); 184 | 185 | private: 186 | std::string m_mtlBasePath; 187 | }; 188 | 189 | /// Loads .obj from a file. 190 | /// 'shapes' will be filled with parsed shape data 191 | /// The function returns error string. 192 | /// Returns true when loading .obj become success. 193 | /// Returns warning and error message into `err` 194 | /// 'mtl_basepath' is optional, and used for base path for .mtl file. 195 | /// 'optional flags 196 | bool LoadObj(std::vector &shapes, // [output] 197 | std::vector &materials, // [output] 198 | std::string &err, // [output] 199 | const char *filename, const char *mtl_basepath = NULL, 200 | unsigned int flags = 1 ); 201 | 202 | /// Loads object from a std::istream, uses GetMtlIStreamFn to retrieve 203 | /// std::istream for materials. 204 | /// Returns true when loading .obj become success. 205 | /// Returns warning and error message into `err` 206 | bool LoadObj(std::vector &shapes, // [output] 207 | std::vector &materials, // [output] 208 | std::string &err, // [output] 209 | std::istream &inStream, MaterialReader &readMatFn, 210 | unsigned int flags = 1); 211 | 212 | /// Loads materials into std::map 213 | void LoadMtl(std::map &material_map, // [output] 214 | std::vector &materials, // [output] 215 | std::istream &inStream); 216 | } 217 | 218 | #ifdef TINYOBJLOADER_IMPLEMENTATION 219 | #include 220 | #include 221 | #include 222 | #include 223 | #include 224 | #include 225 | 226 | #include 227 | #include 228 | 229 | #include "tiny_obj_loader.h" 230 | 231 | namespace tinyobj { 232 | 233 | MaterialReader::~MaterialReader() {} 234 | 235 | #define TINYOBJ_SSCANF_BUFFER_SIZE (4096) 236 | 237 | struct vertex_index { 238 | int v_idx, vt_idx, vn_idx; 239 | vertex_index() : v_idx(-1), vt_idx(-1), vn_idx(-1) {} 240 | explicit vertex_index(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx) {} 241 | vertex_index(int vidx, int vtidx, int vnidx) 242 | : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {} 243 | }; 244 | 245 | struct tag_sizes { 246 | tag_sizes() : num_ints(0), num_floats(0), num_strings(0) {} 247 | int num_ints; 248 | int num_floats; 249 | int num_strings; 250 | }; 251 | 252 | // for std::map 253 | static inline bool operator<(const vertex_index &a, const vertex_index &b) { 254 | if (a.v_idx != b.v_idx) 255 | return (a.v_idx < b.v_idx); 256 | if (a.vn_idx != b.vn_idx) 257 | return (a.vn_idx < b.vn_idx); 258 | if (a.vt_idx != b.vt_idx) 259 | return (a.vt_idx < b.vt_idx); 260 | 261 | return false; 262 | } 263 | 264 | struct obj_shape { 265 | std::vector v; 266 | std::vector vn; 267 | std::vector vt; 268 | }; 269 | 270 | //See http://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf 271 | std::istream& safeGetline(std::istream& is, std::string& t) 272 | { 273 | t.clear(); 274 | 275 | // The characters in the stream are read one-by-one using a std::streambuf. 276 | // That is faster than reading them one-by-one using the std::istream. 277 | // Code that uses streambuf this way must be guarded by a sentry object. 278 | // The sentry object performs various tasks, 279 | // such as thread synchronization and updating the stream state. 280 | 281 | std::istream::sentry se(is, true); 282 | std::streambuf* sb = is.rdbuf(); 283 | 284 | for(;;) { 285 | int c = sb->sbumpc(); 286 | switch (c) { 287 | case '\n': 288 | return is; 289 | case '\r': 290 | if(sb->sgetc() == '\n') 291 | sb->sbumpc(); 292 | return is; 293 | case EOF: 294 | // Also handle the case when the last line has no line ending 295 | if(t.empty()) 296 | is.setstate(std::ios::eofbit); 297 | return is; 298 | default: 299 | t += (char)c; 300 | } 301 | } 302 | } 303 | 304 | #define IS_SPACE( x ) ( ( (x) == ' ') || ( (x) == '\t') ) 305 | #define IS_DIGIT( x ) ( (unsigned int)( (x) - '0' ) < (unsigned int)10 ) 306 | #define IS_NEW_LINE( x ) ( ( (x) == '\r') || ( (x) == '\n') || ( (x) == '\0') ) 307 | 308 | // Make index zero-base, and also support relative index. 309 | static inline int fixIndex(int idx, int n) { 310 | if (idx > 0) 311 | return idx - 1; 312 | if (idx == 0) 313 | return 0; 314 | return n + idx; // negative value = relative 315 | } 316 | 317 | static inline std::string parseString(const char *&token) { 318 | std::string s; 319 | token += strspn(token, " \t"); 320 | size_t e = strcspn(token, " \t\r"); 321 | s = std::string(token, &token[e]); 322 | token += e; 323 | return s; 324 | } 325 | 326 | static inline int parseInt(const char *&token) { 327 | token += strspn(token, " \t"); 328 | int i = atoi(token); 329 | token += strcspn(token, " \t\r"); 330 | return i; 331 | } 332 | 333 | // Tries to parse a floating point number located at s. 334 | // 335 | // s_end should be a location in the string where reading should absolutely 336 | // stop. For example at the end of the string, to prevent buffer overflows. 337 | // 338 | // Parses the following EBNF grammar: 339 | // sign = "+" | "-" ; 340 | // END = ? anything not in digit ? 341 | // digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; 342 | // integer = [sign] , digit , {digit} ; 343 | // decimal = integer , ["." , integer] ; 344 | // float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; 345 | // 346 | // Valid strings are for example: 347 | // -0 +3.1417e+2 -0.0E-3 1.0324 -1.41 11e2 348 | // 349 | // If the parsing is a success, result is set to the parsed value and true 350 | // is returned. 351 | // 352 | // The function is greedy and will parse until any of the following happens: 353 | // - a non-conforming character is encountered. 354 | // - s_end is reached. 355 | // 356 | // The following situations triggers a failure: 357 | // - s >= s_end. 358 | // - parse failure. 359 | // 360 | static bool tryParseDouble(const char *s, const char *s_end, double *result) { 361 | if (s >= s_end) { 362 | return false; 363 | } 364 | 365 | double mantissa = 0.0; 366 | // This exponent is base 2 rather than 10. 367 | // However the exponent we parse is supposed to be one of ten, 368 | // thus we must take care to convert the exponent/and or the 369 | // mantissa to a * 2^E, where a is the mantissa and E is the 370 | // exponent. 371 | // To get the final double we will use ldexp, it requires the 372 | // exponent to be in base 2. 373 | int exponent = 0; 374 | 375 | // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED 376 | // TO JUMP OVER DEFINITIONS. 377 | char sign = '+'; 378 | char exp_sign = '+'; 379 | char const *curr = s; 380 | 381 | // How many characters were read in a loop. 382 | int read = 0; 383 | // Tells whether a loop terminated due to reaching s_end. 384 | bool end_not_reached = false; 385 | 386 | /* 387 | BEGIN PARSING. 388 | */ 389 | 390 | // Find out what sign we've got. 391 | if (*curr == '+' || *curr == '-') { 392 | sign = *curr; 393 | curr++; 394 | } else if (IS_DIGIT(*curr)) { /* Pass through. */ 395 | } else { 396 | goto fail; 397 | } 398 | 399 | // Read the integer part. 400 | while ((end_not_reached = (curr != s_end)) && IS_DIGIT(*curr)) { 401 | mantissa *= 10; 402 | mantissa += static_cast(*curr - 0x30); 403 | curr++; 404 | read++; 405 | } 406 | 407 | // We must make sure we actually got something. 408 | if (read == 0) 409 | goto fail; 410 | // We allow numbers of form "#", "###" etc. 411 | if (!end_not_reached) 412 | goto assemble; 413 | 414 | // Read the decimal part. 415 | if (*curr == '.') { 416 | curr++; 417 | read = 1; 418 | while ((end_not_reached = (curr != s_end)) && IS_DIGIT(*curr)) { 419 | // NOTE: Don't use powf here, it will absolutely murder precision. 420 | mantissa += static_cast(*curr - 0x30) * pow(10.0, -read); 421 | read++; 422 | curr++; 423 | } 424 | } else if (*curr == 'e' || *curr == 'E') { 425 | } else { 426 | goto assemble; 427 | } 428 | 429 | if (!end_not_reached) 430 | goto assemble; 431 | 432 | // Read the exponent part. 433 | if (*curr == 'e' || *curr == 'E') { 434 | curr++; 435 | // Figure out if a sign is present and if it is. 436 | if ((end_not_reached = (curr != s_end)) && (*curr == '+' || *curr == '-')) { 437 | exp_sign = *curr; 438 | curr++; 439 | } else if (IS_DIGIT(*curr)) { /* Pass through. */ 440 | } else { 441 | // Empty E is not allowed. 442 | goto fail; 443 | } 444 | 445 | read = 0; 446 | while ((end_not_reached = (curr != s_end)) && IS_DIGIT(*curr)) { 447 | exponent *= 10; 448 | exponent += static_cast(*curr - 0x30); 449 | curr++; 450 | read++; 451 | } 452 | exponent *= (exp_sign == '+' ? 1 : -1); 453 | if (read == 0) 454 | goto fail; 455 | } 456 | 457 | assemble: 458 | *result = 459 | (sign == '+' ? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), exponent); 460 | return true; 461 | fail: 462 | return false; 463 | } 464 | static inline float parseFloat(const char *&token) { 465 | token += strspn(token, " \t"); 466 | #ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER 467 | float f = (float)atof(token); 468 | token += strcspn(token, " \t\r"); 469 | #else 470 | const char *end = token + strcspn(token, " \t\r"); 471 | double val = 0.0; 472 | tryParseDouble(token, end, &val); 473 | float f = static_cast(val); 474 | token = end; 475 | #endif 476 | return f; 477 | } 478 | 479 | static inline void parseFloat2(float &x, float &y, const char *&token) { 480 | x = parseFloat(token); 481 | y = parseFloat(token); 482 | } 483 | 484 | static inline void parseFloat3(float &x, float &y, float &z, 485 | const char *&token) { 486 | x = parseFloat(token); 487 | y = parseFloat(token); 488 | z = parseFloat(token); 489 | } 490 | 491 | static tag_sizes parseTagTriple(const char *&token) { 492 | tag_sizes ts; 493 | 494 | ts.num_ints = atoi(token); 495 | token += strcspn(token, "/ \t\r"); 496 | if (token[0] != '/') { 497 | return ts; 498 | } 499 | token++; 500 | 501 | ts.num_floats = atoi(token); 502 | token += strcspn(token, "/ \t\r"); 503 | if (token[0] != '/') { 504 | return ts; 505 | } 506 | token++; 507 | 508 | ts.num_strings = atoi(token); 509 | token += strcspn(token, "/ \t\r") + 1; 510 | 511 | return ts; 512 | } 513 | 514 | // Parse triples: i, i/j/k, i//k, i/j 515 | static vertex_index parseTriple(const char *&token, int vsize, int vnsize, 516 | int vtsize) { 517 | vertex_index vi(-1); 518 | 519 | vi.v_idx = fixIndex(atoi(token), vsize); 520 | token += strcspn(token, "/ \t\r"); 521 | if (token[0] != '/') { 522 | return vi; 523 | } 524 | token++; 525 | 526 | // i//k 527 | if (token[0] == '/') { 528 | token++; 529 | vi.vn_idx = fixIndex(atoi(token), vnsize); 530 | token += strcspn(token, "/ \t\r"); 531 | return vi; 532 | } 533 | 534 | // i/j/k or i/j 535 | vi.vt_idx = fixIndex(atoi(token), vtsize); 536 | token += strcspn(token, "/ \t\r"); 537 | if (token[0] != '/') { 538 | return vi; 539 | } 540 | 541 | // i/j/k 542 | token++; // skip '/' 543 | vi.vn_idx = fixIndex(atoi(token), vnsize); 544 | token += strcspn(token, "/ \t\r"); 545 | return vi; 546 | } 547 | 548 | static unsigned int 549 | updateVertex(std::map &vertexCache, 550 | std::vector &positions, std::vector &normals, 551 | std::vector &texcoords, 552 | const std::vector &in_positions, 553 | const std::vector &in_normals, 554 | const std::vector &in_texcoords, const vertex_index &i) { 555 | const std::map::iterator it = vertexCache.find(i); 556 | 557 | if (it != vertexCache.end()) { 558 | // found cache 559 | return it->second; 560 | } 561 | 562 | assert(in_positions.size() > static_cast(3 * i.v_idx + 2)); 563 | 564 | positions.push_back(in_positions[3 * static_cast(i.v_idx) + 0]); 565 | positions.push_back(in_positions[3 * static_cast(i.v_idx) + 1]); 566 | positions.push_back(in_positions[3 * static_cast(i.v_idx) + 2]); 567 | 568 | if ((i.vn_idx >= 0) && 569 | (static_cast(i.vn_idx * 3 + 2) < in_normals.size())) { 570 | normals.push_back(in_normals[3 * static_cast(i.vn_idx) + 0]); 571 | normals.push_back(in_normals[3 * static_cast(i.vn_idx) + 1]); 572 | normals.push_back(in_normals[3 * static_cast(i.vn_idx) + 2]); 573 | } 574 | 575 | if ((i.vt_idx >= 0) && 576 | (static_cast(i.vt_idx * 2 + 1) < in_texcoords.size())) { 577 | texcoords.push_back(in_texcoords[2 * static_cast(i.vt_idx) + 0]); 578 | texcoords.push_back(in_texcoords[2 * static_cast(i.vt_idx) + 1]); 579 | } 580 | 581 | unsigned int idx = static_cast(positions.size() / 3 - 1); 582 | vertexCache[i] = idx; 583 | 584 | return idx; 585 | } 586 | 587 | static void InitMaterial(material_t &material) { 588 | material.name = ""; 589 | material.ambient_texname = ""; 590 | material.diffuse_texname = ""; 591 | material.specular_texname = ""; 592 | material.specular_highlight_texname = ""; 593 | material.bump_texname = ""; 594 | material.displacement_texname = ""; 595 | material.alpha_texname = ""; 596 | for (int i = 0; i < 3; i++) { 597 | material.ambient[i] = 0.f; 598 | material.diffuse[i] = 0.f; 599 | material.specular[i] = 0.f; 600 | material.transmittance[i] = 0.f; 601 | material.emission[i] = 0.f; 602 | } 603 | material.illum = 0; 604 | material.dissolve = 1.f; 605 | material.shininess = 1.f; 606 | material.ior = 1.f; 607 | material.unknown_parameter.clear(); 608 | } 609 | 610 | static bool exportFaceGroupToShape( 611 | shape_t &shape, std::map vertexCache, 612 | const std::vector &in_positions, 613 | const std::vector &in_normals, 614 | const std::vector &in_texcoords, 615 | const std::vector > &faceGroup, 616 | std::vector &tags, const int material_id, const std::string &name, 617 | bool clearCache, unsigned int flags, std::string& err ) { 618 | if (faceGroup.empty()) { 619 | return false; 620 | } 621 | 622 | bool triangulate( ( flags & triangulation ) == triangulation ); 623 | bool normals_calculation( ( flags & calculate_normals ) == calculate_normals ); 624 | 625 | // Flatten vertices and indices 626 | for (size_t i = 0; i < faceGroup.size(); i++) { 627 | const std::vector &face = faceGroup[i]; 628 | 629 | vertex_index i0 = face[0]; 630 | vertex_index i1(-1); 631 | vertex_index i2 = face[1]; 632 | 633 | size_t npolys = face.size(); 634 | 635 | if (triangulate) { 636 | 637 | // Polygon -> triangle fan conversion 638 | for (size_t k = 2; k < npolys; k++) { 639 | i1 = i2; 640 | i2 = face[k]; 641 | 642 | unsigned int v0 = updateVertex( 643 | vertexCache, shape.mesh.positions, shape.mesh.normals, 644 | shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i0); 645 | unsigned int v1 = updateVertex( 646 | vertexCache, shape.mesh.positions, shape.mesh.normals, 647 | shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i1); 648 | unsigned int v2 = updateVertex( 649 | vertexCache, shape.mesh.positions, shape.mesh.normals, 650 | shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i2); 651 | 652 | shape.mesh.indices.push_back(v0); 653 | shape.mesh.indices.push_back(v1); 654 | shape.mesh.indices.push_back(v2); 655 | 656 | shape.mesh.num_vertices.push_back(3); 657 | shape.mesh.material_ids.push_back(material_id); 658 | } 659 | } else { 660 | 661 | for (size_t k = 0; k < npolys; k++) { 662 | unsigned int v = 663 | updateVertex(vertexCache, shape.mesh.positions, shape.mesh.normals, 664 | shape.mesh.texcoords, in_positions, in_normals, 665 | in_texcoords, face[k]); 666 | 667 | shape.mesh.indices.push_back(v); 668 | } 669 | 670 | shape.mesh.num_vertices.push_back(static_cast(npolys)); 671 | shape.mesh.material_ids.push_back(material_id); // per face 672 | } 673 | } 674 | 675 | if (normals_calculation && shape.mesh.normals.empty()) { 676 | const size_t nIndexs = shape.mesh.indices.size(); 677 | if (nIndexs % 3 == 0) { 678 | shape.mesh.normals.resize(shape.mesh.positions.size()); 679 | for (register size_t iIndices = 0; iIndices < nIndexs; iIndices += 3) { 680 | float3 v1, v2, v3; 681 | memcpy(&v1, &shape.mesh.positions[shape.mesh.indices[iIndices] * 3], sizeof(float3)); 682 | memcpy(&v2, &shape.mesh.positions[shape.mesh.indices[iIndices + 1] * 3], sizeof(float3)); 683 | memcpy(&v3, &shape.mesh.positions[shape.mesh.indices[iIndices + 2] * 3], sizeof(float3)); 684 | 685 | float3 v12(v1, v2); 686 | float3 v13(v1, v3); 687 | 688 | float3 normal = v12.crossproduct(v13); 689 | normal.normalize(); 690 | 691 | memcpy(&shape.mesh.normals[shape.mesh.indices[iIndices] * 3], &normal, sizeof(float3)); 692 | memcpy(&shape.mesh.normals[shape.mesh.indices[iIndices + 1] * 3], &normal, sizeof(float3)); 693 | memcpy(&shape.mesh.normals[shape.mesh.indices[iIndices + 2] * 3], &normal, sizeof(float3)); 694 | } 695 | } else { 696 | 697 | std::stringstream ss; 698 | ss << "WARN: The shape " << name << " does not have a topology of triangles, therfore the normals calculation could not be performed. Select the tinyobj::triangulation flag for this object." << std::endl; 699 | err += ss.str(); 700 | } 701 | } 702 | 703 | shape.name = name; 704 | shape.mesh.tags.swap(tags); 705 | 706 | if (clearCache) 707 | vertexCache.clear(); 708 | 709 | return true; 710 | } 711 | 712 | void LoadMtl(std::map &material_map, 713 | std::vector &materials, std::istream &inStream) { 714 | 715 | // Create a default material anyway. 716 | material_t material; 717 | InitMaterial(material); 718 | 719 | while (inStream.peek() != -1) { 720 | std::string linebuf; 721 | safeGetline(inStream, linebuf); 722 | 723 | // Trim newline '\r\n' or '\n' 724 | if (linebuf.size() > 0) { 725 | if (linebuf[linebuf.size() - 1] == '\n') 726 | linebuf.erase(linebuf.size() - 1); 727 | } 728 | if (linebuf.size() > 0) { 729 | if (linebuf[linebuf.size() - 1] == '\r') 730 | linebuf.erase(linebuf.size() - 1); 731 | } 732 | 733 | // Skip if empty line. 734 | if (linebuf.empty()) { 735 | continue; 736 | } 737 | 738 | // Skip leading space. 739 | const char *token = linebuf.c_str(); 740 | token += strspn(token, " \t"); 741 | 742 | assert(token); 743 | if (token[0] == '\0') 744 | continue; // empty line 745 | 746 | if (token[0] == '#') 747 | continue; // comment line 748 | 749 | // new mtl 750 | if ((0 == strncmp(token, "newmtl", 6)) && IS_SPACE((token[6]))) { 751 | // flush previous material. 752 | if (!material.name.empty()) { 753 | material_map.insert(std::pair( 754 | material.name, static_cast(materials.size()))); 755 | materials.push_back(material); 756 | } 757 | 758 | // initial temporary material 759 | InitMaterial(material); 760 | 761 | // set new mtl name 762 | char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; 763 | token += 7; 764 | #ifdef _MSC_VER 765 | sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); 766 | #else 767 | sscanf(token, "%s", namebuf); 768 | #endif 769 | material.name = namebuf; 770 | continue; 771 | } 772 | 773 | // ambient 774 | if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) { 775 | token += 2; 776 | float r, g, b; 777 | parseFloat3(r, g, b, token); 778 | material.ambient[0] = r; 779 | material.ambient[1] = g; 780 | material.ambient[2] = b; 781 | continue; 782 | } 783 | 784 | // diffuse 785 | if (token[0] == 'K' && token[1] == 'd' && IS_SPACE((token[2]))) { 786 | token += 2; 787 | float r, g, b; 788 | parseFloat3(r, g, b, token); 789 | material.diffuse[0] = r; 790 | material.diffuse[1] = g; 791 | material.diffuse[2] = b; 792 | continue; 793 | } 794 | 795 | // specular 796 | if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) { 797 | token += 2; 798 | float r, g, b; 799 | parseFloat3(r, g, b, token); 800 | material.specular[0] = r; 801 | material.specular[1] = g; 802 | material.specular[2] = b; 803 | continue; 804 | } 805 | 806 | // transmittance 807 | if (token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) { 808 | token += 2; 809 | float r, g, b; 810 | parseFloat3(r, g, b, token); 811 | material.transmittance[0] = r; 812 | material.transmittance[1] = g; 813 | material.transmittance[2] = b; 814 | continue; 815 | } 816 | 817 | // ior(index of refraction) 818 | if (token[0] == 'N' && token[1] == 'i' && IS_SPACE((token[2]))) { 819 | token += 2; 820 | material.ior = parseFloat(token); 821 | continue; 822 | } 823 | 824 | // emission 825 | if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) { 826 | token += 2; 827 | float r, g, b; 828 | parseFloat3(r, g, b, token); 829 | material.emission[0] = r; 830 | material.emission[1] = g; 831 | material.emission[2] = b; 832 | continue; 833 | } 834 | 835 | // shininess 836 | if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) { 837 | token += 2; 838 | material.shininess = parseFloat(token); 839 | continue; 840 | } 841 | 842 | // illum model 843 | if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) { 844 | token += 6; 845 | material.illum = parseInt(token); 846 | continue; 847 | } 848 | 849 | // dissolve 850 | if ((token[0] == 'd' && IS_SPACE(token[1]))) { 851 | token += 1; 852 | material.dissolve = parseFloat(token); 853 | continue; 854 | } 855 | if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) { 856 | token += 2; 857 | // Invert value of Tr(assume Tr is in range [0, 1]) 858 | material.dissolve = 1.0f - parseFloat(token); 859 | continue; 860 | } 861 | 862 | // ambient texture 863 | if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) { 864 | token += 7; 865 | material.ambient_texname = token; 866 | continue; 867 | } 868 | 869 | // diffuse texture 870 | if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) { 871 | token += 7; 872 | material.diffuse_texname = token; 873 | continue; 874 | } 875 | 876 | // specular texture 877 | if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) { 878 | token += 7; 879 | material.specular_texname = token; 880 | continue; 881 | } 882 | 883 | // specular highlight texture 884 | if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) { 885 | token += 7; 886 | material.specular_highlight_texname = token; 887 | continue; 888 | } 889 | 890 | // bump texture 891 | if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) { 892 | token += 9; 893 | material.bump_texname = token; 894 | continue; 895 | } 896 | 897 | // alpha texture 898 | if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) { 899 | token += 6; 900 | material.alpha_texname = token; 901 | continue; 902 | } 903 | 904 | // bump texture 905 | if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { 906 | token += 5; 907 | material.bump_texname = token; 908 | continue; 909 | } 910 | 911 | // displacement texture 912 | if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) { 913 | token += 5; 914 | material.displacement_texname = token; 915 | continue; 916 | } 917 | 918 | // unknown parameter 919 | const char *_space = strchr(token, ' '); 920 | if (!_space) { 921 | _space = strchr(token, '\t'); 922 | } 923 | if (_space) { 924 | std::ptrdiff_t len = _space - token; 925 | std::string key(token, static_cast(len)); 926 | std::string value = _space + 1; 927 | material.unknown_parameter.insert( 928 | std::pair(key, value)); 929 | } 930 | } 931 | // flush last material. 932 | material_map.insert(std::pair( 933 | material.name, static_cast(materials.size()))); 934 | materials.push_back(material); 935 | } 936 | 937 | bool MaterialFileReader::operator()(const std::string &matId, 938 | std::vector &materials, 939 | std::map &matMap, 940 | std::string &err) { 941 | std::string filepath; 942 | 943 | if (!m_mtlBasePath.empty()) { 944 | filepath = std::string(m_mtlBasePath) + matId; 945 | } else { 946 | filepath = matId; 947 | } 948 | 949 | std::ifstream matIStream(filepath.c_str()); 950 | LoadMtl(matMap, materials, matIStream); 951 | if (!matIStream) { 952 | std::stringstream ss; 953 | ss << "WARN: Material file [ " << filepath 954 | << " ] not found. Created a default material."; 955 | err += ss.str(); 956 | } 957 | return true; 958 | } 959 | 960 | bool LoadObj(std::vector &shapes, // [output] 961 | std::vector &materials, // [output] 962 | std::string &err, const char *filename, const char *mtl_basepath, 963 | unsigned int flags) { 964 | 965 | shapes.clear(); 966 | 967 | std::stringstream errss; 968 | 969 | std::ifstream ifs(filename); 970 | if (!ifs) { 971 | errss << "Cannot open file [" << filename << "]" << std::endl; 972 | err = errss.str(); 973 | return false; 974 | } 975 | 976 | std::string basePath; 977 | if (mtl_basepath) { 978 | basePath = mtl_basepath; 979 | } 980 | MaterialFileReader matFileReader(basePath); 981 | 982 | return LoadObj(shapes, materials, err, ifs, matFileReader, flags); 983 | } 984 | 985 | bool LoadObj(std::vector &shapes, // [output] 986 | std::vector &materials, // [output] 987 | std::string &err, std::istream &inStream, 988 | MaterialReader &readMatFn, unsigned int flags) { 989 | 990 | std::stringstream errss; 991 | 992 | std::vector v; 993 | std::vector vn; 994 | std::vector vt; 995 | std::vector tags; 996 | std::vector > faceGroup; 997 | std::string name; 998 | 999 | // material 1000 | std::map material_map; 1001 | std::map vertexCache; 1002 | int material = -1; 1003 | 1004 | shape_t shape; 1005 | 1006 | while (inStream.peek() != -1) { 1007 | std::string linebuf; 1008 | safeGetline(inStream, linebuf); 1009 | 1010 | // Trim newline '\r\n' or '\n' 1011 | if (linebuf.size() > 0) { 1012 | if (linebuf[linebuf.size() - 1] == '\n') 1013 | linebuf.erase(linebuf.size() - 1); 1014 | } 1015 | if (linebuf.size() > 0) { 1016 | if (linebuf[linebuf.size() - 1] == '\r') 1017 | linebuf.erase(linebuf.size() - 1); 1018 | } 1019 | 1020 | // Skip if empty line. 1021 | if (linebuf.empty()) { 1022 | continue; 1023 | } 1024 | 1025 | // Skip leading space. 1026 | const char *token = linebuf.c_str(); 1027 | token += strspn(token, " \t"); 1028 | 1029 | assert(token); 1030 | if (token[0] == '\0') 1031 | continue; // empty line 1032 | 1033 | if (token[0] == '#') 1034 | continue; // comment line 1035 | 1036 | // vertex 1037 | if (token[0] == 'v' && IS_SPACE((token[1]))) { 1038 | token += 2; 1039 | float x, y, z; 1040 | parseFloat3(x, y, z, token); 1041 | v.push_back(x); 1042 | v.push_back(y); 1043 | v.push_back(z); 1044 | continue; 1045 | } 1046 | 1047 | // normal 1048 | if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { 1049 | token += 3; 1050 | float x, y, z; 1051 | parseFloat3(x, y, z, token); 1052 | vn.push_back(x); 1053 | vn.push_back(y); 1054 | vn.push_back(z); 1055 | continue; 1056 | } 1057 | 1058 | // texcoord 1059 | if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { 1060 | token += 3; 1061 | float x, y; 1062 | parseFloat2(x, y, token); 1063 | vt.push_back(x); 1064 | vt.push_back(y); 1065 | continue; 1066 | } 1067 | 1068 | // face 1069 | if (token[0] == 'f' && IS_SPACE((token[1]))) { 1070 | token += 2; 1071 | token += strspn(token, " \t"); 1072 | 1073 | std::vector face; 1074 | face.reserve(3); 1075 | 1076 | while (!IS_NEW_LINE(token[0])) { 1077 | vertex_index vi = parseTriple(token, static_cast(v.size() / 3), 1078 | static_cast(vn.size() / 3), 1079 | static_cast(vt.size() / 2)); 1080 | face.push_back(vi); 1081 | size_t n = strspn(token, " \t\r"); 1082 | token += n; 1083 | } 1084 | 1085 | // replace with emplace_back + std::move on C++11 1086 | faceGroup.push_back(std::vector()); 1087 | faceGroup[faceGroup.size() - 1].swap(face); 1088 | 1089 | continue; 1090 | } 1091 | 1092 | // use mtl 1093 | if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { 1094 | 1095 | char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; 1096 | token += 7; 1097 | #ifdef _MSC_VER 1098 | sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); 1099 | #else 1100 | sscanf(token, "%s", namebuf); 1101 | #endif 1102 | 1103 | int newMaterialId = -1; 1104 | if (material_map.find(namebuf) != material_map.end()) { 1105 | newMaterialId = material_map[namebuf]; 1106 | } else { 1107 | // { error!! material not found } 1108 | } 1109 | 1110 | if (newMaterialId != material) { 1111 | // Create per-face material 1112 | exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, tags, 1113 | material, name, true, flags, err ); 1114 | faceGroup.clear(); 1115 | material = newMaterialId; 1116 | } 1117 | 1118 | continue; 1119 | } 1120 | 1121 | // load mtl 1122 | if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { 1123 | char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; 1124 | token += 7; 1125 | #ifdef _MSC_VER 1126 | sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); 1127 | #else 1128 | sscanf(token, "%s", namebuf); 1129 | #endif 1130 | 1131 | std::string err_mtl; 1132 | bool ok = readMatFn(namebuf, materials, material_map, err_mtl); 1133 | err += err_mtl; 1134 | 1135 | if (!ok) { 1136 | faceGroup.clear(); // for safety 1137 | return false; 1138 | } 1139 | 1140 | continue; 1141 | } 1142 | 1143 | // group name 1144 | if (token[0] == 'g' && IS_SPACE((token[1]))) { 1145 | 1146 | // flush previous face group. 1147 | bool ret = 1148 | exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, tags, 1149 | material, name, true, flags, err ); 1150 | if (ret) { 1151 | shapes.push_back(shape); 1152 | } 1153 | 1154 | shape = shape_t(); 1155 | 1156 | // material = -1; 1157 | faceGroup.clear(); 1158 | 1159 | std::vector names; 1160 | names.reserve(2); 1161 | 1162 | while (!IS_NEW_LINE(token[0])) { 1163 | std::string str = parseString(token); 1164 | names.push_back(str); 1165 | token += strspn(token, " \t\r"); // skip tag 1166 | } 1167 | 1168 | assert(names.size() > 0); 1169 | 1170 | // names[0] must be 'g', so skip the 0th element. 1171 | if (names.size() > 1) { 1172 | name = names[1]; 1173 | } else { 1174 | name = ""; 1175 | } 1176 | 1177 | continue; 1178 | } 1179 | 1180 | // object name 1181 | if (token[0] == 'o' && IS_SPACE((token[1]))) { 1182 | 1183 | // flush previous face group. 1184 | bool ret = 1185 | exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, tags, 1186 | material, name, true, flags, err ); 1187 | if (ret) { 1188 | shapes.push_back(shape); 1189 | } 1190 | 1191 | // material = -1; 1192 | faceGroup.clear(); 1193 | shape = shape_t(); 1194 | 1195 | // @todo { multiple object name? } 1196 | char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; 1197 | token += 2; 1198 | #ifdef _MSC_VER 1199 | sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); 1200 | #else 1201 | sscanf(token, "%s", namebuf); 1202 | #endif 1203 | name = std::string(namebuf); 1204 | 1205 | continue; 1206 | } 1207 | 1208 | if (token[0] == 't' && IS_SPACE(token[1])) { 1209 | tag_t tag; 1210 | 1211 | char namebuf[4096]; 1212 | token += 2; 1213 | #ifdef _MSC_VER 1214 | sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); 1215 | #else 1216 | sscanf(token, "%s", namebuf); 1217 | #endif 1218 | tag.name = std::string(namebuf); 1219 | 1220 | token += tag.name.size() + 1; 1221 | 1222 | tag_sizes ts = parseTagTriple(token); 1223 | 1224 | tag.intValues.resize(static_cast(ts.num_ints)); 1225 | 1226 | for (size_t i = 0; i < static_cast(ts.num_ints); ++i) { 1227 | tag.intValues[i] = atoi(token); 1228 | token += strcspn(token, "/ \t\r") + 1; 1229 | } 1230 | 1231 | tag.floatValues.resize(static_cast(ts.num_floats)); 1232 | for (size_t i = 0; i < static_cast(ts.num_floats); ++i) { 1233 | tag.floatValues[i] = parseFloat(token); 1234 | token += strcspn(token, "/ \t\r") + 1; 1235 | } 1236 | 1237 | tag.stringValues.resize(static_cast(ts.num_strings)); 1238 | for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { 1239 | char stringValueBuffer[4096]; 1240 | 1241 | #ifdef _MSC_VER 1242 | sscanf_s(token, "%s", stringValueBuffer, (unsigned)_countof(stringValueBuffer)); 1243 | #else 1244 | sscanf(token, "%s", stringValueBuffer); 1245 | #endif 1246 | tag.stringValues[i] = stringValueBuffer; 1247 | token += tag.stringValues[i].size() + 1; 1248 | } 1249 | 1250 | tags.push_back(tag); 1251 | } 1252 | 1253 | // Ignore unknown command. 1254 | } 1255 | 1256 | bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, 1257 | tags, material, name, true, flags, err ); 1258 | if (ret) { 1259 | shapes.push_back(shape); 1260 | } 1261 | faceGroup.clear(); // for safety 1262 | 1263 | err += errss.str(); 1264 | 1265 | return true; 1266 | } 1267 | 1268 | } // namespace 1269 | 1270 | #endif 1271 | 1272 | #endif // TINY_OBJ_LOADER_H_ 1273 | --------------------------------------------------------------------------------