TA159

Notas, resueltos y trabajos practicos de la materia Sistemas Gráficos
Index Commits Files Refs Submodules README LICENSE
commit e542dbc37d8ad268a43d547ba991e710116fca65
parent 34b252a70c040809264409a9dab62f8236283ac6
Author: mjkloeckner <martin.cachari@gmail.com>
Date:   Mon,  3 Jun 2024 12:20:58 -0300

add tree leaves and terrain texture

Diffstat:
Atp/assets/treesShaders.js | 166+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtp/src/trees.js | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
2 files changed, 267 insertions(+), 18 deletions(-)
diff --git a/tp/assets/treesShaders.js b/tp/assets/treesShaders.js
@@ -0,0 +1,166 @@
+export const vertexShader = `
+    precision highp float;
+
+    attribute vec3 position;
+    attribute vec2 uv;
+
+    uniform mat4 modelMatrix;      // Matriz de transformación del objeto
+    uniform mat4 viewMatrix;       // Matriz de transformación de la cámara
+    uniform mat4 projectionMatrix; // Matriz de proyección de la cámara
+
+    varying vec2 vUv;
+
+    void main() {
+        vec3 pos = position;
+        gl_Position = projectionMatrix*viewMatrix*modelMatrix* vec4(pos, 1.0);
+        vUv = uv;
+    }`;
+
+export const fragmentShader = `
+    precision mediump float;
+
+    uniform float scale1;
+    uniform float mask1low;
+    uniform float mask1high;
+    uniform float mask2low;
+    uniform float mask2high;
+    uniform sampler2D tierraSampler;
+    uniform sampler2D rocaSampler;
+    uniform sampler2D pastoSampler;
+
+    varying vec2 vUv;
+
+    // Perlin Noise
+    vec3 mod289(vec3 x){
+        return x - floor(x * (1.0 / 289.0)) * 289.0;
+    }
+
+    vec4 mod289(vec4 x){
+        return x - floor(x * (1.0 / 289.0)) * 289.0;
+    }
+
+    vec4 permute(vec4 x){
+        return mod289(((x * 34.0) + 1.0) * x);
+    }
+
+    vec4 taylorInvSqrt(vec4 r){
+        return 1.79284291400159 - 0.85373472095314 * r;
+    }
+
+    vec3 fade(vec3 t) {
+        return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
+    }
+
+    // Classic Perlin noise
+    float cnoise(vec3 P){
+        vec3 Pi0 = floor(P);
+        vec3 Pi1 = Pi0 + vec3(1.0);
+        Pi0 = mod289(Pi0);
+        Pi1 = mod289(Pi1);
+        vec3 Pf0 = fract(P);
+        vec3 Pf1 = Pf0 - vec3(1.0);
+        vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
+        vec4 iy = vec4(Pi0.yy, Pi1.yy);
+        vec4 iz0 = Pi0.zzzz;
+        vec4 iz1 = Pi1.zzzz;
+
+        vec4 ixy = permute(permute(ix) + iy);
+        vec4 ixy0 = permute(ixy + iz0);
+        vec4 ixy1 = permute(ixy + iz1);
+
+        vec4 gx0 = ixy0 * (1.0 / 7.0);
+        vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5;
+        gx0 = fract(gx0);
+        vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
+        vec4 sz0 = step(gz0, vec4(0.0));
+        gx0 -= sz0 * (step(0.0, gx0) - 0.5);
+        gy0 -= sz0 * (step(0.0, gy0) - 0.5);
+
+        vec4 gx1 = ixy1 * (1.0 / 7.0);
+        vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5;
+        gx1 = fract(gx1);
+        vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
+        vec4 sz1 = step(gz1, vec4(0.0));
+        gx1 -= sz1 * (step(0.0, gx1) - 0.5);
+        gy1 -= sz1 * (step(0.0, gy1) - 0.5);
+
+        vec3 g000 = vec3(gx0.x, gy0.x, gz0.x);
+        vec3 g100 = vec3(gx0.y, gy0.y, gz0.y);
+        vec3 g010 = vec3(gx0.z, gy0.z, gz0.z);
+        vec3 g110 = vec3(gx0.w, gy0.w, gz0.w);
+        vec3 g001 = vec3(gx1.x, gy1.x, gz1.x);
+        vec3 g101 = vec3(gx1.y, gy1.y, gz1.y);
+        vec3 g011 = vec3(gx1.z, gy1.z, gz1.z);
+        vec3 g111 = vec3(gx1.w, gy1.w, gz1.w);
+
+        vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000),
+                                    dot(g010, g010),
+                                    dot(g100, g100),
+                                    dot(g110, g110)));
+        g000 *= norm0.x;
+        g010 *= norm0.y;
+        g100 *= norm0.z;
+        g110 *= norm0.w;
+        vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001),
+                                    dot(g011, g011),
+                                    dot(g101, g101),
+                                    dot(g111, g111)));
+        g001 *= norm1.x;
+        g011 *= norm1.y;
+        g101 *= norm1.z;
+        g111 *= norm1.w;
+
+        float n000 = dot(g000, Pf0);
+        float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));
+        float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));
+        float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));
+        float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));
+        float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));
+        float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));
+        float n111 = dot(g111, Pf1);
+
+        vec3 fade_xyz = fade(Pf0);
+        vec4 n_z = mix(vec4(n000, n100, n010, n110),
+                        vec4(n001, n101, n011, n111),
+                        fade_xyz.z);
+
+        vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);
+        float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); 
+        return 2.2 * n_xyz;
+    }
+
+    void main(void) {
+        vec2 uv2=vUv*scale1;
+
+        // se mezcla la textura 'pasto' a diferentes escalas
+        vec3 pasto1 = texture2D(pastoSampler, uv2 * 1.0).xyz;
+        vec3 pasto2 = texture2D(pastoSampler, uv2 * 3.13).xyz;
+        vec3 pasto3 = texture2D(pastoSampler, uv2 * 2.37).xyz;
+        vec3 colorPasto = mix(mix(pasto1, pasto2, 0.5), pasto3, 0.3);
+
+        // lo mismo para la textura 'tierra'
+        vec3 tierra1 = texture2D(tierraSampler, uv2*3.77).xyz;
+        vec3 tierra2 = texture2D(tierraSampler, uv2*1.58).xyz;
+        vec3 colorTierra = mix(tierra1, tierra2, 0.5);
+
+        // lo mismo para la textura 'roca'
+        vec3 roca1 = texture2D(rocaSampler, uv2).xyz;
+        vec3 roca2 = texture2D(rocaSampler, uv2*2.38).xyz;
+        vec3 colorRoca = mix(roca1, roca2, 0.5);
+
+        float noise1 = cnoise(uv2.xyx*8.23+23.11);
+        float noise2 = cnoise(uv2.xyx*11.77+9.45);
+        float noise3 = cnoise(uv2.xyx*14.8+21.2);
+        float mask1 = mix(mix(noise1, noise2, 0.5), noise3, 0.3);
+        mask1 = smoothstep(mask1low, mask1high, mask1);
+
+        float noise4 = cnoise(uv2.xyx*8.23*scale1);
+        float noise5 = cnoise(uv2.xyx*11.77*scale1);
+        float noise6 = cnoise(uv2.xyx*14.8*scale1);
+        float mask2 = mix(mix(noise4, noise5, 0.5), noise6, 0.3);
+        mask2 = smoothstep(mask2low, mask2high, mask2);
+        vec3 colorTierraRoca = mix(colorTierra, colorRoca, mask1);
+        vec3 color = mix(colorPasto, colorTierraRoca, mask2);
+
+        gl_FragColor = vec4(color, 1.0);
+    }`;
diff --git a/tp/src/trees.js b/tp/src/trees.js
@@ -1,8 +1,15 @@
 import * as THREE from 'three';
 import * as dat from 'dat.gui';
 import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
