From 8d2ab924faf7fc099103802eb3c530e1a6e4fc1c Mon Sep 17 00:00:00 2001 From: BellSharions Date: Sat, 27 Sep 2025 19:35:28 +0300 Subject: [PATCH] added normal support for previews with light direction --- include/model/ModelStructs.hpp | 1 + include/ui/preview/ModelRenderWidget.h | 14 +- src/model/ModelConverter.cpp | 3 +- src/ui/preview/ModelRenderWidget.cpp | 253 ++++++++++++++++++------- 4 files changed, 205 insertions(+), 66 deletions(-) diff --git a/include/model/ModelStructs.hpp b/include/model/ModelStructs.hpp index 1ae66cf..24d1e8b 100644 --- a/include/model/ModelStructs.hpp +++ b/include/model/ModelStructs.hpp @@ -116,6 +116,7 @@ namespace WarframeExporter::Model std::vector UV1; std::vector UV2; std::vector AO; + std::vector normals; std::vector> colors; std::vector boneIndices; std::vector boneWeights; diff --git a/include/ui/preview/ModelRenderWidget.h b/include/ui/preview/ModelRenderWidget.h index 72ad6b9..4422da3 100644 --- a/include/ui/preview/ModelRenderWidget.h +++ b/include/ui/preview/ModelRenderWidget.h @@ -3,6 +3,10 @@ #include "model/ModelExporterGltf.h" #include "QtOpenGLViewer.h" +#include +#include +#include +#include class ModelRenderWidget : public QtOpenGLViewer { @@ -20,6 +24,8 @@ class ModelRenderWidget : public QtOpenGLViewer void drawScene() override; void loadModel(WarframeExporter::Model::ModelBodyInternal& modelInternal); +private slots: + void onLightDirectionChanged(); private: void loadShaders(); @@ -27,4 +33,10 @@ class ModelRenderWidget : public QtOpenGLViewer void loadUniforms(); void updateUniform(); -}; \ No newline at end of file + QSlider* m_lightXSlider; + QSlider* m_lightYSlider; + QSlider* m_lightZSlider; + QLabel* m_lightDirectionLabel; + glm::vec3 m_lightDirection; + GLint m_lightDirUniformLocation; +}; diff --git a/src/model/ModelConverter.cpp b/src/model/ModelConverter.cpp index 08d0521..f7d7577 100644 --- a/src/model/ModelConverter.cpp +++ b/src/model/ModelConverter.cpp @@ -125,6 +125,7 @@ ModelConverter::convertInternalBodyStaticOrRigged(const ModelHeaderExternal& ext newPositions[x][2] = extBody.positions[x][2] * modelScale[2]; } outBody.positions = std::move(newPositions); + outBody.normals = std::move(extBody.normals); outBody.UV1 = std::move(extBody.UV1); outBody.UV2 = std::move(extBody.UV2); outBody.boneWeights = std::move(extBody.boneWeights); @@ -241,4 +242,4 @@ ModelConverter::getModelScale(const std::vector& meshInfos, gl outScale.z = (outScale.z == 0.0f) ? 1.0f : outScale.z; // Intentionally commented out //outScale.w = (outScale.w == 0.0f) ? 1.0f : outScale.w; -} \ No newline at end of file +} diff --git a/src/ui/preview/ModelRenderWidget.cpp b/src/ui/preview/ModelRenderWidget.cpp index dce27d3..4e3e9e7 100644 --- a/src/ui/preview/ModelRenderWidget.cpp +++ b/src/ui/preview/ModelRenderWidget.cpp @@ -1,11 +1,67 @@ #include "ui/preview/ModelRenderWidget.h" ModelRenderWidget::ModelRenderWidget(QWidget *parent) - : QtOpenGLViewer(parent) +: QtOpenGLViewer(parent), m_lightDirection(0.55f, 0.55f, 0.55f) { setIs3D(true); + + QWidget* controlPanel = new QWidget(this); + QVBoxLayout* mainLayout = new QVBoxLayout(this); + QHBoxLayout* lightLayout = new QHBoxLayout(); + + m_lightDirectionLabel = new QLabel("Light Direction: (0.55, 0.55, 0.55)", this); + + m_lightXSlider = new QSlider(Qt::Horizontal, this); + m_lightYSlider = new QSlider(Qt::Horizontal, this); + m_lightZSlider = new QSlider(Qt::Horizontal, this); + + for (auto slider : {m_lightXSlider, m_lightYSlider, m_lightZSlider}) { + slider->setRange(-100, 100); + slider->setValue(100); + slider->setTickPosition(QSlider::TicksBelow); + slider->setTickInterval(50); + } + + lightLayout->addWidget(new QLabel("X:")); + lightLayout->addWidget(m_lightXSlider); + lightLayout->addWidget(new QLabel("Y:")); + lightLayout->addWidget(m_lightYSlider); + lightLayout->addWidget(new QLabel("Z:")); + lightLayout->addWidget(m_lightZSlider); + + mainLayout->addWidget(m_lightDirectionLabel); + mainLayout->addLayout(lightLayout); + mainLayout->addStretch(); + + controlPanel->setLayout(mainLayout); + + connect(m_lightXSlider, &QSlider::valueChanged, this, &ModelRenderWidget::onLightDirectionChanged); + connect(m_lightYSlider, &QSlider::valueChanged, this, &ModelRenderWidget::onLightDirectionChanged); + connect(m_lightZSlider, &QSlider::valueChanged, this, &ModelRenderWidget::onLightDirectionChanged); } +void +ModelRenderWidget::onLightDirectionChanged() +{ + m_lightDirection.x = m_lightXSlider->value() / 100.0f; + m_lightDirection.y = m_lightYSlider->value() / 100.0f; + m_lightDirection.z = m_lightZSlider->value() / 100.0f; + + float length = sqrt(m_lightDirection.x * m_lightDirection.x + + m_lightDirection.y * m_lightDirection.y + + m_lightDirection.z * m_lightDirection.z); + if (length > 0.001f) { + m_lightDirection /= length; + } + + QString labelText = QString("Light Direction: (%1, %2, %3)") + .arg(m_lightDirection.x, 0, 'f', 2) + .arg(m_lightDirection.y, 0, 'f', 2) + .arg(m_lightDirection.z, 0, 'f', 2); + m_lightDirectionLabel->setText(labelText); + + update(); +} void ModelRenderWidget::initializeGL() { @@ -19,13 +75,19 @@ void ModelRenderWidget::drawScene() { drawAxes(); - - glEnable (GL_DEPTH_TEST); + + glEnable(GL_DEPTH_TEST); glClear(GL_DEPTH_BUFFER_BIT); - + updateUniform(); glUseProgram(m_shaderProgram); + + glUniform3f(m_lightDirUniformLocation, + m_lightDirection.x, + m_lightDirection.y, + m_lightDirection.z); + glBindVertexArray(m_glVertexArray); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_glElementBufferObject); @@ -49,23 +111,9 @@ ModelRenderWidget::loadModel(WarframeExporter::Model::ModelBodyInternal& modelIn glBindVertexArray(m_glVertexArray); glBindBuffer(GL_ARRAY_BUFFER, m_glVertexBufferObject); - size_t combinedVertSize = (sizeof(float) * 3) + 3; + size_t combinedVertSize = (sizeof(float) * 3) + 3 + sizeof(uint8_t) * 4; std::vector buf(combinedVertSize * modelInternal.positions.size()); - // Check if model has no/empty vertex colors - bool fillWithWhite = false; - if (modelInternal.AO.size() == 0) - fillWithWhite = true; - else - { - size_t sum = 0; - for (const uint8_t& x : modelInternal.AO) - sum += x; - if (sum == 0) - fillWithWhite = true; - } - - // Copy Vertex position/color into buffer constexpr uint32_t all1 = 0xFFFFFFFF; for (size_t i = 0; i < modelInternal.positions.size(); i++) { @@ -74,26 +122,30 @@ ModelRenderWidget::loadModel(WarframeExporter::Model::ModelBodyInternal& modelIn memcpy(&buf[0] + bufPtr, &modelInternal.positions[i][0], sizeof(float) * 3); bufPtr += sizeof(float) * 3; - if (fillWithWhite) - memcpy(&buf[0] + bufPtr, &all1, 3); - else - { - memcpy(&buf[0] + bufPtr++, &modelInternal.AO[i], 1); - memcpy(&buf[0] + bufPtr++, &modelInternal.AO[i], 1); - memcpy(&buf[0] + bufPtr++, &modelInternal.AO[i], 1); + memcpy(&buf[0] + bufPtr, &all1, 3); + bufPtr += 3; + + if (i < modelInternal.normals.size()) { + memcpy(&buf[0] + bufPtr, &modelInternal.normals[i][0], sizeof(uint8_t) * 4); + } else { + uint8_t defaultNormal[4] = {128, 128, 255, 0}; + memcpy(&buf[0] + bufPtr, defaultNormal, sizeof(uint8_t) * 4); } } + glBufferData(GL_ARRAY_BUFFER, combinedVertSize * modelInternal.positions.size(), buf.data(), GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_glElementBufferObject); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint16_t) * modelInternal.indices.size(), modelInternal.indices.data(), GL_STATIC_DRAW); - // vertex positions glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, combinedVertSize, (void*)0); glVertexAttribPointer(1, 3, GL_UNSIGNED_BYTE, GL_TRUE, combinedVertSize, (void*)(sizeof(float) * 3)); + glVertexAttribIPointer(2, 4, GL_UNSIGNED_BYTE, combinedVertSize, (void*)(sizeof(float) * 3 + 3)); + glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); - + glEnableVertexAttribArray(2); + paintGL(); } @@ -101,54 +153,128 @@ void ModelRenderWidget::loadShaders() { const char* shaderCode = R"""(#version 420 core - in vec3 inCol; - out vec4 FragColor; + in vec3 inCol; + in vec3 Normal; + out vec4 FragColor; - void main() - { - FragColor = vec4(inCol, 1.0); - })"""; + uniform vec3 lightDir; - unsigned int shader; + void main() + { + vec3 norm = normalize(Normal); - shader = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(shader, 1, &shaderCode, NULL); - glCompileShader(shader); + float diff = max(dot(norm, lightDir), 0.2); - m_shaderProgram = glCreateProgram(); - glAttachShader(m_shaderProgram, shader); - glLinkProgram(m_shaderProgram); + vec3 result = diff * inCol; + + FragColor = vec4(result, 1.0); + })"""; - glDeleteShader(shader); - const char* vertexShaderCode = R"""(#version 420 core - layout (location = 0) in vec3 aPos; - layout (location = 1) in vec3 aCol; - - out vec3 inCol; - - layout(binding = 0) uniform MatrixBlock - { - mat4 projection; - mat4 modelview; - }; - - void main() - { - inCol = aCol; - gl_Position = projection * modelview * vec4(aPos, 1.0); - })"""; - - unsigned int vertexShader; - - vertexShader = glCreateShader(GL_VERTEX_SHADER); + layout (location = 0) in vec3 aPos; + layout (location = 1) in vec3 aCol; + layout (location = 2) in uvec4 aNormal; + + out vec3 inCol; + out vec3 Normal; + + layout(binding = 0) uniform MatrixBlock + { + mat4 projection; + mat4 modelview; + }; + + uniform vec3 lightDir; + + vec3 fixNormal(uvec4 badNormal) + { + vec3 N; + N.x = float(badNormal.x) / 255.0; + N.y = float(badNormal.y) / 255.0; + N.z = float(badNormal.z) / 255.0; + + float r0_x = N.z * 255.0; + float r0_y = N.y * 15.9375; + float r1_y = fract(r0_y); + float r2_y = r1_y * 4096.0 + r0_x; + float r1_z = floor(r0_y); + float r2_x = N.x * 4080.0 + r1_z; + + float r1_y_temp = 0.000489 * r2_x - 1.0; + float r1_z_temp = 0.000489 * r2_y - 1.0; + + r1_y_temp = min(1.0, r1_y_temp); + float r2_x_temp = max(-1.0, r1_y_temp); + r1_z_temp = min(1.0, r1_z_temp); + float r2_y_temp = max(-1.0, r1_z_temp); + + float r1_z_abs = 1.0 - abs(r2_x_temp); + float r2_z = r1_z_abs - abs(r2_y_temp); + float r1_z_final = r2_z * (-1.0); + r1_z_final = max(0.0, r1_z_final); + + bool r0_x_bool = (r2_y_temp >= 0.0); + bool r0_y_bool = (r2_z >= 0.0); + + float r3_x, r3_y; + + if (r0_x_bool) { + r3_x = r1_z_final; + } else { + r3_x = r1_z_final * (-1.0); + } + + if (r0_y_bool) { + r3_y = r1_z_final; + } else { + r3_y = r1_z_final * (-1.0); + } + + float r4_x = r3_x + r2_x_temp; + float r4_y = r3_y + r2_y_temp; + float r4_z = r2_z; + + float r1_y_length = r4_x * r4_x + r4_y * r4_y + r4_z * r4_z; + r1_y_length = sqrt(r1_y_length); + float inv = 1.0 / r1_y_length; + + float r0_x_final = r4_x * inv; + float r0_y_final = r4_y * inv; + float r0_z_final = r4_z * inv; + + vec3 result; + result.x = r0_x_final; + result.y = r0_z_final; + result.z = r0_y_final; + + return result; + } + + void main() + { + inCol = aCol; + vec3 correctNormal = fixNormal(aNormal); + Normal = correctNormal; + gl_Position = projection * modelview * vec4(aPos, 1.0); + })"""; + unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vertexShaderCode, NULL); glCompileShader(vertexShader); + unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragmentShader, 1, &shaderCode, NULL); + glCompileShader(fragmentShader); + + m_shaderProgram = glCreateProgram(); glAttachShader(m_shaderProgram, vertexShader); + glAttachShader(m_shaderProgram, fragmentShader); glLinkProgram(m_shaderProgram); + m_lightDirUniformLocation = glGetUniformLocation(m_shaderProgram, "lightDir"); + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + } void @@ -185,4 +311,3 @@ ModelRenderWidget::updateUniform() glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), projection); glBufferSubData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), modelview); } -