feat:3D multi dot

This commit is contained in:
2026-01-20 23:41:46 +08:00
parent 785b33b089
commit 523d8379b1
367 changed files with 162365 additions and 580 deletions

View File

@@ -7,6 +7,7 @@
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <algorithm>
class GlobalHelper {
public:
@@ -17,7 +18,7 @@ public:
}
static void transToMultiMatrix(const cv::Mat& raw_data, float min_threshold,
float max_range, cv::Size display_res, cv::Mat& out_image) {
float max_range, cv::Size display_res, cv::Mat& out_matrix) {
CV_Assert(raw_data.type() == CV_32F);
cv::Mat raw = raw_data.clone();
@@ -29,7 +30,8 @@ public:
cv::GaussianBlur(raw, raw, cv::Size(3, 3), 0.8, 0.8, cv::BORDER_DEFAULT);
}
cv::Mat norm_data = raw / max_range;
const float safe_range = std::max(max_range, 1e-6f);
cv::Mat norm_data = raw / safe_range;
cv::min(norm_data, 1.0f, norm_data);
cv::max(norm_data, 0.0f, norm_data);
@@ -40,10 +42,11 @@ public:
cv::GaussianBlur(smoothed, smoothed, cv::Size(31, 31), 0.0, 0.0, cv::BORDER_DEFAULT);
cv::transpose(smoothed, out_image);
smoothed *= safe_range;
out_matrix = smoothed;
}
private:
GlobalHelper() {}
};
#endif //TACTILEIPC3D_GLOBALHELPER_H
#endif //TACTILEIPC3D_GLOBALHELPER_H

View File

