/** * 全息镭射卡 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 }