<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
font-family: Arial, sans-serif;
background: #000;
}
canvas {
display: block;
cursor: grab;
}
canvas:active {
cursor: grabbing;
}
.info {
position: absolute;
top: 10px;
left: 10px;
color: white;
background: rgba(255, 0, 0, 0.8);
padding: 15px;
border-radius: 8px;
z-index: 100;
font-size: 14px;
line-height: 1.4;
border: 2px solid #ff4444;
}
.warning {
color: #ffff00;
font-weight: bold;
text-shadow: 0 0 5px rgba(255, 255, 0, 0.5);
}
.performance-indicator {
position: absolute;
bottom: 10px;
left: 10px;
color: white;
padding: 10px;
border-radius: 5px;
z-index: 100;
font-family: monospace;
border: 2px solid #ff4444;
}
.draw-calls {
color: #ff4444;
font-weight: bold;
font-size: 16px;
}
</style>
</head>
<body>
<div class="performance-indicator">
<div id="fps">FPS: 0</div>
<div id="draw-calls" class="draw-calls">Draw Calls: 0</div>
</div>
<script src="https://unpkg.com/[email protected]/build/three.min.js"></script>
<script>
const CONFIG = {
TOTAL_LINES: 1500000, // 总线段数:150万条
LINES_PER_GROUP: 100, // 每组线段数:100条
get MAX_TRANSFORMS() {
return Math.ceil(this.TOTAL_LINES / this.LINES_PER_GROUP);
},
get GRID_SIZE() {
const totalGroups = this.MAX_TRANSFORMS;
const cubeRoot = Math.ceil(Math.pow(totalGroups, 1 / 3));
let x = cubeRoot;
let y = cubeRoot;
let z = Math.ceil(totalGroups / (x * y));
while (x * y * z < totalGroups) {
if (x <= y) x++;
else y++;
z = Math.ceil(totalGroups / (x * y));
}
return { X: x, Y: y, Z: z };
},
LINE_LENGTH_MIN: 3,
LINE_LENGTH_MAX: 12,
GROUP_SPREAD: 30,
GROUP_SPACING: 40,
INITIAL_CAMERA_DISTANCE: 1200,
ZOOM_SPEED: 0.1,
ROTATE_SPEED: 0.01,
PAN_SPEED: 1.5,
};
class BadPerformanceRenderer {
constructor() {
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 20000);
this.renderer = new THREE.WebGLRenderer({ antialias: false });
// 存储所有独立的LineSegments对象 - 这是错误的做法!
this.lineMeshes = [];
// 相机控制
this.orbitTarget = new THREE.Vector3(0, 0, 0);
this.orbitRadius = CONFIG.INITIAL_CAMERA_DISTANCE;
this.orbitTheta = 0;
this.orbitPhi = Math.PI / 4;
this.init();
window.badRenderer = this;
}
init() {
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.setClearColor(0x1a1a1a);
document.body.appendChild(this.renderer.domElement);
this.camera.position.set(300, 300, CONFIG.INITIAL_CAMERA_DISTANCE);
this.camera.lookAt(0, 0, 0);
this.setupCameraControls();
const ambientLight = new THREE.AmbientLight(0x404040, 0.8);
this.scene.add(ambientLight);
this.createManyLineSegments();
this.setupEventListeners();
this.updateCameraPosition();
this.animate();
}
createManyLineSegments() {
// console.log('🚨 开始创建大量独立的LineSegments对象...');
const startTime = performance.now();
const gridSize = CONFIG.GRID_SIZE;
for (let groupIndex = 0; groupIndex < CONFIG.MAX_TRANSFORMS; groupIndex++) {
// ❌ 每个组都创建独立的几何体和材质 - 严重浪费资源!
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(CONFIG.LINES_PER_GROUP * 6);
const colors = new Float32Array(CONFIG.LINES_PER_GROUP * 6);
// 计算组的世界位置
const groupX = (groupIndex % gridSize.X - gridSize.X / 2) * CONFIG.GROUP_SPACING;
const groupY = (Math.floor(groupIndex / gridSize.X) % gridSize.Y - gridSize.Y / 2) * CONFIG.GROUP_SPACING;
const groupZ = (Math.floor(groupIndex / (gridSize.X * gridSize.Y)) - gridSize.Z / 2) * CONFIG.GROUP_SPACING;
// 生成组内的线段
for (let i = 0; i < CONFIG.LINES_PER_GROUP; i++) {
const localStartX = (Math.random() - 0.5) * CONFIG.GROUP_SPREAD;
const localStartY = (Math.random() - 0.5) * CONFIG.GROUP_SPREAD;
const localStartZ = (Math.random() - 0.5) * CONFIG.GROUP_SPREAD;
const length = CONFIG.LINE_LENGTH_MIN + Math.random() * (CONFIG.LINE_LENGTH_MAX - CONFIG.LINE_LENGTH_MIN);
const direction = new THREE.Vector3(
Math.random() - 0.5,
Math.random() - 0.5,
Math.random() - 0.5
).normalize();
const localEndX = localStartX + direction.x * length;
const localEndY = localStartY + direction.y * length;
const localEndZ = localStartZ + direction.z * length;
// 直接计算世界坐标 - 没有变换优化
const worldStartX = groupX + localStartX;
const worldStartY = groupY + localStartY;
const worldStartZ = groupZ + localStartZ;
const worldEndX = groupX + localEndX;
const worldEndY = groupY + localEndY;
const worldEndZ = groupZ + localEndZ;
const vertexIndex = i * 6;
positions[vertexIndex] = worldStartX;
positions[vertexIndex + 1] = worldStartY;
positions[vertexIndex + 2] = worldStartZ;
positions[vertexIndex + 3] = worldEndX;
positions[vertexIndex + 4] = worldEndY;
positions[vertexIndex + 5] = worldEndZ;
// 基于组索引生成颜色
const colorSeed = groupIndex * 137;
const r = ((colorSeed % 256) / 255) * 0.8 + 0.2;
const g = (((colorSeed * 17) % 256) / 255) * 0.8 + 0.2;
const b = (((colorSeed * 31) % 256) / 255) * 0.8 + 0.2;
colors[vertexIndex] = r;
colors[vertexIndex + 1] = g;
colors[vertexIndex + 2] = b;
colors[vertexIndex + 3] = r;
colors[vertexIndex + 4] = g;
colors[vertexIndex + 5] = b;
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
// ❌ 每个组都创建独立的材质 - 浪费GPU资源!
const material = new THREE.LineBasicMaterial({
vertexColors: true,
linewidth: 1 // 注意:大多数平台不支持linewidth > 1
});
// ❌ 创建独立的LineSegments对象 - 导致大量DrawCall!
const lineMesh = new THREE.LineSegments(geometry, material);
lineMesh.userData.groupIndex = groupIndex;
this.scene.add(lineMesh);
this.lineMeshes.push(lineMesh);
// 每1000个组输出一次进度,避免控制台刷屏
// if ((groupIndex + 1) % 1000 === 0) {
// console.log(`❌ 已创建 ${(groupIndex + 1).toLocaleString()} 个独立LineSegments对象...`);
// }
}
const endTime = performance.now();
const creationTime = endTime - startTime;
}
setupCameraControls() {
let isRotating = false;
let isPanning = false;
let lastX = 0, lastY = 0;
// 滚轮缩放
this.renderer.domElement.addEventListener('wheel', (event) => {
event.preventDefault();
const delta = event.deltaY * CONFIG.ZOOM_SPEED;
this.orbitRadius += delta;
this.orbitRadius = Math.max(100, Math.min(10000, this.orbitRadius));
this.updateCameraPosition();
});
// 鼠标控制
this.renderer.domElement.addEventListener('mousedown', (event) => {
if (event.button === 0) { // 左键 - 旋转
isRotating = true;
lastX = event.clientX;
lastY = event.clientY;
event.preventDefault();
} else if (event.button === 1) { // 中键 - 平移
isPanning = true;
lastX = event.clientX;
lastY = event.clientY;
event.preventDefault();
this.renderer.domElement.style.cursor = 'grabbing';
}
});
this.renderer.domElement.addEventListener('mousemove', (event) => {
if (isRotating) {
const deltaX = event.clientX - lastX;
const deltaY = event.clientY - lastY;
this.orbitTheta -= deltaX * CONFIG.ROTATE_SPEED;
this.orbitPhi += deltaY * CONFIG.ROTATE_SPEED;
this.orbitPhi = Math.max(0.01, Math.min(Math.PI - 0.01, this.orbitPhi));
this.updateCameraPosition();
lastX = event.clientX;
lastY = event.clientY;
} else if (isPanning) {
const deltaX = event.clientX - lastX;
const deltaY = event.clientY - lastY;
const right = new THREE.Vector3();
const up = new THREE.Vector3(0, 1, 0);
right.crossVectors(this.camera.getWorldDirection(new THREE.Vector3()), up).normalize();
up.crossVectors(right, this.camera.getWorldDirection(new THREE.Vector3())).normalize();
const panVector = right.clone().multiplyScalar(-deltaX * CONFIG.PAN_SPEED)
.add(up.clone().multiplyScalar(deltaY * CONFIG.PAN_SPEED));
this.orbitTarget.add(panVector);
this.updateCameraPosition();
lastX = event.clientX;
lastY = event.clientY;
}
});
this.renderer.domElement.addEventListener('mouseup', (event) => {
if (event.button === 0) {
isRotating = false;
} else if (event.button === 1) {
isPanning = false;
this.renderer.domElement.style.cursor = 'grab';
}
});
this.renderer.domElement.addEventListener('contextmenu', (e) => e.preventDefault());
}
updateCameraPosition() {
const x = this.orbitTarget.x + this.orbitRadius * Math.sin(this.orbitPhi) * Math.cos(this.orbitTheta);
const y = this.orbitTarget.y + this.orbitRadius * Math.cos(this.orbitPhi);
const z = this.orbitTarget.z + this.orbitRadius * Math.sin(this.orbitPhi) * Math.sin(this.orbitTheta);
this.camera.position.set(x, y, z);
this.camera.lookAt(this.orbitTarget);
}
setupEventListeners() {
window.addEventListener('resize', () => this.onWindowResize());
// 只保留基本的键盘快捷键
document.addEventListener('keydown', (event) => {
switch (event.key.toLowerCase()) {
case 'r':
// 重置相机
this.orbitTarget.set(0, 0, 0);
this.orbitRadius = CONFIG.INITIAL_CAMERA_DISTANCE;
this.orbitTheta = 0;
this.orbitPhi = Math.PI / 4;
this.updateCameraPosition();
break;
}
});
}
onWindowResize() {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
animate() {
requestAnimationFrame(() => this.animate());
// 🚨 这里每帧都会对15000+个对象进行渲染处理!
// 这是导致卡顿的主要原因!
this.renderer.render(this.scene, this.camera);
}
}
// 性能监控 - 专门监控错误示范的性能问题
class BadPerformanceMonitor {
constructor() {
this.lastTime = performance.now();
this.frames = 0;
this.fps = 0;
this.frameTimeHistory = [];
this.worstFrameTime = 0;
this.update();
}
update() {
this.frames++;
const now = performance.now();
const frameTime = now - this.lastTime;
// 记录最差帧时间
if (frameTime > this.worstFrameTime) {
this.worstFrameTime = frameTime;
}
this.frameTimeHistory.push(frameTime);
if (this.frameTimeHistory.length > 60) {
this.frameTimeHistory.shift();
}
if (now >= this.lastTime + 1000) {
this.fps = Math.round((this.frames * 1000) / (now - this.lastTime));
this.frames = 0;
this.lastTime = now;
const fpsElement = document.getElementById('fps');
const drawCallsElement = document.getElementById('draw-calls');
if (fpsElement && window.badRenderer) {
const avgFrameTime = this.frameTimeHistory.reduce((a, b) => a + b, 0) / this.frameTimeHistory.length;
fpsElement.textContent = `FPS: ${this.fps} (${avgFrameTime.toFixed(1)}ms/frame)`;
// 显示DrawCall数量 - 这是性能问题的根源
drawCallsElement.textContent = `Draw Calls: ${window.badRenderer.lineMeshes.length.toLocaleString()}`;
}
}
requestAnimationFrame(() => this.update());
}
}
document.addEventListener('DOMContentLoaded', () => {
try {
const badApp = new BadPerformanceRenderer();
const monitor = new BadPerformanceMonitor();
} catch (error) {
console.error('启动失败:', error);
}
});
// 导出配置
window.CONFIG = CONFIG;
// WebGL支持检查
function checkWebGLSupport() {
try {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
return !!gl;
} catch (e) {
return false;
}
}
if (!checkWebGLSupport()) {
console.error('❌ WebGL 不支持');
const warningDiv = document.createElement('div');
warningDiv.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(255, 0, 0, 0.9);
color: white;
padding: 20px;
border-radius: 10px;
text-align: center;
z-index: 1000;
max-width: 400px;
border: 3px solid #ff4444;
`;
warningDiv.innerHTML = `
<h3>❌ WebGL 不支持</h3>
<p>无法运行错误示范版本</p>
<p>需要支持 WebGL 的浏览器来体验性能问题</p>
`;
document.body.appendChild(warningDiv);
}
</script>
</body>
</html>
index.html
index.html