From f7b5fb1c6a6d715e188356550496c0fc55ac7832 Mon Sep 17 00:00:00 2001 From: Jeremy Hu Date: Thu, 4 Oct 2018 20:51:01 +0800 Subject: [PATCH] Add PBR support Now can configure metalness and roughness for parts, however, the PBR render isn't enabled in the code currently. --- ACKNOWLEDGEMENTS.html | 52 ++++++ resources.qrc | 2 + shaders/default.core.vert | 6 + shaders/default.vert | 5 + shaders/pbr.frag | 320 +++++++++++++++++++++++++++++++++ shaders/pbr.vert | 27 +++ src/gltffile.cpp | 4 +- src/main.cpp | 5 + src/meshgenerator.cpp | 18 +- src/meshloader.cpp | 29 ++- src/meshloader.h | 4 +- src/meshresultcontext.cpp | 22 +-- src/meshresultcontext.h | 19 +- src/modelmeshbinder.cpp | 24 ++- src/modelshaderprogram.cpp | 13 +- src/modelshaderprogram.h | 2 +- src/skeletondocument.cpp | 36 ++++ src/skeletondocument.h | 24 ++- src/skeletondocumentwindow.cpp | 2 + src/skeletonparttreewidget.cpp | 22 +++ src/skeletonparttreewidget.h | 2 + src/skeletonpartwidget.cpp | 61 ++++++- src/skeletonpartwidget.h | 2 + src/skinnedmeshcreator.cpp | 6 +- src/texturegenerator.cpp | 8 +- 25 files changed, 659 insertions(+), 56 deletions(-) create mode 100644 shaders/pbr.frag create mode 100644 shaders/pbr.vert diff --git a/ACKNOWLEDGEMENTS.html b/ACKNOWLEDGEMENTS.html index fea986ca..884d7a67 100644 --- a/ACKNOWLEDGEMENTS.html +++ b/ACKNOWLEDGEMENTS.html @@ -556,3 +556,55 @@ https://www.reddit.com/r/gamedev/comments/5iuf3h/i_am_writting_a_3d_monster_mode http://www.gdcvault.com/play/1020583/Animation-Bootcamp-An-Indie-Approach +

Qt3D

