完成主要交互、高性能组件、国际化和A型传感器数据包接收
This commit is contained in:
@@ -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
28
test/onlygl/README.md
Normal 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`。
|
||||
BIN
test/onlygl/images/metal.jpeg
Normal file
BIN
test/onlygl/images/metal.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
#version 330 core
|
||||
out vec4 FragColor;
|
||||
|
||||
// 视口大小(像素,建议传入 framebuffer 尺寸,HiDPI 下要乘 devicePixelRatio)
|
||||
uniform vec2 uViewport;
|
||||
|
||||
// 以像素为单位的网格间距:细网格/粗网格
|
||||
uniform float uMinorStep;
|
||||
uniform float uMajorStep;
|
||||
|
||||
// 生成抗锯齿网格线(返回 0..1,1 表示在线上)
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
165
test/onlygl/shaders/dots.frag
Normal file
165
test/onlygl/shaders/dots.frag
Normal 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);
|
||||
}
|
||||
31
test/onlygl/shaders/dots.vert
Normal file
31
test/onlygl/shaders/dots.vert
Normal 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);
|
||||
}
|
||||
191
test/onlygl/shaders/panel.frag
Normal file
191
test/onlygl/shaders/panel.frag
Normal 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);
|
||||
}
|
||||
18
test/onlygl/shaders/panel.vert
Normal file
18
test/onlygl/shaders/panel.vert
Normal 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 space)”,OpenGL 用 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);
|
||||
}
|
||||
90
test/onlygl/shaders/room.frag
Normal file
90
test/onlygl/shaders/room.frag
Normal 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);
|
||||
}
|
||||
19
test/onlygl/shaders/room.vert
Normal file
19
test/onlygl/shaders/room.vert
Normal 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user