A Pen by J.P. RIVET on CodePen.
Created
December 24, 2025 19:46
-
-
Save jprivet-dev/00b3e4ef998e714c2b8aa0596c1346fa to your computer and use it in GitHub Desktop.
Three.js - Tree low poly 02
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <canvas id="treeCanvas"></canvas> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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