2010-09-12 19 views
21

Estoy trabajando en un proyecto de aprendizaje personal para hacer un clon Minecraft. Está funcionando muy bien aparte de una cosa. Similar a Minecraft, mi terreno tiene muchos cubos apilados en la Y para que puedas cavar. Aunque hago el sacrificio de triturado, esto todavía significa que dibujo inútilmente 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 ordena Z a la cámara). Básicamente, desde la posición del jugador solo agrego punteros a cubos alrededor del jugador. Luego hago triturado contra estos. Yo no hago la subdivisión del árbol oct. Pensé en simplemente no renderizar las capas debajo del jugador, excepto que esto no funciona si el jugador mira hacia abajo en un hoyo. Dado esto, ¿cómo podría evitar renderizar cubos debajo de mí que no puedo ver, o también cubos que están ocultos por otros cubos?Técnicas de descarte para renderizar muchos 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); 
        } 
       } 

      } 
     } 

} 
+2

Por lo menos, cuando procesa ordenación de adelante hacia atrás, rechaza rápidamente todos los cubos inferiores. Pero un oct-tree será una buena idea. – GManNickG

+0

@GMan ¿Cómo puedo renderizar de adelante hacia atrás si mi ángulo es de 65 grados en x, 70 en y, en ese caso, cómo podría hacerlo sin hacer una verificación de distancia de la cámara al reproductor? – jmasterx

+3

Clonación de un clon. Estoy bastante contento de haber decidido no intentar hacer lo mío. –

Respuesta

13

Renderizar de adelante hacia atrás. Para hacerlo, no necesita clasificación, use octrees. Las hojas no serán cubos individuales, sino grupos más grandes de esos.

Una malla para cada hoja debe almacenarse en caché en una lista de visualización (como se sugirió Bobmitch) o incluso mejor en un búfer de vértices (más barato de actualizar). Cuando genere esta malla , no genere todos los cubos de manera bruta. En cambio, para cada cara de cubo, compruebe si tiene un vecino opaco dentro de la misma hoja, de ser así, no necesita generar esta cara. También puede unificar caras contiguas con el mismo material en un solo rectángulo largo. También puede separar la malla a seis conjuntos, un conjunto para cada dirección principal: +/- XYZ faces. Dibuja solo aquellos conjuntos de caras que pueden enfrentar la cámara.

El renderizado de adelante hacia atrás no ayuda por sí solo. Sin embargo, puede usar occlusion culling ofrecido por hardware moderno para beneficiarse de este pedido. Antes de renderizar una hoja de octree, verifique si su bbox pasa la consulta de oclusión. Si no pasa, no necesita dibujarlo en absoluto.

El enfoque alternativo a la consulta de oclusión puede ser el trazado de rayos. El trazado de rayos es good for rendering such environment. Puedes lanzar un conjunto de rayos dispersos para aproximar qué hojas son visibles y dibujar esas hojas solamente. Sin embargo, esto subestimará el conjunto de visibilidad.

2

Actualmente estoy trabajando en un clon de Minecraft en Python/Pyglet, sólo por curiosidad.

Descompongo los datos en fragmentos, como en minecraft, y luego, para cada fragmento, creo un displaylist opengl basado en la visibilidad del cubo. Luego realizo el simple 2d triturado de heces en estos trozos y llamo a cada lista de visualización dentro de una cierta distancia del jugador.

En la adición/eliminación de cubos recreo el displaylist para el fragmento.

No hay eliminación de oclusión, excepto cubos que están completamente rodeados por otros cubos.

Para escenas simples esto puede alcanzar más de 600 fps en una tarjeta gfx modesta con una distancia de visión de alrededor de 200 cubos.

+0

Una cosa, ¿cómo se hace la detección de colisión? – jmasterx

+0

Una clase de mapa en mi código python usa un diccionario de 'trozos'. Cada clave de este diccionario es una 2ª tupla de coordenadas enteras del mundo real. Cada fragmento contiene un diccionario de bloques, donde cada clave de bloque es una tupla 3d. Así que la detección de colisiones implica, para una actualización dada, ver si el jugador se está moviendo a un nuevo 'bloque', y luego buscar los diccionarios para ver si hay un bloque allí o no. – Bobmitch

1

Octtree etc. funcionará con seguridad, pero otra posible forma de resolver su problema específico podría ser almacenar para agregar un usigned char visible a cada objeto del 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 el primer bit (1) se fijará.
  • si el lado izquierdo (mirando a lo largo del eje x negativo) no tiene vecino, entonces se establecerá el 2do bit (2).
  • si el lado frontal (mirando a lo largo del eje z) no tiene vecino, entonces el 3er bit (4) se configurará
  • ... y así sucesivamente, para que tenga 1 bit para cada uno de los 6 lados de un cubo

Siempre que el jugador cave un cubo de distancia, debe actualizar el campo visible de todos sus cubos vecinos.

Entonces, pero ¿cómo puede ayudar esto? Si un cubo tiene un valor de visible de 0, entonces es fácil: nunca se mostrará el cubo. 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 decide si un cubo puede ser visible será bastante rápido y puede omitir muchos cubos ocultos.

La desventaja de eso, es que esta prueba es solo una prueba por cubo y no se puede saltear grupos completos con eso. Entonces, tal vez un octtree (para omitir regiones completas) y un campo tan visible como el que se describe aquí, para omitir la gran cantidad de cubos ocultos (dado que dichos cubos están apilados, el número de cubos ocultos será mucho más alto que el número de cubos visibles unos) dentro de esa región podría ser una buena solución.

