c++ - tecnicas - Técnicas de selección para hacer muchos cubos.
seleccion por competencias pdf (8)
Actualmente estoy trabajando en un clon de Minecraft en python / pyglet, solo por curiosidad.
Divido los datos en partes, como en Minecraft, y luego, para cada parte, creo una lista de visualización opengl basada en la visibilidad del cubo. Luego, realizo una selección simple en 2D de estos trozos y llamo a cada lista de visualización a una cierta distancia del jugador.
En la adición / eliminación de cubos, vuelvo a crear la lista de visualización para el fragmento.
No hay sacrificio de oclusión, excepto los cubos que están completamente rodeados por otros cubos.
Para escenas simples, esto puede alcanzar más de 600 fps en una modesta tarjeta gfx con una distancia de visualización de alrededor de 200 cubos.
Estoy trabajando en un proyecto de aprendizaje personal para hacer un clon de Minecraft . Está funcionando muy bien aparte de una cosa. Al igual que Minecraft, mi terreno tiene muchos cubos apilados en la Y para que puedas excavar. A pesar de que realizo el sacrificio de frustos, esto todavía significa que inútilmente dibujo todas las capas de cubos debajo de mí. Los cubos son X, Y y Z ordenados (aunque solo en 1 dirección, por lo que técnicamente no se ordenan Z a la cámara). Básicamente, desde la posición del jugador, solo agrego punteros a los cubos que rodean al jugador. A continuación, hago un frustum de sacrificio contra estos. No hago subdivisión del árbol de oct. Pensé en simplemente no renderizar las capas debajo del jugador, excepto que esto no funciona si el jugador mira hacia abajo en un agujero. Dado esto, ¿cómo podría evitar representar cubos debajo de mí que no puedo ver, o también los cubos que están ocultos por otros cubos?
Gracias
void CCubeGame::SetPlayerPosition()
{
PlayerPosition.x = Camera.x / 3;
PlayerPosition.y = ((Camera.y - 2.9) / 3) - 1;
PlayerPosition.z = Camera.z / 3;
}
void CCubeGame::SetCollids()
{
SetPlayerPosition();
int xamount = 70;
int zamount = 70;
int yamount = 17;
int xamountd = xamount * 2;
int zamountd = zamount * 2;
int yamountd = yamount * 2;
PlayerPosition.x -= xamount;
PlayerPosition.y -= yamount;
PlayerPosition.z -= zamount;
collids.clear();
CBox* tmp;
for(int i = 0; i < xamountd; ++i)
{
for(int j = yamountd; j > 0; --j)
{
for(int k = zamountd; k > 0; --k)
{
tmp = GetCube(PlayerPosition.x + i, PlayerPosition.y + j, PlayerPosition.z + k);
if(tmp != 0)
{
if(frustum.sphereInFrustum(tmp->center,25) != NULL)
{
collids.push_back(tmp);
}
}
}
}
}
Como otros, he estado jugando con un "motor" de Block World usando Ogre y escribiendo algunos artículos a medida que avanzo (ver Artículos de Block World ). El enfoque básico que he estado tomando es:
- Solo crea las caras visibles de bloques (no caras entre bloques).
- Divida el mundo en partes más pequeñas (solo es necesario para una actualización más rápida de bloques individuales).
- Combina texturas de bloque en un archivo de textura (atlas de textura).
El solo uso de estos puede obtener un muy buen rendimiento en grandes mundos de bloques simples (por ejemplo, 1024x1024x1024 en hardware decente).
En caso de que solo el dibujo sea el problema (y no la rotación de los vértices no utilizados), ¿no podría ser algo como c-buffer útil? Lo usé con bastante éxito, requiere polígonos ordenados (por ejemplo, por el algoritmo del pintor) y memoria casi cero (en contraste con z-buffer).
Esto es lo que aprendí mientras escribía mi propio clon:
- No solo descargue todos los cubos en OpenGL, sino que tampoco se preocupe por hacer toda la poda de visibilidad. Como se indicó en otra respuesta, verifique las 6 caras para ver si están completamente ocultas por un bloque adyacente. Solo renderizar caras que puedan ser visibles. Esto reduce aproximadamente su recuento de caras de un término cúbico (un volumen de cubos n * n * n) a un término cuadrado (superficie de solo alrededor de n * n).
- OpenGL puede hacer ver frustrum culling mucho más rápido que tú. Una vez que haya convertido todas sus caras de superficie en una lista de visualización o VBO, simplemente envíe el blob completo a OpenGL. Si rompe su geometría en segmentos (o lo que Minecraft llama trozos) puede evitar dibujar los trozos que fácilmente puede determinar que están detrás de la cámara.
- Represente toda su geometría en una lista de visualización (o listas) y vuelva a dibujarla cada vez. Este es un paso fácil de tomar si está usando el modo inmediato porque simplemente ajusta su código existente en glNewList / glEndList y vuelve a dibujar con glCallList. La reducción del recuento de llamadas OpenGL (por cuadro) tendrá un impacto mucho mayor que la reducción del volumen total de polígonos a representar.
- Una vez que vea cuánto tiempo se tarda en generar las listas de visualización que en dibujarlas, comenzará a pensar en cómo colocar las actualizaciones en un hilo. Aquí es donde la conversión a VBO se amortiza: el subproceso se convierte en matrices antiguas (agregando 3 flotadores a una matriz en lugar de llamar a glVertex3f, por ejemplo) y luego el subproceso GL solo tiene que cargarlos en la tarjeta con glBufferSubData. Usted gana dos veces: el código puede ejecutarse en un hilo y puede "dibujar" un punto con 3 escrituras de matriz en lugar de 3 llamadas de función.
Otras cosas que he notado:
Los VBO y las listas de visualización tienen un rendimiento muy similar. Es bastante posible que una implementación OpenGL dada use un VBO internamente para almacenar una lista de visualización. Salté a la derecha por matrices de vértices (una especie de VBO del lado del cliente), así que no estoy seguro de eso. Use la versión de VB de la extensión ARB en lugar del estándar GL 1.5 porque los controladores de Intel solo implementan la extensión (a pesar de que dicen ser compatibles con 1.5) y a los controladores de nvidia y ATI no les importa.
Regla de los atlas de textura. Si está usando una textura por cara, mire cómo funcionan los atlas.
Si quieres ver mi código, búscame en github.
Puede usar PVS (conjunto potencialmente visible) para esto, aunque en general para el terreno, se aplican los mismos principios, se elimina lo que no se puede ver. Gamedev.net tiene un artículo de transformación de terreno que cubre esto también.
Render de frente hacia atrás. Para hacerlo, no necesita ordenación, use octetos. Las hojas no serán cubos individuales, sino grupos más grandes de ellos.
Una malla para cada una de esas hojas debe almacenarse en caché en una lista de visualización (como sugirió Bobmitch) o incluso mejor en un búfer de vértice (más barato de actualizar). Cuando genera esta malla , no genere todos los cubos de forma bruta. En su lugar, para cada cara de cubo, compruebe si tiene un vecino opaco dentro de la misma hoja, si es así, no necesita generar esta cara en absoluto. También puede unificar caras vecinas con el mismo material en un solo rectángulo largo. También puede separar la malla en seis conjuntos, uno para cada dirección principal: +/- caras XYZ. Dibuje solo los conjuntos de caras que pueden enfrentar la cámara.
Hacer frente a la espalda no ayuda por sí solo. Sin embargo, puede utilizar el sacrificio de oclusión ofrecido por el hardware moderno para beneficiarse de este pedido. Antes de representar una hoja de octárbol, verifique si su bbox pasa la consulta de oclusión. Si no pasa no necesitas dibujarlo en absoluto.
El enfoque alternativo a la consulta de oclusión puede ser el trazado de rayos. El trazado de rayos es bueno para representar este entorno . Puede emitir un conjunto de rayos dispersos para aproximar qué hojas son visibles y dibujar esas hojas solamente. Sin embargo, esto subestimará el conjunto de visibilidad.
Seguro que Octtree, etc. funcionará, pero otra forma posible de resolver su problema específico podría ser almacenar para agregar un usigned char visible
para cada objeto de cubo. El valor del campo visible
se calcula de la siguiente manera:
- Si el lado derecho (mirando a lo largo del eje x) no tiene vecino, entonces se establecerá el primer bit (1).
- Si el lado izquierdo (mirando a lo largo del eje x negativo) no tiene vecino, entonces se establecerá el segundo bit (2).
- si el lado frontal (mirando a lo largo del eje z) no tiene vecino, entonces se establecerá el tercer bit (4)
- ... y así sucesivamente, para que tenga 1 bit para cada uno de los 6 lados de un cubo
Siempre que el jugador extraiga un cubo, debes actualizar el campo visible
de todos los cubos vecinos.
Entonces, ¿cómo ayudará esto? Si un cubo tiene un valor visible
de 0, entonces es fácil: el cubo nunca se mostrará. Pero digamos que el cubo tiene un valor visible
de 1. Entonces el cubo (podría) solo ser visible, si Xplayer < Xcube
. Los otros lados funcionan de manera similar, y creo que una función que decida si un cubo puede ser visible será bastante rápida y puede saltar muchos cubos ocultos.
La desventaja de eso, es que esta prueba es solo una prueba por cubo y no puede omitir grupos completos con eso. Entonces, tal vez un octtree (para omitir regiones completas) y un campo visible como el que se describe aquí, para omitir la gran cantidad de cubos ocultos (ya que estos cubos están apilados, la cantidad de cubos ocultos será mucho mayor que la cantidad de los visibles). unos) dentro de esa región podría ser una buena solución.
Solo haz un seguimiento de los cubos que describen tu superficie. Puede hacerlo mediante una estructura de datos simple donde cada cubo mantiene referencias a sus vecinos. En cualquier tarjeta gráfica moderna no debería ser un problema empujar todos esos triángulos. Render desde atrás hacia delante. También renderice los cubos más cerca de una distancia específica para el espectador. El "mundo" podría comenzar con un enorme "cubo de cubos", las conchas exteriores de un cubo, hechas por cubos. Si alguien cava hacia abajo, tienes que verificar si las ubicaciones vecinas ya contienen cubos o no, si no creas esos cubos y los vinculas.
Ejemplo: excavar una superficie plana: quitar el cubo que está ubicado donde se realiza la excavación, agregar 9 cubos nuevos debajo (pero verificar si esas posiciones ya están en uso, en caso de usarlas), vincular la superficie pero vincular los nuevos cubos a Los cubos vecinos de la quitada.
Entonces: para un mundo que contenga 1000x1000x1000 cubos obtendría: 1000 * 1000 * 2 + 998 * 999 * 4 = 5988008 en lugar de 1000 * 1000 * 1000 = 1000000000, o un factor 167 menos cantidad de cubos.
Por supuesto, no debe dibujar todos esos cubos, comience con una distancia simple para que el espectador realice una selección secundaria.
También puede agrupar 8 cubos (grupos) juntos como 1 grupo, y luego continuar así hasta que en el nivel superior solo tenga un grupo de cubos (oct-tree ya mencionado). Este árbol se puede usar para trazar con rayos qué parte del mundo necesitas dibujar y no dibujar. Si un grupo contiene referencias a otros 8 grupos de cubos, no se muestran los que están detrás. Detrás de esto, estarían aquellos grupos de cubos que no se intersecan o se extienden fuera de un cono trazado de rayos que comienza desde el usuario y pasa justo por el borde del grupo utilizado para la prueba. (Mala descripción, pero espero que recibas algunos consejos sobre lo que se podría hacer para la optimización). Esto probablemente no sería necesario en las tarjetas gráficas de hoy de todos modos.
Buena suerte con tu proyecto.