新年快乐 ·Happy Year· ▶ 在线运行案例案例合集三维可视化功能案例threehub.cn开源仓库github地址https://github.com/z2586300277/three-cesium-examples400个案例代码:网盘链接你将学到什么ShaderMaterial 自定义着色器实现核心视觉效果onBeforeCompile 注入 GLSL 改造内置材质OrbitControls 相机轨道交互CatmullRomCurve3 样条曲线路径Canvas 动态纹理贴图BufferGeometry 自定义顶点/索引数据监听窗口resize同步更新 camera 与 renderer效果说明本案例演示新年快乐效果用 Canvas 2D 绘制内容并实时映射为 Three.js 纹理核心用到 ShaderMaterial、onBeforeCompile、OrbitControls。建议先打开文首在线案例查看动态画面再对照下方源码逐步理解。核心概念Scene / Camera / WebGLRenderer构成最小渲染闭环大场景可开logarithmicDepthBuffer缓解 Z-fighting。ShaderMaterial通过uniforms 自定义 GLSL 控制逐像素/逐点效果透明粒子常配合depthTest: false。onBeforeCompile在 Three 拼好内置 shader 后替换#include片段适合在 PBR 材质上叠加大屏特效。OrbitControls提供轨道旋转/缩放开启enableDamping后需在 animate 中controls.update()。曲线类getPoints(n)将贝塞尔/样条离散为路径点再写入 BufferGeometry 驱动飞线或路径动画。实现步骤搭建 Scene、PerspectiveCamera、WebGLRenderer挂载 canvas 并处理resize定义 uniforms / onBeforeCompile 或 ShaderMaterial编写 GLSL 与材质参数用曲线离散点构建 BufferGeometry写入自定义 attribute 驱动动画创建 OrbitControls及 Raycaster 等交互控件若源码包含在requestAnimationFrame循环中更新状态并 renderCesium 为viewer.render或自动渲染代码要点import * as THREE from three;import { OrbitControls } from three/addons/controls/OrbitControls.js;let smoothNoise float N (vec2 st) { // https://thebookofshaders.com/10/ return fract( sin( dot( st.xy, vec2(12.9898,78.233 ) ) ) * 43758.5453123); }float smoothNoise( vec2 ip ){ // https://www.youtube.com/watch?vzXsWftRdsvU vec2 lv fract( ip ); vec2 id floor( ip ); lv lvlv( 3. - 2. * lv ); float bl N( id ); float br N( id vec2( 1, 0 )); float b mix( bl, br, lv.x ); float tl N( id vec2( 0, 1 )); float tr N( id vec2( 1, 1 )); float t mix( tl, tr, lv.x ); return mix( b, t, lv.y ); };class Background extends THREE.Mesh { constructor () { super( new THREE.SphereGeometry(500, 72, 36), new THREE.ShaderMaterial({ side: THREE.BackSide, vertexShader:varying vec3 vPos; void main(){ vPos position; gl_Position projectionMatrixmodelViewMatrixvec4(position, 1.); }, fragmentShader:varying vec3 vPos; void main(){ float f smoothstep(0., 50., vPos.y); vec3 baseCol vec3(0.25, 0.75, 1) * 0.5; vec3 topCol vec3(1, 0.5, 1) * 0.75; vec3 col mix( baseCol, topCol, f); gl_FragColor vec4(col, 1.); }}) ); } }class Sun extends THREE.Mesh { constructor (gu) { super(); this.gu gu; this.geometry new THREE.PlaneGeometry(1, 1, 1000, 1); this.material new THREE.ShaderMaterial({ uniforms: { time: this.gu.time, texYear: this.gu.texYear }, vertexShader:#define PI 3.14159265359 uniform float time; varying vec3 vPos; float getX(float y){ float x sin(mod((y - time0.05)0.9PI2.9., PI2.)); x sqrt(1. - yy); return x; } void main(){ float lengthFactor uv.x; float e 0.001; vec3 pos vec3(getX(lengthFactor),lengthFactor,0.); vec3 pos2 vec3(getX(lengthFactor e), lengthFactor e, 0.); vec2 tan normalize(pos2.xy - pos.xy); pos * 60.; pos.xy vec2(-tan.y, tan.x)sign(position.y)1.; pos.z -250.; vPos pos; gl_Position projectionMatrixmodelViewMatrixvec4(pos, 1.); }, fragmentShader:uniform sampler2D texYear; varying vec3 vPos; void main(){ vec2 tUv (vPos.xy - vec2(-35., -10.)) / 70.; float dYear texture(texYear, tUv).r; vec3 fogCol vec3(0.25, 0.75, 1) * 0.5; vec3 sunCol vec3(1, 0.875, 0.875); vec3 skyCol vec3(1, 0.5, 1) * 0.75; vec3 col mix(fogCol, sunCol, smoothstep(0., 30., vPos.y)); col mix(col, skyCol, smoothstep(50., 60., vPos.y)); col mix(col, vec3(1, 0.5, 0.75), dYear); gl_FragColor vec4(col, 1); }}); } }class SimpleFir extends THREE.Mesh { constructor (gu) { super(); this.gu gu; this.amount 300; this.instCount 1000; this.data new Float32Array(this.instCountthis.amount4); this.curveData new THREE.DataTexture( this.data, this.amount, this.instCount, THREE.RGBAFormat, THREE.FloatType );let ig new THREE.InstancedBufferGeometry().copy( new THREE.PlaneGeometry(1, 1, this.amount, 1).translate(0.5, 0, 0) ); ig.instanceCount this.instCount; ig.setAttribute( instPos, new THREE.InstancedBufferAttribute( new Float32Array( new Array(this.instCount) .fill() .map((p) { return [ (Math.random()0.95 0.05)(Math.random() 0.5 ? -1 : 1) * 25, 0, (Math.random() - 0.5) * 50 ]; }) .flat() ), 3 ) ); let im new THREE.ShaderMaterial({ wireframe: false, uniforms: { time: this.gu.time, curveData: { value: this.curveData } }, vertexShader:#include #define S(a, b, c) smoothstep(a, b, c) uniform float time; uniform sampler2D curveData; attribute vec3 instPos; varying vec3 vPos; varying vec4 vmvPos; varying vec2 vUv; mat2 rot (float a) {return mat2(cos(a), sin(a), -sin(a), cos(a));} void main(){ float t time; // completeFactor vec3 iPos vec3(instPos); iPos.z -25. mod(instPos.z t * 2. 25., 50.); float radiusFactor S(25., 15., length(iPos.xz)); // ////////////// float curveFactor uv.x; float widthFactor S(0., 0.01, curveFactor) - step(radiusFactor, curveFactor); float instFactor float(gl_InstanceID) / ${this.instCount - 1}.; vec4 cData texture(curveData, vec2(curveFactor, instFactor)); vec2 pt cData.xy; vec2 tn cData.wzvec2(-1., 1.)sign(position.y)0.02widthFactor; vec3 pos vec3(pt tn, 0.); // rotation to camera vec2 rotPos cameraPosition.xz - iPos.xz; pos.xz rot(-(atan(rotPos.y, rotPos.x) - PI0.5)); // ////////////////// pos iPos; vPos pos; vUv uv; vec4 mvPosition modelViewMatrix * vec4(pos, 1.); vmvPos mvPosition; gl_Position projectionMatrix * mvPosition; }, fragmentShader:#define S(a, b, c) smoothstep(a, b, c) varying vec3 vPos; varying vec4 vmvPos; varying vec2 vUv; void main() { float ditheringRadius S(2., 0.5, -vmvPos.z); if(length(fract(-vmvPos.xyz * 100.) - 0.5) ditheringRadius) discard; vec3 baseCol vec3(0.25, 0.75, 1); vec3 col mix(baseCol0.5, baseCol1.25, S(0., 1., vPos.y)); col mix(baseCol * 0.5, col, S(-0.2, 0., vPos.y)); // roots col mix(vec3(1, 1, 1), col, S(0.01, 0.05, vUv.x)); // pinnacle gl_FragColor vec4(col, 1); }});this.geometry ig; this.material im;this.setCurveData(); }setCurveData() { let pos new THREE.Vector3(); let tan new THREE.Vector3();for (let iIdx 0; iIdx this.instCount; iIdx) { let basePoints []; basePoints.push(new THREE.Vector3(0, 1.1, 0)); let layers THREE.MathUtils.randInt(5, 10); let step 1 / (layers - 1); for (let i 0; i layers; i) { basePoints.push(new THREE.Vector3(-step0.5i, 1 - step * i, 0)); if (i ! 0) basePoints.push(new THREE.Vector3(step0.5i, 1 - step * i, 0)); } basePoints.push(new THREE.Vector3(0, -0.1, 0)); let xReflect Math.random() 0.5 ? -1 : 1; basePoints.forEach((p) { //p.y 0.1; p.y * layers 5 ? 2 : layers 8 ? 3 : 1.5; p.x * xReflect; });let curve new THREE.CatmullRomCurve3( basePoints, false, catmullrom, 1 );for (let dIdx 0; dIdx this.amount; dIdx) { let getFactor dIdx / (this.amount - 1); curve.getPoint(getFactor, pos); curve.getTangent(getFactor, tan); let d this.data; let curRow iIdxthis.amount4; d[curRow dIdx * 4 0] pos.x; d[curRow dIdx * 4 1] pos.y; d[curRow dIdx * 4 2] tan.x; d[curRow dIdx * 4 3] tan.y; } } this.curveData.needsUpdate true; } }class Road extends THREE.Mesh { constructor (gu) { super(); this.gu gu; this.geometry new THREE.PlaneGeometry(1, 50, 1, 200).rotateX( -Math.PI * 0.5 ); this.material new THREE.ShaderMaterial({ wireframe: false, uniforms: { time: this.gu.time }, vertexShader:#define S(a, b, c) smoothstep(a, b, c) uniform float time; varying vec2 vUv; ${smoothNoise} void main(){ vUv uv; float t time; vec2 rUv uv * vec2(1., 10.); float nz smoothNoise(vec2(rUv.y t * 0.4, 1.1)); nz - 0.5; vec3 pos position; pos.x 0.875S(0.5, 0.4, abs(uv.y - 0.5)); pos.x nz * 0.5; gl_Position projectionMatrixmodelViewMatrixvec4(pos, 1.); }, fragmentShader:#define S(a, b, c) smoothstep(a, b, c) varying vec2 vUv; void main(){ vec2 uv vUv; vec3 col vec3(0.25, 0.75, 1) * 0.875; float absX abs(vUv.x - 0.5) * 2.; float wx fwidth(absX); col mix(col, vec3(1, 0.5, 1), S(0.05 wx, 0.05, abs(absX - 0.5))); // magenta stripes gl_FragColor vec4(col, 1.); }}); } }class Snow extends THREE.Points { constructor (gu) { super(); this.gu gu; this.geometry new THREE.BufferGeometry().setFromPoints( new Array(50000).fill().map(p { let v new THREE.Vector3().random().subScalar(0.5).multiply(new THREE.Vector3(0.5, 1, 1)).multiplyScalar(50); v.y 25; return v; }) ); this.material new THREE.PointsMaterial({ size: 0.2, color: new THREE.Color(0.75, 1, 1), onBeforeCompile: shader { shader.uniforms.time this.gu.time; shader.uniforms.texYear this.gu.texYear; shader.vertexShader uniform float time; varying float vAppearance; ${shader.vertexShader}.replace(#include,#include float z -25. mod(position.z 25. time * 2., 50.); transformed.z z; float y 50. - mod(abs(position.y - 50. - time * 0.25), 50.); transformed.y y; vAppearance smoothstep(5., 2.5, -vec3(modelViewMatrix * vec4(transformed, 1)).z);); console.log(shader.vertexShader); shader.fragmentShader uniform sampler2D texYear; varying float vAppearance; ${shader.fragmentShader}.replace(#include,#include if (length(gl_PointCoord.xy - 0.5) 0.5) discard;).replace(#include,#include vec2 tUv gl_PointCoord.xy; tUv.y 1. - tUv.y 0.05; float ytVal texture(texYear, tUv).r; diffuseColor.rgb mix(diffuseColor.rgb, vec3(0, 1, 1)0.75, ytValvAppearance);); console.log(shader.fragmentShader); } }); } }let scene new THREE.Scene(); //scene.background new THREE.Color(white); let camera new THREE.PerspectiveCamera( 30, innerWidth / innerHeight, 0.01, 1000 ); camera.position.set(0, 0.25, 10).setLength(3); let camShift new THREE.Vector3(0, 0.25, 0); camera.position.add(camShift); let renderer new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(innerWidth, innerHeight); document.body.appendChild(renderer.domElement);window.addEventListener(resize, (event) { camera.aspect innerWidth / innerHeight; camera.updateProjectionMatrix(); renderer.setSize(innerWidth, innerHeight); });let controls new OrbitControls(camera, renderer.domElement); controls.enableDamping true; // controls.enablePan false; controls.target.copy(camShift); controls.update(); // controls.maxDistance controls.getDistance(); // controls.maxPolarAngle controls.getPolarAngle(); // controls.minPolarAngle controls.getPolarAngle();//scene.add(new THREE.GridHelper(50, 10));let gu { time: { value: 0 }, texYear: { value: (() {function createTexture(text) { let c document.createElement(canvas); c.width 1024; c.height 1024; let unit (val) valc.height0.01;let ctx c.getContext(2d); ctx.filter blur(${unit(0.5)}px); ctx.font ${unit(80)}px TulpenOne; ctx.textAlign center; ctx.textBaseline middle; ctx.fillStyle #000; ctx.fillRect(0, 0, c.width, c.height); ctx.fillStyle #fff; ctx.fillText(text, unit(50), unit(50));let tex new THREE.CanvasTexture(c); return tex; }return Math.random() 0.5 ? createTexture(年) : createTexture(乐) })() } };let background new Background(); scene.add(background);let sun new Sun(gu); scene.add(sun);let simpleFir new SimpleFir(gu); scene.add(simpleFir);let road new Road(gu); scene.add(road);let snow new Snow(gu); scene.add(snow);let clock new THREE.Clock(); let t 0;renderer.setAnimationLoop(() { let dt clock.getDelta(); t dt; gu.time.value t; controls.update(); renderer.render(scene, camera); });完整源码GitHub小结本文提供新年快乐完整 Three.js 源码与在线 Demo建议先运行案例再改 uniform/参数做二次实验更多 Three.js 实战案例见 three-cesium-examples 合集 与 GitHub 开源仓库