👨⚕️ 主页: gis分享者
👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅!
文章目录
- 一、🍀前言
-
- [1.1 ☘️THREE.EffectComposer 后期处理](#1.1 ☘️THREE.EffectComposer 后期处理)
-
- [1.1.1 ☘️代码示例](#1.1.1 ☘️代码示例)
- [1.1.2 ☘️构造函数](#1.1.2 ☘️构造函数)
- [1.1.3 ☘️属性](#1.1.3 ☘️属性)
- [1.1.4 ☘️方法](#1.1.4 ☘️方法)
- [1.2 ☘️THREE.RenderPass](#1.2 ☘️THREE.RenderPass)
-
- [1.2.1 ☘️构造函数](#1.2.1 ☘️构造函数)
- [1.2.2 ☘️属性](#1.2.2 ☘️属性)
- [1.2.3 ☘️方法](#1.2.3 ☘️方法)
- [1.3 ☘️THREE.UnrealBloomPass](#1.3 ☘️THREE.UnrealBloomPass)
-
- [1.3.1 ☘️构造函数](#1.3.1 ☘️构造函数)
- [1.3.2 ☘️方法](#1.3.2 ☘️方法)
- 二、🍀实现Transformer架构三维模拟
-
- [1. ☘️实现思路](#1. ☘️实现思路)
- [2. ☘️代码样例](#2. ☘️代码样例)
一、🍀前言
本文详细介绍如何基于threejs在三维场景中使用EffectComposer后期处理组合器(采用RenderPass、UnrealBloomPass渲染通道),实现Transformer架构三维模拟效果,亲测可用。希望能帮助到您。一起学习,加油!加油!
1.1 ☘️THREE.EffectComposer 后期处理
THREE.EffectComposer 用于在three.js中实现后期处理效果。该类管理了产生最终视觉效果的后期处理过程链。 后期处理过程根据它们添加/插入的顺序来执行,最后一个过程会被自动渲染到屏幕上。
1.1.1 ☘️代码示例
javascript
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
// 初始化 composer
const composer = new EffectComposer(renderer);
// 创建 RenderPass 并添加到 composer
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
// 添加其他后期处理通道(如模糊)
// composer.addPass(blurPass);
// 在动画循环中渲染
function animate() {
composer.render();
requestAnimationFrame(animate);
}
1.1.2 ☘️构造函数
EffectComposer( renderer : WebGLRenderer, renderTarget : WebGLRenderTarget )
renderer -- 用于渲染场景的渲染器。
renderTarget -- (可选)一个预先配置的渲染目标,内部由 EffectComposer 使用。
1.1.3 ☘️属性
.passes : Array
一个用于表示后期处理过程链(包含顺序)的数组。
javascript
渲染通道:
BloomPass 该通道会使得明亮区域参入较暗的区域。模拟相机照到过多亮光的情形
DotScreenPass 将一层黑点贴到代表原始图片的屏幕上
FilmPass 通过扫描线和失真模拟电视屏幕
MaskPass 在当前图片上贴一层掩膜,后续通道只会影响被贴的区域
RenderPass 该通道在指定的场景和相机的基础上渲染出一个新的场景
SavePass 执行该通道时,它会将当前渲染步骤的结果复制一份,方便后面使用。这个通道实际应用中作用不大;
ShaderPass 使用该通道你可以传入一个自定义的着色器,用来生成高级的、自定义的后期处理通道
TexturePass 该通道可以将效果组合器的当前状态保存为一个纹理,然后可以在其他EffectCoposer对象中将该纹理作为输入参数
.readBuffer : WebGLRenderTarget
内部读缓冲区的引用。过程一般从该缓冲区读取先前的渲染结果。
.renderer : WebGLRenderer
内部渲染器的引用。
.renderToScreen : Boolean
最终过程是否被渲染到屏幕(默认帧缓冲区)。
.writeBuffer : WebGLRenderTarget
内部写缓冲区的引用。过程常将它们的渲染结果写入该缓冲区。
1.1.4 ☘️方法
.addPass ( pass : Pass ) : undefined
pass -- 将被添加到过程链的过程
将传入的过程添加到过程链。
.dispose () : undefined
释放此实例分配的 GPU 相关资源。每当您的应用程序不再使用此实例时调用此方法。
.insertPass ( pass : Pass, index : Integer ) : undefined
pass -- 将被插入到过程链的过程。
index -- 定义过程链中过程应插入的位置。
将传入的过程插入到过程链中所给定的索引处。
.isLastEnabledPass ( passIndex : Integer ) : Boolean
passIndex -- 被用于检查的过程
如果给定索引的过程在过程链中是最后一个启用的过程,则返回true。 由EffectComposer所使用,来决定哪一个过程应当被渲染到屏幕上。
.removePass ( pass : Pass ) : undefined
pass -- 要从传递链中删除的传递。
从传递链中删除给定的传递。
.render ( deltaTime : Float ) : undefined
deltaTime -- 增量时间值。
执行所有启用的后期处理过程,来产生最终的帧,
.reset ( renderTarget : WebGLRenderTarget ) : undefined
renderTarget -- (可选)一个预先配置的渲染目标,内部由 EffectComposer 使用。
重置所有EffectComposer的内部状态。
.setPixelRatio ( pixelRatio : Float ) : undefined
pixelRatio -- 设备像素比
设置设备的像素比。该值通常被用于HiDPI设备,以阻止模糊的输出。 因此,该方法语义类似于WebGLRenderer.setPixelRatio()。
.setSize ( width : Integer, height : Integer ) : undefined
width -- EffectComposer的宽度。
height -- EffectComposer的高度。
考虑设备像素比,重新设置内部渲染缓冲和过程的大小为(width, height)。 因此,该方法语义类似于WebGLRenderer.setSize()。
.swapBuffers () : undefined
交换内部的读/写缓冲。
1.2 ☘️THREE.RenderPass
THREE.RenderPass用于将场景渲染到中间缓冲区,为后续的后期处理效果(如模糊、色调调整等)提供基础。
1.2.1 ☘️构造函数
RenderPass(scene, camera, overrideMaterial, clearColor, clearAlpha)
- scene THREE.Scene 要渲染的 Three.js 场景对象。
- camera THREE.Camera 场景对应的相机(如 PerspectiveCamera)。
- overrideMaterial THREE.Material (可选) 覆盖场景中所有物体的材质(默认 null)。
- clearColor THREE.Color (可选) 渲染前清除画布的颜色(默认不主动清除)。
- clearAlpha number (可选) 清除画布的透明度(默认 0)。
1.2.2 ☘️属性
.enabled:boolean
是否启用此通道(默认 true)。设为 false 可跳过渲染。
.clear:boolean
渲染前是否清除画布(默认 true)。若需叠加多个 RenderPass,可设为 false。
.needsSwap:boolean
是否需要在渲染后交换缓冲区(通常保持默认 false)。
1.2.3 ☘️方法
.setSize(width, height)
调整通道的渲染尺寸(通常由 EffectComposer 自动调用)。
width: 画布宽度(像素)。
height: 画布高度(像素)。
1.3 ☘️THREE.UnrealBloomPass
UnrealBloomPass 是 Three.js 中实现高质量泛光效果的后期处理通道,通过模拟类似 Unreal Engine 的泛光效果,为场景中的明亮区域添加柔和的光晕,提升视觉表现力。
1.3.1 ☘️构造函数
new UnrealBloomPass(resolution, strength, radius, threshold)
- resolution (Vector2): 泛光效果应用的场景分辨率,需与画布尺寸一致。
示例:new THREE.Vector2(window.innerWidth, window.innerHeight) - strength (Number): 泛光强度,默认值 1.0。值越大,光晕越明显。
- radius (Number): 模糊半径,默认值 0.4。值越大,光晕扩散范围越广。
- threshold (Number): 泛光阈值,默认值 0.85。仅对亮度高于此值的区域生效。
1.3.2 ☘️方法
- renderToScreen: 是否直接渲染到屏幕,默认为 false(需通过 EffectComposer 管理)。
- clearColor: 设置背景清除颜色,默认为透明。
二、🍀实现Transformer架构三维模拟
1. ☘️实现思路
这是一款纯前端实现的 Transformer 架构 3D 交互模拟,作者 Cammakingminds 在 CodePen 上发布了这个作品。打开页面后,你会看到一个沉浸式的暗黑科幻场景:左右两侧分别是 Encoder 和 Decoder 模块,数据以粒子流的形式在模块之间流动,还配有发光效果、Bloom 后处理和伪代码浮窗,视觉冲击力极强。具体代码参考下面代码样例。
2. ☘️代码样例
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Transformer架构三维模拟</title>
<!-- Core Three.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://unpkg.com/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
<script src="https://unpkg.com/three@0.128.0/examples/js/postprocessing/EffectComposer.js"></script>
<script src="https://unpkg.com/three@0.128.0/examples/js/postprocessing/RenderPass.js"></script>
<script src="https://unpkg.com/three@0.128.0/examples/js/postprocessing/ShaderPass.js"></script>
<script src="https://unpkg.com/three@0.128.0/examples/js/shaders/CopyShader.js"></script>
<script src="https://unpkg.com/three@0.128.0/examples/js/shaders/LuminosityHighPassShader.js"></script>
<script src="https://unpkg.com/three@0.128.0/examples/js/postprocessing/UnrealBloomPass.js"></script>
<!-- Animation -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #020205;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
color: #fff;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
touch-action: none;
outline: none;
}
</style>
</head>
<body>
<script>
// --- Unified Color Schema ---
const COLORS = {
attention: 0xff5500,
feedForward: 0x00ffff,
ffn: 0x00ffff,
addNorm: 0xffff00,
embed: 0xff0000,
pe: 0xaaaaaa,
softmax: 0x888888,
crossAttention: 0xff5500,
encoderBox: 0x00ff00,
decoderBox: 0xff0000,
// Particles and internal wiring
posSine: 0x00ffff,
posCosine: 0xff00ff,
particle: 0xffffff,
qLine: 0xff0055,
kLine: 0x00f2ff,
vLine: 0xffcc00,
residual: 0x55aaee
};
const NUM_BLOCKS = 1;
const ENCODER_X = -12;
const DECODER_X = 12;
const NUM_HEADS = 8;
let scene, camera, renderer, controls, composer;
let animatedObjects = [];
let paths = [];
let particles = [];
let volumes = [];
let particleGeometries;
let binaryTex = [],
asciiTex = [],
idTex = [];
window.activeSoftmaxAngle = 0;
function initTextTextures() {
const createTex = (text) => {
const canvas = document.createElement("canvas");
canvas.width = 64;
canvas.height = 64;
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#ffffff";
ctx.font = 'bold 44px "Courier New", monospace';
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(text, 32, 32);
return new THREE.CanvasTexture(canvas);
};
binaryTex = [createTex("0"), createTex("1")];
const chars = [
"A",
"B",
"C",
"D",
"E",
"F",
"X",
"Y",
"Z",
"@",
"#",
"?",
"*"
];
asciiTex = chars.map((c) => createTex(c));
const createIdTex = (text) => {
const canvas = document.createElement("canvas");
canvas.width = 64;
canvas.height = 64;
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#ffffff";
ctx.font = 'bold 26px "Courier New", monospace';
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(text, 32, 32);
return new THREE.CanvasTexture(canvas);
};
for (let i = 0; i < 15; i++)
idTex.push(createIdTex(Math.floor(Math.random() * 900 + 100).toString()));
}
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
let pointerDownPos = new THREE.Vector2();
let pointerCurrentPos = new THREE.Vector2();
let lastTapTime = 0;
let isPointerDown = false;
let isHolding = false;
let pointerDownTimestamp = 0;
let activePointers = 0;
const pseudoCodeData = {
"token-embedding": {
title: "L0: Token Embedding",
color: "#ff0000",
code: `<span class="keyword">def</span> <span class="function">embed</span>(input_ids):\n <span class="comment"># Map discrete IDs to continuous vectors</span>\n <span class="keyword">return</span> Embedding(vocab_size, d_model)(input_ids) * sqrt(d_model)`
},
"pos-encoding": {
title: "L0.5: Positional Encoding",
color: "#aaaaaa",
code: `<span class="keyword">def</span> <span class="function">pos_encoding</span>(x):\n <span class="comment"># Inject sequence order via phase shifts</span>\n pe = generate_sine_cosine(seq_len, d_model)\n <span class="keyword">return</span> x + pe`
},
"self-attention": {
title: "L1: Multi-Head Self-Attention",
color: "#ff5500",
code: `<span class="keyword">def</span> <span class="function">multi_head_attention</span>(x, heads=<span class="keyword">8</span>):\n Q, K, V = x@Wq, x@Wk, x@Wv\n Q_h, K_h, V_h = split_heads(Q, K, V, heads)\n scores = (Q_h @ K_h.T) / sqrt(d_k)\n weights = softmax(scores, dim=-1)\n context = weights @ V_h\n <span class="keyword">return</span> Linear_O(concatenate(context))`
},
"masked-attention": {
title: "L1-M: Masked Multi-Head Attention",
color: "#ff5500",
code: `<span class="keyword">def</span> <span class="function">masked_attn</span>(x, heads=<span class="keyword">8</span>):\n Q, K, V = project_and_split(x)\n scores = (Q @ K.T) / sqrt(d_k)\n mask = tril(ones(seq_len, seq_len))\n scores = scores.masked_fill(mask == <span class="keyword">0</span>, <span class="keyword">-inf</span>)\n weights = softmax(scores, dim=-1)\n <span class="keyword">return</span> Linear_O(weights @ V)`
},
"cross-attention": {
title: "L2: Multi-Head Cross-Attention",
color: "#ff5500",
code: `<span class="keyword">def</span> <span class="function">cross_attn</span>(decoder_x, encoder_out):\n Q = decoder_x @ Wq\n K = encoder_out @ Wk\n V = encoder_out @ Wv\n scores = (Q @ K.T) / sqrt(d_k)\n weights = softmax(scores, dim=-1)\n <span class="keyword">return</span> Linear_O(weights @ V)`
},
"add-norm": {
title: "L3: Add & Norm (Residual + LayerNorm)",
color: "#ffff00",
code: `<span class="keyword">def</span> <span class="function">add_and_norm</span>(x, sublayer_out):\n summed = x + sublayer_out\n x_norm = (summed - mean) / sqrt(var + eps)\n <span class="keyword">return</span> gamma * x_norm + beta`
},
ffn: {
title: "L4: Fully Connected FFN",
color: "#00ffff",
code: `<span class="keyword">def</span> <span class="function">ffn</span>(x):\n <span class="comment"># Expand to high-dimensional space (4x d_model)</span>\n h = ReLU(x @ W1 + b1)\n <span class="comment"># Project back to base dimension</span>\n <span class="keyword">return</span> h @ W2 + b2`
},
softmax: {
title: "L5: Softmax Output",
color: "#888888",
code: `<span class="keyword">def</span> <span class="function">softmax_output</span>(x):\n logits = x @ W_vocab\n probs = softmax(logits)\n <span class="keyword">return</span> probs`
}
};
window.onload = () => {
init();
buildArchitecture();
createDataFlow();
setupInteraction();
animate();
};
function init() {
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x020205, 0.006);
camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
0.1,
2000
);
camera.position.set(0, 25, window.innerWidth < 768 ? 120 : 80);
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
powerPreference: "high-performance"
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.outputEncoding = THREE.sRGBEncoding;
document.body.appendChild(renderer.domElement);
const renderScene = new THREE.RenderPass(scene, camera);
const bloomPass = new THREE.UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
2.0,
0.5,
0.95
);
composer = new THREE.EffectComposer(renderer);
composer.addPass(renderScene);
composer.addPass(bloomPass);
const gridHelper = new THREE.GridHelper(200, 100, 0x004488, 0x001122);
gridHelper.position.y = -18;
scene.add(gridHelper);
scene.add(new THREE.AmbientLight(0x222233, 1.0));
const dir = new THREE.DirectionalLight(0xffffff, 0.8);
dir.position.set(20, 50, 30);
scene.add(dir);
const coreLight = new THREE.PointLight(0x00ffff, 1.5, 100);
coreLight.position.set(0, 20, 0);
scene.add(coreLight);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.target.set(0, 20, 0);
controls.maxDistance = 600;
controls.autoRotateSpeed = 0.8;
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
composer.setSize(window.innerWidth, window.innerHeight);
});
particleGeometries = {
cube: new THREE.BoxGeometry(0.3, 0.3, 0.3),
sphere: new THREE.SphereGeometry(0.18, 8, 8),
beam: new THREE.CylinderGeometry(0.04, 0.04, 0.8, 6).rotateX(Math.PI / 2),
icosahedron: new THREE.IcosahedronGeometry(0.25, 0),
disc: new THREE.CylinderGeometry(0.3, 0.3, 0.05, 12),
torusKnot: new THREE.TorusKnotGeometry(0.12, 0.04, 64, 8),
dataDart: new THREE.ConeGeometry(0.12, 0.5, 4).rotateX(Math.PI / 2)
};
initTextTextures();
}
function createGlass(color, op = 0.5) {
return new THREE.MeshStandardMaterial({
color,
emissive: color,
emissiveIntensity: 1.5,
transparent: true,
opacity: op
});
}
function tag(group, type, volRef) {
group.traverse((c) => {
if (c.isMesh) {
c.userData.blockType = type;
if (volRef) c.userData.volumeRef = volRef;
}
});
}
function createCodePlane(type) {
const data = pseudoCodeData[type];
if (!data) return null;
const canvas = document.createElement("canvas");
canvas.width = 1024;
canvas.height = 512;
const ctx = canvas.getContext("2d");
ctx.fillStyle = "rgba(5, 5, 10, 0.95)";
ctx.fillRect(0, 0, 1024, 512);
ctx.strokeStyle = data.color;
ctx.lineWidth = 12;
ctx.strokeRect(6, 6, 1012, 500);
ctx.fillStyle = "#ffffff";
ctx.font = 'bold 42px "Courier New", monospace';
ctx.fillText(data.title, 40, 70);
ctx.beginPath();
ctx.moveTo(40, 100);
ctx.lineTo(984, 100);
ctx.strokeStyle = "rgba(255, 255, 255, 0.2)";
ctx.lineWidth = 2;
ctx.stroke();
ctx.font = 'bold 32px "Courier New", monospace';
const lines = data.code.split("\n");
let y = 160;
lines.forEach((line) => {
let x = 40;
const parts = line.split(/(<span class="[^"]+">.*?<\/span>)/g);
parts.forEach((part) => {
if (part.startsWith("<span")) {
const match = part.match(/class="(.*?)"\s*>(.*?)</);
if (match) {
const cls = match[1];
const text = match[2];
ctx.fillStyle =
cls === "keyword"
? "#ff00ff"
: cls === "function"
? "#00ffff"
: "#667788";
ctx.fillText(text, x, y);
x += ctx.measureText(text).width;
}
} else {
ctx.fillStyle = "#aaccff";
ctx.fillText(part, x, y);
x += ctx.measureText(part).width;
}
});
y += 45;
});
const tex = new THREE.CanvasTexture(canvas);
const mat = new THREE.MeshBasicMaterial({
map: tex,
transparent: true,
side: THREE.DoubleSide,
depthWrite: false
});
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(16, 8), mat);
mesh.renderOrder = 20;
return mesh;
}
function createCrossAttentionBridge(xS, yS, xE, yE) {
const group = new THREE.Group();
const curve = new THREE.CubicBezierCurve3(
new THREE.Vector3(xS, yS, 0),
new THREE.Vector3(xS + 8, yS, 0),
new THREE.Vector3(xE - 8, yE, 0),
new THREE.Vector3(xE, yE, 0)
);
const pts = curve.getPoints(40);
const geo = new THREE.BufferGeometry().setFromPoints(pts);
const mat = new THREE.LineBasicMaterial({
color: COLORS.residual,
linewidth: 3,
transparent: true,
depthWrite: false
});
const line = new THREE.Line(geo, mat);
line.renderOrder = 16;
group.add(line);
const arrowGeo = new THREE.ConeGeometry(0.8, 2, 8);
const arrowMat = new THREE.MeshBasicMaterial({
color: COLORS.residual,
transparent: true,
depthWrite: false
});
const arrow = new THREE.Mesh(arrowGeo, arrowMat);
arrow.renderOrder = 16;
const tangent = curve.getTangent(1).normalize();
const axis = new THREE.Vector3(0, 1, 0);
arrow.quaternion.setFromUnitVectors(axis, tangent);
arrow.position.copy(curve.getPoint(1));
group.add(arrow);
const canvas = document.createElement("canvas");
canvas.width = 256;
canvas.height = 128;
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#" + new THREE.Color(COLORS.residual).getHexString();
ctx.font = 'bold 44px "Courier New", monospace';
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("Residual", 128, 64);
const tex = new THREE.CanvasTexture(canvas);
const labelMat = new THREE.MeshBasicMaterial({
map: tex,
transparent: true,
depthWrite: false
});
const labelPlane = new THREE.Mesh(new THREE.PlaneGeometry(8, 4), labelMat);
labelPlane.renderOrder = 17;
const midPt = curve.getPoint(0.5);
labelPlane.position.copy(midPt);
labelPlane.position.y += 2.5;
group.add(labelPlane);
scene.add(group);
volumes.push({
volMesh: arrow,
wireMesh: line,
labelPlane: labelPlane,
worldPosTarget: group,
baseOp: 1.0,
edgeOp: 1.0,
labelOp: 1.0,
meshFadeStart: 70,
meshFadeEnd: 150,
wireFadeStart: 70,
wireFadeEnd: 150,
isCodeOpen: false
});
}
function createEnergyCylinder(group, radius, h, colorHex, type, labelText) {
const geo = new THREE.CylinderGeometry(radius, radius, h, 32);
const mat = new THREE.MeshBasicMaterial({
color: colorHex,
transparent: true,
opacity: 0.1,
depthWrite: false,
side: THREE.DoubleSide
});
const volume = new THREE.Mesh(geo, mat);
volume.renderOrder = 15;
const canvas = document.createElement("canvas");
canvas.width = 512;
canvas.height = 256;
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#000000";
ctx.font = 'bold 52px "Courier New", monospace';
ctx.textAlign = "center";
ctx.textBaseline = "middle";
const lines = labelText.split("\n");
const startY = 128 - ((lines.length - 1) * 60) / 2;
lines.forEach((line, i) => ctx.fillText(line, 256, startY + i * 60));
const tex = new THREE.CanvasTexture(canvas);
const labelMat = new THREE.MeshBasicMaterial({
map: tex,
transparent: true,
depthWrite: false,
side: THREE.DoubleSide
});
const labelW = radius * 1.8;
const labelH = labelW * 0.5;
const labelPlane = new THREE.Mesh(
new THREE.PlaneGeometry(labelW, labelH),
labelMat
);
labelPlane.renderOrder = 17;
labelPlane.position.set(0, h / 2 - labelH / 2 - 0.2, radius + 0.05);
const subGroup = new THREE.Group();
subGroup.add(volume);
subGroup.add(labelPlane);
const volData = {
volMesh: volume,
wireMesh: null,
labelPlane: labelPlane,
worldPosTarget: group,
baseOp: 1.0,
edgeOp: 0.0,
labelOp: 1.0,
meshFadeStart: 70,
meshFadeEnd: 150,
wireFadeStart: 45,
wireFadeEnd: 85,
isCodeOpen: false
};
const codeMesh = createCodePlane(type);
if (codeMesh) {
const isRight = group.position.x >= 0;
const edgeX = isRight ? radius + 0.5 : -radius - 0.5;
codeMesh.geometry.translate(isRight ? 8 : -8, 0, 0);
codeMesh.position.set(edgeX, 0, radius + 0.1);
codeMesh.scale.set(0.001, 1, 1);
codeMesh.visible = false;
subGroup.add(codeMesh);
volData.codeMesh = codeMesh;
}
tag(subGroup, type, volData);
group.add(subGroup);
volumes.push(volData);
return subGroup;
}
function createEnergyVolume(group, w, h, d, colorHex, type, labelText) {
const geo = new THREE.BoxGeometry(w, h, d);
const mat = new THREE.MeshBasicMaterial({
color: colorHex,
transparent: true,
opacity: 0.1,
depthWrite: false,
side: THREE.DoubleSide
});
const volume = new THREE.Mesh(geo, mat);
volume.renderOrder = 15;
const edgeMat = new THREE.LineBasicMaterial({
color: colorHex,
transparent: true,
opacity: 1.0,
depthWrite: false
});
const wire = new THREE.LineSegments(new THREE.EdgesGeometry(geo), edgeMat);
wire.renderOrder = 16;
const canvas = document.createElement("canvas");
canvas.width = 512;
canvas.height = 256;
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#000000";
ctx.font = 'bold 56px "Courier New", monospace';
ctx.textAlign = "center";
ctx.textBaseline = "middle";
const lines = labelText.split("\n");
const startY = 128 - ((lines.length - 1) * 60) / 2;
lines.forEach((line, i) => ctx.fillText(line, 256, startY + i * 60));
const tex = new THREE.CanvasTexture(canvas);
const labelMat = new THREE.MeshBasicMaterial({
map: tex,
transparent: true,
depthWrite: false,
side: THREE.DoubleSide
});
const labelW = w * 0.9;
const labelH = labelW * 0.5;
const labelPlane = new THREE.Mesh(
new THREE.PlaneGeometry(labelW, labelH),
labelMat
);
labelPlane.renderOrder = 17;
labelPlane.position.set(0, h / 2 - labelH / 2 - 0.2, d / 2 + 0.05);
const subGroup = new THREE.Group();
subGroup.add(volume);
subGroup.add(wire);
subGroup.add(labelPlane);
const volData = {
volMesh: volume,
wireMesh: wire,
labelPlane: labelPlane,
worldPosTarget: group,
baseOp: 1.0,
edgeOp: 1.0,
labelOp: 1.0,
meshFadeStart: 70,
meshFadeEnd: 150,
wireFadeStart: 45,
wireFadeEnd: 85,
isCodeOpen: false
};
const codeMesh = createCodePlane(type);
if (codeMesh) {
const isRight = group.position.x >= 0;
const edgeX = isRight ? w / 2 + 0.5 : -w / 2 - 0.5;
codeMesh.geometry.translate(isRight ? 8 : -8, 0, 0);
codeMesh.position.set(edgeX, 0, d / 2 + 0.1);
codeMesh.scale.set(0.001, 1, 1);
codeMesh.visible = false;
subGroup.add(codeMesh);
volData.codeMesh = codeMesh;
}
tag(subGroup, type, volData);
group.add(subGroup);
volumes.push(volData);
return subGroup;
}
function createEncapsulationBox(
x,
y,
z,
w,
h,
d,
colorHex,
labelText,
alignLeft
) {
const group = new THREE.Group();
group.position.set(x, y, z);
const geo = new THREE.BoxGeometry(w, h, d);
const mat = new THREE.MeshBasicMaterial({
color: colorHex,
transparent: true,
opacity: 0.1,
depthWrite: false,
side: THREE.DoubleSide
});
const volume = new THREE.Mesh(geo, mat);
const edgeMat = new THREE.LineBasicMaterial({
color: colorHex,
transparent: true,
opacity: 1.0,
depthWrite: false,
linewidth: 2
});
const wire = new THREE.LineSegments(new THREE.EdgesGeometry(geo), edgeMat);
const canvas = document.createElement("canvas");
canvas.width = 512;
canvas.height = 512;
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#" + new THREE.Color(colorHex).getHexString();
ctx.font = 'bold 80px "Courier New", monospace';
ctx.textAlign = "center";
ctx.textBaseline = "middle";
const lines = labelText.split("\n");
const startY = 256 - ((lines.length - 1) * 90) / 2;
lines.forEach((line, i) => ctx.fillText(line, 256, startY + i * 90));
const tex = new THREE.CanvasTexture(canvas);
const labelMat = new THREE.MeshBasicMaterial({
map: tex,
transparent: true,
depthWrite: false,
side: THREE.DoubleSide
});
const labelW = w * 0.8;
const labelH = labelW * 1.0;
const labelPlane = new THREE.Mesh(
new THREE.PlaneGeometry(labelW, labelH),
labelMat
);
if (alignLeft)
labelPlane.position.set(-w / 2 - labelW / 2 + 1, 0, d / 2 + 0.1);
else labelPlane.position.set(w / 2 + labelW / 2 - 1, 0, d / 2 + 0.1);
group.add(volume);
group.add(wire);
group.add(labelPlane);
scene.add(group);
volumes.push({
volMesh: volume,
wireMesh: wire,
labelPlane: labelPlane,
worldPosTarget: group,
baseOp: 0.02,
edgeOp: 1.0,
labelOp: 1.0,
meshFadeStart: 80,
meshFadeEnd: 160,
wireFadeStart: 80,
wireFadeEnd: 160,
isCodeOpen: false
});
}
function toggleCode(vol) {
if (!vol.codeMesh) return;
if (vol.isCodeOpen) {
gsap.to(vol.codeMesh.scale, {
x: 0.001,
duration: 0.25,
ease: "power2.in",
onComplete: () => {
vol.codeMesh.visible = false;
}
});
vol.isCodeOpen = false;
} else {
volumes.forEach((v) => {
if (v !== vol && v.isCodeOpen) toggleCode(v);
});
vol.codeMesh.visible = true;
vol.codeMesh.scale.x = 0.001;
gsap.to(vol.codeMesh.scale, { x: 1, duration: 0.3, ease: "back.out(1.2)" });
vol.isCodeOpen = true;
}
}
function createTokenEmbedding(x, y, z) {
const group = new THREE.Group();
group.position.set(x, y, z);
createEnergyVolume(
group,
10,
8,
10,
COLORS.embed,
"token-embedding",
"TOKEN\nEMBEDDING"
);
const baseRing = new THREE.Mesh(
new THREE.TorusGeometry(10, 0.08, 16, 64),
new THREE.MeshBasicMaterial({
color: 0x4488ff,
transparent: true,
opacity: 0.5
})
);
baseRing.position.y = -6;
baseRing.rotation.x = Math.PI / 2;
group.add(baseRing);
const streamGroup = new THREE.Group();
const funnelColor = new THREE.Color(0x4488ff);
for (let i = 0; i < 40; i++) {
const startX = (Math.random() - 0.5) * 20;
const startZ = (Math.random() - 0.5) * 20;
const endX = (Math.random() - 0.5) * 4;
const endZ = (Math.random() - 0.5) * 4;
const curve = new THREE.CubicBezierCurve3(
new THREE.Vector3(startX, -6, startZ),
new THREE.Vector3(startX * 0.5, -4, startZ * 0.5),
new THREE.Vector3(endX, -3, endZ),
new THREE.Vector3(endX, -2, endZ)
);
const pts = curve.getPoints(20);
const mat = new THREE.LineBasicMaterial({
color: funnelColor,
transparent: true,
opacity: 0.25,
blending: THREE.AdditiveBlending
});
const line = new THREE.Line(
new THREE.BufferGeometry().setFromPoints(pts),
mat
);
streamGroup.add(line);
}
group.add(streamGroup);
const matrix = new THREE.Group();
for (let i = 0; i < 6; i++) {
for (let j = 0; j < 6; j++) {
if (Math.random() > 0.2) {
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(0.5, 0.5, 0.5),
new THREE.MeshStandardMaterial({
color: COLORS.embed,
emissive: COLORS.embed,
emissiveIntensity: 2.0 + Math.random() * 2
})
);
mesh.position.set(i * 0.8 - 2.0, -2, j * 0.8 - 2.0);
matrix.add(mesh);
}
}
}
group.add(matrix);
const lookup = new THREE.Mesh(
new THREE.CylinderGeometry(3.5, 3.5, 0.2, 16),
new THREE.MeshStandardMaterial({
color: 0xffffff,
emissive: COLORS.embed,
emissiveIntensity: 4.0
})
);
lookup.position.y = 0.5;
group.add(lookup);
const webColor = new THREE.Color(COLORS.embed).multiplyScalar(2.0);
const wireMat = new THREE.LineBasicMaterial({
color: webColor,
transparent: true,
opacity: 0.8,
depthWrite: false
});
const pts = [];
for (let i = 0; i < 12; i++) {
pts.push((Math.random() - 0.5) * 4, -2, (Math.random() - 0.5) * 4);
pts.push((Math.random() - 0.5) * 6, 0.5, (Math.random() - 0.5) * 6);
}
const wires = new THREE.LineSegments(
new THREE.BufferGeometry().setAttribute(
"position",
new THREE.Float32BufferAttribute(pts, 3)
),
wireMat
);
wires.renderOrder = 10;
group.add(wires);
scene.add(group);
}
function createPositionalEncoding(x, y, z) {
const group = new THREE.Group();
group.position.set(x, y, z);
createEnergyCylinder(
group,
5.5,
8,
COLORS.pe,
"pos-encoding",
"POS\nENCODING"
);
const core = new THREE.Mesh(
new THREE.CylinderGeometry(0.6, 0.6, 7.5, 32),
new THREE.MeshStandardMaterial({
color: 0xffffff,
emissive: 0xaaccff,
emissiveIntensity: 2.0,
transparent: true,
opacity: 0.3
})
);
group.add(core);
animatedObjects.push({ mesh: core, type: "pulse", speed: 0.05 });
const numDims = 64;
for (let i = 0; i < numDims; i++) {
const isSine = i % 2 === 0;
const color = isSine ? COLORS.posSine : COLORS.posCosine;
const dimPair = Math.floor(i / 2);
const freq = 20.0 / Math.pow(10, dimPair / (numDims / 2));
const pts = [];
const baseRadius = 1.0 + (dimPair / (numDims / 2)) * 4.0;
const angle = (i / numDims) * Math.PI * 2;
for (let j = 0; j <= 100; j++) {
const t = j / 100;
const h = t * 7.5 - 3.75;
const phaseOffset = isSine ? 0 : Math.PI / 2;
const waveOffset = Math.sin(h * freq + phaseOffset) * 0.3;
pts.push(
Math.cos(angle) * (baseRadius + waveOffset),
h,
Math.sin(angle) * (baseRadius + waveOffset)
);
}
const lineMat = new THREE.LineBasicMaterial({
color: new THREE.Color(color).multiplyScalar(2.0),
transparent: true,
opacity: 0.4,
depthWrite: false,
blending: THREE.AdditiveBlending
});
const line = new THREE.Line(
new THREE.BufferGeometry().setAttribute(
"position",
new THREE.Float32BufferAttribute(pts, 3)
),
lineMat
);
line.renderOrder = 10;
group.add(line);
const pulse = new THREE.Mesh(
new THREE.SphereGeometry(0.06, 8, 8),
new THREE.MeshBasicMaterial({ color: 0xffffff })
);
group.add(pulse);
animatedObjects.push({
mesh: pulse,
type: "pe_pulse",
curvePts: pts,
speed: 0.15 + freq * 0.05,
offset: Math.random()
});
}
for (let i = 0; i < 5; i++) {
const ring = new THREE.Mesh(
new THREE.TorusGeometry(5.2, 0.04, 16, 64),
new THREE.MeshBasicMaterial({
color: 0xffffff,
transparent: true,
opacity: 0.8,
blending: THREE.AdditiveBlending
})
);
ring.rotation.x = Math.PI / 2;
group.add(ring);
animatedObjects.push({ mesh: ring, type: "scan_ring", offset: i / 5 });
}
scene.add(group);
}
function createAddNorm(x, y, z) {
const group = new THREE.Group();
group.position.set(x, y, z);
createEnergyVolume(
group,
6,
4.5,
6,
COLORS.addNorm,
"add-norm",
"ADD &\nNORM"
);
const chamber = new THREE.Group();
group.add(chamber);
chamber.add(
new THREE.Mesh(
new THREE.CylinderGeometry(1.5, 1.5, 1.8, 16, 1, true),
createGlass(COLORS.addNorm, 0.3)
)
);
const capMat = new THREE.MeshStandardMaterial({
color: 0x444455,
metalness: 0.8,
roughness: 0.2
});
const top = new THREE.Mesh(
new THREE.CylinderGeometry(1.6, 1.6, 0.1, 16),
capMat
);
top.position.y = 0.9;
chamber.add(top);
const bot = top.clone();
bot.position.y = -0.9;
chamber.add(bot);
const plasma = new THREE.Mesh(
new THREE.CylinderGeometry(0.6, 0.6, 1.6, 16),
new THREE.MeshStandardMaterial({
color: COLORS.addNorm,
emissive: COLORS.addNorm,
emissiveIntensity: 2.0,
transparent: true,
opacity: 0.8
})
);
chamber.add(plasma);
animatedObjects.push({ mesh: plasma, type: "pulse", speed: 0.02 });
const rGeo = new THREE.TorusGeometry(1.2, 0.04, 8, 32);
const gR = new THREE.Mesh(
rGeo,
new THREE.MeshBasicMaterial({ color: 0x00ffcc })
);
gR.position.y = 1.1;
gR.rotation.x = Math.PI / 2;
group.add(gR);
const bR = new THREE.Mesh(
rGeo,
new THREE.MeshBasicMaterial({ color: 0xaa88ff })
);
bR.position.y = 1.3;
bR.rotation.x = Math.PI / 2;
group.add(bR);
scene.add(group);
}
function createAttention(x, y, z, isCross = false, isMasked = false) {
const group = new THREE.Group();
group.position.set(x, y, z);
const labelText = isCross
? "CROSS\nATTENTION"
: isMasked
? "MASKED\nATTENTION"
: "SELF\nATTENTION";
const typeText = isCross
? "cross-attention"
: isMasked
? "masked-attention"
: "self-attention";
createEnergyVolume(group, 11, 7.5, 11, COLORS.attention, typeText, labelText);
const baseColor = COLORS.attention;
const ringGeo = new THREE.TorusGeometry(1.3, 0.06, 12, 48);
[
[-3.2, COLORS.qLine],
[-2.9, COLORS.kLine],
[-2.6, COLORS.vLine]
].forEach((p) => {
const r = new THREE.Mesh(
ringGeo,
new THREE.MeshStandardMaterial({
color: p[1],
emissive: p[1],
emissiveIntensity: 3.0
})
);
r.position.y = p[0];
r.rotation.x = Math.PI / 2;
group.add(r);
});
if (isCross) {
const receptor = new THREE.Mesh(
new THREE.TorusGeometry(1.5, 0.2, 12, 16),
new THREE.MeshStandardMaterial({
color: COLORS.residual,
emissive: COLORS.residual,
emissiveIntensity: 2.0
})
);
receptor.position.set(-8, 0, 0);
receptor.rotation.y = Math.PI / 2;
group.add(receptor);
}
const ptsAttn = [];
for (let h = 0; h < NUM_HEADS; h++) {
const ha = (h / NUM_HEADS) * Math.PI * 2;
const hx = 4.2 * Math.cos(ha);
const hz = 4.2 * Math.sin(ha);
const head = new THREE.Group();
head.position.set(hx, 0, hz);
head.lookAt(0, 2.0, 0);
const qm = new THREE.Mesh(
particleGeometries.sphere,
new THREE.MeshStandardMaterial({
color: COLORS.qLine,
emissive: COLORS.qLine,
emissiveIntensity: 3
})
);
qm.position.set(-0.7, -1.5, 0);
qm.scale.set(0.6, 0.6, 0.6);
head.add(qm);
const km = new THREE.Mesh(
particleGeometries.sphere,
new THREE.MeshStandardMaterial({
color: COLORS.kLine,
emissive: COLORS.kLine,
emissiveIntensity: 3
})
);
km.position.set(0.7, -1.5, 0);
km.scale.set(0.6, 0.6, 0.6);
head.add(km);
const vm = new THREE.Mesh(
particleGeometries.sphere,
new THREE.MeshStandardMaterial({
color: COLORS.vLine,
emissive: COLORS.vLine,
emissiveIntensity: 3
})
);
vm.position.set(1.5, 0.5, 0);
vm.scale.set(0.6, 0.6, 0.6);
head.add(vm);
const dpNode = new THREE.Mesh(
new THREE.OctahedronGeometry(0.3, 0),
new THREE.MeshStandardMaterial({
color: 0xffffff,
emissive: 0xffffff,
emissiveIntensity: 5,
wireframe: true
})
);
dpNode.position.set(0, -0.5, 0);
head.add(dpNode);
animatedObjects.push({ mesh: dpNode, type: "spin", speed: 0.05 });
const matrix = new THREE.Group();
matrix.position.set(0, 0.8, 0);
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
const isMaskedOut = isMasked && j > i;
const c = isMaskedOut ? 0xff0000 : baseColor;
const emi = isMaskedOut ? 0.0 : 1.0 + Math.random();
const op = isMaskedOut ? 0.1 : 0.8;
const tile = new THREE.Mesh(
new THREE.BoxGeometry(0.2, 0.2, 0.05),
new THREE.MeshStandardMaterial({
color: c,
emissive: c,
emissiveIntensity: emi,
transparent: true,
opacity: op
})
);
tile.position.set(i * 0.25 - 0.375, j * 0.25 - 0.375, 0);
matrix.add(tile);
if (isMaskedOut) {
const cross = new THREE.LineSegments(
new THREE.BufferGeometry().setAttribute(
"position",
new THREE.Float32BufferAttribute(
[
-0.06,
-0.06,
0.03,
0.06,
0.06,
0.03,
-0.06,
0.06,
0.03,
0.06,
-0.06,
0.03
],
3
)
),
new THREE.LineBasicMaterial({ color: 0xff0000 })
);
tile.add(cross);
}
}
}
head.add(matrix);
const outNode = new THREE.Mesh(
particleGeometries.sphere,
new THREE.MeshStandardMaterial({
color: 0xffffff,
emissive: baseColor,
emissiveIntensity: 4
})
);
outNode.position.set(0, 2.2, 0);
outNode.scale.set(0.8, 0.8, 0.8);
head.add(outNode);
head.updateMatrixWorld();
const localToGroup = (mesh) => {
const vec = new THREE.Vector3();
mesh.getWorldPosition(vec);
group.worldToLocal(vec);
return vec;
};
const qw = localToGroup(qm);
const kw = localToGroup(km);
const vw = localToGroup(vm);
const dw = localToGroup(dpNode);
const mw = localToGroup(matrix);
const ow = localToGroup(outNode);
ptsAttn.push(
qw.x,
qw.y,
qw.z,
dw.x,
dw.y,
dw.z,
kw.x,
kw.y,
kw.z,
dw.x,
dw.y,
dw.z,
dw.x,
dw.y,
dw.z,
mw.x,
mw.y,
mw.z,
vw.x,
vw.y,
vw.z,
mw.x,
mw.y,
mw.z,
mw.x,
mw.y,
mw.z,
ow.x,
ow.y,
ow.z
);
ptsAttn.push(0, -3.2, 0, qw.x, qw.y, qw.z);
if (isCross) {
ptsAttn.push(-8, 0, 0, kw.x, kw.y, kw.z, -8, 0, 0, vw.x, vw.y, vw.z);
} else {
ptsAttn.push(0, -2.9, 0, kw.x, kw.y, kw.z, 0, -2.6, 0, vw.x, vw.y, vw.z);
}
ptsAttn.push(ow.x, ow.y, ow.z, 0, 3.2, 0);
group.add(head);
}
const webColor = new THREE.Color(baseColor).multiplyScalar(2.0);
const qkvMat = new THREE.LineBasicMaterial({
color: webColor,
transparent: true,
opacity: 0.3,
blending: THREE.AdditiveBlending,
depthWrite: false
});
const qkvWeb = new THREE.LineSegments(
new THREE.BufferGeometry().setAttribute(
"position",
new THREE.Float32BufferAttribute(ptsAttn, 3)
),
qkvMat
);
qkvWeb.renderOrder = 10;
group.add(qkvWeb);
const woRing = new THREE.Mesh(
new THREE.TorusGeometry(1.5, 0.08, 16, 48),
new THREE.MeshStandardMaterial({
color: baseColor,
emissive: baseColor,
emissiveIntensity: 2.0
})
);
woRing.position.y = 3.2;
woRing.rotation.x = Math.PI / 2;
group.add(woRing);
scene.add(group);
}
function createFeedForward(x, y, z) {
const group = new THREE.Group();
group.position.set(x, y, z);
createEnergyVolume(group, 15, 6.5, 15, COLORS.ffn, "ffn", "FEED\nFORWARD");
const color = COLORS.ffn;
const numIn = 16;
const numHid = 64;
const rIn = 1.2;
const rHid = 4.5;
const inNodes = [];
for (let i = 0; i < numIn; i++) {
let a = (i / numIn) * Math.PI * 2;
let pos = new THREE.Vector3(Math.cos(a) * rIn, -2.5, Math.sin(a) * rIn);
inNodes.push(pos);
const m = new THREE.Mesh(
new THREE.SphereGeometry(0.1, 8, 8),
new THREE.MeshStandardMaterial({
color: 0xffffff,
emissive: 0xffffff,
emissiveIntensity: 2.0
})
);
m.position.copy(pos);
group.add(m);
}
const outNodes = [];
for (let i = 0; i < numIn; i++) {
let a = (i / numIn) * Math.PI * 2;
let pos = new THREE.Vector3(Math.cos(a) * rIn, 2.5, Math.sin(a) * rIn);
outNodes.push(pos);
const m = new THREE.Mesh(
new THREE.SphereGeometry(0.1, 8, 8),
new THREE.MeshStandardMaterial({
color: 0xffffff,
emissive: 0xffffff,
emissiveIntensity: 2.0
})
);
m.position.copy(pos);
group.add(m);
}
const hiddenNodes = [];
for (let i = 0; i < numHid; i++) {
let a = (i / numHid) * Math.PI * 2;
let pos = new THREE.Vector3(Math.cos(a) * rHid, 0, Math.sin(a) * rHid);
hiddenNodes.push(pos);
const isActive = Math.random() > 0.5;
const m = new THREE.Mesh(
new THREE.IcosahedronGeometry(0.12, 0),
new THREE.MeshStandardMaterial({
color: isActive ? color : 0x222222,
emissive: isActive ? color : 0x000000,
emissiveIntensity: isActive ? 3.0 : 0,
transparent: true,
opacity: isActive ? 0.9 : 0.2
})
);
m.position.copy(pos);
group.add(m);
}
const ptsFFN = [];
for (let i = 0; i < numIn; i++) {
for (let j = 0; j < numHid; j++) {
ptsFFN.push(
inNodes[i].x,
inNodes[i].y,
inNodes[i].z,
hiddenNodes[j].x,
hiddenNodes[j].y,
hiddenNodes[j].z
);
}
}
for (let j = 0; j < numHid; j++) {
for (let i = 0; i < numIn; i++) {
ptsFFN.push(
hiddenNodes[j].x,
hiddenNodes[j].y,
hiddenNodes[j].z,
outNodes[i].x,
outNodes[i].y,
outNodes[i].z
);
}
}
const webColor = new THREE.Color(color).multiplyScalar(1.5);
const webMat = new THREE.LineBasicMaterial({
color: webColor,
transparent: true,
opacity: 0.05,
blending: THREE.AdditiveBlending,
depthWrite: false
});
const webFFN = new THREE.LineSegments(
new THREE.BufferGeometry().setAttribute(
"position",
new THREE.Float32BufferAttribute(ptsFFN, 3)
),
webMat
);
webFFN.renderOrder = 10;
group.add(webFFN);
scene.add(group);
}
function createOutputLayer(x, y, z) {
const group = new THREE.Group();
group.position.set(x, y, z);
createEnergyVolume(
group,
20,
13,
20,
COLORS.softmax,
"softmax",
"LINEAR &\nSOFTMAX"
);
const proj = new THREE.Mesh(
new THREE.CylinderGeometry(8.5, 1.2, 6, 24, 1, true),
new THREE.MeshBasicMaterial({
color: 0x44aaff,
wireframe: true,
transparent: true,
opacity: 0.1
})
);
proj.position.y = 3;
group.add(proj);
const plate = new THREE.Mesh(
new THREE.CylinderGeometry(9, 9, 0.2, 48),
new THREE.MeshStandardMaterial({
color: 0x0a1020,
emissive: 0x111122,
emissiveIntensity: 0.5
})
);
plate.position.y = 6;
group.add(plate);
const bars = [];
for (let i = 0; i < 120; i++) {
const angle = (i / 120) * Math.PI * 2;
const bar = new THREE.Mesh(
new THREE.BoxGeometry(0.15, 1, 0.15),
new THREE.MeshStandardMaterial({
color: COLORS.softmax,
emissive: COLORS.softmax,
emissiveIntensity: 0.5
})
);
bar.geometry.translate(0, 0.5, 0);
bar.position.set(Math.cos(angle) * 8.5, 6.1, Math.sin(angle) * 8.5);
bar.lookAt(0, 6.1, 0);
group.add(bar);
bars.push(bar);
}
scene.add(group);
function loop() {
const w = Math.floor(Math.random() * 120);
window.activeSoftmaxAngle = (w / 120) * Math.PI * 2;
bars.forEach((b, i) => {
let diff = Math.abs(i - w);
if (diff > 60) diff = 120 - diff;
const p = Math.exp(-diff * 0.6);
gsap.to(b.scale, {
y: 0.1 + p * 18,
duration: 0.5,
ease: "back.out(1.5)"
});
let baseColor = new THREE.Color(COLORS.softmax);
let activeColor = new THREE.Color(0xffaa00);
let targetColor = baseColor.lerp(activeColor, p);
gsap.to(b.material.color, {
r: targetColor.r,
g: targetColor.g,
b: targetColor.b,
duration: 0.5
});
gsap.to(b.material.emissive, {
r: targetColor.r,
g: targetColor.g,
b: targetColor.b,
duration: 0.5
});
gsap.to(b.material, { emissiveIntensity: 0.5 + p * 10.0, duration: 0.5 });
});
setTimeout(loop, 900);
}
loop();
}
function buildArchitecture() {
[
[-12, false],
[12, true]
].forEach((s) => {
createTokenEmbedding(s[0], -12, 0);
createPositionalEncoding(s[0], -2, 0);
for (let i = 0; i < NUM_BLOCKS; i++) {
let by = 10 + i * 38;
if (!s[1]) {
createAttention(-12, by, 0, false, false);
createAddNorm(-12, by + 6, 0);
createFeedForward(-12, by + 16, 0);
createAddNorm(-12, by + 23, 0);
} else {
createAttention(12, by, 0, false, true);
createAddNorm(12, by + 6, 0);
createAttention(12, by + 14, 0, true, false);
createAddNorm(12, by + 20, 0);
createFeedForward(12, by + 30, 0);
createAddNorm(12, by + 37, 0);
paths.push({
curve: new THREE.CubicBezierCurve3(
new THREE.Vector3(-12, 35, 0),
new THREE.Vector3(-4, 35, 0),
new THREE.Vector3(-4, 24, 0),
new THREE.Vector3(4, 24, 0)
),
targetY: 24
});
}
}
});
createOutputLayer(12, 58, 0);
createEncapsulationBox(
-12,
21,
0,
18,
32,
18,
COLORS.encoderBox,
"THE\nENCODER",
true
);
createEncapsulationBox(
12,
28,
0,
18,
46,
18,
COLORS.decoderBox,
"THE\nDECODER",
false
);
createCrossAttentionBridge(-12, 35, 4, 24);
}
function createDataFlow() {
const mat = new THREE.MeshStandardMaterial({
emissiveIntensity: 3.5,
metalness: 0.2
});
for (let i = 0; i < 450; i++) {
const group = new THREE.Group();
const m = new THREE.Mesh(particleGeometries.cube, mat.clone());
const spriteMat = new THREE.SpriteMaterial({
map: binaryTex[0],
transparent: true,
blending: THREE.AdditiveBlending,
depthWrite: false
});
const sprite = new THREE.Sprite(spriteMat);
sprite.visible = false;
group.add(m);
group.add(sprite);
scene.add(group);
particles.push({
group: group,
mesh: m,
sprite: sprite,
type: Math.random() < 0.5 ? "encoder" : "decoder",
progress: Math.random(),
speed: 0.002 + Math.random() * 0.002,
baseAngle: Math.random() * Math.PI * 2,
isResidual: Math.random() < 0.4,
qkvRole: ["Q", "K", "V"][Math.floor(Math.random() * 3)],
headIndex: Math.floor(Math.random() * NUM_HEADS),
ffnRadius: 4.5,
ffnAngle: Math.random() * Math.PI * 2,
reluActive: Math.random() > 0.5,
currentZone: "",
targetScale: new THREE.Vector3(1, 1, 1),
prevPos: new THREE.Vector3(),
gridX: (Math.random() - 0.5) * 8,
gridZ: (Math.random() - 0.5) * 8
});
}
}
function setupInteraction() {
const can = renderer.domElement;
can.addEventListener("pointerdown", (e) => {
activePointers++;
pointerDownPos.set(e.clientX, e.clientY);
pointerCurrentPos.set(e.clientX, e.clientY);
isPointerDown = true;
isHolding = true;
pointerDownTimestamp = performance.now();
});
can.addEventListener("pointermove", (e) => {
pointerCurrentPos.set(e.clientX, e.clientY);
if (isHolding && pointerDownPos.distanceTo(pointerCurrentPos) > 15)
isHolding = false;
});
can.addEventListener("pointerup", (e) => {
activePointers = Math.max(0, activePointers - 1);
isPointerDown = false;
isHolding = false;
if (
pointerDownPos.distanceTo(new THREE.Vector2(e.clientX, e.clientY)) < 15
) {
const now = performance.now();
if (now - lastTapTime < 350) handleDoubleTap(e);
else handleRaycast(e);
lastTapTime = now;
}
});
can.addEventListener("pointercancel", (e) => {
activePointers = Math.max(0, activePointers - 1);
isPointerDown = false;
isHolding = false;
});
can.addEventListener("pointerleave", (e) => {
activePointers = Math.max(0, activePointers - 1);
isPointerDown = false;
isHolding = false;
});
}
function handleDoubleTap(e) {
controls.autoRotate = !controls.autoRotate;
mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const hit = raycaster
.intersectObjects(scene.children, true)
.find((h) => h.object.userData && h.object.userData.blockType);
if (hit) {
const tP = new THREE.Vector3().copy(
hit.object.parent ? hit.object.parent.position : hit.object.position
);
gsap.to(controls.target, { x: tP.x, y: tP.y, z: tP.z, duration: 1 });
gsap.to(camera.position, { y: tP.y + 10, duration: 1 });
}
}
function handleRaycast(e) {
mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const hit = raycaster
.intersectObjects(scene.children, true)
.find((h) => h.object.userData && h.object.userData.volumeRef);
if (hit) {
const vol = hit.object.userData.volumeRef;
if (vol.codeMesh) toggleCode(vol);
}
}
function routeAttention(x, by, dy, p, role, isC, isM) {
let t = (dy + 3.2) / 6.4,
ha = (p.headIndex / NUM_HEADS) * Math.PI * 2,
hx = 4.2 * Math.cos(ha),
hz = 4.2 * Math.sin(ha);
let px = x,
py = by + dy,
pz = 0,
color = COLORS.particle;
if (isM && isC) {
if (t < 0.4) {
let lt = t / 0.4;
px = x - 8 + (8 + hx) * lt;
pz = (role === "K" ? hz + 1.8 : hz) * lt;
color = role === "K" ? COLORS.kLine : COLORS.vLine;
} else if (t < 0.6) {
px = x + hx;
pz = role === "K" ? hz + 1.8 : hz;
color = 0xffffff;
} else {
let lt = (t - 0.6) / 0.4;
px = (x + hx) * (1 - lt) + x * lt;
pz = hz * (1 - lt);
color = COLORS.attention;
}
} else {
if (t < 0.15) {
color =
role === "Q"
? COLORS.qLine
: role === "K"
? COLORS.kLine
: COLORS.vLine;
} else if (t < 0.3) {
let lt = (t - 0.15) / 0.15;
px = x + hx * lt;
pz = role === "V" ? hz * lt : hz * lt + 1.8 * lt;
color =
role === "Q"
? COLORS.qLine
: role === "K"
? COLORS.kLine
: COLORS.vLine;
} else if (t < 0.5) {
px = x + hx;
pz = role === "V" ? hz : hz + 1.8;
color = role === "V" ? COLORS.vLine : 0xffffff;
} else if (t < 0.8) {
px = x + hx;
pz = hz;
color = COLORS.attention;
} else {
let lt = (t - 0.8) / 0.2;
px = (x + hx) * (1 - lt) + x * lt;
pz = hz * (1 - lt);
color = COLORS.attention;
}
}
return { px, py, pz, color, zone: "attention", scale: 1 };
}
function routeFFN(x, by, dy, p, time) {
let t = (dy + 2.5) / 5;
let numIn = 16,
numHid = 64;
let a_in =
(Math.floor((p.baseAngle / (Math.PI * 2)) * numIn) / numIn) * Math.PI * 2;
let a_hid =
(Math.floor((p.ffnAngle / (Math.PI * 2)) * numHid) / numHid) * Math.PI * 2;
let x_in = x + Math.cos(a_in) * 1.2;
let z_in = Math.sin(a_in) * 1.2;
let x_hid = x + Math.cos(a_hid) * 4.5;
let z_hid = Math.sin(a_hid) * 4.5;
let px, pz;
if (t < 0.5) {
let lt = t / 0.5;
px = x_in + (x_hid - x_in) * lt;
pz = z_in + (z_hid - z_in) * lt;
} else {
let lt = (t - 0.5) / 0.5;
px = x_hid + (x_in - x_hid) * lt;
pz = z_hid + (z_in - z_hid) * lt;
}
let py = by + dy;
let scale = !p.reluActive ? 0.3 : t < 0.5 ? 1 + t * 2 : 2 - (t - 0.5) * 2;
return {
px,
py,
pz,
color: !p.reluActive ? 0x111111 : COLORS.ffn,
zone: "ffn",
scale
};
}
function routeStack(x, y, isDec, p, time, isMem) {
if (y < -14) {
let t = Math.max(0, Math.min(1, (y + 18) / 4));
let spread = 3.0 * (1 - t) + 0.5 * t;
return {
px: x + p.gridX * spread,
py: y,
pz: p.gridZ * spread,
color: 0x88ccff,
zone: "raw_text",
scale: 1
};
}
if (y >= -14 && y < -11.5) {
return {
px: x + p.gridX * 0.5,
py: y,
pz: p.gridZ * 0.5,
color: COLORS.embed,
zone: "token_embed",
scale: 1
};
}
if (y >= -11.5 && y < -6)
return {
px: x,
py: y,
pz: 0,
color: COLORS.particle,
zone: "trunk",
scale: 1
};
if (y >= -6 && y < 2) {
return {
px: x + Math.cos(y * 2 + p.baseAngle + time) * 1.5,
py: y,
pz: Math.sin(y * 2 + p.baseAngle + time) * 1.5,
color: Math.sin(y * 4) > 0 ? COLORS.posSine : COLORS.posCosine,
zone: "pos_encoding",
scale: 1
};
}
if (y >= 2 && y < 6.8)
return {
px: x,
py: y,
pz: 0,
color: COLORS.particle,
zone: "trunk",
scale: 1
};
if (isDec && y > 58) {
let dy = y - 58;
let lt = Math.min(dy / 6, 1);
let r = 1.2 + (8.5 - 1.2) * Math.pow(lt, 3);
let startA = p.baseAngle + time * 6.0;
let targetA = window.activeSoftmaxAngle || 0;
let diff = targetA - (startA % (Math.PI * 2));
if (diff > Math.PI) diff -= Math.PI * 2;
if (diff < -Math.PI) diff += Math.PI * 2;
let currentA = startA + diff * Math.pow(lt, 5);
currentA +=
Math.sin(time * 15 + p.baseAngle * 20) * 0.5 * (1 - Math.pow(lt, 4));
let rCol = Math.floor(0x00 + 0xff * lt);
let gCol = Math.floor(0xff - 0x55 * lt);
let bCol = Math.floor(0xff - 0xff * lt);
return {
px: x + Math.cos(currentA) * r,
py: y,
pz: Math.sin(currentA) * r,
color: (rCol << 16) | (gCol << 8) | bCol,
zone: "softmax",
scale: 1 + lt
};
}
for (let i = 0; i < NUM_BLOCKS; i++) {
let by = 10 + i * 38;
if (p.isResidual && !isMem) {
const getBezier = (p0, p1, p2, t) => {
const u = 1 - t;
return new THREE.Vector3()
.addScaledVector(p0, u * u)
.addScaledVector(p1, 2 * u * t)
.addScaledVector(p2, t * t);
};
if (y >= by - 3.2 && y <= by + 5.2) {
let pt = getBezier(
new THREE.Vector3(x, by - 3.2, 0),
new THREE.Vector3(x + (isDec ? 5 : -5), by + 1.0, 0),
new THREE.Vector3(x, by + 5.2, 0),
(y - by + 3.2) / 8.4
);
return {
px: pt.x,
py: y,
pz: pt.z,
color: COLORS.residual,
zone: "residual",
scale: 1
};
}
if (isDec && y >= by + 6.8 && y <= by + 19.2) {
let pt = getBezier(
new THREE.Vector3(x, by + 6.8, 0),
new THREE.Vector3(x + 5, by + 13, 0),
new THREE.Vector3(x, by + 19.2, 0),
(y - by - 6.8) / 12.4
);
return {
px: pt.x,
py: y,
pz: pt.z,
color: COLORS.residual,
zone: "residual",
scale: 1
};
}
let fS = isDec ? by + 20.8 : by + 6.8,
fE = isDec ? by + 36.2 : by + 22.2,
fO = isDec ? -5 : 5;
if (y >= fS && y <= fE) {
let pt = getBezier(
new THREE.Vector3(x, fS, 0),
new THREE.Vector3(x + fO, (fS + fE) / 2, 0),
new THREE.Vector3(x, fE, 0),
(y - fS) / (fE - fS)
);
return {
px: pt.x,
py: y,
pz: pt.z,
color: COLORS.residual,
zone: "residual",
scale: 1
};
}
}
if (y >= by - 3.2 && y <= by + 3.2)
return routeAttention(
x,
by,
y - by,
p,
isDec ? "Q" : p.qkvRole,
false,
false
);
if (y > by + 5.2 && y < by + 6.8)
return {
px: x,
py: y,
pz: 0,
color: COLORS.addNorm,
zone: "norm",
scale: 0.6
};
if (!isDec) {
if (y >= by + 13.5 && y <= by + 18.5)
return routeFFN(x, by + 16, y - by - 16, p, time);
if (y >= by + 22.2 && y < by + 23.8)
return {
px: x,
py: y,
pz: 0,
color: COLORS.addNorm,
zone: "norm",
scale: 0.6
};
} else {
if (y >= by + 11.5 && y <= by + 16.5)
return routeAttention(
x,
by + 14,
y - by - 14,
p,
isMem ? p.qkvRole : "Q",
true,
isMem
);
if (y >= by + 19.2 && y < by + 20.8)
return {
px: x,
py: y,
pz: 0,
color: COLORS.addNorm,
zone: "norm",
scale: 0.6
};
if (y >= by + 27.5 && y <= by + 32.5)
return routeFFN(x, by + 30, y - by - 30, p, time);
if (y >= by + 36.2 && y < by + 37.8)
return {
px: x,
py: y,
pz: 0,
color: COLORS.addNorm,
zone: "norm",
scale: 0.6
};
}
}
return {
px: x,
py: y,
pz: 0,
color: COLORS.particle,
zone: "trunk",
scale: 1
};
}
function animate() {
requestAnimationFrame(animate);
const t = performance.now() * 0.001;
if (
isHolding &&
activePointers === 1 &&
performance.now() - pointerDownTimestamp > 1000
) {
camera.position.y += 0.12;
controls.target.y += 0.12;
}
controls.update();
const camPos = camera.position;
volumes.forEach((vol) => {
const worldPos = new THREE.Vector3();
vol.worldPosTarget.getWorldPosition(worldPos);
const dist = camPos.distanceTo(worldPos);
let meshFactor = Math.max(
0,
Math.min(
1,
(dist - vol.meshFadeStart) / (vol.meshFadeEnd - vol.meshFadeStart)
)
);
let wireFactor = Math.max(
0,
Math.min(
1,
(dist - vol.wireFadeStart) / (vol.wireFadeEnd - vol.wireFadeStart)
)
);
if (vol.volMesh && vol.volMesh.material)
vol.volMesh.material.opacity = vol.baseOp * meshFactor;
if (vol.wireMesh && vol.wireMesh.material)
vol.wireMesh.material.opacity = vol.edgeOp * wireFactor;
if (vol.labelPlane && vol.labelPlane.material)
vol.labelPlane.material.opacity = vol.labelOp * meshFactor;
});
animatedObjects.forEach((o) => {
if (o.type === "spin") o.mesh.rotation.y += o.speed;
if (o.type === "pulse")
o.mesh.scale.x = o.mesh.scale.z = 1 + Math.sin(t * 3) * 0.2;
if (o.type === "wave")
o.mesh.scale.x = o.mesh.scale.z =
1 + Math.sin(t * o.freq + o.offset) * 0.15;
if (o.type === "pe_pulse") {
let t_curve = (t * o.speed + o.offset) % 1.0;
let ptIdx = Math.floor(t_curve * (o.curvePts.length / 3 - 1)) * 3;
if (o.curvePts[ptIdx] !== undefined)
o.mesh.position.set(
o.curvePts[ptIdx],
o.curvePts[ptIdx + 1],
o.curvePts[ptIdx + 2]
);
}
if (o.type === "scan_ring") {
let t_scan = (t * 0.2 + o.offset) % 1.0;
o.mesh.position.y = t_scan * 7.5 - 3.75;
o.mesh.material.opacity = Math.sin(t_scan * Math.PI) * 0.8;
}
});
particles.forEach((p) => {
p.progress += p.type === "cross" ? p.speed * 0.5 : p.speed;
if (p.progress > 1) {
p.progress = 0;
p.gridX = (Math.random() - 0.5) * 8;
p.gridZ = (Math.random() - 0.5) * 8;
if (Math.random() < 0.15 && paths.length > 0) {
p.type = "cross";
p.qkvRole = Math.random() > 0.5 ? "K" : "V";
} else {
p.type = Math.random() < 0.575 ? "decoder" : "encoder";
p.qkvRole = ["Q", "K", "V"][Math.floor(Math.random() * 3)];
}
p.reluActive = Math.random() > 0.5;
}
let s;
if (p.type === "cross") {
const path = paths[Math.floor(p.baseAngle * 10) % paths.length];
if (p.progress < 0.3)
s = routeStack(
ENCODER_X,
-18 + (p.progress / 0.3) * 53,
false,
p,
t,
false
);
else if (p.progress < 0.6) {
let pt = path.curve.getPointAt((p.progress - 0.3) / 0.3);
s = {
px: pt.x,
py: pt.y,
pz: pt.z,
color: 0xffffff,
zone: "cross_bridge",
scale: 1
};
} else
s = routeStack(
DECODER_X,
path.targetY -
3.2 +
((p.progress - 0.6) / 0.4) * (64 - (path.targetY - 3.2)),
true,
p,
t,
true
);
} else
s = routeStack(
p.type === "decoder" ? DECODER_X : ENCODER_X,
-18 + p.progress * (p.type === "decoder" ? 82 : 53),
p.type === "decoder",
p,
t,
false
);
p.group.position.set(s.px, s.py, s.pz);
if (s.zone !== p.currentZone) {
p.currentZone = s.zone;
if (
s.zone === "trunk" ||
s.zone === "residual" ||
s.zone === "cross_bridge"
) {
p.mesh.visible = false;
p.sprite.visible = true;
p.sprite.material.map =
binaryTex[Math.floor(Math.random() * binaryTex.length)];
} else if (s.zone === "raw_text") {
p.mesh.visible = false;
p.sprite.visible = true;
p.sprite.material.map =
asciiTex[Math.floor(Math.random() * asciiTex.length)];
} else if (s.zone === "token_embed") {
p.mesh.visible = false;
p.sprite.visible = true;
p.sprite.material.map = idTex[Math.floor(Math.random() * idTex.length)];
} else if (s.zone === "softmax") {
p.mesh.visible = false;
p.sprite.visible = true;
p.sprite.material.map =
asciiTex[Math.floor(Math.random() * asciiTex.length)];
} else {
p.sprite.visible = false;
p.mesh.visible = true;
let geo = particleGeometries.sphere;
if (s.zone === "pos_encoding") geo = particleGeometries.torusKnot;
else if (s.zone === "attention") geo = particleGeometries.beam;
else if (s.zone === "ffn" || s.zone === "softmax")
geo = particleGeometries.dataDart;
p.mesh.geometry = geo;
}
}
if (p.sprite.visible) {
p.sprite.material.color.setHex(s.color).multiplyScalar(2.5);
p.targetScale.set(s.scale * 1.5, s.scale * 1.5, s.scale * 1.5);
p.sprite.scale.lerp(p.targetScale, 0.1);
} else {
p.mesh.material.color.setHex(s.color);
p.mesh.material.emissive.setHex(s.color);
p.mesh.material.emissiveIntensity = 2.5;
if (s.zone === "softmax") {
p.targetScale.set(s.scale * 0.5, s.scale * 0.5, s.scale * 3.0);
} else {
p.targetScale.set(s.scale, s.scale, s.scale);
}
p.mesh.scale.lerp(p.targetScale, 0.1);
if (s.zone === "attention" || s.zone === "ffn" || s.zone === "softmax") {
const v = new THREE.Vector3().subVectors(p.group.position, p.prevPos);
if (v.lengthSq() > 0.0001)
p.mesh.lookAt(p.group.position.clone().add(v));
if (s.zone === "ffn") p.mesh.rotation.z += 0.3;
} else if (s.zone === "pos_encoding") {
p.mesh.rotation.x += 0.1;
p.mesh.rotation.y += 0.15;
p.mesh.rotation.z += 0.05;
}
}
p.prevPos.copy(p.group.position);
});
composer.render();
}
</script>
</body>
</html>
效果如下

体验