Files
tactileipc3d/test/onlygl/main.cpp
2026-01-20 23:41:46 +08:00

1306 lines
43 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// GLFW 版本的渲染入口:画触觉面板盒子 + 点阵实例化圆点,
// 右键拖拽环绕相机,滚轮调 FOV内置简单波纹数据做演示。
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <algorithm>
#include <cmath>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <iostream>
#include <vector>
#include "myshader.hh"
#include "stb_image_wrap.h"
struct AppState {
// 点阵规格
int rows = 8;
int cols = 10;
float pitch = 0.025f;
float dotRadius = 0.008f;
// 面板尺寸
float panelW = 0.25f;
float panelH = 0.35f;
float panelD = 0.01f;
// 数据范围
int minV = 0;
int maxV = 500;
// 渲染模式0=有光照1=无光照flat/unlit
int renderMode = 1;
// 相机状态(环绕)
float camYaw = -90.0f;
float camPitch = 0.0f;
float zoom = 45.0f;
bool rightDown = false;
double lastMouseX = 0.0;
double lastMouseY = 0.0;
bool valuesDirty = true;
std::vector<float> values;
glm::mat4 mvp{1.0f};
glm::vec3 cameraPos{0.0f, 0.0f, 3.0f};
};
AppState g_state;
Shader* bg_shader = nullptr;
Shader* base_shader = nullptr;
Shader* room_shader = nullptr;
Shader* panel_shader = nullptr;
Shader* dots_shader = nullptr;
Shader* heatmap_shader = nullptr;
unsigned int panel_vao = 0;
unsigned int panel_vbo = 0;
unsigned int panel_ibo = 0;
int panel_index_count = 0;
unsigned int room_vao = 0;
unsigned int room_vbo = 0;
unsigned int room_ibo = 0;
int room_index_count = 0;
unsigned int dots_vao = 0;
unsigned int dots_vbo = 0;
unsigned int instance_vbo = 0;
int instance_count = 0;
unsigned int dot_tex = 0;
unsigned int bg_vao = 0;
unsigned int bg_vbo = 0;
unsigned int metal_tex = 0;
bool panel_geometry_dirty = true;
bool dot_geometry_dirty = true;
unsigned int base_vao = 0;
unsigned int base_vbo = 0;
unsigned int base_ibo = 0;
int base_index_count = 0;
unsigned int skirt_vao = 0;
unsigned int skirt_vbo = 0;
unsigned int skirt_ibo = 0;
int skirt_index_count = 0;
unsigned int heatmap_vao = 0;
unsigned int heatmap_vbo = 0;
unsigned int heatmap_ibo = 0;
int heatmap_index_count = 0;
unsigned int height_tex = 0;
constexpr int kLowRows = 7;
constexpr int kLowCols = 12;
constexpr int kHeightW = 1200;
constexpr int kHeightH = 700;
constexpr int kMeshCols = 240;
constexpr int kMeshRows = 140;
constexpr float kPlaneW = 1.2f;
constexpr float kPlaneH = 0.7f;
constexpr float kThickness = 0.12f;
constexpr float kDentMax = 0.08f;
std::vector<float> low_values;
std::vector<float> height_values;
bool height_dirty = true;
std::vector<glm::vec2> skirt_uvs;
std::vector<glm::vec3> skirt_normals;
std::vector<float> skirt_vertices;
void framebuffer_size_callback(GLFWwindow* /*window*/, int width, int height) {
// 视口随窗口大小变化
glViewport(0, 0, width, height);
}
void mouse_button_callback(GLFWwindow* window, int button, int action,
int /*mods*/) {
// 右键按下/抬起,用于开启/关闭环绕相机拖拽
if (button == GLFW_MOUSE_BUTTON_RIGHT) {
if (action == GLFW_PRESS) {
g_state.rightDown = true;
glfwGetCursorPos(window, &g_state.lastMouseX, &g_state.lastMouseY);
} else if (action == GLFW_RELEASE) {
g_state.rightDown = false;
}
}
}
void cursor_pos_callback(GLFWwindow* /*window*/, double xpos, double ypos) {
// 右键拖拽时,根据鼠标增量更新 yaw/pitch
if (!g_state.rightDown) return;
const float dx = static_cast<float>(xpos - g_state.lastMouseX);
const float dy = static_cast<float>(ypos - g_state.lastMouseY);
g_state.lastMouseX = xpos;
g_state.lastMouseY = ypos;
g_state.camYaw += dx * 0.3f;
/* if (g_state.camYaw >= -70) {
g_state.camYaw = -70;
}
if (g_state.camYaw <= -110) {
g_state.camYaw = -110;
} */
g_state.camPitch = std::clamp(g_state.camPitch + dy * 0.3f, -89.0f, 89.0f);
}
void scroll_callback(GLFWwindow* /*window*/, double /*xoffset*/,
double yoffset) {
// 滚轮调整视角 FOV越小越近
const float factor = std::pow(0.9f, static_cast<float>(yoffset));
g_state.zoom = std::clamp(g_state.zoom * factor, 5.0f, 80.0f);
}
void process_input(GLFWwindow* window) {
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
glfwSetWindowShouldClose(window, true);
}
}
GLFWwindow* glfw_init() {
// 初始化 GLFW + GLAD创建窗口与上下文
if (!glfwInit()) {
std::cerr << "Failed to init GLFW" << std::endl;
return nullptr;
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#if __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
GLFWwindow* window = glfwCreateWindow(800, 600, "Tactile Module Test (GLFW)",
nullptr, nullptr);
if (window == nullptr) {
std::cerr << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return nullptr;
}
glfwMakeContextCurrent(window);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cerr << "Failed to initialize GLAD" << std::endl;
glfwDestroyWindow(window);
glfwTerminate();
return nullptr;
}
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
glfwSetMouseButtonCallback(window, mouse_button_callback);
glfwSetCursorPosCallback(window, cursor_pos_callback);
glfwSetScrollCallback(window, scroll_callback);
glViewport(0, 0, 800, 600);
glfwSwapInterval(1);
return window;
}
int dot_count() {
// 当前点阵总数
return g_state.rows * g_state.cols;
}
void set_panel_size(float w, float h, float d) {
g_state.panelW = w;
g_state.panelH = h;
g_state.panelD = d;
panel_geometry_dirty = true;
}
void set_spec(int rows, int cols, float pitch, float dotRadius) {
// 设置点阵规格,同时自动计算面板宽/深以留出圆点边界
g_state.rows = std::max(0, rows);
g_state.cols = std::max(0, cols);
g_state.pitch = std::max(0.0f, pitch);
g_state.dotRadius = std::max(0.0f, dotRadius);
const float gridW =
static_cast<float>(std::max(0, g_state.cols - 1)) * g_state.pitch;
const float gridD =
static_cast<float>(std::max(0, g_state.rows - 1)) * g_state.pitch;
g_state.panelW = gridW + 2.0f * g_state.dotRadius;
g_state.panelH = gridD + 2.0f * g_state.dotRadius;
panel_geometry_dirty = true;
dot_geometry_dirty = true;
g_state.values.assign(dot_count(), static_cast<float>(g_state.minV));
g_state.valuesDirty = true;
}
void init_background_geometry() {
// 背景网格:全屏二三角形,顶点坐标直接在 NDC 空间
if (bg_vbo) {
glDeleteBuffers(1, &bg_vbo);
bg_vbo = 0;
}
if (bg_vao) {
glDeleteVertexArrays(1, &bg_vao);
bg_vao = 0;
}
const float verts[] = {
-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f,
};
glGenVertexArrays(1, &bg_vao);
glBindVertexArray(bg_vao);
glGenBuffers(1, &bg_vbo);
glBindBuffer(GL_ARRAY_BUFFER, bg_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glBindVertexArray(0);
}
void init_room_geometry() {
// 房间:一个“倒扣的大盒子”(立方体),我们从里面看,所以法线朝向盒子内部
if (room_ibo) {
glDeleteBuffers(1, &room_ibo);
room_ibo = 0;
}
if (room_vbo) {
glDeleteBuffers(1, &room_vbo);
room_vbo = 0;
}
if (room_vao) {
glDeleteVertexArrays(1, &room_vao);
room_vao = 0;
}
struct V {
float x, y, z;
float nx, ny, nz;
};
// 单位立方体:顶点范围 [-1, 1],真正房间大小在 room.vert 里通过 uRoomHalfSize
// 缩放 下面的法线是“朝内”的(方便在房间内部打光)
V verts[24] = {
// floor (y = -1), normal +Y
{-1, -1, -1, 0, +1, 0},
{+1, -1, -1, 0, +1, 0},
{+1, -1, +1, 0, +1, 0},
{-1, -1, +1, 0, +1, 0},
// ceiling (y = +1), normal -Y
{-1, +1, +1, 0, -1, 0},
{+1, +1, +1, 0, -1, 0},
{+1, +1, -1, 0, -1, 0},
{-1, +1, -1, 0, -1, 0},
// back wall (z = -1), normal +Z
{-1, +1, -1, 0, 0, +1},
{+1, +1, -1, 0, 0, +1},
{+1, -1, -1, 0, 0, +1},
{-1, -1, -1, 0, 0, +1},
// front wall (z = +1), normal -Z
{+1, +1, +1, 0, 0, -1},
{-1, +1, +1, 0, 0, -1},
{-1, -1, +1, 0, 0, -1},
{+1, -1, +1, 0, 0, -1},
// left wall (x = -1), normal +X
{-1, +1, +1, +1, 0, 0},
{-1, +1, -1, +1, 0, 0},
{-1, -1, -1, +1, 0, 0},
{-1, -1, +1, +1, 0, 0},
// right wall (x = +1), normal -X
{+1, +1, -1, -1, 0, 0},
{+1, +1, +1, -1, 0, 0},
{+1, -1, +1, -1, 0, 0},
{+1, -1, -1, -1, 0, 0},
};
unsigned int idx[36] = {
0, 1, 2, 0, 2, 3, // floor
4, 5, 6, 4, 6, 7, // ceiling
8, 9, 10, 8, 10, 11, // back
12, 13, 14, 12, 14, 15, // front
16, 17, 18, 16, 18, 19, // left
20, 21, 22, 20, 22, 23 // right
};
room_index_count = 36;
glGenVertexArrays(1, &room_vao);
glBindVertexArray(room_vao);
glGenBuffers(1, &room_vbo);
glBindBuffer(GL_ARRAY_BUFFER, room_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
glGenBuffers(1, &room_ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, room_ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idx), idx, GL_STATIC_DRAW);
// layout 和 panel 一样:
// location 0: position
// location 1: normal
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 init_panel_geometry() {
// 面板盒子VAO+VBO+IBO包含顶面/底面/侧面 6 个面
if (panel_ibo) {
glDeleteBuffers(1, &panel_ibo);
panel_ibo = 0;
}
if (panel_vbo) {
glDeleteBuffers(1, &panel_vbo);
panel_vbo = 0;
}
if (panel_vao) {
glDeleteVertexArrays(1, &panel_vao);
panel_vao = 0;
}
const float y = g_state.panelH * 0.5f;
const float hw = g_state.panelW * 0.5f;
const float hd = g_state.panelD * 0.5f;
struct V {
float x, y, z;
float nx, ny, nz;
};
V verts[24] = {
{-hw, +y, -hd, 0, +1, 0}, {+hw, +y, -hd, 0, +1, 0},
{+hw, +y, +hd, 0, +1, 0}, {-hw, +y, +hd, 0, +1, 0},
{-hw, +y, +hd, 0, 0, +1}, {+hw, +y, +hd, 0, 0, +1},
{+hw, -y, +hd, 0, 0, +1}, {-hw, -y, +hd, 0, 0, +1},
{-hw, -y, +hd, 0, -1, 0}, {+hw, -y, +hd, 0, -1, 0},
{+hw, -y, -hd, 0, -1, 0}, {-hw, -y, -hd, 0, -1, 0},
{+hw, +y, -hd, 0, 0, -1}, {-hw, +y, -hd, 0, 0, -1},
{-hw, -y, -hd, 0, 0, -1}, {+hw, -y, -hd, 0, 0, -1},
{-hw, +y, -hd, -1, 0, 0}, {-hw, +y, +hd, -1, 0, 0},
{-hw, -y, +hd, -1, 0, 0}, {-hw, -y, -hd, -1, 0, 0},
{+hw, +y, +hd, +1, 0, 0}, {+hw, +y, -hd, +1, 0, 0},
{+hw, -y, -hd, +1, 0, 0}, {+hw, -y, +hd, +1, 0, 0},
};
unsigned int idx[36] = {0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7,
8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15,
16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23};
panel_index_count = 36;
glGenVertexArrays(1, &panel_vao);
glBindVertexArray(panel_vao);
glGenBuffers(1, &panel_vbo);
glBindBuffer(GL_ARRAY_BUFFER, panel_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
glGenBuffers(1, &panel_ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, panel_ibo);
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 init_dot_geometry() {
// 圆点:基于一个单位 quad使用 instanced attributes 传位置/值
if (instance_vbo) {
glDeleteBuffers(1, &instance_vbo);
instance_vbo = 0;
}
if (dots_vbo) {
glDeleteBuffers(1, &dots_vbo);
dots_vbo = 0;
}
if (dots_vao) {
glDeleteVertexArrays(1, &dots_vao);
dots_vao = 0;
}
struct V {
float x, y;
float u, v;
};
V quad[6] = {
{-1, -1, 0, 0}, {1, -1, 1, 0}, {1, 1, 1, 1},
{-1, -1, 0, 0}, {1, 1, 1, 1}, {-1, 1, 0, 1},
};
glGenVertexArrays(1, &dots_vao);
glBindVertexArray(dots_vao);
glGenBuffers(1, &dots_vbo);
glBindBuffer(GL_ARRAY_BUFFER, dots_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(V), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(V),
(void*)(2 * sizeof(float)));
glGenBuffers(1, &instance_vbo);
glBindBuffer(GL_ARRAY_BUFFER, instance_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 3 * std::max(1, dot_count()),
nullptr, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glVertexAttribDivisor(2, 1);
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, 3 * sizeof(float),
(void*)(2 * sizeof(float)));
glVertexAttribDivisor(3, 1);
glBindVertexArray(0);
}
float lerp_float(float a, float b, float t) { return a + (b - a) * t; }
float gaussian2d(float dx, float dy, float sigma) {
const float s = std::max(1e-6f, sigma);
const float inv = 1.0f / (2.0f * s * s);
return std::exp(-(dx * dx + dy * dy) * inv);
}
float sample_height_value(float u, float v) {
if (height_values.empty()) {
return static_cast<float>(g_state.minV);
}
const float x = std::clamp(u, 0.0f, 1.0f) * static_cast<float>(kHeightW - 1);
const float y = std::clamp(v, 0.0f, 1.0f) * static_cast<float>(kHeightH - 1);
const int x0 = std::clamp(static_cast<int>(std::floor(x)), 0, kHeightW - 1);
const int y0 = std::clamp(static_cast<int>(std::floor(y)), 0, kHeightH - 1);
const int x1 = std::min(x0 + 1, kHeightW - 1);
const int y1 = std::min(y0 + 1, kHeightH - 1);
const float fx = x - static_cast<float>(x0);
const float fy = y - static_cast<float>(y0);
const float v00 = height_values[static_cast<size_t>(y0) * static_cast<size_t>(kHeightW) + static_cast<size_t>(x0)];
const float v10 = height_values[static_cast<size_t>(y0) * static_cast<size_t>(kHeightW) + static_cast<size_t>(x1)];
const float v01 = height_values[static_cast<size_t>(y1) * static_cast<size_t>(kHeightW) + static_cast<size_t>(x0)];
const float v11 = height_values[static_cast<size_t>(y1) * static_cast<size_t>(kHeightW) + static_cast<size_t>(x1)];
const float vx0 = lerp_float(v00, v10, fx);
const float vx1 = lerp_float(v01, v11, fx);
return lerp_float(vx0, vx1, fy);
}
void init_base_geometry() {
if (base_ibo) {
glDeleteBuffers(1, &base_ibo);
base_ibo = 0;
}
if (base_vbo) {
glDeleteBuffers(1, &base_vbo);
base_vbo = 0;
}
if (base_vao) {
glDeleteVertexArrays(1, &base_vao);
base_vao = 0;
}
const float hw = kPlaneW * 0.5f;
const float hh = kPlaneH * 0.5f;
const float hz = kThickness * 0.5f;
struct V {
float x, y, z;
float nx, ny, nz;
};
V verts[24] = {
{-hw, -hh, +hz, 0, 0, +1}, {+hw, -hh, +hz, 0, 0, +1},
{+hw, +hh, +hz, 0, 0, +1}, {-hw, +hh, +hz, 0, 0, +1},
{+hw, -hh, -hz, 0, 0, -1}, {-hw, -hh, -hz, 0, 0, -1},
{-hw, +hh, -hz, 0, 0, -1}, {+hw, +hh, -hz, 0, 0, -1},
{-hw, -hh, -hz, -1, 0, 0}, {-hw, -hh, +hz, -1, 0, 0},
{-hw, +hh, +hz, -1, 0, 0}, {-hw, +hh, -hz, -1, 0, 0},
{+hw, -hh, +hz, +1, 0, 0}, {+hw, -hh, -hz, +1, 0, 0},
{+hw, +hh, -hz, +1, 0, 0}, {+hw, +hh, +hz, +1, 0, 0},
{-hw, +hh, +hz, 0, +1, 0}, {+hw, +hh, +hz, 0, +1, 0},
{+hw, +hh, -hz, 0, +1, 0}, {-hw, +hh, -hz, 0, +1, 0},
{-hw, -hh, -hz, 0, -1, 0}, {+hw, -hh, -hz, 0, -1, 0},
{+hw, -hh, +hz, 0, -1, 0}, {-hw, -hh, +hz, 0, -1, 0},
};
unsigned int idx[6] = {0, 1, 2, 0, 2, 3};
base_index_count = 6;
glGenVertexArrays(1, &base_vao);
glBindVertexArray(base_vao);
glGenBuffers(1, &base_vbo);
glBindBuffer(GL_ARRAY_BUFFER, base_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
glGenBuffers(1, &base_ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, base_ibo);
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 init_skirt_geometry() {
if (skirt_ibo) {
glDeleteBuffers(1, &skirt_ibo);
skirt_ibo = 0;
}
if (skirt_vbo) {
glDeleteBuffers(1, &skirt_vbo);
skirt_vbo = 0;
}
if (skirt_vao) {
glDeleteVertexArrays(1, &skirt_vao);
skirt_vao = 0;
}
skirt_uvs.clear();
skirt_normals.clear();
const int cols = std::max(2, kMeshCols);
const int rows = std::max(2, kMeshRows);
auto push_border = [&](int c, int r, const glm::vec3& n) {
const float u = (cols > 1) ? static_cast<float>(c) / static_cast<float>(cols - 1) : 0.0f;
const float v = (rows > 1) ? static_cast<float>(r) / static_cast<float>(rows - 1) : 0.0f;
skirt_uvs.emplace_back(u, v);
skirt_normals.emplace_back(n);
};
for (int c = 0; c < cols; ++c) {
push_border(c, 0, glm::vec3(0.0f, -1.0f, 0.0f));
}
for (int r = 1; r < rows - 1; ++r) {
push_border(cols - 1, r, glm::vec3(1.0f, 0.0f, 0.0f));
}
for (int c = cols - 1; c >= 0; --c) {
push_border(c, rows - 1, glm::vec3(0.0f, 1.0f, 0.0f));
}
for (int r = rows - 2; r >= 1; --r) {
push_border(0, r, glm::vec3(-1.0f, 0.0f, 0.0f));
}
const int border_count = static_cast<int>(skirt_uvs.size());
skirt_vertices.assign(static_cast<size_t>(border_count) * 2 * 6, 0.0f);
std::vector<unsigned int> idx;
idx.reserve(static_cast<size_t>(border_count) * 6);
for (int i = 0; i < border_count; ++i) {
const int next = (i + 1) % border_count;
const unsigned int top0 = static_cast<unsigned int>(i * 2);
const unsigned int bot0 = static_cast<unsigned int>(i * 2 + 1);
const unsigned int top1 = static_cast<unsigned int>(next * 2);
const unsigned int bot1 = static_cast<unsigned int>(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);
}
skirt_index_count = static_cast<int>(idx.size());
glGenVertexArrays(1, &skirt_vao);
glBindVertexArray(skirt_vao);
glGenBuffers(1, &skirt_vbo);
glBindBuffer(GL_ARRAY_BUFFER, skirt_vbo);
glBufferData(GL_ARRAY_BUFFER, skirt_vertices.size() * sizeof(float), skirt_vertices.data(), GL_DYNAMIC_DRAW);
glGenBuffers(1, &skirt_ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, skirt_ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx.size() * sizeof(unsigned int), idx.data(), 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 init_heatmap_geometry() {
if (heatmap_ibo) {
glDeleteBuffers(1, &heatmap_ibo);
heatmap_ibo = 0;
}
if (heatmap_vbo) {
glDeleteBuffers(1, &heatmap_vbo);
heatmap_vbo = 0;
}
if (heatmap_vao) {
glDeleteVertexArrays(1, &heatmap_vao);
heatmap_vao = 0;
}
const int cols = std::max(2, kMeshCols);
const int rows = std::max(2, kMeshRows);
const int vertex_count = cols * rows;
std::vector<float> verts;
verts.reserve(vertex_count * 5);
for (int r = 0; r < rows; ++r) {
const float v = (rows > 1)
? static_cast<float>(r) / static_cast<float>(rows - 1)
: 0.0f;
const float y = (v - 0.5f) * kPlaneH;
for (int c = 0; c < cols; ++c) {
const float u = (cols > 1)
? static_cast<float>(c) / static_cast<float>(cols - 1)
: 0.0f;
const float x = (u - 0.5f) * kPlaneW;
verts.push_back(x);
verts.push_back(y);
verts.push_back(0.0f);
verts.push_back(u);
verts.push_back(v);
}
}
std::vector<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 = static_cast<unsigned int>(r * cols + c);
const unsigned int i1 = static_cast<unsigned int>(r * cols + c + 1);
const unsigned int i2 = static_cast<unsigned int>((r + 1) * cols + c + 1);
const unsigned int i3 = static_cast<unsigned int>((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);
}
}
heatmap_index_count = static_cast<int>(idx.size());
glGenVertexArrays(1, &heatmap_vao);
glBindVertexArray(heatmap_vao);
glGenBuffers(1, &heatmap_vbo);
glBindBuffer(GL_ARRAY_BUFFER, heatmap_vbo);
glBufferData(GL_ARRAY_BUFFER, verts.size() * sizeof(float), verts.data(),
GL_STATIC_DRAW);
glGenBuffers(1, &heatmap_ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, heatmap_ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx.size() * sizeof(unsigned int),
idx.data(), 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 init_height_texture() {
if (height_tex) {
glDeleteTextures(1, &height_tex);
height_tex = 0;
}
if (height_values.size() !=
static_cast<size_t>(kHeightW) * static_cast<size_t>(kHeightH)) {
height_values.assign(
static_cast<size_t>(kHeightW) * static_cast<size_t>(kHeightH), 0.0f);
}
glGenTextures(1, &height_tex);
glBindTexture(GL_TEXTURE_2D, height_tex);
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, kHeightW, kHeightH, 0, GL_RED,
GL_FLOAT, height_values.data());
glBindTexture(GL_TEXTURE_2D, 0);
}
void update_skirt_vertices() {
if (skirt_vertices.empty() || skirt_uvs.empty()) {
return;
}
const float baseZ = -kThickness * 0.5f;
const float backZ = baseZ + kThickness;
const float minV = static_cast<float>(g_state.minV);
const float maxV = static_cast<float>(g_state.maxV);
const float invRange = 1.0f / std::max(1e-6f, (maxV - minV));
const int border_count = static_cast<int>(skirt_uvs.size());
for (int i = 0; i < border_count; ++i) {
const float u = skirt_uvs[i].x;
const float v = skirt_uvs[i].y;
const float value = sample_height_value(u, v);
const float t = std::clamp((value - minV) * invRange, 0.0f, 1.0f);
const float h = t * kDentMax;
const float x = (u - 0.5f) * kPlaneW;
const float y = (v - 0.5f) * kPlaneH;
const glm::vec3 n = skirt_normals[i];
const int topIndex = i * 2;
const int botIndex = i * 2 + 1;
const size_t topBase = static_cast<size_t>(topIndex) * 6;
skirt_vertices[topBase + 0] = x;
skirt_vertices[topBase + 1] = y;
skirt_vertices[topBase + 2] = baseZ + h;
skirt_vertices[topBase + 3] = n.x;
skirt_vertices[topBase + 4] = n.y;
skirt_vertices[topBase + 5] = n.z;
const size_t botBase = static_cast<size_t>(botIndex) * 6;
skirt_vertices[botBase + 0] = x;
skirt_vertices[botBase + 1] = y;
skirt_vertices[botBase + 2] = backZ;
skirt_vertices[botBase + 3] = n.x;
skirt_vertices[botBase + 4] = n.y;
skirt_vertices[botBase + 5] = n.z;
}
glBindBuffer(GL_ARRAY_BUFFER, skirt_vbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, skirt_vertices.size() * sizeof(float), skirt_vertices.data());
}
void update_demo_heightmap(float t) {
if (low_values.size() !=
static_cast<size_t>(kLowRows) * static_cast<size_t>(kLowCols)) {
low_values.assign(
static_cast<size_t>(kLowRows) * static_cast<size_t>(kLowCols), 0.0f);
}
if (height_values.size() !=
static_cast<size_t>(kHeightW) * static_cast<size_t>(kHeightH)) {
height_values.assign(
static_cast<size_t>(kHeightW) * static_cast<size_t>(kHeightH), 0.0f);
}
const float minV = static_cast<float>(g_state.minV);
const float maxV = static_cast<float>(g_state.maxV);
const float range = std::max(1.0f, maxV - minV);
const float t1 = t * 0.25f;
const float t2 = t * 0.18f;
const float t3 = t * 0.12f;
const float cx1 = 0.30f + 0.12f * std::sin(t1);
const float cy1 = 0.50f + 0.10f * std::cos(t1 * 0.9f);
const float cx2 = 0.72f + 0.10f * std::cos(t2 * 1.1f);
const float cy2 = 0.36f + 0.10f * std::sin(t2 * 0.8f);
const float cx3 = 0.50f + 0.05f * std::sin(t3);
const float cy3 = 0.72f + 0.05f * std::cos(t3 * 1.2f);
const float a1 = 0.70f + 0.15f * std::sin(t * 0.20f);
const float a2 = 0.55f + 0.20f * std::cos(t * 0.16f);
const float a3 = 0.25f + 0.10f * std::sin(t * 0.13f);
const float s1 = 0.18f;
const float s2 = 0.12f;
const float s3 = 0.10f;
for (int r = 0; r < kLowRows; ++r) {
const float v = (kLowRows > 1) ? static_cast<float>(r) /
static_cast<float>(kLowRows - 1)
: 0.0f;
for (int c = 0; c < kLowCols; ++c) {
const float u = (kLowCols > 1) ? static_cast<float>(c) /
static_cast<float>(kLowCols - 1)
: 0.0f;
const float g1 = gaussian2d(u - cx1, v - cy1, s1);
const float g2 = gaussian2d(u - cx2, v - cy2, s2);
const float g3 = gaussian2d(u - cx3, v - cy3, s3);
float value01 = 0.05f + a1 * g1 + a2 * g2 + a3 * g3;
value01 = std::clamp(value01, 0.0f, 1.0f);
low_values[static_cast<size_t>(r) * static_cast<size_t>(kLowCols) +
static_cast<size_t>(c)] = minV + range * value01;
}
}
const float invHighW =
(kHeightW > 1) ? (1.0f / static_cast<float>(kHeightW - 1)) : 0.0f;
const float invHighH =
(kHeightH > 1) ? (1.0f / static_cast<float>(kHeightH - 1)) : 0.0f;
const float lowW = static_cast<float>(kLowCols - 1);
const float lowH = static_cast<float>(kLowRows - 1);
for (int y = 0; y < kHeightH; ++y) {
const float gy = static_cast<float>(y) * invHighH * lowH;
const int y0 = std::clamp(static_cast<int>(gy), 0, kLowRows - 1);
const int y1 = std::min(y0 + 1, kLowRows - 1);
const float fy = gy - static_cast<float>(y0);
for (int x = 0; x < kHeightW; ++x) {
const float gx = static_cast<float>(x) * invHighW * lowW;
const int x0 = std::clamp(static_cast<int>(gx), 0, kLowCols - 1);
const int x1 = std::min(x0 + 1, kLowCols - 1);
const float fx = gx - static_cast<float>(x0);
const float v00 =
low_values[static_cast<size_t>(y0) * static_cast<size_t>(kLowCols) +
static_cast<size_t>(x0)];
const float v10 =
low_values[static_cast<size_t>(y0) * static_cast<size_t>(kLowCols) +
static_cast<size_t>(x1)];
const float v01 =
low_values[static_cast<size_t>(y1) * static_cast<size_t>(kLowCols) +
static_cast<size_t>(x0)];
const float v11 =
low_values[static_cast<size_t>(y1) * static_cast<size_t>(kLowCols) +
static_cast<size_t>(x1)];
const float vx0 = lerp_float(v00, v10, fx);
const float vx1 = lerp_float(v01, v11, fx);
const float vxy = lerp_float(vx0, vx1, fy);
height_values[static_cast<size_t>(y) * static_cast<size_t>(kHeightW) +
static_cast<size_t>(x)] = vxy;
}
}
height_dirty = true;
}
void upload_height_texture_if_needed() {
if (!height_dirty || !height_tex) {
return;
}
glBindTexture(GL_TEXTURE_2D, height_tex);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kHeightW, kHeightH, GL_RED, GL_FLOAT,
height_values.data());
glBindTexture(GL_TEXTURE_2D, 0);
height_dirty = false;
}
void render_heatmap() {
if (!heatmap_shader || !heatmap_vao || heatmap_index_count <= 0 ||
!height_tex) {
return;
}
heatmap_shader->use();
heatmap_shader->setMat4("uMVP", g_state.mvp);
heatmap_shader->setFloat("uMinV", static_cast<float>(g_state.minV));
heatmap_shader->setFloat("uMaxV", static_cast<float>(g_state.maxV));
heatmap_shader->setFloat("uHeightScale", kDentMax);
heatmap_shader->setFloat("uBaseZ", -kThickness * 0.5f);
heatmap_shader->setVec2("uTexelSize",
glm::vec2(1.0f / static_cast<float>(kHeightW),
1.0f / static_cast<float>(kHeightH)));
heatmap_shader->setVec2("uPlaneSize", glm::vec2(kPlaneW, kPlaneH));
heatmap_shader->setVec3("uCameraPos", g_state.cameraPos);
heatmap_shader->setVec3("uLightDir",
glm::normalize(glm::vec3(-0.25f, 0.35f, 1.0f)));
heatmap_shader->setInt("uHeightTex", 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, height_tex);
glBindVertexArray(heatmap_vao);
glDrawElements(GL_TRIANGLES, heatmap_index_count, GL_UNSIGNED_INT, nullptr);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
}
// void init_dot_texture() {
// // 简单 4x4 RGBA 程序化纹理,模拟金属纹理感
// if (dot_tex) {
// glDeleteTextures(1, &dot_tex);
// dot_tex = 0;
// }
// // Simple procedural 4x4 texture to mimic a brushed metal feel.
// const unsigned char pixels[] = {
// 180, 175, 170, 255, 185, 180, 175, 255, 190, 185, 180, 255, 185, 180,
// 175, 255, 185, 180, 175, 255, 190, 185, 180, 255, 195, 190, 185, 255,
// 190, 185, 180, 255, 190, 185, 180, 255, 195, 190, 185, 255, 200, 195,
// 190, 255, 195, 190, 185, 255, 185, 180, 175, 255, 190, 185, 180, 255,
// 195, 190, 185, 255, 190, 185, 180, 255,
// };
// glGenTextures(1, &dot_tex);
// glBindTexture(GL_TEXTURE_2D, dot_tex);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
// GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D,
// GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,
// GL_TEXTURE_WRAP_S,GL_LINEAR_MIPMAP_LINEAR);
// glTexParameteri(GL_TEXTURE_2D,
// GL_TEXTURE_WRAP_T,GL_LINEAR_MIPMAP_LINEGL_LINEARAR);
// glPixelStorei(GL_UNPACK_ALIGNMENT, 1GL_LINEARGL_CLAMP_TO_EDGE
// glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 4, 40, GL_RGBA,
// GL_UNSIGNED_BYTE, pixels); glGenerateMipmap(GL_TEXTURE_GL_CLAMP_TO_EDGE2D
// glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// glBindTexture(GL_TEXTURE_2D, 0);
// }
void init_programs() {
// 加载并编译 shader program
const std::string vsb_path = "../shaders/bg.vert";
const std::string fsb_path = "../shaders/bg.frag";
const std::string vsbase_path = "../shaders/base.vert";
const std::string fsbase_path = "../shaders/base.frag";
const std::string vsh_path = "../shaders/heatmap.vert";
const std::string fsh_path = "../shaders/heatmap.frag";
bg_shader = new Shader(vsb_path.c_str(), fsb_path.c_str());
base_shader = new Shader(vsbase_path.c_str(), fsbase_path.c_str());
heatmap_shader = new Shader(vsh_path.c_str(), fsh_path.c_str());
}
void update_instance_buffer_if_needed() {
// 如果有新数据,重新填充实例缓冲:每个点 3 个 float (x,z,value)
if (dot_count() <= 0) {
instance_count = 0;
return;
}
if (!g_state.valuesDirty) return;
const int n = dot_count();
instance_count = n;
std::vector<float> inst;
inst.resize(n * 3);
const float w =
static_cast<float>(std::max(0, g_state.cols - 1)) * g_state.pitch;
const float h =
static_cast<float>(std::max(0, g_state.rows - 1)) * g_state.pitch;
for (int i = 0; i < n; ++i) {
const int r = (g_state.cols > 0) ? (i / g_state.cols) : 0;
const int c = (g_state.cols > 0) ? (i % g_state.cols) : 0;
const float x = (static_cast<float>(c) * g_state.pitch) - w * 0.5f;
const float y = (static_cast<float>(r) * g_state.pitch) - h * 0.5f;
inst[i * 3 + 0] = x;
inst[i * 3 + 1] = y;
inst[i * 3 + 2] = (i < static_cast<int>(g_state.values.size()))
? g_state.values[i]
: static_cast<float>(g_state.minV);
}
glBindBuffer(GL_ARRAY_BUFFER, instance_vbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, inst.size() * sizeof(float), inst.data());
g_state.valuesDirty = false;
}
void update_matrices(int fbWidth, int fbHeight) {
// 计算 MVP透视投影 * 观察(环绕相机)* 单位模型
if (fbWidth <= 0 || fbHeight <= 0) {
g_state.mvp = glm::mat4(1.0f);
return;
}
const float aspect =
static_cast<float>(fbWidth) / static_cast<float>(fbHeight);
const float radius = 0.5f * std::sqrt(kPlaneW * kPlaneW + kPlaneH * kPlaneH);
const float distance = std::max(0.6f, radius * 2.4f);
// 目标点:面板中心
const glm::vec3 center(0.0f, 0.0f, 0.0f);
const float yawRad = glm::radians(g_state.camYaw);
const float pitchRad = glm::radians(g_state.camPitch);
const float cosPitch = std::cos(pitchRad);
const glm::vec3 eye =
center + glm::vec3(distance * cosPitch * std::cos(yawRad),
distance * std::sin(pitchRad),
distance * cosPitch * std::sin(yawRad));
g_state.cameraPos = eye;
// 构建视图矩阵与透视矩阵
const glm::mat4 view = glm::lookAt(eye, center, glm::vec3(0.0f, 1.0f, 0.0f));
const glm::mat4 proj =
glm::perspective(glm::radians(g_state.zoom), aspect, 0.01f,
std::max(10.0f, distance * 10.0f));
g_state.mvp = proj * view;
}
void render_background(int fbWidth, int fbHeight) {
// 屏幕空间网格,不受相机旋转影响
if (!bg_shader || !bg_vao) return;
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
bg_shader->use();
bg_shader->setVec2("uViewport", glm::vec2(static_cast<float>(fbWidth),
static_cast<float>(fbHeight)));
bg_shader->setFloat("uMinorStep", 24.0f);
bg_shader->setFloat("uMajorStep", 120.0f);
glBindVertexArray(bg_vao);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
}
void render_base() {
if (!base_shader || !base_vao) {
return;
}
base_shader->use();
base_shader->setMat4("uMVP", g_state.mvp);
base_shader->setVec3("uCameraPos", g_state.cameraPos);
base_shader->setVec3("uLightDir",
glm::normalize(glm::vec3(-0.25f, 0.35f, 1.0f)));
base_shader->setVec3("uColor", glm::vec3(0.10f, 0.15f, 0.85f));
glBindVertexArray(base_vao);
glDrawElements(GL_TRIANGLES, base_index_count, GL_UNSIGNED_INT, nullptr);
glBindVertexArray(0);
}
void render_skirt() {
if (!base_shader || !skirt_vao || skirt_index_count <= 0) {
return;
}
base_shader->use();
base_shader->setMat4("uMVP", g_state.mvp);
base_shader->setVec3("uCameraPos", g_state.cameraPos);
base_shader->setVec3("uLightDir", glm::normalize(glm::vec3(-0.25f, 0.35f, 1.0f)));
base_shader->setVec3("uColor", glm::vec3(0.10f, 0.15f, 0.85f));
glBindVertexArray(skirt_vao);
glDrawElements(GL_TRIANGLES, skirt_index_count, GL_UNSIGNED_INT, nullptr);
glBindVertexArray(0);
}
void render_room() {
// 3D 房间背景:用一个大盒子把场景包起来(相当于“屋子墙壁/地面”)
if (!room_shader || !room_vao) return;
// 房间尺寸:根据面板尺寸做一个“够大”的包围盒(单位:世界坐标)
const float base = std::max({g_state.panelW, g_state.panelH, g_state.panelD});
const glm::vec3 roomHalfSize(
std::max(1.0f, base * 1.1f), // X: 左右墙离中心的距离
std::max(1.0f, base * 1.1f), // Y: 地面/天花板离中心的距离
std::max(1.0f, base * 1.1f) // Z: 前后墙离中心的距离
);
// 网格尺寸(让房间更有空间感)
const float minorStep = std::max(0.05f, base * 0.25f);
const float majorStep = minorStep * 5.0f;
room_shader->use();
room_shader->setMat4("uMVP", g_state.mvp);
room_shader->setVec3("uCameraPos", g_state.cameraPos);
room_shader->setVec3("uRoomHalfSize", roomHalfSize);
room_shader->setFloat("uMinorStep", minorStep);
room_shader->setFloat("uMajorStep", majorStep);
room_shader->setInt("uRenderMode", g_state.renderMode);
glBindVertexArray(room_vao);
glDrawElements(GL_TRIANGLES, room_index_count, GL_UNSIGNED_INT, nullptr);
glBindVertexArray(0);
}
void render_panel() {
// 绘制面板盒子
if (!panel_shader || !panel_vao) return;
panel_shader->use();
panel_shader->setMat4("uMVP", g_state.mvp);
panel_shader->setVec3("uCameraPos", g_state.cameraPos);
panel_shader->setFloat("uPanelW", g_state.panelW);
panel_shader->setFloat("uPanelH", g_state.panelH);
panel_shader->setFloat("uPanelD", g_state.panelD);
panel_shader->setInt("uRows", g_state.rows);
panel_shader->setInt("uCols", g_state.cols);
panel_shader->setFloat("uPitch", g_state.pitch);
panel_shader->setFloat("uDotRadius", g_state.dotRadius);
panel_shader->setInt("uRenderMode", g_state.renderMode);
glBindVertexArray(panel_vao);
glDrawElements(GL_TRIANGLES, panel_index_count, GL_UNSIGNED_INT, nullptr);
glBindVertexArray(0);
}
void render_dots() {
// 实例化绘制圆点
if (!dots_shader || !dots_vao || instance_count <= 0) return;
dots_shader->use();
dots_shader->setMat4("uMVP", g_state.mvp);
dots_shader->setInt("uRenderMode", g_state.renderMode);
dots_shader->setFloat("uDotRadius", g_state.dotRadius);
dots_shader->setFloat("uBaseZ", -(g_state.panelD * 0.5f) - 0.001f);
dots_shader->setFloat("uMinV", static_cast<float>(g_state.minV));
dots_shader->setFloat("uMaxV", static_cast<float>(g_state.maxV));
dots_shader->setInt("uHasData", 1);
dots_shader->setVec3("uCameraPos", g_state.cameraPos);
dots_shader->setInt("uDotTex", 0);
if (dot_tex) {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, dot_tex);
}
glBindVertexArray(dots_vao);
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, instance_count);
glBindVertexArray(0);
if (dot_tex) {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, 0);
}
}
void update_demo_values(float t) {
// 生成演示用波纹数据,保持实例缓冲在动
const int n = dot_count();
if (n <= 0) {
instance_count = 0;
return;
}
if (static_cast<int>(g_state.values.size()) != n) {
g_state.values.assign(n, static_cast<float>(g_state.minV));
}
for (int i = 0; i < n; ++i) {
const float phase = t * 0.6f + static_cast<float>(i) * 0.35f;
const float wave = 0.5f + 0.5f * std::sin(phase);
const float minV = static_cast<float>(g_state.minV);
const float maxV = static_cast<float>(g_state.maxV);
g_state.values[i] = minV + (maxV - minV) * wave;
}
g_state.valuesDirty = true;
}
void destroy_context() {
delete bg_shader;
delete base_shader;
delete room_shader;
delete panel_shader;
delete dots_shader;
delete heatmap_shader;
if (room_vao) glDeleteVertexArrays(1, &room_vao);
if (room_vbo) glDeleteBuffers(1, &room_vbo);
if (room_ibo) glDeleteBuffers(1, &room_ibo);
if (panel_vao) glDeleteVertexArrays(1, &panel_vao);
if (panel_vbo) glDeleteBuffers(1, &panel_vbo);
if (panel_ibo) glDeleteBuffers(1, &panel_ibo);
if (dots_vao) glDeleteVertexArrays(1, &dots_vao);
if (dots_vbo) glDeleteBuffers(1, &dots_vbo);
if (instance_vbo) glDeleteBuffers(1, &instance_vbo);
if (bg_vao) glDeleteVertexArrays(1, &bg_vao);
if (bg_vbo) glDeleteBuffers(1, &bg_vbo);
if (dot_tex) glDeleteTextures(1, &dot_tex);
if (base_vao) glDeleteVertexArrays(1, &base_vao);
if (base_vbo) glDeleteBuffers(1, &base_vbo);
if (base_ibo) glDeleteBuffers(1, &base_ibo);
if (skirt_vao) glDeleteVertexArrays(1, &skirt_vao);
if (skirt_vbo) glDeleteBuffers(1, &skirt_vbo);
if (skirt_ibo) glDeleteBuffers(1, &skirt_ibo);
if (heatmap_vao) glDeleteVertexArrays(1, &heatmap_vao);
if (heatmap_vbo) glDeleteBuffers(1, &heatmap_vbo);
if (heatmap_ibo) glDeleteBuffers(1, &heatmap_ibo);
if (height_tex) glDeleteTextures(1, &height_tex);
}
void load_metal_texture(const std::string& path) {
if (metal_tex) {
glDeleteTextures(1, &metal_tex);
metal_tex = 0;
}
glGenTextures(1, &metal_tex);
glBindTexture(GL_TEXTURE_2D, metal_tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_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);
int width, height, channels;
unsigned char* data = stbi_load(path.c_str(), &width, &height, &channels, 0);
if (data) {
std::cout << "load texture image path: " << path << std::endl;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB,
GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
}
stbi_image_free(data);
}
int main() {
// 初始化窗口/上下文
GLFWwindow* window = glfw_init();
if (window == nullptr) {
return -1;
}
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
// 初始化规格与资源
init_programs();
init_background_geometry();
init_base_geometry();
init_heatmap_geometry();
init_skirt_geometry();
update_demo_heightmap(0.0f);
init_height_texture();
update_skirt_vertices();
while (!glfwWindowShouldClose(window)) {
const float currentFrame = static_cast<float>(glfwGetTime());
process_input(window);
int fbWidth = 0, fbHeight = 0;
glfwGetFramebufferSize(window, &fbWidth, &fbHeight);
glViewport(0, 0, fbWidth, fbHeight);
glClearColor(0.06f, 0.07f, 0.09f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
update_matrices(fbWidth, fbHeight);
update_demo_heightmap(currentFrame);
upload_height_texture_if_needed();
update_skirt_vertices();
render_background(fbWidth, fbHeight);
render_base();
render_skirt();
render_heatmap();
glfwSwapBuffers(window);
glfwPollEvents();
}
destroy_context();
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}