MOODUL 10 · TASE 4 — EDASIJÕUDNUD+

🎲 Three.js — 3D Mängud Brauseris

WebGL, 3D stseenid, valgustus, materjalid, kaamera, füüsika — kogu 3D maailm su brauseris!

⏱️ ~8-10 tundi 📝 7 harjutust 🔴 Edasijõudnud+

⚡ Samm 1 — Three.js Põhialused

Terminal
npm create vite@latest my-3d-game -- --template vanilla-ts
cd my-3d-game
npm install three @types/three
npm install -D vite
npm run dev
src/main.ts — Esimene 3D stseen
import * as THREE from 'three';

// 1. Stseen — konteiner kõigele
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a2e);
scene.fog = new THREE.Fog(0x0a0a2e, 10, 50);

// 2. Kaamera — vaade maailma
const camera = new THREE.PerspectiveCamera(
  75,                            // FOV (vaateväli)
  window.innerWidth / window.innerHeight,  // Aspect ratio
  0.1,                           // Near plane
  1000                           // Far plane
);
camera.position.set(0, 5, 10);
camera.lookAt(0, 0, 0);

// 3. Renderer — joonistab stseeni
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);

// 4. Valgus
const ambientLight = new THREE.AmbientLight(0x404080, 0.5);
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 5);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
scene.add(directionalLight);

// 5. Põrand
const floorGeometry = new THREE.PlaneGeometry(50, 50);
const floorMaterial = new THREE.MeshStandardMaterial({
  color: 0x1a1a3e,
  roughness: 0.8,
});
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.rotation.x = -Math.PI / 2;
floor.receiveShadow = true;
scene.add(floor);

// 6. Kuubik (mängija)
const playerGeometry = new THREE.BoxGeometry(1, 1, 1);
const playerMaterial = new THREE.MeshStandardMaterial({
  color: 0x22c55e,
  metalness: 0.3,
  roughness: 0.4,
});
const player = new THREE.Mesh(playerGeometry, playerMaterial);
player.position.y = 0.5;
player.castShadow = true;
scene.add(player);

// 7. Mängutsükkel
const clock = new THREE.Clock();

function animate() {
  requestAnimationFrame(animate);
  const delta = clock.getDelta();

  // Mängija rotatsioon
  player.rotation.y += delta;

  renderer.render(scene, camera);
}
animate();

// Resize
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

🏃 Samm 2 — Third-Person Kontroller

PlayerController.ts
class PlayerController {
  private velocity = new THREE.Vector3();
  private direction = new THREE.Vector3();
  private speed = 8;
  private keys: Record<string, boolean> = {};

  constructor(
    private mesh: THREE.Mesh,
    private camera: THREE.PerspectiveCamera
  ) {
    window.addEventListener('keydown', (e) => this.keys[e.key.toLowerCase()] = true);
    window.addEventListener('keyup', (e) => this.keys[e.key.toLowerCase()] = false);
  }

  update(delta: number): void {
    this.direction.set(0, 0, 0);

    if (this.keys['w']) this.direction.z -= 1;
    if (this.keys['s']) this.direction.z += 1;
    if (this.keys['a']) this.direction.x -= 1;
    if (this.keys['d']) this.direction.x += 1;

    if (this.direction.length() > 0) {
      this.direction.normalize();

      // Liikumine kaamera suunas
      const cameraDirection = new THREE.Vector3();
      this.camera.getWorldDirection(cameraDirection);
      cameraDirection.y = 0;
      cameraDirection.normalize();

      const sideDirection = new THREE.Vector3();
      sideDirection.crossVectors(this.camera.up, cameraDirection).normalize();

      const moveDirection = new THREE.Vector3();
      moveDirection.addScaledVector(cameraDirection, -this.direction.z);
      moveDirection.addScaledVector(sideDirection, -this.direction.x);
      moveDirection.normalize();

      this.mesh.position.addScaledVector(moveDirection, this.speed * delta);

      // Pööra mängija liikumise suunas
      const angle = Math.atan2(moveDirection.x, moveDirection.z);
      this.mesh.rotation.y = THREE.MathUtils.lerp(
        this.mesh.rotation.y, angle, 0.15
      );
    }

    // Kaamera jälgib mängijat
    const cameraOffset = new THREE.Vector3(0, 5, 10);
    const targetPos = this.mesh.position.clone().add(cameraOffset);
    this.camera.position.lerp(targetPos, 0.05);
    this.camera.lookAt(this.mesh.position);
  }
}

💡 Samm 3 — Materjalid ja Valgustus

