Stratos: Punto de Encuentro de Desarrolladores

¡Bienvenido a Stratos!

Acceder

Foros





Efecto "ondas" o "burbuja" en Direct3d

Iniciado por Loover, 19 de Marzo de 2008, 10:54:13 AM

« anterior - próximo »

Loover

Pero yo creo que lo que ganaría por usar un solo VertexBuffer, lo perdería al tener que, en cada frame, meter los vértices de todos los sprites en dicho buffer. Que algunos sprites, como ya he dicho, pueden estar taselados en más de dos triángulos.

Aparte, que para poder aplicar las transformaciones, tendría que seguir usando distintos DrawPrimitive, intercalados con los métodos de transformación. Vamos, que no podría hacer un solo "DrawPrimitive", sino varios, intercalados con las transformaciones. ¿No es así?

Y eso, lo veo igual que tener distintos vertexbuffer y hacer DrawPrimitive de cada uno de ellos. ¿No?
IndieLib Libreria 2.5d utilizando aceleración por hardware para la programación de juegos 2d.
Indie Rover The monkeys are reading!

[EX3]

Cita de: "AK47"Y repito, los vertex shaders por CPU van mu bien :D
Tomo nota :) El asunto que aun no me habeis aclarado, para aplicar un efecto tipo ondas, bien sea con VertexShader o a pelo, se requiere una cantidad "minima" de vertices para hacer el efecto minimamente decente o hay algun mecanimso "esoterico" que con dos triangulos lo permita? :)

Salu2...
José Miguel Sánchez Fernández
.NET Developer | Game Programmer | Unity Developer

Blog | Game Portfolio | LinkedIn | Twitter | Itch.io | Gamejolt

AK47

Con dos triangulos, [EX3], la forma esoterica seria usar displacement mapping o alguna movida de estas. Resumiendo: pixel shaders y no cualquier shader model. Para hacer lo de la ondulacion hay que meter mas vertices, no hay mas ;)

En cuanto a lo de tener que llamar a DrawPrimitive por cada sprites es cierto si... Si no usas la tecnica de "shader instancing" con vertex shaders, que puedes usarlos porque en CPU van bien (TM) XD

Ahora que menciono el shader instancing, me doy cuenta que con esta tecnica no necesitas hacer lock/unlock en absoluto, como dijo antes Prompt.

La idea del shader instancing es la siguiente: tienes el quad (formado por 2 triangulos) unitario que es el que usas para dibujar sprites, es decir, el ancho y alto del quad miden 1 unidad. Yo lo pondria en el plano ZY con la X = 0, con el centro del quad en (0, 0, 0).

Bien, tenemos este quad, pues en el vertex buffer al cargar el juego lo copiamos N veces. Si, en ese vertex buffer hay un porron de quads, todos iguales, ocupando el mismo espacio.

Ahora biene lo bueno: por cada sprite, calculas su matriz de transformacion, que contiene toda la informacion necesaria (translacion, rotacion y escalado). Lo que se hace es agrupar estas matrices en un array y pasarselo al vertex shader, que se lo aplica a cada quad. Asi puedes posicionar, rotar o escalar los sprites como quieras, con una unica llamada. No es del todo cierto, ya que estas limitado por el numero de constantes del vertex shader (96 en 1.1).

Como sabe el vertex shader que matriz coger para aplicarselo al vertice? Muy facil: cada vertice lleva un indice, indicando que matriz debe usarse para transformarlo. Por lo tanto, los 4 vertices de un quad compartiran este indice (o no, quizas puedas hacer efectorros).

Se puede sofisticar esto para añadir matrices de transformacion para las coordenadas de texturas y demas cosas, pero creo que ya pillas la idea (espero, no se si me he explicado mu bien XD).

Ala, a ver si le vale a alguien :)

[EX3]

Buff, creo que me retiro, se me queda grande el asunto de los vertexBuffer y vertexShaders y de los pixelShaders ni hablemos :lol: Creo que me conformare con usar alphas para simular efectos "discretos" :P

Salu2...
José Miguel Sánchez Fernández
.NET Developer | Game Programmer | Unity Developer

Blog | Game Portfolio | LinkedIn | Twitter | Itch.io | Gamejolt

AK47

No es dificil comprenderlo. La idea clave es que en el vertex shader, se procesa un vertice cada vez, y que no puedes acceder a ningun otro vertice de la malla. Solo tienes los datos del vertice y los datos que le has pasado (como se chorizo de matrices)  8)

Loover

