学习threejs,使用自定义GLSL 着色器,生成漂流的3D能量球

👨‍⚕️ 主页: gis分享者
👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅!
👨‍⚕️ 收录于专栏:threejs gis工程师


文章目录

  • 一、🍀前言
    • 1.1 ☘️GLSL着色器
      • 1.1.1 ☘️着色器类型
      • 1.1.2 ☘️工作原理
      • 1.1.3 ☘️核心特点
      • 1.1.4 ☘️应用场景
      • 1.1.5 ☘️实战示例
  • 二、🍀使用自定义GLSL 着色器,生成漂流的3D 能量球
    • 1. ☘️实现思路
    • 2. ☘️代码样例


一、🍀前言

本文详细介绍如何基于threejs在三维场景中自定义GLSL 着色器,生成漂流的3D 能量球,亲测可用。希望能帮助到您。一起学习,加油!加油!

1.1 ☘️GLSL着色器

GLSL(OpenGL Shading Language)是OpenGL的核心编程语言,用于编写图形渲染管线中可定制的计算逻辑。其核心设计目标是通过GPU并行计算实现高效的图形处理,支持从基础几何变换到复杂物理模拟的多样化需求。

1.1.1 ☘️着色器类型

顶点着色器(Vertex Shader)

  • 功能:处理每个顶点的坐标变换(如模型视图投影矩阵变换)、法线计算及顶点颜色传递。
  • 输出:裁剪空间坐标gl_Position,供后续光栅化阶段使用。

片段着色器(Fragment Shader)

  • 功能:计算每个像素的最终颜色,支持纹理采样、光照模型(如Phong、PBR)及后处理效果(如模糊、景深)。
  • 输出:像素颜色gl_FragColor或gl_FragColor(RGBA格式)。

计算着色器(Compute Shader,高级)

  • 功能:执行通用并行计算任务(如物理模拟、图像处理),不直接绑定渲染管线。
  • 特点:通过工作组(Work Group)实现高效数据并行处理。

1.1.2 ☘️工作原理

渲染管线流程

  • 顶点处理:CPU提交顶点数据(位置、颜色、纹理坐标),GPU并行执行顶点着色器处理每个顶点。
  • 光栅化:将顶点数据转换为像素片段,生成片段着色器输入。
  • 片段处理:GPU并行执行片段着色器计算每个像素颜色。
  • 输出合并:将片段颜色与帧缓冲区混合,生成最终图像。

数据流动

  • 顶点属性:通过glVertexAttribPointer传递位置、颜色等数据,索引由layout(location=N)指定。
  • Uniform变量:CPU通过glGetUniformLocation传递常量数据(如变换矩阵、时间),在渲染循环中更新。
  • 内置变量: gl_Position(顶点着色器输出):裁剪空间坐标。 gl_FragCoord(片段着色器输入):当前像素的窗口坐标。
    gl_FrontFacing(片段着色器输入):判断像素是否属于正面三角形。

1.1.3 ☘️核心特点

语法特性

  • C语言变体:支持条件语句、循环、函数等结构,天然适配图形算法。
  • 向量/矩阵运算:内置vec2/vec3/vec4及mat2/mat3/mat4类型,支持点乘、叉乘等操作。
  • 精度限定符:如precision mediump float,控制计算精度与性能平衡。

硬件加速

  • 并行计算:GPU数千个核心并行执行着色器代码,适合处理大规模数据(如粒子系统、体素渲染)。
  • 内存模型:支持常量内存(Uniform)、纹理内存(Sampler)及共享内存(计算着色器),优化数据访问效率。

灵活性

  • 可编程管线:完全替代固定渲染管线,支持自定义光照、阴影、后处理效果。
  • 跨平台兼容性:OpenGL ES(移动端)与WebGL(Web)均支持GLSL,代码可移植性强。

1.1.4 ☘️应用场景

游戏开发

  • 实时渲染:实现PBR材质、动态阴影、屏幕空间反射。
  • 特效系统:粒子火焰、流体模拟、布料物理。
  • 性能优化:通过计算着色器加速AI计算、碰撞检测。

数据可视化

  • 科学计算:将多维数据映射为颜色/高度图(如气象数据、流场可视化)。
  • 信息图表:动态生成3D柱状图、热力图,增强数据表现力。

艺术创作

  • 程序化生成:使用噪声函数(如Perlin、Simplex)生成地形、纹理。
  • 交互式装置:结合传感器数据实时修改着色器参数,创造动态艺术作品。

教育与研究

  • 算法实验:实时调试光线追踪、路径追踪算法。
  • 教学工具:可视化线性代数运算(如矩阵变换、向量投影)。

1.1.5 ☘️实战示例

顶点着色器(传递法线与世界坐标):

#version 330 core
layout(location=0) in vec3 aPos;
layout(location=1) in vec3 aNormal;
out vec3 FragPos;
out vec3 Normal;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main() {FragPos = vec3(model * vec4(aPos, 1.0));Normal = mat3(transpose(inverse(model))) * aNormal; // 模型空间到世界空间的法线变换gl_Position = projection * view * vec4(FragPos, 1.0);
}

片段着色器(实现Blinn-Phong光照):

#version 330 core
in vec3 FragPos;
in vec3 Normal;
out vec4 FragColor;
uniform vec3 lightPos;
uniform vec3 viewPos;
uniform vec3 lightColor;
uniform vec3 objectColor;
void main() {// 环境光vec3 ambient = 0.1 * lightColor;// 漫反射vec3 norm = normalize(Normal);vec3 lightDir = normalize(lightPos - FragPos);float diff = max(dot(norm, lightDir), 0.0);vec3 diffuse = diff * lightColor;// 镜面反射vec3 viewDir = normalize(viewPos - FragPos);vec3 reflectDir = reflect(-lightDir, norm);float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);vec3 specular = 0.5 * spec * lightColor;// 最终颜色vec3 result = (ambient + diffuse + specular) * objectColor;FragColor = vec4(result, 1.0);
}

