Files
tactileipc3d/test/onlygl/main.cpp

775 lines
25 KiB
C++
Raw 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 <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;
}