Creo que de momento voy a dejar los shaders de lado. Porque seguro surgirán diferentes complicaciones con otros aspectos del engine, o limitaciones como la que has citado. Aparte que no los veo necesarios, porque no creo que un juego vaya a necesitar más sprites en pantalla que los que usa el Harvest, que es una salvajada, y les va de perlas con el DrawPrimitiveUp (pero lo que si hacen es agrupar por textura).

En cuanto a lo de un solo buffer, tiene su gracia si vas a hacer un solo DrawPrimitive, pero si antes de cada sprite tengo que hacer las transformaciones, al final voy a hacer tantos DrawPrimitive como si hubiera distintos buffers, por lo que no gano nada. Al revés, pierdo en complejidad y en el tiempo en crear ese buffer gigante.

Otra forma de ganar velocidad que me han comentado es agrupar los sprites por textura. Pero eso no me libra de las transformaciones...

[Ex3], lo de los efectos tipo ondulación y tal, consiste en esto:
- Cargas un sprite y lo divides en bloques (tantos como quieras). Vamos, un grid.
- Permites, mediante una función, mover cada vértice del grid.

Ahora, recorriendo dichos vértices, puedes hacer todo tipo de animaciones. Desde una cortina (uniendo dichos vértices a un engine de física), hasta un efecto de ondas (moviéndo vértices usando alguna función sin, etc).

Desde la primera versión de LooverLib, tengo el método que carga las imágenes trocéandolas en cuantos trozos quiero. Ahora lo interesante es dar unos métodos para mover los vértices de dicho grid. Y luego, si acaso, hacer una clase de efectos, que moviendo dichos vértices, cree distintos efectos animados.
IndieLib Libreria 2.5d utilizando aceleración por hardware para la programación de juegos 2d.
Indie Rover The monkeys are reading!

[EX3]

José Miguel Sánchez Fernández
.NET Developer | Game Programmer | Unity Developer

Blog | Game Portfolio | LinkedIn | Twitter | Itch.io | Gamejolt

[EX3]

Cita de: "AK47"No es dificil comprenderlo.
No, si lo dificil es adaptarlo al codigo del render de la dx_lib32 :lol: El buffer de profundidad que implemente y la forma de trabajar con el ha hecho que me limite mucho el implementar cosas complejas como vertexBuffers o incluso PixelShaders (que solo podria aplicar shaders a texturas independientes y no en conjunto).

Cita de: "Loover"[Ex3], lo de los efectos tipo ondulación y tal, consiste en esto:
- Cargas un sprite y lo divides en bloques (tantos como quieras). Vamos, un grid.
- Permites, mediante una función, mover cada vértice del grid.
Esto puedo hacerlo sin modificar nada del codigo actual ya que puedo dibujar una misma textura pero seleccionando regiones o frames, lo que me da como resultado varios sprites en pantalla, lo que es lo mismo, varios quads. El unico engorro seria el tener calcular la rotacion o transformacion correcta de cada frame del grupo de sprites (he ahi lo malo de tenerlos independientes) y que tendria que calcular a pelo sin vertexShaders las alteraciones de los sprites, en el engine del juego (hablo de implementar todo esto fuera de la dx_lib32).

Salu2...
José Miguel Sánchez Fernández
.NET Developer | Game Programmer | Unity Developer

Blog | Game Portfolio | LinkedIn | Twitter | Itch.io | Gamejolt

Prompt

Cita de: "Loover"Bueno, antes de renderizar cada sprite, aplico una serie de transformaciones para calcular su rotación, posición, escalado, etc... que son independientes para cada sprite. Y también una transformación del color (puedes hacer entintados, fades, transparencias, etc).

Por lo tanto, si aplico una trasnformación antes de cada sprite, no puedo meterlos dentro de un mismo vertexbuffer. ¿O sí se puede?

Umm, pensandolo bien, debe poderse aplicar la transformación y luego hacer el SetTexture y el DrawPrimitiveUp... pero cada vez que creara un nuevo sprite tendría que agrandar el VertexBuffer "gigante" con todos los sprites y cada ver que borrara un sprite se quedaría un hueco y tendría que copiar zonas del array y ponerlas más atrás... ¿ganaría algo con todo ese tinglado en vez de tener un VertexBuffer por sprite como tengo ahora?

¿Cómo lo haces tú [Ex3]?

Es mucho mejor:
- Destruir un buffer.
- Crear uno nuevo.
- Agrupar todo.
- Evidarlo a la tarjeta.

Que ir mandandolo poco a poco. El coste de esto es minimo y la agrupación antes de mandarla por el BUS a la tarjeta grafica la hace la CPU.

El rendimiento es buenisimo. Intenta que esto no ocurra mucho, reservando bloques de memoria lo suficientemente grandes, es decir, si en tu vertexBuffer borras elementos, agrupas y resulta que sobra un buen cacho no te preocupes. Redimensionalo solo si necesitas más y hazlo pensando en que no se vuelva hacer al menos en unos pocos de frames o segundos.

