323 lines
11 KiB
JavaScript
323 lines
11 KiB
JavaScript
/**
|
||
* 全息镭射卡 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
|
||
}
|