Skip to content

Instantly share code, notes, and snippets.

@jprivet-dev
Created December 24, 2025 19:46
Show Gist options
  • Select an option

  • Save jprivet-dev/00b3e4ef998e714c2b8aa0596c1346fa to your computer and use it in GitHub Desktop.

Select an option

Save jprivet-dev/00b3e4ef998e714c2b8aa0596c1346fa to your computer and use it in GitHub Desktop.
Three.js - Tree low poly 02
<canvas id="treeCanvas"></canvas>
// JAVASCRIPT PANEL (CodePen)
// N'oublie pas d'ajouter ces deux URLs dans les paramètres JS de CodePen (Add External Scripts/Pens) :
// https://esm.sh/three
// https://esm.sh/three/examples/jsm/controls/OrbitControls
import * as THREE from "https://esm.sh/three";
import { OrbitControls } from "https://esm.sh/three/examples/jsm/controls/OrbitControls";
// --- 1. Initialisation de la scène, caméra, et renderer ---
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xEEEEEE);
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
const renderer = new THREE.WebGLRenderer({
antialias: true,
canvas: document.getElementById("treeCanvas")
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// --- 2. Lumières ---
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 1.0);
scene.add(hemiLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 10, 7.5);
directionalLight.castShadow = true;
// Ajustements de la shadow map pour couvrir le nouveau plan plus grand
directionalLight.shadow.mapSize.width = 2048; // Augmente la résolution pour un plus grand plan
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 80; // Augmente la portée de la lumière
directionalLight.shadow.camera.left = -25; // Étend la zone de la caméra de l'ombre
directionalLight.shadow.camera.right = 25;
directionalLight.shadow.camera.top = 25;
directionalLight.shadow.camera.bottom = -25;
directionalLight.shadow.radius = 2;
scene.add(directionalLight);
// --- 3. Sol (pour recevoir les ombres) ---
const groundGeometry = new THREE.PlaneGeometry(50, 50); // Agrandit le plan à 50x50 unités
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0xCCCCCC,
roughness: 0.9,
metalness: 0.0
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
// --- 4. Fonctions pour créer les modèles Low Poly ---
function createLowPolyPineTree() {
const tree = new THREE.Group();
const trunkGeometry = new THREE.CylinderGeometry(0.3, 0.5, 2, 6);
const trunkMaterial = new THREE.MeshStandardMaterial({
color: 0x8B4513,
roughness: 0.9,
metalness: 0.0
});
const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
trunk.position.y = 1;
trunk.castShadow = true;
trunk.receiveShadow = true;
tree.add(trunk);
const leavesMaterial = new THREE.MeshStandardMaterial({
color: 0x228B22,
roughness: 0.9,
metalness: 0.0
});
const coneBottom = new THREE.Mesh(new THREE.ConeGeometry(2.5, 4, 6), leavesMaterial);
coneBottom.position.y = 3;
coneBottom.castShadow = true;
coneBottom.receiveShadow = true;
tree.add(coneBottom);
const coneMiddle = new THREE.Mesh(new THREE.ConeGeometry(1.8, 3.5, 6), leavesMaterial);
coneMiddle.position.y = 5.5;
coneMiddle.castShadow = true;
coneMiddle.receiveShadow = true;
tree.add(coneMiddle);
const coneTop = new THREE.Mesh(new THREE.ConeGeometry(1.0, 3, 6), leavesMaterial);
coneTop.position.y = 7.5;
coneTop.castShadow = true;
coneTop.receiveShadow = true;
tree.add(coneTop);
return tree;
}
function createLowPolyDeciduousTree() {
const tree = new THREE.Group();
const trunkGeometry = new THREE.CylinderGeometry(0.4, 0.6, 2.5, 6);
const trunkMaterial = new THREE.MeshStandardMaterial({
color: 0x8B4513,
roughness: 0.9,
metalness: 0.0
});
const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
trunk.position.y = 1.25;
trunk.castShadow = true;
trunk.receiveShadow = true;
tree.add(trunk);
const leavesMaterial = new THREE.MeshStandardMaterial({
color: 0x4CAF50,
roughness: 0.9,
metalness: 0.0
});
const leafCluster1 = new THREE.Mesh(new THREE.IcosahedronGeometry(1.5, 0), leavesMaterial);
leafCluster1.position.set(0, 3.5, 0);
leafCluster1.castShadow = true;
leafCluster1.receiveShadow = true;
tree.add(leafCluster1);
const leafCluster2 = new THREE.Mesh(new THREE.IcosahedronGeometry(1.2, 0), leavesMaterial);
leafCluster2.position.set(1.0, 4.0, 0.5);
leafCluster2.castShadow = true;
leafCluster2.receiveShadow = true;
tree.add(leafCluster2);
const leafCluster3 = new THREE.Mesh(new THREE.IcosahedronGeometry(1.3, 0), leavesMaterial);
leafCluster3.position.set(-0.8, 3.8, -0.7);
leafCluster3.castShadow = true;
leafCluster3.receiveShadow = true;
tree.add(leafCluster3);
return tree;
}
function createLowPolyRock() {
const rock = new THREE.Mesh(
new THREE.SphereGeometry(1.5, 4, 4),
new THREE.MeshStandardMaterial({
color: 0x808080,
roughness: 0.9,
metalness: 0.0
})
);
rock.position.y = 0.75;
rock.castShadow = true;
rock.receiveShadow = true;
return rock;
}
// --- 5. Génération du paysage (plusieurs arbres et rochers) ---
const spawnArea = 22; // Définit la moitié de la taille de la zone de spawn (pour un plan de 50x50, c'est 25, on prend un peu moins pour rester sur le plan)
for (let i = 0; i < 15; i++) { // Augmente le nombre d'objets pour un paysage plus riche
// Génère un arbre
let tree;
if (Math.random() < 0.5) {
tree = createLowPolyPineTree();
} else {
tree = createLowPolyDeciduousTree();
}
tree.position.set(
(Math.random() - 0.5) * spawnArea * 2, // Position X aléatoire dans la zone
0,
(Math.random() - 0.5) * spawnArea * 2 // Position Z aléatoire dans la zone
);
tree.scale.setScalar(0.8 + Math.random() * 0.4);
tree.rotation.y = Math.random() * Math.PI * 2; // Rotation aléatoire sur l'axe Y
scene.add(tree);
// Génère un rocher
const rock = createLowPolyRock();
rock.position.set(
(Math.random() - 0.5) * spawnArea * 2,
0.75,
(Math.random() - 0.5) * spawnArea * 2
);
rock.scale.setScalar(0.5 + Math.random() * 1.0);
rock.rotation.y = Math.random() * Math.PI * 2; // Rotation aléatoire sur l'axe Y
scene.add(rock);
}
// --- 6. Configuration initiale de la caméra ---
camera.position.z = 30; // Recule encore un peu la caméra pour voir le plus grand plan
camera.position.y = 15; // Élève la caméra
camera.lookAt(new THREE.Vector3(0, 3, 0)); // Vise un peu plus haut pour le paysage
// --- 7. Contrôles de la caméra (OrbitControls) ---
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.target.set(0, 3, 0); // Centre la rotation autour du milieu de ton paysage
// --- 8. Boucle d'animation (Render Loop) ---
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
// --- 9. Gestion du redimensionnement de la fenêtre ---
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.178.0/three.tsl.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.178.0/three.module.js"></script>
body {
margin: 0;
overflow: hidden;
}
canvas {
display: block;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment