├── .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 |
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