+import { vertexShader, fragmentShader } from '/assets/treesShaders.js';
 
-let scene, camera, renderer, container, material;
+let scene, camera, renderer, container, terrainMaterial, instancedTrees;
+
+const textures = {
+    tierra: { url: '/assets/tierra.jpg', object: null },
+    roca: { url: '/assets/roca.jpg', object: null },
+    pasto: { url: '/assets/pasto.jpg', object: null },
+};
 
 function onResize() {
     camera.aspect = container.offsetWidth / container.offsetHeight;
@@ -31,9 +38,12 @@ function setupThreeJs() {
     scene.add(hemisphereLight);
 
     const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
-    directionalLight.position.set(1, 1, 1);
+    directionalLight.position.set(100, 100, 100);
     scene.add(directionalLight);
 
+    const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5);
+    scene.add(directionalLightHelper);
+
     //const gridHelper = new THREE.GridHelper(50, 20);
     //scene.add(gridHelper);
 
@@ -47,20 +57,30 @@ function setupThreeJs() {
 function createInstancedTrees(count) {
     console.log('Generating `' + count + '` instances of tree');
 
-    const treeLogGeometry    = new THREE.CylinderGeometry(0.35, 0.35, 4, 40, 40);
-    //const treeLeavesGeometry = new THREE.SphereGeometry(1.75,40,40);
+    let logHeight = 4.0;
+
+    const treeLogGeometry    = new THREE.CylinderGeometry(0.30, 0.30, logHeight, 40, 40);
+    const treeLeavesGeometry = new THREE.SphereGeometry(1.75,40,40);
 
-    treeLogGeometry.translate(0, 2, 0);
+    treeLogGeometry.translate(0, logHeight/2.0, 0);
 
     const instancedTreeLogGeometry = new THREE.InstancedBufferGeometry();
     instancedTreeLogGeometry.copy(treeLogGeometry);
 
-    const treeLogMaterial = new THREE.MeshPhongMaterial({color: 0x7c3f00});
+    const instancedTreeLeavesGeometry = new THREE.InstancedBufferGeometry();
+    instancedTreeLeavesGeometry.copy(treeLeavesGeometry);
+
+    const treeLogMaterial   = new THREE.MeshPhongMaterial({color: 0x7c3f00});
     const instancedTreeLogs = new THREE.InstancedMesh(instancedTreeLogGeometry, treeLogMaterial, count);
 
+    const treeLeavesMaterial  = new THREE.MeshPhongMaterial({color: 0x365829});
+    const instancedTreeLeaves = new THREE.InstancedMesh(instancedTreeLeavesGeometry, treeLeavesMaterial, count);
+
     const rotMatrix = new THREE.Matrix4();
+
     const translationMatrix = new THREE.Matrix4();
-    const matrix = new THREE.Matrix4();
+    const treeLogMatrix    = new THREE.Matrix4();
+    const treeLeavesMatrix = new THREE.Matrix4();
 
     //let origin = new THREE.Vector3();
     const RANGE = 50 - 4/2;
@@ -75,15 +95,25 @@ function createInstancedTrees(count) {
         translationMatrix.makeTranslation(position);
 
         //rotMatrix.lookAt(0, 0, new THREE.Vector3(0, 1, 0));
-        matrix.identity();
-        matrix.makeScale(1, 0.5 + Math.random()*2, 1);
+        treeLogMatrix.identity();
+        treeLeavesMatrix.identity();
+
+        let scale = 0.5 + (Math.random()*(logHeight/3));
+        treeLogMatrix.makeScale(1, scale, 1);
         //matrix.premultiply(rotMatrix);
-        matrix.premultiply(translationMatrix);
 
-        instancedTreeLogs.setMatrixAt(i, matrix);
+        treeLogMatrix.premultiply(translationMatrix);
+
+        position.y = scale*logHeight;
+        translationMatrix.makeTranslation(position);
+        treeLeavesMatrix.premultiply(translationMatrix);
+
+        instancedTreeLogs.setMatrixAt(i, treeLogMatrix);
+        instancedTreeLeaves.setMatrixAt(i, treeLeavesMatrix);
     }
 
-    return instancedTreeLogs;
+    scene.add(instancedTreeLogs);
+    scene.add(instancedTreeLeaves);
 }
 
 function buildScene() {
@@ -91,19 +121,72 @@ function buildScene() {
 
     console.log('Generating terrain');
     const terrainGeometry = new THREE.PlaneGeometry(50, 50);
-    const terrainMaterial = new THREE.MeshPhongMaterial( {color: 0x365829, side: THREE.DoubleSide} );
+    //const terrainMaterial = new THREE.MeshPhongMaterial( {color: 0x365829, side: THREE.DoubleSide} );
+    terrainMaterial = new THREE.RawShaderMaterial({
+        uniforms: {
+            tierraSampler: { type: 't', value: textures.tierra.object },
+            rocaSampler: { type: 't', value: textures.roca.object },
+            pastoSampler: { type: 't', value: textures.pasto.object },
+            scale1: { type: 'f', value: 2.0 },
+
+            mask1low: { type: 'f', value: -0.38 },
+            mask1high: { type: 'f', value: 0.1 },
+
+            mask2low: { type: 'f', value: 0.05 },
+            mask2high: { type: 'f', value: -0.70 },
+        },
+        vertexShader: vertexShader,
+        fragmentShader: fragmentShader,
+        side: THREE.DoubleSide,
+    });
+    terrainMaterial.needsUpdate = true;
+
     const terrain = new THREE.Mesh(terrainGeometry, terrainMaterial);
     terrain.rotateX(Math.PI/2);
     terrain.position.set(0, 0, 0);
     scene.add(terrain);
 
     console.log('Generating trees');
-    const trees = createInstancedTrees(20);
-    scene.add(trees);
+    createInstancedTrees(35);
 }
 
+function onTextureLoaded(key, texture) {
+    texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
+    textures[key].object = texture;
+    console.log('Texture `' + key + '` loaded');
+}
+
+function loadTextures(callback) {
+    const loadingManager = new THREE.LoadingManager();
+
+    loadingManager.onLoad = () => {
+        console.log('All textures loaded');
+        callback();
+    };
+
+    for (const key in textures) {
+        console.log("Loading textures");
+        const loader = new THREE.TextureLoader(loadingManager);
+        const texture = textures[key];
+        texture.object = loader.load(
+            texture.url,
+            onTextureLoaded.bind(this, key),
+            null,
+            (error) => {
+                console.error(error);
+            }
+        );
+    }
+}
+
+
 function createMenu() {
     const gui = new dat.GUI({ width: 400 });
+    gui.add(terrainMaterial.uniforms.scale1, 'value', 0, 10).name('Texture scale');
+    gui.add(terrainMaterial.uniforms.mask1low, 'value', -1, 1).name('Mask1 Low');
+    gui.add(terrainMaterial.uniforms.mask1high, 'value', -1, 1).name('Mask1 High');
+    gui.add(terrainMaterial.uniforms.mask2low, 'value', -1, 1).name('Mask2 Low');
+    gui.add(terrainMaterial.uniforms.mask2high, 'value', -1, 1).name('Mask2 High');
 }
 
 function mainLoop() {
@@ -112,10 +195,10 @@ function mainLoop() {
 }
 
 function main() {
-    setupThreeJs();
     buildScene();
-    //createMenu();
+    createMenu();
     mainLoop();
 }
 
-main();
+setupThreeJs();
+loadTextures(main);