775 lines
25 KiB
C++
775 lines
25 KiB
C++
// GLFW 版本的渲染入口:画触觉面板盒子 + 点阵实例化圆点,
|
||
// 右键拖拽环绕相机,滚轮调 FOV,内置简单波纹数据做演示。
|
||
#include <algorithm>
|
||
#include <cmath>
|
||
#include <iostream>
|
||
#include <vector>
|
||
|
||
#include <glad/glad.h>
|
||
#include <GLFW/glfw3.h>
|
||
#include <glm/glm.hpp>
|
||
#include <glm/gtc/matrix_transform.hpp>
|
||
#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 = 100;
|
||
// 渲染模式: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* room_shader = nullptr;
|
||
Shader* panel_shader = nullptr;
|
||
Shader* dots_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;
|
||
|
||
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, -20.0f, 20.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);
|
||
}
|
||
|
||
// 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 vsd_path = "../shaders/dots.vert";
|
||
const std::string fsd_path = "../shaders/dots.frag";
|
||
glBindTexture(GL_TEXTURE_2D, 0);
|
||
const std::string vsb_path = "../shaders/bg.vert";
|
||
const std::string fsb_path = "../shaders/bg.frag";
|
||
const std::string vsr_path = "../shaders/room.vert";
|
||
const std::string fsr_path = "../shaders/room.frag";
|
||
const std::string vsp_path = "../shaders/panel.vert";
|
||
const std::string fsp_path = "../shaders/panel.frag";
|
||
|
||
bg_shader = new Shader(vsb_path.c_str(), fsb_path.c_str());
|
||
room_shader = new Shader(vsr_path.c_str(), fsr_path.c_str());
|
||
dots_shader = new Shader(vsd_path.c_str(), fsd_path.c_str());
|
||
panel_shader = new Shader(vsp_path.c_str(), fsp_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(g_state.panelW * g_state.panelW + g_state.panelD * g_state.panelD);
|
||
const float distance = std::max(0.5f, radius * 2.5f);
|
||
|
||
// 目标点:面板中心
|
||
const glm::vec3 center(0.0f, 0.0f, g_state.panelD*0.5f);
|
||
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_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 room_shader;
|
||
delete panel_shader;
|
||
delete dots_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);
|
||
}
|
||
|
||
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);
|
||
|
||
// 初始化规格与资源
|
||
set_spec(8, 10, 0.025f, 0.008f);
|
||
init_programs();
|
||
// init_dot_texture();
|
||
init_room_geometry();
|
||
load_metal_texture("../images/metal.jpeg");
|
||
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(1.0f, 1.0f, 1.0f, 1.0f);
|
||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||
|
||
if (panel_geometry_dirty) {
|
||
init_panel_geometry();
|
||
panel_geometry_dirty = false;
|
||
}
|
||
if (dot_geometry_dirty) {
|
||
init_dot_geometry();
|
||
dot_geometry_dirty = false;
|
||
g_state.valuesDirty = true;
|
||
}
|
||
|
||
update_matrices(fbWidth, fbHeight);
|
||
update_demo_values(currentFrame);
|
||
update_instance_buffer_if_needed();
|
||
|
||
render_room();
|
||
render_panel();
|
||
render_dots();
|
||
|
||
glfwSwapBuffers(window);
|
||
glfwPollEvents();
|
||
}
|
||
|
||
destroy_context();
|
||
glfwDestroyWindow(window);
|
||
glfwTerminate();
|
||
return 0;
|
||
}
|