Autor: Oscar García i Panyella - “KOKOPUS”

Fecha: 13-04-01

Técnicas de “Flocking” para entornos de Realidad Virtual

Este artículo pretende retomar uno de los temas que en su día introduje en la revista. Ahora la aproximación a la problemática será mucho más práctica, intentando plantear soluciones concretas.

Introducción

¿Cómo podemos animar muchos seres virtuales (de ahora en adelante “avatars”) sin tener que modelarlos uno a uno?. Esta es la cuestión que quisiera plantearos. En grandes factorías de producción Multimedia como Disney, producen películas con escenas en las que inundan la pantalla de avatars que se comportan independientemente uno de otro. Por ejemplo la escena de la plaza en EL JOROBADO DE NOTRE DAME o bien la estampida de animales en EL REY LEON además de los típicos escuadrones de aviones “clónicos” que aparecen en muchas películas bélicas.

Al contrario de lo que pudiera parecer, los diseñadores no modelan y colorean a cada avatar por separado. Existen técnicas que automatizan este proceso. La idea es que el ordenador calcule automáticamente los efectos de “flocking” (bandadas, rebaños, estampidas, etc...) de forma que la responsabilidad del diseñador sólo implique la óptima elección del modelo 3D (mallado) o 2D (textura) para cada avatar.

Veamos un ejemplo con el que he trabajado recientemente:

Varias especies de animales marinos conviven en un Acuario Virtual.

En rojo se observan las partículas de comida que engullen los animales.

Para crear avatars “inteligentes” les tendremos que dotar de:

·         Visión. La posibilidad de ver todo aquello que les rodea. Técnicamente se trata de definir una región de “sensibilidad” para cada uno, dentro de la cual tienen acceso a la base de datos de geometría.

·         Movimiento. Es obvio que un pájaro vuela y un pez nada. Eso lo podemos solventar con un “solver numérico” de más o menos orden, según la precisión buscada. Ejemplos típicos son los algoritmos de Euler, Punto Medio o Runge-Kutta.

·         Colisión. El avatar tendrá conciencia del resto de elementos de la escena y chocará con ellos, ya sean objetos estáticos u otros avatars. Este punto es inseparable del primero, los sensores de visión artificial.

·         Intencionalidad. Un avatar tiene asociado un generador de intenciones que básicamente se encarga de las decisiones en cada momento: ¿voy a chocar?, ¿me estoy alejando de mi destino?, ¿estoy cerca del resto del grupo?, etc...

Existen aún más posibilidades al respecto pero de momento trabajaremos con lo dicho. Para centrar el tema hablaré de un ejemplo concreto: la simulación de bandadas de pájaros.

Pensad que son muchos los juegos que se sustentan en técnicas de este estilo. Algunos ejemplos son Unreal o Half-life.

Diagrama General

Bueno que mejor que un diagrama para tener una visión global del sistema. Vamos a echarle una ojeada:

Nuestros avatars-pajaros “piensan por sí mismos” y por tanto tienen que ser capaces de priorizar entre diversas acciones. En este caso observamos que sus “heurísticas” de pensamiento son:

·         Tengo que volar hacia un destino predefinido, como por ejemplo un nido, un árbol o un montón de comida.

·         Tengo que permanecer cerca de la bandada. Los pájaros tienden a volar juntos en grupo y nunca se alejan demasiado a no ser que la necesidad sea imperiosa!.

·         Tengo que evitar colisiones. Esta claro que un pájaro no desea chocar contra un árbol, contra otro pájaro o bien contra un posible predador.

·         Tengo que seguir a mi líder. En el caso de existir puede optarse porque nuestros pájaros sigan más o menos fielmente a uno que ejerce de “jefe de grupo”.

El hecho de decidir qué hacer y qué no hacer o incluso priorizar entre varias opciones existentes se relega al llamado “Generador de Intenciones”. Este módulo es el que realmente modela el cerebro de cada pájaro. Un águila no actúa igual que una paloma o que un gorrión, a eso me refiero.

En la simulación intervienen diversos módulos que paso a describir uno por uno a continuación:

Solver Numérico

Parece contradictorio que empiece por el último módulo pero lo necesito para justificar el proceso que viene antes.

Un solver numérico se encarga de calcular la evolución dinámica del sistema, es decir, la posición y velocidad de cada avatar a cada instante de simulación. La idea fundamental radica en definir lo que se llama un “problema de valor inicial”. Se parte de unas condiciones iniciales conocidas, típicamente las posiciones y velocidades iniciales de todos los avatars. A partir de éstas y conocido el Dt (incremento de tiempo) que supondremos que se produce a cada iteración, pasamos a “mover” toda la geometría. O sea que:

