完成主要交互、高性能组件、国际化和A型传感器数据包接收

This commit is contained in:
2026-01-13 16:34:28 +08:00
parent 47e6dc7244
commit 1960e6a5b9
84 changed files with 7752 additions and 332 deletions

View File

@@ -1,6 +1,8 @@
cmake_minimum_required(VERSION 3.5)
project(base-project)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(X11 REQUIRED)
add_executable(
${PROJECT_NAME}

28
test/onlygl/README.md Normal file
View File

@@ -0,0 +1,28 @@
# GLFW port notes
## 这次重要修改/新增了什么,为什么这么改
- main.cpp: 改成纯 GLFW/GLAD 版本,重建相机/输入、面板/点阵 VAO/VBO、实例化上传逻辑避免 Qt 依赖,方便直接跑 OpenGL 核心模式。
- shaders/panel.frag & shaders/dots.frag: 修过语法/Uniform对接 GLFW 管线,并保持金属质感(不再做数据伪彩色);点用内置小纹理,省掉外部贴图。
- myshader.hh: 增加常用 uniform setter、修正 program 链接日志、析构释放 program方便传矩阵/向量。
- CMakeLists.txt: 指定 C++17保证可用 std::clamp 等工具。
- README.md: 补了构建、运行和操作说明,标明从项目根运行以找到 `./shaders`
## Controls
- 右键拖拽绕物体旋转相机yaw/pitch
- 滚轮:调节视角 FOV 缩放
- Esc退出
## Build & run
```bash
mkdir -p build
cd build
cmake ..
make -j
```
从项目根目录运行(确保能找到 `./shaders`
```bash
./build/base-project
```
目前用 `update_demo_values` 生成简单波纹示例数据,如需接入传感器数据,替换 main.cpp 里的该函数并在循环前设置好 `set_spec` / `set_panel_size`

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,43 +1,122 @@
#include <cstddef>
// GLFW 版本的渲染入口:画触觉面板盒子 + 点阵实例化圆点,
// 右键拖拽环绕相机,滚轮调 FOV内置简单波纹数据做演示。
#include <algorithm>
#include <cmath>
#include <iostream>
#include <vector>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/ext/vector_float3.hpp>
#include <iostream>
#include "camera.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "myshader.hh"
#include "stb_image_wrap.h"
float deltaTime = 0.0f;
float lastFrame = 0.0f;
float yaw = -90.0f;
float pitch = 0.0f;
float fov = 45.0f;
Camera camera(glm::vec3(0.0f, 0.0f, 3.0));
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;
int dot_rows = 3;
int dot_cols = 4;
float panel_width = 1.2f;
float panel_height = 0.08f;
float panel_deep = 0.08f;
// 相机状态(环绕)
float camYaw = -90.0f;
float camPitch = 0.0f;
float zoom = 45.0f;
bool rightDown = false;
double lastMouseX = 0.0;
double lastMouseY = 0.0;
Shader bg_shader();
Shader panel_shader();
Shader dots_shader();
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;
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
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);
@@ -45,58 +124,198 @@ void process_input(GLFWwindow* window) {
}
GLFWwindow* glfw_init() {
glfwInit();
// 初始化 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", NULL, NULL);
if (window == NULL) {
std::cout << "Failed to create GLFW window" << std::endl;
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 NULL;
return nullptr;
}
glfwMakeContextCurrent(window);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cout << "Failed to initialize GLAD" << std::endl;
return NULL;
std::cerr << "Failed to initialize GLAD" << std::endl;
glfwDestroyWindow(window);
glfwTerminate();
return nullptr;
}
glViewport(0, 0, 800, 600);
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;
}
void glfw_window_loop(GLFWwindow* window) {
while (!glfwWindowShouldClose(window)) {
process_input(window);
glfwSwapBuffers(window);
glfwPollEvents();
}
int dot_count() {
// 当前点阵总数
return g_state.rows * g_state.cols;
}
void destroy_context() {
if (panel_vao) {
glDeleteVertexArrays(1, &panel_vao);
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 (panel_vbo) {
glDeleteBuffers(1, &panel_vbo);
if (bg_vao) {
glDeleteVertexArrays(1, &bg_vao);
bg_vao = 0;
}
if (panel_ibo) {
glDeleteBuffers(1, &panel_ibo);
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 (dots_vao) {
glDeleteVertexArrays(1, &dots_vao);
if (room_vbo) {
glDeleteBuffers(1, &room_vbo);
room_vbo = 0;
}
if (dots_vbo) {
glDeleteBuffers(1, &dots_vbo);
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;
@@ -110,65 +329,59 @@ void init_panel_geometry() {
panel_vao = 0;
}
const float y = panel_height * 0.5f;
const float hw = panel_width * 0.5f;
const float hd = panel_deep * 0.5;
using V = struct {
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] = {
// +Y 顶面 (normal 0, +1, 0)
{-hw, +y, -hd, 0, +1, 0}, // 0
{+hw, +y, -hd, 0, +1, 0}, // 1
{+hw, +y, +hd, 0, +1, 0}, // 2
{-hw, +y, +hd, 0, +1, 0}, // 3
// +Z 前面 (normal 0, 0, +1)
{-hw, +y, +hd, 0, 0, +1}, // 4
{+hw, +y, +hd, 0, 0, +1}, // 5
{+hw, -y, +hd, 0, 0, +1}, // 6
{-hw, -y, +hd, 0, 0, +1}, // 7
// -Y 底面 (normal 0, -1, 0)
{-hw, -y, +hd, 0, -1, 0}, // 8
{+hw, -y, +hd, 0, -1, 0}, // 9
{+hw, -y, -hd, 0, -1, 0}, // 10
{-hw, -y, -hd, 0, -1, 0}, // 11
// -Z 后面 (normal 0, 0, -1)
{+hw, +y, -hd, 0, 0, -1}, // 12
{-hw, +y, -hd, 0, 0, -1}, // 13
{-hw, -y, -hd, 0, 0, -1}, // 14
{+hw, -y, -hd, 0, 0, -1}, // 15
// -X 左面 (normal -1, 0, 0)
{-hw, +y, -hd, -1, 0, 0}, // 16
{-hw, +y, +hd, -1, 0, 0}, // 17
{-hw, -y, +hd, -1, 0, 0}, // 18
{-hw, -y, -hd, -1, 0, 0}, // 19
// +X 右面 (normal +1, 0, 0)
{+hw, +y, +hd, +1, 0, 0}, // 20
{+hw, +y, -hd, +1, 0, 0}, // 21
{+hw, -y, -hd, +1, 0, 0}, // 22
{+hw, -y, +hd, +1, 0, 0}, // 23
{-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, // top
4, 5, 6, 4, 6, 7, // front
8, 9, 10, 8, 10,11, // bottom
12,13,14, 12,14,15, // back
16,17,18, 16,18,19, // left
20,21,22, 20,22,23 // right
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
};
int panel_index_count = 36;
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);
@@ -176,28 +389,386 @@ void init_panel_geometry() {
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(V), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FLOAT, sizeof(V), (void*)(3 * sizeof(float)));
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(V), (void*)(3 * sizeof(float)));
glBindVertexArray(0);
}
void set_panel_size(float w, float h, float d) {
panel_width = w;
panel_height = h;
panel_deep = d;
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);
}
void set_spec(int rows, int cols, float pitch);
int main() {
// 初始化窗口/上下文
GLFWwindow* window = glfw_init();
if (window == NULL) {
if (window == nullptr) {
return -1;
}
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glfw_window_loop(window);
// 初始化规格与资源
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;
}

View File

@@ -6,6 +6,8 @@
#include <fstream>
#include <sstream>
#include <iostream>
#include <glm/gtc/type_ptr.hpp>
#include <glm/glm.hpp>
class Shader {
public:
@@ -13,6 +15,7 @@ public:
unsigned int ID;
// 构造器读取并构建着色器
Shader(const char* vertexPath, const char* fragmentPath) {
std::cout << "begin compile [" << vertexPath << "] and [" << fragmentPath << "] !" << std::endl;
std::string vertexCode;
std::string fragmentCode;
std::ifstream vShaderFile;
@@ -70,13 +73,18 @@ public:
glLinkProgram(ID);
glGetProgramiv(ID, GL_LINK_STATUS, &success);
if (!success) {
glGetShaderInfoLog(ID, 512, NULL, infoLog);
glGetProgramInfoLog(ID, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
glDeleteShader(vertex);
glDeleteShader(fragment);
}
~Shader() {
if (ID) {
glDeleteProgram(ID);
}
}
// 使用/激活程序
void use() {
glUseProgram(ID);
@@ -91,4 +99,13 @@ public:
void setFloat(const std::string& name, float value) const {
glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
}
void setVec2(const std::string& name, const glm::vec2& value) const {
glUniform2fv(glGetUniformLocation(ID, name.c_str()), 1, glm::value_ptr(value));
}
void setVec3(const std::string& name, const glm::vec3& value) const {
glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, glm::value_ptr(value));
}
void setMat4(const std::string& name, const glm::mat4& value) const {
glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, glm::value_ptr(value));
}
};

View File

@@ -1,15 +1,19 @@
#version 330 core
out vec4 FragColor;
// 视口大小(像素,建议传入 framebuffer 尺寸HiDPI 下要乘 devicePixelRatio
uniform vec2 uViewport;
// 以像素为单位的网格间距:细网格/粗网格
uniform float uMinorStep;
uniform float uMajorStep;
// 生成抗锯齿网格线(返回 0..11 表示在线上)
float gridLine(float stepPx) {
// 当前坐标像素
vec2 coord = gl_FragCoord.xy;
vec2 q = coord / stepPx;
// 距离最近网格线的归一化距离,再用 fwidth 做抗锯齿
vec2 g = abs(fract(q - 0.5) - 0.5) / fwidth(q);
float line = 1.0 - min(min(g.x, g.y), 1.0);
return line;
@@ -17,21 +21,28 @@ float gridLine(float stepPx) {
void main() {
vec2 viewport = max(uViewport, vec2(1.0));
vec2 uv = gl_FragCoord.xy / viewport;
vec2 uv = gl_FragCoord.xy / viewport; // 0..1
// 背景渐变:上更亮、下稍灰,常见 3D 软件的“科技感”底色
vec3 topCol = vec3(0.99, 0.99, 1.00);
vec3 botCol = vec3(0.94, 0.95, 0.98);
vec3 col = mix(botCol, topCol, uv.y);
// 网格线:细线 + 粗线(每隔一段更深一点)
float minor = gridLine(max(uMinorStep, 1.0));
float major = gridLine(max(uMajorStep, 1.0));
vec3 minorCol = vec3(0.80, 0.82, 0.87);
vec3 majorcol = vec3(0.70, 0.73, 0.80);
vec3 majorCol = vec3(0.70, 0.73, 0.80);
col = mix(col, minorCol, minor * 0.22);
col = mix(col, majorcol, major * 0.35);
col = mix(col, majorCol, major * 0.35);
// 轻微 vignette四角略暗让画面更“聚焦”
vec2 p = uv * 2.0 - 1.0;
float v = clamp(1.0 - dot(p, p) * 0.12, 0.0, 1.0);
col *= mix(1.0, v, 0.35);
FragColor = vec4(col, 1.0);
}

View File

@@ -1,6 +1,8 @@
#version 330 core
layout(location = 0) in vec2 aPos;
// 全屏背景:直接在裁剪空间画一个矩形(不受相机/旋转影响)
layout(location = 0) in vec2 aPos; // NDC: [-1,1]
void main() {
gl_Position = vec4(aPos, 0.0, 1.0);
}
}

View File

@@ -0,0 +1,165 @@
#version 330 core
in vec2 vUV;
in float vValue;
in vec3 vWorldPos;
out vec4 FragColor;
uniform float uMinV;
uniform float uMaxV;
uniform sampler2D uDotTex;
uniform int uHasData; // 0 = no data, 1 = has data
uniform vec3 uCameraPos;
uniform float uDotRadius;
uniform int uRenderMode; // 0=realistic, 1=dataViz
const float PI = 3.14159265359;
float saturate(float x) { return clamp(x, 0.0, 1.0); }
vec3 dataColorRamp(float t) {
t = saturate(t);
vec3 c0 = vec3(0.10, 0.75, 1.00); // cyan-blue (low)
vec3 c1 = vec3(0.10, 0.95, 0.35); // green
vec3 c2 = vec3(1.00, 0.92, 0.22); // yellow
vec3 c3 = vec3(1.00, 0.22, 0.10); // red (high)
if (t < 0.33) return mix(c0, c1, t / 0.33);
if (t < 0.66) return mix(c1, c2, (t - 0.33) / 0.33);
return mix(c2, c3, (t - 0.66) / 0.34);
}
vec3 fresnelSchlick(float cosTheta, vec3 F0) {
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
float D_GGX(float NdotH, float roughness) {
float a = max(0.04, roughness);
float alpha = a * a;
float alpha2 = alpha * alpha;
float denom = (NdotH * NdotH) * (alpha2 - 1.0) + 1.0;
return alpha2 / (PI * denom * denom + 1e-7);
}
float G_SchlickGGX(float NdotV, float roughness) {
float r = roughness + 1.0;
float k = (r * r) / 8.0;
return NdotV / (NdotV * (1.0 - k) + k + 1e-7);
}
float G_Smith(float NdotV, float NdotL, float roughness) {
float ggx1 = G_SchlickGGX(NdotV, roughness);
float ggx2 = G_SchlickGGX(NdotL, roughness);
return ggx1 * ggx2;
}
float D_GGX_Aniso(vec3 N, vec3 H, vec3 T, vec3 B, float ax, float ay) {
float NdotH = saturate(dot(N, H));
float TdotH = dot(T, H);
float BdotH = dot(B, H);
float ax2 = ax * ax;
float ay2 = ay * ay;
float denom = (TdotH * TdotH) / (ax2 + 1e-7) + (BdotH * BdotH) / (ay2 + 1e-7) + NdotH * NdotH;
return 1.0 / (PI * ax * ay * denom * denom + 1e-7);
}
vec3 evalLight(
vec3 N,
vec3 V,
vec3 L,
vec3 lightColor,
vec3 baseColor,
float metallic,
float roughness,
float aniso,
vec3 brushDir
) {
float NdotL = saturate(dot(N, L));
float NdotV = saturate(dot(N, V));
if (NdotL <= 0.0 || NdotV <= 0.0) return vec3(0.0);
vec3 H = normalize(V + L);
float NdotH = saturate(dot(N, H));
float VdotH = saturate(dot(V, H));
vec3 F0 = mix(vec3(0.04), baseColor, metallic);
vec3 F = fresnelSchlick(VdotH, F0);
float D = D_GGX(NdotH, roughness);
if (aniso > 0.001) {
vec3 T = normalize(brushDir - N * dot(brushDir, N));
vec3 B = normalize(cross(N, T));
float alpha = max(0.04, roughness);
float a = alpha * alpha;
float ax = mix(a, a * 0.30, aniso);
float ay = mix(a, a * 2.00, aniso);
D = D_GGX_Aniso(N, H, T, B, ax, ay);
}
float G = G_Smith(NdotV, NdotL, roughness);
vec3 spec = (D * G * F) / max(4.0 * NdotV * NdotL, 1e-6);
vec3 kD = (vec3(1.0) - F) * (1.0 - metallic);
vec3 diff = kD * baseColor / PI;
return (diff + spec) * lightColor * NdotL;
}
void main() {
vec2 p = vUV * 2.0 - 1.0;
float r = length(p);
if (r > 1.0) discard;
float r01 = saturate(r);
// Industrial engineering model: simple plated metal pad (brass/gold-ish).
// When no data, keep a bright gold base. When data is present, render the
// data color directly (no remaining gold tint), while preserving depth cues.
vec3 metalBase = vec3(0.98, 0.82, 0.30);
float value01 = clamp((vValue - uMinV) / max(1e-6, (uMaxV - uMinV)), 0.0, 1.0);
vec3 dataCol = dataColorRamp(value01);
// bool hasData = (uHasData != 0);
// vec3 baseColor = hasData ? dataCol : metalBase;
vec3 baseColor = metalBase;
// dataViz: flat/unlit, no lighting modulation (keep pure baseColor)
if (uRenderMode == 1) {
FragColor = vec4(clamp(baseColor, 0.0, 1.0), 1.0);
return;
}
// Mostly flat, with a slight bevel near the edge to catch highlights.
float slope = mix(0.06, 0.28, smoothstep(0.55, 1.0, r01));
// Face the camera: dots live on the panel front face (XY plane), so the base normal points -Z.
// vec3 N = normalize(vec3(p.x * slope, p.y * slope, -1.0));
vec3 N = normalize(vec3(0.0, 0.15, -1.0));
vec3 V = normalize(uCameraPos - vWorldPos);
// float metallic = hasData ? 0.0 : 0.90;
// float roughness = hasData ? 0.78 : ((uRenderMode == 1) ? 0.70 : 0.55);
float metallic = 0.90;
float roughness = 0.55;
// "Front light": make the light come from the camera direction (like a headlight/flashlight).
vec3 keyL = V;
vec3 fillL = V;
vec3 keyC = vec3(1.00, 0.98, 0.95) * 1.8;
vec3 fillC = vec3(0.85, 0.90, 1.00) * 0.9;
vec3 Lo = vec3(0.0);
Lo += evalLight(N, V, keyL, keyC, baseColor, metallic, roughness, 0.0, vec3(1.0, 0.0, 0.0));
Lo += evalLight(N, V, fillL, fillC, baseColor, metallic, roughness, 0.0, vec3(1.0, 0.0, 0.0));
vec3 F0 = mix(vec3(0.04), baseColor, metallic);
vec3 ambient = baseColor * 0.10 + F0 * 0.04;
float edgeAO = smoothstep(0.88, 1.0, r01);
float ao = 1.0 - edgeAO * 0.10;
// Subtle boundary ring (engineering-model crispness, not a UI outline).
float ring = smoothstep(0.82, 0.92, r01) - smoothstep(0.92, 1.00, r01);
vec3 col = (ambient + Lo) * ao;
col = mix(col, col * 0.82, ring * 0.35);
FragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
}

View File

@@ -0,0 +1,31 @@
#version 330 core
layout(location = 0) in vec2 qQuadPos; // 单位 quad 的局部顶点坐标(范围 [-1,1]
layout(location = 1) in vec2 aUV; // UV用于 fragment shader 把 quad 变成圆形)
layout(location = 2) in vec2 iOffsetXZ; // 每个点的偏移(世界坐标 XZ
layout(location = 3) in float iValue; // 每个点的数值(用于颜色映射)
out vec2 vUV;
out float vValue;
out vec3 vWorldPos;
uniform mat4 uMVP; // Projection * View * Model这里 Model 约等于单位矩阵)
uniform float uDotRadius; // dot 半径(世界坐标单位)
uniform float uBaseZ; // dot 的高度(通常 = panel 顶面 y + 一点点偏移)
void main() {
vUV = aUV;
vValue = iValue;
// 先确定 dot 的中心点(世界坐标)
vec3 world = vec3(iOffsetXZ.x, iOffsetXZ.y, uBaseZ);
// 再把单位 quad 按半径缩放并加到中心点上(让 quad 落在 XZ 平面)
world.x += qQuadPos.x * uDotRadius;
world.y += qQuadPos.y * uDotRadius;
// 输出裁剪空间坐标(最终会进行透视除法与视口映射,变成屏幕上的像素)
vWorldPos = world;
gl_Position = uMVP * vec4(world, 1.0);
}

View File

@@ -0,0 +1,191 @@
#version 330 core
in vec3 vWorldPos;
in vec3 vWorldNormal;
out vec4 FragColor;
uniform vec3 uCameraPos;
uniform float uPanelW;
uniform float uPanelH;
uniform float uPanelD;
uniform int uRows;
uniform int uCols;
uniform float uPitch;
uniform float uDotRadius;
uniform int uRenderMode; // 0=realistic, 1=dataViz
const float PI = 3.14159265359;
float saturate(float x) { return clamp(x, 0.0, 1.0); }
float hash12(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * 0.1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
float noise2d(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
float a = hash12(i);
float b = hash12(i + vec2(1.0, 0.0));
float c = hash12(i + vec2(0.0, 1.0));
float d = hash12(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
float fbm(vec2 p) {
float v = 0.0;
float a = 0.5;
for (int i = 0; i < 4; ++i) {
v += a * noise2d(p);
p *= 2.0;
a *= 0.5;
}
return v;
}
vec3 fresnelSchlick(float cosTheta, vec3 F0) {
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
float D_GGX(float NdotH, float roughness) {
float a = max(0.04, roughness);
float alpha = a * a;
float alpha2 = alpha * alpha;
float denom = (NdotH * NdotH) * (alpha2 - 1.0) + 1.0;
return alpha2 / (PI * denom * denom + 1e-7);
}
float G_SchlickGGX(float NdotV, float roughness) {
float r = roughness + 1.0;
float k = (r * r) / 8.0;
return NdotV / (NdotV * (1.0 - k) + k + 1e-7);
}
float G_Smith(float NdotV, float NdotL, float roughness) {
float ggx1 = G_SchlickGGX(NdotV, roughness);
float ggx2 = G_SchlickGGX(NdotL, roughness);
return ggx1 * ggx2;
}
float D_GGX_Aniso(vec3 N, vec3 H, vec3 T, vec3 B, float ax, float ay) {
float NdotH = saturate(dot(N, H));
float TdotH = dot(T, H);
float BdotH = dot(B, H);
float ax2 = ax * ax;
float ay2 = ay * ay;
float denom = (TdotH * TdotH) / (ax2 + 1e-7) + (BdotH * BdotH) / (ay2 + 1e-7) + NdotH * NdotH;
return 1.0 / (PI * ax * ay * denom * denom + 1e-7);
}
vec3 evalLight(
vec3 N,
vec3 V,
vec3 L,
vec3 lightColor,
vec3 baseColor,
float metallic,
float roughness,
float aniso,
vec3 brushDir
) {
float NdotL = saturate(dot(N, L));
float NdotV = saturate(dot(N, V));
if (NdotL <= 0.0 || NdotV <= 0.0) return vec3(0.0);
vec3 H = normalize(V + L);
float NdotH = saturate(dot(N, H));
float VdotH = saturate(dot(V, H));
vec3 F0 = mix(vec3(0.04), baseColor, metallic);
vec3 F = fresnelSchlick(VdotH, F0);
float D = D_GGX(NdotH, roughness);
if (aniso > 0.001) {
vec3 T = normalize(brushDir - N * dot(brushDir, N));
vec3 B = normalize(cross(N, T));
float alpha = max(0.04, roughness);
float a = alpha * alpha;
float ax = mix(a, a * 0.35, aniso);
float ay = mix(a, a * 1.80, aniso);
D = D_GGX_Aniso(N, H, T, B, ax, ay);
}
float G = G_Smith(NdotV, NdotL, roughness);
vec3 spec = (D * G * F) / max(4.0 * NdotV * NdotL, 1e-6);
vec3 kD = (vec3(1.0) - F) * (1.0 - metallic);
vec3 diff = kD * baseColor / PI;
return (diff + spec) * lightColor * NdotL;
}
float nearestDotDistanceXZ(vec2 xz) {
if (uPitch <= 0.0 || uRows <= 0 || uCols <= 0) return 1e6;
int colsM1 = max(uCols - 1, 0);
int rowsM1 = max(uRows - 1, 0);
float halfGridW = float(colsM1) * uPitch * 0.5;
float halfGridD = float(rowsM1) * uPitch * 0.5;
vec2 g = (xz + vec2(halfGridW, halfGridD)) / max(1e-6, uPitch);
vec2 gi = floor(g + 0.5);
gi = clamp(gi, vec2(0.0), vec2(float(colsM1), float(rowsM1)));
vec2 c = gi * uPitch - vec2(halfGridW, halfGridD);
return length(xz - c);
}
void main() {
// panel 先用一个固定颜色(后续可以加光照/材质)
vec3 N = normalize(vWorldNormal);
vec3 V = normalize(uCameraPos - vWorldPos);
float isTop = step(0.75, N.y);
// ------------------------------------------------------------
// Industrial engineering model: neutral matte gray panel (support layer only)
// ------------------------------------------------------------
vec3 topBase = vec3(0.30, 0.31, 0.32);
vec3 sideBase = vec3(0.27, 0.28, 0.29);
vec3 baseColor = mix(sideBase, topBase, isTop);
// dataViz: flat/unlit, no lighting modulation (keep pure baseColor)
if (uRenderMode == 1) {
FragColor = vec4(clamp(baseColor, 0.0, 1.0), 1.0);
return;
}
vec2 xz = vWorldPos.xz;
float dotContact = 0.0;
if (isTop > 0.5 && uDotRadius > 0.0) {
float d = nearestDotDistanceXZ(xz);
float w = max(0.002, uDotRadius * 0.22);
dotContact = 1.0 - smoothstep(uDotRadius, uDotRadius + w, d);
}
// "Front light": make the light come from the camera direction (like a headlight/flashlight).
vec3 L = V;
// L = normalize(vec3(0.0, 0.15, -1.0));
float diff = saturate(dot(N, L));
float lighting = 0.90 + 0.10 * diff;
float hw = max(1e-6, uPanelW * 0.5);
float hd = max(1e-6, uPanelD * 0.5);
float edgeDist = min(hw - abs(vWorldPos.x), hd - abs(vWorldPos.z));
float edgeW = max(0.002, min(hw, hd) * 0.012);
float edgeLine = (1.0 - smoothstep(edgeW, edgeW * 2.5, edgeDist)) * isTop;
float rim = pow(1.0 - saturate(dot(N, V)), 2.2) * isTop;
float ao = 1.0 - dotContact * 0.08;
vec3 col = baseColor * lighting * ao;
col += edgeLine * vec3(0.020);
col += rim * vec3(0.015);
// Slightly deepen the bottom face to read as thickness, but keep it subtle.
float isBottom = step(0.75, -N.y);
col *= mix(1.0, 0.92, isBottom);
FragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
}

View File

@@ -0,0 +1,18 @@
#version 330 core
// 顶点输入(来自 VBO
layout(location=0) in vec3 aPos; // 顶点位置(当前我们直接当作“世界坐标”来用)
layout(location=1) in vec3 aN; // 法线(当前没用到,先保留)
// uMVP = Projection * View * Model
// 把顶点从“世界坐标”变换到“裁剪空间clip spaceOpenGL 用 gl_Position 来完成屏幕投影
out vec3 vWorldPos;
out vec3 vWorldNormal;
uniform mat4 uMVP;
void main() {
// Model is identity in this project; treat vertex data as world space.
vWorldPos = aPos;
vWorldNormal = aN;
gl_Position = uMVP * vec4(aPos, 1.0);
}

View File

@@ -0,0 +1,90 @@
#version 330 core
in vec3 vWorldPos;
in vec3 vWorldNormal;
out vec4 FragColor;
uniform vec3 uCameraPos;
uniform vec3 uRoomHalfSize;
uniform float uMinorStep;
uniform float uMajorStep;
uniform int uRenderMode; // 0=realistic, 1=dataViz (flat/unlit)
float saturate(float x) {
return clamp(x, 0.0, 1.0);
}
float gridLine(vec2 coord, float stepSize) {
stepSize = max(stepSize, 1e-4);
vec2 q = coord / stepSize;
vec2 g = abs(fract(q - 0.5) - 0.5) / fwidth(q);
return 1.0 - min(min(g.x, g.y), 1.0);
}
vec2 pickGridPlane(vec3 N, vec3 P) {
// 根据朝向选择在哪个平面画网格:
// - 地面/天花板(法线接近 ±Y用 XZ
// - 前后墙(法线接近 ±Z用 XY
// - 左右墙(法线接近 ±X用 ZY
vec3 a = abs(N);
if (a.y > a.x && a.y > a.z) return P.xz;
if (a.z > a.x) return P.xy;
return P.zy;
}
void main() {
vec3 N = normalize(vWorldNormal);
vec3 V = normalize(uCameraPos - vWorldPos);
// 区分地面/天花板/墙面配色(简单做个“房间感”)
float isFloor = step(0.8, N.y);
float isCeil = step(0.8, -N.y);
vec3 floorCol = vec3(0.90, 0.90, 0.92);
vec3 wallCol = vec3(0.96, 0.96, 0.98);
vec3 ceilCol = vec3(0.98, 0.98, 1.00);
vec3 baseCol = wallCol;
baseCol = mix(baseCol, floorCol, isFloor);
baseCol = mix(baseCol, ceilCol, isCeil);
// 在不同面上画网格:小格 + 大格
vec2 plane = pickGridPlane(N, vWorldPos);
float minor = gridLine(plane, uMinorStep);
float major = gridLine(plane, uMajorStep);
vec3 minorCol = vec3(0.78, 0.80, 0.85);
vec3 majorCol = vec3(0.68, 0.70, 0.77);
baseCol = mix(baseCol, minorCol, minor * 0.18);
baseCol = mix(baseCol, majorCol, major * 0.28);
// dataViz: flat/unlit, no lighting modulation (keep pure baseCol + grid)
if (uRenderMode == 1) {
FragColor = vec4(clamp(baseCol, 0.0, 1.0), 1.0);
return;
}
// 简单两盏灯:主光 + 补光(够用就好)
// "Front light": make the light come from the camera direction (like a headlight/flashlight).
vec3 keyL = V;
vec3 fillL = V;
float diff1 = max(dot(N, keyL), 0.0);
float diff2 = max(dot(N, fillL), 0.0);
float lighting = 0.65 + 0.25 * diff1 + 0.10 * diff2;
// 角落稍微压暗,增强“箱体/房间”感觉
vec3 p = abs(vWorldPos / max(uRoomHalfSize, vec3(1e-4)));
float corner = pow(max(p.x, max(p.y, p.z)), 6.0);
float cornerDark = mix(1.0, 0.80, corner);
// 轻微雾化:远处更亮一点点,让边界更柔和
float dist = length(uCameraPos - vWorldPos);
float fog = exp(-dist * 0.06);
vec3 fogCol = vec3(0.985, 0.987, 0.995);
vec3 col = baseCol * lighting * cornerDark;
col = mix(fogCol, col, saturate(fog));
// 增加一点边缘轮廓(靠观察方向)
float rim = pow(1.0 - saturate(dot(N, V)), 2.0);
col += rim * 0.04;
FragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
}

View File

@@ -0,0 +1,19 @@
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aN;
out vec3 vWorldPos;
out vec3 vWorldNormal;
uniform mat4 uMVP;
uniform vec3 uRoomHalfSize;
void main() {
// 把单位立方体 [-1,1] 缩放成房间大小
vec3 world = aPos * uRoomHalfSize;
vWorldPos = world;
vWorldNormal = aN;
gl_Position = uMVP * vec4(world, 1.0);
}