终末地-登录入口 ·EndField Index· ▶ 在线运行案例案例合集三维可视化功能案例threehub.cn开源仓库github地址https://github.com/z2586300277/three-cesium-examples400个案例代码:网盘链接你将学到什么ShaderMaterial 自定义着色器实现核心视觉效果OrbitControls 相机轨道交互THREE.Points 粒子点渲染Canvas 动态纹理贴图FBXLoader 加载 FBX 城市/角色模型骨骼动画与 AnimationMixerrequestAnimationFrame渲染循环与resize自适应效果说明本案例演示终末地-登录入口效果用 Canvas 2D 绘制内容并实时映射为 Three.js 纹理核心用到 ShaderMaterial、OrbitControls、THREE.Points。建议先打开文首在线案例查看动态画面再对照下方源码逐步理解。核心概念Scene / Camera / WebGLRenderer构成最小渲染闭环大场景可开logarithmicDepthBuffer缓解 Z-fighting。ShaderMaterial通过uniforms 自定义 GLSL 控制逐像素/逐点效果透明粒子常配合depthTest: false。OrbitControls提供轨道旋转/缩放开启enableDamping后需在 animate 中controls.update()。THREE.Points将每个顶点渲染为可控大小的粒子可用自定义 attribute如u_index驱动片元/顶点动画。CanvasTexture每帧或按需把 2D Canvas 内容上传 GPU适合动态文字、图表、视频帧贴图。实现步骤搭建 Scene、PerspectiveCamera、WebGLRenderer挂载 canvas 并处理resize异步加载模型 / 3D Tiles / GeoJSON 等资源并加入 scene 或 entities定义 uniforms / onBeforeCompile 或 ShaderMaterial编写 GLSL 与材质参数创建 OrbitControls及 Raycaster 等交互控件若源码包含在requestAnimationFrame循环中更新状态并 renderCesium 为viewer.render或自动渲染代码要点import * as THREE from three;import { FBXLoader } from three/examples/jsm/loaders/FBXLoader.js; import { SVGLoader } from three/examples/jsm/loaders/SVGLoader.js; import { FullScreenQuad } from three/examples/jsm/postprocessing/Pass.js; import { OrbitControls } from three/examples/jsm/controls/OrbitControls.js; import { GUI } from dat.gui;// 渲染流程 // 1. 渲染模型到 rt_model // 2. 渲染场景到 rt_scene // 3. 应用噪声偏移采样点采样获取模型深度实现故障效果 // 4. 采样获取场景深度 // 5. 深度比较对模型部分应用特殊材质场景部分正常输出const setting { baseClearColor: 0xfafafa, baseClearAlpha: 1, };const rt_model new THREE.RenderTarget(); rt_model.depthTexture new THREE.DepthTexture(); const rt_scene new THREE.RenderTarget(); rt_scene.depthTexture new THREE.DepthTexture();// 模型材质渲染深度用做遮罩图 // 骨骼绑定具体参考 Three.js 着色器 skin const model_mat new THREE.ShaderMaterial({ vertexShader: /glsl/uniform mat4 bindMatrix; uniform mat4 bindMatrixInverse;uniform highp sampler2D boneTexture;mat4 getBoneMatrix(const in float i) { int size textureSize(boneTexture, 0).x; int j int(i) * 4; int x j % size; int y j / size; vec4 v1 texelFetch(boneTexture, ivec2(x 0, y), 0); vec4 v2 texelFetch(boneTexture, ivec2(x 1, y), 0); vec4 v3 texelFetch(boneTexture, ivec2(x 2, y), 0); vec4 v4 texelFetch(boneTexture, ivec2(x 3, y), 0); return mat4(v1, v2, v3, v4); }void main() { vec3 pos position;mat4 boneMatX getBoneMatrix(skinIndex.x); mat4 boneMatY getBoneMatrix(skinIndex.y); mat4 boneMatZ getBoneMatrix(skinIndex.z); mat4 boneMatW getBoneMatrix(skinIndex.w);vec4 skinVertex bindMatrix * vec4(pos, 1.0);vec4 skinned vec4(0.0); skinned boneMatXskinVertexskinWeight.x; skinned boneMatYskinVertexskinWeight.y; skinned boneMatZskinVertexskinWeight.z; skinned boneMatWskinVertexskinWeight.w;pos (bindMatrixInverse * skinned).xyz;gl_Position projectionMatrixmodelViewMatrixvec4(pos, 1.0); }, fragmentShader: /glsl/void main() { gl_FragColor vec4(1.0); }, });// 融合管线合并模型和场景并着色 const pass_mix new FullScreenQuad( new THREE.ShaderMaterial({ uniforms: { t_model: { value: rt_model.texture, }, t_modelDepth: { value: rt_model.depthTexture, }, t_scene: { value: rt_scene.texture, }, t_sceneDepth: { value: rt_scene.depthTexture, }, time: { value: 0.0, }, // 故障效果强度 faultStrength: { value: 1.0, }, // 是否启用条纹效果 useStripe: { value: true, }, }, vertexShader: /glsl/void main() { gl_Position vec4(position, 1.0); }, fragmentShader: /glsl/uniform sampler2D t_model ; uniform sampler2D t_modelDepth ; uniform sampler2D t_scene ; uniform sampler2D t_sceneDepth ; uniform float time ; uniform float faultStrength ; uniform bool useStripe ;// RGBA深度解包具体参考 Three.js 着色器 packing const float UnpackDownscale 255.0 / 256.0; const vec4 PackFactors vec4(1.0, 256.0, 256.0256.0, 256.0256.0 * 256.0); const vec4 UnpackFactors4 vec4(UnpackDownscale / PackFactors.rgb, 1.0 / PackFactors.a); float unpackRGBAToDepth(const in vec4 v) { return dot(v, UnpackFactors4); }// 1D 柏林噪声 float Random(float x) { return 2.0fract(sin(x12.9898) * 43758.5453) - 1.0; } float CubicInterpolate(float x) { return 3.0pow(x, 2.0) - 2.0pow(x, 3.0); } float PerlinNoise1D(float Position) { float PosInt floor(Position); float PosFloat fract(Position); float v0 Random(PosInt); float v1 Random(PosInt 1.0); return v1CubicInterpolate(PosFloat) v0CubicInterpolate(1.0 - PosFloat); }// 星空环境贴图 vec4 hash43x(vec3 p) { uvec3 x uvec3(ivec3(p)); x 1103515245U * ((x.xyz 1U) ^ (x.yzx)); uint h 1103515245U * ((x.x ^ x.z) ^ (x.y 3U)); uvec4 rz uvec4(h, h16807U, h48271U, h * 69621U); return vec4((rz 1) uvec4(0x7fffffffU)) / float(0x7fffffff); } vec3 stars(vec3 p, vec2 rsln) { vec3 col vec3(0); float rad .087 * rsln.y; float dens 0.15; float id 0.; float rz 0.; float z 1.; for(float i 0.; i 5.; i) { p * mat3(0.86564, -0.28535, 0.41140, 0.50033, 0.46255, -0.73193, 0.01856, 0.83942, 0.54317); vec3 q abs(p); vec3 p2 p / max(q.x, max(q.y, q.z)); p2 * rad; vec3 ip floor(p2 1e-5); vec3 fp fract(p2 1e-5); vec4 rand hash43x(ip * 283.1); vec3 q2 abs(p2); vec3 pl 1.0 - step(max(q2.x, max(q2.y, q2.z)), q2); vec3 pp fp - ((rand.xyz - 0.5).6 0.5)pl; float pr length(ip) - rad; if(rand.w (dens - denspr0.035)) pp 1e6; float d dot(pp, pp); d / pow(fract(rand.w * 172.1), 32.) .25; float bri dot(rand.xyz * (1. - pl), vec3(1.)); id fract(rand.w * 101.); col briz.00009 / pow(d 0.025, 3.0)(mix(vec3(1.0, 0.45, 0.1), vec3(0.75, 0.85, 1.), id)0.6 0.4); rad floor(rad * 1.08); dens * 1.45; z * 0.6; p p.yxz; } return col; }void main() { ivec2 texelCoord ivec2(gl_FragCoord.xy); ivec2 texelCoord2 texelCoord ivec2( int( // 噪声控制效果整体效果 故障强度控制 随机位移效果 时间轴动画 PerlinNoise1D(gl_FragCoord.y0.15time0.25)200.0faultStrengthRandom(gl_FragCoord.y) * (mod(time, 2.0) 1.0 ? pow((mod(time, 2.0) - 1.5), 2.5) : 0.0 ) ), 0 ); ivec2 texSize textureSize(t_model, 0);float d_model (texelCoord2.x 0 texelCoord2.x texSize.x) ? unpackRGBAToDepth(texelFetch(t_modelDepth, texelCoord2, 0)) : 1000000.0; float d_scene unpackRGBAToDepth(texelFetch(t_sceneDepth, texelCoord, 0));// 遮罩混合 if (d_model d_scene) { // 这部分可替换为环境贴图此处为了节省网络带宽使用着色器实现 // fov 90 视线方向重建视角旋转 vec3 dir normalize(vec3(vec2(texelCoord), 0.0) - vec3(ivec3(texSize, texSize.y)) * 0.5); float a dir.x / dir.z - time * 0.1; float l length(dir.xz); dir.x cos(a) * l; dir.z sin(a) * l;gl_FragColor vec4(stars(dir, vec2(texSize) * 0.3), 1.0); } else { gl_FragColor texelFetch(t_scene, texelCoord, 0); }// 条纹效果 if (useStripe mod(gl_FragCoord.y, 8.0) 6.4) { gl_FragColor.rgb * 0.93; } }, }) );const logoStr ; const modelUrl https://ylfq.github.io/model/walk.fbx;const canvas document.createElement(canvas); canvas.style.width 100vw !important; canvas.style.height 100vh !important; document.body.appendChild(canvas);const renderer new THREE.WebGLRenderer({ canvas: canvas, antialias: true, alpha: true }); renderer.setClearColor(setting.baseClearColor, setting.baseClearAlpha);const scene new THREE.Scene(); const ground new THREE.Points( new THREE.PlaneGeometry(2000, 1000, 45, 40).rotateX(-Math.PI / 2), new THREE.ShaderMaterial({ uniforms: { size: { value: 5.0, }, color: { value: new THREE.Color(0xaaaaaa), }, }, vertexShader: /glsl/uniform float size; varying float z;void main() { vec4 mvPos modelViewMatrix * vec4(position, 1.0);z mvPos.z;gl_PointSize 300.0 / -mvPos.z * size; gl_Position projectionMatrix * mvPos; }, fragmentShader: /glsl/uniform vec3 color;varying float z;void main() { // 远处渐隐 gl_FragColor vec4(color, max(0.0, 0.4 z * 0.0008)); }, transparent: true, }) ); ground.position.set(0, -120, 0); // 给地面套一个组方便计算位移 const group new THREE.Group(); group.rotation.x -0.35; scene.add(group.add(ground));const loader_fbx new FBXLoader(); const loader_svg new SVGLoader();const logo new THREE.Mesh( new THREE.ShapeGeometry(loader_svg.parse(logoStr).paths[0].toShapes(true)).center(), new THREE.ShaderMaterial({ uniforms: { c_0: { value: new THREE.Color(0xf8f22d), }, }, vertexShader: /glsl/void main() { gl_Position projectionMatrixmodelViewMatrixvec4(position, 1.0); }, fragmentShader: /glsl/uniform vec3 c_0;void main() { if (length(fract(gl_FragCoord * 0.07)) 0.9) { gl_FragColor vec4(c_0 * 0.9, 1.0); } else { gl_FragColor vec4(c_0, 1.0); } }, side: THREE.DoubleSide, }) ); logo.position.set(0, 0, 78); logo.scale.set(0.15, -0.15, 1); scene.add(logo);const model await loader_fbx.loadAsync(modelUrl); model.traverse((obj) { if (obj instanceof THREE.Mesh) { obj.material model_mat; } }); const mixer new THREE.AnimationMixer(model); const action mixer.clipAction(model.animations[0]); action.setLoop(THREE.LoopRepeat); action.play();const camera new THREE.PerspectiveCamera(90, 1, 0.1, 1000); camera.position.set(0, 0, 140);const controls new OrbitControls(camera, canvas); controls.enableDamping true;const timer new THREE.Timer();const tick (delta, elapsed) { controls.update(delta);// 更新动画并限制模型不进行移动 mixer.update(delta * 0.9); model.children[2].children[0].position.set(0, 0, 0);// 地面移动 ground.position.z (elapsed * -50) % 25;// 材质时间更新 pass_mix.material.uniforms.time.value elapsed; };const render () { renderer.setRenderTarget(rt_model); renderer.clearDepth(); renderer.setClearColor(0x000000, 1); renderer.render(model, camera);renderer.setRenderTarget(rt_scene); renderer.clearDepth(); renderer.setClearColor(setting.baseClearColor, setting.baseClearAlpha); renderer.render(scene, camera);renderer.setRenderTarget(null); renderer.clearDepth(); pass_mix.render(renderer); };const ani () { const elapsed timer.getElapsed(); const delta timer.getDelta();timer.update();tick(delta, elapsed); render();requestAnimationFrame(ani); };const data { get faultStrength() { return pass_mix.material.uniforms.faultStrength.value; }, set faultStrength(value) { pass_mix.material.uniforms.faultStrength.value value; },get useStripe() { return pass_mix.material.uniforms.useStripe.value; }, set useStripe(value) { pass_mix.material.uniforms.useStripe.value value; },get c_0() { return#${logo.material.uniforms.c_0.value.getHexString()}; }, set c_0(value) { logo.material.uniforms.c_0.value.set(value); },get c_p() { return#${ground.material.uniforms.color.value.getHexString()}; }, set c_p(value) { ground.material.uniforms.color.value.set(value); },get p_s() { return ground.material.uniforms.size.value; }, set p_s(value) { ground.material.uniforms.size.value value; }, }; const gui new GUI(); gui.add(data, faultStrength, 0, 5).step(0.001).name(故障强度); gui.add(data, useStripe).name(启用条纹); gui.addColor(data, c_0).name(logo颜色); gui.addColor(data, c_p).name(粒子颜色); gui.add(data, p_s, 1, 20, 0.001).name(粒子大小);new ResizeObserver(() { const rect document.body.getBoundingClientRect(); const w rect.width; const h rect.height; const a w / h; const dpr window.devicePixelRatio * 1.25;renderer.setSize(w, h, false); renderer.setPixelRatio(dpr);rt_model.setSize(wdpr, hdpr); rt_scene.setSize(wdpr, hdpr);camera.aspect a; camera.updateProjectionMatrix(); }).observe(document.body);ani();完整源码GitHub小结本文提供终末地-登录入口完整 Three.js 源码与在线 Demo建议先运行案例再改 uniform/参数做二次实验更多 Three.js 实战案例见 three-cesium-examples 合集 与 GitHub 开源仓库