Normalmente todo lo precargas antes de una escena, y esto debería ocurrir muy poco.

PD: no se si lo he explicado muy bien, me voy a casita!! Esto se explica todo en el "instancing" para D3D. En OpenGL se llama pseudo instancing porque esta gestion según los datos que maneja la gente de nVidia dicen que glDrawElements ( creo que era ) el == a DrawPrimitives, va muchisimo mejor que en DX y la perdida es mínima.

No obstante... estoy seguro que haciendo lo mismo va mejor. Yo aun no lo tengo hecho, pero tengo pendiente crear un único VBO para todo.

No me extiendo más... adioooooooooo !! A ver si luego en casa miro los demás post!


Loover

Pero no puedo agruparlo y mandarlo todo si tengo que hacer transformaciones por sprite. Antes de cada DrawPrimitive, tengo que hacer una transformación. Para posicionar el sprite en coordenadas de mundo (rotaciones, escalados, traslaciones). Y también efectos de entintado, fade, etc.

Vamos, no puedo "mandarlo todo" y ale.

Tengo que:

- Aplicar transformaciones del sprite en cuestión
- Hacer un DrawPrimitive del sprite

¿O hay alguna forma de hacer eso en un solo buffer?

¿Cómo lo tienes tu [Ex3]?
IndieLib Libreria 2.5d utilizando aceleración por hardware para la programación de juegos 2d.
Indie Rover The monkeys are reading!

AK47

Puedes agrupar los sprites por texturas y calcular sus matrices. Luego, por cada conjunto de sprites que comparten textura, coges sus matrices y los envias en trozos que puedan entrar en las constantes del vertex shader. De esta forma en mi juego puedo dibujar 90 arboles de golpe (con sus limitaciones, claro).

Aun asi igual no merece la pena, yo agruparia los sprites en texturas y los dibujaria de golpe todos a la vez (N llamadas de DrawprimitiveUp, por ejemplo, donde N = numero texturas diferentes en pantalla). No hace falta enviar matrices a la tarjeta, los vertices que le envias ya estan transformados por la CPU. Si, asi no se aprovecha la tarjeta, pero creo que es mas rapido en vez de enviarlos uno por uno.

Prompt

Lo puedes hacer por grupo Loover machote.

Ordenas por material.
Lo que tenga transparencia.
etc...

Aplicas las transformaciones a todos los vertices del "grupo" y subes!

Busca en la ayuda de DX o en nVidia sobre el Instancing, seguro que al leerlo un poco lo ves más claro.

Loover

CitarAplicas las transformaciones a todos los vertices del "grupo" y subes!

Pero es que no hay grupos que coincidan... cada sprite tiene posiciones, rotaciones y escalados distintos. Aparte de fades, entintados y niveles de transparencia también distintos...

Citar
No hace falta enviar matrices a la tarjeta, los vertices que le envias ya estan transformados por la CPU. Si, asi no se aprovecha la tarjeta, pero creo que es mas rapido en vez de enviarlos uno por uno.

¿Me recomiendas que aplique las transformaciones y genere un vertexbuffer con todos los vértices transformados y entonces lo dibuje agrupando por textura? ¿En vez de hacer el típico "Set" de la matriz de mundo con las rotaciones, escalados, etc del sprite y entonces dibujarlo (y que se encargue de las transformaciones la tarjeta)? Pues no lo veo claro, porque hago pruebas con burradas de sprites en pantalla y va de perlas.

Sin embargo, el tener que generar dicho buffer, y en cada frame copiar toda la info de los sprites (pq puede que alguno no haya que dibujarlo porque el usuario lo haya desactivado o borrado) me da repelús (no solo por el memcopy al vertexbuffer) sino aparte por que tendría que usar la CPU para transformar los vértices de cada sprite.

Aunque por otro lado no he hecho pruebas, por lo que quizás si que ganaría algunos fps en situaciones con más de 1000 sprites en pantalla. No lo sé...

[Ex3], pronunciate, ¿como lo haces tú en la dx_lib32?
IndieLib Libreria 2.5d utilizando aceleración por hardware para la programación de juegos 2d.
Indie Rover The monkeys are reading!

[EX3]

Cita de: "Loover"[Ex3], pronunciate, ¿como lo haces tú en la dx_lib32?
Invocado me has :P

