Tabla de contenidos

efectos de fluidos están empezando a ser lo suficientemente barato que puedan ser utilizados ampliamente en aplicaciones en tiempo real tales como juegos. Parte de esto se debe a la obra de escritores de sombreado inteligentes que han puesto simulaciones de dinámica de fluidos en la GPU (echa un vistazo a esta hermosa shader por ejemplo), pero también hay una serie de algoritmos aproximados que son mucho más rápido que hacer el pleno de Navier Stokes ecuación. Me voy a centrar en un algoritmo poco conocido llamado estocástico Rotación Dinámica (SRD). Tiene la propiedad agradable que no importa lo que haces a ella, es estable incondicionalmente (puede obtener resultados extraños, pero nunca tendrá la simulación de soplar para arriba en usted). En este artículo, vamos a construir un conjunto basado en WebGL de shaders que utiliza SRD para simular un fluido.

estocástico Rotación Dinámica [1]

La idea detrás de este método es modelar el fluido con las partículas, pero para reemplazar las complejas colisiones entre partículas individuales con una interacción aleatorizado. Esto significa que todas las partículas se pueden mover balístico, que es mucho más sencillo de calcular. Las colisiones aleatorios se implementan mediante la adopción de todas las partículas dentro de una cierta región del espacio (una celda de cuadrícula, digamos) y la rotación de sus velocidades relativas del centro de la velocidad de masa de la célula por una cantidad aleatoria.

Así que, ¿por qué funciona esto? Básicamente, eliminando el componente promedio de sus velocidades, lo que queda es algo que se hizo caso omiso de todos modos por los enfoques de fluido continuo – una distribución específica de las fluctuaciones en torno a la media. Así, en el límite del fluido continuo, no importa todo lo que mucho de lo que hacemos a esta distribución con tal de que no persista en escalas de tiempo largos. Este proceso es básicamente la versión ‘basado en partículas’ de la etapa de colisión en el algoritmo del enrejado-Boltzmann. Sirve para que las partículas se influyen entre sí de una manera general, sin tener que preocuparse por los detalles de las interacciones.

La desventaja principal de SRD es que su algo difícil de hacer otra cosa que un simple líquido uniforme con él (por ejemplo su difícil modelo algo así como la tensión superficial) nada. Sin embargo, puede obtener algunos errores espectaculares cuando intenta.


Shader Diseño

Debido a que estamos básicamente va a ser la simulación de partículas en la GPU, tenemos que tomar un paso atrás y pensar en cómo vamos a empaquetar los datos de Hay dos pasos principales para el algoritmo -. Streaming de las partículas en función de su velocidad y, a continuación, recoger partículas en cada celda de la cuadrícula y los girar alrededor de velocidad media de la red.

Cada partícula tiene una coordenadas x e y, así como una x y la velocidad y. Probablemente necesitamos más precisión que un solo byte de ofertas de color, así que voy a utilizar la extensión OES_float_texture para WebGL, que nos permite tener 4 bytes por cada punto de color texturas flotantes. Esto hará las cosas mucho más fácil – si no lo hace (y puede que no sea capaz de si está utilizando Three.js que aún no lo apoyan, o una plataforma en la que no se proporciona la extensión), entonces que empacar básicamente los valores a través de múltiples canales de color y texturas múltiples. Este es un buen punto de partida si usted tiene que ir por ese camino.

Ahora podemos almacenar nuestra información de las partículas, pero hay otra parte de la información que vamos a nuestra necesidad de shader – es decir, la velocidad media de todas las partículas en la misma celda de la cuadrícula. La forma más sencilla se podría hacer esto sería básicamente que cada partícula de cómputo de su propio medio por iteración a través de todas las otras partículas y comprobar si están en la misma celda. Esto es prohibitivamente caro, incluso en la GPU, ya que es O (L4) en el tamaño del sistema L (porque hay partículas NαL2, y la comprobación de cada par de partículas es O (N2)).

Parece que podríamos hacer un poco mejor mediante el uso de un intermedio render pase, y tienen recuento de cada celda de la cuadrícula hasta sus propias velocidades medias antes de pasarlos a las partículas. La forma ingenua sería de nuevo, que cada celda de la cuadrícula iterar sobre cada partícula de averiguar qué partículas pertenecen a ella. Resulta que esta manera de hacerlo es también O (L4), pero tiene una mejor constante hacia el frente.