1

Puede usar PVS(Potentially visible set) para esto, aunque generalmente es para terreno, se aplican los mismos principios, elimine lo que no se puede ver. Gamedev.net también tiene un artículo de morphing de terreno que cubre esto.

+0

PVS necesita preprocesamiento, por lo que no se puede utilizar en un mundo dinámico como se requiere aquí. – ybungalobill

0

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 utilicé con bastante éxito, requiere polígonos ordenados (por ejemplo, mediante el algoritmo del pintor) y memoria casi nula (en contraste con z-buffer).

+0

enlace muerto? c-buffer – jmasterx

+0

URL reparada, gracias. –

-1

Solo haga un seguimiento de los cubos que describen su 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 de atrás hacia adelante. También solo rinde cubos más cerca que una distancia específica al espectador. El "mundo" podría comenzar con un enorme "cubo de cubos", las capas externas de un cubo, hechas con cubos. Si alguien desentierra, tiene el control si las ubicaciones vecinas ya contienen cubos o no, si no los crea y los vincula.

Ejemplo: excavando una superficie plana: retire el cubo que se encuentra donde se excava hecho, agregue 9 cubos nuevos debajo (pero revise si esas posiciones ya están en uso, en caso de usarlas), vincule la superficie pero vincule los cubos nuevos con los cubos vecinos del que se eliminó. Para un mundo que contiene 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 que no debe dibujar todos esos cubos, comience con una distancia simple al visor para hacer una selección secundaria.

También podría ir a agrupar 8 cubos (grupos) juntos como 1 grupo, y luego continuar así hasta que en el nivel superior tenga solo un grupo de cubos (árbol de oct ya mencionado). Este árbol se puede usar para rastrear qué parte del mundo necesitas para 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 en esto estarían aquellos grupos de cubos que no se cruzan o quedan fuera de un cono trazado por rayos comenzando por el usuario y pasando justo al borde del grupo utilizado para la prueba.(Mala descripción, pero espero que obtengas alguna pista sobre qué se puede hacer para la optimización). Probablemente esto no sea necesario en las tarjetas gráficas de hoy en día.

Buena suerte con su proyecto.

14

Esto es lo que he aprendido al escribir mi propio clon:

  1. No se limite a volcar cada cubo en OpenGL, sino que también no se preocupe por hacer todo de la visibilidad de la poda a sí mismo. Como se dijo en otra respuesta, verifique las 6 caras para ver si están completamente ocluidas por un bloque adyacente. Solo renderiza caras que podrían ser visibles. Esto reduce aproximadamente tu conteo 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).
  2. OpenGL puede ver el sacrificio de fumus mucho más rápido que usted. Una vez que haya renderizado todas sus caras superficiales en una lista de visualización o VBO, simplemente envíe toda la burbuja a OpenGL. Si divide su geometría en secciones (o lo que Minecraft llama trozos) puede evitar dibujar los trozos que fácilmente determinar están detrás de la cámara.
  3. Render toda su geometría en una lista de visualización (o listas) y volver a dibujar cada vez. Este es un paso fácil de tomar si está utilizando el modo inmediato porque simplemente ajusta su código existente en glNewList/glEndList y vuelve a dibujar con glCallList. La reducción del conteo de llamadas OpenGL (por cuadro) tendrá un impacto mucho mayor que la reducción del volumen total de polígonos para renderizar.
  4. Una vez que vea cuánto tiempo más se tarda en generar las listas de visualización que dibujarlas, comenzará a pensar en cómo poner las actualizaciones en una secuencia. Aquí es donde la conversión a VBO vale la pena: el hilo se convierte en matrices antiguas simples (agregando 3 flotantes a una matriz en lugar de llamar a glVertex3f, por ejemplo) y luego la cadena GL solo tiene que cargarlas 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 en lugar de 3 llamadas a funciones.

Otras cosas que he notado:

OISCIV y listas de visualización tienen un rendimiento muy similar. Es muy posible que una implementación de OpenGL determinada use un VBO internamente para almacenar una lista de visualización. Me salté directamente por las matrices de vértices (una especie de VBO del lado del cliente), así que no estoy seguro de eso. Utilice la versión de extensión ARB de VBO en lugar de la norma GL 1.5 porque los controladores Intel solo implementan la extensión (a pesar de afirmar que admiten 1.5) y a los controladores nvidia y ATI no les importa.

Norma de atlas de textura. Si usa una textura por cara, observe cómo funcionan las atlas.

Si quiere ver mi código, encuéntreme en github.

+0

Gracias por la lista de ideas, obtienes la recompensa. –

5

Como otros, he estado jugando con un "motor" mundial de bloques usando Ogre y escribiendo algunos artículos sobre la marcha (ver Block World Articles). El enfoque básico que he estado tomando es:

  • Solo cree las caras visibles de los bloques (no las caras entre los bloques).
  • Divida el mundo en fragmentos más pequeños (solo necesarios para una actualización más rápida de los bloques individuales).
  • Combina texturas de bloque en un archivo de textura (atlas de textura).

El solo hecho de utilizar estos puede obtener muy buen rendimiento en grandes mundos de bloques simples (por ejemplo, 1024x1024x1024 en hardware decente).