topfans/frontend/utils/webgl/holographic-shaders.js
2026-05-16 02:42:32 +08:00

323 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 全息镭射卡 WebGL 着色器源码
*
* 核心设计(人物清晰优先):
* - 安全区域safeZone: 卡片中心人物主体区,纹理直通输出,零损耗
* - 环形特效区: 边缘光谱色散 + 微结构衍射 + 高光流转
* - Mipmap 纹理采样保证缩小/倾斜时的清晰度
* - 边缘倒角光影(不干扰人物)
*/
export const HOLO_VERT_SRC = `
attribute vec2 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
varying vec2 v_position;
uniform vec2 u_resolution;
void main() {
v_texCoord = a_texCoord;
v_position = a_position * u_resolution;
gl_Position = vec4(a_position * 2.0 - 1.0, 0.0, 1.0);
}
`
export const HOLO_FRAG_SRC = `
precision highp float;
varying vec2 v_texCoord;
varying vec2 v_position;
uniform vec2 u_resolution;
uniform float u_time;
uniform float u_dpr;
uniform sampler2D u_baseImage;
uniform sampler2D u_scratchMap;
uniform sampler2D u_spectrumRamp;
uniform vec3 u_viewAngle;
uniform vec3 u_lightDir;
uniform float u_hasBaseImage;
uniform float u_effectIntensity;
uniform float u_dispersionStrength;
uniform float u_diffractionScale;
uniform float u_highlightSpeed;
uniform float u_highlightWidth;
uniform float u_fresnelPower;
uniform float u_noiseScale;
uniform float u_noiseOctaves;
uniform float u_cardCornerRadius;
uniform float u_safeZoneRadius;
uniform float u_safeZoneSoftness;
const float PI = 3.14159265359;
const float TAU = 6.28318530718;
// ---- 哈希与噪声 ----
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}
float hash3(vec3 p) {
return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453123);
}
float noise2D(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
return mix(mix(hash(i), hash(i + vec2(1.0, 0.0)), f.x),
mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), f.x), f.y);
}
float fbm(vec2 p) {
float value = 0.0;
float amplitude = 0.5;
float frequency = 1.0;
float lacunarity = 2.1;
float persistence = 0.55;
int octaves = int(u_noiseOctaves);
mat2 rot = mat2(1.6, 1.2, -1.2, 1.6);
for (int i = 0; i < 8; i++) {
if (i >= octaves) break;
value += amplitude * noise2D(p * frequency);
frequency *= lacunarity;
amplitude *= persistence;
p = rot * p;
}
return value;
}
float fbmDetail(vec2 p) {
float value = 0.0;
float amplitude = 0.45;
float frequency = 2.5;
mat2 rot = mat2(1.4, -0.9, 0.9, 1.4);
for (int i = 0; i < 5; i++) {
value += amplitude * noise2D(p * frequency);
frequency *= 2.3;
amplitude *= 0.48;
p = rot * p;
}
return value;
}
// ---- 颜色工具 ----
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
vec3 sampleSpectrum(float t) {
t = fract(t);
float s = 0.85 + 0.15 * sin(t * TAU * 2.3);
float l = 0.45 + 0.18 * sin(t * TAU * 1.7 + 0.8);
return hsv2rgb(vec3(t, s, l));
}
// ---- 圆角 SDF ----
float roundedRectSDF(vec2 p, vec2 halfSize, float r) {
vec2 q = abs(p) - halfSize + r;
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
}
// ---- 菲涅尔 ----
float fresnelSchlick(float cosTheta, float f0) {
return f0 + (1.0 - f0) * pow(1.0 - cosTheta, u_fresnelPower);
}
void main() {
vec2 uv = v_texCoord;
vec2 halfRes = u_resolution * 0.5;
vec2 pn = v_position - halfRes;
float maxDim = max(halfRes.x, halfRes.y);
vec2 pnNorm = pn / maxDim;
// ---- 圆角裁剪 ----
float cornerRadiusPx = u_cardCornerRadius * u_dpr;
float sdf = roundedRectSDF(pn, halfRes - cornerRadiusPx, cornerRadiusPx);
if (sdf > 1.5) discard;
float cornerMask = 1.0 - smoothstep(-1.5, 1.5, sdf);
// ---- 视角参数 ----
float viewX = u_viewAngle.x;
float viewY = u_viewAngle.y;
float cosTheta = 1.0 / sqrt(1.0 + viewX * viewX + viewY * viewY);
float fresnel = fresnelSchlick(cosTheta, 0.04);
float fresnelEdge = fresnelSchlick(cosTheta, 0.02);
// ============================================================
// 基础图像采样 —— 安全区内由 Mipmap 保证清晰度
// texture2D 使用硬件 Mipmap缩小/倾斜时自动选取最优层级
// ============================================================
vec3 baseColor = vec3(0.04, 0.03, 0.08);
if (u_hasBaseImage > 0.5) {
vec4 baseSample = texture2D(u_baseImage, uv);
baseColor = baseSample.rgb;
}
// ============================================================
// 安全区域计算
// distToCenter: 0=中心, ~0.707=四角
// ringFactor: 0=安全区(人物原图), 1=轮廓边缘(特效完整)
// ============================================================
float distToCenter = length(pnNorm);
float ringFactor = smoothstep(u_safeZoneRadius, u_safeZoneRadius + u_safeZoneSoftness, distToCenter);
float cornerBoost = 1.0 + smoothstep(0.55, 0.78, distToCenter) * 0.45;
ringFactor = clamp(ringFactor * cornerBoost, 0.0, 1.0);
// ============================================================
// 噪声计算(用于边缘特效,安全区内这些值不被消费)
// ============================================================
float noiseCoordScale = u_noiseScale * 3.5;
vec2 noiseCoord = uv * u_resolution * noiseCoordScale / (100.0 * u_dpr);
vec2 noiseShift = vec2(viewX * 0.15, viewY * 0.12);
float microFbm = fbm(noiseCoord + noiseShift + u_time * 0.0003);
float microDetail = fbmDetail(noiseCoord * 2.3 + noiseShift * 1.7 + u_time * 0.0005);
float diffIntensity = u_diffractionScale * (0.4 + fresnel * 1.1) * u_effectIntensity;
float diffPattern = microFbm * 0.7 + microDetail * 0.3;
// ---- 光谱色散 ----
float dispersionShift = (viewX * 0.65 + viewY * 0.35) * u_dispersionStrength;
float localPhase = diffPattern * 0.35 + microDetail * 0.2;
float spectrumPhase = fract(dispersionShift + localPhase + u_time * 0.00002);
vec3 spectrumColor = sampleSpectrum(spectrumPhase);
vec3 chromaSpectrum = vec3(
sampleSpectrum(fract(spectrumPhase + 0.012)).r,
spectrumColor.g,
sampleSpectrum(fract(spectrumPhase - 0.012)).b
);
// ---- 高光流转 ----
float highlightPhase = u_time * 0.001 * u_highlightSpeed;
float highlightAngle = viewX * 1.8 + viewY * 0.9;
vec2 hlCoord = vec2(
(uv.x - 0.5) * cos(highlightAngle) + (uv.y - 0.5) * sin(highlightAngle),
-(uv.x - 0.5) * sin(highlightAngle) + (uv.y - 0.5) * cos(highlightAngle)
);
float hlDist = abs(hlCoord.x - sin(highlightPhase * 1.3) * 0.55);
float hlWidth = u_highlightWidth * 0.18;
float highlight = exp(-hlDist * hlDist / (hlWidth * hlWidth));
highlight *= 0.75 + (microFbm * 0.35 + microDetail * 0.15) * 0.5;
float hlSoft = exp(-hlDist * hlDist / (hlWidth * hlWidth * 2.5));
highlight = mix(highlight, hlSoft, 0.3);
float hl2Dist = abs(hlCoord.x - sin(highlightPhase * 0.9 + 1.2) * 0.45);
highlight += exp(-hl2Dist * hl2Dist / (hlWidth * hlWidth * 1.8)) * 0.4;
// ---- 微划痕 ----
float scratch = 0.0;
float scratchFreq = 180.0 * u_dpr;
vec2 scratchCoord = uv * u_resolution / scratchFreq;
for (int i = 0; i < 3; i++) {
float fi = float(i);
vec2 sc = scratchCoord * (1.0 + fi * 0.7) + vec2(fi * 3.7, fi * 5.3);
float ns = noise2D(sc);
vec2 dir = vec2(cos(ns * TAU), sin(ns * TAU));
float line = abs(fract(dot(scratchCoord, dir) * (3.0 + fi * 2.0)) - 0.5);
scratch += smoothstep(0.04, 0.0, line) * ns * 0.018 / (1.0 + fi * 0.6);
}
// ---- 珠光颗粒 ----
float sparkle = 0.0;
float sparkleSeed = hash3(vec3(floor(uv * u_resolution * 1.5), floor(u_time * 0.025)));
if (sparkleSeed > 0.992) {
float b = (sparkleSeed - 0.992) / 0.008;
sparkle = b * b * 1.6 * (0.5 + fresnelEdge * 1.2);
}
// ============================================================
// 颜色合成 —— ringFactor 严格约束所有镭射效果
//
// 反模糊关键设计:
// 1. 安全区内(ringFactor=0) → baseColor 原封不动输出
// 2. 所有 mix/screen/add 等混合运算的结果乘以 ringFactor
// 3. 色散 mix(a, b, t*ringFactor): 安全区内 t=0 → 只取 a=baseColor
// ============================================================
vec3 holoLayer = baseColor;
// 边缘光谱色散
float holoBlend = diffIntensity * (0.45 + fresnel * 0.65) * ringFactor;
holoLayer = mix(holoLayer, chromaSpectrum, holoBlend * 0.42);
// 微结构衍射
float diffOverlay = diffIntensity * 0.28 * ringFactor;
holoLayer = 1.0 - (1.0 - holoLayer) * (1.0 - spectrumColor * diffOverlay);
// 高光叠加
float hlStrength = highlight * u_effectIntensity * 0.55 * ringFactor;
vec3 hlColor = mix(vec3(1.0), spectrumColor, 0.3);
holoLayer = mix(holoLayer, holoLayer + hlColor * hlStrength * 0.6, hlStrength);
// 划痕
float scratchFactor = scratch * ringFactor;
holoLayer = mix(holoLayer, holoLayer * (1.0 - scratchFactor * 3.0), step(0.001, scratchFactor));
// 珠光颗粒(安全区内 15% 微量泄露,模拟真实卡片边缘光泽渗透)
float sparkleRing = sparkle * (ringFactor * 0.85 + 0.15);
holoLayer += sparkleRing * spectrumColor * 1.2;
// ============================================================
// 边缘倒角光影 —— 不干扰人物主体的结构光影
// ============================================================
float edgeSoft = 0.0;
if (cornerRadiusPx > 0.0) {
float distToEdge = abs(sdf) / cornerRadiusPx;
edgeSoft = smoothstep(1.5, 6.0, distToEdge);
}
holoLayer = mix(holoLayer, holoLayer * (1.0 - edgeSoft * 0.25), step(0.001, edgeSoft));
vec2 lightDir2D = normalize(u_lightDir.xy);
float edgeHighlight = smoothstep(0.6, 2.0, abs(sdf) / max(cornerRadiusPx, 1.0))
* max(0.0, dot(normalize(pnNorm + vec2(0.001)), lightDir2D)) * 0.25;
holoLayer += edgeHighlight * u_effectIntensity * 0.3;
// ---- 暗角 ----
float vignette = 1.0 - pow(clamp(length(pnNorm) * 1.1, 0.0, 1.0), 2.8) * 0.35;
holoLayer *= vignette;
// ---- 输出 ----
float edgeAA = 1.0 - smoothstep(-1.5, 1.5, sdf);
float alpha = cornerMask * edgeAA;
holoLayer = clamp(holoLayer, 0.0, 1.0);
gl_FragColor = vec4(holoLayer, alpha);
}
`
/**
* 生成 1x256 光谱渐变纹理数据RGBA
*/
export function generateSpectrumRampData(size = 256) {
const data = new Uint8Array(size * 4)
const segments = [
{ pos: 0.0, r: 1.0, g: 0.0, b: 0.0 },
{ pos: 0.08, r: 1.0, g: 0.3, b: 0.0 },
{ pos: 0.17, r: 1.0, g: 0.85, b: 0.0 },
{ pos: 0.33, r: 0.0, g: 1.0, b: 0.15 },
{ pos: 0.50, r: 0.0, g: 0.85, b: 0.85 },
{ pos: 0.67, r: 0.0, g: 0.15, b: 1.0 },
{ pos: 0.83, r: 0.55, g: 0.0, b: 1.0 },
{ pos: 1.0, r: 1.0, g: 0.0, b: 0.1 },
]
for (let i = 0; i < size; i++) {
const t = i / size
let segIdx = 0
for (let s = 1; s < segments.length; s++) {
if (t <= segments[s].pos) { segIdx = s - 1; break }
}
if (t >= segments[segments.length - 1].pos) segIdx = segments.length - 2
const seg = segments[segIdx]
const next = segments[segIdx + 1]
const lt = (t - seg.pos) / (next.pos - seg.pos)
const idx = i * 4
data[idx] = Math.round((seg.r + (next.r - seg.r) * lt) * 255)
data[idx + 1] = Math.round((seg.g + (next.g - seg.g) * lt) * 255)
data[idx + 2] = Math.round((seg.b + (next.b - seg.b) * lt) * 255)
data[idx + 3] = 255
}
return data
}