commit f67d99412dcedad4a256988b8cf09043ac2291f2
parent d0156f875221aac6e9ceba896eb73832f53d6f5d
Author: mjkloeckner <martin.cachari@gmail.com>
Date: Fri, 31 May 2024 17:53:38 -0300
`tp` files insertion
Diffstat:
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();
+}