官方文档

二、🍀使用自定义GLSL 着色器,生成漂流的3D 能量球

1. ☘️实现思路

使用自定义GLSL 着色器定义THREE.ShaderMaterial材质material,定义THREE.SphereGeometry球体使用material材质生成漂流的3D 能量球。具体代码参考代码样例。可以直接运行。

2. ☘️代码样例

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>3d能量球</title><style>body {margin: 0;overflow: hidden;background-color: #050510;font-family: sans-serif;}canvas {display: block;width: 100%;height: 100%;cursor: pointer;}#message-box {position: absolute;top: 10px;left: 10px;background-color: rgba(0, 0, 0, 0.7);color: white;padding: 10px 15px;border-radius: 5px;font-size: 14px;display: block;z-index: 10;pointer-events: none;}</style>
</head>
<body>
<div id="message-box">Click/Tap the bubble for energy waves. Drag to rotate.</div>
<script type="importmap">{"imports": {"three": "https://cdnjs.cloudflare.com/ajax/libs/three.js/0.163.0/three.module.min.js","three/addons/": "https://cdn.jsdelivr.net/npm/three@0.163.0/examples/jsm/"}}</script>
<script type="module">import * as THREE from "three";import { OrbitControls } from "three/addons/controls/OrbitControls.js";import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";import { RenderPass } from "three/addons/postprocessing/RenderPass.js";import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";let scene, camera, renderer, bubble, innerCore, emissionBubble, clock, controls, particles;let composer;const raycaster = new THREE.Raycaster();const mouse = new THREE.Vector2();let isHovering = false;const surfaceWaves = [];const maxWaves = 5;const lightningBranches = [];const maxBranches = 15;const particleCount = 5000;let originalParticlePositions;const simplexNoise3D = `vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }float snoise(vec3 v) {const vec2 C = vec2(1.0/6.0, 1.0/3.0); const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);vec3 i = floor(v + dot(v, C.yyy)); vec3 x0 = v - i + dot(i, C.xxx);vec3 g = step(x0.yzx, x0.xyz); vec3 l = 1.0 - g; vec3 i1 = min(g.xyz, l.zxy); vec3 i2 = max(g.xyz, l.zxy);vec3 x1 = x0 - i1 + C.xxx; vec3 x2 = x0 - i2 + C.yyy; vec3 x3 = x0 - D.yyy;i = mod289(i);vec4 p = permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + vec4(0.0, i1.y, i2.y, 1.0)) + i.x + vec4(0.0, i1.x, i2.x, 1.0));float n_ = 0.142857142857; vec3 ns = n_ * D.wyz - D.xzx;vec4 j = p - 49.0 * floor(p * ns.z * ns.z);vec4 x_ = floor(j * ns.z); vec4 y_ = floor(j - 7.0 * x_);vec4 x = x_ *ns.x + ns.yyyy; vec4 y = y_ *ns.x + ns.yyyy; vec4 h = 1.0 - abs(x) - abs(y);vec4 b0 = vec4(x.xy, y.xy); vec4 b1 = vec4(x.zw, y.zw);vec4 s0 = floor(b0)*2.0 + 1.0; vec4 s1 = floor(b1)*2.0 + 1.0; vec4 sh = -step(h, vec4(0.0));vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy; vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww;vec3 p0 = vec3(a0.xy,h.x); vec3 p1 = vec3(a0.zw,h.y); vec3 p2 = vec3(a1.xy,h.z); vec3 p3 = vec3(a1.zw,h.w);vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w;vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); m = m * m;return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));}`;const simplexNoise2D = `vec2 mod289_2d(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }vec3 mod289_3d(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }vec3 permute_3d(vec3 x) { return mod289_3d(((x*34.0)+1.0)*x); }float snoise2d(vec2 v) {const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439);vec2 i = floor(v + dot(v, C.yy)); vec2 x0 = v - i + dot(i, C.xx);vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);vec4 x12 = x0.xyxy + C.xxzz; x12.xy -= i1;i = mod289_2d(i);vec3 p = permute_3d(permute_3d(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0));vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0); m = m*m; m = m*m;vec3 x = 2.0 * fract(p * C.www) - 1.0; vec3 h = abs(x) - 0.5; vec3 ox = floor(x + 0.5); vec3 a0 = x - ox;m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);vec3 g; g.x = a0.x * x0.x + h.x * x0.y; g.yz = a0.yz * x12.xz + h.yz * x12.yw;return 130.0 * dot(m, g);}`;function init() {scene = new THREE.Scene();clock = new THREE.Clock();camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);camera.position.z = 7;renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });renderer.setSize(window.innerWidth, window.innerHeight);renderer.setPixelRatio(window.devicePixelRatio);renderer.toneMapping = THREE.ACESFilmicToneMapping;renderer.toneMappingExposure = 1.2;document.body.appendChild(renderer.domElement);controls = new OrbitControls(camera, renderer.domElement);controls.enableDamping = true;controls.dampingFactor = 0.05;controls.screenSpacePanning = false;controls.minDistance = 3;controls.maxDistance = 25;controls.autoRotate = true;controls.autoRotateSpeed = 0.15;const ambientLight = new THREE.AmbientLight(0xffffff, 0.7);scene.add(ambientLight);const directionalLight = new THREE.DirectionalLight(0xffffff, 1.8);directionalLight.position.set(5, 7, 5).normalize();scene.add(directionalLight);const pointLight = new THREE.PointLight(0xffccaa, 1.2, 150);pointLight.position.set(-6, 4, -4);scene.add(pointLight);const cubeTextureLoader = new THREE.CubeTextureLoader();const environmentMap = cubeTextureLoader.load(["https://threejs.org/examples/textures/cube/Park3Med/px.jpg","https://threejs.org/examples/textures/cube/Park3Med/nx.jpg","https://threejs.org/examples/textures/cube/Park3Med/py.jpg","https://threejs.org/examples/textures/cube/Park3Med/ny.jpg","https://threejs.org/examples/textures/cube/Park3Med/pz.jpg","https://threejs.org/examples/textures/cube/Park3Med/nz.jpg",],() => {scene.background = environmentMap;scene.environment = environmentMap;if (bubble) {bubble.material.uniforms.envMap.value = environmentMap;bubble.material.needsUpdate = true;}},undefined,(error) => {console.error("Error loading environment map:", error);scene.background = new THREE.Color(0x15151a);const fallbackEnvMap = new THREE.CubeTexture();scene.environment = fallbackEnvMap;if (bubble) {bubble.material.uniforms.envMap.value = fallbackEnvMap;bubble.material.needsUpdate = true;}});scene.background = new THREE.Color(0x15151a);const bubbleVertexShader = `uniform float time;uniform vec2 waveOrigins[${maxWaves}];uniform float waveStartTimes[${maxWaves}];uniform float waveSpeeds[${maxWaves}];uniform float waveAmplitudes[${maxWaves}];varying vec3 vNormal;varying vec3 vWorldNormal;varying vec3 vPosition;varying vec2 vUv;varying vec3 vViewPosition;varying float vWaveIntensity;${simplexNoise3D}void main() {vUv = uv;float noiseScale1=0.8; float noiseScale2=1.8; float noiseScale3=3.2;float baseWobbleAmp=0.12; float mediumWobbleAmp=0.06; float rippleAmp=0.03;vec3 baseWobblePos = position * noiseScale1 + vec3(time*0.15, time*0.12, time*0.20);float baseWobble = snoise(baseWobblePos) * baseWobbleAmp;vec3 mediumWobblePos = position * noiseScale2 + vec3(time*0.3, time*0.4, time*0.25);float mediumWobble = snoise(mediumWobblePos) * mediumWobbleAmp;vec3 ripplePos = position * noiseScale3 + vec3(time*0.6, time*0.7, time*0.5);float ripple = snoise(ripplePos) * rippleAmp;float deformation = baseWobble + mediumWobble + ripple;float totalWaveDeformation = 0.0; vWaveIntensity = 0.0;for(int i=0; i<${maxWaves}; i++) {if(waveStartTimes[i] > 0.0) {float waveTime = time - waveStartTimes[i];if(waveTime > 0.0 && waveTime < 2.0) {float dist = distance(uv, waveOrigins[i]);float waveRadius = waveTime * waveSpeeds[i];float waveFalloff = exp(-waveTime * 2.0);float waveWidth = 0.1;float wave = exp(-pow((dist - waveRadius) / waveWidth, 2.0)) * waveFalloff;totalWaveDeformation += wave * waveAmplitudes[i] * sin(dist * 30.0 - waveTime * 15.0);vWaveIntensity += wave * waveFalloff;} } }deformation += totalWaveDeformation * 0.2;vec3 deformedNormal = normalize(normal);vec3 newPosition = position + deformedNormal * deformation;vec4 worldPosition = modelMatrix * vec4(newPosition, 1.0);vPosition = worldPosition.xyz;vWorldNormal = normalize(mat3(modelMatrix) * deformedNormal);vNormal = normalize(normalMatrix * deformedNormal);vec4 mvPosition = modelViewMatrix * vec4(newPosition, 1.0);vViewPosition = -mvPosition.xyz;gl_Position = projectionMatrix * mvPosition;}`;const bubbleFragmentShader = `uniform samplerCube envMap; uniform float time; uniform float aberrationStrength;uniform float iridescenceIntensity; uniform float u_hoverIntensity;uniform vec2 u_crackleOriginUV; uniform float u_crackleStartTime; uniform float u_crackleDuration;uniform vec3 u_crackleColor; uniform float u_crackleIntensity; uniform float u_crackleScale;uniform float u_crackleSpeed; uniform float u_volumetricIntensity;uniform vec2 u_branchOrigins[${maxBranches}]; uniform vec2 u_branchEnds[${maxBranches}];uniform float u_branchStartTimes[${maxBranches}]; uniform float u_branchIntensities[${maxBranches}];varying vec3 vNormal; varying vec3 vWorldNormal; varying vec3 vPosition;varying vec2 vUv; varying vec3 vViewPosition; varying float vWaveIntensity;${simplexNoise2D}float cracklePattern(vec2 uv, float scale, float timeOffset) {float flowNoise = snoise2d(uv * scale * 0.3 + vec2(timeOffset * 0.2));vec2 flowDirection = vec2(cos(flowNoise * 2.0), sin(flowNoise * 2.0));vec2 flowUV = uv + flowDirection * 0.02;float n1 = snoise2d(flowUV * scale);float n2 = snoise2d(flowUV * scale * 1.5 + vec2(timeOffset * 0.3));float ridge1 = 1.0 - abs(n1);float ridge2 = 1.0 - abs(n2 * 0.7);float pattern = max(ridge1, ridge2);pattern = smoothstep(0.85, 0.9, pattern);float branches = abs(snoise2d(flowUV * scale * 3.0 - timeOffset));branches = smoothstep(0.98, 0.99, branches);pattern = max(pattern, branches * 0.5);return smoothstep(0.4, 0.6, pattern);}float lightningBranch(vec2 uv, vec2 start, vec2 end, float thickness, float time) {vec2 dir=end-start; float len=length(dir); if(len==0.0) return 0.0; vec2 norm=dir/len; vec2 perp=vec2(-norm.y,norm.x);vec2 toPoint=uv-start; float alongLine=dot(toPoint,norm); float perpDist=abs(dot(toPoint,perp));if(alongLine<0.0||alongLine>len) return 0.0; float noiseOffset=snoise2d(vec2(alongLine*10.0,time*3.0))*0.02;perpDist-=noiseOffset; float intensity=exp(-perpDist*perpDist/(thickness*thickness)); return intensity; }void main() {vec3 viewDirection=normalize(vViewPosition); vec3 normal=normalize(vNormal); vec3 worldNormal=normalize(vWorldNormal);vec3 worldViewDir=normalize(cameraPosition-vPosition); vec3 reflectDir=reflect(-worldViewDir,worldNormal);float iorRatio=1.0/1.33; vec3 refractDirBase=refract(-worldViewDir,worldNormal,iorRatio);vec3 aberrationOffset=worldNormal*aberrationStrength*0.05; vec3 refractDirR=normalize(refractDirBase+aberrationOffset);vec3 refractDirG=refractDirBase; vec3 refractDirB=normalize(refractDirBase-aberrationOffset);float refractR=textureCube(envMap,refractDirR).r; float refractG=textureCube(envMap,refractDirG).g; float refractB=textureCube(envMap,refractDirB).b;vec3 refractedColorAberrated=vec3(refractR,refractG,refractB); vec4 reflectColor=textureCube(envMap,reflectDir);float fresnelPower=4.0; float fresnelBase=0.06; float fresnel=fresnelBase+(1.0-fresnelBase)*pow(1.0-max(0.0,dot(viewDirection,normal)),fresnelPower);fresnel=clamp(fresnel,0.0,1.0); float noiseScale=3.5; float n1=snoise2d(vUv*noiseScale+vec2(time*0.05))*0.5+0.5;float n2=snoise2d(vUv*noiseScale*1.5+vec2(time*0.08+50.0))*0.5+0.5; float thicknessNoise=n1*n2;float baseFilmThickness=350.0; float filmThicknessRange=450.0; float filmThickness=baseFilmThickness+thicknessNoise*filmThicknessRange;vec3 wavelengths=vec3(700.0,530.0,440.0); vec3 interference=vec3(sin(filmThickness/wavelengths.r*20.0+time*0.5)*0.5+0.5,sin(filmThickness/wavelengths.g*20.0+time*0.6)*0.5+0.5, sin(filmThickness/wavelengths.b*20.0+time*0.7)*0.5+0.5);interference=pow(interference,vec3(1.5)); vec3 combinedColor=mix(refractedColorAberrated,reflectColor.rgb,fresnel);combinedColor=mix(combinedColor,combinedColor*interference,iridescenceIntensity); float rimPower=3.0; float rimAmount=0.7;float rim=rimAmount*pow(1.0-max(0.0,dot(viewDirection,normal)),rimPower); combinedColor+=vec3(rim*(0.8+u_hoverIntensity*0.4));float crackleEmissionStrength=0.0; if(u_crackleStartTime>0.0){float crackleTime=time-u_crackleStartTime;if(crackleTime>=0.0&&crackleTime<u_crackleDuration){float dist=distance(vUv,u_crackleOriginUV)*10.0; float currentRadius=crackleTime*u_crackleSpeed; if(dist<currentRadius){float timeProgress=crackleTime/u_crackleDuration; float timeFalloff=smoothstep(1.0,0.5,timeProgress);float patternValue=cracklePattern(vUv,u_crackleScale,time*0.8);float distMask=smoothstep(currentRadius,currentRadius*0.5,dist);float depth=length(vViewPosition); float volumetricFactor=1.0+u_volumetricIntensity*(1.0-exp(-depth*0.1));crackleEmissionStrength=patternValue*u_crackleIntensity*timeFalloff*distMask*volumetricFactor;}}}float lightningEmissionStrength=0.0; for(int i=0;i<${maxBranches};i++){if(u_branchStartTimes[i]>0.0){float branchTime=time-u_branchStartTimes[i];float branchDuration=0.5; if(branchTime>0.0&&branchTime<branchDuration){float branchProgress=branchTime/branchDuration;float branchFade=smoothstep(1.0,0.0,branchProgress); float branchIntensity=lightningBranch(vUv,u_branchOrigins[i],u_branchEnds[i],0.005,time);lightningEmissionStrength+=branchIntensity*u_branchIntensities[i]*branchFade*2.0;}}}vec3 waveGlow = u_crackleColor * vWaveIntensity * 0.2;float patternOnly = step(0.7, crackleEmissionStrength + lightningEmissionStrength);combinedColor += u_crackleColor * (crackleEmissionStrength + lightningEmissionStrength) * 0.05 * patternOnly + waveGlow;float baseAlpha=0.4; float finalAlpha=mix(baseAlpha*0.5,baseAlpha,fresnel);finalAlpha=clamp(finalAlpha+rim*0.1+(crackleEmissionStrength+lightningEmissionStrength)*0.1+vWaveIntensity*0.2,0.0,1.0);gl_FragColor=vec4(combinedColor,finalAlpha);}`;const emissionOnlyFragmentShader = `uniform float time; uniform vec2 u_crackleOriginUV; uniform float u_crackleStartTime;uniform float u_crackleDuration; uniform vec3 u_crackleColor; uniform float u_crackleIntensity;uniform float u_crackleScale; uniform float u_crackleSpeed; uniform float u_volumetricIntensity;uniform vec2 u_branchOrigins[${maxBranches}]; uniform vec2 u_branchEnds[${maxBranches}];uniform float u_branchStartTimes[${maxBranches}]; uniform float u_branchIntensities[${maxBranches}];varying vec2 vUv; varying vec3 vViewPosition;${simplexNoise2D}float cracklePattern(vec2 uv, float scale, float timeOffset) {float n1=snoise2d(uv*scale+vec2(timeOffset*0.5));float n2=snoise2d(uv*scale*2.1+vec2(-timeOffset*0.3,timeOffset*0.4)+10.0);float n3=snoise2d(uv*scale*0.8+vec2(timeOffset*0.2,-timeOffset*0.6)-5.0);float combined=abs(n1*0.5+n2*0.3+n3*0.2);float pattern=pow(1.0-combined,40.0);float sparks=snoise2d(uv*scale*5.0+timeOffset*2.0);pattern+=pow(max(0.0,sparks),40.0)*0.1;pattern = step(0.95, pattern);return pattern;}float lightningBranch(vec2 uv, vec2 start, vec2 end, float thickness, float time) {vec2 dir=end-start; float len=length(dir); if(len==0.0) return 0.0; vec2 norm=dir/len; vec2 perp=vec2(-norm.y,norm.x);vec2 toPoint=uv-start; float alongLine=dot(toPoint,norm); float perpDist=abs(dot(toPoint,perp));if(alongLine<0.0||alongLine>len) return 0.0; float noiseOffset=snoise2d(vec2(alongLine*10.0,time*3.0))*0.02;perpDist-=noiseOffset; float intensity=exp(-perpDist*perpDist/(thickness*thickness)); return intensity; }void main() {float crackleEmissionStrength=0.0; if(u_crackleStartTime>0.0){float crackleTime=time-u_crackleStartTime;if(crackleTime>=0.0&&crackleTime<u_crackleDuration){float dist=distance(vUv,u_crackleOriginUV)*10.0; float currentRadius=crackleTime*u_crackleSpeed; if(dist<currentRadius){float timeProgress=crackleTime/u_crackleDuration; float timeFalloff=smoothstep(1.0,0.5,timeProgress);float patternValue=cracklePattern(vUv,u_crackleScale,time*0.8);float distMask=smoothstep(currentRadius,currentRadius*0.5,dist);float depth=length(vViewPosition); float volumetricFactor=1.0+u_volumetricIntensity*(1.0-exp(-depth*0.1));crackleEmissionStrength=patternValue*u_crackleIntensity*timeFalloff*distMask*volumetricFactor;}}}float lightningEmissionStrength=0.0; for(int i=0;i<${maxBranches};i++){if(u_branchStartTimes[i]>0.0){float branchTime=time-u_branchStartTimes[i];float branchDuration=0.5; if(branchTime>0.0&&branchTime<branchDuration){float branchProgress=branchTime/branchDuration;float branchFade=smoothstep(1.0,0.0,branchProgress); float branchIntensity=lightningBranch(vUv,u_branchOrigins[i],u_branchEnds[i],0.005,time);lightningEmissionStrength+=branchIntensity*u_branchIntensities[i]*branchFade*2.0;}}}float totalEmissionStrength = crackleEmissionStrength + lightningEmissionStrength;float emissionBoost = 8.0;vec3 finalColor = u_crackleColor * totalEmissionStrength * emissionBoost;gl_FragColor = vec4(finalColor, step(0.9, totalEmissionStrength));}`;const coreVertexShader = `uniform float time; uniform float noiseScale; uniform float noiseAmplitude; varying float vNoise;${simplexNoise3D}void main() {float noise=snoise(position*noiseScale+vec3(time*0.3)); vNoise=noise;vec3 displacedPosition=position+normal*noise*noiseAmplitude; gl_Position=projectionMatrix*modelViewMatrix*vec4(displacedPosition,1.0);}`;const coreFragmentShader = `uniform float time; uniform vec3 baseColor; uniform float opacityFactor; varying float vNoise;void main() {float colorIntensity=smoothstep(-1.0,1.0,vNoise)*0.6+0.8; vec3 dynamicColor=baseColor*colorIntensity;float pulse=sin(time*2.5+vNoise*2.0)*0.5+0.5; float noiseOpacity=smoothstep(-0.6,0.2,vNoise);float finalOpacity=noiseOpacity*pulse*opacityFactor; gl_FragColor=vec4(dynamicColor,finalOpacity);}`;const particleData = createReactiveParticleSystem();particles = particleData.particles;originalParticlePositions = particleData.originalPositions;scene.add(particles);const bubbleGeometry = new THREE.SphereGeometry(2, 128, 128);const bubbleMaterial = new THREE.ShaderMaterial({vertexShader: bubbleVertexShader,fragmentShader: bubbleFragmentShader,uniforms: THREE.UniformsUtils.clone({envMap: { value: scene.environment || new THREE.CubeTexture() },time: { value: 0 },aberrationStrength: { value: 0.8 },iridescenceIntensity: { value: 0.6 },u_hoverIntensity: { value: 0.0 },u_crackleOriginUV: { value: new THREE.Vector2(0.5, 0.5) },u_crackleStartTime: { value: -1.0 },u_crackleDuration: { value: 1.5 },u_crackleColor: { value: new THREE.Color(0.9, 0.95, 1.0) },u_crackleIntensity: { value: 1.5 },u_crackleScale: { value: 25.0 },u_crackleSpeed: { value: 8.0 },u_volumetricIntensity: { value: 0.05 },waveOrigins: {value: Array(maxWaves).fill().map(() => new THREE.Vector2(0, 0)),},waveStartTimes: { value: Array(maxWaves).fill(-1) },waveSpeeds: { value: Array(maxWaves).fill(1.0) },waveAmplitudes: { value: Array(maxWaves).fill(0.1) },u_branchOrigins: {value: Array(maxBranches).fill().map(() => new THREE.Vector2(0, 0)),},u_branchEnds: {value: Array(maxBranches).fill().map(() => new THREE.Vector2(0, 0)),},u_branchStartTimes: { value: Array(maxBranches).fill(-1) },u_branchIntensities: { value: Array(maxBranches).fill(1.0) },}),transparent: true,side: THREE.DoubleSide,depthWrite: false,});bubble = new THREE.Mesh(bubbleGeometry, bubbleMaterial);scene.add(bubble);const emissionOnlyMaterial = new THREE.ShaderMaterial({vertexShader: bubbleVertexShader,fragmentShader: emissionOnlyFragmentShader,uniforms: bubbleMaterial.uniforms,transparent: true,blending: THREE.AdditiveBlending,depthWrite: false,});emissionBubble = new THREE.Mesh(bubbleGeometry, emissionOnlyMaterial);scene.add(emissionBubble);const coreGeometry = new THREE.SphereGeometry(0.6, 64, 64);const coreMaterial = new THREE.ShaderMaterial({vertexShader: coreVertexShader,fragmentShader: coreFragmentShader,uniforms: {time: { value: 0.0 },noiseScale: { value: 2.5 },noiseAmplitude: { value: 0.25 },baseColor: { value: new THREE.Color(0x99bbff) },opacityFactor: { value: 0.85 },},transparent: true,blending: THREE.AdditiveBlending,depthWrite: false,});innerCore = new THREE.Mesh(coreGeometry, coreMaterial);scene.add(innerCore);setupPostProcessing();window.addEventListener("resize", onWindowResize);renderer.domElement.addEventListener("mousedown", onMouseDown);renderer.domElement.addEventListener("mousemove", onMouseMove);}function createParticleTexture() {const canvas = document.createElement("canvas");canvas.width = 64;canvas.height = 64;const context = canvas.getContext("2d");const gradient = context.createRadialGradient(32, 32, 0, 32, 32, 32);gradient.addColorStop(0, "rgba(255,255,255,1)");gradient.addColorStop(0.2, "rgba(255,255,255,0.8)");gradient.addColorStop(0.6, "rgba(200,200,255,0.4)");gradient.addColorStop(1, "rgba(150,150,255,0)");context.fillStyle = gradient;context.fillRect(0, 0, 64, 64);return new THREE.CanvasTexture(canvas);}function createReactiveParticleSystem() {const positions = new Float32Array(particleCount * 3);const colors = new Float32Array(particleCount * 3);const velocities = new Float32Array(particleCount * 3);const radius = 15;for (let i = 0; i < particleCount; i++) {const i3 = i * 3;const u = Math.random();const v = Math.random();const theta = u * 2.0 * Math.PI;const phi = Math.acos(2.0 * v - 1.0);const r = Math.cbrt(Math.random()) * radius;positions[i3] = r * Math.sin(phi) * Math.cos(theta);positions[i3 + 1] = r * Math.sin(phi) * Math.sin(theta);positions[i3 + 2] = r * Math.cos(phi);const colorVariance = Math.random() * 0.3;colors[i3] = 1.0 - colorVariance * 0.5;colors[i3 + 1] = 1.0 - colorVariance * 0.5;colors[i3 + 2] = 1.0;velocities[i3] = (Math.random() - 0.5) * 0.02;velocities[i3 + 1] = (Math.random() - 0.5) * 0.02;velocities[i3 + 2] = (Math.random() - 0.5) * 0.02;}const particleGeometry = new THREE.BufferGeometry();particleGeometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));particleGeometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));const particleTexture = createParticleTexture();const particleMaterial = new THREE.PointsMaterial({size: 0.12,map: particleTexture,vertexColors: true,transparent: true,opacity: 0.7,blending: THREE.AdditiveBlending,depthWrite: false,sizeAttenuation: true,});const particles = new THREE.Points(particleGeometry, particleMaterial);particles.userData.velocities = velocities;return { particles: particles, originalPositions: new Float32Array(positions) };}function setupPostProcessing() {composer = new EffectComposer(renderer);const renderPass = new RenderPass(scene, camera);composer.addPass(renderPass);const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.1, 0.3, 0.97);composer.addPass(bloomPass);const colorGradingShader = {uniforms: { tDiffuse: { value: null }, contrast: { value: 1.15 }, brightness: { value: 0.03 }, saturation: { value: 1.2 } },vertexShader: `varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`,fragmentShader: ` uniform sampler2D tDiffuse; uniform float contrast; uniform float brightness; uniform float saturation; varying vec2 vUv;vec3 adjustSaturation(vec3 color, float adjustment) { vec3 gray = vec3(dot(color, vec3(0.299, 0.587, 0.114))); return mix(gray, color, adjustment); }void main() { vec4 color = texture2D(tDiffuse, vUv); color.rgb = adjustSaturation(color.rgb, saturation); color.rgb = (color.rgb - 0.5) * contrast + 0.5 + brightness; gl_FragColor = clamp(color, 0.0, 1.0); }`,};const colorGradingPass = new ShaderPass(colorGradingShader);composer.addPass(colorGradingPass);}function onMouseDown(event) {mouse.x = (event.clientX / window.innerWidth) * 2 - 1;mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;raycaster.setFromCamera(mouse, camera);const intersects = raycaster.intersectObject(bubble);if (intersects.length > 0) {const intersection = intersects[0];const uv = intersection.uv;bubble.material.uniforms.u_crackleOriginUV.value.copy(uv);bubble.material.uniforms.u_crackleStartTime.value = clock.getElapsedTime();addSurfaceWave(uv);generateLightningBranches(uv);}}function onMouseMove(event) {mouse.x = (event.clientX / window.innerWidth) * 2 - 1;mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;raycaster.setFromCamera(mouse, camera);const intersects = raycaster.intersectObject(bubble);isHovering = intersects.length > 0;}function onWindowResize() {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);composer.setSize(window.innerWidth, window.innerHeight);}function addSurfaceWave(uv) {const waveIndex = surfaceWaves.length % maxWaves;const uniforms = bubble.material.uniforms;uniforms.waveOrigins.value[waveIndex].copy(uv);uniforms.waveStartTimes.value[waveIndex] = clock.getElapsedTime();uniforms.waveSpeeds.value[waveIndex] = 0.8 + Math.random() * 0.4;uniforms.waveAmplitudes.value[waveIndex] = 0.08 + Math.random() * 0.04;surfaceWaves.push({ index: waveIndex, startTime: clock.getElapsedTime() });}function generateLightningBranches(origin) {const branchCount = 1 + Math.floor(Math.random() * 3);const uniforms = bubble.material.uniforms;for (let i = 0; i < branchCount; i++) {const branchIndex = lightningBranches.length % maxBranches;const angle = Math.random() * Math.PI * 2;const length = 0.1 + Math.random() * 0.3;uniforms.u_branchOrigins.value[branchIndex].copy(origin);uniforms.u_branchEnds.value[branchIndex].set(origin.x + Math.cos(angle) * length, origin.y + Math.sin(angle) * length);uniforms.u_branchStartTimes.value[branchIndex] = clock.getElapsedTime() + Math.random() * 0.2;uniforms.u_branchIntensities.value[branchIndex] = 0.5 + Math.random() * 0.5;lightningBranches.push({ index: branchIndex });}}function updateParticles(time, deltaTime) {const positions = particles.geometry.attributes.position.array;const velocities = particles.userData.velocities;const bubblePosition = bubble.position;for (let i = 0; i < particleCount; i++) {const i3 = i * 3;let x = positions[i3];let y = positions[i3 + 1];let z = positions[i3 + 2];const dx = x - bubblePosition.x;const dy = y - bubblePosition.y;const dz = z - bubblePosition.z;const distSq = dx * dx + dy * dy + dz * dz;const dist = Math.sqrt(distSq);if (bubble.material.uniforms.u_crackleStartTime.value > 0) {const crackleTime = time - bubble.material.uniforms.u_crackleStartTime.value;if (crackleTime > 0 && crackleTime < 1.5 && dist > 0) {const repelForce = 0.5 * (1 - crackleTime / 1.5);const invDist = 1.0 / dist;velocities[i3] += dx * invDist * repelForce * deltaTime;velocities[i3 + 1] += dy * invDist * repelForce * deltaTime;velocities[i3 + 2] += dz * invDist * repelForce * deltaTime;}}const attractionForce = 0.1;if (dist > 3 && dist > 0) {const invDist = 1.0 / dist;velocities[i3] -= dx * invDist * attractionForce * deltaTime;velocities[i3 + 1] -= dy * invDist * attractionForce * deltaTime;velocities[i3 + 2] -= dz * invDist * attractionForce * deltaTime;}positions[i3] += velocities[i3];positions[i3 + 1] += velocities[i3 + 1];positions[i3 + 2] += velocities[i3 + 2];velocities[i3] *= 0.98;velocities[i3 + 1] *= 0.98;velocities[i3 + 2] *= 0.98;}particles.geometry.attributes.position.needsUpdate = true;}function animate() {requestAnimationFrame(animate);const elapsedTime = clock.getElapsedTime();const deltaTime = clock.getDelta();if (bubble) {bubble.material.uniforms.time.value = elapsedTime;const targetHover = isHovering ? 1.0 : 0.0;bubble.material.uniforms.u_hoverIntensity.value += (targetHover - bubble.material.uniforms.u_hoverIntensity.value) * 0.1;}if (innerCore) {innerCore.material.uniforms.time.value = elapsedTime;}updateParticles(elapsedTime, deltaTime);controls.update();composer.render();}window.onload = () => {try {init();animate();setTimeout(() => {const msgBox = document.getElementById("message-box");if (msgBox) msgBox.style.display = "none";}, 5000);} catch (error) {console.error("Initialization or Animation Error:", error);const msgBox = document.getElementById("message-box");if (msgBox) {msgBox.textContent = "Error initializing simulation. Check console.";msgBox.style.backgroundColor = "red";msgBox.style.display = "block";}}};
</script>
</body>
</html>

