Stratos: Punto de Encuentro de Desarrolladores

¡Bienvenido a Stratos!

Acceder

Foros





Renderizando con Vertex e Index buffers

Iniciado por AgeR, 07 de Diciembre de 2007, 12:45:16 AM

« anterior - próximo »

AgeR

Sigo con el tema del terreno, y voy a empezar a optimizar. Hasta el momento tenía un único vertex e index buffer (uso DirectX 9). Obviamente renderizar un terreno grande así es una locura, así que voy a implementar un quadtree y me surge una duda de novato  :oops:

¿Podría seguir teniendo un único VertexBuffer para toda la geometría del terreno, y tener por cada hoja del quadtree un IndexBuffer referenciando a los vértices del VertexBuffer? Sí, ya sé que sí, la pregunta realmente es...

¿Si mando renderizar todo el VertexBuffer y solo el IndexBuffer de una hoja por ejemplo, existe alguna penalización? En teoría no debería haberla, ya que solo se renderizan los triángulos que indica el IndexBuffer, pero tampoco estoy seguro.

Zaelsius

Quizá si habría una penalización, en términos de espacio/memoria.

Tal y como lo planteas, yo entiendo que todo el vertex buffer debería estar en VRAM a la vez, aunque sólo renderices una parte de él.

¿Tiene sentido lo que digo? (hace mucho tiempo que no programo gráficos 3d)

AgeR

El vertex buffer debería estar en la vram como dices... debería. En principio esto no debería ser problema hoy en día. Ahora mismo tengo un heightmap de 128x128, con lo que tengo 16384 vértices en el VertexBuffer: no me parece mucho para hoy en día.

La idea es que si puedo tener todos los vértices del terreno en este vertex buffer, en las hojas del quadtree bastaría con tener los índices a los vértices que utilice cada una. Como el quadtree se recorre en un determinado momento, en principio se ahorra el tiempo de "subir" cada vez un vertex buffer a la vram.

ZaelSiuS, gracias por la respuesta. Como dices, todo el vertex buffer debería estar en vram, aunque solo se renderizaran las partes que indiquen los diferentes index buffers. Yo ahí veo la ventaja esa, de trabajar siempre con un solo VB, no tener que subir los diferentes VB a la tarjeta.

Pero claro, no sé si tendrá alguna penalización más, además de lo que comentas de memoria, que no me preocupa demasiado ahora mismo (prefiero rapidez en el renderizado).

senior wapo

Usa un vertex buffer por cada tile de NxN vértices y dibújalo como un triangle strip en lugar de usar index buffers.

Mientras se transfiere y dibuja un buffer preparas los datos del siguiente y además es escalable a terrenos infinitos y compatible con distintos algoritmos de generación y de LOD.

Intutitivamente me parece más amigable para la cache de vértices también.

AgeR

Gracias por los apuntes senior wapo  :D

La verdad es que tiene bastante sentido, aunque le veo un problema. Para la capa base los triangle strip estarían bien, pero no podría usarlos para las otras capas con alpha (estoy usando vertex alpha para blendear las diferentes capas de texturas, y renderizo solo los que no tienen el alpha a 0).

Supongo que al final lo mejor será hacer pruebas y ver cómo queda la balanza sencillez/rapidez/versatilidad.

AK47

Yo tengo todo el terreno en un unico VB y renderizo cada trozo (chunk) del terreno usando los index buffer, sin problemas :)

senior wapo

Por cierto, si usas tiles de NxN siempre puedes usar un mismo index buffer para todos los bloques de misma resolución ya que todos tendrán el mismo número de triángulos/vertices y los puedes crear en el mismo orden.

Si algún bloque tiene agujeros (para entrar en subterráneos y cuevas) tendrás que crearle un Index Buffer propio para ese bloque, pero la mayoría compartirán el mismo.

Lo de saltarte los triángulos con alpha 0 no se yo si compensa, la verdad, pero ya lo decidirás tú comparando tiempos.

AK47

En efecto, si tienes muchos bloques iguales (en mi caso todos) puedes usar un único index buffer para renderizarlos, simplemente cambiado el offset y algún que otro parámetro de la llamada. Ademas, jugando con estos indices obtienes los niveles de detalle :D

senior wapo

Cita de: "AK47"En efecto, si tienes muchos bloques iguales (en mi caso todos) puedes usar un único index buffer para renderizarlos, simplemente cambiado el offset y algún que otro parámetro de la llamada. Ademas, jugando con estos indices obtienes los niveles de detalle :D

