From 55e657852c8b73ab4509bf6b58fc61aa4db597a7 Mon Sep 17 00:00:00 2001 From: Jeremy Hu Date: Thu, 2 Apr 2020 20:09:57 +0930 Subject: [PATCH] Add menu: Export as Image New feature: Exporting rendered model as png. A offscreen render been introduced for generating normal and depth maps, which are been generated for toon shader edge detection. --- dust3d.pro | 6 + languages/dust3d_es_AR.ts | 10 +- languages/dust3d_it_IT.ts | 14 ++- languages/dust3d_zh_CN.ts | 10 +- shaders/default.core.frag | 156 +++++++++++++++++++++-- shaders/default.frag | 157 +++++++++++++++++++++-- shaders/default.vert | 1 + src/documentwindow.cpp | 113 ++++++++++++++++- src/documentwindow.h | 11 ++ src/logbrowser.cpp | 1 + src/modelmeshbinder.cpp | 74 +++++++++++ src/modelmeshbinder.h | 11 ++ src/modelofflinerender.cpp | 186 ++++++++++++++++++++++++++++ src/modelofflinerender.h | 39 ++++++ src/modelshaderprogram.cpp | 44 ++++++- src/modelshaderprogram.h | 16 ++- src/modelwidget.cpp | 53 +++++++- src/modelwidget.h | 8 ++ src/normalanddepthmapsgenerator.cpp | 70 +++++++++++ src/normalanddepthmapsgenerator.h | 34 +++++ src/preferences.cpp | 26 ++-- src/preferences.h | 8 +- src/preferenceswidget.cpp | 12 +- 23 files changed, 998 insertions(+), 62 deletions(-) create mode 100644 src/modelofflinerender.cpp create mode 100644 src/modelofflinerender.h create mode 100644 src/normalanddepthmapsgenerator.cpp create mode 100644 src/normalanddepthmapsgenerator.h diff --git a/dust3d.pro b/dust3d.pro index e3856ec5..9fc90ad4 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -134,6 +134,12 @@ include(thirdparty/QtAwesome/QtAwesome/QtAwesome.pri) INCLUDEPATH += src +SOURCES += src/normalanddepthmapsgenerator.cpp +HEADERS += src/normalanddepthmapsgenerator.h + +SOURCES += src/modelofflinerender.cpp +HEADERS += src/modelofflinerender.h + SOURCES += src/modelshaderprogram.cpp HEADERS += src/modelshaderprogram.h diff --git a/languages/dust3d_es_AR.ts b/languages/dust3d_es_AR.ts index 385c4d34..6de99229 100644 --- a/languages/dust3d_es_AR.ts +++ b/languages/dust3d_es_AR.ts @@ -391,6 +391,14 @@ Consejos: Rotation Rotación + + Export as Image... + + + + Image (*.png) + + ExportPreviewWidget @@ -969,7 +977,7 @@ Consejos: - Tong shading: + Toon shading: diff --git a/languages/dust3d_it_IT.ts b/languages/dust3d_it_IT.ts index c43c23e3..aa4b5dcb 100755 --- a/languages/dust3d_it_IT.ts +++ b/languages/dust3d_it_IT.ts @@ -71,8 +71,8 @@ Tips: - Make multiple parts instead of one single part for whole model Generazione mesh non riuscita, annulla o modifica i nodi modificati di recente Suggerimenti: -   - Non lasciare che la mesh generata si intersechi in se stessa -   - Crea più parti invece di una singola parte per l'intero modello +   - Non lasciare che la mesh generata si intersechi in se stessa +   - Crea più parti invece di una singola parte per l'intero modello Parts @@ -398,6 +398,14 @@ Suggerimenti: Enter zoom out mode + + Export as Image... + + + + Image (*.png) + + ExportPreviewWidget @@ -976,7 +984,7 @@ Suggerimenti: - Tong shading: + Toon shading: diff --git a/languages/dust3d_zh_CN.ts b/languages/dust3d_zh_CN.ts index 552bf10d..871dfe61 100644 --- a/languages/dust3d_zh_CN.ts +++ b/languages/dust3d_zh_CN.ts @@ -391,6 +391,14 @@ Tips: Script 脚本 + + Export as Image... + 导出成图片... + + + Image (*.png) + 图片 (*.png) + ExportPreviewWidget @@ -969,7 +977,7 @@ Tips: 纹理大小: - Tong shading: + Toon shading: 卡通渲染 diff --git a/shaders/default.core.frag b/shaders/default.core.frag index 29496534..6a1e6800 100644 --- a/shaders/default.core.frag +++ b/shaders/default.core.frag @@ -86,7 +86,13 @@ uniform samplerCube environmentIrradianceMapId; uniform int environmentIrradianceMapEnabled; uniform samplerCube environmentSpecularMapId; uniform int environmentSpecularMapEnabled; -uniform int tongShadingEnabled; +uniform int toonShadingEnabled; +uniform int renderPurpose; +uniform int toonEdgeEnabled; +uniform float screenWidth; +uniform float screenHeight; +uniform sampler2D toonNormalMapId; +uniform sampler2D toonDepthMapId; const int MAX_LIGHTS = 8; const int TYPE_POINT = 0; @@ -106,6 +112,122 @@ struct Light { int lightCount; Light lights[MAX_LIGHTS]; +float sobel_v[9]; +float sobel_h[9]; + +float normalEdgeSobel() +{ + sobel_v[0] = -1.0; + sobel_v[1] = 0.0; + sobel_v[2] = 1.0; + sobel_v[3] = -2.0; + sobel_v[4] = 0.0; + sobel_v[5] = 2.0; + sobel_v[6] = -1.0; + sobel_v[7] = 0.0; + sobel_v[8] = 1.0; + + sobel_h[0] = -1.0; + sobel_h[1] = -2.0; + sobel_h[2] = -1.0; + sobel_h[3] = 0.0; + sobel_h[4] = 0.0; + sobel_h[5] = 0.0; + sobel_h[6] = 1.0; + sobel_h[7] = 2.0; + sobel_h[8] = 1.0; + + // vec2 coord = gl_TexCoord[0].st; + vec2 coord = vec2(gl_FragCoord.x / screenWidth, 1.0 - gl_FragCoord.y / screenHeight); + //float len = length(coord); + + float sx = 1.0 / screenWidth; + float sy = 1.0 / screenHeight; + float n[9]; + vec3 ref = vec3(1.0, 1.0, 1.0); + + n[0] = dot(texture(toonNormalMapId, vec2(coord.x - sx, coord.y - sy)).rgb, ref); + n[1] = dot(texture(toonNormalMapId, vec2(coord.x, coord.y - sy)).rgb, ref); + n[2] = dot(texture(toonNormalMapId, vec2(coord.x + sx, coord.y - sy)).rgb, ref); + n[3] = dot(texture(toonNormalMapId, vec2(coord.x - sx, coord.y)).rgb, ref); + n[4] = dot(texture(toonNormalMapId, vec2(coord.x, coord.y)).rgb, ref); + n[5] = dot(texture(toonNormalMapId, vec2(coord.x + sx, coord.y)).rgb, ref); + n[6] = dot(texture(toonNormalMapId, vec2(coord.x - sx, coord.y + sy)).rgb, ref); + n[7] = dot(texture(toonNormalMapId, vec2(coord.x, coord.y + sy)).rgb, ref); + n[8] = dot(texture(toonNormalMapId, vec2(coord.x + sx, coord.y + sy)).rgb, ref); + + float v, h; + + v = 0.0; + h = 0.0; + + for (int i = 0; i < 9; ++i) { + v += sobel_v[i] * n[i]; + h += sobel_h[i] * n[i]; + } + + float enhanceFactor = 1.0; + + float r = sqrt(v * v * enhanceFactor + h * h * enhanceFactor); + return smoothstep(0.0, 1.0, r); +} + +float depthEdgeSobel() +{ + sobel_v[0] = -1; + sobel_v[1] = 0; + sobel_v[2] = 1; + sobel_v[3] = -2; + sobel_v[4] = 0; + sobel_v[5] = 2; + sobel_v[6] = -1; + sobel_v[7] = 0; + sobel_v[8] = 1; + + sobel_h[0] = -1; + sobel_h[1] = -2; + sobel_h[2] = -1; + sobel_h[3] = 0; + sobel_h[4] = 0; + sobel_h[5] = 0; + sobel_h[6] = 1; + sobel_h[7] = 2; + sobel_h[8] = 1; + + // vec2 coord = gl_TexCoord[0].st; + vec2 coord = vec2(gl_FragCoord.x / screenWidth, 1.0 - gl_FragCoord.y / screenHeight); + //float len = length(coord); + + float sx = 1.0 / screenWidth; + float sy = 1.0 / screenHeight; + float n[9]; + + n[0] = texture(toonDepthMapId, vec2(coord.x - sx, coord.y - sy)).r; + n[1] = texture(toonDepthMapId, vec2(coord.x, coord.y - sy)).r; + n[2] = texture(toonDepthMapId, vec2(coord.x + sx, coord.y - sy)).r; + n[3] = texture(toonDepthMapId, vec2(coord.x - sx, coord.y)).r; + n[4] = texture(toonDepthMapId, vec2(coord.x, coord.y)).r; + n[5] = texture(toonDepthMapId, vec2(coord.x + sx, coord.y)).r; + n[6] = texture(toonDepthMapId, vec2(coord.x - sx, coord.y + sy)).r; + n[7] = texture(toonDepthMapId, vec2(coord.x, coord.y + sy)).r; + n[8] = texture(toonDepthMapId, vec2(coord.x + sx, coord.y + sy)).r; + + float v, h; + + v = 0.0; + h = 0.0; + + for (int i = 0; i < 9; ++i) { + v += sobel_v[i] * n[i]; + h += sobel_h[i] * n[i]; + } + + float enhanceFactor = 10.0; + + float r = sqrt(v * v * enhanceFactor + h * h * enhanceFactor); + return smoothstep(0.0, 1.0, r); +} + int mipLevelCount(const in samplerCube cube) { int baseSize = textureSize(cube, 0).x; @@ -385,7 +507,7 @@ vec4 metalRoughFunction(const in vec4 baseColor, // Remap roughness for a perceptually more linear correspondence float alpha = remapRoughness(roughness); - if (tongShadingEnabled != 1) { + if (toonShadingEnabled != 1) { if (environmentIrradianceMapEnabled == 1) { cLinear += pbrIblModel(worldNormal, worldView, @@ -414,6 +536,14 @@ vec4 metalRoughFunction(const in vec4 baseColor, cLinear = hsv2rgb(vec3(hsv.r, hsv.g, hsv.b * 2.0)); else cLinear = hsv2rgb(vec3(hsv.r, hsv.g, hsv.b * 0.1)); + + if (toonEdgeEnabled == 1) { + float depthEdge = depthEdgeSobel(); + float normalEdge = normalEdgeSobel(); + if (depthEdge >= 0.009 || normalEdge >= 0.6) { + cLinear = hsv2rgb(vec3(hsv.r, hsv.g, hsv.b * 0.02)); + } + } } // Apply exposure correction @@ -508,12 +638,18 @@ void main() roughness = min(0.99, roughness); metalness = min(0.99, metalness); } - - fragColor = metalRoughFunction(vec4(color, alpha), - metalness, - roughness, - ambientOcclusion, - vert, - normalize(cameraPos - vert), - normal); + + if (renderPurpose == 0) { + fragColor = metalRoughFunction(vec4(color, alpha), + metalness, + roughness, + ambientOcclusion, + vert, + normalize(cameraPos - vert), + normal); + } else if (renderPurpose == 1) { + fragColor = vec4(normal, 1.0); + } else if (renderPurpose == 2) { + fragColor = vec4(vec3(gl_FragCoord.w), 1.0); + } } diff --git a/shaders/default.frag b/shaders/default.frag index 8714b995..162bf6d7 100644 --- a/shaders/default.frag +++ b/shaders/default.frag @@ -1,3 +1,4 @@ +#version 110 /**************************************************************************** ** ** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). @@ -80,7 +81,13 @@ uniform int ambientOcclusionMapEnabled; uniform int mousePickEnabled; uniform vec3 mousePickTargetPosition; uniform float mousePickRadius; -uniform int tongShadingEnabled; +uniform int toonShadingEnabled; +uniform int renderPurpose; +uniform int toonEdgeEnabled; +uniform float screenWidth; +uniform float screenHeight; +uniform sampler2D toonNormalMapId; +uniform sampler2D toonDepthMapId; const int MAX_LIGHTS = 8; const int TYPE_POINT = 0; @@ -100,6 +107,122 @@ struct Light { int lightCount; Light lights[MAX_LIGHTS]; +float sobel_v[9]; +float sobel_h[9]; + +float normalEdgeSobel() +{ + sobel_v[0] = -1.0; + sobel_v[1] = 0.0; + sobel_v[2] = 1.0; + sobel_v[3] = -2.0; + sobel_v[4] = 0.0; + sobel_v[5] = 2.0; + sobel_v[6] = -1.0; + sobel_v[7] = 0.0; + sobel_v[8] = 1.0; + + sobel_h[0] = -1.0; + sobel_h[1] = -2.0; + sobel_h[2] = -1.0; + sobel_h[3] = 0.0; + sobel_h[4] = 0.0; + sobel_h[5] = 0.0; + sobel_h[6] = 1.0; + sobel_h[7] = 2.0; + sobel_h[8] = 1.0; + + // vec2 coord = gl_TexCoord[0].st; + vec2 coord = vec2(gl_FragCoord.x / screenWidth - 1.0, 1.0 - gl_FragCoord.y / screenHeight); + //float len = length(coord); + + float sx = 1.0 / screenWidth; + float sy = 1.0 / screenHeight; + float n[9]; + vec3 ref = vec3(1.0, 1.0, 1.0); + + n[0] = dot(texture2D(toonNormalMapId, vec2(coord.x - sx, coord.y - sy)).rgb, ref); + n[1] = dot(texture2D(toonNormalMapId, vec2(coord.x, coord.y - sy)).rgb, ref); + n[2] = dot(texture2D(toonNormalMapId, vec2(coord.x + sx, coord.y - sy)).rgb, ref); + n[3] = dot(texture2D(toonNormalMapId, vec2(coord.x - sx, coord.y)).rgb, ref); + n[4] = dot(texture2D(toonNormalMapId, vec2(coord.x, coord.y)).rgb, ref); + n[5] = dot(texture2D(toonNormalMapId, vec2(coord.x + sx, coord.y)).rgb, ref); + n[6] = dot(texture2D(toonNormalMapId, vec2(coord.x - sx, coord.y + sy)).rgb, ref); + n[7] = dot(texture2D(toonNormalMapId, vec2(coord.x, coord.y + sy)).rgb, ref); + n[8] = dot(texture2D(toonNormalMapId, vec2(coord.x + sx, coord.y + sy)).rgb, ref); + + float v, h; + + v = 0.0; + h = 0.0; + + for (int i = 0; i < 9; ++i) { + v += sobel_v[i] * n[i]; + h += sobel_h[i] * n[i]; + } + + float enhanceFactor = 1.0; + + float r = sqrt(v * v * enhanceFactor + h * h * enhanceFactor); + return smoothstep(0.0, 1.0, r); +} + +float depthEdgeSobel() +{ + sobel_v[0] = -1.0; + sobel_v[1] = 0.0; + sobel_v[2] = 1.0; + sobel_v[3] = -2.0; + sobel_v[4] = 0.0; + sobel_v[5] = 2.0; + sobel_v[6] = -1.0; + sobel_v[7] = 0.0; + sobel_v[8] = 1.0; + + sobel_h[0] = -1.0; + sobel_h[1] = -2.0; + sobel_h[2] = -1.0; + sobel_h[3] = 0.0; + sobel_h[4] = 0.0; + sobel_h[5] = 0.0; + sobel_h[6] = 1.0; + sobel_h[7] = 2.0; + sobel_h[8] = 1.0; + + // vec2 coord = gl_TexCoord[0].st; + vec2 coord = vec2(gl_FragCoord.x / screenWidth - 1.0, 1.0 - gl_FragCoord.y / screenHeight); + //float len = length(coord); + + float sx = 1.0 / screenWidth; + float sy = 1.0 / screenHeight; + float n[9]; + + n[0] = texture2D(toonDepthMapId, vec2(coord.x - sx, coord.y - sy)).r; + n[1] = texture2D(toonDepthMapId, vec2(coord.x, coord.y - sy)).r; + n[2] = texture2D(toonDepthMapId, vec2(coord.x + sx, coord.y - sy)).r; + n[3] = texture2D(toonDepthMapId, vec2(coord.x - sx, coord.y)).r; + n[4] = texture2D(toonDepthMapId, vec2(coord.x, coord.y)).r; + n[5] = texture2D(toonDepthMapId, vec2(coord.x + sx, coord.y)).r; + n[6] = texture2D(toonDepthMapId, vec2(coord.x - sx, coord.y + sy)).r; + n[7] = texture2D(toonDepthMapId, vec2(coord.x, coord.y + sy)).r; + n[8] = texture2D(toonDepthMapId, vec2(coord.x + sx, coord.y + sy)).r; + + float v, h; + + v = 0.0; + h = 0.0; + + for (int i = 0; i < 9; ++i) { + v += sobel_v[i] * n[i]; + h += sobel_h[i] * n[i]; + } + + float enhanceFactor = 10.0; + + float r = sqrt(v * v * enhanceFactor + h * h * enhanceFactor); + return smoothstep(0.0, 1.0, r); +} + float remapRoughness(const in float roughness) { // As per page 14 of @@ -281,7 +404,7 @@ vec4 metalRoughFunction(const in vec4 baseColor, // Remap roughness for a perceptually more linear correspondence float alpha = remapRoughness(roughness); - if (tongShadingEnabled != 1) { + if (toonShadingEnabled != 1) { for (int i = 0; i < lightCount; ++i) { cLinear += pbrModel(i, worldPosition, @@ -301,6 +424,14 @@ vec4 metalRoughFunction(const in vec4 baseColor, cLinear = hsv2rgb(vec3(hsv.r, hsv.g, hsv.b * 2.0)); else cLinear = hsv2rgb(vec3(hsv.r, hsv.g, hsv.b * 0.1)); + + if (toonEdgeEnabled == 1) { + float depthEdge = depthEdgeSobel(); + float normalEdge = normalEdgeSobel(); + if (depthEdge >= 0.009 || normalEdge >= 0.6) { + cLinear = hsv2rgb(vec3(hsv.r, hsv.g, hsv.b * 0.02)); + } + } } // Apply exposure correction @@ -393,12 +524,18 @@ void main() roughness = min(0.99, roughness); metalness = min(0.99, metalness); - - gl_FragColor = metalRoughFunction(vec4(color, alpha), - metalness, - roughness, - ambientOcclusion, - vert, - normalize(cameraPos - vert), - normal); + + if (renderPurpose == 0) { + gl_FragColor = metalRoughFunction(vec4(color, alpha), + metalness, + roughness, + ambientOcclusion, + vert, + normalize(cameraPos - vert), + normal); + } else if (renderPurpose == 1) { + gl_FragColor = vec4(normal, 1.0); + } else if (renderPurpose == 2) { + gl_FragColor = vec4(vec3(gl_FragCoord.w), 1.0); + } } diff --git a/shaders/default.vert b/shaders/default.vert index e9e86e49..423ca484 100644 --- a/shaders/default.vert +++ b/shaders/default.vert @@ -1,3 +1,4 @@ +#version 110 attribute vec4 vertex; attribute vec3 normal; attribute vec3 color; diff --git a/src/documentwindow.cpp b/src/documentwindow.cpp index 11dce9bc..e54b140c 100644 --- a/src/documentwindow.cpp +++ b/src/documentwindow.cpp @@ -42,6 +42,7 @@ #include "scriptwidget.h" #include "variablesxml.h" #include "updatescheckwidget.h" +#include "modelofflinerender.h" int DocumentWindow::m_modelRenderWidgetInitialX = 16; int DocumentWindow::m_modelRenderWidgetInitialY = 16; @@ -310,7 +311,8 @@ DocumentWindow::DocumentWindow() : m_modelRenderWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); m_modelRenderWidget->move(DocumentWindow::m_modelRenderWidgetInitialX, DocumentWindow::m_modelRenderWidgetInitialY); m_modelRenderWidget->setMousePickRadius(m_document->mousePickRadius()); - m_modelRenderWidget->toggleWireframe(); + if (!Preferences::instance().toonShading()) + m_modelRenderWidget->toggleWireframe(); m_modelRenderWidget->enableEnvironmentLight(); connect(m_modelRenderWidget, &ModelWidget::mouseRayChanged, m_document, @@ -341,6 +343,8 @@ DocumentWindow::DocumentWindow() : m_modelRenderWidget->setMousePickTargetPositionInModelSpace(m_document->mouseTargetPosition()); }); + connect(m_modelRenderWidget, &ModelWidget::renderParametersChanged, this, &DocumentWindow::delayedGenerateNormalAndDepthMaps); + m_graphicsWidget->setModelWidget(m_modelRenderWidget); containerWidget->setModelWidget(m_modelRenderWidget); @@ -494,9 +498,9 @@ DocumentWindow::DocumentWindow() : connect(m_exportAsObjAction, &QAction::triggered, this, &DocumentWindow::exportObjResult, Qt::QueuedConnection); m_fileMenu->addAction(m_exportAsObjAction); - //m_exportRenderedAsImageAction = new QAction(tr("Export as PNG..."), this); - //connect(m_exportRenderedAsImageAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportRenderedResult, Qt::QueuedConnection); - //m_fileMenu->addAction(m_exportRenderedAsImageAction); + m_exportRenderedAsImageAction = new QAction(tr("Export as Image..."), this); + connect(m_exportRenderedAsImageAction, &QAction::triggered, this, &DocumentWindow::exportRenderedResult, Qt::QueuedConnection); + m_fileMenu->addAction(m_exportRenderedAsImageAction); //m_exportAsObjPlusMaterialsAction = new QAction(tr("Wavefront (.obj + .mtl)..."), this); //connect(m_exportAsObjPlusMaterialsAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportObjPlusMaterialsResult, Qt::QueuedConnection); @@ -518,7 +522,7 @@ DocumentWindow::DocumentWindow() : m_exportAsObjAction->setEnabled(m_graphicsWidget->hasItems()); //m_exportAsObjPlusMaterialsAction->setEnabled(m_graphicsWidget->hasItems()); m_exportAction->setEnabled(m_graphicsWidget->hasItems()); - //m_exportRenderedAsImageAction->setEnabled(m_graphicsWidget->hasItems()); + m_exportRenderedAsImageAction->setEnabled(m_graphicsWidget->hasItems()); }); m_editMenu = menuBar()->addMenu(tr("&Edit")); @@ -1603,6 +1607,16 @@ void DocumentWindow::showPreferences() m_preferencesWidget->raise(); } +void DocumentWindow::exportRenderedResult() +{ + QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), + tr("Image (*.png)")); + if (filename.isEmpty()) { + return; + } + exportImageToFilename(filename); +} + void DocumentWindow::exportObjResult() { QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), @@ -1940,3 +1954,92 @@ void DocumentWindow::checkExportWaitingList() // m_infoWidget->move(0, m_graphicsContainerWidget->height() - m_infoWidget->height() - 5); //} +void DocumentWindow::normalAndDepthMapsReady() +{ + QImage *normalMap = m_normalAndDepthMapsGenerator->takeNormalMap(); + QImage *depthMap = m_normalAndDepthMapsGenerator->takeDepthMap(); + + m_modelRenderWidget->updateToonNormalAndDepthMaps(normalMap, depthMap); + + //m_normalAndDepthMapsGenerator->setRenderThread(QGuiApplication::instance()->thread()); + + delete m_normalAndDepthMapsGenerator; + m_normalAndDepthMapsGenerator = nullptr; + + if (m_isNormalAndDepthMapsObsolete) { + generateNormalAndDepthMaps(); + } +} + +void DocumentWindow::generateNormalAndDepthMaps() +{ + if (nullptr != m_normalAndDepthMapsGenerator) { + m_isNormalAndDepthMapsObsolete = true; + return; + } + + m_isNormalAndDepthMapsObsolete = false; + + auto resultMesh = m_document->takeResultMesh(); + if (nullptr == resultMesh) + return; + + QThread *thread = new QThread; + m_normalAndDepthMapsGenerator = new NormalAndDepthMapsGenerator(m_modelRenderWidget); + m_normalAndDepthMapsGenerator->updateMesh(resultMesh); + m_normalAndDepthMapsGenerator->moveToThread(thread); + //m_normalAndDepthMapsGenerator->setRenderThread(thread); + connect(thread, &QThread::started, m_normalAndDepthMapsGenerator, &NormalAndDepthMapsGenerator::process); + connect(m_normalAndDepthMapsGenerator, &NormalAndDepthMapsGenerator::finished, this, &DocumentWindow::normalAndDepthMapsReady); + connect(m_normalAndDepthMapsGenerator, &NormalAndDepthMapsGenerator::finished, thread, &QThread::quit); + connect(thread, &QThread::finished, thread, &QThread::deleteLater); + thread->start(); + + //m_normalAndDepthMapsGenerator = new NormalAndDepthMapsGenerator(m_modelRenderWidget); + //m_normalAndDepthMapsGenerator->updateMesh(resultMesh); + //connect(m_normalAndDepthMapsGenerator, &NormalAndDepthMapsGenerator::finished, this, &DocumentWindow::normalAndDepthMapsReady); + //m_normalAndDepthMapsGenerator->process(); +} + +void DocumentWindow::delayedGenerateNormalAndDepthMaps() +{ + if (!Preferences::instance().toonShading()) + return; + + //delete m_normalAndDepthMapsDelayTimer; + //m_normalAndDepthMapsDelayTimer = new QTimer(this); + //m_normalAndDepthMapsDelayTimer->setSingleShot(true); + //m_normalAndDepthMapsDelayTimer->setInterval(250); + //connect(m_normalAndDepthMapsDelayTimer, &QTimer::timeout, [=] { + generateNormalAndDepthMaps(); + //}); + //m_normalAndDepthMapsDelayTimer->start(); +} + +void DocumentWindow::exportImageToFilename(const QString &filename) +{ + QApplication::setOverrideCursor(Qt::WaitCursor); + MeshLoader *resultMesh = m_modelRenderWidget->fetchCurrentMesh(); + if (nullptr != resultMesh) { + ModelOfflineRender *offlineRender = new ModelOfflineRender(m_modelRenderWidget->format()); + offlineRender->setXRotation(m_modelRenderWidget->xRot()); + offlineRender->setYRotation(m_modelRenderWidget->yRot()); + offlineRender->setZRotation(m_modelRenderWidget->zRot()); + offlineRender->setRenderPurpose(0); + QImage *normalMap = new QImage(); + QImage *depthMap = new QImage(); + m_modelRenderWidget->fetchCurrentToonNormalAndDepthMaps(normalMap, depthMap); + if (!normalMap->isNull() && !depthMap->isNull()) { + offlineRender->updateToonNormalAndDepthMaps(normalMap, depthMap); + } else { + delete normalMap; + delete depthMap; + } + offlineRender->updateMesh(resultMesh); + if (Preferences::instance().toonShading()) + offlineRender->setToonShading(true); + offlineRender->toImage(QSize(m_modelRenderWidget->widthInPixels(), + m_modelRenderWidget->heightInPixels())).save(filename); + } + QApplication::restoreOverrideCursor(); +} diff --git a/src/documentwindow.h b/src/documentwindow.h index 581325e3..26c7659d 100644 --- a/src/documentwindow.h +++ b/src/documentwindow.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "document.h" #include "modelwidget.h" #include "exportpreviewwidget.h" @@ -18,6 +19,7 @@ #include "posemanagewidget.h" #include "preferenceswidget.h" #include "graphicscontainerwidget.h" +#include "normalanddepthmapsgenerator.h" class SkeletonGraphicsWidget; @@ -49,6 +51,7 @@ public slots: void open(); void openExample(const QString &modelName); void openPathAs(const QString &path, const QString &asName); + void exportRenderedResult(); void exportObjResult(); void exportGlbResult(); void exportFbxResult(); @@ -78,11 +81,15 @@ public slots: void showCutFaceSettingPopup(const QPoint &globalPos, std::set nodeIds); void setExportWaitingList(const QStringList &filenames); void checkExportWaitingList(); + void exportImageToFilename(const QString &filename); void exportObjToFilename(const QString &filename); void exportFbxToFilename(const QString &filename); void exportGlbToFilename(const QString &filename); void toggleRotation(); //void updateInfoWidgetPosition(); + void generateNormalAndDepthMaps(); + void delayedGenerateNormalAndDepthMaps(); + void normalAndDepthMapsReady(); private: void initLockButton(QPushButton *button); void setCurrentFilename(const QString &filename); @@ -199,6 +206,10 @@ private: QPushButton *m_radiusLockButton; QMetaObject::Connection m_partListDockerVisibleSwitchConnection; + + NormalAndDepthMapsGenerator *m_normalAndDepthMapsGenerator = nullptr; + QTimer *m_normalAndDepthMapsDelayTimer = nullptr; + bool m_isNormalAndDepthMapsObsolete = false; public: static int m_modelRenderWidgetInitialX; static int m_modelRenderWidgetInitialY; diff --git a/src/logbrowser.cpp b/src/logbrowser.cpp index cd9b3834..f884af03 100644 --- a/src/logbrowser.cpp +++ b/src/logbrowser.cpp @@ -1,6 +1,7 @@ #include "logbrowser.h" // Modified from https://wiki.qt.io/Browser_for_QDebug_output #include +#include #include "logbrowserdialog.h" LogBrowser::LogBrowser(QObject *parent) : diff --git a/src/modelmeshbinder.cpp b/src/modelmeshbinder.cpp index 6e509bef..2bd2cd40 100644 --- a/src/modelmeshbinder.cpp +++ b/src/modelmeshbinder.cpp @@ -23,6 +23,12 @@ ModelMeshBinder::~ModelMeshBinder() delete m_metalnessRoughnessAmbientOcclusionMap; delete m_environmentIrradianceMap; delete m_environmentSpecularMap; + delete m_toonNormalMap; + delete m_toonDepthMap; + delete m_newToonNormalMap; + delete m_newToonDepthMap; + delete m_currentToonNormalMap; + delete m_currentToonDepthMap; } void ModelMeshBinder::updateMesh(MeshLoader *mesh) @@ -61,6 +67,14 @@ void ModelMeshBinder::enableEnvironmentLight() m_environmentLightEnabled = true; } +MeshLoader *ModelMeshBinder::fetchCurrentMesh() +{ + QMutexLocker lock(&m_meshMutex); + if (nullptr == m_mesh) + return nullptr; + return new MeshLoader(*m_mesh); +} + void ModelMeshBinder::paint(ModelShaderProgram *program) { MeshLoader *newMesh = nullptr; @@ -74,6 +88,33 @@ void ModelMeshBinder::paint(ModelShaderProgram *program) hasNewMesh = true; } } + if (m_newToonMapsComing) { + QMutexLocker lock(&m_toonNormalAndDepthMapMutex); + if (m_newToonMapsComing) { + + delete m_toonNormalMap; + m_toonNormalMap = nullptr; + delete m_currentToonNormalMap; + m_currentToonNormalMap = nullptr; + if (nullptr != m_newToonNormalMap) { + m_toonNormalMap = new QOpenGLTexture(*m_newToonNormalMap); + m_currentToonNormalMap = m_newToonNormalMap; + m_newToonNormalMap = nullptr; + } + + delete m_toonDepthMap; + m_toonDepthMap = nullptr; + delete m_currentToonDepthMap; + m_currentToonDepthMap = nullptr; + if (nullptr != m_newToonDepthMap) { + m_toonDepthMap = new QOpenGLTexture(*m_newToonDepthMap); + m_currentToonDepthMap = m_newToonDepthMap; + m_newToonDepthMap = nullptr; + } + + m_newToonMapsComing = false; + } + } { QMutexLocker lock(&m_meshMutex); if (hasNewMesh) { @@ -218,6 +259,8 @@ void ModelMeshBinder::paint(ModelShaderProgram *program) program->setUniformValue(program->metalnessRoughnessAmbientOcclusionMapIdLoc(), 2); program->setUniformValue(program->environmentIrradianceMapIdLoc(), 3); program->setUniformValue(program->environmentSpecularMapIdLoc(), 4); + program->setUniformValue(program->toonNormalMapIdLoc(), 5); + program->setUniformValue(program->toonDepthMapIdLoc(), 6); if (m_showWireframes) { if (m_renderEdgeVertexCount > 0) { QOpenGLVertexArrayObject::Binder vaoBinder(&m_vaoEdge); @@ -275,6 +318,11 @@ void ModelMeshBinder::paint(ModelShaderProgram *program) program->setUniformValue(program->environmentSpecularMapEnabledLoc(), 0); } } + if (nullptr != m_toonNormalMap && nullptr != m_toonDepthMap) { + m_toonNormalMap->bind(5); + m_toonDepthMap->bind(6); + program->setUniformValue(program->toonEdgeEnabledLoc(), 1); + } f->glDrawArrays(GL_TRIANGLES, 0, m_renderTriangleVertexCount); } if (m_toolEnabled) { @@ -295,6 +343,28 @@ void ModelMeshBinder::paint(ModelShaderProgram *program) } } +void ModelMeshBinder::fetchCurrentToonNormalAndDepthMaps(QImage *normalMap, QImage *depthMap) +{ + QMutexLocker lock(&m_toonNormalAndDepthMapMutex); + if (nullptr != normalMap && nullptr != m_currentToonNormalMap) + *normalMap = *m_currentToonNormalMap; + if (nullptr != depthMap && nullptr != m_currentToonDepthMap) + *depthMap = *m_currentToonDepthMap; +} + +void ModelMeshBinder::updateToonNormalAndDepthMaps(QImage *normalMap, QImage *depthMap) +{ + QMutexLocker lock(&m_toonNormalAndDepthMapMutex); + + delete m_newToonNormalMap; + m_newToonNormalMap = normalMap; + + delete m_newToonDepthMap; + m_newToonDepthMap = depthMap; + + m_newToonMapsComing = true; +} + void ModelMeshBinder::cleanup() { if (m_vboTriangle.isCreated()) @@ -315,6 +385,10 @@ void ModelMeshBinder::cleanup() m_environmentIrradianceMap = nullptr; delete m_environmentSpecularMap; m_environmentSpecularMap = nullptr; + delete m_toonNormalMap; + m_toonNormalMap = nullptr; + delete m_toonDepthMap; + m_toonDepthMap = nullptr; } void ModelMeshBinder::showWireframes() diff --git a/src/modelmeshbinder.h b/src/modelmeshbinder.h index dc03de9a..ad4108a5 100644 --- a/src/modelmeshbinder.h +++ b/src/modelmeshbinder.h @@ -13,6 +13,7 @@ class ModelMeshBinder public: ModelMeshBinder(bool toolEnabled=false); ~ModelMeshBinder(); + MeshLoader *fetchCurrentMesh(); void updateMesh(MeshLoader *mesh); void initialize(); void paint(ModelShaderProgram *program); @@ -25,6 +26,8 @@ public: void enableEnvironmentLight(); bool isCheckUvEnabled(); void reloadMesh(); + void fetchCurrentToonNormalAndDepthMaps(QImage *normalMap, QImage *depthMap); + void updateToonNormalAndDepthMaps(QImage *normalMap, QImage *depthMap); private: MeshLoader *m_mesh = nullptr; MeshLoader *m_newMesh = nullptr; @@ -46,6 +49,13 @@ private: bool m_environmentLightEnabled = false; QOpenGLTexture *m_environmentIrradianceMap = nullptr; QOpenGLTexture *m_environmentSpecularMap = nullptr; + QOpenGLTexture *m_toonNormalMap = nullptr; + QOpenGLTexture *m_toonDepthMap = nullptr; + QImage *m_newToonNormalMap = nullptr; + QImage *m_newToonDepthMap = nullptr; + QImage *m_currentToonNormalMap = nullptr; + QImage *m_currentToonDepthMap = nullptr; + bool m_newToonMapsComing = false; private: QOpenGLVertexArrayObject m_vaoTriangle; QOpenGLBuffer m_vboTriangle; @@ -55,6 +65,7 @@ private: QOpenGLBuffer m_vboTool; QMutex m_meshMutex; QMutex m_newMeshMutex; + QMutex m_toonNormalAndDepthMapMutex; }; #endif diff --git a/src/modelofflinerender.cpp b/src/modelofflinerender.cpp new file mode 100644 index 00000000..098a297a --- /dev/null +++ b/src/modelofflinerender.cpp @@ -0,0 +1,186 @@ +#include +#include +#include +#include "modelofflinerender.h" + +ModelOfflineRender::ModelOfflineRender(const QSurfaceFormat &format, QScreen *targetScreen) : + QOffscreenSurface(targetScreen), + m_context(nullptr), + m_mesh(nullptr) +{ + setFormat(format); + create(); + if (!isValid()) + qDebug() << "ModelOfflineRender is invalid"; +} + +ModelOfflineRender::~ModelOfflineRender() +{ + destroy(); + delete m_mesh; + delete m_normalMap; + delete m_depthMap; +} + +void ModelOfflineRender::updateMesh(MeshLoader *mesh) +{ + delete m_mesh; + m_mesh = mesh; +} + +void ModelOfflineRender::setRenderThread(QThread *thread) +{ + //this->moveToThread(thread); +} + +void ModelOfflineRender::setXRotation(int angle) +{ + m_xRot = angle; +} + +void ModelOfflineRender::setYRotation(int angle) +{ + m_yRot = angle; +} + +void ModelOfflineRender::setZRotation(int angle) +{ + m_zRot = angle; +} + +void ModelOfflineRender::setRenderPurpose(int purpose) +{ + m_renderPurpose = purpose; +} + +void ModelOfflineRender::setToonShading(bool toonShading) +{ + m_toonShading = toonShading; +} + +void ModelOfflineRender::updateToonNormalAndDepthMaps(QImage *normalMap, QImage *depthMap) +{ + delete m_normalMap; + m_normalMap = normalMap; + + delete m_depthMap; + m_depthMap = depthMap; +} + +QImage ModelOfflineRender::toImage(const QSize &size) +{ + QImage image; + + m_context = new QOpenGLContext(); + m_context->setFormat(format()); + if (!m_context->create()) { + delete m_context; + m_context = nullptr; + + qDebug() << "QOpenGLContext create failed"; + return image; + } + + if (!m_context->makeCurrent(this)) { + delete m_context; + m_context = nullptr; + + qDebug() << "QOpenGLContext makeCurrent failed"; + return image; + } + + QOpenGLFramebufferObjectFormat format; + format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); + format.setSamples(4); + format.setTextureTarget(GL_TEXTURE_2D); + format.setInternalTextureFormat(GL_RGBA32F_ARB); + QOpenGLFramebufferObject *renderFbo = new QOpenGLFramebufferObject(size, format); + renderFbo->bind(); + m_context->functions()->glViewport(0, 0, size.width(), size.height()); + + if (nullptr != m_mesh) { + QMatrix4x4 projection; + QMatrix4x4 world; + QMatrix4x4 camera; + + bool isCoreProfile = false; + const char *versionString = (const char *)m_context->functions()->glGetString(GL_VERSION); + if (nullptr != versionString && + '\0' != versionString[0] && + 0 == strstr(versionString, "Mesa")) { + isCoreProfile = m_context->format().profile() == QSurfaceFormat::CoreProfile; + } + + ModelShaderProgram *program = new ModelShaderProgram(isCoreProfile); + ModelMeshBinder meshBinder; + meshBinder.initialize(); + meshBinder.hideWireframes(); + if (nullptr != m_normalMap && nullptr != m_depthMap) { + meshBinder.updateToonNormalAndDepthMaps(m_normalMap, m_depthMap); + m_normalMap = nullptr; + m_depthMap = nullptr; + } + + m_context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + m_context->functions()->glEnable(GL_BLEND); + m_context->functions()->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + m_context->functions()->glEnable(GL_DEPTH_TEST); + m_context->functions()->glEnable(GL_CULL_FACE); +#ifdef GL_LINE_SMOOTH + m_context->functions()->glEnable(GL_LINE_SMOOTH); +#endif + + world.setToIdentity(); + world.rotate(m_xRot / 16.0f, 1, 0, 0); + world.rotate(m_yRot / 16.0f, 0, 1, 0); + world.rotate(m_zRot / 16.0f, 0, 0, 1); + + projection.setToIdentity(); + projection.perspective(45.0f, GLfloat(size.width()) / size.height(), 0.01f, 100.0f); + + camera.setToIdentity(); + camera.translate(QVector3D(0, 0, -4.0)); + + program->bind(); + program->setUniformValue(program->lightPosLoc(), QVector3D(0, 0, 70)); + program->setUniformValue(program->toonShadingEnabledLoc(), m_toonShading ? 1 : 0); + program->setUniformValue(program->projectionMatrixLoc(), projection); + program->setUniformValue(program->modelMatrixLoc(), world); + QMatrix3x3 normalMatrix = world.normalMatrix(); + program->setUniformValue(program->normalMatrixLoc(), normalMatrix); + program->setUniformValue(program->viewMatrixLoc(), camera); + program->setUniformValue(program->textureEnabledLoc(), 0); + program->setUniformValue(program->normalMapEnabledLoc(), 0); + program->setUniformValue(program->mousePickEnabledLoc(), 0); + program->setUniformValue(program->renderPurposeLoc(), m_renderPurpose); + + program->setUniformValue(program->toonEdgeEnabledLoc(), 0); + program->setUniformValue(program->screenWidthLoc(), (GLfloat)size.width()); + program->setUniformValue(program->screenHeightLoc(), (GLfloat)size.height()); + program->setUniformValue(program->toonNormalMapIdLoc(), 0); + program->setUniformValue(program->toonDepthMapIdLoc(), 0); + + meshBinder.updateMesh(m_mesh); + meshBinder.paint(program); + + meshBinder.cleanup(); + + program->release(); + delete program; + + m_mesh = nullptr; + } + + m_context->functions()->glFlush(); + + image = renderFbo->toImage(); + + renderFbo->bindDefault(); + delete renderFbo; + + m_context->doneCurrent(); + delete m_context; + m_context = nullptr; + + return image; +} diff --git a/src/modelofflinerender.h b/src/modelofflinerender.h new file mode 100644 index 00000000..2198332f --- /dev/null +++ b/src/modelofflinerender.h @@ -0,0 +1,39 @@ +#ifndef DUST3D_MODEL_OFFLINE_RENDER_H +#define DUST3D_MODEL_OFFLINE_RENDER_H +#include +#include +#include +#include +#include +#include +#include "modelshaderprogram.h" +#include "modelmeshbinder.h" +#include "meshloader.h" + +class ModelOfflineRender : QOffscreenSurface +{ +public: + ModelOfflineRender(const QSurfaceFormat &format, QScreen *targetScreen = Q_NULLPTR); + ~ModelOfflineRender(); + void setXRotation(int angle); + void setYRotation(int angle); + void setZRotation(int angle); + void setRenderPurpose(int purpose); + void setRenderThread(QThread *thread); + void updateMesh(MeshLoader *mesh); + void updateToonNormalAndDepthMaps(QImage *normalMap, QImage *depthMap); + void setToonShading(bool toonShading); + QImage toImage(const QSize &size); +private: + int m_xRot = 0; + int m_yRot = 0; + int m_zRot = 0; + int m_renderPurpose = 0; + QOpenGLContext *m_context = nullptr; + MeshLoader *m_mesh = nullptr; + QImage *m_normalMap = nullptr; + QImage *m_depthMap = nullptr; + bool m_toonShading = false; +}; + +#endif diff --git a/src/modelshaderprogram.cpp b/src/modelshaderprogram.cpp index 9948ffda..6756a34e 100644 --- a/src/modelshaderprogram.cpp +++ b/src/modelshaderprogram.cpp @@ -58,7 +58,13 @@ ModelShaderProgram::ModelShaderProgram(bool isCoreProfile) m_mousePickEnabledLoc = this->uniformLocation("mousePickEnabled"); m_mousePickTargetPositionLoc = this->uniformLocation("mousePickTargetPosition"); m_mousePickRadiusLoc = this->uniformLocation("mousePickRadius"); - m_tongShadingEnabledLoc = this->uniformLocation("tongShadingEnabled"); + m_toonShadingEnabledLoc = this->uniformLocation("toonShadingEnabled"); + m_renderPurposeLoc = this->uniformLocation("renderPurpose"); + m_toonEdgeEnabledLoc = this->uniformLocation("toonEdgeEnabled"); + m_screenWidthLoc = this->uniformLocation("screenWidth"); + m_screenHeightLoc = this->uniformLocation("screenHeight"); + m_toonNormalMapIdLoc = this->uniformLocation("toonNormalMapId"); + m_toonDepthMapIdLoc = this->uniformLocation("toonDepthMapId"); if (m_isCoreProfile) { m_environmentIrradianceMapIdLoc = this->uniformLocation("environmentIrradianceMapId"); m_environmentIrradianceMapEnabledLoc = this->uniformLocation("environmentIrradianceMapEnabled"); @@ -167,7 +173,37 @@ int ModelShaderProgram::environmentSpecularMapEnabledLoc() return m_environmentSpecularMapEnabledLoc; } -int ModelShaderProgram::tongShadingEnabledLoc() +int ModelShaderProgram::toonShadingEnabledLoc() { - return m_tongShadingEnabledLoc; -} \ No newline at end of file + return m_toonShadingEnabledLoc; +} + +int ModelShaderProgram::renderPurposeLoc() +{ + return m_renderPurposeLoc; +} + +int ModelShaderProgram::toonEdgeEnabledLoc() +{ + return m_toonEdgeEnabledLoc; +} + +int ModelShaderProgram::screenWidthLoc() +{ + return m_screenWidthLoc; +} + +int ModelShaderProgram::screenHeightLoc() +{ + return m_screenHeightLoc; +} + +int ModelShaderProgram::toonNormalMapIdLoc() +{ + return m_toonNormalMapIdLoc; +} + +int ModelShaderProgram::toonDepthMapIdLoc() +{ + return m_toonDepthMapIdLoc; +} diff --git a/src/modelshaderprogram.h b/src/modelshaderprogram.h index 729dfa9f..4c845df0 100644 --- a/src/modelshaderprogram.h +++ b/src/modelshaderprogram.h @@ -27,7 +27,13 @@ public: int environmentIrradianceMapEnabledLoc(); int environmentSpecularMapIdLoc(); int environmentSpecularMapEnabledLoc(); - int tongShadingEnabledLoc(); + int toonShadingEnabledLoc(); + int renderPurposeLoc(); + int toonEdgeEnabledLoc(); + int screenWidthLoc(); + int screenHeightLoc(); + int toonNormalMapIdLoc(); + int toonDepthMapIdLoc(); bool isCoreProfile(); static const QString &loadShaderSource(const QString &name); private: @@ -52,7 +58,13 @@ private: int m_environmentIrradianceMapEnabledLoc = 0; int m_environmentSpecularMapIdLoc = 0; int m_environmentSpecularMapEnabledLoc = 0; - int m_tongShadingEnabledLoc = 0; + int m_toonShadingEnabledLoc = 0; + int m_renderPurposeLoc = 0; + int m_toonEdgeEnabledLoc = 0; + int m_screenWidthLoc = 0; + int m_screenHeightLoc = 0; + int m_toonNormalMapIdLoc = 0; + int m_toonDepthMapIdLoc = 0; }; #endif diff --git a/src/modelwidget.cpp b/src/modelwidget.cpp index ebce009e..79b0a231 100644 --- a/src/modelwidget.cpp +++ b/src/modelwidget.cpp @@ -46,13 +46,18 @@ ModelWidget::ModelWidget(QWidget *parent) : setFormat(fmt); } setContextMenuPolicy(Qt::CustomContextMenu); + + m_widthInPixels = width() * window()->devicePixelRatio(); + m_heightInPixels = height() * window()->devicePixelRatio(); + zoom(200); - connect(&Preferences::instance(), &Preferences::tongShadingChanged, this, &ModelWidget::reRender); + connect(&Preferences::instance(), &Preferences::toonShadingChanged, this, &ModelWidget::reRender); } void ModelWidget::reRender() { + emit renderParametersChanged(); update(); } @@ -82,6 +87,7 @@ void ModelWidget::setXRotation(int angle) if (angle != m_xRot) { m_xRot = angle; emit xRotationChanged(angle); + emit renderParametersChanged(); update(); } } @@ -92,6 +98,7 @@ void ModelWidget::setYRotation(int angle) if (angle != m_yRot) { m_yRot = angle; emit yRotationChanged(angle); + emit renderParametersChanged(); update(); } } @@ -102,10 +109,16 @@ void ModelWidget::setZRotation(int angle) if (angle != m_zRot) { m_zRot = angle; emit zRotationChanged(angle); + emit renderParametersChanged(); update(); } } +MeshLoader *ModelWidget::fetchCurrentMesh() +{ + return m_meshBinder.fetchCurrentMesh(); +} + void ModelWidget::cleanup() { if (m_program == nullptr) @@ -141,7 +154,7 @@ void ModelWidget::initializeGL() if (nullptr != versionString && '\0' != versionString[0] && 0 == strstr(versionString, "Mesa")) { - isCoreProfile = QSurfaceFormat::defaultFormat().profile() == QSurfaceFormat::CoreProfile; + isCoreProfile = format().profile() == QSurfaceFormat::CoreProfile; } m_program = new ModelShaderProgram(isCoreProfile); @@ -174,6 +187,7 @@ void ModelWidget::paintGL() #ifdef GL_LINE_SMOOTH glEnable(GL_LINE_SMOOTH); #endif + glViewport(0, 0, m_widthInPixels, m_heightInPixels); m_world.setToIdentity(); m_world.rotate(m_xRot / 16.0f, 1, 0, 0); @@ -181,7 +195,7 @@ void ModelWidget::paintGL() m_world.rotate(m_zRot / 16.0f, 0, 0, 1); m_program->bind(); - m_program->setUniformValue(m_program->tongShadingEnabledLoc(), Preferences::instance().tongShading() ? 1 : 0); + m_program->setUniformValue(m_program->toonShadingEnabledLoc(), Preferences::instance().toonShading() ? 1 : 0); m_program->setUniformValue(m_program->projectionMatrixLoc(), m_projection); m_program->setUniformValue(m_program->modelMatrixLoc(), m_world); QMatrix3x3 normalMatrix = m_world.normalMatrix(); @@ -189,6 +203,13 @@ void ModelWidget::paintGL() m_program->setUniformValue(m_program->viewMatrixLoc(), m_camera); m_program->setUniformValue(m_program->textureEnabledLoc(), 0); m_program->setUniformValue(m_program->normalMapEnabledLoc(), 0); + m_program->setUniformValue(m_program->renderPurposeLoc(), 0); + + m_program->setUniformValue(m_program->toonEdgeEnabledLoc(), 0); + m_program->setUniformValue(m_program->screenWidthLoc(), (GLfloat)m_widthInPixels); + m_program->setUniformValue(m_program->screenHeightLoc(), (GLfloat)m_heightInPixels); + m_program->setUniformValue(m_program->toonNormalMapIdLoc(), 0); + m_program->setUniformValue(m_program->toonDepthMapIdLoc(), 0); if (m_mousePickingEnabled && !m_mousePickTargetPositionInModelSpace.isNull()) { m_program->setUniformValue(m_program->mousePickEnabledLoc(), 1); @@ -207,8 +228,11 @@ void ModelWidget::paintGL() void ModelWidget::resizeGL(int w, int h) { + m_widthInPixels = w * window()->devicePixelRatio(); + m_heightInPixels = h * window()->devicePixelRatio(); m_projection.setToIdentity(); m_projection.perspective(45.0f, GLfloat(w) / h, 0.01f, 100.0f); + emit renderParametersChanged(); } std::pair ModelWidget::screenPositionToMouseRay(const QPoint &screenPosition) @@ -383,6 +407,7 @@ void ModelWidget::zoom(float delta) } } setGeometry(geometry().marginsAdded(margins)); + emit renderParametersChanged(); } void ModelWidget::setMousePickTargetPositionInModelSpace(QVector3D position) @@ -400,9 +425,31 @@ void ModelWidget::setMousePickRadius(float radius) void ModelWidget::updateMesh(MeshLoader *mesh) { m_meshBinder.updateMesh(mesh); + emit renderParametersChanged(); update(); } +void ModelWidget::fetchCurrentToonNormalAndDepthMaps(QImage *normalMap, QImage *depthMap) +{ + m_meshBinder.fetchCurrentToonNormalAndDepthMaps(normalMap, depthMap); +} + +void ModelWidget::updateToonNormalAndDepthMaps(QImage *normalMap, QImage *depthMap) +{ + m_meshBinder.updateToonNormalAndDepthMaps(normalMap, depthMap); + update(); +} + +int ModelWidget::widthInPixels() +{ + return m_widthInPixels; +} + +int ModelWidget::heightInPixels() +{ + return m_heightInPixels; +} + void ModelWidget::enableMove(bool enabled) { m_moveEnabled = enabled; diff --git a/src/modelwidget.h b/src/modelwidget.h index 12efe1c0..a9297f3c 100644 --- a/src/modelwidget.h +++ b/src/modelwidget.h @@ -23,6 +23,7 @@ signals: void mousePressed(); void mouseReleased(); void addMouseRadius(float radius); + void renderParametersChanged(); public: ModelWidget(QWidget *parent = 0); ~ModelWidget(); @@ -34,6 +35,7 @@ public: { m_transparent = t; } + MeshLoader *fetchCurrentMesh(); void updateMesh(MeshLoader *mesh); void setGraphicsFunctions(SkeletonGraphicsFunctions *graphicsFunctions); void toggleWireframe(); @@ -48,6 +50,10 @@ public: bool inputWheelEventFromOtherWidget(QWheelEvent *event); bool inputMouseReleaseEventFromOtherWidget(QMouseEvent *event); QPoint convertInputPosFromOtherWidget(QMouseEvent *event); + void fetchCurrentToonNormalAndDepthMaps(QImage *normalMap, QImage *depthMap); + void updateToonNormalAndDepthMaps(QImage *normalMap, QImage *depthMap); + int widthInPixels(); + int heightInPixels(); public slots: void setXRotation(int angle); void setYRotation(int angle); @@ -98,6 +104,8 @@ private: QRect m_moveStartGeometry; int m_modelInitialHeight = 0; QTimer *m_rotationTimer = nullptr; + int m_widthInPixels = 0; + int m_heightInPixels = 0; std::pair screenPositionToMouseRay(const QPoint &screenPosition); public: static int m_defaultXRotation; diff --git a/src/normalanddepthmapsgenerator.cpp b/src/normalanddepthmapsgenerator.cpp new file mode 100644 index 00000000..6eb6913b --- /dev/null +++ b/src/normalanddepthmapsgenerator.cpp @@ -0,0 +1,70 @@ +#include "normalanddepthmapsgenerator.h" + +NormalAndDepthMapsGenerator::NormalAndDepthMapsGenerator(ModelWidget *modelWidget) +{ + m_viewPortSize = QSize(modelWidget->widthInPixels(), + modelWidget->heightInPixels()); + m_normalMapRender = createOfflineRender(modelWidget, 1); + m_depthMapRender = createOfflineRender(modelWidget, 2); +} + +void NormalAndDepthMapsGenerator::updateMesh(MeshLoader *mesh) +{ + if (nullptr == mesh) { + m_normalMapRender->updateMesh(nullptr); + m_depthMapRender->updateMesh(nullptr); + return; + } + m_normalMapRender->updateMesh(new MeshLoader(*mesh)); + m_depthMapRender->updateMesh(mesh); +} + +void NormalAndDepthMapsGenerator::setRenderThread(QThread *thread) +{ + //m_normalMapRender->setRenderThread(thread); + //m_depthMapRender->setRenderThread(thread); +} + +ModelOfflineRender *NormalAndDepthMapsGenerator::createOfflineRender(ModelWidget *modelWidget, int purpose) +{ + ModelOfflineRender *offlineRender = new ModelOfflineRender(modelWidget->format()); + offlineRender->setXRotation(modelWidget->xRot()); + offlineRender->setYRotation(modelWidget->yRot()); + offlineRender->setZRotation(modelWidget->zRot()); + offlineRender->setRenderPurpose(purpose); + return offlineRender; +} + +NormalAndDepthMapsGenerator::~NormalAndDepthMapsGenerator() +{ + delete m_normalMapRender; + delete m_depthMapRender; + delete m_normalMap; + delete m_depthMap; +} + +void NormalAndDepthMapsGenerator::generate() +{ + m_normalMap = new QImage(m_normalMapRender->toImage(m_viewPortSize)); + m_depthMap = new QImage(m_depthMapRender->toImage(m_viewPortSize)); +} + +void NormalAndDepthMapsGenerator::process() +{ + generate(); + emit finished(); +} + +QImage *NormalAndDepthMapsGenerator::takeNormalMap() +{ + QImage *normalMap = m_normalMap; + m_normalMap = nullptr; + return normalMap; +} + +QImage *NormalAndDepthMapsGenerator::takeDepthMap() +{ + QImage *depthMap = m_depthMap; + m_depthMap = nullptr; + return depthMap; +} diff --git a/src/normalanddepthmapsgenerator.h b/src/normalanddepthmapsgenerator.h new file mode 100644 index 00000000..4fb8e7c6 --- /dev/null +++ b/src/normalanddepthmapsgenerator.h @@ -0,0 +1,34 @@ +#ifndef DUST3D_NORMAL_AND_DEPTH_MAPS_GENERATOR_H +#define DUST3D_NORMAL_AND_DEPTH_MAPS_GENERATOR_H +#include +#include +#include "modelwidget.h" +#include "modelofflinerender.h" +#include "meshloader.h" + +class NormalAndDepthMapsGenerator : public QObject +{ + Q_OBJECT +public: + NormalAndDepthMapsGenerator(ModelWidget *modelWidget); + void updateMesh(MeshLoader *mesh); + void setRenderThread(QThread *thread); + ~NormalAndDepthMapsGenerator(); + QImage *takeNormalMap(); + QImage *takeDepthMap(); +signals: + void finished(); +public slots: + void process(); +private: + ModelOfflineRender *m_normalMapRender = nullptr; + ModelOfflineRender *m_depthMapRender = nullptr; + QSize m_viewPortSize; + QImage *m_normalMap = nullptr; + QImage *m_depthMap = nullptr; + + ModelOfflineRender *createOfflineRender(ModelWidget *modelWidget, int purpose); + void generate(); +}; + +#endif diff --git a/src/preferences.cpp b/src/preferences.cpp index 90d66f94..3facd8ff 100644 --- a/src/preferences.cpp +++ b/src/preferences.cpp @@ -14,8 +14,8 @@ void Preferences::loadDefault() { m_componentCombineMode = CombineMode::Normal; m_partColor = Qt::white; - m_flatShading = true; - m_tongShading = false; + m_flatShading = false; + m_toonShading = false; m_textureSize = 1024; } @@ -40,11 +40,11 @@ Preferences::Preferences() m_flatShading = isTrueValueString(value); } { - QString value = m_settings.value("tongShading").toString(); + QString value = m_settings.value("toonShading").toString(); if (value.isEmpty()) - m_tongShading = false; + m_toonShading = false; else - m_tongShading = isTrueValueString(value); + m_toonShading = isTrueValueString(value); } { QString value = m_settings.value("textureSize").toString(); @@ -68,9 +68,9 @@ bool Preferences::flatShading() const return m_flatShading; } -bool Preferences::tongShading() const +bool Preferences::toonShading() const { - return m_tongShading; + return m_toonShading; } int Preferences::textureSize() const @@ -105,13 +105,13 @@ void Preferences::setFlatShading(bool flatShading) emit flatShadingChanged(); } -void Preferences::setTongShading(bool tongShading) +void Preferences::setToonShading(bool toonShading) { - if (m_tongShading == tongShading) + if (m_toonShading == toonShading) return; - m_tongShading = tongShading; - m_settings.setValue("tongShading", tongShading ? "true" : "false"); - emit tongShadingChanged(); + m_toonShading = toonShading; + m_settings.setValue("toonShading", toonShading ? "true" : "false"); + emit toonShadingChanged(); } void Preferences::setTextureSize(int textureSize) @@ -140,6 +140,6 @@ void Preferences::reset() emit componentCombineModeChanged(); emit partColorChanged(); emit flatShadingChanged(); - emit tongShadingChanged(); + emit toonShadingChanged(); emit textureSizeChanged(); } diff --git a/src/preferences.h b/src/preferences.h index ad2b77db..cb360700 100644 --- a/src/preferences.h +++ b/src/preferences.h @@ -14,7 +14,7 @@ public: CombineMode componentCombineMode() const; const QColor &partColor() const; bool flatShading() const; - bool tongShading() const; + bool toonShading() const; QSize documentWindowSize() const; void setDocumentWindowSize(const QSize&); int textureSize() const; @@ -22,20 +22,20 @@ signals: void componentCombineModeChanged(); void partColorChanged(); void flatShadingChanged(); - void tongShadingChanged(); + void toonShadingChanged(); void textureSizeChanged(); public slots: void setComponentCombineMode(CombineMode mode); void setPartColor(const QColor &color); void setFlatShading(bool flatShading); - void setTongShading(bool tongShading); + void setToonShading(bool toonShading); void setTextureSize(int textureSize); void reset(); private: CombineMode m_componentCombineMode; QColor m_partColor; bool m_flatShading; - bool m_tongShading; + bool m_toonShading; QSettings m_settings; int m_textureSize; private: diff --git a/src/preferenceswidget.cpp b/src/preferenceswidget.cpp index b8fd80f9..42c020e7 100644 --- a/src/preferenceswidget.cpp +++ b/src/preferenceswidget.cpp @@ -65,10 +65,10 @@ PreferencesWidget::PreferencesWidget(const Document *document, QWidget *parent) Preferences::instance().setFlatShading(flatShadingBox->isChecked()); }); - QCheckBox *tongShadingBox = new QCheckBox(); - Theme::initCheckbox(tongShadingBox); - connect(tongShadingBox, &QCheckBox::stateChanged, this, [=]() { - Preferences::instance().setTongShading(tongShadingBox->isChecked()); + QCheckBox *toonShadingBox = new QCheckBox(); + Theme::initCheckbox(toonShadingBox); + connect(toonShadingBox, &QCheckBox::stateChanged, this, [=]() { + Preferences::instance().setToonShading(toonShadingBox->isChecked()); }); QComboBox *textureSizeSelectBox = new QComboBox; @@ -84,14 +84,14 @@ PreferencesWidget::PreferencesWidget(const Document *document, QWidget *parent) formLayout->addRow(tr("Part color:"), colorLayout); formLayout->addRow(tr("Combine mode:"), combineModeSelectBox); formLayout->addRow(tr("Flat shading:"), flatShadingBox); - formLayout->addRow(tr("Tong shading:"), tongShadingBox); + formLayout->addRow(tr("Toon shading:"), toonShadingBox); formLayout->addRow(tr("Texture size:"), textureSizeSelectBox); auto loadFromPreferences = [=]() { updatePickButtonColor(); combineModeSelectBox->setCurrentIndex((int)Preferences::instance().componentCombineMode()); flatShadingBox->setChecked(Preferences::instance().flatShading()); - tongShadingBox->setChecked(Preferences::instance().tongShading()); + toonShadingBox->setChecked(Preferences::instance().toonShading()); textureSizeSelectBox->setCurrentIndex( textureSizeSelectBox->findText(QString::number(Preferences::instance().textureSize())) );