@@ -21,8 +21,12 @@
#include <QImage>
#include <QPainter>
#include <QFontMetrics>
#include <QPen>
#include <algorithm>
#include <limits>
#include <cstring>
#include <cmath>
#include "globalhelper.h"
// 读取文本文件内容(这里主要用来从 Qt Resource `:/shaders/...` 读取 shader 源码)
static QByteArray readFile(const QString& path) {
@@ -54,6 +58,8 @@ GLWidget::~GLWidget() {
delete m_bgProg;
delete m_panelProg;
delete m_dotsProg;
delete m_heatmapProg;
delete m_baseProg;
if (m_panelIbo)
glDeleteBuffers(1, &m_panelIbo);
@@ -74,6 +80,26 @@ GLWidget::~GLWidget() {
if (m_dotTex)
glDeleteTextures(1, &m_dotTex);
if (m_heatmapVao)
glDeleteVertexArrays(1, &m_heatmapVao);
if (m_heatmapVbo)
glDeleteBuffers(1, &m_heatmapVbo);
if (m_heatmapIbo)
glDeleteBuffers(1, &m_heatmapIbo);
if (m_baseVao)
glDeleteVertexArrays(1, &m_baseVao);
if (m_baseVbo)
glDeleteBuffers(1, &m_baseVbo);
if (m_baseIbo)
glDeleteBuffers(1, &m_baseIbo);
if (m_skirtVao)
glDeleteVertexArrays(1, &m_skirtVao);
if (m_skirtVbo)
glDeleteBuffers(1, &m_skirtVbo);
if (m_skirtIbo)
glDeleteBuffers(1, &m_skirtIbo);
if (m_heightTex)
glDeleteTextures(1, &m_heightTex);
doneCurrent();
}
@@ -82,6 +108,7 @@ void GLWidget::setPanelSize(float w, float h, float d) {
m_panelH = h;
m_panelD = d;
m_panelGeometryDirty = true;
m_heightDirty = true;
update();
}
@@ -90,6 +117,7 @@ void GLWidget::setPanelThickness(float h) {
return;
m_panelD = h;
m_panelGeometryDirty = true;
m_heightDirty = true;
update();
}
@@ -115,6 +143,7 @@ void GLWidget::setSpec(int rows, int cols, float pitch, float dotRaius) {
}
m_valuesDirty = true;
m_hasData = false;
m_heightDirty = true;
update();
}
@@ -126,6 +155,7 @@ void GLWidget::submitValues(const QVector<float> &values) {
m_latestValues = values;
m_valuesDirty = true;
m_hasData = true;
m_heightDirty = true;
}
update();
@@ -134,6 +164,7 @@ void GLWidget::submitValues(const QVector<float> &values) {
void GLWidget::setRange(int minV, int maxV) {
m_min = minV;
m_max = maxV;
m_heightDirty = true;
update();
}
@@ -230,6 +261,12 @@ void GLWidget::initializeGL() {
initPanelGeometry_();
initDotGeometry_();
initRoomGeometry_();
initBaseGeometry_();
initHeatmapGeometry_();
initSkirtGeometry_();
updateHeatmapData_();
initHeightTexture_();
updateSkirtVertices_();
m_panelGeometryDirty = false;
m_dotsGeometryDirty = false;
@@ -244,6 +281,12 @@ void GLWidget::initGeometry_() {
initPanelGeometry_();
initDotGeometry_();
initRoomGeometry_();
initBaseGeometry_();
initHeatmapGeometry_();
initSkirtGeometry_();
updateHeatmapData_();
initHeightTexture_();
updateSkirtVertices_();
}
void GLWidget::resizeGL(int w, int h) {
@@ -257,6 +300,10 @@ void GLWidget::paintGL() {
// 如果点阵规格/面板尺寸发生变化,需要在有 GL 上下文时重建几何体 buffer。
if (m_panelGeometryDirty) {
initPanelGeometry_();
initBaseGeometry_();
initHeatmapGeometry_();
initSkirtGeometry_();
m_heightDirty = true;
m_panelGeometryDirty = false;
}
if (m_dotsGeometryDirty) {
@@ -291,10 +338,19 @@ void GLWidget::paintGL() {
// 1) 更新相机/投影矩阵MVP决定如何把 3D 世界投影到屏幕
updateMatrices_();
updateRoom_();
// 2) 如果外部提交了新数据,就把 CPU 生成的 instance 数据更新到 GPU
updateInstanceBufferIfNeeded_();
if (m_useHeatmap) {
updateHeatmapData_();
uploadHeightTexture_();
updateSkirtVertices_();
renderBase_();
renderSkirt_();
renderHeatmap_();
} else {
// 2) 如果外部提交了新数据,就把 CPU 生成的 instance 数据更新到 GPU
updateInstanceBufferIfNeeded_();
}
if (m_panelProg) {
if (!m_useHeatmap && m_panelProg) {
m_panelProg->bind();
// uniforms每次 draw 前设置的一组“常量参数”(对当前 draw call 的所有顶点/片元都一致)
// uMVP: Model-View-Projection 矩阵(把顶点从世界坐标 -> 裁剪空间gl_Position 必须输出裁剪空间坐标)
@@ -315,7 +371,7 @@ void GLWidget::paintGL() {
m_panelProg->release();
}
if (m_dotsProg) {
if (!m_useHeatmap && m_dotsProg) {
m_dotsProg->bind();
// uniforms每次 draw 前设置的一组“常量参数”(对当前 draw call 的所有 instance 都一致)
// uMVP: 同上;用于把每个 dot 的世界坐标变换到屏幕
@@ -351,7 +407,7 @@ void GLWidget::paintGL() {
// m_dotsProg->release();
}
if (m_labelMode != LabelsOff && dotCount() > 0) {
if (!m_useHeatmap && m_labelMode != LabelsOff && dotCount() > 0) {
QVector<float> valuesCopy;
{
QMutexLocker lk(&m_dataMutex);
@@ -453,6 +509,9 @@ void GLWidget::paintGL() {
}
}
}
QPainter uiPainter(this);
drawToggleButton_(&uiPainter);
}
void GLWidget::mousePressEvent(QMouseEvent *event) {
@@ -463,6 +522,16 @@ void GLWidget::mousePressEvent(QMouseEvent *event) {
return;
}
if (event->button() == Qt::LeftButton) {
if (m_toggleRect.contains(event->pos())) {
m_useHeatmap = !m_useHeatmap;
update();
event->accept();
return;
}
if (m_useHeatmap) {
event->accept();
return;
}
QVector3D world;
const int index = pickDotIndex_(event->pos(), &world);
qInfo() << "clicked index: " << index;
@@ -498,7 +567,7 @@ void GLWidget::mouseMoveEvent(QMouseEvent *event) {
return;
}
if (m_labelMode != LabelsOff && dotCount() > 0) {
if (!m_useHeatmap && m_labelMode != LabelsOff && dotCount() > 0) {
auto projectToScreen = [&](const QVector3D& world, QPointF& out) -> bool {
const QVector4D clip = m_mvp * QVector4D(world, 1.0f);
if (clip.w() <= 1e-6f)
@@ -946,6 +1015,458 @@ void GLWidget::initRoomGeometry_() {
glBindVertexArray(0);
}
void GLWidget::initBaseGeometry_() {
if (m_baseIbo) {
glDeleteBuffers(1, &m_baseIbo);
m_baseIbo = 0;
}
if (m_baseVbo) {
glDeleteBuffers(1, &m_baseVbo);
m_baseVbo = 0;
}
if (m_baseVao) {
glDeleteVertexArrays(1, &m_baseVao);
m_baseVao = 0;
}
const float hw = m_panelW * 0.5f;
const float hh = m_panelH * 0.5f;
const float z = m_panelD * 0.5f;
struct V {
float x, y, z;
float nx, ny, nz;
};
V verts[4] = {
{-hw, -hh, z, 0, 0, -1},
{+hw, -hh, z, 0, 0, -1},
{+hw, +hh, z, 0, 0, -1},
{-hw, +hh, z, 0, 0, -1},
};
unsigned int idx[6] = {0, 1, 2, 0, 2, 3};
m_baseIndexCount = 6;
glGenVertexArrays(1, &m_baseVao);
glBindVertexArray(m_baseVao);
glGenBuffers(1, &m_baseVbo);
glBindBuffer(GL_ARRAY_BUFFER, m_baseVbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
glGenBuffers(1, &m_baseIbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_baseIbo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idx), idx, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(V), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(V), (void*)(3 * sizeof(float)));
glBindVertexArray(0);
}
void GLWidget::initHeatmapGeometry_() {
if (m_heatmapIbo) {
glDeleteBuffers(1, &m_heatmapIbo);
m_heatmapIbo = 0;
}
if (m_heatmapVbo) {
glDeleteBuffers(1, &m_heatmapVbo);
m_heatmapVbo = 0;
}
if (m_heatmapVao) {
glDeleteVertexArrays(1, &m_heatmapVao);
m_heatmapVao = 0;
}
const float aspect = (m_panelW > 1e-6f) ? (m_panelH / m_panelW) : 1.0f;
const int cols = qMax(2, qMin(m_cols * m_upscale, 240));
int rows = qMax(2, int(cols * aspect + 0.5f));
rows = qMax(2, qMin(rows, 240));
m_heatmapCols = cols;
m_heatmapRows = rows;
const int vertex_count = cols * rows;
QVector<float> verts;
verts.reserve(vertex_count * 5);
for (int r = 0; r < rows; ++r) {
const float v = (rows > 1) ? float(r) / float(rows - 1) : 0.0f;
const float y = (v - 0.5f) * m_panelH;
for (int c = 0; c < cols; ++c) {
const float u = (cols > 1) ? float(c) / float(cols - 1) : 0.0f;
const float x = (u - 0.5f) * m_panelW;
verts.push_back(x);
verts.push_back(y);
verts.push_back(0.0f);
verts.push_back(u);
verts.push_back(v);
}
}
QVector<unsigned int> idx;
idx.reserve((cols - 1) * (rows - 1) * 6);
for (int r = 0; r < rows - 1; ++r) {
for (int c = 0; c < cols - 1; ++c) {
const unsigned int i0 = unsigned(r * cols + c);
const unsigned int i1 = unsigned(r * cols + c + 1);
const unsigned int i2 = unsigned((r + 1) * cols + c + 1);
const unsigned int i3 = unsigned((r + 1) * cols + c);
idx.push_back(i0);
idx.push_back(i1);
idx.push_back(i2);
idx.push_back(i0);
idx.push_back(i2);
idx.push_back(i3);
}
}
m_heatmapIndexCount = int(idx.size());
glGenVertexArrays(1, &m_heatmapVao);
glBindVertexArray(m_heatmapVao);
glGenBuffers(1, &m_heatmapVbo);
glBindBuffer(GL_ARRAY_BUFFER, m_heatmapVbo);
glBufferData(GL_ARRAY_BUFFER, verts.size() * sizeof(float), verts.constData(), GL_STATIC_DRAW);
glGenBuffers(1, &m_heatmapIbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_heatmapIbo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx.size() * sizeof(unsigned int), idx.constData(), GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glBindVertexArray(0);
}
void GLWidget::initSkirtGeometry_() {
if (m_skirtIbo) {
glDeleteBuffers(1, &m_skirtIbo);
m_skirtIbo = 0;
}
if (m_skirtVbo) {
glDeleteBuffers(1, &m_skirtVbo);
m_skirtVbo = 0;
}
if (m_skirtVao) {
glDeleteVertexArrays(1, &m_skirtVao);
m_skirtVao = 0;
}
m_skirtUVs.clear();
m_skirtNormals.clear();
const int cols = qMax(2, m_heatmapCols);
const int rows = qMax(2, m_heatmapRows);
auto push_border = [&](int c, int r, const QVector3D& n) {
const float u = (cols > 1) ? float(c) / float(cols - 1) : 0.0f;
const float v = (rows > 1) ? float(r) / float(rows - 1) : 0.0f;
m_skirtUVs.push_back(QVector2D(u, v));
m_skirtNormals.push_back(n);
};
for (int c = 0; c < cols; ++c) {
push_border(c, 0, QVector3D(0.0f, -1.0f, 0.0f));
}
for (int r = 1; r < rows - 1; ++r) {
push_border(cols - 1, r, QVector3D(1.0f, 0.0f, 0.0f));
}
for (int c = cols - 1; c >= 0; --c) {
push_border(c, rows - 1, QVector3D(0.0f, 1.0f, 0.0f));
}
for (int r = rows - 2; r >= 1; --r) {
push_border(0, r, QVector3D(-1.0f, 0.0f, 0.0f));
}
const int border_count = m_skirtUVs.size();
m_skirtVertices.fill(0.0f, border_count * 2 * 6);
QVector<unsigned int> idx;
idx.reserve(border_count * 6);
for (int i = 0; i < border_count; ++i) {
const int next = (i + 1) % border_count;
const unsigned int top0 = unsigned(i * 2);
const unsigned int bot0 = unsigned(i * 2 + 1);
const unsigned int top1 = unsigned(next * 2);
const unsigned int bot1 = unsigned(next * 2 + 1);
idx.push_back(top0);
idx.push_back(top1);
idx.push_back(bot1);
idx.push_back(top0);
idx.push_back(bot1);
idx.push_back(bot0);
}
m_skirtIndexCount = int(idx.size());
glGenVertexArrays(1, &m_skirtVao);
glBindVertexArray(m_skirtVao);
glGenBuffers(1, &m_skirtVbo);
glBindBuffer(GL_ARRAY_BUFFER, m_skirtVbo);
glBufferData(GL_ARRAY_BUFFER, m_skirtVertices.size() * sizeof(float), m_skirtVertices.constData(), GL_DYNAMIC_DRAW);
glGenBuffers(1, &m_skirtIbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_skirtIbo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx.size() * sizeof(unsigned int), idx.constData(), GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glBindVertexArray(0);
}
void GLWidget::initHeightTexture_() {
if (m_heightW <= 0 || m_heightH <= 0 || m_heightValues.isEmpty()) {
return;
}
if (m_heightTex) {
glDeleteTextures(1, &m_heightTex);
m_heightTex = 0;
}
glGenTextures(1, &m_heightTex);
glBindTexture(GL_TEXTURE_2D, m_heightTex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, m_heightW, m_heightH, 0, GL_RED, GL_FLOAT, m_heightValues.constData());
m_heightTexW = m_heightW;
m_heightTexH = m_heightH;
glBindTexture(GL_TEXTURE_2D, 0);
}
float GLWidget::sampleHeightValue_(float u, float v) const {
if (m_heightW <= 0 || m_heightH <= 0 || m_heightValues.isEmpty()) {
return float(m_min);
}
const float x = qBound(0.0f, u, 1.0f) * float(m_heightW - 1);
const float y = qBound(0.0f, v, 1.0f) * float(m_heightH - 1);
const int x0 = qBound(0, int(std::floor(x)), m_heightW - 1);
const int y0 = qBound(0, int(std::floor(y)), m_heightH - 1);
const int x1 = qMin(x0 + 1, m_heightW - 1);
const int y1 = qMin(y0 + 1, m_heightH - 1);
const float fx = x - float(x0);
const float fy = y - float(y0);
const float v00 = m_heightValues[y0 * m_heightW + x0];
const float v10 = m_heightValues[y0 * m_heightW + x1];
const float v01 = m_heightValues[y1 * m_heightW + x0];
const float v11 = m_heightValues[y1 * m_heightW + x1];
const float vx0 = v00 + (v10 - v00) * fx;
const float vx1 = v01 + (v11 - v01) * fx;
return vx0 + (vx1 - vx0) * fy;
}
void GLWidget::updateHeatmapData_() {
if (!m_heightDirty && !m_heightValues.isEmpty()) {
return;
}
if (m_rows <= 0 || m_cols <= 0) {
return;
}
QVector<float> valuesCopy;
{
QMutexLocker lk(&m_dataMutex);
valuesCopy = m_latestValues;
}
cv::Mat raw(m_rows, m_cols, CV_32F);
for (int r = 0; r < m_rows; ++r) {
for (int c = 0; c < m_cols; ++c) {
const int index = r * m_cols + c;
const float v = (index < valuesCopy.size()) ? valuesCopy[index] : float(m_min);
raw.at<float>(r, c) = v;
}
}
const int outW = qMax(1, m_cols * m_upscale);
const int outH = qMax(1, m_rows * m_upscale);
cv::Mat out;
GlobalHelper::transToMultiMatrix(raw, float(m_min), float(m_max), cv::Size(outW, outH), out);
if (out.empty()) {
return;
}
if (out.type() != CV_32F) {
out.convertTo(out, CV_32F);
}
const int nextW = out.cols;
const int nextH = out.rows;
if (nextW <= 0 || nextH <= 0) {
return;
}
m_heightW = nextW;
m_heightH = nextH;
m_heightValues.resize(m_heightW * m_heightH);
if (out.isContinuous()) {
const size_t count = size_t(m_heightW) * size_t(m_heightH);
std::memcpy(m_heightValues.data(), out.ptr<float>(0), count * sizeof(float));
} else {
for (int r = 0; r < m_heightH; ++r) {
const float* rowPtr = out.ptr<float>(r);
std::memcpy(m_heightValues.data() + r * m_heightW, rowPtr, m_heightW * sizeof(float));
}
}
m_heightDirty = false;
}
void GLWidget::uploadHeightTexture_() {
if (m_heightValues.isEmpty() || m_heightW <= 0 || m_heightH <= 0) {
return;
}
if (!m_heightTex || m_heightTexW != m_heightW || m_heightTexH != m_heightH) {
initHeightTexture_();
}
if (!m_heightTex) {
return;
}
glBindTexture(GL_TEXTURE_2D, m_heightTex);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_heightW, m_heightH, GL_RED, GL_FLOAT, m_heightValues.constData());
glBindTexture(GL_TEXTURE_2D, 0);
}
void GLWidget::updateSkirtVertices_() {
if (m_skirtVertices.isEmpty() || m_skirtUVs.isEmpty()) {
return;
}
const float baseZ = -m_panelD * 0.5f;
const float backZ = baseZ + m_panelD;
const float minV = float(m_min);
const float maxV = float(m_max);
const float invRange = 1.0f / qMax(1e-6f, (maxV - minV));
const float dentMax = qMin(m_dentMax, m_panelD);
const int border_count = m_skirtUVs.size();
for (int i = 0; i < border_count; ++i) {
const float u = m_skirtUVs[i].x();
const float v = m_skirtUVs[i].y();
const float value = sampleHeightValue_(u, v);
const float t = qBound(0.0f, (value - minV) * invRange, 1.0f);
const float h = t * dentMax;
const float x = (u - 0.5f) * m_panelW;
const float y = (v - 0.5f) * m_panelH;
const QVector3D n = m_skirtNormals[i];
const int topIndex = i * 2;
const int botIndex = i * 2 + 1;
const int topBase = topIndex * 6;
m_skirtVertices[topBase + 0] = x;
m_skirtVertices[topBase + 1] = y;
m_skirtVertices[topBase + 2] = baseZ + h;
m_skirtVertices[topBase + 3] = n.x();
m_skirtVertices[topBase + 4] = n.y();
m_skirtVertices[topBase + 5] = n.z();
const int botBase = botIndex * 6;
m_skirtVertices[botBase + 0] = x;
m_skirtVertices[botBase + 1] = y;
m_skirtVertices[botBase + 2] = backZ;
m_skirtVertices[botBase + 3] = n.x();
m_skirtVertices[botBase + 4] = n.y();
m_skirtVertices[botBase + 5] = n.z();
}
glBindBuffer(GL_ARRAY_BUFFER, m_skirtVbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, m_skirtVertices.size() * sizeof(float), m_skirtVertices.constData());
}
void GLWidget::renderBase_() {
if (!m_baseProg || !m_baseVao) {
return;
}
m_baseProg->bind();
m_baseProg->setUniformValue("uMVP", m_mvp);
m_baseProg->setUniformValue("uCameraPos", m_cameraPos);
m_baseProg->setUniformValue("uLightDir", QVector3D(-0.25f, 0.35f, -1.0f).normalized());
m_baseProg->setUniformValue("uColor", m_colorLow);
glBindVertexArray(m_baseVao);
glDrawElements(GL_TRIANGLES, m_baseIndexCount, GL_UNSIGNED_INT, nullptr);
glBindVertexArray(0);
m_baseProg->release();
}
void GLWidget::renderSkirt_() {
if (!m_baseProg || !m_skirtVao || m_skirtIndexCount <= 0) {
return;
}
m_baseProg->bind();
m_baseProg->setUniformValue("uMVP", m_mvp);
m_baseProg->setUniformValue("uCameraPos", m_cameraPos);
m_baseProg->setUniformValue("uLightDir", QVector3D(-0.25f, 0.35f, -1.0f).normalized());
m_baseProg->setUniformValue("uColor", m_colorLow);
glBindVertexArray(m_skirtVao);
glDrawElements(GL_TRIANGLES, m_skirtIndexCount, GL_UNSIGNED_INT, nullptr);
glBindVertexArray(0);
m_baseProg->release();
}
void GLWidget::renderHeatmap_() {
if (!m_heatmapProg || !m_heatmapVao || m_heatmapIndexCount <= 0 || !m_heightTex) {
return;
}
m_heatmapProg->bind();
m_heatmapProg->setUniformValue("uMVP", m_mvp);
m_heatmapProg->setUniformValue("uMinV", float(m_min));
m_heatmapProg->setUniformValue("uMaxV", float(m_max));
m_heatmapProg->setUniformValue("uHeightScale", qMin(m_dentMax, m_panelD));
m_heatmapProg->setUniformValue("uBaseZ", -m_panelD * 0.5f);
m_heatmapProg->setUniformValue("uTexelSize", QVector2D(1.0f / float(m_heightW), 1.0f / float(m_heightH)));
m_heatmapProg->setUniformValue("uPlaneSize", QVector2D(m_panelW, m_panelH));
m_heatmapProg->setUniformValue("uCameraPos", m_cameraPos);
m_heatmapProg->setUniformValue("uLightDir", QVector3D(-0.25f, 0.35f, -1.0f).normalized());
m_heatmapProg->setUniformValue("uColorLow", m_colorLow);
m_heatmapProg->setUniformValue("uColorMid", m_colorMid);
m_heatmapProg->setUniformValue("uColorHigh", m_colorHigh);
m_heatmapProg->setUniformValue("uHeightTex", 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_heightTex);
glBindVertexArray(m_heatmapVao);
glDrawElements(GL_TRIANGLES, m_heatmapIndexCount, GL_UNSIGNED_INT, nullptr);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
m_heatmapProg->release();
}
void GLWidget::drawToggleButton_(QPainter* painter) {
if (!painter) {
return;
}
const QRectF rect(12.0, 12.0, 160.0, 30.0);
m_toggleRect = rect;
painter->save();
painter->setRenderHint(QPainter::Antialiasing, true);
QColor bg = m_useHeatmap ? QColor(0, 150, 136, 220) : QColor(45, 45, 50, 220);
QColor border(255, 255, 255, 80);
painter->setPen(QPen(border, 1.0));
painter->setBrush(bg);
painter->drawRoundedRect(rect, 6.0, 6.0);
painter->setPen(QColor(255, 255, 255, 235));
painter->drawText(rect, Qt::AlignCenter, m_useHeatmap ? QStringLiteral("Heatmap") : QStringLiteral("Dots"));
painter->restore();
}
void GLWidget::initPrograms_() {
// Qt Resource 里打包的 shader 文件路径:使用 `:/` 前缀(不是文件系统路径)
const QString vsd_path = QStringLiteral(":/shaders/dots.vert");
@@ -956,6 +1477,10 @@ void GLWidget::initPrograms_() {
const QString fsp_path = QStringLiteral(":/shaders/panel.frag");
const QString vsr_path = QStringLiteral(":/shaders/room.vert");
const QString fsr_path = QStringLiteral(":/shaders/room.frag");
const QString vsh_path = QStringLiteral(":/shaders/heatmap.vert");
const QString fsh_path = QStringLiteral(":/shaders/heatmap.frag");
const QString vsbase_path = QStringLiteral(":/shaders/base.vert");
const QString fsbase_path = QStringLiteral(":/shaders/base.frag");
{
auto *p = new QOpenGLShaderProgram;
@@ -1005,6 +1530,30 @@ void GLWidget::initPrograms_() {
}
m_roomProg = p;
}
{
auto *p = new QOpenGLShaderProgram;
const bool okV = p->addShaderFromSourceCode(QOpenGLShader::Vertex, readFile(vsh_path));
const bool okF = p->addShaderFromSourceCode(QOpenGLShader::Fragment, readFile(fsh_path));
const bool okL = okV && okF && p->link();
if (!okL) {
qWarning() << "heatmap program build failed:" << vsh_path << fsh_path << p->log();
delete p;
p = nullptr;
}
m_heatmapProg = p;
}
{
auto *p = new QOpenGLShaderProgram;
const bool okV = p->addShaderFromSourceCode(QOpenGLShader::Vertex, readFile(vsbase_path));
const bool okF = p->addShaderFromSourceCode(QOpenGLShader::Fragment, readFile(fsbase_path));
const bool okL = okV && okF && p->link();
if (!okL) {
qWarning() << "base program build failed:" << vsbase_path << fsbase_path << p->log();
delete p;
p = nullptr;
}
m_baseProg = p;
}
}
void GLWidget::updateInstanceBufferIfNeeded_() {

View File

@@ -15,9 +15,11 @@
#include <QVector3D>
#include <QString>
#include <QColor>
#include <QRectF>
#include <atomic>
#include <qtmetamacros.h>
#include <qvectornd.h>
#include <QVector2D>
struct Ray {
QVector3D origin;
@@ -101,11 +103,23 @@ private:
void initPrograms_();
void initDotTexture_();
void initRoomGeometry_();
void initHeatmapGeometry_();
void initBaseGeometry_();
void initSkirtGeometry_();
void initHeightTexture_();
void updateInstanceBufferIfNeeded_();
void updateMatrices_();
void updateRoom_();
void updateHeatmapData_();
void uploadHeightTexture_();
void updateSkirtVertices_();
bool projectToScreen_(const QVector3D& world, QPointF* out) const;
int pickDotIndex_(const QPoint& pos, QVector3D* worldOut) const;
float sampleHeightValue_(float u, float v) const;
void renderHeatmap_();
void renderBase_();
void renderSkirt_();
void drawToggleButton_(QPainter* painter);
private:
// 传感值范围(用于颜色映射)
int m_min = 0;
@@ -136,6 +150,8 @@ private:
QOpenGLShaderProgram* m_roomProg = nullptr;
QOpenGLShaderProgram* m_panelProg = nullptr;
QOpenGLShaderProgram* m_dotsProg = nullptr;
QOpenGLShaderProgram* m_heatmapProg = nullptr;
QOpenGLShaderProgram* m_baseProg = nullptr;
unsigned int m_dotTex = 0;
@@ -150,6 +166,39 @@ private:
unsigned int m_dotsVao = 0;
unsigned int m_dotsVbo = 0;
unsigned int m_instanceVbo = 0;
unsigned int m_heatmapVao = 0;
unsigned int m_heatmapVbo = 0;
unsigned int m_heatmapIbo = 0;
int m_heatmapIndexCount = 0;
int m_heatmapCols = 0;
int m_heatmapRows = 0;
unsigned int m_baseVao = 0;
unsigned int m_baseVbo = 0;
unsigned int m_baseIbo = 0;
int m_baseIndexCount = 0;
unsigned int m_skirtVao = 0;
unsigned int m_skirtVbo = 0;
unsigned int m_skirtIbo = 0;
int m_skirtIndexCount = 0;
QVector<QVector2D> m_skirtUVs;
QVector<QVector3D> m_skirtNormals;
QVector<float> m_skirtVertices;
unsigned int m_heightTex = 0;
int m_heightW = 0;
int m_heightH = 0;
int m_heightTexW = 0;
int m_heightTexH = 0;
QVector<float> m_heightValues;
bool m_heightDirty = false;
bool m_useHeatmap = false;
QRectF m_toggleRect;
int m_upscale = 100;
float m_dentMax = 0.08f;
unsigned int room_vao = 0;
unsigned int room_vbo = 0;