// GLFW 版本的渲染入口:画触觉面板盒子 + 点阵实例化圆点, // 右键拖拽环绕相机,滚轮调 FOV,内置简单波纹数据做演示。 #include #include #include #include #include #include #include #include #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 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 low_values; std::vector height_values; bool height_dirty = true; std::vector skirt_uvs; std::vector skirt_normals; std::vector 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(xpos - g_state.lastMouseX); const float dy = static_cast(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(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(std::max(0, g_state.cols - 1)) * g_state.pitch; const float gridD = static_cast(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(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(g_state.minV); } const float x = std::clamp(u, 0.0f, 1.0f) * static_cast(kHeightW - 1); const float y = std::clamp(v, 0.0f, 1.0f) * static_cast(kHeightH - 1); const int x0 = std::clamp(static_cast(std::floor(x)), 0, kHeightW - 1); const int y0 = std::clamp(static_cast(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(x0); const float fy = y - static_cast(y0); const float v00 = height_values[static_cast(y0) * static_cast(kHeightW) + static_cast(x0)]; const float v10 = height_values[static_cast(y0) * static_cast(kHeightW) + static_cast(x1)]; const float v01 = height_values[static_cast(y1) * static_cast(kHeightW) + static_cast(x0)]; const float v11 = height_values[static_cast(y1) * static_cast(kHeightW) + static_cast(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(c) / static_cast(cols - 1) : 0.0f; const float v = (rows > 1) ? static_cast(r) / static_cast(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(skirt_uvs.size()); skirt_vertices.assign(static_cast(border_count) * 2 * 6, 0.0f); std::vector idx; idx.reserve(static_cast(border_count) * 6); for (int i = 0; i < border_count; ++i) { const int next = (i + 1) % border_count; const unsigned int top0 = static_cast(i * 2); const unsigned int bot0 = static_cast(i * 2 + 1); const unsigned int top1 = static_cast(next * 2); const unsigned int bot1 = static_cast(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(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 verts; verts.reserve(vertex_count * 5); for (int r = 0; r < rows; ++r) { const float v = (rows > 1) ? static_cast(r) / static_cast(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(c) / static_cast(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 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(r * cols + c); const unsigned int i1 = static_cast(r * cols + c + 1); const unsigned int i2 = static_cast((r + 1) * cols + c + 1); const unsigned int i3 = static_cast((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(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(kHeightW) * static_cast(kHeightH)) { height_values.assign( static_cast(kHeightW) * static_cast(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(g_state.minV); const float maxV = static_cast(g_state.maxV); const float invRange = 1.0f / std::max(1e-6f, (maxV - minV)); const int border_count = static_cast(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(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(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(kLowRows) * static_cast(kLowCols)) { low_values.assign( static_cast(kLowRows) * static_cast(kLowCols), 0.0f); } if (height_values.size() != static_cast(kHeightW) * static_cast(kHeightH)) { height_values.assign( static_cast(kHeightW) * static_cast(kHeightH), 0.0f); } const float minV = static_cast(g_state.minV); const float maxV = static_cast(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(r) / static_cast(kLowRows - 1) : 0.0f; for (int c = 0; c < kLowCols; ++c) { const float u = (kLowCols > 1) ? static_cast(c) / static_cast(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(r) * static_cast(kLowCols) + static_cast(c)] = minV + range * value01; } } const float invHighW = (kHeightW > 1) ? (1.0f / static_cast(kHeightW - 1)) : 0.0f; const float invHighH = (kHeightH > 1) ? (1.0f / static_cast(kHeightH - 1)) : 0.0f; const float lowW = static_cast(kLowCols - 1); const float lowH = static_cast(kLowRows - 1); for (int y = 0; y < kHeightH; ++y) { const float gy = static_cast(y) * invHighH * lowH; const int y0 = std::clamp(static_cast(gy), 0, kLowRows - 1); const int y1 = std::min(y0 + 1, kLowRows - 1); const float fy = gy - static_cast(y0); for (int x = 0; x < kHeightW; ++x) { const float gx = static_cast(x) * invHighW * lowW; const int x0 = std::clamp(static_cast(gx), 0, kLowCols - 1); const int x1 = std::min(x0 + 1, kLowCols - 1); const float fx = gx - static_cast(x0); const float v00 = low_values[static_cast(y0) * static_cast(kLowCols) + static_cast(x0)]; const float v10 = low_values[static_cast(y0) * static_cast(kLowCols) + static_cast(x1)]; const float v01 = low_values[static_cast(y1) * static_cast(kLowCols) + static_cast(x0)]; const float v11 = low_values[static_cast(y1) * static_cast(kLowCols) + static_cast(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(y) * static_cast(kHeightW) + static_cast(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(g_state.minV)); heatmap_shader->setFloat("uMaxV", static_cast(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(kHeightW), 1.0f / static_cast(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 inst; inst.resize(n * 3); const float w = static_cast(std::max(0, g_state.cols - 1)) * g_state.pitch; const float h = static_cast(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(c) * g_state.pitch) - w * 0.5f; const float y = (static_cast(r) * g_state.pitch) - h * 0.5f; inst[i * 3 + 0] = x; inst[i * 3 + 1] = y; inst[i * 3 + 2] = (i < static_cast(g_state.values.size())) ? g_state.values[i] : static_cast(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(fbWidth) / static_cast(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(fbWidth), static_cast(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(g_state.minV)); dots_shader->setFloat("uMaxV", static_cast(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(g_state.values.size()) != n) { g_state.values.assign(n, static_cast(g_state.minV)); } for (int i = 0; i < n; ++i) { const float phase = t * 0.6f + static_cast(i) * 0.35f; const float wave = 0.5f + 0.5f * std::sin(phase); const float minV = static_cast(g_state.minV); const float maxV = static_cast(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(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; }