Lo que nos gustaría hacer es decir para cada partícula, determinar qué celda de la cuadrícula que está y añadir su información en el búfer de celda de la cuadrícula en ese lugar en particular. El problema es que no podemos escribir en el buffer de celda de la cuadrícula cuando estamos operativo en los fragmentos de partículas. En general, sin embargo, la GPU no tiene este problema – puedes mezcla de aditivos cientos de partículas entre sí utilizando la funcionalidad de tuberías fijo. Resulta que hay un ingenioso truco utilizado para el mapeo de fotones llamada ‘de la plantilla de enrutamiento’ que hace algo muy parecido a esto (se puede extraer el papel aquí). La idea básica es que cada partícula también corresponder a un vértice que se pasa al vertex shader, que nos da acceso aleatorio a un búfer de datos de salida. En la técnica de mapeo de fotones, la memoria intermedia de la plantilla se utiliza entonces para averiguar dónde exactamente en el búfer de salida para escribir.

Vamos a hacer algo un poco más simple, y sólo tiene que utilizar una mezcla de aditivos con la velocidad de cada partícula almacenada en los canales rojo y verde. También es necesario para obtener el número total de partículas en cada celda de la cuadrícula, por lo que cada partícula se sumará 1 al canal azul de la salida. Puesto que estamos utilizando texturas de punto flotante, esto es, básicamente, todo lo que tenemos que hacer para acumular las estadísticas de partículas, enrutamiento requiere ninguna plantilla!

La Parte Javascript

Hay una gran cantidad de código repetitivo que yo no quiero pasar por demasiados detalles – su incluida en el paquete de código en la parte final del artículo, en cualquier caso, que he tratado de comentario a fondo. Básicamente tenemos que configurar nuestros render pases y así sucesivamente. Sólo voy a cubrir trucos particulares que son útiles para conocer o difícil de entender.

Vamos a configurar una textura para la red de células, con una resolución de 256 × 256. a continuación, queremos tener suficientes partículas para llenar general, cada célula de esta cuadrícula con un par de partículas. Para ello, tenemos que configurar dos cosas – una textura con un píxel por partícula, y una serie de vértices para hacer uso de nuestra gl.POINTS y ​​‘gridSortShader’. Voy a ir con unos 500k partículas, lo que corresponde a una textura de 700 × 700. Esto es un poco arbitrario – Empecé con 512 × 512 que parecía ruidoso y corrió rápido, y fue hasta 1024 × 1024, que parecía suave, pero era muy lento en mi GPU, por lo que 700 × 700 era una especie de la punta de lanza de lo que mi hardware podría manejar. El algoritmo funciona con casi cualquier número, pero un menor número de partículas por célula de la parrilla es más ruidoso (por supuesto puede utilizar un menor número de celdas de la cuadrícula también).