Me supongo que ahora preguntaras por lo de las transformaciones. Recapitualndo un poco para que se entienda esto, mi render funciona en un modo "virtual" por llamarlo asi, esto es, cada llamada que hago a mis funciones graficas de dibujo solo generan parametros de salida que voy agrupando en colecciones de arrays que almacenarian las llamadas reales, un orden que se define como coordenada Z. En el caso de la funcion de dibujo de sprites, todo el grueso de calculos y transformaciones las hago en la llamada virtual, DRAW_MapEx(). En esta funcion calculo la posicion de los vertices, la region de la textura si se ha definido, la configuracion UV de la textura para aplicar el espejado indicado y la transformacion por matrices para escalar y/o rotar los vertices. Despues simplemente agrego la llamada a la coleccion en el array correspondiente a la Z indicada. El render como tal seria un metodo interno que ejecutaria por orden los 17 niveles de la coleccion o "buffer de profundidad" (de -8 el mas lejano a la camara, pasando por 0 que es el nivel intermedio hasta 8 el mas cercano a la camara) llamada tras llamada, en el caso de los sprites/tiles o cualquier grafico textura lo que hace en este metodo seria configurar los renderstates y rendertextures que definen dos parametros de la llamada a DRAW_MapEx(), el modo de alphablending y el filtro de textura (solo modifica renderstates y rendertextures si los valores no son los mismos que los ya activados), por ultimo activo la textura a dibujar y llamo a DrawPrimitiveUP().

Si no ha quedado muy claro, por que reconozco que es un poco lioso de explicar, aqui un pequeño croquis:
zBuffer o tambien RenderBuffer:

zBuffer[-8][2] {e, f}
zBuffer[-3][1] {c}
zBuffer[0][4] {a,b,d,g}
zBuffer[6][1] {h}

Las letras definen el orden en que fueron agregados al buffer pero no su orden de dibujado, que lo define el array segun su posicion en el zBuffer, lo que en este caso seria dibujar en el siguiente orden: e, f, c, a, b, d, g, h.

// La funcion de dibujo de sprites con opciones de transformacion:
DRAW_MapEx()
{
   Defino el quad inicial con las dimensiones indicadas
   Asigno los colores a los vertices y el specular si se indico
   Configuro el UV de la textura en los vertices del quad segun el espejado indicado
   Aplico transformaciones con matrices para escalar y rotar si fuese necesario
   Agrego lista de parametros calculados al nivel correcto definido por la coordenada Z al zBuffer
}

Frame() // Seria el Flip() o Render() de dx_lib32, es la llamada que ejecutaria el envio de datos a la grafica:
{
   Control de recuperacion de device
   Limpiamos escena (D3DDevice.Clear())
   ExecuteRenderBuffer() <-- El render de llamadas
   D3DDevice.Present()
   Calculo de FPS
}

// El render por excelencia:
ExecuteRenderBuffer()
{
   D3DDevice.BeginScene()

   Recorremos cada nivel del zBuffer (desde -8 hasta 8)
   {
       Leemos cada elemento del array (desde 0 hasta n)
       {
           Configuramos renderstates (modo de alphablending o efecto de color (invertir colores por ejemplo))
           Configuramos rendertextures (filtro textura)

           swich (tipo operacion)
           {
               texturas y/o primitivas:
               {
                   Llamamos a DrawPrimitiveUP()
               }
               textos:
               {
                   ...
               }
           }
       }
   }
   D3DDevice.EndSecene()
   Vaciamos el zBuffer
}

He de decir que este sistema funciona mejor de lo que me esperaba en su momento, teniendo en cuenta que son llamadas totalmente aisladas (de ahi que me sea problematico hacer agrupaciones con vertexBuffers y similares) y que es codigo compilado en Visual Basic 6.0 (no quiero saber como iria esto en C++).

De implementar yo efectos, si son filtros para invertir colores o aplicar combinaciones de blendings para el alpha, esto seria en el espacio donde configuro los renderstates en ExecuteRenderBuffer(), mientras que si se tratase de un efecto a nivel de vertices, como los de simulacion de perspectivas 3D de DRAW_AdvMap(), esto se haria en la propia funcion de llamada "virtual", DRAW_loquesea().

No sera la implementacion mas optima pero si resulta comoda para organizar llamadas por grupos sin importar el orden de ejecucion del codigo :) (realmente una mania adquirida en mis años de programacion con Div Games Studio, dichosa coordenada Z de los procesos xD)

Salu2...
José Miguel Sánchez Fernández
.NET Developer | Game Programmer | Unity Developer

Blog | Game Portfolio | LinkedIn | Twitter | Itch.io | Gamejolt






Stratos es un servicio gratuito, cuyos costes se cubren en parte con la publicidad.
Por favor, desactiva el bloqueador de anuncios en esta web para ayudar a que siga adelante.
Muchísimas gracias.