No me refería a eso, en realidad le comentaba otra cosa a Ager.

Si todas las mallas (chunks), cada una con su propio vertex buffer de (por decir algo) 64 vértices, tienen exactamente la misma cantidad de triángulos y en el mismo orden de vértices, entonces su index buffer coincide en su contenido totalmente con los de las otras mallas.

Realmente, cuando dibujas cada chunk, estás dibujando la misma malla pero con sus vértices desplazados, lo cual no afecta a los índices.
De ahí que puedas usar el mismo index buffer pequeño entero sin modificarlo ni restringirte a parte de él aunque cada vez el vertex buffer sea distinto.

Todo esto en el caso de que decida usar modo inmediato o muchos vertex buffers en lugar de uno grande.

AK47

Yo tambien me referia a eso que comentas, solo que como yo tengo un único vertex buffer para toda la geometría del terreno, tengo que ir cambiando el offset de la llamada de DrawIndexedPrimitive() para que renderice los vertices adecuados (además de poder usar indices de 16 bits en vez de 32). Como tu partes de que tienes un vertex buffer por cada chunk del terreno, el offset siempre sera 0 en la llamada de dibujado.

Por cierto, lo de modo inmediato es de OpenGL?

senior wapo

Cita de: "AK47"Por cierto, lo de modo inmediato es de OpenGL?

No, también hay modo inmediato en Direct3D.

Cuando proporcionas los datos (vértices, índices, píxeles) directamente como parámetros o en un array de la aplicación usas modo inmediato.

Cuando llamas a glBegin/glVertex en OpenGL o a device->Begin en Direct3D6 (DX6 es la única versión de DX con equivalente al glBegin+glVertex de OpenGL) usas modo inmediato.

Cuando llamas a device->DrawPrimitiveUP en DX5+ usas modo inmediato, igual que cuando llamas en OpenGL a glDrawElements y el buffer que has asignado es un array y no un VBO.

Cuando asignas una textura en OpenGL 1.0 (sin "proxy"/glGenTextureX) usas modo inmediato.

Cuando usas buffers y objetos del API, no. Tienes que pedir acceso al contenido del objeto, y ejecutar una función del API que lea/escriba el contenido y luego a la hora de dibujar te limitas a identificar el objeto a usar.

En modo inmediato te ahorras todos esos pasos proporcionando los datos directamente (menos eficiente pero más cómodo para aprender).

A la mayoría de la gente lo que le viene a la cabeza al leerlo es glVertex, eso sí.

AgeR

Cita de: "senior wapo"
Si todas las mallas (chunks), cada una con su propio vertex buffer de (por decir algo) 64 vértices, tienen exactamente la misma cantidad de triángulos y en el mismo orden de vértices, entonces su index buffer coincide en su contenido totalmente con los de las otras mallas.

Realmente, cuando dibujas cada chunk, estás dibujando la misma malla pero con sus vértices desplazados, lo cual no afecta a los índices.
De ahí que puedas usar el mismo index buffer pequeño entero sin modificarlo ni restringirte a parte de él aunque cada vez el vertex buffer sea distinto.

Todo esto en el caso de que decida usar modo inmediato o muchos vertex buffers en lugar de uno grande.


Creo que no he explicado del todo bien lo que pretendo hacer. Tengo un heightmap de donde leo los datos de la capa base. Esto para la base del terreno lo hago sin problemas. Después tengo otros archivos que almacenan un valor de alpha para cada vértice de las diferentes capas (layers) de texturas que se renderizarán por encima de la capa base. El vertex alpha lo uso para simular los blends entre las texturas.

Entonces, solo me interesa almacenar por cada capa los triángulos cuyos vértices no tengan alpha 0. Si su alpha es 0, me quedaré solo con el triángulo de la capa más alta. De este modo, por ejemplo, me ahorro renderizar siempre la capa base, aun cuando esté oculta completamente por otras capas.

Entonces, mi idea era tener un vertex buffer por cada capa del terreno, estrictamente con los vértices necesarios. Después, al generar el quadtree, solo necesitamos crear un index buffer por cada hoja y por cada capa.

Para renderizar, se recorre el quadtree N veces (donde N es el número de capas).

El terreno no tendrá cuevas, ni LOD ni similares, ya que para lo que necesito realmente no hace falta (no voy a usar terrenos muy grandes).