Estas texturas son también nuestros framebuffers. Vamos a ser la representación en ellos con el fin de iterate la simulación. El código para configurarlas se ve así:
var dataBuffer, dataBufferTex; var gridBuffer, gridBufferTex; createBuffers de función () {dataBufferTex = createTexture (); // Esta es una función que llama gl.createTexture (), se une la textura, y conjuntos de envoltura y la interpolación params gl.bindTexture (gl.TEXTURE_2D, dataBufferTex); gl.texImage2D (gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.FLOAT, baseImage2); // baseImage2 es una carga que la imagen con una condición inicial dataBuffer = gl.createFramebuffer (); gl.bindFramebuffer (gl.FRAMEBUFFER, dataBuffer); gl.framebufferTexture2D (gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, dataBufferTex, 0); gridBufferTex = createTexture (); gl.bindTexture (gl.TEXTURE_2D, gridBufferTex); gl.texImage2D (gl.TEXTURE_2D, 0, gl.RGBA, 256, 256, 0, gl.RGBA, gl.FLOAT, null); gridBuffer = gl.createFramebuffer (); gl.bindFramebuffer (gl.FRAMEBUFFER, gridBuffer); gl.framebufferTexture2D (gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, gridBufferTex, 0);} 1234567891011121314151617181920vardataBuffer, dataBufferTex; vargridBuffer, gridBufferTex; functioncreateBuffers () {dataBufferTex = createTexture (); // Esta es una función que las llamadas Gl. createTexture (), se une la textura, y conjuntos de envoltura y la interpolación paramsgl.bindTexture (gl.TEXTURE_2D, dataBufferTex); gl.texImage2D (gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.FLOAT, baseImage2); / / baseImage2 es una carga que Image con una inicial conditiondataBuffer = gl.createFramebuffer (); gl.bindFramebuffer (gl.FRAMEBUFFER, dataBuffer); gl.framebufferTexture2D (gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, dataBufferTex, 0); gridBufferTex = createTexture (); gl.bindTexture (gl.TEXTURE_2D, gridBufferTex); gl.texImage2D (gl.TEXTURE_2D, 0, gl.RGBA, 256,256,0, gl.RGBA, gl.FLOAT, null); gridBuffer = gl. createFramebuffer (); gl.bindFramebuffer (gl.FRAMEBUFFER, gridBuffer); gl.framebufferTexture2D (gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, gridBufferTex , 0);}

Una cosa a la nota – queremos usar gl.NEAREST interpolación para estas texturas, ya que estamos tratando de utilizarlos para almacenar estructuras de datos que no son de textura. La interpolación se hace un lío nuestros datos.

Ahora quieren crear la geometría que necesito para hacer realidad en estos tampones. Esto consiste en una matriz masiva vértice para las partículas ‘el objeto de partículas’, y luego dos quads pantalla completa para hacer corridas de simulación en las memorias intermedias de datos. Uno de estos quads estará asociado con el vértice y fragmento shader que realizan el movimiento real de las partículas (permite llamar a esto el ‘simulación quad’). El otro se utilizará para hacer la simulación a la pantalla (la ‘quad representación’).

El objeto de partículas, en particular, vamos a hacer un poco de un truco. Nos buffer de una serie de flotadores 2N, pero estos flotadores no son las posiciones de las partículas reales (que sería demasiado caro para actualizar en JS). En su lugar, los flotadores dan las coordenadas x e y de la partícula en la textura de datos. Esto permitirá que nuestra vertex shader buscar fácilmente la posición de la partícula de la textura de datos, por lo que puede averiguar dónde trazar la misma. La estructura de este es:
… parts.array = new Float32Array (700 * 700 * 2); for (var i = 0; i & lt; 700 * 700; i ++) {parts.array [2 * i] =% i 700; parts.array [2 * i + 1] = Math.floor (i / 700); } … 1234 … parts.array = newFloat32Array (700 * 700 * 2); for (vari = 0; i & lt; 700 * 700; i ++) {parts.array [2 * i] =% i 700; partes .array [2 * i + 1] = Math.floor (i / 700);} …

Ahora, en el bucle de render principal, vamos a dibujar el objeto de partículas en el gridBuffer, entonces nosotros’ vamos a hacer que el cuádruple de simulación en el dataBuffer. Este es un paso completo de nuestra simulación. Desde su suficientemente rápido, podemos hacer dos de ellos por fotograma procesado. La clave es que permite la mezcla y ajustar la función de mezcla withgl.blendFunc (gl.SRC_ALPHA, gl.ONE) antes de renderizar las partículas, y luego desactivar mezcla después para preservar el canal alfa de nuestra dataBuffer. Este código es todo en code.js, en la función de render ().

El Shaders

Hay dos programas de sombreado a nuestra simulación. Se toma la textura de posiciones de las partículas y las velocidades y la utiliza para rinden partículas en la red para el promediado, utilizando el shader de vértice:
… aVertexPosition atributo vec2; // Cargamos en el índice derecho para cada partícula aquí variando vec2 VUV; dataTexture sampler2D uniforme; flotador h = 1,0 / 700,0; // Este es el píxel espaciamiento de la memoria intermedia de datos, de modo que obtenemos la partícula derecho void main () {VUV = vec2 (h * aVertexPosition.x, h * aVertexPosition.y); gl_Position = vec4 (2,0 * Texture2D (dataTexture, VUV) .XY-1.0,0.0,1.0); / * Lo 2 * x-1 aquí es porque las coordenadas de recorte son -1 a 1, pero los UVs son de 0 a 1 * / gl_PointSize = 1,0; // Render en un píxel} 1234567891011121314 … atributo vec2 aVertexPosition; // Cargamos en el índice derecho para cada herevarying partícula vec2 VUV; uniforme sampler2D dataTexture; floath = 1,0 / 700,0; // Este es el espaciado de píxeles de los datos tampón, por lo que obtenemos la particlevoidmain derecha () {VUV = vec2 (h * aVertexPosition.x, h * aVertexPosition.y); gl_Position = vec4 (2,0 * Texture2D (dataTexture, VUV) .XY-1.0,0.0,1.0); / * lo 2 * x-1 aquí es porque las coordenadas de recorte son -1 a 1, pero los UVs son de 0 a 1 * / gl_PointSize = 1,0; // genera en un píxel}

Este bit sólo lo hace una búsqueda de la textura usando las coordenadas que cargan en la matriz de vértices y obtiene la verdadera posición de la partícula – los dos primeros canales de nuestro color de punto flotante. A continuación, asigna esta en la red, y le dice al fragment shader para rendir un píxel en esa posición en la rejilla (con mezcla de aditivos). El color fragmento que queremos hacer es los componentes X e Y de la velocidad de la partícula, que se almacenan en el azul y alfa canales de la textura de datos. También vamos a añadir ‘1’ para el canal azul de la textura de la red para cada partícula en esa celda:
… gl_FragColor = vec4 (Texture2D (dataTexture, VUV) .ba, 1.0,1.0); … 123 … gl_FragColor = vec4 (Texture2D (dataTexture, VUV) .ba, 1.0,1.0); …

Eso es todo para la etapa de promediado. Ahora, pasamos a la shader que se mueve y gira las partículas. El vertex shader es la duración habitual de ‘coordenadas de los vértices de paso sobre’ trato, pero el fragment shader es donde la carne es de este. Hay una gran cantidad de código en la que declaro uniformes que voy a saltar, pero aquí está el resumen: cuatro numbersz1 al azar, Z2, Z3, Z4 sembrar la rotación aleatoria, el cursor del ratón y velocidad actuales de manera que el usuario puede perturbar la sistema, y ​​las tres texturas: la textura de datos, textura de la red, y una textura que contiene cualquier obstáculo que queremos el flujo .

Lo primero no trivial es que necesitamos para generar números aleatorios dentro de nuestro shader. Hay un poco de código de sombreado para este flotando alrededor que me atrapé con el propósito
flotar al azar (vec2 co) {fract retorno (sin (punto (co.xy, vec2 (12.9898,78.233))) * 43758,5453); } 1floatrandom (co vec2) {returnfract (sin (punto (co.xy, vec2 (12.9898,78.233))) * 43758,5453);}

En el bloque principal de nuestra fragment shader, nos agarra la partícula de la textura de flotación y utilizar sus coordenadas x e y para encontrar la velocidad media que corresponde en su celda de la cuadrícula actual (los canales rojo y verde de la textura de la red). Tenemos para normalizar esto por el número total de partículas en esa celda de la cuadrícula, por lo dividimos los valores de rojo y verde por el valor de azul.
void main () {vec4 thisParticle = Texture2D (dataTexture, VUV); vec3 VBar = Texture2D (gridTexture, thisParticle.xy) .xyz; si (vbar.z & lt; 1,0) vbar.z = 1,0; // Prevenir infinitos por apriete esto en 1 vbar.x / = vbar.z; vbar.y / = vbar.z; // Go de ‘velocidad total’ a ‘velocidad media’. Blue12345voidmain () {vec4 thisParticle = Texture2D (dataTexture, VUV); vec3 VBar = Texture2D (gridTexture, thisParticle.xy) .xyz; si (vbar.z & lt; 1,0) vbar.z = 1,0; // Prevenir infinitos por apriete esto en 1vbar.x / = vbar.z; vbar.y / = vbar.z; // Go de ‘velocidad total’ a ‘velocidad media’. Azul

A continuación, calcular la diferencia entre la velocidad de la partícula (azul, canales alfa) con la velocidad media de la célula:
vec2 dv = thisParticle.zw-vbar.xy; 1vec2 dv = thisParticle.zw-vbar.xy;

ahora queremos girar este por un ángulo aleatorio, que debe ser el mismo ángulo aleatorio para todas las partículas en la misma cuadrícula celular con el fin de conservar el impulso. Esto utiliza la propiedad de que cuando he sustraído de la del centro de la velocidad de masa de un grupo de partículas, las cantidades de movimiento residual de esas partículas sumas a cero. Es similar a como si gira un objeto alrededor de su centro de masa, la rotación no causa el centro de masa no se mueve.
flotar dtheta = aleatorio (vec2 (z1 * piso (thisParticle.x * GX) + z3, z2 * piso (thisParticle.y * GX) + z4)) * 6,283185307 ;, / * Un ángulo aleatorio para ct rotación * / float = cos (dtheta); flotar st = sin (dtheta); thisParticle.z = (dv.x * ct + dv.y * st + vbar.x); // Aplicar una matriz de rotación a DV y añadir VBar de vuelta en thisParticle.w = (- dv.x * st + dv.y * ct + vbar.y); 12345floatdtheta = aleatorio (vec2 (z1 * piso (thisParticle.x * GX) + z3, z2 * piso (thisParticle.y * GX) + z4)) * 6,283185307;, / * Un ángulo aleatorio para la rotación * / floatct = cos (dtheta); floatst = sin (dtheta); thisParticle.z = (dv.x * ct + dv.y * st + vbar.x); // Aplicar una matriz de rotación a dV y añadir VBar volver inthisParticle.w = (- dv.x * st + dv.y * ct + VBar. y);

también quiero echar a perder un poco con la velocidad de la partícula aquí. Sería bueno si el movimiento en el sistema finalmente se calmó, así que voy a añadir un poco de amortiguación. Al mismo tiempo, quiero mantener un cierto grado de ‘livelyness’, con partículas que se mueven en varias direcciones al azar por lo que añado forzando una al azar. La combinación de estos dos efectos es algo que se llama una relación ‘de fluctuación-disipación’ que da el gas una temperatura bien definida. Esto a su vez da la presión de un fluido y la viscosidad. Hay un punto técnico, que es que si va a añadir un forzando al azar a una partícula, que debe escalar con Dt — √ lugar de Dt debido al hecho de que cuanto menor sea el paso de tiempo, más las muestras aleatorias tenderán a cabo promedio. Este es un truco útil en general, si por ejemplo, va a añadir fuerzas aleatorias a una simulación física y desea que los resultados sean independientes del tamaño de paso de tiempo.
flotador theta2 = aleatorio (vec2 (97. * z2 * vUv.x + z1,45.3 * z4 * vUv.y + z3)) * 6,283185307; // Otro ángulo aleatorio thisParticle.z = thisParticle.z * (1,0 a 0,0001 * dt) + 0,0025 sqrt * (dt) * cos (theta2); thisParticle.w = thisParticle.w * (1,0 a 0,0001 * dt) + 0,0025 sqrt * (dt) * sin (theta2); 123floattheta2 = aleatorio (vec2 (97. * z2 * vUv.x + z1,45.3 * z4 * VUV .y + z3)) * 6,283185307; // Otra anglethisParticle.z aleatorio = thisParticle.z * (1,0 a 0,0001 * dt) + 0,0025 * sqrt (dt) * cos (theta2); thisParticle.w = thisParticle.w * ( 1,0-0,0001 dt *) + 0,0025 * sqrt (dt) * sin (theta2);

I también dan las partículas de un empuje sobre la base de la distancia desde el cursor del ratón:
float r = (cursor.x-thisParticle.x) * (cursor.x-thisParticle.x) + (cursor.y-thisParticle.y) * (cursor.y-thisParticle.y); flotar w = exp (- (r / 0,01) * (r / 0,01)); thisParticle.z + = dt * cursorVel.x * w * 0,2; thisParticle.w + = dt * cursorVel.y * w * 0,2; 1234floatr = (cursor.x-thisParticle.x) * (cursor.x-thisParticle.x) + (cursor.y-thisParticle.y) * (cursor.y -thisParticle.y); floatw = exp (- (r / 0,01) * (r / 0,01)); thisParticle.z + = dt * cursorVel.x * w * 0,2; thisParticle.w + = dt * cursorVel.y * w * 0,2;

Ahora que hemos actualizado velocidad de la partícula, vamos a pasar la partícula usando esa velocidad. Vamos a medir todo en términos de las coordenadas textura de la red, por lo dividimos por GX que es la resolución de la textura de la red:
thisParticle.x + = dt * thisParticle.z / GX; thisParticle.y + = dt * thisParticle.w / GX; 12thisParticle.x + = dt * thisParticle.z / GX; thisParticle.y + = dt * thisParticle.w / GX;

Ahora, comprobamos por colisiones con los obstáculos. Si chocamos, respaldamos la partícula fuera y voltear su velocidad. Esto hace que las superficies de objetos actúan especie de ‘pegajoso’ al fluido, provocando que sea estacionaria cerca de la superficie (si sólo volteado la componente normal a la superficie que conseguiríamos ‘resbaladiza’ objetos en su lugar). Puede ser que una partícula se genera dentro de un objeto por alguna razón -. En este caso, tan sólo reduce la partícula en algún otro lugar al azar en el espacio de simulación
si (Texture2D (obstacleTexture, thisParticle.xy) .R & gt; 0,5) {thisParticle.x- = dt * thisParticle.z / GX; thisParticle.y- = dt * thisParticle.w / GX; thisParticle.z * = – 1,0; thisParticle.w * = – 1,0; // Si estamos todavía en un obstáculo, nos metieron alguna manera, por lo que sólo deje caer esta partícula en otros lugares al azar si (Texture2D (obstacleTexture, thisParticle.xy) .R & gt; 0,5) = {thisParticle.x aleatorio (vec2 (39. * z2 * vUv.x + 17,0 * z4,19.3 * z3 * vUv.y + 7,6 z1 *)); thisParticle.y = aleatorio (vec2 (5,9 * z3 * vUv.x + 97,7 * z4,89.1 * z1 * vUv.y + 12,5 * z2)); } 1234567891011if (Texture2D (obstacleTexture, thisParticle.xy) .R & gt; 0,5) {thisParticle.x- = dt * thisParticle.z / GX; thisParticle.y- = dt * thisParticle.w / GX; thisParticle.z * = – 1,0 ; thisParticle.w * = – 1,0; // Si estamos todavía en un obstáculo, nos quedamos atrapados de alguna manera, por lo que sólo soltar esta partícula en otro lugar en randomif (Texture2D (obstacleTexture, thisParticle.xy) .R & gt; 0,5) {thisParticle.x = aleatorio (vec2 (39. * z2 * vUv.x + 17,0 * z4,19.3 * z3 * vUv.y + 7,6 * z1)); thisParticle.y = aleatorio (vec2 (5,9 * z3 * vUv.x + 97,7 * z4,89.1 * z1 * z2 vUv.y + 12,5 *));}

Y eso es básicamente el algoritmo SRD allí mismo. Queremos hacer un poco de contabilidad en la parte superior de esta. Envuelvo las coordenadas de partículas alrededor de, por lo que el área de simulación es periódica sobre las coordenadas de textura de la textura de la red (0 a 1), y también sujetar las velocidades de las partículas entre -4 y 4 para asegurar que las partículas no empezar a saltar sobre cuadrícula células por completo.

Conclusión

He puesto una versión del shader SRD para que pueda comprobar que funciona sin tener que reinventar la rueda. También hemos empaquetado el código de descarga para que pueda meterse con él por su cuenta.

El algoritmo de SRD es una pequeña cosa ordenada debido a su establo básicamente incondicionalmente. El usuario puede cerrar de golpe estas partículas en torno a todo lo que les gusta, y la simulación no va a explotar o estancarse debido a las altas velocidades. Dicho esto, es un poco más caro que el de Navier-Stokes porque está modelando un fluido compresible con ondas de sonido y todo, mientras que el habitual incompresible de Navier-Stokes opera en escalas de tiempo más largos. Dicho esto, si usted quiere tener efectos como nubes, nebulosas, llama, y ​​similares, estos efectos compresibles son mucho, mucho más barato conseguir a través de algo así como SRD que yendo a las ecuaciones compresibles completos de Navier-Stokes (que son muy inestables numéricamente).

Hay muchas cosas que se pueden hacer para mejorar en este sencillo sombreado. Hay una gran cantidad de ruido en los resultados – podríamos reducir ese añadiendo un poco de desenfoque de movimiento, con un promedio del flujo de fluido actual con los resultados de los últimos fotogramas por acumulación sucesiva renders en una memoria intermedia en lugar de sólo dar salida directamente a la pantalla. Otra cosa que puede suceder es que cuando la densidad está baja, las velocidades se hacen grandes y ruidoso – uno podría imaginar haciendo que la velocidad de cada partícula de más de una celda de la cuadrícula de la cuadrícula, tal vez incluso el uso de un punto de tamaño variable en función de cómo bajo la densidad local era. La otra cosa que podemos considerar es la adición de diversas fuerzas a las partículas -. Gravedad, por ejemplo

he mencionado al principio del artículo que es difícil hacer cosas como líquidos con esto, debido a las inestabilidades que surgen cuando se metan con la energía de las partículas. Tenía algunas ideas en esta dirección mientras se trabaja en el shader – no puede de manera segura lío con el aumento de la energía, pero lo que puede hacer es enfriar las partículas cuando están muy juntos, amortiguando las fluctuaciones residuales (la variable dv en el shader) cuando la densidad local es alta. Esto debería hacer que las partículas que tienden a agruparse juntos, si alguien quiere darle un tiro.

Mensajes relacionados

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *