




[{"content":"","date":"29 Enero 2026","externalUrl":null,"permalink":"/es/blog/","section":"Blogs","summary":"","title":"Blogs","type":"blog"},{"content":"He estado siguiendo la masterclass de Iñigo Quilez «Live Coding \u0026ldquo;Happy Jumping\u0026rdquo;», donde implementa en ShaderToy un shader con raymarching que genera una escena con un monstruo saltarín paseando por un terreno peculiar. Para él era un ejercicio de animación, ya que aplica principios como el squash and stretch.\nPara mí, en cambio, es una oportunidad para aprender cómo se construyen estas escenas tridimensionales usando shaders, sin modelos 3D, solo con matemáticas y código. Con este artículo intento expandir algunos conceptos para que queden más claros y profundizar un poco más en el tema.\nVer el shader \u0026ldquo;Happy Jumping\u0026rdquo; en ShaderToy\nRaymarching\rSegún Iñigo Quilez, el raymarching puede entenderse como una técnica para explorar una escena tridimensional mediante rayos. Cada píxel de la pantalla genera un rayo que se adentra en el espacio virtual y busca las superficies presentes en la escena. Con raymarching, la escena se describe mediante distancias en lugar de calcular de forma exacta la intersección entre un rayo y la geometría (ray tracing). El algoritmo avanza de forma iterativa a lo largo del rayo utilizando la distancia estimada hasta la superficie más cercana.\nEn cada iteración se evalúa una Signed Distance Function (SDF), una función que devuelve la distancia firmada hasta la superficie más cercana. Este valor determina cuánto puede avanzar el rayo de forma segura sin atravesar ningún objeto. Cuando la superficie está lejos, los pasos pueden ser grandes; a medida que el rayo se aproxima a un objeto, los incrementos se reducen progresivamente, aumentando la precisión del cálculo.\nDe GPU Gems 2: Chapter 8.\nGracias a este mecanismo, es posible representar escenas complejas definidas únicamente mediante funciones matemáticas, sin necesidad de mallas, vértices o estructuras geométricas tradicionales.\n// This normalize the coordinates from (-aspectRatio,-1) to (aspectRatio, 1) vec2 p = (2.0*(fragCoord+offset)-iResolution.xy) / iResolution.y; vec3 ro = vec3(0.0,0.0,2.0); vec3 rd = normalize(vec3(p,-1.5)); // if ray doesn\u0026#39;t hit anything value -1 vec4 res = vec4(-1.0, -1.0, 0.0,1.0); float t = 0.0; for(int i = 0; i \u0026lt; 128; ++i) { vec3 pos = ro + t* rd; // map function will return a distance that is secure to advance vec4 h = map(pos,atime); t+=h.x; } Pseudocódigo del raymarching\nAhora bien, ¿cómo se calcula esa distancia máxima que se puede avanzar sin atravesar los distintos objetos? No hay una respuesta única: dependiendo del tipo de objeto existen distintas fórmulas. Una esfera seria un caso simple:\nDemostración del cálculo de h\nLa distancia máxima (h) a la que podemos avanzar sin atravesar la esfera es la distancia entre el origen del rayo y el centro de la esfera, menos el radio de esta. Con esto ya podemos empezar a pintar algo: en cuanto sabemos que hemos dado con la esfera, podemos mostrarla en pantalla e incluso empezar a iluminarla.\nPrimer render de una esfera con raymarching\nCalcular normales\rPara poder calcular la iluminación de la escena, primero debemos calcular las normales de los objetos con los que colisiona el rayo. Cuando sabemos a qué distancia t se produce la colisión, podemos hallar el punto exacto de la superficie, y a partir de él calcular la normal.\nvec3 pos = ro + t*rd; vec3 nor = calcNormal( pos, time ); Como la función map() es continua, la dirección en la que la distancia cambia más rápido (su gradiente) coincide con la normal de la superficie. Para aproximar ese gradiente, tomamos el punto de colisión y lo desplazamos ligeramente en cada eje (x, y, z), calculando la diferencia de distancia entre desplazar el punto hacia un lado y hacia el otro (diferencias finitas centradas). El resultado, normalizado, nos da la dirección de la normal.\nvec3 calcNormal( in vec3 pos, float time ) { float e = 0.0001; // pequeño desplazamiento float dx = map( pos + vec3(e,0,0), time ).x - map( pos - vec3(e,0,0), time ).x; float dy = map( pos + vec3(0,e,0), time ).x - map( pos - vec3(0,e,0), time ).x; float dz = map( pos + vec3(0,0,e), time ).x - map( pos - vec3(0,0,e), time ).x; return normalize( vec3(dx, dy, dz) ); } Diferencia finita centrada: en vez de comparar el punto de colisión con un único punto desplazado, comparamos dos puntos situados simétricamente a ambos lados (pos+e y pos-e). Esto da una aproximación más precisa de la derivada que si solo miráramos hacia un lado, ya que promedia la variación en ambas direcciones.\nCálculo de luz\rSun Diffuse\rPara calcular cuánta luz directa del sol recibe un punto de la superficie, usamos la normal del objeto y la dirección hacia la luz (el sol, en este caso). Calculamos el producto escalar (dot product) entre ambos vectores; como los dos son vectores unitarios (normalizados), el resultado es directamente el coseno del ángulo que forman.\nEste coseno es lo que se conoce como ley del coseno de Lambert: cuanto más alineada esté la normal con la dirección de la luz (ángulo cercano a 0°), más luz recibe la superficie (coseno cercano a 1); cuanto más de lado reciba la luz (ángulo cercano a 90°), menos luz le llega (coseno cercano a 0). Esto simula cómo la misma cantidad de luz se \u0026ldquo;reparte\u0026rdquo; sobre más o menos superficie según el ángulo de incidencia.\nvec3 sun_dir = normalize(vec3(0.6, 0.35, 0.5)); float sun_dif = clamp(dot(sun_dir, normal), 0.0, 1.0); Hacemos clamp(..., 0.0, 1.0) porque, si el ángulo supera los 90° (la superficie mira en dirección opuesta al sol), el coseno se vuelve negativo — y una cantidad de luz negativa no tiene sentido físico. Acotando el valor entre 0 y 1 nos aseguramos de que esas superficies simplemente no reciban luz directa del sol (quedan en sombra propia), en vez de \u0026ldquo;restar\u0026rdquo; luz de forma incorrecta.\nSky Diffuse \u0026amp; Bounce Diffuse\rPara darle una iluminación del cielo y del suelo (la luz que rebota), utilizamos la misma técnica que con la luz del sol.\nHacemos el dot product de la normal con el vector vertical: positivo (0,1,0) para el cielo, negativo (0,-1,0) para el rebote del suelo. También hacemos un remapeo para que los valores vayan de 0 a 1, ya que el dot product devuelve un valor entre -1 y 1. En sky_dif usamos 0.5 + 0.5*x, que reparte el rango de forma simétrica (mitad y mitad); en bou_dif usamos 0.1 + 0.9*x, una mezcla distinta que da más peso al dot product (rebote más intenso cuando la normal mira directamente al suelo) y menos peso a un valor base constante. Por último, el resultado se clampea para que no se salga del rango [0.0, 1.0].\nfloat sky_dif = clamp(0.5 + 0.5*dot(normal, vec3(0.0, 1.0, 0.0)), 0.0, 1.0); float bou_dif = clamp(0.1 + 0.9*dot(normal, vec3(0.0, -1.0, 0.0)), 0.0, 1.0); Esto se puede simplificar, ya que multiplicar por un vector unitario que solo tiene una componente activa simplemente \u0026ldquo;extrae\u0026rdquo; esa componente. Por tanto, podemos usar normal.y directamente en vez de hacer el dot product:\nfloat sky_dif = clamp(0.5 + 0.5*normal.y, 0.0, 1.0); float bou_dif = clamp(0.1 - 0.9*normal.y, 0.0, 1.0); Hard Shadows\rLa idea de calcular sombras en raymarching es bastante sencilla. Reutilizamos la misma función castRay que usamos para el rayo de la cámara, pero ahora lanzamos el rayo desde la superficie del objeto (ligeramente desplazado a lo largo de la normal, para no chocar consigo mismo por errores de precisión) en dirección a la luz. Si ese rayo golpea algo antes de llegar a la fuente de luz, el punto original está en sombra. float sun_sha = step(castRay(pos + 0.001*nor, sun_lig, time), 0.0); step(valor, 0.0) — la función step(edge, x) devuelve 0.0 si x \u0026lt; edge, y 1.0 si x \u0026gt;= edge. Aquí se usa \u0026ldquo;al revés\u0026rdquo; de como se suele leer: step(castRay(...), 0.0) compara 0.0 contra el resultado de castRay. Como en esta versión simplificada castRay devuelve -1.0 cuando no hay impacto:\nSi no hay impacto → castRay(...) = -1.0 → step(-1.0, 0.0) = 1.0 (0.0 ≥ -1.0) → sin sombra, luz plena. Si hay impacto (choca con algo) → castRay(...) es una distancia positiva (por ejemplo 3.5) → step(3.5, 0.0) = 0.0 (0.0 \u0026lt; 3.5) → en sombra total. Calculo de la oclusión\rEsta función simula un efecto muy sutil pero importante para el realismo: las zonas donde hay geometría cercana rodeando un punto (rincones, pliegues, huecos entre formas) reciben menos luz ambiental, porque parte de esa luz queda bloqueada por los propios objetos vecinos. Es lo que hace que las axilas de un personaje, o el hueco entre dos dedos, se vean ligeramente más oscuros aunque no haya una sombra proyectada por el sol.\nfloat h = 0.01 + 0.11*float(i)/4.0; vec3 opos = pos + h*nor; float d = map( opos, time ).x; occ += (h-d)*sca; Para cada una de las 5 iteraciones, tomamos el punto de la superficie (pos) y nos movemos una distancia h a lo largo de la normal (nor) — es decir, nos alejamos perpendicularmente hacia afuera del objeto. Si no hubiera nada más cerca (superficie totalmente convexa y despejada), la distancia real hasta cualquier otro objeto en ese punto (d, obtenida de map()) sería exactamente h — porque no hay ningún obstáculo interfiriendo.\nPero si hay geometría vecina cerca (un rincón, un pliegue), la distancia real d será menor que h, porque hay algo estorbando antes de esa distancia esperada. La diferencia h - d mide justo eso: cuánto \u0026ldquo;más cerca de lo esperado\u0026rdquo; hay superficie ahí — cuanta más geometría vecina haya, mayor será esta diferencia.\nreturn clamp( 1.0 - 2.0*occ, 0.0, 1.0 ); occ acumula las diferencias (h-d) ponderadas: cuanto mayor sea occ, más \u0026ldquo;encerrado\u0026rdquo; está el punto por geometría vecina. Al hacer 1.0 - 2.0*occ, invertimos la escala: si occ es alto (mucha oclusión), el resultado se acerca a 0 (poca luz ambiental); si occ es bajo o cero (superficie despejada), el resultado se acerca a 1 (luz ambiental completa). El clamp asegura que el resultado quede siempre en el rango [0, 1], ya que la multiplicación por 2.0 es un factor arbitrario para amplificar el efecto (ajuste artístico, no físico) y podría sacar el valor fuera de rango.\nCamera (Look-at)\rPara construir una cámara que siempre mira a un punto fijo. Se define un target, y a partir de ahí la posición de la cámara (que también será el origen del rayo, ro).\nCon el target y el origen podemos definir el vector \u0026ldquo;adelante\u0026rdquo; (ww), normalizado: es simplemente la dirección desde ro hacia target.\nPara generar el vector de la \u0026ldquo;derecha\u0026rdquo; (uu), hacemos el producto vectorial del vector adelante y el vector vertical del mundo. El producto vectorial (cross) entre dos vectores da un tercer vector perpendicular a ambos. Al hacer cross(ww, vec3(0,1,0)) (adelante × arriba-del-mundo), obtenemos un vector perpendicular tanto a \u0026ldquo;hacia dónde mira la cámara\u0026rdquo; como al eje vertical del mundo — que es exactamente la dirección \u0026ldquo;derecha\u0026rdquo; de la cámara.\nUna vez tenemos los vectores adelante y derecha, repetimos el proceso para obtener el vector \u0026ldquo;arriba\u0026rdquo; real de la cámara (vv). No podemos simplemente reutilizar vec3(0,1,0) como arriba, porque si la cámara mira hacia arriba o hacia abajo, ese \u0026ldquo;arriba del mundo\u0026rdquo; ya no sería perpendicular a los otros dos ejes. Por eso lo recalculamos con cross(uu, ww), asegurando que los tres vectores formen una base perfectamente ortogonal entre sí.\nvec3 target = vec3(0.0, 0.75, 0.4); // ray origin vec3 ro = target + vec3(1.5 * sin(an),-.1,1.5 * cos(an)); vec3 ww = normalize(target - ro); vec3 uu = normalize(cross(ww, vec3(0,1,0))); vec3 vv = normalize(cross(uu,ww)); Estos vectores forman la base para construir la dirección de cada rayo por píxel:\n// This normalize the coordinates from [-aspectRatio,-1] to [aspectRatio, 1] vec2 p = (2.0*(fragCoord+offset) - iResolution.xy) / iResolution.y; vec3 rd = normalize(p.x*uu + p.y*vv + 1.8*ww); Aquí p.x y p.y son las coordenadas normalizadas del píxel en pantalla: p.y va de -1 a 1, mientras que p.x va de -aspectRatio a aspectRatio (donde aspectRatio es el ancho entre el alto de la pantalla). Estas coordenadas se combinan con los ejes uu (derecha), vv (arriba) y ww (adelante) de la cámara para construir la dirección del rayo en el espacio del mundo — es decir, \u0026ldquo;cuánto me desvío a la derecha/arriba desde el centro de la vista, y cuánto avanzo hacia adelante\u0026rdquo;. El 1.8 que multiplica a ww controla el campo de visión (FOV): cuanto mayor sea ese número, más \u0026ldquo;zoom\u0026rdquo; (campo de visión más estrecho); cuanto menor, más angular (FOV más amplio).\nEscena simple\rEste es el resultado final: el shader de Iñigo Quilez con una escena básica que reúne los elementos que hemos ido explicando — el bucle de raymarching, el cálculo de normales, y la iluminación combinando la luz del sol, del cielo, del rebote y las sombras.\nShader de Iñigo Quilez ShaderToy\n","date":"29 Enero 2026","externalUrl":null,"permalink":"/es/blog/0_raymarching/","section":"Blogs","summary":"","title":"Introducción al Raymarching","type":"blog"},{"content":"\rDesarrolladora de videojuegos especializada en C++ y renderizado en tiempo real, con una sólida base en arquitectura de motores de videojuegos, además de una experiencia práctica en programación de gráficos 3D. Poseo un Grado en Diseño y Desarrollo de Videojuegos por la UPC y un Máster en Computación Gráfica, Simulación y Realidad Virtual en U-TAD.\nAunque mi enfoque principal es la programación en C/C++, también he trabajado con C#, Python, Dart y JavaScript. Tengo experiencia con librerías como OpenGL y SDL, y he desarrollado mi propio motor de videojuegos, incorporando características como animación esquelética y frustum culling mediante particionado espacial. También he participado en proyectos colaborativos utilizando Unity, incluyendo varias game jams y experiencias premiadas.\nMe apasiona el aprendizaje continuo, el trabajo en equipos multidisciplinares y la exploración de las tecnologías interactivas más punteras.\nDescarga CV ","date":"26 Enero 2025","externalUrl":null,"permalink":"/es/about-me/","section":"Main","summary":"","title":"About me","type":"page"},{"content":"Soy ingeniera de software especializada en gráficos en tiempo real y desarrollo de videojuegos, con experiencia tanto en el sector del entretenimiento como en el de tecnologías interactivas. En los últimos años he participado en proyectos que van desde software para máquinas recreativas desarrollado en C++ hasta herramientas de visualización médica y aplicaciones interactivas creadas con Unity. Mi trabajo abarca sistemas de gameplay, funcionalidades de renderizado, desarrollo de interfaces de usuario, herramientas internas y programación orientada al rendimiento. Disfruto creando soluciones técnicas robustas, diseñando flujos de trabajo eficientes y colaborando en equipos multidisciplinares con un fuerte enfoque en la claridad y el mantenimiento del código.\n","date":"26 Enero 2025","externalUrl":null,"permalink":"/es/resume/","section":"Resume","summary":"","title":"Resume","type":"resume"},{"content":"Desarrolladora de Videojuegos y Artista Técnico\n","date":"13 Enero 2022","externalUrl":null,"permalink":"/es/","section":"Main","summary":"","title":"Main","type":"page"},{"content":"","date":"25 Enero 2021","externalUrl":null,"permalink":"/es/tags/c++/","section":"Tags","summary":"","title":"C++","type":"tags"},{"content":"","date":"25 Enero 2021","externalUrl":null,"permalink":"/es/tags/engine-development/","section":"Tags","summary":"","title":"Engine Development","type":"tags"},{"content":"","date":"25 Enero 2021","externalUrl":null,"permalink":"/es/tags/graphics-programming/","section":"Tags","summary":"","title":"Graphics Programming","type":"tags"},{"content":"","date":"25 Enero 2021","externalUrl":null,"permalink":"/es/tags/opengl/","section":"Tags","summary":"","title":"OpenGL","type":"tags"},{"content":"","date":"25 Enero 2021","externalUrl":null,"permalink":"/es/projects/","section":"Projects","summary":"","title":"Projects","type":"projects"},{"content":"","date":"25 Enero 2021","externalUrl":null,"permalink":"/es/tags/real-time-rendering/","section":"Tags","summary":"","title":"Real-Time Rendering","type":"tags"},{"content":"\rDescripción general\r#\rShadow es una librería de renderizado en tiempo real que implementa varias técnicas centrales utilizadas en los motores de videojuegos modernos. Está escrita íntegramente en C++ con OpenGL, y fue desarrollada como mi Trabajo de Fin de Grado.\nRepositorio de GitHub Sobre el proyecto\r#\rEl objetivo de Shadow fue estudiar e implementar técnicas de renderizado en tiempo real desde cero, centrándome en los sistemas gráficos a nivel de motor en lugar de utilizar frameworks o pipelines de shading prediseñados. El motor sigue un pipeline de Deferred Rendering e incluye múltiples funcionalidades de post-procesado y sombreado.\nCaracterísticas de la librería\r#\rDiseñé e implementé:\nPipeline de Deferred Rendering Layout del G-Buffer y arquitectura multipaso Physically-Based Rendering (PBR) Flujo de trabajo de Metalness/Roughness Screen Space Ambient Occlusion (SSAO) Pipeline de post-procesado Bloom Normal Mapping Generación de base tangente-espacio (TBN) Sistema de gestión de recursos Herramientas de depuración y visualización con ImGui Bucle de renderizado de escena y sistema de materiales Pipeline de renderizado\r#\rShadow utiliza un flujo de trabajo de Deferred Shading para soportar múltiples luces dinámicas de forma eficiente. Los datos de material se almacenan en un G-Buffer y la iluminación se calcula en un pase separado.\nResultado visual\r#\rRegistro de versiones\r#\rv0.1\r#\rBase del motor (ventanas, sistema de entrada, carga de recursos) Renderizador de skybox Inicialización del G-Buffer Implementación base de Deferred Shading v1.0 (Finalización del Trabajo de Fin de Grado)\r#\rPhysically-Based Rendering (PBR) Pase SSAO Post-procesado Bloom Pipeline de Normal Mapping ","date":"25 Enero 2021","externalUrl":null,"permalink":"/es/projects/shadow-graphics-library/","section":"Projects","summary":"","title":"Shadow Graphics Library","type":"projects"},{"content":"","date":"25 Enero 2021","externalUrl":null,"permalink":"/es/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"29 Enero 2020","externalUrl":null,"permalink":"/es/tags/c%23/","section":"Tags","summary":"","title":"C#","type":"tags"},{"content":"","date":"29 Enero 2020","externalUrl":null,"permalink":"/es/tags/game-jam/","section":"Tags","summary":"","title":"Game Jam","type":"tags"},{"content":"\rDescripción general\r#\rK.U.B.O es un juego de puzles y plataformas creado durante la Game Jam de la Universidad CITM-UPC, cuyo tema fue Paradoja. Nuestro equipo decidió explorar este concepto a través de la perspectiva forzada y el diseño de niveles basado en ilusiones.\nJuegas como un pequeño cubo atrapado en un mundo 2D. Sin embargo, al pulsar el botón rojo, el entorno revela brevemente su verdadera estructura 3D, exponiendo trucos de perspectiva y caminos ocultos necesarios para resolver cada nivel.\nEsta mecánica de doble vista —jugabilidad 2D con revelaciones 3D momentáneas— se convirtió en la paradoja central sobre la que se construyó todo el juego.\nRepositorio de GitHub Mis contribuciones\r#\rProgramación de gameplay Apoyo en el diseño de niveles Mecánicas de puzles con cambios de perspectiva Depuración general y pulido 🏆 Premio: Mejor Juego\r#\rK.U.B.O ganó el Premio al Mejor Juego en la Game Jam de la CITM-UPC. El jurado destacó que fue el proyecto que mejor capturó e integró el tema de la jam: \u0026ldquo;Paradoja\u0026rdquo;, ofreciendo un uso inteligente y coherente de las mecánicas basadas en la perspectiva.\nCréditos\r#\rAlex Morales GitHub: Alexmg99\nYessica Servin GitHub: YessicaSD\nPol Vázquez GitHub: Amade128\nHimar Bravo GitHub: Himar33\nMarc Pavón Llop GitHub: Mackitus\n","date":"29 Enero 2020","externalUrl":null,"permalink":"/es/projects/kubo/","section":"Projects","summary":"","title":"K.U.B.O - Game Jam","type":"projects"},{"content":"","date":"29 Enero 2020","externalUrl":null,"permalink":"/es/tags/unity/","section":"Tags","summary":"","title":"Unity","type":"tags"},{"content":"","date":"12 Enero 2020","externalUrl":null,"permalink":"/es/tags/customengine/","section":"Tags","summary":"","title":"CustomEngine","type":"tags"},{"content":"\rDescripción general\r#\rThe Witcher: A Bard\u0026rsquo;s Tale es un beat-\u0026rsquo;em-up / hack-and-slash cooperativo. Los jugadores controlan a Geralt de Rivia y Yennefer de Vengerberg mientras luchan para rescatar a Ciri.\nJugar Sobre el proyecto\r#\rEs un proyecto universitario en grupo construido con un motor personalizado en C++ y OpenGL. El diseño se centra en un combate responsivo, encuentros cinemáticos con jefes y una presentación pulida. Mi trabajo abarca programación de gráficos, herramientas de gameplay y creación de contenido.\nMis contribuciones individuales\r#\rSkinning en GPU (skinning en vertex shader) Sistema de subtítulos (importación JSON + efecto máquina de escribir) Presentación de jefes (movimiento cinemático de cámara) Enemigo dummy para pruebas de combate Efectos de shader de disolución Modelos de personajes principales y assets 3D Iluminación de escena Diseño de HUD y pulido de menús Skinning con shaders\r#\rPara mejorar el rendimiento en tiempo de ejecución, movimos el skinning a la GPU (vertex skinning en el vertex shader). Cada vértice almacena:\nun ivec4 de índices de huesos (máximo 4 huesos por vértice), y un vec4 de pesos de huesos (un peso por índice de hueso). En la CPU subimos un array de matrices de transformación de huesos (un mat4 por hueso). El vertex shader lee los índices y pesos de los huesos, construye una matriz de skinning a partir de las matrices de huesos correspondientes, y transforma la posición del vértice y la normal íntegramente en la GPU.\nTambién corregí un error donde algunos modelos colapsaban hacia el origen. La causa raíz era aplicar las transformaciones de huesos relativas a las posiciones originales del vértice en lugar de usar el origen de la malla; la corrección consistió en calcular las posiciones con skinning relativas al origen de la malla y aplicar correctamente las transformaciones de huesos.\nVertex skinning en GPU en acción\rDemostración del error: modelo colapsando al origen antes de la corrección\rSistema de subtítulos\r#\rEl juego incluye secuencias narrativas que requieren subtítulos. Utilizamos Subtitle Horse para crear los subtítulos y los exportamos a JSON para integrarlos fácilmente en nuestro motor.\nImplementé un Gestor de Subtítulos que:\nCarga los tiempos y textos de los subtítulos desde archivos JSON Muestra líneas temporizadas con un efecto de máquina de escribir Sincroniza los subtítulos con los tiempos de las cinemáticas Subtítulos con efecto de máquina de escribir\rFotograma final de cinemática con subtítulos\rPresentación del jefe (cámara cinemática)\r#\rPara las introducciones de los jefes creé un sistema de cámara cinemática. La cámara sigue una curva de Bézier para centrarse en el jefe con un revelado dramático, y luego regresa suavemente al jugador antes de reanudar el gameplay.\nEstá implementado como un pequeño script que muestrea una cúbica de Bézier en tiempo normalizado e interpola tanto la posición como la orientación.\nIntroducción cinemática del jefe usando un camino de cámara Bézier\rEnemigo dummy\r#\rImplementé un enemigo dummy para el entrenamiento de combate. El dummy permite a los jugadores practicar ataques y combos, y ayuda al equipo a depurar la temporización de entradas, la detección de golpes y las transiciones de animación.\nEnemigo dummy para pruebas de combate\rShader de disolución\r#\rImplementé un shader de disolución para transiciones de aparición/desaparición estilizadas. El efecto usa una textura de ruido (o ruido procedural) y un parámetro que controla una máscara de umbral, creando fundidos suaves y controlables con detalle en los bordes.\nModelos 3D y assets\r#\rProduje personajes y assets del juego, y aprendí el pipeline de producción artística: escultura, retopología en Maya, pintura de texturas en Substance y creación de bases low-poly para producción.\nGeralt de Rivia\r#\rArte conceptual de Geralt\rProceso de retopología de Geralt\rGeralt of Rivia in a Chibi Style by Yessica Servin Dominguez on Sketchfab Yennefer de Vengerberg\r#\rConcepto y retopología de Yennefer\rYennefer en el motor\rYennefer The Witcher by Yessica Servin Dominguez on Sketchfab Enemigo Ghoul\r#\rEscultura conceptual del Ghoul\rComparación de tamaño del Ghoul\rGhoul - The Witcher by Yessica Servin Dominguez on Sketchfab Otros assets\r#\rCreé props de entorno como arbustos, rocas y minerales utilizados en diferentes escenas.\nDiseño de HUD\r#\rCreé el layout del HUD e iconos, iteré sobre prototipos de UI, e implementé iconos pulidos y elementos de feedback.\nPulido del menú principal\r#\rPara el menú principal añadí sistemas de partículas y un post-proceso de bloom global para hacer la escena más dinámica y atmosférica.\nAjustes de iluminación\r#\rIteré sobre la iluminación de la escena para que coincidiera con la atmósfera de cada arena, equilibrando luces direccionales, contribución ambiental y elementos precalculados donde correspondía.\nEnlaces y descargas\r#\rDemo / build: Jugar en GitHub Pages Código fuente: Repositorio de GitHub Web del juego: Sitio web del juego ","date":"12 Enero 2020","externalUrl":null,"permalink":"/es/projects/the-witcher-a-bards-tale/","section":"Projects","summary":"","title":"The Witcher: A bard's tale","type":"projects"},{"content":"","date":"29 Enero 2019","externalUrl":null,"permalink":"/es/tags/glew/","section":"Tags","summary":"","title":"Glew","type":"tags"},{"content":"\r¿Qué es Hinata Engine?\r#\rHinata Engine es un motor de juego desarrollado para la asignatura de Motores de Videojuegos del Grado en Diseño y Desarrollo de Videojuegos de la UPC. Fue creado de forma colaborativa por Jaume Montagut y yo, con el objetivo principal de comprender cómo funciona internamente un motor de juego moderno.\nEl motor incluye los sistemas habituales que se esperan de un editor y runtime básico:\nImportación de modelos y texturas Arquitectura GameObject–Componente Guardado y carga de escenas UI de editor basada en ImGui Pipeline básico de cámara y renderizado También incorpora varias optimizaciones como:\nParticionado espacial con Octree Frustum Culling Animación Esquelética, una implementación completa basada en investigación para la tarea final. Repositorio de GitHub Mis contribuciones individuales\r#\rFui responsable de varios sistemas centrales del motor, incluyendo:\nFrustum Culling\r#\rImplementé comprobaciones de visibilidad en CPU basadas en los planos del frustum de la cámara. Integré bounding boxes para el culling en tiempo de ejecución. Añadí vista de depuración a través del panel \u0026ldquo;Configuración → Camera3D\u0026rdquo;. Octree\r#\rImplementé el particionado espacial de la escena para mejorar el renderizado y el picking. Añadí visualización dentro del panel de Escena. Serialización de escenas\r#\rImplementé el guardado y la carga de escenas jerárquicas usando JSON mediante Parson. Carga de escenas mediante arrastrar y soltar desde el editor. Mouse Picking\r#\rImplementé ray casting desde la cámara hacia la escena. Permití la selección de objetos haciendo clic sobre las mallas. Sistema de Animación Esquelética\r#\rComo parte de la tarea de investigación, implementamos un sistema completo de animación esquelética:\nUsamos Assimp para importar archivos .dae y .fbx a nuestro formato personalizado. Convertimos los datos importados al formato propio del motor. Implementamos animación de huesos y skinning de mallas. Añadimos blending de animaciones entre distintos clips. Habilitamos la visualización de la jerarquía de huesos para depuración. Soportamos la manipulación manual de huesos cuando el componente del animador está desactivado. Resumen de funcionalidades del motor\r#\rAñadir modelos\r#\rArrastra un archivo .fbx desde el panel Assets a la Escena para crear automáticamente la jerarquía de GameObjects. Mouse Picking\r#\rHaz clic en cualquier malla en la vista de Escena para seleccionarla. Frustum Culling (Depuración)\r#\rAbre Configuración → Camera3D y activa \u0026ldquo;Ver frustum culling\u0026rdquo;. Mueve o rota la cámara para visualizar el efecto. Particionado espacial\r#\rEl Octree siempre es visible en la vista de Escena para depuración. Gestión del tiempo\r#\rPlay: ejecutar el motor Pause: pausar y reanudar la simulación Gestión de recursos\r#\rLas estadísticas de uso de recursos son visibles en el panel Resources. Serialización de escenas\r#\rArrastra un archivo de escena a la vista de Escena para cargarlo. Guarda las escenas mediante Archivo → Guardar Escena. Librerías utilizadas\r#\rSDL — ventanas y entrada Dear ImGui — UI del editor Glew — cargador de extensiones OpenGL Parson — análisis JSON OpenGL 3 — backend de renderizado ","date":"29 Enero 2019","externalUrl":null,"permalink":"/es/projects/hinata-engine/","section":"Projects","summary":"","title":"Hinata Engine","type":"projects"},{"content":"","date":"29 Enero 2019","externalUrl":null,"permalink":"/es/tags/imgui/","section":"Tags","summary":"","title":"ImGui","type":"tags"},{"content":"","date":"29 Enero 2019","externalUrl":null,"permalink":"/es/tags/parson/","section":"Tags","summary":"","title":"Parson","type":"tags"},{"content":"\rDescripción general\r#\rSaving The Flamingos es un juego cooperativo creado durante la Not Only Games Jam de King. Dos jugadores deben cooperar para guiarse mutuamente a través de varios niveles y rescatar a los flamencos que se han separado de su grupo.\nLos jugadores asumen los roles de un Topo y una Serpiente, cada uno con habilidades asimétricas:\nEl Topo lleva los ojos vendados y no puede ver, pero puede escuchar todas las pistas de audio del juego a través de auriculares. La Serpiente puede ver el entorno pero no puede escuchar ningún sonido. Repositorio de GitHub Cómo se juega\r#\rAmbos jugadores controlan su personaje usando el D-pad de un mando. También se puede usar el teclado (WASD e IJKL), aunque no es la experiencia recomendada.\nTopo (con los ojos vendados):\nNo puede ver la pantalla. Escucha todas las pistas de audio. Puede detectar las \u0026ldquo;trampas de la Serpiente\u0026rdquo; por el sonido. Serpiente (no puede oír):\nPuede ver el entorno. No puede escuchar ninguna pista de audio. Puede identificar visualmente las \u0026ldquo;trampas del Topo\u0026rdquo;. El objetivo es que ambos personajes colaboren y lleguen al flamenco al final de cada nivel.\nNot Only Games Jam de King\r#\rEste juego fue creado para la #NotOnlyGamesJam organizada por King, bajo el tema principal:\n\u0026ldquo;Crea soluciones innovadoras para aumentar la diversidad y la inclusión en la sociedad.\u0026rdquo;\nLos subtemas de la jam fueron \u0026ldquo;Flamenco\u0026rdquo; y \u0026ldquo;Ponerse en los zapatos del otro\u0026rdquo;. ¡Agradecemos a King por invitarnos a participar en un evento tan inspirador e inclusivo!\n🏆 Premio a la Mejor Experiencia de Usuario\r#\rEl juego recibió el Premio a la Mejor Experiencia de Usuario, reconocido por sus mecánicas asimétricas únicas, intuitivas y altamente accesibles que fomentaron la colaboración y la empatía entre los jugadores.\nCréditos\r#\rAlex Campanar – @IamAcaree David Lozano – @DavidLozano42 Jaume Montagut – @Jaume_Montagut Joan Valiente – @KaikJoan Yessica Servin – @Yessica_SD Spoiler (Explicación del gameplay)\r#\rSi algo no queda claro, aquí está el sistema de trampas:\nTrampas de la Serpiente: Matan a la Serpiente. El Topo puede detectarlas por el sonido y puede pisarlas sin peligro.\nTrampas del Topo: Matan al Topo. La Serpiente puede verlas como baldosas grises/marrones con pinchos y puede pasarlas sin peligro.\n","date":"19 Enero 2019","externalUrl":null,"permalink":"/es/projects/savingtheflamingos/","section":"Projects","summary":"","title":"King's Not Only Games Jam - Saving The Flamingos","type":"projects"},{"content":"","date":"26 Enero 2019","externalUrl":null,"permalink":"/es/tags/sdl/","section":"Tags","summary":"","title":"SDL","type":"tags"},{"content":"\rDescripción general\r#\rTankerfield es un juego de supervivencia cooperativo para 4 jugadores en el que los jugadores controlan tanques y luchan contra oleadas de enemigos. El proyecto fue desarrollado por Gamificalo Studio, un equipo de 8 estudiantes de la Universidad CITM-UPC de Terrassa, España.\nComo parte del equipo de programación, implementé varios sistemas centrales de gameplay y motor.\nRepositorio de GitHub Mis contribuciones individuales\r#\rIntegración del Quadtree en el pipeline de renderizado Sistema de controladores (detección, vibración, remapeo + guardado persistente) Lógica de IA enemiga y sistema de comportamiento basado en estados Sistema de Object Pooling Extracción de sprites de los archivos originales del juego y modelos 3D Mejoras al módulo de mapa (ordenación de tiles, carga de colisiones) Extracción de sprites\r#\rInvestigué e implementé el flujo de trabajo para extraer sprites tanto de:\narchivos originales del juego modelos 3D (capturando y aislando fotogramas de animación) Esto permitió al equipo crear assets de mayor calidad y mantener la coherencia con el estilo artístico original.\nMejoras al módulo de mapa\r#\rRefactoricé y amplié el sistema de mapa:\nQuadtree en el pipeline de renderizado\r#\rMejoró enormemente el rendimiento del renderizado asegurando que los tiles y elementos del mundo se descartaran y renderizaran eficientemente.\nOrdenación de tiles\r#\rOrden correcto de los elementos según profundidad y tipo.\nCarga automática de colisiones desde Tiled\r#\rHizo la creación de niveles más rápida, menos propensa a errores y más amigable para los diseñadores.\nSistema de controladores\r#\rImplementé todo el pipeline de controladores:\nDetección de controladores conectados/desconectados Vibración del controlador Mapeo y remapeo de entradas Integración en la UI para indicaciones de botones Guardado de la configuración de entrada entre sesiones Este sistema garantizó una jugabilidad fluida para los cuatro jugadores y adaptabilidad a diferentes controladores.\nSistema de IA enemiga\r#\rDiseñé un sistema de IA basado en estados, facilitando la extensión, depuración y equilibrio de los enemigos. Algunos estados implementados:\nGET_PATH – Búsqueda de camino hacia el jugador más cercano MOVE – Seguir el camino generado BURN – Al ser alcanzado por fuego + aceite, los enemigos entran en pánico y reciben daño con el tiempo TELEPORT – Los enemigos lejos de los jugadores reaparecen más cerca UNSTUCK – Lógica para recuperarse cuando los enemigos aparecen o se mueven sobre un tile no transitable Este diseño modular permitió a los diseñadores añadir nuevos tipos de enemigos y habilidades rápidamente.\nSistema de Object Pooling\r#\rDebido al gran número de enemigos y proyectiles en pantalla, implementé un object pool para evitar asignaciones de memoria innecesarias. Esto mejoró significativamente el rendimiento y la estabilidad de memoria, especialmente durante las oleadas avanzadas.\nTareas adicionales\r#\rImplementación de la lógica del lanzacohetes (comportamiento de disparo basado en distancia) Múltiples correcciones de errores y mejoras de estabilidad a lo largo del proyecto ","date":"26 Enero 2019","externalUrl":null,"permalink":"/es/projects/tankerfield/","section":"Projects","summary":"","title":"Tankerfield","type":"projects"},{"content":"","date":"16 Enero 2018","externalUrl":null,"permalink":"/es/tags/2d-platformer/","section":"Tags","summary":"","title":"2D Platformer","type":"tags"},{"content":"","date":"16 Enero 2018","externalUrl":null,"permalink":"/es/tags/game-development/","section":"Tags","summary":"","title":"Game Development","type":"tags"},{"content":"\rDescripción general\r#\rSpooky Skeleton es un juego de plataformas 2D desarrollado con C++ y SDL. El juego presenta a un esqueleto protagonista que debe atravesar dos niveles de temática terrorífica: una cueva subterránea y el bosque exterior.\nRepositorio de GitHub Mis contribuciones individuales\r#\rSistema de movimiento del jugador (incluyendo lógica de salto y aceleración personalizada de \u0026ldquo;deslizamiento sobre hielo\u0026rdquo;). Comportamiento de enemigos terrestres. Implementación del algoritmo A* de pathfinding para enemigos. Carga de mapa y animaciones usando el formato Tiled (.tmx). Implementación del sistema de UI, totalmente data-driven y cargado desde XML. Sobre el juego\r#\rObjetivo Llegar al final de cada nivel evitando o derrotando a los enemigos y recogiendo todas las monedas.\nControles\nESPACIO – Saltar FLECHAS IZQUIERDA / DERECHA – Moverse Q – Atacar ESC – Abrir menú Características destacadas\r#\r❄️ Mecánica de plataforma de hielo\r#\rLas baldosas de hielo reducen la fricción, haciendo que el jugador se deslice. Esto requirió ajustes en la lógica de aceleración y desaceleración.\n🧟‍♂️ Enemigos y combate\r#\rEl jugador puede atacar y derrotar a los zombis.\n🧩 UI basada en datos\r#\rTodos los elementos de la UI se cargan desde archivos XML, permitiendo cambios de diseño flexibles sin modificar el código fuente.\nSubsistemas principales\r#\rEl juego está estructurado en torno a una arquitectura modular. El módulo principal de la aplicación (j1App.cpp) gestiona el ciclo de vida de todos los demás módulos llamando a métodos de clase base compartidos:\nAwake PreUpdate Update PostUpdate CleanUp Fábrica de entidades\r#\rSe utiliza un patrón factoría para crear y gestionar todas las entidades del juego, mejorando tanto la organización como el rendimiento.\nMódulo de UI\r#\rEl sistema de UI está completamente desacoplado de la lógica de gameplay. Gestiona eventos de forma independiente, permitiendo una separación clara entre la interfaz y los sistemas principales.\nDatos basados en XML\r#\rLos mapas, animaciones, texturas, rutas de archivo y datos de la UI se cargan desde XML. Esto mejora la legibilidad, facilita la iteración y evita los \u0026ldquo;números mágicos\u0026rdquo; hardcodeados.\n","date":"16 Enero 2018","externalUrl":null,"permalink":"/es/projects/spooky-skeleton/","section":"Projects","summary":"","title":"Spooky Skeleton","type":"projects"},{"content":"","date":"16 Enero 2018","externalUrl":null,"permalink":"/es/tags/tiled-map-editor/","section":"Tags","summary":"","title":"Tiled Map Editor","type":"tags"},{"content":"\rDescripción general\r#\rLast Resort es un remake tributo del juego arcade shoot \u0026rsquo;em up de SNK de 1992. El proyecto fue desarrollado desde cero en C++ por un equipo de cuatro estudiantes del Grado en Diseño y Desarrollo de Videojuegos de la UPC–CITM.\nNuestro objetivo era recrear fielmente el gameplay central, los comportamientos de los enemigos, las armas y la sensación general del juego original, aprendiendo al mismo tiempo cómo estructurar un motor de juego y un bucle de gameplay a pequeña escala.\nRepositorio de GitHub Mis contribuciones individuales\r#\rContribuí a varios sistemas centrales de gameplay, incluyendo:\nComportamiento de enemigos Implementé la lógica de comportamiento para varios tipos de enemigos, incluyendo patrones de movimiento, temporización de ataques e interacciones con el jugador.\nImplementación de armas Desarrollé una de las armas principales y su progresión de mejoras, integrándola en los sistemas de combate y colisiones existentes.\nCréditos\r#\rGestión y Programación: Jaume Montagut i Guix Web: wadoren.wixsite.com/gamedev GitHub: JaumeMontagut\nArte, QA y Programación: Alejandro Gamarra Niño Instagram: @ax3_rt GitHub: alejandro61299\nQA: Dani Sanchez Flores Instagram: @vampir_nex GitHub: Dasanch\nProgramación y Revisión de Código: Yessica Servín Domínguez Instagram: @randomgerbit GitHub: YessicaSD\nSobre el juego\r#\rAl borde del apocalipsis, Last Resort sitúa al jugador al mando de una poderosa nave espacial acompañada de un dron de apoyo conocido como la \u0026ldquo;unidad\u0026rdquo;. Juntos, el objetivo es luchar contra oleadas de enemigos y evitar la catástrofe inminente.\nVídeo del remake\r#\rVídeo del juego original\r#\r","date":"3 Enero 2018","externalUrl":null,"permalink":"/es/projects/last-resort/","section":"Projects","summary":"","title":"Last Resort","type":"projects"},{"content":"","externalUrl":null,"permalink":"/es/resume/experience/3dtechomegazeta/","section":"Resume","summary":"","title":"3D Tech Omega Zeta","type":"resume"},{"content":"","externalUrl":null,"permalink":"/es/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/es/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","externalUrl":null,"permalink":"/es/resume/education/","section":"Resume","summary":"","title":"Education","type":"resume"},{"content":"","externalUrl":null,"permalink":"/es/resume/experience/","section":"Resume","summary":"","title":"Experiencia","type":"resume"},{"content":"","externalUrl":null,"permalink":"/es/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","externalUrl":null,"permalink":"/es/resume/experience/unidesa-cirsa/","section":"Resume","summary":"","title":"Unidesa I+D Cirsa","type":"resume"}]