TA159

Notas, resueltos y trabajos practicos de la materia Sistemas Gráficos
Index Commits Files Refs Submodules README LICENSE
commit 66b541d75ddbaefa3460836e8678184c50a5eb8a
parent e3d257f95c7e7e6bdfa0430e1776c4f7b6335dd7
Author: Martin Kloeckner <mjkloeckner@gmail.com>
Date:   Sat,  6 Jul 2024 12:54:10 -0300

make terrain react to lights by using customized `MeshPhongMaterial`

Diffstat:
Mtp/src/scene.js | 165++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 160 insertions(+), 5 deletions(-)
diff --git a/tp/src/scene.js b/tp/src/scene.js
@@ -131,7 +131,6 @@ function firstPersonCameraHandler(eventName) {
     //     return;
     // }
 
-    // console.log(eventName);
     switch(eventName) {
         case 'click':
             console.log('click');
@@ -151,7 +150,6 @@ function firstPersonCameraHandler(eventName) {
 }
 
 function keyHandler(event) {
-    // console.log(event);
     if(event.type == 'keydown') {
         switch (event.code) {
             case 'ArrowUp':
@@ -460,6 +458,162 @@ function buildRails() {
     scene.add(rails);
 }
 
+function buildTerrainCustomMaterial() {
+    const customMaterial = new THREE.MeshPhongMaterial({
+        color: 0xffffff,
+        specular: 0x333333,
+        shininess: 10,
+        side: THREE.DoubleSide,
+        reflectivity: 1
+    });
+
+    // definos las variables uniformes adicionales que necesitamos
+    let additionalUniforms = {
+        // grassTexture: { value: grassTexture, type: 't' },
+        // rockTexture: { value: rockTexture, type: 't' },
+
+        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 },
+    };
+    // le decimos al material que vamos a usar UVs para que incluya las coordenadas UV en el shader
+    customMaterial.defines = { USE_UV: true };
+
+    // Este callback se ejecuta antes de compilar el shader
+    // Hay que ver como referencia el archivo
+    // node_modules/three/src/renderers/shaders/ShaderLib/meshphong.glsl.js
+    // para saber que chunks podemos reemplazar
+
+    customMaterial.onBeforeCompile = function (shader) {
+        // le agregamos las variables uniformes adicionales al shader
+        shader.uniforms.dirtSampler = additionalUniforms.dirtSampler;
+        shader.uniforms.rockSampler = additionalUniforms.rockSampler;
+        shader.uniforms.grassSampler = additionalUniforms.grassSampler;
+        shader.uniforms.scale = additionalUniforms.scale;
+        shader.uniforms.terrainAmplitude = additionalUniforms.terrainAmplitude;
+        shader.uniforms.terrainAmplitudeBottom = additionalUniforms.terrainAmplitudeBottom;
+        shader.uniforms.worldNormalMatrix = additionalUniforms.worldNormalMatrix;
+        shader.uniforms.dirtStepWidth = additionalUniforms.dirtStepWidth;
+        shader.uniforms.rockStepWidth = additionalUniforms.rockStepWidth;
+
+        // hacemos un search and replace en el vertex shader
+        // buscamos la linea que dice
+        // vViewPosition = - mvPosition.xyz;
+        // y le agregamos una linea mas que guarde la posicion del vertice en el espacio del mundo
+        shader.vertexShader = shader.vertexShader.replace(
+            'vViewPosition = - mvPosition.xyz;',
+            `vViewPosition = - mvPosition.xyz;
+             vWorldPosition = (modelMatrix*vec4(transformed,1.0)).xyz;`
+        );
+
+        // agregamos una variable varying al comienzo del vertex shader
+        // para pasar la posicion del vertice en coordenadas del mundo al fragment shader
+        shader.vertexShader =
+            `varying vec3 vWorldPosition;
+        ` + shader.vertexShader;
+
+        // agregamos las variables uniformes y varying al fragment shader
+        // Siempre hay que tener cuidado con los nombres de las variables que definimos
+        // no deben coincidir con las variables que usa Three.js
+
+        shader.fragmentShader = `
+uniform float scale;
+uniform float terrainAmplitude;
+uniform float terrainAmplitudeBottom;
+uniform float dirtStepWidth;
+uniform float rockStepWidth;
+
+uniform sampler2D dirtSampler;
+uniform sampler2D rockSampler;
+uniform sampler2D grassSampler;
+varying vec3 vWorldPosition;
+
+` + shader.fragmentShader;
+
+        shader.fragmentShader = shader.fragmentShader.replace(
+            'void main() {',
+            `
+float myNormalizeFunc(float inputValue, float minValue, float maxValue) {
+    return (inputValue - minValue) / (maxValue - minValue);
+}
+
+void main () {
+    float heightFactor = vWorldPosition.y - terrainAmplitudeBottom;
+    float heightFactorNormalized = myNormalizeFunc(heightFactor, 0.0, terrainAmplitude);
+`);
+
+        // reemplazamos el include del chunk map_fragment por nuestro propio codigo
+        shader.fragmentShader = shader.fragmentShader.replace(
+            '#include <map_fragment>',
+            `// calculamos las coordenadas UV en base a las coordenadas de mundo
+vec2 uvCoords=vWorldPosition.xz/100.0;
+vec2 myUV  = uvCoords*8.0;
+vec2 myUV2 = uvCoords*scale;
+
+vec3 grass = texture2D(grassSampler, uvCoords).xyz;
+vec3 dirt  = texture2D(dirtSampler, uvCoords*4.0).xyz;
+vec3 rock  = texture2D(rockSampler, uvCoords).xyz;
+
+// si quisieramos podriamos usar la variabl vUv tambien que son las coordenadas UV del vertice
+
+// muestreo de pasto a diferentes escalas, luego se combina con \`mix()\`
+vec3 grass1 = texture2D(grassSampler, myUV2*1.00).xyz;
+vec3 grass2 = texture2D(grassSampler, myUV2*3.13).xyz;
+vec3 grass3 = texture2D(grassSampler, myUV2*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, myUV2*3.77).xyz;
+vec3 dirt2 = texture2D(dirtSampler, myUV2*1.58).xyz;
+vec3 dirt3 = texture2D(dirtSampler, myUV2*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,myUV2*0.40).xyz;
+vec3 rock2 = texture2D(rockSampler,myUV2*2.38).xyz;
+vec3 rock3 = texture2D(rockSampler,myUV2*3.08).xyz;
+vec3 colorRock = mix(mix(rock1, rock2, 0.5), rock3,0.5);
+
+float u = heightFactorNormalized;
+
+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);
+
+diffuseColor = vec4(color, 1.0);
+
+// leemos los colores de las texturas
+// vec4 grassColor = texture2D(grassSampler,uvCoords);
+// vec4 rockColor = texture2D(rockSampler,uvCoords);
+
+// mezclamos los colores en base a la altura del vertice
+// diffuseColor = mix(grassColor, rockColor, smoothstep(0.0,5.0,vWorldPosition.y));`);
+
+        // imprimimos el shader para debuggear
+        console.log(shader.vertexShader);
+        console.log(shader.fragmentShader);
+    };
+    return customMaterial;
+}
+
 function buildTerrain() {
     const width = 100;
     const height = 100;
@@ -471,12 +625,13 @@ function buildTerrain() {
         textures.elevationMap.object);
 
     console.log('Applying textures');
+
     terrainMaterial = 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 },
+            scale: { type: 'f', value: 2.5 },
             terrainAmplitude: { type: 'f', value: amplitude },
             terrainAmplitudeBottom: { type: 'f', value: amplitudeBottom },
             worldNormalMatrix: { type: 'm4', value: null },
@@ -487,7 +642,8 @@ function buildTerrain() {
         fragmentShader: fragmentShader,
         side: THREE.DoubleSide,
     });
-    terrain = new THREE.Mesh(terrainGeometry, terrainMaterial);
+    const customMaterial = buildTerrainCustomMaterial();
+    terrain = new THREE.Mesh(terrainGeometry, customMaterial );
 
     terrainMaterial.onBeforeRender = (renderer, scene, camera, geometry, terrain) => {
         let m = terrain.matrixWorld.clone();
@@ -532,7 +688,6 @@ function buildTunnel() {
     const trainPathPos = getRailsPathPosAt(0.32);
     tunnel.position.set(trainPathPos[0].x, 0, trainPathPos[0].z);
     tunnel.lookAt(trainPathPos[1].x*1000, 0, trainPathPos[1].z*1000);
-    console.log(trainPathPos);
 
     scene.add(tunnel);