materials.ts
// Erinevad materjali tüübid
const materials = {
  // Metall
  metal: new THREE.MeshStandardMaterial({
    color: 0x888888,
    metalness: 1.0,
    roughness: 0.2,
  }),

  // Puit
  wood: new THREE.MeshStandardMaterial({
    color: 0x8B4513,
    metalness: 0,
    roughness: 0.9,
  }),

  // Klaas
  glass: new THREE.MeshPhysicalMaterial({
    color: 0x88ccff,
    transmission: 0.9,
    thickness: 0.5,
    roughness: 0,
    metalness: 0,
  }),

  // Helendav (ei vaja valgust)
  glow: new THREE.MeshBasicMaterial({
    color: 0x22c55e,
  }),

  // Toon shader (joonistusfilm stiil)
  toon: new THREE.MeshToonMaterial({
    color: 0x44aa88,
  }),
};

// Valguse tüübid
function setupLighting(scene: THREE.Scene): void {
  // 1. Ambient — ühtlane taustvalgus
  scene.add(new THREE.AmbientLight(0x404080, 0.4));

  // 2. Directional — päikesevalgus (paralleelsed kiired)
  const sun = new THREE.DirectionalLight(0xffeedd, 1.2);
  sun.position.set(10, 20, 10);
  sun.castShadow = true;
  scene.add(sun);

  // 3. Point — lambipirn (kiirgab kõikjale)
  const torch = new THREE.PointLight(0xff6600, 2, 15);
  torch.position.set(0, 3, 0);
  torch.castShadow = true;
  scene.add(torch);

  // 4. Spot — prožektor (koonus)
  const spot = new THREE.SpotLight(0xffffff, 3, 20, Math.PI / 6);
  spot.position.set(0, 10, 0);
  spot.castShadow = true;
  scene.add(spot);

  // 5. Hemisphere — taevas + maapind
  scene.add(new THREE.HemisphereLight(0x87ceeb, 0x362d1b, 0.3));
}

🔫 Samm 4 — Raycasting (Klikkimine 3D-s)

raycasting.ts
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();

canvas.addEventListener('click', (event) => {
  // Normaliseeri hiire positsioon (-1 kuni +1)
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

  raycaster.setFromCamera(mouse, camera);

  // Kontrolli mille peale klikkisid
  const intersects = raycaster.intersectObjects(scene.children, true);

  if (intersects.length > 0) {
    const hit = intersects[0];
    console.log('Tabatud objekt:', hit.object.name);
    console.log('Tabamise punkt:', hit.point);
    console.log('Kaugus:', hit.distance);

    // Visuaalne tagasiside
    if (hit.object instanceof THREE.Mesh) {
      const original = (hit.object.material as THREE.MeshStandardMaterial).color.clone();
      (hit.object.material as THREE.MeshStandardMaterial).color.set(0xff0000);
      setTimeout(() => {
        (hit.object.material as THREE.MeshStandardMaterial).color.copy(original);
      }, 200);
    }
  }
});

📝 Harjutused

  • Esimene 3D stseen

    Seadista Three.js + Vite projekt. Loo stseen: põrand, 5 erinevat geomeetriat (kuubik, sfäär, silinder, torus, koonus), kaamera orbiting. Lisa varjud.

  • Third-person kontroller

    Loo WASD liikumine 3D-s, kaamera jälgib mängijat. Lisa hüppamine (gravitatsiooniga), kaamera orbit hiire liigutamisega. Maapinna kokkupõrge.

  • Materjalide galerii

    Loo 10 sfääri erinevate materjalidega: metall, puit, klaas, kivi, helendav jne. Lisa GUI sliderid (roughness, metalness, color) reaalajas muutmiseks.

  • Valgustuse labor

    Lisa stseenile iga valgusetüüp: ambient, directional, point, spot, hemisphere. GUI kontrollid iga valguse jaoks (positsioon, värv, intensiivsus). Lisa real-time varjud.

  • 3D objektide kogumine

    Loo mäng: 20 hõljuvat münti 3D maailmas. Mängija liigub ja kogub münte. Raycasting hiire klikkimisega alternatiivina. Skoor ja animatsioonid.

  • Protseduurne 3D maastik

    Genereeri 3D maastik Perlin noise'iga (muuda PlaneGeometry vertex'ite Y koordinaate). Lisa värvid kõrguse järgi. Lisa vesi udustas planeti pind.

  • Partikliefektid 3D-s

    Loo Three.js Points süsteemiga 3D partiklid: tulekahju, lumesadu, ilutulestik. Lisa custom shaderid partiklite kujutamiseks.

← Moodul 09b Moodul 10b: Shaderid →