效果如下
在这里插入图片描述
参考:源码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/88636.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/88636.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

分布式推客系统全栈开发指南:SpringCloud+Neo4j+Redis实战解析

一、推客系统概述与市场背景推客系统&#xff08;或称"推荐客"系统&#xff09;是一种基于社交关系和内容分发的推荐营销平台&#xff0c;近年来在电商、内容平台和社交媒体领域迅速崛起。根据最新统计数据&#xff0c;2023年全球社交电商市场规模已达1.2万亿美元&am…

Redis数据类型之list

上篇文章&#xff1a; Redis数据类型之hashhttps://blog.csdn.net/sniper_fandc/article/details/149139615?fromshareblogdetail&sharetypeblogdetail&sharerId149139615&sharereferPC&sharesourcesniper_fandc&sharefromfrom_link 目录 1 lpush、lpu…

在 Windows 上安装和配置 Kafka

消息代理是一种软件&#xff0c;充当在不同应用程序之间发送消息的中介。它的功能类似于服务器&#xff0c;从一个应用程序&#xff08;称为生产者&#xff09;接收消息&#xff0c;并将其路由到一个或多个其他应用程序&#xff08;称为消费者&#xff09;。消息代理的主要目的…

FPGA实现SDI转LVDS视频发送,基于GTP+OSERDES2原语架构,提供工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐我已有的所有工程源码总目录----方便你快速找到自己喜欢的项目本博已有的 SDI 编解码方案FPGA实现LVDS视频收发方案 3、工程详细设计方案工程设计原理框图SDI 输入设备Gv8601a 均衡器GTP 高速接口-->解串SMPTE SD/HD/3G SDI IP…

