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.
master
Jeremy Hu 2020-04-02 20:09:57 +09:30
parent 03dbe89b33
commit 55e657852c
23 changed files with 998 additions and 62 deletions

View File

@ -134,6 +134,12 @@ include(thirdparty/QtAwesome/QtAwesome/QtAwesome.pri)
INCLUDEPATH += src INCLUDEPATH += src
SOURCES += src/normalanddepthmapsgenerator.cpp
HEADERS += src/normalanddepthmapsgenerator.h
SOURCES += src/modelofflinerender.cpp
HEADERS += src/modelofflinerender.h
SOURCES += src/modelshaderprogram.cpp SOURCES += src/modelshaderprogram.cpp
HEADERS += src/modelshaderprogram.h HEADERS += src/modelshaderprogram.h

View File

@ -391,6 +391,14 @@ Consejos:
<source>Rotation</source> <source>Rotation</source>
<translation>Rotación</translation> <translation>Rotación</translation>
</message> </message>
<message>
<source>Export as Image...</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Image (*.png)</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>ExportPreviewWidget</name> <name>ExportPreviewWidget</name>
@ -969,7 +977,7 @@ Consejos:
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source>Tong shading:</source> <source>Toon shading:</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>

View File

@ -71,8 +71,8 @@ Tips:
- Make multiple parts instead of one single part for whole model</source> - Make multiple parts instead of one single part for whole model</source>
<translation>Generazione mesh non riuscita, annulla o modifica i nodi modificati di recente <translation>Generazione mesh non riuscita, annulla o modifica i nodi modificati di recente
Suggerimenti: Suggerimenti:
   - Non lasciare che la mesh generata si intersechi in se stessa &#xa0;&#xa0; - Non lasciare che la mesh generata si intersechi in se stessa
   - Crea più parti invece di una singola parte per l&apos;intero modello</translation> &#xa0;&#xa0; - Crea più parti invece di una singola parte per l&apos;intero modello</translation>
</message> </message>
<message> <message>
<source>Parts</source> <source>Parts</source>
@ -398,6 +398,14 @@ Suggerimenti:
<source>Enter zoom out mode</source> <source>Enter zoom out mode</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Export as Image...</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Image (*.png)</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>ExportPreviewWidget</name> <name>ExportPreviewWidget</name>
@ -976,7 +984,7 @@ Suggerimenti:
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source>Tong shading:</source> <source>Toon shading:</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>

View File

@ -391,6 +391,14 @@ Tips:
<source>Script</source> <source>Script</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Export as Image...</source>
<translation>...</translation>
</message>
<message>
<source>Image (*.png)</source>
<translation> (*.png)</translation>
</message>
</context> </context>
<context> <context>
<name>ExportPreviewWidget</name> <name>ExportPreviewWidget</name>
@ -969,7 +977,7 @@ Tips:
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Tong shading:</source> <source>Toon shading:</source>
<translation></translation> <translation></translation>
</message> </message>
</context> </context>

View File

