TA159

Notas, resueltos y trabajos practicos de la materia Sistemas Gráficos
Index Commits Files Refs Submodules README LICENSE
commit f67d99412dcedad4a256988b8cf09043ac2291f2
parent d0156f875221aac6e9ceba896eb73832f53d6f5d
Author: mjkloeckner <martin.cachari@gmail.com>
Date:   Fri, 31 May 2024 17:53:38 -0300

`tp` files insertion

Diffstat:
Atp/README.md | 34++++++++++++++++++++++++++++++++++
Atp/assets/elevation_map.png | 0
Atp/assets/elevation_map2.png | 0
Atp/assets/elevation_map3.png | 0
Atp/assets/pasto.jpg | 0
Atp/assets/pasto100pDiffusePatronChico.jpg | 0
Atp/assets/roca.jpg | 0
Atp/assets/shaders.js | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atp/assets/tierra.jpg | 0
Atp/assets/tierraSeca.jpg | 0
Atp/assets/uv.jpg | 0
Atp/enunciado/enunciado.pdf | 0
Atp/index.html | 18++++++++++++++++++
Atp/package.json | 18++++++++++++++++++
Atp/src/main.js | 279+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
15 files changed, 460 insertions(+), 0 deletions(-)
diff --git a/tp/README.md b/tp/README.md
@@ -0,0 +1,34 @@
+# Trabajo Practico Sistemas Gráficos
+
+El trabajo practico consiste en implementar una escena en 3D utilizando WebGL,
+en particular la libreria [Three.js]()
+
+## Elementos de la escena
+
+### Terreno
+
+Para generar la geometría del terreno se utiliza un mapa de elevación el cual se
+puede encontrar en [`assets/elevationMap.png`](./assets/elevationMap.png), luego
+para la textura del terreno se utiliza 3 texturas diferentes, las cuales
+se utilizan en el terreno de acuerdo a la elevación del mismo.
+
+Para utilizar la misma textura varias veces y evitar que se note la repetición,
+se utiliza la función Ruido de Perlin para obtener valores pseudo-aleatorios.
+
+#### Ruido Perlin
+
+### Arboles
+
+### Vias de Tren
+
+### Locomotora
+
+### Puente
+
+### Túnel
+
+### Camaras
+
+### Texturas
+
+### Iluminación
diff --git a/tp/assets/elevation_map.png b/tp/assets/elevation_map.png
Binary files differ.
diff --git a/tp/assets/elevation_map2.png b/tp/assets/elevation_map2.png
Binary files differ.
diff --git a/tp/assets/elevation_map3.png b/tp/assets/elevation_map3.png
Binary files differ.
diff --git a/tp/assets/pasto.jpg b/tp/assets/pasto.jpg
Binary files differ.
diff --git a/tp/assets/pasto100pDiffusePatronChico.jpg b/tp/assets/pasto100pDiffusePatronChico.jpg
Binary files differ.
diff --git a/tp/assets/roca.jpg b/tp/assets/roca.jpg
Binary files differ.
diff --git a/tp/assets/shaders.js b/tp/assets/shaders.js
@@ -0,0 +1,111 @@
+export const vertexShader = `
+    precision highp float;
+
+    // Atributos de los vértices
+    attribute vec3 position; // Posición del vértice
+    attribute vec3 normal;   // Normal del vértice
+    attribute vec2 uv;       // Coordenadas de textura
+
+    // Uniforms
+    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
+    uniform mat4 worldNormalMatrix; // Matriz de normales
+
+    // Varying
+    varying vec2  vUv;       // Coordenadas de textura que se pasan al fragment shader
+    varying vec3  vNormal;   // Normal del vértice que se pasa al fragment shader
+    varying vec3  vWorldPos; // Posición del vértice en el espacio  de mundo
+
+    void main() {
+        // Lee la posición del vértice desde los atributos
+        vec3 pos = position;
+
+        // Se calcula la posición final del vértice
+        // Se aplica la transformación del objeto, la de la cámara y la de proyección
+        gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(pos, 1.0);
+
+        // Se pasan las coordenadas de textura al fragment shader
+        vUv = uv;
+        vNormal = normalize(vec3(worldNormalMatrix * vec4(normal, 0.0)));
+        vWorldPos = (modelMatrix * vec4(pos, 1.0)).xyz;
+    }
+`;
+
+export const fragmentShader = `
+    precision mediump float;
+    varying vec2 vUv;
+    varying vec3 vNormal;
+    varying vec3 vWorldPos;
+
+    uniform float scale;
+    uniform float terrainAmplitude;
+    uniform float terrainAmplitudeBottom;
+    uniform float dirtStepWidth;
+    uniform float rockStepWidth;
+
+    uniform sampler2D dirtSampler;
+    uniform sampler2D rockSampler;
+    uniform sampler2D grassSampler;
+
+    float normalize(float inputValue, float minValue, float maxValue) {
+        return (inputValue - minValue) / (maxValue - minValue);
+    }
+
+    void main(void) {
+        vec2 uv = vUv*8.0;
+        vec2 uv2 = vUv*scale;
+
+        float verticallity = 1.0-max(0.0,vNormal.y);
+        float flatness = 1.0-verticallity;
+        float heightFactor = vWorldPos.y - terrainAmplitudeBottom;
+        float heightFactorNormalized = normalize(heightFactor, 0.0, terrainAmplitude);
+
+        vec3 grass = texture2D(grassSampler, uv).xyz;
+        vec3 dirt  = texture2D(dirtSampler, uv*4.0).xyz;
+        vec3 rock  = texture2D(rockSampler, uv).xyz;
+
+        // muestreo de pasto a diferentes escalas, luego se combina con \`mix()\`
+        vec3 grass1 = texture2D(grassSampler, uv2*1.00).xyz;
+        vec3 grass2 = texture2D(grassSampler, uv2*3.13).xyz;
+        vec3 grass3 = texture2D(grassSampler, uv2*2.37).xyz;
+        vec3 colorGrass = mix(mix(grass1,grass2,0.5),grass3,0.3);
+
+        // lo mismo para la textura de tierra
+        vec3 dirt1 = texture2D(dirtSampler, uv2*3.77).xyz;
+        vec3 dirt2 = texture2D(dirtSampler, uv2*1.58).xyz;
+        vec3 dirt3 = texture2D(dirtSampler, uv2*1.00).xyz;
+        vec3 colorDirt = mix(mix(dirt1, dirt2, 0.5), dirt3, 0.3);
+
+        // lo mismo para la textura de roca
+        vec3 rock1 = texture2D(rockSampler,uv2*0.40).xyz;
+        vec3 rock2 = texture2D(rockSampler,uv2*2.38).xyz;
+        vec3 rock3 = texture2D(rockSampler,uv2*3.08).xyz;
+        vec3 colorRock = mix(mix(rock1, rock2, 0.5), rock3,0.5);
+
+        float u = heightFactorNormalized;
+
+        // float pi = 3.141592654;
+        // float grassFactor = sin(pi*u);
+        // float dirtFactor  = abs(sin(2.0*pi));
+        // float rockFactor  = clamp(cos(2.0*pi*u), 0.0, 1.0);
+
+        float width2 = rockStepWidth;
+        float rockFactor = 2.00 - smoothstep(0.0, width2, u)
+                                - smoothstep(1.0, 1.00 - width2, u);
+
+        float width = dirtStepWidth;
+        float s1 = smoothstep(0.00, width, u);
+        float s2 = smoothstep(width, width*2.0, u);
+        float s3 = smoothstep(0.50, 0.50 + width, u);
+        float s4 = smoothstep(0.50 + width, 0.50 + width*2.0, u);
+        float dirtFactor = (s1 - s2) + (s3 - s4);
+
+        float grassFactor = smoothstep(0.0, 0.35, u) - smoothstep(0.35, 1.00, u);
+
+        vec3 colorDirtGrass = mix(colorDirt, colorGrass, grassFactor);
+        vec3 colorDirtGrassDirt = mix(colorDirtGrass, colorDirt, dirtFactor);
+        vec3 color = mix(colorDirtGrassDirt, colorRock, rockFactor);
+
+        gl_FragColor = vec4(color, 1.0);
+    }`;
diff --git a/tp/assets/tierra.jpg b/tp/assets/tierra.jpg
Binary files differ.
diff --git a/tp/assets/tierraSeca.jpg b/tp/assets/tierraSeca.jpg
Binary files differ.
diff --git a/tp/assets/uv.jpg b/tp/assets/uv.jpg
Binary files differ.
diff --git a/tp/enunciado/enunciado.pdf b/tp/enunciado/enunciado.pdf
Binary files differ.
diff --git a/tp/index.html b/tp/index.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta charset="utf-8" />
+        <title>Trabajo Practico Sistemas Graficos | Martin Klöckner</title>
+        <link rel="icon" href="data:,"> <!-- Do not request favicon -->
+        <style>
+            html, body, #mainContainer {
+                padding: 0;
+                margin: 0;
+                height: 100%;
+        </style>
+    </head>
+    <body>
+        <div id="mainContainer"></div>
+        <script type="module" src="/src/main.js"></script>
+    </body>
+</html>
diff --git a/tp/package.json b/tp/package.json
@@ -0,0 +1,18 @@
+{
+  "name": "Trabajo Práctico",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "MIT",
+  "dependencies": {
+    "dat.gui": "^0.7.9",
+    "three": "^0.162.0"
+  },
+  "devDependencies": {
+    "vite": "^5.1.4"
+  }
+}
diff --git a/tp/src/main.js b/tp/src/main.js
@@ -0,0 +1,279 @@
+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/shaders.js';
+
+let scene, camera, renderer, container, material;
+
+const textures = {
+    tierra: { url: '/assets/tierra.jpg', object: null },
+    roca: { url: '/assets/roca.jpg', object: null },
+    pasto: { url: '/assets/pasto.jpg', object: null },
+    elevationMap: { url: '/assets/elevation_map2.png', object: null },
+};
+
+function onResize() {
+    camera.aspect = container.offsetWidth / container.offsetHeight;
+    camera.updateProjectionMatrix();
+    renderer.setSize(container.offsetWidth, container.offsetHeight);
+}
+
+function setupThreeJs() {
+    scene = new THREE.Scene();
+    container = document.getElementById('mainContainer');
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setClearColor(0x606060);
+    container.appendChild(renderer.domElement);
+
+    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 1000);
+    camera.position.set(-40, 50, 30);
+    camera.lookAt(0, 0, 0);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+
+    const ambientLight = new THREE.AmbientLight(0xffffff);
+    //scene.add(ambientLight);
+
+    const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x000000, 0.25);
+    //scene.add(hemisphereLight);
+
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
+    directionalLight.position.set(1, 1, 1);
+    scene.add(directionalLight);
+
+    const gridHelper = new THREE.GridHelper(50, 20);
+    scene.add(gridHelper);
+
+    const axesHelper = new THREE.AxesHelper( 5 );
+    scene.add( axesHelper );
+
+    window.addEventListener('resize', onResize);
+    onResize();
+}
+
+function elevationPlane(width, height, widthSegments, heightSegments, texture) {
+}
+
+// La funcion devuelve una geometria de Three.js
+// width: Ancho del plano
+// height: Alto del plano
+// amplitude: Amplitud de la elevacion
+// widthSegments: Numero de segmentos en el ancho
+// heightSegments: Numero de segmentos en el alto
+// texture: Textura que se usara para la elevacion
+function elevationGeometry(width, height, amplitude, widthSegments, heightSegments, texture) {
+    console.log('Generating terrain geometry');
+    let geometry = new THREE.BufferGeometry();
+
+    const positions = [];
+    const indices = [];
+    const normals = [];
+    const uvs = [];
+
+    // Creamos un canvas para poder leer los valores de los píxeles de la textura
+    let canvas = document.createElement('canvas');
+    let ctx = canvas.getContext('2d');
+    let img = texture.image;
+
+    // Ajustamos el tamaño del canvas segun la cantidad de segmentos horizontales y verticales
+    canvas.width = widthSegments;
+    canvas.height = heightSegments;
+
+    // Dibujamos la textura en el canvas en la escala definida por widthSegments y heightSegments
+    ctx.drawImage(img, 0, 0, widthSegments, heightSegments);
+
+    // Obtenemos los valores de los píxeles de la textura
+    let imageData = ctx.getImageData(0, 0, widthSegments, heightSegments);
+    let data = imageData.data; // Este es un array con los valores de los píxeles
+
+    const quadsPerRow = widthSegments - 1;
+
+    // Recorremos los segmentos horizontales y verticales
+    for (let i = 0; i < widthSegments - 1; i++) {
+        for (let j = 0; j < heightSegments - 1; j++) {
+            // Obtenemos los valores de los píxeles de los puntos adyacentes
+            let xPrev = undefined;
+            let xNext = undefined;
+            let yPrev = undefined;
+            let yNext = undefined;
+
+            // Obtenemos el valor del pixel en la posicion i, j
+            let z0 = data[(i + j * widthSegments) * 4] / 255;
+
+            // Obtenemos los valores de los píxeles adyacentes
+            xPrev = i > 0 ? data[(i - 1 + j * widthSegments) * 4] / 255 : undefined;
+            xNext = i < widthSegments - 1 ? (xNext = data[(i + 1 + j * widthSegments) * 4] / 255) : undefined;
+
+            yPrev = j > 0 ? data[(i + (j - 1) * widthSegments) * 4] / 255 : undefined;
+            yNext = j < heightSegments - 1 ? data[(i + (j + 1) * widthSegments) * 4] / 255 : undefined;
+
+            // calculamos la diferencia entre los valores de los píxeles adyacentes
+            // en el eje `x` y en el eje `y` de la imagen (en el espacio de la textura
+            // Ojo no confundir con el espacio 3D del modelo 3D donde Y es la altura)
+            let deltaX;
+            if (xPrev == undefined) {
+                deltaX = xNext - z0;
+            } else if (yNext == undefined) {
+                deltaX = xPrev - z0;
+            } else {
+                deltaX = (xNext - xPrev) / 2;
+            }
+
+            let deltaY;
+            if (yPrev == undefined) {
+                deltaY = yNext - z0;
+            } else if (yNext == undefined) {
+                deltaY = yPrev - z0;
+            } else {
+                deltaY = (yNext - yPrev) / 2;
+            }
+
+            // Calculamos la altura del punto en el espacio 3D
+            const z = amplitude * z0;
+
+            // Añadimos los valores de los puntos al array de posiciones
+            positions.push((width * i) / widthSegments - width / 2);
+            positions.push(z);
+            positions.push((height * j) / heightSegments - height / 2);
+
+            // Calculamos los vectores tangentes a la superficie en el ejex y en el eje y
+            let tanX = new THREE.Vector3(width / widthSegments, deltaX * amplitude, 0).normalize();
+            let tanY = new THREE.Vector3(0, deltaY * amplitude, height / heightSegments).normalize();
+
+            // Calculamos el vector normal a la superficie
+            let n = new THREE.Vector3();
+            n.crossVectors(tanY, tanX);
+
+            // Añadimos los valores de los vectores normales al array de normales
+            normals.push(n.x);
+            normals.push(n.y);
+            normals.push(n.z);
+
+            uvs.push(i / (widthSegments - 1));
+            uvs.push(j / (heightSegments - 1));
+
+            if (i == widthSegments - 2 || j == heightSegments - 2) continue;
+
+            // Ensamblamos los triangulos
+            indices.push(i + j * quadsPerRow);
+            indices.push(i + 1 + j * quadsPerRow);
+            indices.push(i + 1 + (j + 1) * quadsPerRow);
+
+            indices.push(i + j * quadsPerRow);
+            indices.push(i + 1 + (j + 1) * quadsPerRow);
+            indices.push(i + (j + 1) * quadsPerRow);
+        }
+    }
+
+    geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
+    geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
+    geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
+    geometry.setIndex(indices);
+
+    return geometry;
+}
+
+function buildScene() {
+    console.log('Building scene');
+
+    const width = 45;
+    const height = 45;
+    const amplitude = 4.50;
+    const widthSegments = 600;
+    const heightSegments = 600;
+    const amplitudeBottom = -1.00;
+
+    const geometry = elevationGeometry(
+        width, height,
+        amplitude,
+        widthSegments, heightSegments,
+        textures.elevationMap.object);
+
+    const waterOnlyGeometry = elevationGeometry(
+        width, height,
+        amplitude,
+        widthSegments, heightSegments,
+        textures.elevationMap.object);
+
+    console.log('Applying textures');
+    material = new THREE.RawShaderMaterial({
+        uniforms: {
+            dirtSampler: { type: 't', value: textures.tierra.object },
+            rockSampler: { type: 't', value: textures.roca.object },
+            grassSampler: { type: 't', value: textures.pasto.object },
+            scale: { type: 'f', value: 3.0 },
+            terrainAmplitude: { type: 'f', value: amplitude },
+            terrainAmplitudeBottom: { type: 'f', value: amplitudeBottom },
+            worldNormalMatrix: { type: 'm4', value: null },
+            dirtStepWidth: { type: 'f', value: 0.20 },
+            rockStepWidth: { type: 'f', value: 0.15 },
+        },
+        vertexShader: vertexShader,
+        fragmentShader: fragmentShader,
+        side: THREE.DoubleSide,
+    });
+    material.needsUpdate = true;
+
+    const mesh = new THREE.Mesh(geometry, material);
+    mesh.position.set(0,amplitudeBottom,0);
+    scene.add(mesh);
+
+    console.log('Generating water');
+    const waterGeometry = new THREE.PlaneGeometry(width/2, height);
+    const waterMaterial = new THREE.MeshPhongMaterial( {color: 0x12ABFF, side: THREE.DoubleSide} );
+    const water = new THREE.Mesh( waterGeometry, waterMaterial );
+    water.rotateX(Math.PI/2);
+    water.position.set(0, 0, 0);
+    scene.add( water );
+}
+
+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(material.uniforms.scale, 'value', 1.00, 5.00).name('Terrain texture scale');
+    gui.add(material.uniforms.dirtStepWidth, 'value', 0.0, 1.0).name('dirt step width');
+    gui.add(material.uniforms.rockStepWidth, 'value', 0.10, 0.50).name('rock step width');
+}
+
+function mainLoop() {
+    requestAnimationFrame(mainLoop);
+    renderer.render(scene, camera);
+}
+
+setupThreeJs();
+loadTextures(main);
+
+function main() {
+    buildScene();
+    createMenu();
+    mainLoop();
+}