Skip to content

Instantly share code, notes, and snippets.

@lpenguin
Created February 13, 2026 22:23
Show Gist options
  • Select an option

  • Save lpenguin/e847a4a46eb1af486aa8e3affb187861 to your computer and use it in GitHub Desktop.

Select an option

Save lpenguin/e847a4a46eb1af486aa8e3affb187861 to your computer and use it in GitHub Desktop.
Ghibli-style 3D Diorama Prototype (Three.js)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ghibli Diorama Prototype</title>
<style>
body { margin: 0; overflow: hidden; background: #FFF4E0; font-family: sans-serif; }
#info { position: absolute; top: 10px; width: 100%; text-align: center; color: #6D6875; pointer-events: none; }
</style>
</head>
<body>
<div id="info">Ghibli Diorama Prototype<br>Hold to rotate • Scroll to zoom</div>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// --- SCENE SETUP ---
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xFFF4E0); // Warm sky
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(10, 8, 10);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.SoftShadowMap;
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
// --- LIGHTING (Golden Hour) ---
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const sunLight = new THREE.DirectionalLight(0xffe4b5, 1.5);
sunLight.position.set(10, 20, 10);
sunLight.castShadow = true;
sunLight.shadow.mapSize.width = 1024;
sunLight.shadow.mapSize.height = 1024;
scene.add(sunLight);
// --- GROUND ---
const groundGeo = new THREE.CylinderGeometry(8, 8, 0.5, 6); // Hexagonal base
const groundMat = new THREE.MeshStandardMaterial({ color: 0x9DBE94, roughness: 0.8 });
const ground = new THREE.Mesh(groundGeo, groundMat);
ground.receiveShadow = true;
scene.add(ground);
// --- HELPER FUNCTIONS ---
function createTree(x, z) {
const group = new THREE.Group();
// Trunk
const trunkGeo = new THREE.CylinderGeometry(0.3, 0.5, 4, 8);
const trunkMat = new THREE.MeshStandardMaterial({ color: 0x6D6875 });
const trunk = new THREE.Mesh(trunkGeo, trunkMat);
trunk.position.y = 2;
trunk.castShadow = true;
group.add(trunk);
// Foliage (Cloud style)
const foliageMat = new THREE.MeshStandardMaterial({ color: 0x52796F, roughness: 1 });
const sphereGeo = new THREE.SphereGeometry(1.5, 16, 16);
const mainCloud = new THREE.Mesh(sphereGeo, foliageMat);
mainCloud.position.y = 4.5;
mainCloud.scale.set(1.5, 1, 1.5);
mainCloud.castShadow = true;
group.add(mainCloud);
for(let i=0; i<3; i++) {
const cloud = new THREE.Mesh(sphereGeo, new THREE.MeshStandardMaterial({ color: 0x84A59D }));
cloud.position.set(Math.random()-0.5, 4 + Math.random(), Math.random()-0.5);
cloud.position.multiplyScalar(2);
cloud.scale.set(0.8, 0.8, 0.8);
cloud.castShadow = true;
group.add(cloud);
}
group.position.set(x, 0, z);
scene.add(group);
}
function createBush(x, z) {
const group = new THREE.Group();
const bushMat = new THREE.MeshStandardMaterial({ color: 0x4F6F52 });
const bushGeo = new THREE.SphereGeometry(0.8, 12, 12);
const bush = new THREE.Mesh(bushGeo, bushMat);
bush.position.y = 0.4;
bush.castShadow = true;
group.add(bush);
// Berries/Roses
const berryGeo = new THREE.SphereGeometry(0.15, 8, 8);
const berryMat = new THREE.MeshStandardMaterial({ color: 0xE5989B });
for(let i=0; i<8; i++) {
const berry = new THREE.Mesh(berryGeo, berryMat);
const angle = Math.random() * Math.PI * 2;
const r = 0.7;
berry.position.set(Math.cos(angle)*r, 0.5 + Math.random()*0.5, Math.sin(angle)*r);
group.add(berry);
}
group.position.set(x, 0, z);
scene.add(group);
}
// --- POPULATE SCENE ---
createTree(-3, -2);
createTree(4, 3);
for(let i=0; i<10; i++) {
const angle = Math.random() * Math.PI * 2;
const r = 3 + Math.random() * 3;
createBush(Math.cos(angle)*r, Math.sin(angle)*r);
}
// --- ANIMATION RECTIVITY ---
function animate() {
requestAnimationFrame(animate);
// Subtle breathing effect for foliage
scene.traverse((object) => {
if (object.isMesh && object.geometry.type === 'SphereGeometry' && object.position.y > 1) {
object.scale.x += Math.sin(Date.now() * 0.002) * 0.0001;
object.scale.y += Math.sin(Date.now() * 0.002) * 0.0001;
}
});
controls.update();
renderer.render(scene, camera);
}
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
animate();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment