Johann Felipe González Ávila

Johann Felipe González Ávila


Computación Visual Interactiva.

Modelo

En este ocasión era mi intención seleccionar un ejemplo que fuera de mi interés para mostrarles. En ese sentido me he encontrado un ejemplo de la cuenta github evanw que me parece simplemente impresionante. Acá podemos verlo.

El ejemplo es impresionante. Todo en este ejemplo es para destacar. El movimiento del agua, el efecto de gravedad, las texturas, los reflejos y manejo de luces, el manejo de la pausa y las interacciones en este estado.

El ejemplo en código por otro lado es bastante complejo y se me escapan muchas (la verdad la mayoría) de cosas que el autor realiza. Gran mayoría de las cosas que se realizan sobrepasan mi conocimiento. No obstante daré un pequeño resumen de lo que realiza.

Breve, pero brevísimo resumen

Gran parte de toda la lógica detrás del ejemplo se maneja es con diferentes shaders. Pero ya lo verémos en detalle. Miremos la estructura de archivos que maneja.

  • cubemap.js
  • lightgl.js
  • main.js
  • OES_texture_float_linear-polyfill.js
  • render.js
  • water.js

Repasemos uno a uno

Cubemap

El script más simple. Solo se ncarga de la texturas del cubo donde se está dibujando todo.

lightgl

Este script hace todo lo relacionado a la logística matemática. En primera define el contexto. Define operaciones matriciales para el manejo de la cámara. También define operaciones de pila para matrices. Maneja la lógica de los colores. También se encarga de los eventos del usuario. No menos imporante maneja la carga de mallas, texturas y definición de vectores.

Sin embargo, su tarea principal es manejar los procedimientos matem[aticos y recurrentes. Más logística que técnica. Inversiones, multiplicaciones matriciales, etc

renderer

Uno de los más importantes. Acá se definen los elementos que realmente van a desplegar la visualización. Acá se define la manera en que se va a pintar eso. Para eso primero se define una variable helper. En el se definene las cosas básicas de dibujo que bajo cualquier situación o interacción se debe pintar. El agua, los muros, la esfera.

Se define también un renderer que es el encargado de pintar el agua. Acá se definene elementos como la dirección de la luz, la textura, la malla del agua. Para dibujar el agua se crean dos shaders.

for (var i = 0; i < 2; i++) {
this.waterShaders[i] = new GL.Shader('\

más abajo cerca de las líneas 165 en adelante se define que cada shader será por si se mira debajo del agua o pode encima del agua. Los cambios se definen en el vector normal, en la manera de reflexión de la luz y la manera de visualizar.

Debajo del agua

normal = -normal;\
vec3 reflectedRay = reflect(incomingRay, normal);\
vec3 refractedRay = refract(incomingRay, normal, IOR_WATER / IOR_AIR);\
float fresnel = mix(0.5, 1.0, pow(1.0 - dot(normal, -incomingRay), 3.0));\
\
vec3 reflectedColor = getSurfaceRayColor(position, reflectedRay, underwaterColor);\
vec3 refractedColor = getSurfaceRayColor(position, refractedRay, vec3(1.0)) * vec3(0.8, 1.0, 1.1);\
\
gl_FragColor = vec4(mix(reflectedColor, refractedColor, (1.0 - fresnel) * length(refractedRay)), 1.0);\

Sobre el agua

vec3 reflectedRay = reflect(incomingRay, normal);\
vec3 refractedRay = refract(incomingRay, normal, IOR_AIR / IOR_WATER);\
float fresnel = mix(0.25, 1.0, pow(1.0 - dot(normal, -incomingRay), 3.0));\
\
vec3 reflectedColor = getSurfaceRayColor(position, reflectedRay, abovewaterColor);\
vec3 refractedColor = getSurfaceRayColor(position, refractedRay, abovewaterColor);\
\
gl_FragColor = vec4(mix(refractedColor, reflectedColor, fresnel), 1.0);\

Después de esto también se define un shader para la esfera.

this.sphereShader = new GL.Shader(helperFunctions + '\
varying vec3 position;\
void main() {\
position = sphereCenter + gl_Vertex.xyz * sphereRadius;\
gl_Position = gl_ModelViewProjectionMatrix * vec4(position, 1.0);\
}\
', helperFunctions + '\
varying vec3 position;\
void main() {\
gl_FragColor = vec4(getSphereColor(position), 1.0);\
vec4 info = texture2D(water, position.xz * 0.5 + 0.5);\
if (position.y < info.r) {\
gl_FragColor.rgb *= underwaterColor * 1.2;\
}\
}\
');

También se define para el cubo y para el agua ademas de algunas funciones de actualziación. Como nos podemos dar cuenta. El grueso del trabajo está sobre los shaders

water

Esta clase es la que no entiendo bien. Si bien es claro que su función es crear shaders (si, también shaders) no encuentro la relación con la clase anterior. Creo que su finción está más centrada a las interacciones pues maneja mpetodos como mover la esfera, pasos de simulación (Cuando se mueve la pelota el agua hace un movimiento que empieza a parar con el paso del tiempo). Actualiza los vectores normales para dibujar entre otros.

El entendimiento está en proceso y empezaré a actualizar a medida que vaya entendiendo.

OES_texture_float_linear-polyfill.js

Esta es una extensión para el manejo de texturas. Es una mejora a una librería anterior creada por el mismo autor. La información la encontramos aquí

main

Llegamos a la clase que lo hace todo y nada a la vez. Como es de suponer, el script main es el gran orquestador. Realiza las tareas de organización. Dice que ejecutarse y cuando. Hace tareas de organización. Acá exponemos solo dos de los métodos más importantes.

function update(seconds) {
if (seconds > 1) return;
frame += seconds * 2;

if (mode == MODE_MOVE_SPHERE) {
  // Start from rest when the player releases the mouse after moving the sphere
  velocity = new GL.Vector();
} else if (useSpherePhysics) {
  // Fall down with viscosity under water
  var percentUnderWater = Math.max(0, Math.min(1, (radius - center.y) / (2 * radius)));
  velocity = velocity.add(gravity.multiply(seconds - 1.1 * seconds * percentUnderWater));
  velocity = velocity.subtract(velocity.unit().multiply(percentUnderWater * seconds * velocity.dot(velocity)));
  center = center.add(velocity.multiply(seconds));

  // Bounce off the bottom
  if (center.y < radius - 1) {
	center.y = radius - 1;
	velocity.y = Math.abs(velocity.y) * 0.7;
  }
}

// Displace water around the sphere
water.moveSphere(oldCenter, center, radius);
oldCenter = center;

// Update the water simulation and graphics
water.stepSimulation();
water.stepSimulation();
water.updateNormals();
renderer.updateCaustics(water);
}

function draw() {
// Change the light direction to the camera look vector when the L key is pressed
if (GL.keys.L) {
  renderer.lightDir = GL.Vector.fromAngles((90 - angleY) * Math.PI / 180, -angleX * Math.PI / 180);
  if (paused) renderer.updateCaustics(water);
}

gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.loadIdentity();
gl.translate(0, 0, -4);
gl.rotate(-angleX, 1, 0, 0);
gl.rotate(-angleY, 0, 1, 0);
gl.translate(0, 0.5, 0);

gl.enable(gl.DEPTH_TEST);
renderer.sphereCenter = center;
renderer.sphereRadius = radius;
renderer.renderCube();
renderer.renderWater(water, cubemap);
renderer.renderSphere();
gl.disable(gl.DEPTH_TEST);
}
							

Vemos que solo realiza el llamado a las funciones. Otro manejo importante se hace sobre los que definene las acciones de cuando se realiza un drag sobre el escenario y cuando este termina.

Extra

A manera de información, me gustaría compartirles una librería que en algún momento utilicé. La considero perfecta para hacer trabajo de manera rápida sin mucho conocimiento y con resultados ágiles. Si bien parece estar orientada a propósitos médicos, tienen herramientas que son muy faciles de usar. XTK es una librería con primitivas muy sencillas que realizan mucho de los que se necesita. Acá les dejo 4 de los ejemplos donde pueden ver lo fácil que es hacer desarrollo