+
+    /****************************************************************************
+    **
+    ** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+    ** Contact: https://www.qt.io/licensing/
+    **
+    ** This file is part of the Qt3D module of the Qt Toolkit.
+    **
+    ** $QT_BEGIN_LICENSE:BSD$
+    ** Commercial License Usage
+    ** Licensees holding valid commercial Qt licenses may use this file in
+    ** accordance with the commercial license agreement provided with the
+    ** Software or, alternatively, in accordance with the terms contained in
+    ** a written agreement between you and The Qt Company. For licensing terms
+    ** and conditions see https://www.qt.io/terms-conditions. For further
+    ** information use the contact form at https://www.qt.io/contact-us.
+    **
+    ** BSD License Usage
+    ** Alternatively, you may use this file under the terms of the BSD license
+    ** as follows:
+    **
+    ** "Redistribution and use in source and binary forms, with or without
+    ** modification, are permitted provided that the following conditions are
+    ** met:
+    **   * Redistributions of source code must retain the above copyright
+    **     notice, this list of conditions and the following disclaimer.
+    **   * Redistributions in binary form must reproduce the above copyright
+    **     notice, this list of conditions and the following disclaimer in
+    **     the documentation and/or other materials provided with the
+    **     distribution.
+    **   * Neither the name of The Qt Company Ltd nor the names of its
+    **     contributors may be used to endorse or promote products derived
+    **     from this software without specific prior written permission.
+    **
+    **
+    ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+    ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+    ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+    ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+    ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+    ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+    ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+    ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+    ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+    ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+    **
+    ** $QT_END_LICENSE$
+    **
+    ****************************************************************************/
+
\ No newline at end of file diff --git a/resources.qrc b/resources.qrc index 3392db02..79ed3608 100644 --- a/resources.qrc +++ b/resources.qrc @@ -10,6 +10,8 @@ shaders/default.frag shaders/default.core.vert shaders/default.core.frag + shaders/pbr.vert + shaders/pbr.frag ACKNOWLEDGEMENTS.html AUTHORS CONTRIBUTORS diff --git a/shaders/default.core.vert b/shaders/default.core.vert index 4aafcdb5..876b2ba6 100644 --- a/shaders/default.core.vert +++ b/shaders/default.core.vert @@ -3,10 +3,14 @@ in vec4 vertex; in vec3 normal; in vec3 color; in vec2 texCoord; +in float metalness; +in float roughness; out vec3 vert; out vec3 vertNormal; out vec3 vertColor; out vec2 vertTexCoord; +out float vertMetalness; +out float vertRoughness; uniform mat4 projMatrix; uniform mat4 mvMatrix; uniform mat3 normalMatrix; @@ -16,5 +20,7 @@ void main() vertNormal = normalMatrix * normal; vertColor = color; vertTexCoord = texCoord; + vertMetalness = metalness; + vertRoughness = roughness; gl_Position = projMatrix * mvMatrix * vertex; } \ No newline at end of file diff --git a/shaders/default.vert b/shaders/default.vert index 63a207d4..a711eda0 100644 --- a/shaders/default.vert +++ b/shaders/default.vert @@ -2,10 +2,15 @@ attribute vec4 vertex; attribute vec3 normal; attribute vec3 color; attribute vec2 texCoord; +attribute float metalness; +attribute float roughness; varying vec3 vert; varying vec3 vertNormal; varying vec3 vertColor; varying vec2 vertTexCoord; +varying float vertMetalness; +varying float vertRoughness; +varying vec3 vertView; uniform mat4 projMatrix; uniform mat4 mvMatrix; uniform mat3 normalMatrix; diff --git a/shaders/pbr.frag b/shaders/pbr.frag new file mode 100644 index 00000000..3309648a --- /dev/null +++ b/shaders/pbr.frag @@ -0,0 +1,320 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// Please note that, this file "pbr.frag" is copied and slightly modified from the Qt3D's pbr shader "metalrough.inc.frag" +// https://github.com/qt/qt3d/blob/5.11/src/extras/shaders/gl3/metalrough.inc.frag + +// Exposure correction +float exposure; +// Gamma correction +float gamma; + +varying highp vec3 vert; +varying highp vec3 vertNormal; +varying highp vec3 vertColor; +varying highp vec2 vertTexCoord; +varying highp float vertMetalness; +varying highp float vertRoughness; +varying highp vec3 vertView; +uniform highp vec3 lightPos; +uniform highp sampler2D textureId; +uniform highp int textureEnabled; + +const int MAX_LIGHTS = 8; +const int TYPE_POINT = 0; +const int TYPE_DIRECTIONAL = 1; +const int TYPE_SPOT = 2; +struct Light { + int type; + vec3 position; + vec3 color; + float intensity; + vec3 direction; + float constantAttenuation; + float linearAttenuation; + float quadraticAttenuation; + float cutOffAngle; +}; +int lightCount; +Light lights[MAX_LIGHTS]; + +float remapRoughness(const in float roughness) +{ + // As per page 14 of + // http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr.pdf + // we remap the roughness to give a more perceptually linear response + // of "bluriness" as a function of the roughness specified by the user. + // r = roughness^2 + float maxSpecPower; + float minRoughness; + maxSpecPower = 999999.0; + minRoughness = sqrt(2.0 / (maxSpecPower + 2.0)); + return max(roughness * roughness, minRoughness); +} + +float normalDistribution(const in vec3 n, const in vec3 h, const in float alpha) +{ + // Blinn-Phong approximation - see + // http://graphicrants.blogspot.co.uk/2013/08/specular-brdf-reference.html + float specPower = 2.0 / (alpha * alpha) - 2.0; + return (specPower + 2.0) / (2.0 * 3.14159) * pow(max(dot(n, h), 0.0), specPower); +} + +vec3 fresnelFactor(const in vec3 color, const in float cosineFactor) +{ + // Calculate the Fresnel effect value + vec3 f = color; + vec3 F = f + (1.0 - f) * pow(1.0 - cosineFactor, 5.0); + return clamp(F, f, vec3(1.0)); +} + +float geometricModel(const in float lDotN, + const in float vDotN, + const in vec3 h) +{ + // Implicit geometric model (equal to denominator in specular model). + // This currently assumes that there is no attenuation by geometric shadowing or + // masking according to the microfacet theory. + return lDotN * vDotN; +} + +vec3 specularModel(const in vec3 F0, + const in float sDotH, + const in float sDotN, + const in float vDotN, + const in vec3 n, + const in vec3 h) +{ + // Clamp sDotN and vDotN to small positive value to prevent the + // denominator in the reflection equation going to infinity. Balance this + // by using the clamped values in the geometric factor function to + // avoid ugly seams in the specular lighting. + float sDotNPrime = max(sDotN, 0.001); + float vDotNPrime = max(vDotN, 0.001); + + vec3 F = fresnelFactor(F0, sDotH); + float G = geometricModel(sDotNPrime, vDotNPrime, h); + + vec3 cSpec = F * G / (4.0 * sDotNPrime * vDotNPrime); + return clamp(cSpec, vec3(0.0), vec3(1.0)); +} + +vec3 pbrModel(const in int lightIndex, + const in vec3 wPosition, + const in vec3 wNormal, + const in vec3 wView, + const in vec3 baseColor, + const in float metalness, + const in float alpha, + const in float ambientOcclusion) +{ + // Calculate some useful quantities + vec3 n = wNormal; + vec3 s = vec3(0.0); + vec3 v = wView; + vec3 h = vec3(0.0); + + float vDotN = dot(v, n); + float sDotN = 0.0; + float sDotH = 0.0; + float att = 1.0; + + if (lights[lightIndex].type != TYPE_DIRECTIONAL) { + // Point and Spot lights + vec3 sUnnormalized = vec3(lights[lightIndex].position) - wPosition; + s = normalize(sUnnormalized); + + // Calculate the attenuation factor + sDotN = dot(s, n); + if (sDotN > 0.0) { + if (lights[lightIndex].constantAttenuation != 0.0 + || lights[lightIndex].linearAttenuation != 0.0 + || lights[lightIndex].quadraticAttenuation != 0.0) { + float dist = length(sUnnormalized); + att = 1.0 / (lights[lightIndex].constantAttenuation + + lights[lightIndex].linearAttenuation * dist + + lights[lightIndex].quadraticAttenuation * dist * dist); + } + + // The light direction is in world space already + if (lights[lightIndex].type == TYPE_SPOT) { + // Check if fragment is inside or outside of the spot light cone + if (degrees(acos(dot(-s, lights[lightIndex].direction))) > lights[lightIndex].cutOffAngle) + sDotN = 0.0; + } + } + } else { + // Directional lights + // The light direction is in world space already + s = normalize(-lights[lightIndex].direction); + sDotN = dot(s, n); + } + + h = normalize(s + v); + sDotH = dot(s, h); + + // Calculate diffuse component + vec3 diffuseColor = (1.0 - metalness) * baseColor * lights[lightIndex].color; + vec3 diffuse = diffuseColor * max(sDotN, 0.0) / 3.14159; + + // Calculate specular component + vec3 dielectricColor = vec3(0.04); + vec3 F0 = mix(dielectricColor, baseColor, metalness); + vec3 specularFactor = vec3(0.0); + if (sDotN > 0.0) { + specularFactor = specularModel(F0, sDotH, sDotN, vDotN, n, h); + specularFactor *= normalDistribution(n, h, alpha); + } + vec3 specularColor = lights[lightIndex].color; + vec3 specular = specularColor * specularFactor; + + // Blend between diffuse and specular to conserver energy + vec3 color = att * lights[lightIndex].intensity * (specular + diffuse * (vec3(1.0) - specular)); + + // Reduce by ambient occlusion amount + color *= ambientOcclusion; + + return color; +} + +vec3 toneMap(const in vec3 c) +{ + return c / (c + vec3(1.0)); +} + +vec3 gammaCorrect(const in vec3 color) +{ + return pow(color, vec3(1.0 / gamma)); +} + +vec4 metalRoughFunction(const in vec4 baseColor, + const in float metalness, + const in float roughness, + const in float ambientOcclusion, + const in vec3 worldPosition, + const in vec3 worldView, + const in vec3 worldNormal) +{ + vec3 cLinear = vec3(0.0); + + // Remap roughness for a perceptually more linear correspondence + float alpha = remapRoughness(roughness); + + for (int i = 0; i < lightCount; ++i) { + cLinear += pbrModel(i, + worldPosition, + worldNormal, + worldView, + baseColor.rgb, + metalness, + alpha, + ambientOcclusion); + } + + // Apply exposure correction + cLinear *= pow(2.0, exposure); + + // Apply simple (Reinhard) tonemap transform to get into LDR range [0, 1] + vec3 cToneMapped = toneMap(cLinear); + + // Apply gamma correction prior to display + vec3 cGamma = gammaCorrect(cToneMapped); + + return vec4(cGamma, 1.0); +} + +void main() +{ + highp vec3 color = vertColor; + if (textureEnabled == 1) { + color = texture2D(textureId, vertTexCoord).rgb; + } + + // FIXME: don't hard code here + exposure = 0.0; + gamma = 2.2; + const highp float vertAmbientOcclusion = 1.0; + + // Light settings: + // https://doc-snapshots.qt.io/qt5-5.12/qt3d-pbr-materials-lights-qml.html + lightCount = 3; + + lights[0].type = TYPE_POINT; + lights[0].position = vec3(0.0, 5.0, 0.0); + lights[0].color = vec3(1.0, 1.0, 1.0); + lights[0].intensity = 5.0; + lights[0].constantAttenuation = 1.0; + lights[0].linearAttenuation = 0.0; + lights[0].quadraticAttenuation = 0.0025; + + lights[1].type = TYPE_POINT; + lights[1].position = vec3(5.0, 0.0, 0.0); + lights[1].color = vec3(1.0, 1.0, 1.0); + lights[1].intensity = 0.8; + lights[1].constantAttenuation = 1.0; + lights[1].linearAttenuation = 0.0; + lights[1].quadraticAttenuation = 0.0025; + + lights[2].type = TYPE_POINT; + lights[2].position = vec3(0.0, -5.0, 0.0); + lights[2].color = vec3(1.0, 1.0, 1.0); + lights[2].intensity = 0.15; + lights[2].constantAttenuation = 1.0; + lights[2].linearAttenuation = 0.0; + lights[2].quadraticAttenuation = 0.0025; + + gl_FragColor = metalRoughFunction(vec4(color, 1.0), + vertMetalness, + vertRoughness, + vertAmbientOcclusion, + vert, + vertView, + vertNormal); +} diff --git a/shaders/pbr.vert b/shaders/pbr.vert new file mode 100644 index 00000000..bbfe78fe --- /dev/null +++ b/shaders/pbr.vert @@ -0,0 +1,27 @@ +attribute vec4 vertex; +attribute vec3 normal; +attribute vec3 color; +attribute vec2 texCoord; +attribute float metalness; +attribute float roughness; +varying vec3 vert; +varying vec3 vertNormal; +varying vec3 vertColor; +varying vec2 vertTexCoord; +varying float vertMetalness; +varying float vertRoughness; +varying vec3 vertView; +uniform mat4 projMatrix; +uniform mat4 mvMatrix; +uniform mat3 normalMatrix; +void main() +{ + vert = vertex.xyz; + vertNormal = normalize((projMatrix * mvMatrix * vec4(normal, 1.0)).xyz); + vertColor = color; + vertTexCoord = texCoord; + vertMetalness = metalness; + vertRoughness = roughness; + vertView = (projMatrix * mvMatrix * vec4(0, 0, 0, 1.0)).xyz; + gl_Position = projMatrix * mvMatrix * vertex; +} \ No newline at end of file diff --git a/src/gltffile.cpp b/src/gltffile.cpp index 807c8cfb..cffed23e 100644 --- a/src/gltffile.cpp +++ b/src/gltffile.cpp @@ -157,8 +157,8 @@ GltfFileWriter::GltfFileWriter(MeshResultContext &resultContext, m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["WEIGHTS_0"] = bufferViewIndex + (++attributeIndex); } m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["baseColorTexture"]["index"] = 0; - m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["metallicFactor"] = 0.0; - m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["roughnessFactor"] = 1.0; + m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["metallicFactor"] = part.second.material.metalness; + m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["roughnessFactor"] = part.second.material.roughness; primitiveIndex++; diff --git a/src/main.cpp b/src/main.cpp index ffe1216a..06dd36d7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "skeletondocumentwindow.h" #include "theme.h" #include "version.h" @@ -12,6 +13,10 @@ int main(int argc, char ** argv) { QApplication app(argc, argv); + QSurfaceFormat format = QSurfaceFormat::defaultFormat(); + format.setProfile(QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile); + QSurfaceFormat::setDefaultFormat(format); + // QuantumCD/Qt 5 Dark Fusion Palette // https://gist.github.com/QuantumCD/6245215 qApp->setStyle(QStyleFactory::create("Fusion")); diff --git a/src/meshgenerator.cpp b/src/meshgenerator.cpp index e055fa0f..b51d81ab 100644 --- a/src/meshgenerator.cpp +++ b/src/meshgenerator.cpp @@ -251,6 +251,16 @@ void *MeshGenerator::combinePartMesh(QString partId) if (MeshGenerator::m_enableDebug) meshlite_bmesh_enable_debug(m_meshliteContext, bmeshId, 1); + float metalness = 0.0; + QString metalnessString = valueOfKeyInMapOrEmpty(part, "metalness"); + if (!metalnessString.isEmpty()) + metalness = metalnessString.toFloat(); + + float roughness = 1.0; + QString roughnessString = valueOfKeyInMapOrEmpty(part, "roughness"); + if (!roughnessString.isEmpty()) + roughness = roughnessString.toFloat(); + QString mirroredPartId; QUuid mirroredPartIdNotAsString; if (xMirrored) { @@ -291,7 +301,9 @@ void *MeshGenerator::combinePartMesh(QString partId) bmeshNode.origin = QVector3D(x, y, z); bmeshNode.radius = radius; bmeshNode.nodeId = QUuid(nodeId); - bmeshNode.color = partColor; + bmeshNode.material.color = partColor; + bmeshNode.material.metalness = metalness; + bmeshNode.material.roughness = roughness; bmeshNode.boneMark = boneMark; //if (SkeletonBoneMark::None != boneMark) // bmeshNode.color = SkeletonBoneMarkToColor(boneMark); @@ -362,7 +374,7 @@ void *MeshGenerator::combinePartMesh(QString partId) if (m_requirePreviewPartIds.find(partIdNotAsString) != m_requirePreviewPartIds.end()) { int trimedMeshId = meshlite_trim(m_meshliteContext, meshId, 1); - m_partPreviewMeshMap[partIdNotAsString] = new MeshLoader(m_meshliteContext, trimedMeshId, -1, partColor, nullptr, m_smoothNormal); + m_partPreviewMeshMap[partIdNotAsString] = new MeshLoader(m_meshliteContext, trimedMeshId, -1, {partColor, metalness, roughness}, nullptr, m_smoothNormal); m_generatedPreviewPartIds.insert(partIdNotAsString); } @@ -697,7 +709,7 @@ void MeshGenerator::process() if (resultMeshId > 0) { loadGeneratedPositionsToMeshResultContext(m_meshliteContext, triangulatedFinalMeshId); - m_mesh = new MeshLoader(m_meshliteContext, resultMeshId, triangulatedFinalMeshId, Theme::white, &m_meshResultContext->triangleColors(), m_smoothNormal); + m_mesh = new MeshLoader(m_meshliteContext, resultMeshId, triangulatedFinalMeshId, {Theme::white, 0.0, 1.0}, &m_meshResultContext->triangleMaterials(), m_smoothNormal); } if (needDeleteCacheContext) { diff --git a/src/meshloader.cpp b/src/meshloader.cpp index 886be732..95c9ee2a 100644 --- a/src/meshloader.cpp +++ b/src/meshloader.cpp @@ -6,7 +6,7 @@ #define MAX_VERTICES_PER_FACE 100 -MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, QColor modelColor, const std::vector *triangleColors, bool smoothNormal) : +MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, Material material, const std::vector *triangleMaterials, bool smoothNormal) : m_triangleVertices(nullptr), m_triangleVertexCount(0), m_edgeVertices(nullptr), @@ -68,6 +68,8 @@ MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, QColo v->colorB = 0.0; v->texU = 0.0; v->texV = 0.0; + v->metalness = 0; + v->roughness = 1.0; } } @@ -95,9 +97,9 @@ MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, QColo GLfloat *triangleNormals = new GLfloat[triangleCount * 3]; int loadedTriangleNormalItemCount = meshlite_get_triangle_normal_array(meshlite, triangleMesh, triangleNormals, triangleCount * 3); - float modelR = modelColor.redF(); - float modelG = modelColor.greenF(); - float modelB = modelColor.blueF(); + float modelR = material.color.redF(); + float modelG = material.color.greenF(); + float modelB = material.color.blueF(); m_triangleVertexCount = triangleCount * 3; m_triangleVertices = new Vertex[m_triangleVertexCount * 3]; for (int i = 0; i < triangleCount; i++) { @@ -105,11 +107,15 @@ MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, QColo float useColorR = modelR; float useColorG = modelG; float useColorB = modelB; - if (triangleColors && i < (int)triangleColors->size()) { - QColor triangleColor = (*triangleColors)[i]; - useColorR = triangleColor.redF(); - useColorG = triangleColor.greenF(); - useColorB = triangleColor.blueF(); + float useMetalness = material.metalness; + float useRoughness = material.roughness; + if (triangleMaterials && i < (int)triangleMaterials->size()) { + auto triangleMaterial = (*triangleMaterials)[i]; + useColorR = triangleMaterial.color.redF(); + useColorG = triangleMaterial.color.greenF(); + useColorB = triangleMaterial.color.blueF(); + useMetalness = triangleMaterial.metalness; + useRoughness = triangleMaterial.roughness; } TriangulatedFace triangulatedFace; triangulatedFace.color.setRedF(useColorR); @@ -136,6 +142,8 @@ MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, QColo v->colorR = useColorR; v->colorG = useColorG; v->colorB = useColorB; + v->metalness = useMetalness; + v->roughness = useRoughness; } m_triangulatedFaces.push_back(triangulatedFace); } @@ -226,6 +234,7 @@ MeshLoader::MeshLoader(MeshResultContext &resultContext) : const ResultVertex *srcVert = &part.second.vertices[vertexIndex]; const QVector3D *srcNormal = &part.second.interpolatedVertexNormals[vertexIndex]; const ResultVertexUv *srcUv = &part.second.vertexUvs[vertexIndex]; + const Material *srcMaterial = &part.second.material; Vertex *dest = &m_triangleVertices[destIndex]; dest->colorR = 0; dest->colorG = 0; @@ -238,6 +247,8 @@ MeshLoader::MeshLoader(MeshResultContext &resultContext) : dest->normX = srcNormal->x(); dest->normY = srcNormal->y(); dest->normZ = srcNormal->z(); + dest->metalness = srcMaterial->metalness; + dest->roughness = srcMaterial->roughness; destIndex++; } } diff --git a/src/meshloader.h b/src/meshloader.h index 71a166f5..ed364ceb 100644 --- a/src/meshloader.h +++ b/src/meshloader.h @@ -26,6 +26,8 @@ typedef struct GLfloat colorB; GLfloat texU; GLfloat texV; + GLfloat metalness; + GLfloat roughness; } Vertex; #pragma pack(pop) @@ -38,7 +40,7 @@ struct TriangulatedFace class MeshLoader { public: - MeshLoader(void *meshlite, int meshId, int triangulatedMeshId=-1, QColor modelColor=Theme::white, const std::vector *triangleColors=nullptr, bool smoothNormal=true); + MeshLoader(void *meshlite, int meshId, int triangulatedMeshId=-1, Material material={Theme::white, 0.0, 1.0}, const std::vector *triangleMaterials=nullptr, bool smoothNormal=true); MeshLoader(MeshResultContext &resultContext); MeshLoader(Vertex *triangleVertices, int vertexNum); MeshLoader(const MeshLoader &mesh); diff --git a/src/meshresultcontext.cpp b/src/meshresultcontext.cpp index 76de8231..850b6430 100644 --- a/src/meshresultcontext.cpp +++ b/src/meshresultcontext.cpp @@ -26,7 +26,7 @@ struct CandidateEdge MeshResultContext::MeshResultContext() : m_triangleSourceResolved(false), - m_triangleColorResolved(false), + m_triangleMaterialResolved(false), m_triangleEdgeSourceMapResolved(false), m_bmeshNodeMapResolved(false), m_resultPartsResolved(false), @@ -56,13 +56,13 @@ const std::map> &MeshResultContext::vertexSourceMap return m_vertexSourceMap; } -const std::vector &MeshResultContext::triangleColors() +const std::vector &MeshResultContext::triangleMaterials() { - if (!m_triangleColorResolved) { - calculateTriangleColors(m_triangleColors); - m_triangleColorResolved = true; + if (!m_triangleMaterialResolved) { + calculateTriangleMaterials(m_triangleMaterials); + m_triangleMaterialResolved = true; } - return m_triangleColors; + return m_triangleMaterials; } const std::map, std::pair> &MeshResultContext::triangleEdgeSourceMap() @@ -257,15 +257,15 @@ void MeshResultContext::calculateRemainingVertexSourceNodesAfterTriangleSourceNo } } -void MeshResultContext::calculateTriangleColors(std::vector &triangleColors) +void MeshResultContext::calculateTriangleMaterials(std::vector &triangleMaterials) { - std::map, QColor> nodeColorMap; + std::map, Material> nodeMaterialMap; for (const auto &it: bmeshNodes) { - nodeColorMap[std::make_pair(it.partId, it.nodeId)] = it.color; + nodeMaterialMap[std::make_pair(it.partId, it.nodeId)] = it.material; } const auto sourceNodes = triangleSourceNodes(); for (const auto &it: sourceNodes) { - triangleColors.push_back(nodeColorMap[it]); + triangleMaterials.push_back(nodeMaterialMap[it]); } } @@ -322,7 +322,7 @@ void MeshResultContext::calculateResultParts(std::map &parts) auto it = parts.find(sourceNode.first); if (it == parts.end()) { ResultPart newPart; - newPart.color = triangleColors()[x]; + newPart.material = triangleMaterials()[x]; parts.insert(std::make_pair(sourceNode.first, newPart)); } auto &resultPart = parts[sourceNode.first]; diff --git a/src/meshresultcontext.h b/src/meshresultcontext.h index db345328..acf1358b 100644 --- a/src/meshresultcontext.h +++ b/src/meshresultcontext.h @@ -10,13 +10,20 @@ #define MAX_WEIGHT_NUM 4 +struct Material +{ + QColor color; + float metalness; + float roughness; +}; + struct BmeshNode { QUuid partId; QUuid nodeId; QVector3D origin; float radius = 0; - QColor color; + Material material; SkeletonBoneMark boneMark; }; @@ -51,7 +58,7 @@ struct ResultVertexUv struct ResultPart { - QColor color; + Material material; std::vector vertices; std::vector verticesOldIndicies; std::vector interpolatedVertexNormals; @@ -83,7 +90,7 @@ public: MeshResultContext(); public: const std::vector> &triangleSourceNodes(); - const std::vector &triangleColors(); + const std::vector &triangleMaterials(); const std::map, std::pair> &triangleEdgeSourceMap(); const std::map, BmeshNode *> &bmeshNodeMap(); const std::map &parts(); @@ -94,7 +101,7 @@ public: const std::vector &interpolatedVertexNormals(); private: bool m_triangleSourceResolved; - bool m_triangleColorResolved; + bool m_triangleMaterialResolved; bool m_triangleEdgeSourceMapResolved; bool m_bmeshNodeMapResolved; bool m_resultPartsResolved; @@ -103,7 +110,7 @@ private: bool m_vertexNormalsInterpolated; private: std::vector> m_triangleSourceNodes; - std::vector m_triangleColors; + std::vector m_triangleMaterials; std::map, std::pair> m_triangleEdgeSourceMap; std::map, BmeshNode *> m_bmeshNodeMap; std::map m_resultParts; @@ -117,7 +124,7 @@ private: private: void calculateTriangleSourceNodes(std::vector> &triangleSourceNodes, std::map> &vertexSourceMap); void calculateRemainingVertexSourceNodesAfterTriangleSourceNodesSolved(std::map> &vertexSourceMap); - void calculateTriangleColors(std::vector &triangleColors); + void calculateTriangleMaterials(std::vector &triangleMaterials); void calculateTriangleEdgeSourceMap(std::map, std::pair> &triangleEdgeSourceMap); void calculateBmeshNodeMap(std::map, BmeshNode *> &bmeshNodeMap); void calculateResultParts(std::map &parts); diff --git a/src/modelmeshbinder.cpp b/src/modelmeshbinder.cpp index 2b025ac6..c8bb8383 100644 --- a/src/modelmeshbinder.cpp +++ b/src/modelmeshbinder.cpp @@ -152,10 +152,14 @@ void ModelMeshBinder::paint(ModelShaderProgram *program) f->glEnableVertexAttribArray(1); f->glEnableVertexAttribArray(2); f->glEnableVertexAttribArray(3); - f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(GLfloat), 0); - f->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(GLfloat), reinterpret_cast(3 * sizeof(GLfloat))); - f->glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(GLfloat), reinterpret_cast(6 * sizeof(GLfloat))); - f->glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, 11 * sizeof(GLfloat), reinterpret_cast(9 * sizeof(GLfloat))); + f->glEnableVertexAttribArray(4); + f->glEnableVertexAttribArray(5); + f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0); + f->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(3 * sizeof(GLfloat))); + f->glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(6 * sizeof(GLfloat))); + f->glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(9 * sizeof(GLfloat))); + f->glVertexAttribPointer(4, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(11 * sizeof(GLfloat))); + f->glVertexAttribPointer(5, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(12 * sizeof(GLfloat))); m_vboTriangle.release(); } { @@ -171,10 +175,14 @@ void ModelMeshBinder::paint(ModelShaderProgram *program) f->glEnableVertexAttribArray(1); f->glEnableVertexAttribArray(2); f->glEnableVertexAttribArray(3); - f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(GLfloat), 0); - f->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(GLfloat), reinterpret_cast(3 * sizeof(GLfloat))); - f->glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(GLfloat), reinterpret_cast(6 * sizeof(GLfloat))); - f->glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, 11 * sizeof(GLfloat), reinterpret_cast(9 * sizeof(GLfloat))); + f->glEnableVertexAttribArray(4); + f->glEnableVertexAttribArray(5); + f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0); + f->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(3 * sizeof(GLfloat))); + f->glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(6 * sizeof(GLfloat))); + f->glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(9 * sizeof(GLfloat))); + f->glVertexAttribPointer(4, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(11 * sizeof(GLfloat))); + f->glVertexAttribPointer(5, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(12 * sizeof(GLfloat))); m_vboEdge.release(); } } else { diff --git a/src/modelshaderprogram.cpp b/src/modelshaderprogram.cpp index cd4a161b..b2ed2062 100644 --- a/src/modelshaderprogram.cpp +++ b/src/modelshaderprogram.cpp @@ -17,19 +17,26 @@ const QString &ModelShaderProgram::loadShaderSource(const QString &name) return insertResult.first->second; } -ModelShaderProgram::ModelShaderProgram() +ModelShaderProgram::ModelShaderProgram(bool usePBR) { if (QSurfaceFormat::defaultFormat().profile() == QSurfaceFormat::CoreProfile) { this->addShaderFromSourceCode(QOpenGLShader::Vertex, loadShaderSource(":/shaders/default.core.vert")); this->addShaderFromSourceCode(QOpenGLShader::Fragment, loadShaderSource(":/shaders/default.core.frag")); } else { - this->addShaderFromSourceCode(QOpenGLShader::Vertex, loadShaderSource(":/shaders/default.vert")); - this->addShaderFromSourceCode(QOpenGLShader::Fragment, loadShaderSource(":/shaders/default.frag")); + if (usePBR) { + this->addShaderFromSourceCode(QOpenGLShader::Vertex, loadShaderSource(":/shaders/pbr.vert")); + this->addShaderFromSourceCode(QOpenGLShader::Fragment, loadShaderSource(":/shaders/pbr.frag")); + } else { + this->addShaderFromSourceCode(QOpenGLShader::Vertex, loadShaderSource(":/shaders/default.vert")); + this->addShaderFromSourceCode(QOpenGLShader::Fragment, loadShaderSource(":/shaders/default.frag")); + } } this->bindAttributeLocation("vertex", 0); this->bindAttributeLocation("normal", 1); this->bindAttributeLocation("color", 2); this->bindAttributeLocation("texCoord", 3); + this->bindAttributeLocation("metalness", 4); + this->bindAttributeLocation("roughness", 5); this->link(); this->bind(); diff --git a/src/modelshaderprogram.h b/src/modelshaderprogram.h index 11c3a6d2..eb7da3f8 100644 --- a/src/modelshaderprogram.h +++ b/src/modelshaderprogram.h @@ -6,7 +6,7 @@ class ModelShaderProgram : public QOpenGLShaderProgram { public: - ModelShaderProgram(); + ModelShaderProgram(bool usePBR=false); int projMatrixLoc(); int mvMatrixLoc(); int normalMatrixLoc(); diff --git a/src/skeletondocument.cpp b/src/skeletondocument.cpp index ba33da4f..e91593c5 100644 --- a/src/skeletondocument.cpp +++ b/src/skeletondocument.cpp @@ -874,6 +874,10 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::setparts[part["id"]] = part; } for (const auto &nodeIt: nodeMap) { @@ -1071,6 +1075,12 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr const auto &deformWidthIt = partKv.second.find("deformWidth"); if (deformWidthIt != partKv.second.end()) part.setDeformWidth(deformWidthIt->second.toFloat()); + const auto &metalnessIt = partKv.second.find("metalness"); + if (metalnessIt != partKv.second.end()) + part.metalness = metalnessIt->second.toFloat(); + const auto &roughnessIt = partKv.second.find("roughness"); + if (roughnessIt != partKv.second.end()) + part.roughness = roughnessIt->second.toFloat(); newAddedPartIds.insert(part.id); } for (const auto &nodeKv: snapshot.nodes) { @@ -2145,6 +2155,32 @@ void SkeletonDocument::setPartDeformWidth(QUuid partId, float width) emit skeletonChanged(); } +void SkeletonDocument::setPartMetalness(QUuid partId, float metalness) +{ + auto part = partMap.find(partId); + if (part == partMap.end()) { + qDebug() << "Part not found:" << partId; + return; + } + part->second.metalness = metalness; + part->second.dirty = true; + emit partMetalnessChanged(partId); + emit skeletonChanged(); +} + +void SkeletonDocument::setPartRoughness(QUuid partId, float roughness) +{ + auto part = partMap.find(partId); + if (part == partMap.end()) { + qDebug() << "Part not found:" << partId; + return; + } + part->second.roughness = roughness; + part->second.dirty = true; + emit partRoughnessChanged(partId); + emit skeletonChanged(); +} + void SkeletonDocument::setPartRoundState(QUuid partId, bool rounded) { auto part = partMap.find(partId); diff --git a/src/skeletondocument.h b/src/skeletondocument.h index 599fabd6..26532ad9 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -97,6 +97,8 @@ public: std::vector nodeIds; bool dirty; bool wrapped; + float metalness; + float roughness; SkeletonPart(const QUuid &withId=QUuid()) : visible(true), locked(false), @@ -110,7 +112,9 @@ public: color(Theme::white), hasColor(false), dirty(true), - wrapped(false) + wrapped(false), + metalness(0.0), + roughness(1.0) { id = withId.isNull() ? QUuid::createUuid() : withId; } @@ -142,6 +146,18 @@ public: { return deformThicknessAdjusted() || deformWidthAdjusted(); } + bool metalnessAdjusted() const + { + return fabs(metalness - 0.0) >= 0.01; + } + bool roughnessAdjusted() const + { + return fabs(roughness - 1.0) >= 0.01; + } + bool materialAdjusted() const + { + return metalnessAdjusted() || roughnessAdjusted(); + } bool isEditVisible() const { return visible && !disabled; @@ -162,6 +178,8 @@ public: wrapped = other.wrapped; componentId = other.componentId; dirty = other.dirty; + metalness = other.metalness; + roughness = other.roughness; } void updatePreviewMesh(MeshLoader *previewMesh) { @@ -444,6 +462,8 @@ signals: void partRoundStateChanged(QUuid partId); void partColorStateChanged(QUuid partId); void partWrapStateChanged(QUuid partId); + void partMetalnessChanged(QUuid partId); + void partRoughnessChanged(QUuid partId); void componentInverseStateChanged(QUuid partId); void cleanup(); void originChanged(); @@ -583,6 +603,8 @@ public slots: void setPartRoundState(QUuid partId, bool rounded); void setPartColorState(QUuid partId, bool hasColor, QColor color); void setPartWrapState(QUuid partId, bool wrapped); + void setPartMetalness(QUuid partId, float metalness); + void setPartRoughness(QUuid partId, float roughness); void setComponentInverseState(QUuid componentId, bool inverse); void moveComponentUp(QUuid componentId); void moveComponentDown(QUuid componentId); diff --git a/src/skeletondocumentwindow.cpp b/src/skeletondocumentwindow.cpp index 683183c7..a41941b4 100644 --- a/src/skeletondocumentwindow.cpp +++ b/src/skeletondocumentwindow.cpp @@ -760,6 +760,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(m_document, &SkeletonDocument::partRoundStateChanged, partTreeWidget, &SkeletonPartTreeWidget::partRoundStateChanged); connect(m_document, &SkeletonDocument::partWrapStateChanged, partTreeWidget, &SkeletonPartTreeWidget::partWrapStateChanged); connect(m_document, &SkeletonDocument::partColorStateChanged, partTreeWidget, &SkeletonPartTreeWidget::partColorStateChanged); + connect(m_document, &SkeletonDocument::partMetalnessChanged, partTreeWidget, &SkeletonPartTreeWidget::partMetalnessChanged); + connect(m_document, &SkeletonDocument::partRoughnessChanged, partTreeWidget, &SkeletonPartTreeWidget::partRoughnessChanged); connect(m_document, &SkeletonDocument::partRemoved, partTreeWidget, &SkeletonPartTreeWidget::partRemoved); connect(m_document, &SkeletonDocument::cleanup, partTreeWidget, &SkeletonPartTreeWidget::removeAllContent); connect(m_document, &SkeletonDocument::partChecked, partTreeWidget, &SkeletonPartTreeWidget::partChecked); diff --git a/src/skeletonparttreewidget.cpp b/src/skeletonparttreewidget.cpp index 7b40e6d1..05977b15 100644 --- a/src/skeletonparttreewidget.cpp +++ b/src/skeletonparttreewidget.cpp @@ -932,6 +932,28 @@ void SkeletonPartTreeWidget::partColorStateChanged(QUuid partId) widget->updateColorButton(); } +void SkeletonPartTreeWidget::partMetalnessChanged(QUuid partId) +{ + auto item = m_partItemMap.find(partId); + if (item == m_partItemMap.end()) { + qDebug() << "Part item not found:" << partId; + return; + } + SkeletonPartWidget *widget = (SkeletonPartWidget *)itemWidget(item->second, 0); + widget->updateColorButton(); +} + +void SkeletonPartTreeWidget::partRoughnessChanged(QUuid partId) +{ + auto item = m_partItemMap.find(partId); + if (item == m_partItemMap.end()) { + qDebug() << "Part item not found:" << partId; + return; + } + SkeletonPartWidget *widget = (SkeletonPartWidget *)itemWidget(item->second, 0); + widget->updateColorButton(); +} + void SkeletonPartTreeWidget::partChecked(QUuid partId) { auto item = m_partItemMap.find(partId); diff --git a/src/skeletonparttreewidget.h b/src/skeletonparttreewidget.h index 1fb8c6e0..73d7736f 100644 --- a/src/skeletonparttreewidget.h +++ b/src/skeletonparttreewidget.h @@ -62,6 +62,8 @@ public slots: void partRoundStateChanged(QUuid partId); void partWrapStateChanged(QUuid partId); void partColorStateChanged(QUuid partId); + void partMetalnessChanged(QUuid partId); + void partRoughnessChanged(QUuid partId); void partChecked(QUuid partId); void partUnchecked(QUuid partId); void groupChanged(QTreeWidgetItem *item, int column); diff --git a/src/skeletonpartwidget.cpp b/src/skeletonpartwidget.cpp index c8b3f2bf..8a2f7319 100644 --- a/src/skeletonpartwidget.cpp +++ b/src/skeletonpartwidget.cpp @@ -131,6 +131,8 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p connect(this, &SkeletonPartWidget::setPartRoundState, m_document, &SkeletonDocument::setPartRoundState); connect(this, &SkeletonPartWidget::setPartWrapState, m_document, &SkeletonDocument::setPartWrapState); connect(this, &SkeletonPartWidget::setPartColorState, m_document, &SkeletonDocument::setPartColorState); + connect(this, &SkeletonPartWidget::setPartMetalness, m_document, &SkeletonDocument::setPartMetalness); + connect(this, &SkeletonPartWidget::setPartRoughness, m_document, &SkeletonDocument::setPartRoughness); connect(this, &SkeletonPartWidget::checkPart, m_document, &SkeletonDocument::checkPart); connect(this, &SkeletonPartWidget::enableBackgroundBlur, m_document, &SkeletonDocument::enableBackgroundBlur); connect(this, &SkeletonPartWidget::disableBackgroundBlur, m_document, &SkeletonDocument::disableBackgroundBlur); @@ -301,9 +303,10 @@ void SkeletonPartWidget::showColorSettingPopup(const QPoint &pos) palette.setColor(QPalette::Button, choosenColor); pickButton->setPalette(palette); - QHBoxLayout *layout = new QHBoxLayout; - layout->addWidget(colorEraser); - layout->addWidget(pickButton); + QHBoxLayout *colorLayout = new QHBoxLayout; + colorLayout->addWidget(colorEraser); + colorLayout->addWidget(pickButton); + colorLayout->addStretch(); connect(colorEraser, &QPushButton::clicked, [=]() { emit setPartColorState(m_partId, false, Theme::white); @@ -320,7 +323,55 @@ void SkeletonPartWidget::showColorSettingPopup(const QPoint &pos) } }); - popup->setLayout(layout); + FloatNumberWidget *metalnessWidget = new FloatNumberWidget; + metalnessWidget->setItemName(tr("Metalness")); + metalnessWidget->setRange(0, 1); + metalnessWidget->setValue(part->metalness); + + connect(metalnessWidget, &FloatNumberWidget::valueChanged, [=](float value) { + emit setPartMetalness(m_partId, value); + emit groupOperationAdded(); + }); + + FloatNumberWidget *roughnessWidget = new FloatNumberWidget; + roughnessWidget->setItemName(tr("Roughness")); + roughnessWidget->setRange(0, 1); + roughnessWidget->setValue(part->roughness); + + connect(roughnessWidget, &FloatNumberWidget::valueChanged, [=](float value) { + emit setPartRoughness(m_partId, value); + emit groupOperationAdded(); + }); + + QPushButton *metalnessEraser = new QPushButton(QChar(fa::eraser)); + initToolButton(metalnessEraser); + + connect(metalnessEraser, &QPushButton::clicked, [=]() { + metalnessWidget->setValue(0.0); + emit groupOperationAdded(); + }); + + QPushButton *roughnessEraser = new QPushButton(QChar(fa::eraser)); + initToolButton(roughnessEraser); + + connect(roughnessEraser, &QPushButton::clicked, [=]() { + roughnessWidget->setValue(1.0); + emit groupOperationAdded(); + }); + + QHBoxLayout *metalnessLayout = new QHBoxLayout; + QHBoxLayout *roughnessLayout = new QHBoxLayout; + metalnessLayout->addWidget(metalnessEraser); + metalnessLayout->addWidget(metalnessWidget); + roughnessLayout->addWidget(roughnessEraser); + roughnessLayout->addWidget(roughnessWidget); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(colorLayout); + mainLayout->addLayout(metalnessLayout); + mainLayout->addLayout(roughnessLayout); + + popup->setLayout(mainLayout); QWidgetAction *action = new QWidgetAction(this); action->setDefaultWidget(popup); @@ -518,7 +569,7 @@ void SkeletonPartWidget::updateColorButton() qDebug() << "Part not found:" << m_partId; return; } - if (part->hasColor) + if (part->hasColor || part->materialAdjusted()) updateButton(m_colorButton, QChar(fa::eyedropper), true); else updateButton(m_colorButton, QChar(fa::eyedropper), false); diff --git a/src/skeletonpartwidget.h b/src/skeletonpartwidget.h index 589beae6..f227c3cd 100644 --- a/src/skeletonpartwidget.h +++ b/src/skeletonpartwidget.h @@ -21,6 +21,8 @@ signals: void setPartRoundState(QUuid partId, bool rounded); void setPartColorState(QUuid partId, bool hasColor, QColor color); void setPartWrapState(QUuid partId, bool wrapped); + void setPartMetalness(QUuid partId, float metalness); + void setPartRoughness(QUuid partId, float roughness); void movePartUp(QUuid partId); void movePartDown(QUuid partId); void movePartToTop(QUuid partId); diff --git a/src/skinnedmeshcreator.cpp b/src/skinnedmeshcreator.cpp index 01424718..a38555cb 100644 --- a/src/skinnedmeshcreator.cpp +++ b/src/skinnedmeshcreator.cpp @@ -45,11 +45,11 @@ MeshLoader *SkinnedMeshCreator::createMeshFromTransform(const std::vector &triangleColors = m_resultContext->triangleColors(); + const std::vector &triangleMaterials = m_resultContext->triangleMaterials(); const std::vector &triangleUvs = m_resultContext->triangleUvs(); m_resultTextureColorImage = new QImage(TextureGenerator::m_textureWidth, TextureGenerator::m_textureHeight, QImage::Format_ARGB32); @@ -105,10 +105,10 @@ void TextureGenerator::process() path.lineTo(uv->uv[j][0] * TextureGenerator::m_textureWidth, uv->uv[j][1] * TextureGenerator::m_textureHeight); } } - QPen textureBorderPen(triangleColors[i]); + QPen textureBorderPen(triangleMaterials[i].color); textureBorderPen.setWidth(32); texturePainter.setPen(textureBorderPen); - texturePainter.setBrush(QBrush(triangleColors[i])); + texturePainter.setBrush(QBrush(triangleMaterials[i].color)); texturePainter.drawPath(path); } // round 2, real paint @@ -123,7 +123,7 @@ void TextureGenerator::process() path.lineTo(uv->uv[j][0] * TextureGenerator::m_textureWidth, uv->uv[j][1] * TextureGenerator::m_textureHeight); } } - texturePainter.fillPath(path, QBrush(triangleColors[i])); + texturePainter.fillPath(path, QBrush(triangleMaterials[i].color)); } pen.setWidth(0);