first commit
This commit is contained in:
48
shaders/bg.frag
Normal file
48
shaders/bg.frag
Normal file
@@ -0,0 +1,48 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 viewport = max(uViewport, vec2(1.0));
|
||||
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);
|
||||
|
||||
col = mix(col, minorCol, minor * 0.22);
|
||||
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);
|
||||
}
|
||||
|
||||
8
shaders/bg.vert
Normal file
8
shaders/bg.vert
Normal file
@@ -0,0 +1,8 @@
|
||||
#version 330 core
|
||||
// 全屏背景:直接在裁剪空间画一个矩形(不受相机/旋转影响)
|
||||
layout(location = 0) in vec2 aPos; // NDC: [-1,1]
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(aPos, 0.0, 1.0);
|
||||
}
|
||||
|
||||
153
shaders/dots.frag
Normal file
153
shaders/dots.frag
Normal file
@@ -0,0 +1,153 @@
|
||||
#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;
|
||||
|
||||
// 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));
|
||||
vec3 N = normalize(vec3(p.x * slope, 1.0, p.y * slope));
|
||||
vec3 V = normalize(uCameraPos - vWorldPos);
|
||||
|
||||
float metallic = hasData ? 0.0 : 0.90;
|
||||
float roughness = hasData ? 0.78 : ((uRenderMode == 1) ? 0.70 : 0.55);
|
||||
|
||||
vec3 keyL = normalize(vec3(0.55, 1.00, 0.25));
|
||||
vec3 fillL = normalize(vec3(-0.30, 0.70, -0.80));
|
||||
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
shaders/dots.vert
Normal file
31
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 uBaseY; // dot 的高度(通常 = panel 顶面 y + 一点点偏移)
|
||||
|
||||
void main() {
|
||||
vUV = aUV;
|
||||
vValue = iValue;
|
||||
|
||||
// 先确定 dot 的中心点(世界坐标)
|
||||
vec3 world = vec3(iOffsetXZ.x, uBaseY, iOffsetXZ.y);
|
||||
|
||||
// 再把单位 quad 按半径缩放并加到中心点上(让 quad 落在 XZ 平面)
|
||||
world.x += qQuadPos.x * uDotRadius;
|
||||
world.z += qQuadPos.y * uDotRadius;
|
||||
|
||||
// 输出裁剪空间坐标(最终会进行透视除法与视口映射,变成屏幕上的像素)
|
||||
vWorldPos = world;
|
||||
gl_Position = uMVP * vec4(world, 1.0);
|
||||
}
|
||||
183
shaders/panel.frag
Normal file
183
shaders/panel.frag
Normal file
@@ -0,0 +1,183 @@
|
||||
#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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
vec3 L = normalize(vec3(0.45, 1.00, 0.20));
|
||||
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
shaders/panel.vert
Normal file
18
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);
|
||||
}
|
||||
Reference in New Issue
Block a user