para t = t0, conocemos la posición(t0) = (x0,y0,z0) y la velocidad(t0) = (vx0,vy0,vz0) de cada avatar

No es nada raro. De hecho básicamente a todos os tendría que sonar la famosa segunda ley de Newton:

Fuerza = Masa * Aceleración

Definidas cuáles son las fuerzas que actúan sobre un determinado avatar y conocida su masa, deducimos su aceleración. A partir de la aceleración y del tiempo deducimos su incremento de velocidad instantánea y de ésta su incremento de posición...osease:

1) Aceleración = Fuerza / Masa

2) Nueva velocidad = Velocidad anterior + Aceleración * Dt

3) Nueva posición = Posición anterior + Velocidad anterior * Dt

y ya lo tenemos! si hacemos esto repetidamente iteración tras iteración renderizando al final de cada una, tenemos un sistema de simulación dinámica. A este procedimiento tan simple se le conoce como “método explícito de Euler”. Es muy inestable para Dt’s demasiado grandes pero si se controla este parámetro no hay problema. Existen métodos más completos para integrar pero suelen emplearse en simulación física, cuando la precisión es un factor decisivo. En nuestro caso podemos sacrificarla un poco en favor de la velocidad, idealmente “tiempo real”.

Por tanto siempre tendremos una función dedicada a la resolución del método de Euler en nuestro sistema. Conclusión: necesitamos fuerzas como parámetro de entrada!

Para más información leed mis artículos sobre Simulación física. Métodos numéricos y Paso adaptativo.

Fuerzas

Podemos “inventar” tantas fuerzas como consideremos conveniente. De hecho existen procedimientos matemáticos complejos para deducir fuerzas a partir de condiciones aunque no entraré en ese punto. Se trata de introducir gravedad, fricción con el aire, tornados, eléctricas, magnéticas, muelles ... De momento pueden sernos de interés las dos primeras:

Gravedad Þ Fuerza (Newton’s) = masa (Kg’s) * G (típicamente constante de gravitación universal de valor -9.81 m/s2)

Fricción Þ Fuerza (Newton’s) = -Kv * velocidad (Kv es una constante de restitución a escoger por parte del programador. Se observa que esta fuerza siempre va en contra de la dirección del movimiento y dota al sistema global de más estabilidad).

Obviamente para la primera se nos hace imperiosa la necesidad de definir una masa para cada avatar.

Módulo de cálculo de aceleraciones

Bueno acabamos de ver que necesitamos trabajar con fuerzas para poder calcular aceleraciones. De hecho también es totalmente lícito añadir aceleraciones precalculadas a las obtenidas a partir de las fuerzas. Esto es lo que haremos con el generador de intenciones.

La idea básica es que en función de las decisiones del avatar se calcule un vector de aceleración que sumado a las aceleraciones provenientes de las fuerzas, se utilizará para desplazarlo en la próxima iteración. Una posible aproximación al cálculo es el promediado de varios vectores ponderados que es lo que se muestra en el diagrama. El avatar-pájaro calcula los siguientes vectores (siempre en coordenadas de mundo 3D):

1.       Vector desde su posición hasta el destino final prefijado. Este vector lo pondera la constante KD.

2.       Vector desde su posición hasta el centroide de la bandada (se entiende por centroide el promedio de las posiciones de todo el resto de pájaros). Este vector lo pondera la constante KB.

3.       En el caso de detección inminente de colisión, vector de respuesta a ésta. Me refiero al vector que evita la colisión. Este vector lo pondera la constante KC.

4.       Vector desde su posición hasta la del líder de la bandada (en caso de haberlo). Este vector lo pondera la constante KL.

Calculados estos vectores con un procedimiento puramente geométrico (hablo de ésto en mi curso de Gráficos publicado en esta misma revista) los ponderamos (multiplicamos) con las constantes para después promediarlos. Lo fundamental es escoger adecuadamente los valores de las constantes de forma que demos más peso a las decisiones de más prioridad y menos a las menos importantes (para el avatar-pájaro que estemos modelando está claro puesto que cada uno tiene sus propias constantes). Esto quedaría así:

Aceleración intenciones = [ KD(xd,yd,zd) + KB(xb,yb,zb) + KC(xc,yc,zc) + KL(xl,yl,zl) ] / 4.0

Aceleración fuerzas = S (Fuerzas) / masa

Aceleración Total = Aceleración intenciones + Aceleración fuerzas

De esta forma tenemos en cuenta tanto lo que decide el pájaro como lo que no puede controlar, es decir la influencia del resto del entorno sobre él. A partir de esta Aceleración Total y empleando el solver numérico deduciremos las nuevas posiciones / velocidades.

Correspondencia de velocidades

En el caso de que sea importante seguir el ritmo del resto de la bandada será de importancia este criterio. Si bien ya lo hemos tenido algo en cuenta en el apartado anterior con el vector avatar-centroide bandada, no está de más añadir un segundo punto de ajuste fino.

Simplemente se trata de escalar el módulo de la velocidad recién calculada (nada de tocar su dirección y sentido !!!) con la constante KV en el caso de detectar que somos demasiado lentos para el resto de la bandada aunque nos estemos dirigiendo hacia ella. La corrección pues:

Nueva velocidad final = Nueva velocidad * KV

De esta forma podremos acelerar a nuestros avatars si se quedan cortos! Quede claro que la metodología que estoy comentando conduce a un efecto realista a nivel de visualización y no de precisión. Si deseáramos lo segundo probablemente tendríamos que recurrir a algoritmos mucho más complejos. Esto nos irá muy bien para una bandada de pájaros o para el acuario virtual que os presentaba antes pero no para un sistema de cirugía virtual en tiempo real, por ejemplo.

Colisiones

Para documentaros sobre este tema podéis dirigiros a los artículos que escribí en su día sobre Detección de Colisiones entre objetos  deformables y Respuesta a las colisiones.

Además de lo mencionado en esos artículos quisiera comentar otra técnica que os puede ser muy útil para evitar obstáculos de forma automática en sistemas como éste, basados en cálculos dinámicos. Se trata de la utilización de voxels inicializados con campos vectoriales. Vamos a ello.

Un voxel es básicamente un “cubito” 3D en el espacio . Cada voxel almacena un valor de propiedad. Por claridad vamos a imaginarnos lo que se vería desde arriba, algo así como una proyección ortográfica superior de un trocito de nuestro mundo 3D:

En este caso supongamos el caso en que uno de nuestros pájaros tiene que bordear una columna. Podríamos optar por un procedimiento puramente geométrico a partir de vectores proyectándola sobre el plano de visión del pájaro y tratando de encontrar cual es la dirección que éste debe tomar para no chocar. Esto es complicado y tedioso puesto que empiezan a aparecer casos y más casos de esos que hay que contemplar aparte (singularidades...buuufff).

Ya que tenemos un sistema basado en fuerzas vamos a utilizarlas para evitar la columna con lo cual no tendremos que añadir procesos “extraños” al sistema. Sólo aprovecharemos lo que ya hay hecho!

Bueno se trata de añadir una nueva fuerza: un campo vectorial gradiente que apunte en dirección normal y opuesta al centro de la columna. Es un campo radial, normal a la superficie de ésta. Vamos mirad la figura y decidme que entendéis lo que digo!!!

Cada cajita es un voxel, una discretización 3D del espacio. Tiene unas dimensiones fijas conocidas y por lo tanto es muy fácil saber si nuestro pájaro esta dentro de uno. Montamos una “grid” como la de la figura (la resolución de ésta la decidimos a priori según nos parezca), asociamos un vector orientado de forma radial y hacia afuera en cada voxel y a simular.

El vector de cada voxel puede calcularse fácilmente a partir del centro de éste y el centro de la sección circular de la columna.

Si detectamos que nuestro pájaro se ha metido dentro de un voxel, o sea que se está acercando peligrosamente a la columna, le aplicaremos ,además de todo lo anterior, la fuerza que contiene. Podemos incluso escalarla para darle más o menos peso en relación al resto. Dejamos al solver numérico que haga su trabajo y tachán!!! (mirad la línea naranja de la figura) ... el pajarito no choca!!!

Consideraciones finales

Para acabar deciros que típicamente se normalizan todos los vectores (ya sabéis, se les divide por su módulo para hacerlos unitarios) de forma que inicialmente todos los módulos sean iguales. En esta situación digamos que todos “pesan” igual y es entonces cuando se escalan por sus constantes respectivas. Con estas constantes estamos dando más peso a unos y menos a otros, en definitiva, estamos modelando el “cerebro” del avatar.

Quede claro que “No es oro todo lo que brilla” y que la implementación de sistemas de este tipo siempre pasa por la resolución de distintos problemas. Para empezar no es fácil escoger el valor de cada constante que nos da el comportamiento exacto que buscamos. Tampoco lo es encontrar el valor adecuado del Dt (queremos velocidad pero si lo subimos mucho tendremos inestabilidad...). Hay que probar y probar pero bueno, ¿algo de “salsa” tiene que haber no? ¿si no dónde está la aventura?

Si os interesa más el tema podéis leer mis artículos sobre Aprendizaje Artificial aplicado a Modelos deformables, también aquí en Macedonia.