uniapp+vue3项目实现:H5的文件预览、文件下载功能(文章参考)

uniappvue3项目实现&#xff1a;H5的文件预览、文件下载功能&#xff08;文章参考&#xff09; 文章参考&#xff1a; uniapp的移动端h5实现文件下载兼容手机各版本浏览器 uni-app之微信小程序实现‘下载保存至本地预览’功能 uniapp&#xff1a;h5和微信小程序文件下载方式

汽车功能安全-软件单元验证 (Software Unit Verification)【定义、目的、要求建议】6

文章目录1 软件单元验证 (Software Unit Verification)2 ISO 26262-6对单元验证的实施要求和建议2.1 要求和建议2.2 通俗易懂的解释与总结2.3 示例2.3.1 场景1&#xff1a;电动助力转向系统 (EPS)2.3.2 场景2&#xff1a;自动紧急制动系统 (AEB)2.3.3 示例模型验证2.4 核心要点…

提示工程:突破Transformer极限的计算科学

Why Prompt Design Matters and Works: A Complexity Analysis of Prompt Search Space in LLMs 提示工程如何从经验技巧升级为系统科学 一、Transformer的先天缺陷:计算深度固化与信息丢失 原理 Transformer架构的计算能力存在固有局限: 计算深度固化:其隐状态仅在层间…