@ -86,7 +86,13 @@ uniform samplerCube environmentIrradianceMapId;
uniform int environmentIrradianceMapEnabled; uniform int environmentIrradianceMapEnabled;
uniform samplerCube environmentSpecularMapId; uniform samplerCube environmentSpecularMapId;
uniform int environmentSpecularMapEnabled; 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 MAX_LIGHTS = 8;
const int TYPE_POINT = 0; const int TYPE_POINT = 0;
@ -106,6 +112,122 @@ struct Light {
int lightCount; int lightCount;
Light lights[MAX_LIGHTS]; 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 mipLevelCount(const in samplerCube cube)
{ {
int baseSize = textureSize(cube, 0).x; int baseSize = textureSize(cube, 0).x;
@ -385,7 +507,7 @@ vec4 metalRoughFunction(const in vec4 baseColor,
// Remap roughness for a perceptually more linear correspondence // Remap roughness for a perceptually more linear correspondence
float alpha = remapRoughness(roughness); float alpha = remapRoughness(roughness);
if (tongShadingEnabled != 1) { if (toonShadingEnabled != 1) {
if (environmentIrradianceMapEnabled == 1) { if (environmentIrradianceMapEnabled == 1) {
cLinear += pbrIblModel(worldNormal, cLinear += pbrIblModel(worldNormal,
worldView, worldView,
@ -414,6 +536,14 @@ vec4 metalRoughFunction(const in vec4 baseColor,
cLinear = hsv2rgb(vec3(hsv.r, hsv.g, hsv.b * 2.0)); cLinear = hsv2rgb(vec3(hsv.r, hsv.g, hsv.b * 2.0));
else else
cLinear = hsv2rgb(vec3(hsv.r, hsv.g, hsv.b * 0.1)); 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 // Apply exposure correction
@ -508,12 +638,18 @@ void main()
roughness = min(0.99, roughness); roughness = min(0.99, roughness);
metalness = min(0.99, metalness); metalness = min(0.99, metalness);
} }
fragColor = metalRoughFunction(vec4(color, alpha), if (renderPurpose == 0) {
metalness, fragColor = metalRoughFunction(vec4(color, alpha),
roughness, metalness,
ambientOcclusion, roughness,
vert, ambientOcclusion,
normalize(cameraPos - vert), vert,
normal); 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);
}
} }

View File

@ -1,3 +1,4 @@
#version 110
/**************************************************************************** /****************************************************************************
** **
** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). ** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
@ -80,7 +81,13 @@ uniform int ambientOcclusionMapEnabled;
uniform int mousePickEnabled; uniform int mousePickEnabled;
uniform vec3 mousePickTargetPosition; uniform vec3 mousePickTargetPosition;
uniform float mousePickRadius; 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 MAX_LIGHTS = 8;
const int TYPE_POINT = 0; const int TYPE_POINT = 0;
@ -100,6 +107,122 @@ struct Light {
int lightCount; int lightCount;
Light lights[MAX_LIGHTS]; 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) float remapRoughness(const in float roughness)
{ {
// As per page 14 of // As per page 14 of
@ -281,7 +404,7 @@ vec4 metalRoughFunction(const in vec4 baseColor,
// Remap roughness for a perceptually more linear correspondence // Remap roughness for a perceptually more linear correspondence
float alpha = remapRoughness(roughness); float alpha = remapRoughness(roughness);
if (tongShadingEnabled != 1) { if (toonShadingEnabled != 1) {
for (int i = 0; i < lightCount; ++i) { for (int i = 0; i < lightCount; ++i) {
cLinear += pbrModel(i, cLinear += pbrModel(i,
worldPosition, worldPosition,
@ -301,6 +424,14 @@ vec4 metalRoughFunction(const in vec4 baseColor,
cLinear = hsv2rgb(vec3(hsv.r, hsv.g, hsv.b * 2.0)); cLinear = hsv2rgb(vec3(hsv.r, hsv.g, hsv.b * 2.0));
else else
cLinear = hsv2rgb(vec3(hsv.r, hsv.g, hsv.b * 0.1)); 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 // Apply exposure correction
@ -393,12 +524,18 @@ void main()
roughness = min(0.99, roughness); roughness = min(0.99, roughness);
metalness = min(0.99, metalness); metalness = min(0.99, metalness);
gl_FragColor = metalRoughFunction(vec4(color, alpha), if (renderPurpose == 0) {
metalness, gl_FragColor = metalRoughFunction(vec4(color, alpha),
roughness, metalness,
ambientOcclusion, roughness,
vert, ambientOcclusion,
normalize(cameraPos - vert), vert,
normal); 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);
}
} }

View File

@ -1,3 +1,4 @@
#version 110
attribute vec4 vertex; attribute vec4 vertex;
attribute vec3 normal; attribute vec3 normal;
attribute vec3 color; attribute vec3 color;

View File

@ -42,6 +42,7 @@
#include "scriptwidget.h" #include "scriptwidget.h"
#include "variablesxml.h" #include "variablesxml.h"
#include "updatescheckwidget.h" #include "updatescheckwidget.h"
#include "modelofflinerender.h"
int DocumentWindow::m_modelRenderWidgetInitialX = 16; int DocumentWindow::m_modelRenderWidgetInitialX = 16;
int DocumentWindow::m_modelRenderWidgetInitialY = 16; int DocumentWindow::m_modelRenderWidgetInitialY = 16;
@ -310,7 +311,8 @@ DocumentWindow::DocumentWindow() :
m_modelRenderWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); m_modelRenderWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
m_modelRenderWidget->move(DocumentWindow::m_modelRenderWidgetInitialX, DocumentWindow::m_modelRenderWidgetInitialY); m_modelRenderWidget->move(DocumentWindow::m_modelRenderWidgetInitialX, DocumentWindow::m_modelRenderWidgetInitialY);
m_modelRenderWidget->setMousePickRadius(m_document->mousePickRadius()); m_modelRenderWidget->setMousePickRadius(m_document->mousePickRadius());
m_modelRenderWidget->toggleWireframe(); if (!Preferences::instance().toonShading())
m_modelRenderWidget->toggleWireframe();
m_modelRenderWidget->enableEnvironmentLight(); m_modelRenderWidget->enableEnvironmentLight();
connect(m_modelRenderWidget, &ModelWidget::mouseRayChanged, m_document, connect(m_modelRenderWidget, &ModelWidget::mouseRayChanged, m_document,
@ -341,6 +343,8 @@ DocumentWindow::DocumentWindow() :
m_modelRenderWidget->setMousePickTargetPositionInModelSpace(m_document->mouseTargetPosition()); m_modelRenderWidget->setMousePickTargetPositionInModelSpace(m_document->mouseTargetPosition());
}); });
connect(m_modelRenderWidget, &ModelWidget::renderParametersChanged, this, &DocumentWindow::delayedGenerateNormalAndDepthMaps);
m_graphicsWidget->setModelWidget(m_modelRenderWidget); m_graphicsWidget->setModelWidget(m_modelRenderWidget);
containerWidget->setModelWidget(m_modelRenderWidget); containerWidget->setModelWidget(m_modelRenderWidget);
@ -494,9 +498,9 @@ DocumentWindow::DocumentWindow() :
connect(m_exportAsObjAction, &QAction::triggered, this, &DocumentWindow::exportObjResult, Qt::QueuedConnection); connect(m_exportAsObjAction, &QAction::triggered, this, &DocumentWindow::exportObjResult, Qt::QueuedConnection);
m_fileMenu->addAction(m_exportAsObjAction); m_fileMenu->addAction(m_exportAsObjAction);
//m_exportRenderedAsImageAction = new QAction(tr("Export as PNG..."), this); m_exportRenderedAsImageAction = new QAction(tr("Export as Image..."), this);
//connect(m_exportRenderedAsImageAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportRenderedResult, Qt::QueuedConnection); connect(m_exportRenderedAsImageAction, &QAction::triggered, this, &DocumentWindow::exportRenderedResult, Qt::QueuedConnection);
//m_fileMenu->addAction(m_exportRenderedAsImageAction); m_fileMenu->addAction(m_exportRenderedAsImageAction);
//m_exportAsObjPlusMaterialsAction = new QAction(tr("Wavefront (.obj + .mtl)..."), this); //m_exportAsObjPlusMaterialsAction = new QAction(tr("Wavefront (.obj + .mtl)..."), this);
//connect(m_exportAsObjPlusMaterialsAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportObjPlusMaterialsResult, Qt::QueuedConnection); //connect(m_exportAsObjPlusMaterialsAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportObjPlusMaterialsResult, Qt::QueuedConnection);
@ -518,7 +522,7 @@ DocumentWindow::DocumentWindow() :
m_exportAsObjAction->setEnabled(m_graphicsWidget->hasItems()); m_exportAsObjAction->setEnabled(m_graphicsWidget->hasItems());
//m_exportAsObjPlusMaterialsAction->setEnabled(m_graphicsWidget->hasItems()); //m_exportAsObjPlusMaterialsAction->setEnabled(m_graphicsWidget->hasItems());
m_exportAction->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")); m_editMenu = menuBar()->addMenu(tr("&Edit"));
@ -1603,6 +1607,16 @@ void DocumentWindow::showPreferences()
m_preferencesWidget->raise(); 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() void DocumentWindow::exportObjResult()
{ {
QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), 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); // 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();
}

View File

@ -10,6 +10,7 @@
#include <map> #include <map>
#include <QStringList> #include <QStringList>
#include <QLabel> #include <QLabel>
#include <QTimer>
#include "document.h" #include "document.h"
#include "modelwidget.h" #include "modelwidget.h"
#include "exportpreviewwidget.h" #include "exportpreviewwidget.h"
@ -18,6 +19,7 @@
#include "posemanagewidget.h" #include "posemanagewidget.h"
#include "preferenceswidget.h" #include "preferenceswidget.h"
#include "graphicscontainerwidget.h" #include "graphicscontainerwidget.h"
#include "normalanddepthmapsgenerator.h"
class SkeletonGraphicsWidget; class SkeletonGraphicsWidget;
@ -49,6 +51,7 @@ public slots:
void open(); void open();
void openExample(const QString &modelName); void openExample(const QString &modelName);
void openPathAs(const QString &path, const QString &asName); void openPathAs(const QString &path, const QString &asName);
void exportRenderedResult();
void exportObjResult(); void exportObjResult();
void exportGlbResult(); void exportGlbResult();
void exportFbxResult(); void exportFbxResult();
@ -78,11 +81,15 @@ public slots:
void showCutFaceSettingPopup(const QPoint &globalPos, std::set<QUuid> nodeIds); void showCutFaceSettingPopup(const QPoint &globalPos, std::set<QUuid> nodeIds);
void setExportWaitingList(const QStringList &filenames); void setExportWaitingList(const QStringList &filenames);
void checkExportWaitingList(); void checkExportWaitingList();
void exportImageToFilename(const QString &filename);
void exportObjToFilename(const QString &filename); void exportObjToFilename(const QString &filename);
void exportFbxToFilename(const QString &filename); void exportFbxToFilename(const QString &filename);
void exportGlbToFilename(const QString &filename); void exportGlbToFilename(const QString &filename);
void toggleRotation(); void toggleRotation();
//void updateInfoWidgetPosition(); //void updateInfoWidgetPosition();
void generateNormalAndDepthMaps();
void delayedGenerateNormalAndDepthMaps();
void normalAndDepthMapsReady();
private: private:
void initLockButton(QPushButton *button); void initLockButton(QPushButton *button);
void setCurrentFilename(const QString &filename); void setCurrentFilename(const QString &filename);
@ -199,6 +206,10 @@ private:
QPushButton *m_radiusLockButton; QPushButton *m_radiusLockButton;
QMetaObject::Connection m_partListDockerVisibleSwitchConnection; QMetaObject::Connection m_partListDockerVisibleSwitchConnection;
NormalAndDepthMapsGenerator *m_normalAndDepthMapsGenerator = nullptr;
QTimer *m_normalAndDepthMapsDelayTimer = nullptr;
bool m_isNormalAndDepthMapsObsolete = false;
public: public:
static int m_modelRenderWidgetInitialX; static int m_modelRenderWidgetInitialX;
static int m_modelRenderWidgetInitialY; static int m_modelRenderWidgetInitialY;

View File

@ -1,6 +1,7 @@
#include "logbrowser.h" #include "logbrowser.h"
// Modified from https://wiki.qt.io/Browser_for_QDebug_output // Modified from https://wiki.qt.io/Browser_for_QDebug_output
#include <QMetaType> #include <QMetaType>
#include <stdio.h>
#include "logbrowserdialog.h" #include "logbrowserdialog.h"
LogBrowser::LogBrowser(QObject *parent) : LogBrowser::LogBrowser(QObject *parent) :

View File

@ -23,6 +23,12 @@ ModelMeshBinder::~ModelMeshBinder()
delete m_metalnessRoughnessAmbientOcclusionMap; delete m_metalnessRoughnessAmbientOcclusionMap;
delete m_environmentIrradianceMap; delete m_environmentIrradianceMap;
delete m_environmentSpecularMap; 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) void ModelMeshBinder::updateMesh(MeshLoader *mesh)
@ -61,6 +67,14 @@ void ModelMeshBinder::enableEnvironmentLight()
m_environmentLightEnabled = true; 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) void ModelMeshBinder::paint(ModelShaderProgram *program)
{ {
MeshLoader *newMesh = nullptr; MeshLoader *newMesh = nullptr;
@ -74,6 +88,33 @@ void ModelMeshBinder::paint(ModelShaderProgram *program)
hasNewMesh = true; 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); QMutexLocker lock(&m_meshMutex);
if (hasNewMesh) { if (hasNewMesh) {
@ -218,6 +259,8 @@ void ModelMeshBinder::paint(ModelShaderProgram *program)
program->setUniformValue(program->metalnessRoughnessAmbientOcclusionMapIdLoc(), 2); program->setUniformValue(program->metalnessRoughnessAmbientOcclusionMapIdLoc(), 2);
program->setUniformValue(program->environmentIrradianceMapIdLoc(), 3); program->setUniformValue(program->environmentIrradianceMapIdLoc(), 3);
program->setUniformValue(program->environmentSpecularMapIdLoc(), 4); program->setUniformValue(program->environmentSpecularMapIdLoc(), 4);
program->setUniformValue(program->toonNormalMapIdLoc(), 5);
program->setUniformValue(program->toonDepthMapIdLoc(), 6);
if (m_showWireframes) { if (m_showWireframes) {
if (m_renderEdgeVertexCount > 0) { if (m_renderEdgeVertexCount > 0) {
QOpenGLVertexArrayObject::Binder vaoBinder(&m_vaoEdge); QOpenGLVertexArrayObject::Binder vaoBinder(&m_vaoEdge);
@ -275,6 +318,11 @@ void ModelMeshBinder::paint(ModelShaderProgram *program)
program->setUniformValue(program->environmentSpecularMapEnabledLoc(), 0); 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); f->glDrawArrays(GL_TRIANGLES, 0, m_renderTriangleVertexCount);
} }
if (m_toolEnabled) { 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() void ModelMeshBinder::cleanup()
{ {
if (m_vboTriangle.isCreated()) if (m_vboTriangle.isCreated())
@ -315,6 +385,10 @@ void ModelMeshBinder::cleanup()
m_environmentIrradianceMap = nullptr; m_environmentIrradianceMap = nullptr;
delete m_environmentSpecularMap; delete m_environmentSpecularMap;
m_environmentSpecularMap = nullptr; m_environmentSpecularMap = nullptr;
delete m_toonNormalMap;
m_toonNormalMap = nullptr;
delete m_toonDepthMap;
m_toonDepthMap = nullptr;
} }
void ModelMeshBinder::showWireframes() void ModelMeshBinder::showWireframes()

View File

@ -13,6 +13,7 @@ class ModelMeshBinder
public: public:
ModelMeshBinder(bool toolEnabled=false); ModelMeshBinder(bool toolEnabled=false);
~ModelMeshBinder(); ~ModelMeshBinder();
MeshLoader *fetchCurrentMesh();
void updateMesh(MeshLoader *mesh); void updateMesh(MeshLoader *mesh);
void initialize(); void initialize();
void paint(ModelShaderProgram *program); void paint(ModelShaderProgram *program);
@ -25,6 +26,8 @@ public:
void enableEnvironmentLight(); void enableEnvironmentLight();
bool isCheckUvEnabled(); bool isCheckUvEnabled();
void reloadMesh(); void reloadMesh();
void fetchCurrentToonNormalAndDepthMaps(QImage *normalMap, QImage *depthMap);
void updateToonNormalAndDepthMaps(QImage *normalMap, QImage *depthMap);
private: private:
MeshLoader *m_mesh = nullptr; MeshLoader *m_mesh = nullptr;
MeshLoader *m_newMesh = nullptr; MeshLoader *m_newMesh = nullptr;
@ -46,6 +49,13 @@ private:
bool m_environmentLightEnabled = false; bool m_environmentLightEnabled = false;
QOpenGLTexture *m_environmentIrradianceMap = nullptr; QOpenGLTexture *m_environmentIrradianceMap = nullptr;
QOpenGLTexture *m_environmentSpecularMap = 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: private:
QOpenGLVertexArrayObject m_vaoTriangle; QOpenGLVertexArrayObject m_vaoTriangle;
QOpenGLBuffer m_vboTriangle; QOpenGLBuffer m_vboTriangle;
@ -55,6 +65,7 @@ private:
QOpenGLBuffer m_vboTool; QOpenGLBuffer m_vboTool;
QMutex m_meshMutex; QMutex m_meshMutex;
QMutex m_newMeshMutex; QMutex m_newMeshMutex;
QMutex m_toonNormalAndDepthMapMutex;
}; };
#endif #endif

186
src/modelofflinerender.cpp Normal file
View File

@ -0,0 +1,186 @@
#include <QOpenGLFramebufferObjectFormat>
#include <QThread>
#include <QDebug>
#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;
}

39
src/modelofflinerender.h Normal file
View File

@ -0,0 +1,39 @@
#ifndef DUST3D_MODEL_OFFLINE_RENDER_H
#define DUST3D_MODEL_OFFLINE_RENDER_H
#include <QOffscreenSurface>
#include <QScreen>
#include <QOpenGLFunctions>
#include <QOpenGLContext>
#include <QImage>
#include <QThread>
#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

View File

@ -58,7 +58,13 @@ ModelShaderProgram::ModelShaderProgram(bool isCoreProfile)
m_mousePickEnabledLoc = this->uniformLocation("mousePickEnabled"); m_mousePickEnabledLoc = this->uniformLocation("mousePickEnabled");
m_mousePickTargetPositionLoc = this->uniformLocation("mousePickTargetPosition"); m_mousePickTargetPositionLoc = this->uniformLocation("mousePickTargetPosition");
m_mousePickRadiusLoc = this->uniformLocation("mousePickRadius"); 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) { if (m_isCoreProfile) {
m_environmentIrradianceMapIdLoc = this->uniformLocation("environmentIrradianceMapId"); m_environmentIrradianceMapIdLoc = this->uniformLocation("environmentIrradianceMapId");
m_environmentIrradianceMapEnabledLoc = this->uniformLocation("environmentIrradianceMapEnabled"); m_environmentIrradianceMapEnabledLoc = this->uniformLocation("environmentIrradianceMapEnabled");
@ -167,7 +173,37 @@ int ModelShaderProgram::environmentSpecularMapEnabledLoc()
return m_environmentSpecularMapEnabledLoc; return m_environmentSpecularMapEnabledLoc;
} }
int ModelShaderProgram::tongShadingEnabledLoc() int ModelShaderProgram::toonShadingEnabledLoc()
{ {
return m_tongShadingEnabledLoc; 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;
}

View File

@ -27,7 +27,13 @@ public:
int environmentIrradianceMapEnabledLoc(); int environmentIrradianceMapEnabledLoc();
int environmentSpecularMapIdLoc(); int environmentSpecularMapIdLoc();
int environmentSpecularMapEnabledLoc(); int environmentSpecularMapEnabledLoc();
int tongShadingEnabledLoc(); int toonShadingEnabledLoc();
int renderPurposeLoc();
int toonEdgeEnabledLoc();
int screenWidthLoc();
int screenHeightLoc();
int toonNormalMapIdLoc();
int toonDepthMapIdLoc();
bool isCoreProfile(); bool isCoreProfile();
static const QString &loadShaderSource(const QString &name); static const QString &loadShaderSource(const QString &name);
private: private:
@ -52,7 +58,13 @@ private:
int m_environmentIrradianceMapEnabledLoc = 0; int m_environmentIrradianceMapEnabledLoc = 0;
int m_environmentSpecularMapIdLoc = 0; int m_environmentSpecularMapIdLoc = 0;
int m_environmentSpecularMapEnabledLoc = 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 #endif

View File

@ -46,13 +46,18 @@ ModelWidget::ModelWidget(QWidget *parent) :
setFormat(fmt); setFormat(fmt);
} }
setContextMenuPolicy(Qt::CustomContextMenu); setContextMenuPolicy(Qt::CustomContextMenu);
m_widthInPixels = width() * window()->devicePixelRatio();
m_heightInPixels = height() * window()->devicePixelRatio();
zoom(200); zoom(200);
connect(&Preferences::instance(), &Preferences::tongShadingChanged, this, &ModelWidget::reRender); connect(&Preferences::instance(), &Preferences::toonShadingChanged, this, &ModelWidget::reRender);
} }
void ModelWidget::reRender() void ModelWidget::reRender()
{ {
emit renderParametersChanged();
update(); update();
} }
@ -82,6 +87,7 @@ void ModelWidget::setXRotation(int angle)
if (angle != m_xRot) { if (angle != m_xRot) {
m_xRot = angle; m_xRot = angle;
emit xRotationChanged(angle); emit xRotationChanged(angle);
emit renderParametersChanged();
update(); update();
} }
} }
@ -92,6 +98,7 @@ void ModelWidget::setYRotation(int angle)
if (angle != m_yRot) { if (angle != m_yRot) {
m_yRot = angle; m_yRot = angle;
emit yRotationChanged(angle); emit yRotationChanged(angle);
emit renderParametersChanged();
update(); update();
} }
} }
@ -102,10 +109,16 @@ void ModelWidget::setZRotation(int angle)
if (angle != m_zRot) { if (angle != m_zRot) {
m_zRot = angle; m_zRot = angle;
emit zRotationChanged(angle); emit zRotationChanged(angle);
emit renderParametersChanged();
update(); update();
} }
} }
MeshLoader *ModelWidget::fetchCurrentMesh()
{
return m_meshBinder.fetchCurrentMesh();
}
void ModelWidget::cleanup() void ModelWidget::cleanup()
{ {
if (m_program == nullptr) if (m_program == nullptr)
@ -141,7 +154,7 @@ void ModelWidget::initializeGL()
if (nullptr != versionString && if (nullptr != versionString &&
'\0' != versionString[0] && '\0' != versionString[0] &&
0 == strstr(versionString, "Mesa")) { 0 == strstr(versionString, "Mesa")) {
isCoreProfile = QSurfaceFormat::defaultFormat().profile() == QSurfaceFormat::CoreProfile; isCoreProfile = format().profile() == QSurfaceFormat::CoreProfile;
} }
m_program = new ModelShaderProgram(isCoreProfile); m_program = new ModelShaderProgram(isCoreProfile);
@ -174,6 +187,7 @@ void ModelWidget::paintGL()
#ifdef GL_LINE_SMOOTH #ifdef GL_LINE_SMOOTH
glEnable(GL_LINE_SMOOTH); glEnable(GL_LINE_SMOOTH);
#endif #endif
glViewport(0, 0, m_widthInPixels, m_heightInPixels);
m_world.setToIdentity(); m_world.setToIdentity();
m_world.rotate(m_xRot / 16.0f, 1, 0, 0); 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_world.rotate(m_zRot / 16.0f, 0, 0, 1);
m_program->bind(); 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->projectionMatrixLoc(), m_projection);
m_program->setUniformValue(m_program->modelMatrixLoc(), m_world); m_program->setUniformValue(m_program->modelMatrixLoc(), m_world);
QMatrix3x3 normalMatrix = m_world.normalMatrix(); 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->viewMatrixLoc(), m_camera);
m_program->setUniformValue(m_program->textureEnabledLoc(), 0); m_program->setUniformValue(m_program->textureEnabledLoc(), 0);
m_program->setUniformValue(m_program->normalMapEnabledLoc(), 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()) { if (m_mousePickingEnabled && !m_mousePickTargetPositionInModelSpace.isNull()) {
m_program->setUniformValue(m_program->mousePickEnabledLoc(), 1); m_program->setUniformValue(m_program->mousePickEnabledLoc(), 1);
@ -207,8 +228,11 @@ void ModelWidget::paintGL()
void ModelWidget::resizeGL(int w, int h) void ModelWidget::resizeGL(int w, int h)
{ {
m_widthInPixels = w * window()->devicePixelRatio();
m_heightInPixels = h * window()->devicePixelRatio();
m_projection.setToIdentity(); m_projection.setToIdentity();
m_projection.perspective(45.0f, GLfloat(w) / h, 0.01f, 100.0f); m_projection.perspective(45.0f, GLfloat(w) / h, 0.01f, 100.0f);
emit renderParametersChanged();
} }
std::pair<QVector3D, QVector3D> ModelWidget::screenPositionToMouseRay(const QPoint &screenPosition) std::pair<QVector3D, QVector3D> ModelWidget::screenPositionToMouseRay(const QPoint &screenPosition)
@ -383,6 +407,7 @@ void ModelWidget::zoom(float delta)
} }
} }
setGeometry(geometry().marginsAdded(margins)); setGeometry(geometry().marginsAdded(margins));
emit renderParametersChanged();
} }
void ModelWidget::setMousePickTargetPositionInModelSpace(QVector3D position) void ModelWidget::setMousePickTargetPositionInModelSpace(QVector3D position)
@ -400,9 +425,31 @@ void ModelWidget::setMousePickRadius(float radius)
void ModelWidget::updateMesh(MeshLoader *mesh) void ModelWidget::updateMesh(MeshLoader *mesh)
{ {
m_meshBinder.updateMesh(mesh); m_meshBinder.updateMesh(mesh);
emit renderParametersChanged();
update(); 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) void ModelWidget::enableMove(bool enabled)
{ {
m_moveEnabled = enabled; m_moveEnabled = enabled;

View File

@ -23,6 +23,7 @@ signals:
void mousePressed(); void mousePressed();
void mouseReleased(); void mouseReleased();
void addMouseRadius(float radius); void addMouseRadius(float radius);
void renderParametersChanged();
public: public:
ModelWidget(QWidget *parent = 0); ModelWidget(QWidget *parent = 0);
~ModelWidget(); ~ModelWidget();
@ -34,6 +35,7 @@ public:
{ {
m_transparent = t; m_transparent = t;
} }
MeshLoader *fetchCurrentMesh();
void updateMesh(MeshLoader *mesh); void updateMesh(MeshLoader *mesh);
void setGraphicsFunctions(SkeletonGraphicsFunctions *graphicsFunctions); void setGraphicsFunctions(SkeletonGraphicsFunctions *graphicsFunctions);
void toggleWireframe(); void toggleWireframe();
@ -48,6 +50,10 @@ public:
bool inputWheelEventFromOtherWidget(QWheelEvent *event); bool inputWheelEventFromOtherWidget(QWheelEvent *event);
bool inputMouseReleaseEventFromOtherWidget(QMouseEvent *event); bool inputMouseReleaseEventFromOtherWidget(QMouseEvent *event);
QPoint convertInputPosFromOtherWidget(QMouseEvent *event); QPoint convertInputPosFromOtherWidget(QMouseEvent *event);
void fetchCurrentToonNormalAndDepthMaps(QImage *normalMap, QImage *depthMap);
void updateToonNormalAndDepthMaps(QImage *normalMap, QImage *depthMap);
int widthInPixels();
int heightInPixels();
public slots: public slots:
void setXRotation(int angle); void setXRotation(int angle);
void setYRotation(int angle); void setYRotation(int angle);
@ -98,6 +104,8 @@ private:
QRect m_moveStartGeometry; QRect m_moveStartGeometry;
int m_modelInitialHeight = 0; int m_modelInitialHeight = 0;
QTimer *m_rotationTimer = nullptr; QTimer *m_rotationTimer = nullptr;
int m_widthInPixels = 0;
int m_heightInPixels = 0;
std::pair<QVector3D, QVector3D> screenPositionToMouseRay(const QPoint &screenPosition); std::pair<QVector3D, QVector3D> screenPositionToMouseRay(const QPoint &screenPosition);
public: public:
static int m_defaultXRotation; static int m_defaultXRotation;

View File

@ -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;
}

View File

@ -0,0 +1,34 @@
#ifndef DUST3D_NORMAL_AND_DEPTH_MAPS_GENERATOR_H
#define DUST3D_NORMAL_AND_DEPTH_MAPS_GENERATOR_H
#include <QObject>
#include <QImage>
#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

View File

@ -14,8 +14,8 @@ void Preferences::loadDefault()
{ {
m_componentCombineMode = CombineMode::Normal; m_componentCombineMode = CombineMode::Normal;
m_partColor = Qt::white; m_partColor = Qt::white;
m_flatShading = true; m_flatShading = false;
m_tongShading = false; m_toonShading = false;
m_textureSize = 1024; m_textureSize = 1024;
} }
@ -40,11 +40,11 @@ Preferences::Preferences()
m_flatShading = isTrueValueString(value); m_flatShading = isTrueValueString(value);
} }
{ {
QString value = m_settings.value("tongShading").toString(); QString value = m_settings.value("toonShading").toString();
if (value.isEmpty()) if (value.isEmpty())
m_tongShading = false; m_toonShading = false;
else else
m_tongShading = isTrueValueString(value); m_toonShading = isTrueValueString(value);
} }
{ {
QString value = m_settings.value("textureSize").toString(); QString value = m_settings.value("textureSize").toString();
@ -68,9 +68,9 @@ bool Preferences::flatShading() const
return m_flatShading; return m_flatShading;
} }
bool Preferences::tongShading() const bool Preferences::toonShading() const
{ {
return m_tongShading; return m_toonShading;
} }
int Preferences::textureSize() const int Preferences::textureSize() const
@ -105,13 +105,13 @@ void Preferences::setFlatShading(bool flatShading)
emit flatShadingChanged(); emit flatShadingChanged();
} }
void Preferences::setTongShading(bool tongShading) void Preferences::setToonShading(bool toonShading)
{ {
if (m_tongShading == tongShading) if (m_toonShading == toonShading)
return; return;
m_tongShading = tongShading; m_toonShading = toonShading;
m_settings.setValue("tongShading", tongShading ? "true" : "false"); m_settings.setValue("toonShading", toonShading ? "true" : "false");
emit tongShadingChanged(); emit toonShadingChanged();
} }
void Preferences::setTextureSize(int textureSize) void Preferences::setTextureSize(int textureSize)
@ -140,6 +140,6 @@ void Preferences::reset()
emit componentCombineModeChanged(); emit componentCombineModeChanged();
emit partColorChanged(); emit partColorChanged();
emit flatShadingChanged(); emit flatShadingChanged();
emit tongShadingChanged(); emit toonShadingChanged();
emit textureSizeChanged(); emit textureSizeChanged();
} }

View File

@ -14,7 +14,7 @@ public:
CombineMode componentCombineMode() const; CombineMode componentCombineMode() const;
const QColor &partColor() const; const QColor &partColor() const;
bool flatShading() const; bool flatShading() const;
bool tongShading() const; bool toonShading() const;
QSize documentWindowSize() const; QSize documentWindowSize() const;
void setDocumentWindowSize(const QSize&); void setDocumentWindowSize(const QSize&);
int textureSize() const; int textureSize() const;
@ -22,20 +22,20 @@ signals:
void componentCombineModeChanged(); void componentCombineModeChanged();
void partColorChanged(); void partColorChanged();
void flatShadingChanged(); void flatShadingChanged();
void tongShadingChanged(); void toonShadingChanged();
void textureSizeChanged(); void textureSizeChanged();
public slots: public slots:
void setComponentCombineMode(CombineMode mode); void setComponentCombineMode(CombineMode mode);
void setPartColor(const QColor &color); void setPartColor(const QColor &color);
void setFlatShading(bool flatShading); void setFlatShading(bool flatShading);
void setTongShading(bool tongShading); void setToonShading(bool toonShading);
void setTextureSize(int textureSize); void setTextureSize(int textureSize);
void reset(); void reset();
private: private:
CombineMode m_componentCombineMode; CombineMode m_componentCombineMode;
QColor m_partColor; QColor m_partColor;
bool m_flatShading; bool m_flatShading;
bool m_tongShading; bool m_toonShading;
QSettings m_settings; QSettings m_settings;
int m_textureSize; int m_textureSize;
private: private:

View File

@ -65,10 +65,10 @@ PreferencesWidget::PreferencesWidget(const Document *document, QWidget *parent)
Preferences::instance().setFlatShading(flatShadingBox->isChecked()); Preferences::instance().setFlatShading(flatShadingBox->isChecked());
}); });
QCheckBox *tongShadingBox = new QCheckBox(); QCheckBox *toonShadingBox = new QCheckBox();
Theme::initCheckbox(tongShadingBox); Theme::initCheckbox(toonShadingBox);
connect(tongShadingBox, &QCheckBox::stateChanged, this, [=]() { connect(toonShadingBox, &QCheckBox::stateChanged, this, [=]() {
Preferences::instance().setTongShading(tongShadingBox->isChecked()); Preferences::instance().setToonShading(toonShadingBox->isChecked());
}); });
QComboBox *textureSizeSelectBox = new QComboBox; QComboBox *textureSizeSelectBox = new QComboBox;
@ -84,14 +84,14 @@ PreferencesWidget::PreferencesWidget(const Document *document, QWidget *parent)
formLayout->addRow(tr("Part color:"), colorLayout); formLayout->addRow(tr("Part color:"), colorLayout);
formLayout->addRow(tr("Combine mode:"), combineModeSelectBox); formLayout->addRow(tr("Combine mode:"), combineModeSelectBox);
formLayout->addRow(tr("Flat shading:"), flatShadingBox); formLayout->addRow(tr("Flat shading:"), flatShadingBox);
formLayout->addRow(tr("Tong shading:"), tongShadingBox); formLayout->addRow(tr("Toon shading:"), toonShadingBox);
formLayout->addRow(tr("Texture size:"), textureSizeSelectBox); formLayout->addRow(tr("Texture size:"), textureSizeSelectBox);
auto loadFromPreferences = [=]() { auto loadFromPreferences = [=]() {
updatePickButtonColor(); updatePickButtonColor();
combineModeSelectBox->setCurrentIndex((int)Preferences::instance().componentCombineMode()); combineModeSelectBox->setCurrentIndex((int)Preferences::instance().componentCombineMode());
flatShadingBox->setChecked(Preferences::instance().flatShading()); flatShadingBox->setChecked(Preferences::instance().flatShading());
tongShadingBox->setChecked(Preferences::instance().tongShading()); toonShadingBox->setChecked(Preferences::instance().toonShading());
textureSizeSelectBox->setCurrentIndex( textureSizeSelectBox->setCurrentIndex(
textureSizeSelectBox->findText(QString::number(Preferences::instance().textureSize())) textureSizeSelectBox->findText(QString::number(Preferences::instance().textureSize()))
); );