동화책 속 아이스크림 계곡을 배경으로 한 기발한 스톱모션 스타일 애니메이션
Wikiprompt, 무료 프롬프트 백과사전에서
동화책 속 아이스크림 계곡을 배경으로 한 기발한 스톱모션 스타일 애니메이션 Three.js에서 절차적으로 생성된 애니메이션 복셀 산악 계곡 세계를 렌더링하기 위한 상세한 단일 파일 프롬프트로, 배회하는 주민, 낮/밤 주기, 커스텀 궤도 카메라를 포함합니다.
프롬프트 내용저장
🌐
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Voxel Mountain Valley</title>
<style>
body { margin: 0; overflow: hidden; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
#info {
position: absolute;
bottom: 20px;
left: 20px;
background: rgba(0,0,0,0.5);
color: white;
padding: 8px 16px;
border-radius: 20px;
font-size: 14px;
backdrop-filter: blur(4px);
pointer-events: none;
letter-spacing: 0.5px;
border: 1px solid rgba(255,255,255,0.2);
user-select: none;
z-index: 100;
}
#info i {
font-style: normal;
display: inline-block;
margin-right: 8px;
}
</style>
</head>
<body>
<div id="info">🖱️ Drag to rotate - Scroll to zoom</div>
<!-- Three.js from CDN -->
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.128.0/build/three.module.js"
}
}
</script>
<script type="module">
import * as THREE from 'three';
// --- Setup Scene, Camera, Renderer ---
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB); // sky blue, will change with day/night
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(25, 18, 30);
camera.lookAt(0, 4, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
// --- Lights ---
// Ambient light (will be adjusted in day/night cycle)
const ambientLight = new THREE.AmbientLight(0x404060);
scene.add(ambientLight);
// Directional light (sun/moon)
const sunLight = new THREE.DirectionalLight(0xffeedd, 1.2);
sunLight.position.set(30, 30, 20);
sunLight.castShadow = true;
sunLight.shadow.mapSize.width = 1024;
sunLight.shadow.mapSize.height = 1024;
const d = 30;
sunLight.shadow.camera.left = -d;
sunLight.shadow.camera.right = d;
sunLight.shadow.camera.top = d;
sunLight.shadow.camera.bottom = -d;
sunLight.shadow.camera.near = 1;
sunLight.shadow.camera.far = 60;
scene.add(sunLight);
// Stars (created later, hidden by default)
const stars = new THREE.Group();
const starGeo = new THREE.BufferGeometry();
const starCount = 600;
const starPos = new Float32Array(starCount * 3);
for (let i = 0; i < starCount * 3; i += 3) {
starPos[i] = (Math.random() - 0.5) * 200;
starPos[i+1] = (Math.random() * 60) + 10;
starPos[i+2] = (Math.random() - 0.5) * 200;
}
starGeo.setAttribute('position', new THREE.BufferAttribute(starPos, 3));
const starMat = new THREE.PointsMaterial({ color: 0xffffff, size: 0.15, transparent: true });
const starPoints = new THREE.Points(starGeo, starMat);
stars.add(starPoints);
scene.add(stars);
// --- Ground / Terrain (voxel style) ---
// We'll build a heightmap using a simple function: peaks at corners, valley in middle
const terrainGroup = new THREE.Group();
const size = 30; // grid size (30x30)
const voxelSize = 1.0;
const heightMap = [];
// Generate heights
for (let x = 0; x < size; x++) {
heightMap[x] = [];
for (let z = 0; z < size; z++) {
// distance from center
const dx = (x - size/2) / (size/2);
const dz = (z - size/2) / (size/2);
const dist = Math.sqrt(dx*dx + dz*dz);
// mountain peaks at corners, valley in middle
let h = 0;
// corner mountains
const cornerFactor = Math.max(0, 1 - dist * 0.8);
h += cornerFactor * 6.0;
// rolling hills
h += Math.sin(x * 0.5) * Math.cos(z * 0.5) * 1.5;
// central valley lower
h += (1 - Math.min(1, dist)) * 2.0;
// clamp
h = Math.max(0.5, Math.min(8, h));
heightMap[x][z] = Math.floor(h);
}
}
// Create voxel cubes for terrain
const terrainMat = new THREE.MeshStandardMaterial({ color: 0x6b8e23, roughness: 0.8 });
const snowMat = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.6 });
const stoneMat = new THREE.MeshStandardMaterial({ color: 0x8a8a8a, roughness: 0.9 });
const dirtMat = new THREE.MeshStandardMaterial({ color: 0x8B5A2B, roughness: 0.9 });
// We'll store all cubes in a group for performance (but we'll merge later? For simplicity, individual cubes but limited count)
// To keep voxel count modest, we use a single merged geometry? But for simplicity and to keep colors, we'll use individual cubes with shared geometry.
// But 30x30x8 = 7200 cubes max, but we only place surface and some below. Let's optimize by only placing visible cubes.
// We'll place a cube for each column at heightMap level, plus some below for thickness.
const cubeGeo = new THREE.BoxGeometry(1, 1, 1);
// To reduce draw calls, we'll use InstancedMesh? But for simplicity and to avoid complexity, we'll just add cubes.
// But 30x30 = 900 columns, each with 1-3 cubes = ~2000 cubes, acceptable.
// However, we want to keep it smooth. Let's use a merged geometry approach? Actually, let's just add cubes but with shared geometry and materials.
// But we need different colors (snow, grass, stone). We'll use groups.
const grassCubes = [];
const snowCubes = [];
const stoneCubes = [];
for (let x = 0; x < size; x++) {
for (let z = 0; z < size; z++) {
const h = heightMap[x][z];
// place top cube
const posX = x - size/2 + 0.5;
const posZ = z - size/2 + 0.5;
// determine material based on height
let mat;
if (h > 6.5) mat = snowMat;
else if (h > 4.5) mat = stoneMat;
else mat = terrainMat;
const cube = new THREE.Mesh(cubeGeo, mat);
cube.position.set(posX, h - 0.5, posZ);
cube.castShadow = true;
cube.receiveShadow = true;
terrainGroup.add(cube);
// add a couple below for thickness (only for non-snow to save)
if (h > 1) {
const cube2 = new THREE.Mesh(cubeGeo, h > 6.5 ? stoneMat : dirtMat);
cube2.position.set(posX, h - 1.5, posZ);
cube2.castShadow = true;
cube2.receiveShadow = true;
terrainGroup.add(cube2);
}
if (h > 3) {
const cube3 = new THREE.Mesh(cubeGeo, dirtMat);
cube3.position.set(posX, h - 2.5, posZ);
cube3.castShadow = true;
cube3.receiveShadow = true;
terrainGroup.add(cube3);
}
}
}
scene.add(terrainGroup);
// --- River (blue voxels) ---
// Simple river through valley: from x=5 to x=25 at z=15 area
const waterMat = new THREE.MeshStandardMaterial({ color: 0x3a6ea5, roughness: 0.2, transparent: true, opacity: 0.8 });
for (let x = 8; x < 22; x++) {
for (let z = 12; z < 18; z++) {
// only if terrain height is low (valley)
const h = heightMap[x][z];
if (h < 3.5) {
const waterCube = new THREE.Mesh(cubeGeo, waterMat);
waterCube.position.set(x - size/2 + 0.5, 0.2, z - size/2 + 0.5);
waterCube.scale.y = 0.3;
waterCube.castShadow = false;
waterCube.receiveShadow = true;
terrainGroup.add(waterCube);
}
}
}
// --- Trees (pine trees) ---
const treeGroup = new THREE.Group();
const trunkMat = new THREE.MeshStandardMaterial({ color: 0x8B4513 });
const leafMat = new THREE.MeshStandardMaterial({ color: 0x2e8b57 });
function addTree(x, z) {
const h = heightMap[Math.floor(x + size/2)]?.[Math.floor(z + size/2)];
if (h === undefined || h > 5.5) return; // only in valley/grass areas
const trunk = new THREE.Mesh(cubeGeo, trunkMat);
trunk.position.set(x, h + 0.5, z);
trunk.scale.set(0.4, 1.2, 0.4);
trunk.castShadow = true;
trunk.receiveShadow = true;
treeGroup.add(trunk);
// leaves (3 cubes)
for (let i = 0; i < 3; i++) {
const leaf = new THREE.Mesh(cubeGeo, leafMat);
leaf.position.set(x + (i-1)*0.6, h + 1.8 + i*0.5, z + (i%2)*0.5);
leaf.scale.set(0.8, 0.8, 0.8);
leaf.castShadow = true;
leaf.receiveShadow = true;
treeGroup.add(leaf);
}
}
// Place trees randomly but avoid water and high peaks
for (let i = 0; i < 40; i++) {
const x = (Math.random() - 0.5) * 20;
const z = (Math.random() - 0.5) * 20;
const h = heightMap[Math.floor(x + size/2)]?.[Math.floor(z + size/2)];
if (h !== undefined && h > 1.5 && h < 5.5) {
// avoid river area
if (z > -2 && z < 2 && x > -5 && x < 5) continue;
addTree(x, z);
}
}
scene.add(treeGroup);
// --- Village houses (5-8) ---
const houseGroup = new THREE.Group();
const wallMat = new THREE.MeshStandardMaterial({ color: 0xDEB887 });
const roofMat = new THREE.MeshStandardMaterial({ color: 0x8B0000 });
const windowMat = new THREE.MeshStandardMaterial({ color: 0xffaa00, emissive: 0xffaa00, emissiveIntensity: 0.5 });
function addHouse(x, z) {
const h = heightMap[Math.floor(x + size/2)]?.[Math.floor(z + size/2)];
if (h === undefined || h > 4.5) return;
// base
const base = new THREE.Mesh(cubeGeo, wallMat);
base.position.set(x, h + 0.5, z);
base.scale.set(1.8, 1.0, 1.8);
base.castShadow = true;
base.receiveShadow = true;
houseGroup.add(base);
// roof
const roof = new THREE.Mesh(cubeGeo, roofMat);
roof.position.set(x, h + 1.5, z);
roof.scale.set(2.0, 0.4, 2.0);
roof.castShadow = true;
roof.receiveShadow = true;
houseGroup.add(roof);
// windows (glowing)
const win1 = new THREE.Mesh(cubeGeo, windowMat);
win1.position.set(x - 0.6, h + 0.8, z + 0.9);
win1.scale.set(0.4, 0.4, 0.1);
win1.castShadow = false;
houseGroup.add(win1);
const win2 = new THREE.Mesh(cubeGeo, windowMat);
win2.position.set(x + 0.6, h + 0.8, z + 0.9);
win2.scale.set(0.4, 0.4, 0.1);
win2.castShadow = false;
houseGroup.add(win2);
}
// Place houses in a cluster
const housePositions = [
[-4, -2], [-2, -3], [0, -2], [2, -3], [4, -2], [-3, 0], [3, 0]
];
housePositions.forEach(pos => addHouse(pos[0], pos[1]));
scene.add(houseGroup);
// --- Villagers and animals (blocky characters) ---
const characters = [];
const charMat = new THREE.MeshStandardMaterial({ color: 0xE0B0FF }); // lavender
const sheepMat = new THREE.MeshStandardMaterial({ color: 0xFAFAD2 });
function createCharacter(type) {
const group = new THREE.Group();
// body
const body = new THREE.Mesh(cubeGeo, type === 'sheep' ? sheepMat : charMat);
body.scale.set(0.6, 0.8, 0.6);
body.position.y = 0.4;
body.castShadow = true;
body.receiveShadow = true;
group.add(body);
// head
const head = new THREE.Mesh(cubeGeo, type === 'sheep' ? sheepMat : new THREE.MeshStandardMaterial({ color: 0xFFDAB9 }));
head.scale.set(0.4, 0.4, 0.4);
head.position.y = 1.0;
head.castShadow = true;
head.receiveShadow = true;
group.add(head);
// legs
const legMat = new THREE.MeshStandardMaterial({ color: 0x8B4513 });
for (let i = 0; i < 4; i++) {
const leg = new THREE.Mesh(cubeGeo, legMat);
leg.scale.set(0.15, 0.3, 0.15);
leg.position.set((i%2===0?-0.2:0.2), 0.15, (i<2?-0.2:0.2));
leg.castShadow = true;
leg.receiveShadow = true;
group.add(leg);
}
return group;
}
// Place characters
for (let i = 0; i < 8; i++) {
const char = createCharacter(i < 3 ? 'sheep' : 'villager');
const x = (Math.random() - 0.5) * 12;
const z = (Math.random() - 0.5) * 12;
const h = heightMap[Math.floor(x + size/2)]?.[Math.floor(z + size/2)];
if (h === undefined || h > 4.5) continue;
char.position.set(x, h, z);
char.userData = {
target: new THREE.Vector3(x, h, z),
speed: 0.5 + Math.random() * 0.3,
pause: 0,
waddlePhase: Math.random() * Math.PI * 2
};
scene.add(char);
characters.push(char);
}
// --- Clouds (blocky) ---
const cloudGroup = new THREE.Group();
const cloudMat = new THREE.MeshStandardMaterial({ color: 0xffffff, transparent: true, opacity: 0.8 });
for (let i = 0; i < 6; i++) {
const cloud = new THREE.Group();
const cubes = [];
for (let j = 0; j < 5; j++) {
const c = new THREE.Mesh(cubeGeo, cloudMat);
c.position.set((j-2)*0.8, Math.random()*0.3, (j%2)*0.5);
c.scale.set(0.8, 0.3, 0.6);
c.castShadow = false;
cloud.add(c);
}
cloud.position.set((Math.random()-0.5)*30, 12 + Math.random()*3, (Math.random()-0.5)*30);
cloud.userData.speed = 0.02 + Math.random() * 0.03;
cloudGroup.add(cloud);
}
scene.add(cloudGroup);
// --- Day/Night Cycle variables ---
let time = 0; // 0 to 60 seconds
const cycleDuration = 60;
// --- Camera Orbit (custom) ---
let cameraAngle = 0;
let cameraRadius = 25;
let cameraHeight = 15;
let isDragging = false;
let previousMouse = { x: 0, y: 0 };
let autoRotate = true;
let autoRotateSpeed = 0.002;
// Event listeners
renderer.domElement.addEventListener('mousedown', (e) => {
isDragging = true;
autoRotate = false;
previousMouse = { x: e.clientX, y: e.clientY };
});
window.addEventListener('mouseup', () => {
isDragging = false;
autoRotate = true;
});
window.addEventListener('mousemove', (e) => {
if (isDragging) {
const dx = e.clientX - previousMouse.x;
const dy = e.clientY - previousMouse.y;
cameraAngle -= dx * 0.005;
cameraHeight += dy * 0.05;
cameraHeight = Math.max(5, Math.min(30, cameraHeight));
previousMouse = { x: e.clientX, y: e.clientY };
}
});
renderer.domElement.addEventListener('wheel', (e) => {
cameraRadius += e.deltaY * 0.02;
cameraRadius = Math.max(10, Math.min(45, cameraRadius));
});
// --- Animation Loop ---
function animate() {
requestAnimationFrame(animate);
// Day/night cycle
time = (time + 0.016) % cycleDuration; // ~60 sec
const t = time / cycleDuration;
// Sun position
const sunAngle = t * Math.PI * 2;
sunLight.position.set(Math.sin(sunAngle) * 30, Math.cos(sunAngle) * 25 + 10, 20);
// Light colors
const dayColor = new THREE.Color(0xfff5e0);
const nightColor = new THREE.Color(0x8a9bb8);
const sunsetColor = new THREE.Color(0xffaa66);
let lightColor;
let intensity;
let ambientIntensity;
let bgColor;
// Determine phase
if (t < 0.25) { // morning
const f = t / 0.25;
lightColor = dayColor.clone().lerp(sunsetColor, f);
intensity = 0.8 + f * 0.6;
ambientIntensity = 0.3 + f * 0.2;
bgColor = new THREE.Color(0x87CEEB).lerp(new THREE.Color(0xffcc88), f);
} else if (t < 0.5) { // noon
const f = (t - 0.25) / 0.25;
lightColor = sunsetColor.clone().lerp(dayColor, f);
intensity = 1.4;
ambientIntensity = 0.5;
bgColor = new THREE.Color(0xffcc88).lerp(new THREE.Color(0x87CEEB), f);
} else if (t < 0.75) { // sunset
const f = (t - 0.5) / 0.25;
lightColor = dayColor.clone().lerp(sunsetColor, f);
intensity = 1.4 - f * 0.8;
ambientIntensity = 0.5 - f * 0.2;
bgColor = new THREE.Color(0x87CEEB).lerp(new THREE.Color(0xff8844), f);
} else { // night
const f = (t - 0.75) / 0.25;
lightColor = sunsetColor.clone().lerp(nightColor, f);
intensity = 0.6 - f * 0.4;
ambientIntensity = 0.2 - f * 0.1;
bgColor = new THREE.Color(0xff8844).lerp(new THREE.Color(0x0a0a2a), f);
}
sunLight.color = lightColor;
sunLight.intensity = intensity;
ambientLight.intensity = ambientIntensity;
scene.background = bgColor;
// Stars visibility
stars.visible = t > 0.75 || t < 0.1;
// Window glow intensity
const glowIntensity = t > 0.7 || t < 0.15 ? 1.0 : 0.2;
houseGroup.children.forEach(child => {
if (child.material && child.material.emissive) {
child.material.emissiveIntensity = glowIntensity;
}
});
// Clouds movement
cloudGroup.children.forEach(cloud => {
cloud.position.x += cloud.userData.speed;
if (cloud.position.x > 20) cloud.position.x = -20;
});
// Characters wandering
characters.forEach(char => {
if (char.userData.pause > 0) {
char.userData.pause -= 0.016;
} else {
// move towards target
const target = char.userData.target;
const dx = target.x - char.position.x;
const dz = target.z - char.position.z;
const dist = Math.sqrt(dx*dx + dz*dz);
if (dist < 0.2) {
// pick new target
const h = heightMap[Math.floor(char.position.x + size/2)]?.[Math.floor(char.position.z + size/2)];
if (h !== undefined) {
let newX, newZ, newH;
let attempts = 0;
do {
newX = char.position.x + (Math.random() - 0.5) * 4;
newZ = char.position.z + (Math.random() - 0.5) * 4;
newH = heightMap[Math.floor(newX + size/2)]?.[Math.floor(newZ + size/2)];
attempts++;
} while ((newH === undefined || newH > 5 || newH < 0.5) && attempts < 10);
if (newH !== undefined && newH <= 5) {
char.userData.target.set(newX, newH, newZ);
}
char.userData.pause = 1 + Math.random() * 2;
}
} else {
// move
const moveSpeed = char.userData.speed * 0.016;
char.position.x += (dx / dist) * moveSpeed;
char.position.z += (dz / dist) * moveSpeed;
// waddle animation
char.userData.waddlePhase += 0.1;
char.position.y = char.userData.target.y + Math.sin(char.userData.waddlePhase) * 0.05;
}
}
});
// Camera auto-rotate
if (autoRotate) {
cameraAngle += autoRotateSpeed;
}
// Update camera position
camera.position.x = Math.sin(cameraAngle) * cameraRadius;
camera.position.z = Math.cos(cameraAngle) * cameraRadius;
camera.position.y = cameraHeight;
camera.lookAt(0, 4, 0);
renderer.render(scene, camera);
}
animate();
// Resize handler
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// Start with a slight auto-rotate
cameraAngle = 0.8;
</script>
</body>
</html>
전체 프롬프트를 보려면 로그인하세요
Continue with:
By logging in, you agree to our Terms of Use and Privacy Policy
사용법
이 프롬프트는 coding와 함께 사용하도록 설계되었습니다. 위의 프롬프트 내용을 복사하여 원하는 AI 도구에 붙여넣으세요.
최상의 결과를 얻으려면 자리 표시자(대괄호 또는 대문자로 표시)를 특정 요구 사항으로 사용자 지정할 수 있습니다.
토론
댓글 0개