【2025/07/11】GitHub 今日热门项目

GitHub 今日热门项目 &#x1f680; 每日精选优质开源项目 | 发现优质开源项目&#xff0c;跟上技术发展趋势 &#x1f4cb; 报告概览 &#x1f4ca; 统计项&#x1f4c8; 数值&#x1f4dd; 说明&#x1f4c5; 报告日期2025-07-11 (周五)GitHub Trending 每日快照&#x1f55…

LeetCode 278. 第一个错误的版本

LeetCode 278. 第一个错误的版本 解析 这个问题要求找到第一个错误的版本&#xff0c;其中给定一个 API isBadVersion(version) 可以判断某个版本是否错误。由于版本号是有序的&#xff0c;且错误版本之后的所有版本都是错误的&#xff0c;因此可以使用二分查找高效地定位第一个…

【RK3568+PG2L50H开发板实验例程】FPGA部分 | Pango 的时钟资源——锁相环

本原创文章由深圳市小眼睛科技有限公司创作&#xff0c;版权归本公司所有&#xff0c;如需转载&#xff0c;需授权并注明出处&#xff08;www.meyesemi.com) 1.实验简介 实验目的&#xff1a; 了解 PLL IP 的基本使用方法。 实验环境&#xff1a; Window11 PDS2022.2-SP6.4…

