first commit

This commit is contained in:
2025-11-14 15:25:23 +08:00
commit 911a99bd3d
39 changed files with 6658 additions and 0 deletions

497
others/demo.html Normal file
View File

@@ -0,0 +1,497 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js 技术分享 Demo</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
h1 {
text-align: center;
color: white;
margin-bottom: 30px;
font-size: 2.5em;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.demo-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.demo-card {
background: white;
border-radius: 15px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
transition: transform 0.3s;
}
.demo-card:hover {
transform: translateY(-5px);
}
.demo-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 15px;
color: white;
}
.demo-header h3 {
margin: 0;
font-size: 1.3em;
}
.demo-header p {
margin: 5px 0 0 0;
opacity: 0.9;
font-size: 0.9em;
}
.canvas-container {
width: 100%;
height: 350px;
background: #f0f0f0;
position: relative;
}
canvas {
display: block;
width: 100%;
height: 100%;
}
.controls {
padding: 15px;
background: #f9f9f9;
}
.control-group {
margin-bottom: 10px;
}
.control-group label {
display: block;
margin-bottom: 5px;
font-weight: 600;
color: #333;
}
button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
margin-right: 10px;
margin-bottom: 5px;
transition: transform 0.2s;
}
button:hover {
transform: scale(1.05);
}
input[type="range"] {
width: 100%;
}
.info-panel {
background: white;
border-radius: 15px;
padding: 30px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
.info-panel h2 {
color: #667eea;
margin-bottom: 15px;
}
.info-panel ul {
margin-left: 20px;
line-height: 1.8;
}
.concept-box {
background: #f0f4ff;
padding: 15px;
border-left: 4px solid #667eea;
margin: 10px 0;
border-radius: 5px;
}
.concept-box strong {
color: #667eea;
}
</style>
</head>
<body>
<div class="container">
<h1>🎨 Three.js 技术分享 - 互动 Demo</h1>
<div class="demo-grid">
<!-- Demo 1: 旋转立方体 -->
<div class="demo-card">
<div class="demo-header">
<h3>Demo 1: Hello Cube</h3>
<p>最基础的 Three.js 场景:旋转的彩色立方体</p>
</div>
<div class="canvas-container" id="demo1"></div>
<div class="controls">
<div class="control-group">
<label>旋转速度:</label>
<input type="range" id="speed1" min="0" max="0.05" step="0.001" value="0.01">
</div>
<button onclick="changeColor1()">随机颜色</button>
<button onclick="toggleRotation1()">暂停/继续</button>
</div>
</div>
<!-- Demo 2: 交互式场景 -->
<div class="demo-card">
<div class="demo-header">
<h3>Demo 2: 交互式多物体</h3>
<p>鼠标控制相机,点击物体改变颜色</p>
</div>
<div class="canvas-container" id="demo2"></div>
<div class="controls">
<div class="control-group">
<label>光照强度:</label>
<input type="range" id="lightIntensity" min="0" max="3" step="0.1" value="1">
</div>
<button onclick="addSphere()">添加球体</button>
<button onclick="resetScene2()">重置场景</button>
</div>
</div>
<!-- Demo 3: 动画场景 -->
<div class="demo-card">
<div class="demo-header">
<h3>Demo 3: 粒子系统</h3>
<p>动态粒子效果与后处理</p>
</div>
<div class="canvas-container" id="demo3"></div>
<div class="controls">
<div class="control-group">
<label>粒子数量: <span id="particleCount">1000</span></label>
<input type="range" id="particleSlider" min="500" max="5000" step="100" value="1000">
</div>
<button onclick="changeParticleColor()">改变颜色</button>
<button onclick="resetParticles()">重置粒子</button>
</div>
</div>
</div>
<div class="info-panel">
<h2>💡 Three.js 核心概念速记</h2>
<div class="concept-box">
<strong>Scene(场景)</strong> - 电影片场,所有3D对象的容器
</div>
<div class="concept-box">
<strong>Camera(相机)</strong> - 观众的眼睛,决定从哪个角度看场景
</div>
<div class="concept-box">
<strong>Renderer(渲染器)</strong> - 摄影机,把3D场景渲染成2D图像
</div>
<div class="concept-box">
<strong>Mesh = Geometry + Material</strong> - 网格 = 形状骨架 + 表面材质
</div>
<div class="concept-box">
<strong>Light(光源)</strong> - 片场的灯光,让物体可见
</div>
<h2 style="margin-top: 30px;">🎯 与建模师沟通清单</h2>
<ul>
<li>✅ 文件格式:优先使用 <strong>GLTF/GLB</strong> 格式</li>
<li>✅ 面数控制:移动端 &lt;5万面,PC端 &lt;20万面</li>
<li>✅ 贴图尺寸:建议 2K 以内,使用 2 的幂次方(512, 1024, 2048)</li>
<li>✅ 材质类型:推荐 <strong>PBR 材质</strong>(基于物理的渲染)</li>
<li>✅ 命名规范:使用英文,有意义的名称</li>
<li>✅ 坐标系统:统一使用米制单位</li>
<li>✅ 轴心点:确认物体中心点位置是否正确</li>
</ul>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// ========== Demo 1: 基础旋转立方体 ==========
let scene1, camera1, renderer1, cube1, isRotating1 = true;
function initDemo1() {
const container = document.getElementById('demo1');
scene1 = new THREE.Scene();
scene1.background = new THREE.Color(0x1a1a2e);
camera1 = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000);
camera1.position.z = 5;
renderer1 = new THREE.WebGLRenderer({ antialias: true });
renderer1.setSize(container.clientWidth, container.clientHeight);
container.appendChild(renderer1.domElement);
const geometry = new THREE.BoxGeometry(2, 2, 2);
const material = new THREE.MeshPhongMaterial({
color: 0x00ff88,
shininess: 100
});
cube1 = new THREE.Mesh(geometry, material);
scene1.add(cube1);
const light = new THREE.PointLight(0xffffff, 1, 100);
light.position.set(5, 5, 5);
scene1.add(light);
const ambientLight = new THREE.AmbientLight(0x404040);
scene1.add(ambientLight);
animate1();
}
function animate1() {
requestAnimationFrame(animate1);
if (isRotating1) {
const speed = parseFloat(document.getElementById('speed1').value);
cube1.rotation.x += speed;
cube1.rotation.y += speed;
}
renderer1.render(scene1, camera1);
}
function changeColor1() {
cube1.material.color.setHex(Math.random() * 0xffffff);
}
function toggleRotation1() {
isRotating1 = !isRotating1;
}
// ========== Demo 2: 交互式场景 ==========
let scene2, camera2, renderer2, objects2 = [], pointLight2;
function initDemo2() {
const container = document.getElementById('demo2');
scene2 = new THREE.Scene();
scene2.background = new THREE.Color(0x16213e);
camera2 = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000);
camera2.position.set(0, 3, 8);
camera2.lookAt(0, 0, 0);
renderer2 = new THREE.WebGLRenderer({ antialias: true });
renderer2.setSize(container.clientWidth, container.clientHeight);
container.appendChild(renderer2.domElement);
// 添加地面
const planeGeometry = new THREE.PlaneGeometry(20, 20);
const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x0f3460 });
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -Math.PI / 2;
scene2.add(plane);
// 添加初始物体
createInitialObjects();
// 光源
pointLight2 = new THREE.PointLight(0xffffff, 1);
pointLight2.position.set(0, 5, 5);
scene2.add(pointLight2);
const ambientLight = new THREE.AmbientLight(0x404040);
scene2.add(ambientLight);
// 鼠标控制
let isDragging = false;
let previousMousePosition = { x: 0, y: 0 };
renderer2.domElement.addEventListener('mousedown', () => isDragging = true);
renderer2.domElement.addEventListener('mouseup', () => isDragging = false);
renderer2.domElement.addEventListener('mousemove', (e) => {
if (isDragging) {
const deltaX = e.offsetX - previousMousePosition.x;
const deltaY = e.offsetY - previousMousePosition.y;
camera2.position.x += deltaX * 0.01;
camera2.position.y -= deltaY * 0.01;
camera2.lookAt(0, 0, 0);
}
previousMousePosition = { x: e.offsetX, y: e.offsetY };
});
// 点击改变颜色
renderer2.domElement.addEventListener('click', (e) => {
const mouse = new THREE.Vector2(
(e.offsetX / container.clientWidth) * 2 - 1,
-(e.offsetY / container.clientHeight) * 2 + 1
);
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera2);
const intersects = raycaster.intersectObjects(objects2);
if (intersects.length > 0) {
intersects[0].object.material.color.setHex(Math.random() * 0xffffff);
}
});
document.getElementById('lightIntensity').addEventListener('input', (e) => {
pointLight2.intensity = parseFloat(e.target.value);
});
animate2();
}
function createInitialObjects() {
const geometries = [
new THREE.BoxGeometry(1, 1, 1),
new THREE.SphereGeometry(0.6, 32, 32),
new THREE.ConeGeometry(0.5, 1, 32),
new THREE.TorusGeometry(0.5, 0.2, 16, 100)
];
for (let i = 0; i < 4; i++) {
const material = new THREE.MeshStandardMaterial({
color: Math.random() * 0xffffff,
metalness: 0.5,
roughness: 0.5
});
const mesh = new THREE.Mesh(geometries[i], material);
mesh.position.set((i - 1.5) * 2, 1, 0);
scene2.add(mesh);
objects2.push(mesh);
}
}
function animate2() {
requestAnimationFrame(animate2);
objects2.forEach((obj, i) => {
obj.rotation.y += 0.01;
obj.position.y = 1 + Math.sin(Date.now() * 0.001 + i) * 0.3;
});
renderer2.render(scene2, camera2);
}
function addSphere() {
const geometry = new THREE.SphereGeometry(0.5, 32, 32);
const material = new THREE.MeshStandardMaterial({
color: Math.random() * 0xffffff,
metalness: 0.5,
roughness: 0.5
});
const sphere = new THREE.Mesh(geometry, material);
sphere.position.set(Math.random() * 6 - 3, 1, Math.random() * 6 - 3);
scene2.add(sphere);
objects2.push(sphere);
}
function resetScene2() {
objects2.forEach(obj => scene2.remove(obj));
objects2 = [];
createInitialObjects();
}
// ========== Demo 3: 粒子系统 ==========
let scene3, camera3, renderer3, particles3;
function initDemo3() {
const container = document.getElementById('demo3');
scene3 = new THREE.Scene();
scene3.background = new THREE.Color(0x000000);
camera3 = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000);
camera3.position.z = 50;
renderer3 = new THREE.WebGLRenderer({ antialias: true });
renderer3.setSize(container.clientWidth, container.clientHeight);
container.appendChild(renderer3.domElement);
createParticles(1000);
document.getElementById('particleSlider').addEventListener('input', (e) => {
const count = parseInt(e.target.value);
document.getElementById('particleCount').textContent = count;
scene3.remove(particles3);
createParticles(count);
});
animate3();
}
function createParticles(count) {
const geometry = new THREE.BufferGeometry();
const positions = [];
const colors = [];
for (let i = 0; i < count; i++) {
positions.push(
Math.random() * 100 - 50,
Math.random() * 100 - 50,
Math.random() * 100 - 50
);
colors.push(Math.random(), Math.random(), Math.random());
}
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
const material = new THREE.PointsMaterial({
size: 0.5,
vertexColors: true,
transparent: true,
opacity: 0.8
});
particles3 = new THREE.Points(geometry, material);
scene3.add(particles3);
}
function animate3() {
requestAnimationFrame(animate3);
particles3.rotation.y += 0.002;
particles3.rotation.x += 0.001;
const positions = particles3.geometry.attributes.position.array;
for (let i = 0; i < positions.length; i += 3) {
positions[i + 1] += Math.sin(Date.now() * 0.001 + i) * 0.01;
}
particles3.geometry.attributes.position.needsUpdate = true;
renderer3.render(scene3, camera3);
}
function changeParticleColor() {
const colors = particles3.geometry.attributes.color.array;
for (let i = 0; i < colors.length; i += 3) {
colors[i] = Math.random();
colors[i + 1] = Math.random();
colors[i + 2] = Math.random();
}
particles3.geometry.attributes.color.needsUpdate = true;
}
function resetParticles() {
const count = parseInt(document.getElementById('particleSlider').value);
scene3.remove(particles3);
createParticles(count);
}
// 初始化所有 Demo
window.onload = () => {
initDemo1();
initDemo2();
initDemo3();
};
// 响应式处理
window.addEventListener('resize', () => {
const containers = ['demo1', 'demo2', 'demo3'];
const cameras = [camera1, camera2, camera3];
const renderers = [renderer1, renderer2, renderer3];
containers.forEach((id, i) => {
const container = document.getElementById(id);
cameras[i].aspect = container.clientWidth / container.clientHeight;
cameras[i].updateProjectionMatrix();
renderers[i].setSize(container.clientWidth, container.clientHeight);
});
});
</script>
</body>
</html>