A ver qué os parece la idea, o si pensáis que hay alguna solución mejor, tanto por el uso de index buffers sobre un unico vertex buffer, como por el renderizado del terreno usando capas con alpha.  :D

Miki

Lamento no haber escrito antes.

Hace no mucho terminé una implementación de octree para mi motor. La verdad es que le eché MUCHAS horas en su momento, y aunque puede parecer un problema trivial tiene bastantes trampas.

Por un lado habéis comentado lo del tamaño/cantidad de VB's. nVidia y ATI no recomiendan VB's demasiado grandes, al igual que tampoco recomiendan texturas muy grandes XD. Tampoco recomiendan muchos y pequeños VB's. En esto, como en todo, hay que llegar a un término medio. No hay que comerse mucho la cabeza con esto, porque luego cada t.gráfica (y esto cambia cada 6 meses XD) tiene una cantidad distinta de vram, de caché, y de streams.

Lo que respecta en sí al sistema de render, la verdad es que depende mucho de lo que quieras hacer. En mi sistema propuse el peor de los casos. Es decir, aquel en el cual puedes tener X nº de triángulos e Y nº de meteriales, en los que cada nodo puede contener una cantidad diferente de triangulos y materiales. Es decir, no es el típico terreno de NxN con 4 layers.

Bien, lo primero que hay que hacer al computar el quadtree/octree (sobretodo en DirectX) es reconstruir un nuevo VB, y generar un IB para cada nodo del árbol.
¿Por qué lo de reconstruir un VB? Normalmente el quadtree/octree se genera a partir de una malla normal, osease, una que no tiene quadtree/octree. En esta malla normal el render se hace a piñón, pasando por todos sus materiales, en principio una draw call por material. Pero cuando hablamos de render basado en nodos esto cambia.
Por ejemplo, imagina tu terreno base. Lo tienes todo en un VB, y los vértices dentro de este VB están dispuestos de forma lineal, como si conformasen filas para rellenar el terreno (luego el IB forma los triangulos). Bien, ahora imagina la cámara mirando la esquina que corresponde con el primer vértice del terreno, abarcando además otras
2 esquinas, viendo más o menos un 50% del terreno. Al pintar, efectivamente gracias al uso de nodos y de un IB, pintarás más o menos un 50% de triángulos del terreno, pero a la GPU le vas a mandar casi el 100% de los vértices. Porque aunque el vértice X, que está en medio de otros que SI estás usando, no figure en el IB de la draw call,
al hacer los preparativos del render Direct3D tiene que meterlo en el stream de datos. Solo este pequeño detalle hace que un
quadtree/octree sea poco más eficiente que pintar todo el terreno y esperar a que la GPU y Direct3D te hagan el clipping. Esto, aunque parezca mentira, es uno de los performance penalties más chungos de Direct3D, de ahí que los métodos Draw.....() tengan los parámetros para indicar el baseVertexIndex, minIndex, y vertexCount.

En cambio, si reconstruyes como te decía, un VB en el que imaginariamente esté dividido en trozos, cada uno perteneciente a uno de los nodos del árbol, te aseguras que al pintar cada nodo solo estás subiendo los vértices justos a la GPU. Lógicamente esto tiene un problema, y es que se acaban repitiendo los vértices de los triangulos
que están compartidos por 2 o más nodos, y al hacer un render masivo de nodos, el rendimiento es inferior al que sería si pintaras la malla a pelo.

Luego, aunque como he dicho, cada nodo tiene asociado un IB, no hace falta que crees tropecientos IB's en vram y los vayas asociando al device mientras pintas. Puedes dejar todos esos IB's como parte de la estructura del nodo (en RAM obviamente) e ir copiando el data de los IB's que necesites a un IB maestro que sí está alojado en vram.
Esto lo harías en tiempo de < nodos vs frustum >.

Puedes también organizar todo en un VB y un IB (actualizando ambos en cada frame) de forma que solo tengas que hacer X draw calls por frame, donde X es igual al número de materiales vistos. Si en tu caso solo usas un material para todo el terreno, pues lo harías todo en una sola llamada, como el megatexture del tito John :-)

Recuerda que hacer muchas draw calls es caro, medio megaherz aprox por llamada.

Finalmente, yo no recomiendo particionar demasiado el árbol (2000 caras por nodo está bien). Si particionas más, no deja de ser una buena idea desabilitar el clipping de Direct3D, aunque con algunas t.gráficas da problemas, sobretodo integradas de portátil.

Bueno, espero, aunque tarde, que te haya servido de algo.

Saludos






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.