Graph Contrastive Learning with Generative Adversarial Network基于生成对抗网络的图对比学习

1. 什么是图&#xff1f;&#xff08;Graph&#xff09;想象一下社交网络&#xff0c;每个人是一个“点”&#xff08;节点&#xff09;&#xff0c;他们之间的朋友关系是“线”&#xff08;边&#xff09;。这样的点和线组成的结构就是“图”。在计算机科学中&#xff0c;图被…

PyTorch中的torch.argmax()和torch.max()区别

在PyTorch中&#xff0c;torch.argmax()和torch.max()都是针对张量操作的函数&#xff0c;但它们的核心区别在于返回值的类型和用途&#xff1a;1. torch.argmax() 作用&#xff1a;仅返回张量中最大值所在的索引位置&#xff08;下标&#xff09;。返回值&#xff1a;一个整数…

WebSocket主从服务器架构完整教程

目录 1. 前言:为什么要学习WebSocket主从架构 第一章:基础知识准备 2.1 什么是WebSocket 生活中的例子 技术特点 2.2 WebSocket vs HTTP 什么时候用WebSocket? 2.3 什么是主从架构 生活中的例子 技术架构图 2.4 环境准备 需要的软件 项目结构 第二章:WebSock…

Java的extends通配符

在Java泛型中&#xff0c;extends通配符用于限定泛型类型的上界&#xff0c;即指定泛型可以是某个类型或其子类型。它有两种常见用法&#xff1a;类型参数限定和通配符限定&#xff0c;下面详细介绍&#xff1a; 1. 类型参数限定&#xff08;在类/方法定义中&#xff09; 在定义…

