<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>樱谷·蓝调时刻 | Voxel Landscape</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
background-color: #0d1b2a;
color: #e0e8f0;
}
#info {
position: fixed;
bottom: 16px;
left: 16px;
z-index: 20;
background: rgba(10, 20, 30, 0.55);
backdrop-filter: blur(4px);
padding: 8px 14px;
border-radius: 30px;
font-size: 12px;
letter-spacing: 0.5px;
border: 1px solid rgba(255, 180, 200, 0.15);
color: #d0e0ea;
pointer-events: none;
user-select: none;
}
#ui-controls {
position: fixed;
top: 16px;
right: 16px;
z-index: 30;
display: flex;
flex-direction: column;
gap: 8px;
align-items: flex-end;
}
.ctrl-group {
background: rgba(15, 25, 35, 0.7);
backdrop-filter: blur(4px);
padding: 8px 12px;
border-radius: 40px;
border: 1px solid rgba(200, 160, 170, 0.2);
display: flex;
gap: 6px;
flex-wrap: wrap;
justify-content: flex-end;
max-width: 200px;
pointer-events: auto;
}
.ctrl-btn, .view-btn {
background: rgba(30, 40, 55, 0.6);
color: #eceff4;
border: 1px solid rgba(255, 255, 255, 0.15);
padding: 6px 12px;
border-radius: 30px;
font-size: 12px;
font-weight: 400;
cursor: pointer;
transition: 0.2s ease;
letter-spacing: 0.3px;
backdrop-filter: blur(2px);
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
white-space: nowrap;
}
.ctrl-btn:hover, .view-btn:hover {
background: #2c405a;
border-color: rgba(255, 180, 200, 0.5);
color: #fff;
}
.view-btn.active {
background: #607b96;
border-color: #f2c6d0;
}
.ctrl-btn.icon {
width: 36px;
padding: 6px 0;
text-align: center;
}
#hide-btn::before {
content: "⌁";
font-weight: 200;
}
#save-btn::before {
content: "⭳";
font-weight: 400;
}
#reset-btn::before {
content: "⟲";
font-weight: 400;
}
#mood-group .mood {
background: transparent;
border: none;
color: #aac3d8;
padding: 4px 10px;
font-size: 12px;
border-bottom: 1px solid transparent;
transition: 0.2s;
}
#mood-group .mood.active {
color: #ffd7e5;
border-bottom: 1px solid #ff9eb5;
}
#toggle-audio {
background: transparent;
border: 1px solid transparent;
font-size: 18px;
cursor: pointer;
opacity: 0.7;
padding: 0 4px;
}
#toggle-audio:hover { opacity: 1; }
#loading {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
background: #0d1b2a;
color: #bcd0dd;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 100;
font-size: 18px;
letter-spacing: 2px;
opacity: 1;
transition: opacity 0.8s;
pointer-events: none;
}
#webgl-warning {
position: fixed;
bottom: 90px;
left: 50%;
transform: translateX(-50%);
background: #1e2a38;
color: #f0c0c0;
padding: 10px 20px;
border-radius: 40px;
font-size: 13px;
display: none;
z-index: 60;
border: 1px solid #a06a6a;
}
@media (max-width: 600px) {
#ui-controls { top: 8px; right: 8px; }
.ctrl-group { padding: 5px 8px; gap: 4px; max-width: 170px; }
.ctrl-btn, .view-btn { font-size: 10px; padding: 5px 8px; }
#info { bottom: 8px; left: 8px; font-size: 10px; padding: 4px 10px; }
}
</style>
</head>
<body>
<div id="loading">🌸 樱谷 · 生成中</div>
<div id="webgl-warning">⚠️ WebGL不可用或初始化失败</div>
<div id="ui-controls">
<div class="ctrl-group" id="view-group">
<button class="view-btn active" data-view="overview">全景</button>
<button class="view-btn" data-view="river">河畔</button>
<button class="view-btn" data-view="temple">小径</button>
<button class="view-btn" data-view="slope">山坡</button>
</div>
<div class="ctrl-group" id="mood-group">
<button class="mood active" data-mood="blue">蓝调</button>
<button class="mood" data-mood="morning">清晨</button>
<button class="mood" data-mood="rain">雨中</button>
</div>
<div class="ctrl-group" id="action-group">
<button class="ctrl-btn icon" id="reset-btn" title="重置视角"></button>
<button class="ctrl-btn icon" id="hide-btn" title="隐藏界面"></button>
<button class="ctrl-btn icon" id="save-btn" title="保存画面"></button>
<button id="toggle-audio" title="环境音">🔇</button>
</div>
</div>
<div id="info">🌸 日式樱花山谷 · 拖动/滚轮 | 蓝调时刻</div>
<!-- 引入 Three.js 核心与扩展 -->
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.128.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.128.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import { FXAAShader } from 'three/addons/shaders/FXAAShader.js';
// --- 渲染器配置 (高画质/轻量兼顾) ---
const renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: 'high-performance' });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111c2c); // 蓝调默认
scene.fog = new THREE.FogExp2(0x162a3a, 0.012);
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000);
camera.position.set(22, 14, 28);
camera.lookAt(0, 2, 0);
// 控制
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.06;
controls.maxPolarAngle = Math.PI / 2.2;
controls.minDistance = 8;
controls.maxDistance = 70;
controls.target.set(0, 4, 0);
// 后期效果: FXAA + Bloom
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
const bloom = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 0.5, 0.2, 0.1);
composer.addPass(bloom);
const fxaaPass = new ShaderPass(FXAAShader);
fxaaPass.material.uniforms['resolution'].value.set(1/window.innerWidth, 1/window.innerHeight);
composer.addPass(fxaaPass);
// --- 灯光系统 (氛围切换用) ---
const ambientLight = new THREE.AmbientLight(0x404868, 0.7);
scene.add(ambientLight);
const dirLight = new THREE.DirectionalLight(0xffe0c0, 1.2);
dirLight.position.set(10, 30, 15);
dirLight.castShadow = true;
dirLight.shadow.mapSize.width = 2048;
dirLight.shadow.mapSize.height = 2048;
dirLight.shadow.camera.near = 1;
dirLight.shadow.camera.far = 80;
dirLight.shadow.camera.left = -30;
dirLight.shadow.camera.right = 30;
dirLight.shadow.camera.top = 30;
dirLight.shadow.camera.bottom = -30;
scene.add(dirLight);
const fillLight = new THREE.DirectionalLight(0xb0c8ff, 0.4);
fillLight.position.set(-15, 5, 10);
scene.add(fillLight);
// 灯笼点光源 (氛围)
const lanternLight = new THREE.PointLight(0xffa07a, 0.8, 18);
lanternLight.position.set(3, 2.2, -2);
scene.add(lanternLight);
const lanternLight2 = new THREE.PointLight(0xffb499, 0.5, 12);
lanternLight2.position.set(-4, 1.8, 2.5);
scene.add(lanternLight2);
// --- 程序化地形 & 山谷 ---
// 使用多个立方体/组合几何体构造起伏。为了性能,用实例化网格或组合mesh。
// 地面基座
const groundMat = new THREE.MeshStandardMaterial({ color: 0x3d4a3a, roughness: 0.85, metalness: 0.1 });
const ground = new THREE.Mesh(new THREE.BoxGeometry(90, 1, 90), groundMat);
ground.position.y = -0.5;
ground.receiveShadow = true;
scene.add(ground);
// 添加高低起伏的土层 (几十个box, 可接受)
const hillMat = new THREE.MeshStandardMaterial({ color: 0x4e5a4a, roughness: 0.9, flatShading: true });
function addHill(x, z, w, h, d) {
const box = new THREE.Mesh(new THREE.BoxGeometry(w, h, d), hillMat);
box.position.set(x, h/2 - 0.2, z);
box.castShadow = true;
box.receiveShadow = true;
scene.add(box);
}
// 随机起伏
addHill(-8, 8, 12, 3.2, 12); addHill(10, 6, 10, 2.5, 10); addHill(6, -8, 8, 2.0, 8);
addHill(-6, -10, 12, 4.0, 12); addHill(0, 12, 16, 3.0, 10); addHill(15, 12, 8, 1.8, 8);
addHill(-15, -5, 10, 2.5, 12); addHill(-12, 2, 8, 1.5, 8);
// 河流底部凹陷
const riverMat = new THREE.MeshStandardMaterial({ color: 0x2f4f5f, roughness: 0.2, metalness: 0.1, emissive: 0x113a4a, emissiveIntensity: 0.1 });
const riverBox = new THREE.Mesh(new THREE.BoxGeometry(6, 0.3, 24), riverMat);
riverBox.position.set(0, 0.1, -2);
riverBox.receiveShadow = true;
scene.add(riverBox);
// 水面反射近似 (实际用材质)
const waterMat = new THREE.MeshStandardMaterial({
color: 0x1c4a5a,
roughness: 0.1,
metalness: 0.1,
emissive: 0x224a5a,
emissiveIntensity: 0.2,
transparent: true,
opacity: 0.85,
envMapIntensity: 0.5
});
const water = new THREE.Mesh(new THREE.PlaneGeometry(5.2, 23), waterMat);
water.rotation.x = -Math.PI / 2;
water.position.set(0, 0.35, -2);
scene.add(water);
// --- 樱花树 (程序化体素结构) ---
function createSakuraTree(posX, posZ, scale = 1.0) {
const group = new THREE.Group();
const trunkMat = new THREE.MeshStandardMaterial({ color: 0x8a6a4a, roughness: 0.8 });
const leafMat = new THREE.MeshStandardMaterial({ color: 0xffc0cb, roughness: 0.6, emissive: 0x8a5a6a, emissiveIntensity: 0.1 });
// 树干 (多个转折方块)
const trunkBase = new THREE.Mesh(new THREE.BoxGeometry(0.8, 2.2, 0.8), trunkMat);
trunkBase.position.y = 1.0;
trunkBase.castShadow = true;
group.add(trunkBase);
const trunkMid = new THREE.Mesh(new THREE.BoxGeometry(0.7, 1.4, 0.7), trunkMat);
trunkMid.position.set(0.3, 2.2, -0.1);
trunkMid.castShadow = true;
group.add(trunkMid);
const trunkTop = new THREE.Mesh(new THREE.BoxGeometry(0.6, 1.0, 0.6), trunkMat);
trunkTop.position.set(-0.1, 3.2, 0.3);
trunkTop.castShadow = true;
group.add(trunkTop);
// 树冠 (多个球体团块近似立方体风格)
const leafPositions = [
[0.6, 3.9, 0.3, 1.1], [-0.7, 4.0, 0.8, 0.9], [0.9, 3.5, -0.6, 0.8],
[-1.1, 3.3, -0.9, 1.0], [0.2, 4.6, -0.4, 0.7], [1.1, 4.1, 0.9, 0.7],
[-0.3, 4.8, 0.5, 0.6], [0.5, 3.0, 1.2, 0.8], [-0.8, 2.9, -0.2, 0.9]
];
leafPositions.forEach(([x, y, z, s]) => {
const leaf = new THREE.Mesh(new THREE.BoxGeometry(s, s, s), leafMat);
leaf.position.set(x, y, z);
leaf.castShadow = true;
group.add(leaf);
});
group.position.set(posX, 0, posZ);
group.scale.set(scale, scale, scale);
return group;
}
const sakura1 = createSakuraTree(-6, 6, 1.4);
sakura1.position.y -= 0.2;
scene.add(sakura1);
const sakura2 = createSakuraTree(7, 3, 1.0);
scene.add(sakura2);
const sakura3 = createSakuraTree(4, -7, 0.9);
scene.add(sakura3);
const sakura4 = createSakuraTree(-8, -5, 1.1);
scene.add(sakura4);
// --- 建筑: 红色桥、塔、神社、茶屋 ---
function createBridge() {
const group = new THREE.Group();
const woodMat = new THREE.MeshStandardMaterial({ color: 0x9a5a3a, roughness: 0.7 });
const railMat = new THREE.MeshStandardMaterial({ color: 0xb04040, roughness: 0.6 });
for (let i = -2; i <= 2; i++) {
const beam = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.2, 0.5), i % 2 === 0 ? railMat : woodMat);
beam.position.set(i * 1.0, 1.2, 0);
beam.castShadow = true;
group.add(beam);
}
// 桥面
const deck = new THREE.Mesh(new THREE.BoxGeometry(5.2, 0.2, 1.6), woodMat);
deck.position.set(0, 0.9, 0);
deck.castShadow = true;
group.add(deck);
// 栏杆
for (let j = -2; j <= 2; j++) {
for (let side = -1; side <= 1; side+=2) {
const post = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.5, 0.2), railMat);
post.position.set(j*1.0, 1.3, side*0.9);
group.add(post);
}
}
return group;
}
const bridge = createBridge();
bridge.position.set(0, 0.1, -1.5);
bridge.rotation.y = 0.2;
scene.add(bridge);
function createPagoda(x, z, hOffset = 0) {
const group = new THREE.Group();
const stoneMat = new THREE.MeshStandardMaterial({ color: 0xa08070, roughness: 0.9, flatShading: true });
const roofMat = new THREE.MeshStandardMaterial({ color: 0x6a4a3a, roughness: 0.7, emissive: 0x3a1a1a, emissiveIntensity: 0.1 });
// 塔基
const base = new THREE.Mesh(new THREE.BoxGeometry(3.2, 1, 3.2), stoneMat);
base.position.y = 0.5;
base.castShadow = true;
group.add(base);
// 三层塔身
for (let i = 0; i < 3; i++) {
const hgt = 1.2;
const body = new THREE.Mesh(new THREE.BoxGeometry(2.0 - i*0.3, hgt, 2.0 - i*0.3), stoneMat);
body.position.y = 1.2 + i*1.4;
group.add(body);
const roof = new THREE.Mesh(new THREE.BoxGeometry(3.0 - i*0.4, 0.4, 3.0 - i*0.4), roofMat);
roof.position.y = 1.8 + i*1.4;
roof.castShadow = true;
group.add(roof);
}
group.position.set(x, hOffset, z);
return group;
}
const pagoda = createPagoda(10, -6, 0);
scene.add(pagoda);
function createTemple(x, z) {
const group = new THREE.Group();
const wood = new THREE.MeshStandardMaterial({ color: 0xb07050, roughness: 0.8 });
const roofDark = new THREE.MeshStandardMaterial({ color: 0x3a2a2a, roughness: 0.6 });
const body = new THREE.Mesh(new THREE.BoxGeometry(3.2, 1.6, 3.0), wood);
body.position.y = 1.4;
body.castShadow = true;
group.add(body);
// 屋顶
const roof = new THREE.Mesh(new THREE.BoxGeometry(4.2, 0.5, 4.0), roofDark);
roof.position.y = 2.4;
group.add(roof);
// 窗格
const windowMat = new THREE.MeshStandardMaterial({ color: 0x2a2a2a, emissive: 0xffaa66, emissiveIntensity: 0.7 });
for (let i = -1; i <= 1; i++) {
const win = new THREE.Mesh(new THREE.BoxGeometry(0.7, 0.5, 0.2), windowMat);
win.position.set(i*0.8, 1.2, 1.51);
group.add(win);
}
group.position.set(x, 0, z);
return group;
}
const temple = createTemple(-5, -1.8);
scene.add(temple);
const teaHouse = createTemple(6, -0.5);
teaHouse.scale.set(0.9, 0.9, 0.9);
scene.add(teaHouse);
// 石灯笼及岩石
function addRock(x, z, s) {
const rockMat = new THREE.MeshStandardMaterial({ color: 0x7a7a6a, roughness: 0.9 });
const rock = new THREE.Mesh(new THREE.BoxGeometry(s, s*0.7, s), rockMat);
rock.position.set(x, s*0.3, z);
rock.castShadow = true;
scene.add(rock);
}
addRock(-3, 1.5, 0.7); addRock(2.5, 4, 0.9); addRock(-2.5, -3.2, 0.6); addRock(8, 1, 1.0);
// 石灯笼
function addLantern(x, z) {
const lanternGroup = new THREE.Group();
const stone = new THREE.MeshStandardMaterial({ color: 0xaaaar98, roughness: 0.7 });
const base = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.3, 0.8), stone);
base.position.y = 0.15;
lanternGroup.add(base);
const pillar = new THREE.Mesh(new THREE.BoxGeometry(0.3, 1.2, 0.3), stone);
pillar.position.y = 0.8;
lanternGroup.add(pillar);
const light = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.6, 0.6), new THREE.MeshStandardMaterial({ color: 0xffffaa, emissive: 0xffaa55, emissiveIntensity: 0.8 }));
light.position.y = 1.7;
lanternGroup.add(light);
lanternGroup.position.set(x, 0, z);
scene.add(lanternGroup);
}
addLantern(-3.5, 2.8); addLantern(5, -3.3);
// 远山
function addDistantHill(x, z, s, h) {
const mat = new THREE.MeshStandardMaterial({ color: 0x6a7a9a, roughness: 1.0, emissive: 0x2a3a6a, emissiveIntensity: 0.1 });
const hill = new THREE.Mesh(new THREE.BoxGeometry(s, h, s), mat);
hill.position.set(x, h/2, z);
hill.castShadow = true;
scene.add(hill);
}
addDistantHill(20, -15, 15, 8); addDistantHill(-20, 12, 20, 10); addDistantHill(5, -25, 18, 6);
addDistantHill(-15, -22, 14, 9);
// 远处雪山
const snowMat = new THREE.MeshStandardMaterial({ color: 0xd0e8f0, roughness: 0.7 });
const snowPeak = new THREE.Mesh(new THREE.BoxGeometry(22, 10, 22), snowMat);
snowPeak.position.set(-30, 5, -45);
scene.add(snowPeak);
// 锦鲤 (小方块)
const fishMat = new THREE.MeshStandardMaterial({ color: 0xff8844, emissive: 0xff5500, emissiveIntensity: 0.2 });
const fish1 = new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.2, 0.8), fishMat);
fish1.position.set(-0.8, 0.6, -1.0);
scene.add(fish1);
const fish2 = fish1.clone(); fish2.position.set(0.5, 0.6, 0.2);
scene.add(fish2);
// 落花 (少量漂浮)
const petalMat = new THREE.MeshStandardMaterial({ color: 0xffb0c0, emissive: 0xff88aa, emissiveIntensity: 0.3 });
for (let i=0;i<30;i++) {
const petal = new THREE.Mesh(new THREE.BoxGeometry(0.15, 0.02, 0.15), petalMat);
petal.position.set((Math.random()-0.5)*20, 2+Math.random()*5, (Math.random()-0.5)*20);
scene.add(petal);
}
// --- 氛围切换 ---
let currentMood = 'blue';
const moodBg = {
blue: new THREE.Color(0x111c2c),
morning: new THREE.Color(0xa3b8cc),
rain: new THREE.Color(0x3a4a5a)
};
const moodFog = {
blue: new THREE.FogExp2(0x162a3a, 0.012),
morning: new THREE.FogExp2(0x9ab0c0, 0.010),
rain: new THREE.FogExp2(0x3a4a5a, 0.020)
};
const moodDirIntensity = { blue: 1.2, morning: 1.8, rain: 0.5 };
const moodAmbient = { blue: 0x404868, morning: 0xccccdd, rain: 0x606870 };
function setMood(mood) {
currentMood = mood;
scene.background = moodBg[mood];
scene.fog = moodFog[mood];
dirLight.intensity = moodDirIntensity[mood];
ambientLight.color.setHex(moodAmbient[mood]);
// 雨: 降低部分光
if (mood === 'rain') {
bloom.strength = 0.2;
dirLight.position.set(5, 20, -5);
} else {
bloom.strength = 0.5;
dirLight.position.set(10, 30, 15);
}
}
// --- 镜头预设 ---
const views = {
overview: { pos: [22, 14, 28], target: [0, 4, 0] },
river: { pos: [-3, 2.5, 12], target: [0, 0.5, -2] },
temple: { pos: [-7, 4, 5], target: [-2, 1, 0] },
slope: { pos: [8, 8, -10], target: [2, 2, -3] }
};
function setView(vname) {
const v = views[vname];
if (!v) return;
controls.target.set(v.target[0], v.target[1], v.target[2]);
camera.position.set(v.pos[0], v.pos[1], v.pos[2]);
controls.update();
}
// --- UI交互 ---
document.querySelectorAll('.view-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.view-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
setView(btn.dataset.view);
});
});
document.querySelectorAll('.mood').forEach(mbtn => {
mbtn.addEventListener('click', () => {
document.querySelectorAll('.mood').forEach(b => b.classList.remove('active'));
mbtn.classList.add('active');
setMood(mbtn.dataset.mood);
});
});
document.getElementById('reset-btn').addEventListener('click', () => setView('overview'));
document.getElementById('hide-btn').addEventListener('click', () => {
document.getElementById('ui-controls').style.visibility = 'hidden';
document.getElementById('info').style.visibility = 'hidden';
setTimeout(() => {
document.getElementById('ui-controls').style.visibility = 'visible';
document.getElementById('info').style.visibility = 'visible';
}, 4000);
});
document.getElementById('save-btn').addEventListener('click', () => {
renderer.render(scene, camera);
const url = renderer.domElement.toDataURL('image/png');
const a = document.createElement('a');
a.href = url;
a.download = 'sakura-valley.png';
a.click();
});
// 音频 (无真实音频, 仅模拟图标切换)
let audioOn = false;
document.getElementById('toggle-audio').addEventListener('click', (e) => {
audioOn = !audioOn;
e.target.textContent = audioOn ? '🔊' : '🔇';
});
// 加载完成
window.setTimeout(() => {
document.getElementById('loading').style.opacity = '0';
setTimeout(() => {
document.getElementById('loading').style.display = 'none';
}, 800);
}, 1000);
// 错误处理
window.addEventListener('webglcontextcreationerror', (e) => {
document.getElementById('webgl-warning').style.display = 'block';
});
// 窗口尺寸
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
composer.setSize(window.innerWidth, window.innerHeight);
fxaaPass.material.uniforms['resolution'].value.set(1/window.innerWidth, 1/window.innerHeight);
});
// 默认蓝调
setMood('blue');
setView('overview');
// 动画循环
function animate() {
requestAnimationFrame(animate);
controls.update();
// 细微动态 (落花/鲤鱼)
// 简单不动
composer.render();
}
animate();
// 快速启动检查
console.log('🌸 樱谷渲染中');
</script>
</body>
</html>
Diskussion
0 Kommentare