vue自定义提示框组件

不想要elementui的消息提示&#xff0c;自定义一个组件系统统一使用 一、写页面 vue &#xff08;我放的目录是src/plugins/message.vue&#xff09;&#xff08;这里面使用elementui 里面icon 需要单独引入&#xff09; <template><Transition name"down"&…

自动驾驶数据集综述:统计特征、标注质量与未来展望

自动驾驶数据集综述&#xff1a;统计特征、标注质量与未来展望 A Survey on Autonomous Driving Datasets: Statistics, Annotation Quality, and a Future Outlook 得益于硬件和深度学习技术的快速进步&#xff0c;自动驾驶近年来迅速发展并展现出良好的性能。高质量的数据集…

redis数据结构和数据类型

1.动态字符串SIMPLE DYNAMIC STRING(SDS)观察上图中的SDS结构&#xff0c;头部包含字符串长度和分配的空间&#xff0c;可以以O&#xff08;1&#xff09;的时间复杂度计算出字符串长度&#xff0c;并且有了字符串长度后可以无视c语言的字符串缺陷&#xff08;\0作为结尾标识&a…

深度学习--神经网络

一、深度学习的简单概念深度学习是一种模仿人类大脑的运行方式&#xff0c;从大量数据中学习特征的学习模式。深度学习是机器学习的子集&#xff0c;它与机器学习的关系如下&#xff1a;二、感知神经网络2.1简单定义神经网络&#xff08;Neural Networks&#xff09;是一种模拟…

.NET 程序的强名称签名与安全防护技术干货

在 .NET 开发领域&#xff0c;保障程序的安全性和完整性至关重要。强名称签名和有效的安全防护措施是实现这一目标的关键手段。下面将详细介绍 .NET 程序的强名称签名以及相关的安全防护方法。一、什么是强名称签名强名称签名是 .NET 框架提供的一种安全机制&#xff0c;其主要…

DNS(Domain Name System,域名系统)

目录 **一、DNS的核心功能****二、DNS的工作原理****1. 解析流程(以车载导航请求为例)****2. 关键机制****三、车载以太网中DNS的特殊性**1. **高可靠性要求**2. **低延迟优化**3. **安全挑战与防护****四、DNS相关协议与技术****五、车载DNS配置示例****六、DNS故障排查工具…