Representación perfecta de mosaico (imágenes adyacentes sin bordes)

18

Tengo un motor de juego 2D que dibuja mapas de mosaicos dibujando mosaicos de una imagen de conjunto de mosaicos. Debido a que, por defecto, OpenGL solo puede envolver toda la textura ( GL_REPEAT), y no solo una parte, cada mosaico se divide en una textura separada. Luego, las regiones del mismo mosaico se vuelven adyacentes entre sí. Así es como se ve cuando funciona según lo previsto:

Mapa de mosaico sin costuras

Sin embargo, tan pronto como introduzca la escala fraccional, aparecerán las costuras:

Mapa de mosaico con costuras

¿Por qué pasó esto? Pensé que se debía al filtrado lineal que mezclaba los bordes de los quads, pero aún sucede con el filtrado de puntos. La única solución que he encontrado hasta ahora es asegurar que todo el posicionamiento y la escala solo ocurran en valores enteros, y usar el filtrado de puntos. Esto puede degradar la calidad visual del juego (particularmente que el posicionamiento de subpíxeles ya no funciona, por lo que el movimiento no es tan suave).

Cosas que he probado / considerado:

  • antialiasing reduce, pero no elimina por completo, las costuras
  • desactivar mipmapping, no tiene efecto
  • renderice cada mosaico individualmente y extruya los bordes en 1px, pero esto es una des-optimización, ya que ya no puede procesar regiones de mosaicos de una sola vez, y crea otros artefactos a lo largo de los bordes de las áreas de transparencia
  • agregue un borde de 1px alrededor de las imágenes de origen y repita los últimos píxeles, pero ya no son la potencia de dos, causando problemas de compatibilidad con sistemas sin soporte NPOT
  • escribir un sombreador personalizado para manejar imágenes en mosaico, pero entonces, ¿qué haría diferente? GL_REPEATdebe tomar el píxel desde el lado opuesto de la imagen en los bordes, y no elegir la transparencia.
  • la geometría es exactamente adyacente, no hay errores de redondeo de coma flotante.
  • Si el sombreador de fragmentos está codificado para devolver el mismo color, las costuras desaparecerán .
  • si las texturas se establecen en GL_CLAMPlugar de GL_REPEAT, las costuras desaparecen (aunque el renderizado es incorrecto).
  • si las texturas están configuradas en GL_MIRRORED_REPEAT, las costuras desaparecen (aunque el renderizado vuelve a ser incorrecto).
  • Si hago el fondo rojo, las costuras siguen siendo blancas. Esto sugiere que está muestreando blanco opaco de algún lugar en lugar de transparencia.

Por lo tanto, las costuras aparecen solo cuando GL_REPEATestá configurado. Por alguna razón solo en este modo, en los bordes de la geometría hay algo de sangrado / fuga / transparencia. ¿Como puede ser? Toda la textura es opaca.

AshleysBrain
fuente
Podría intentar configurar su muestra de textura para sujetar, o ajustar el color del borde, ya que sospecho que puede estar relacionado con eso. Además, GL_Repeat simplemente envuelve las coordenadas UV, por lo que personalmente estoy un poco confundido cuando dices que solo puede repetir toda la textura, en lugar de una parte de ella.
Evan
1
¿Es posible que estés introduciendo grietas en tu malla de alguna manera? Puede probarlo configurando el sombreador para que solo devuelva un color sólido en lugar de muestrear una textura. Si aún ve grietas en ese punto, están en la geometría. El hecho de que el antialiasing reduzca las costuras sugiere que este podría ser el problema, ya que el antialiasing no debería afectar el muestreo de textura.
Nathan Reed
Puede implementar la repetición de mosaicos individuales en un atlas de texturas. Sin embargo, debe hacer algunos cálculos matemáticos de coordenadas adicionales e insertar texturas de borde alrededor de cada mosaico. Este artículo explica mucho de esto (aunque principalmente con un enfoque en la convención de coordenadas de textura molesta de D3D9). Alternativamente, si su implementación es lo suficientemente nueva, puede usar texturas de matriz para su mapa de mosaico; suponiendo que cada mosaico tenga las mismas dimensiones, esto funcionará muy bien y no requerirá ninguna matemática de coordenadas adicional.
Andon M. Coleman
Las texturas 3D con GL_NEARESTmuestreo en la Rdirección de coordenadas también funcionan tan bien como las texturas de matriz para la mayoría de las cosas en este escenario. Mipmapping no va a funcionar, pero a juzgar por su aplicación, probablemente no necesite mipmaps de todos modos.
Andon M. Coleman
1
¿De qué manera la representación es "incorrecta" cuando se establece en CLAMP?
Martijn Courteaux

Respuestas:

1

Las costuras son el comportamiento correcto para el muestreo con GL_REPEAT. Considere el siguiente mosaico:

azulejo de borde

El muestreo en los bordes del mosaico utilizando valores fraccionales mezcla colores del borde y el borde opuesto, lo que da como resultado colores incorrectos: el borde izquierdo debe ser verde, pero es una mezcla de verde y beige. El borde derecho debe ser beige, pero también tiene un color mixto. Especialmente las líneas de color beige en el fondo verde son muy visibles, pero si miras de cerca puedes ver el sangrado verde en el borde:

azulejo escalado

antialiasing reduce, pero no elimina por completo, las costuras

MSAA funciona tomando más muestras alrededor de los bordes de los polígonos. Las muestras tomadas a la izquierda o derecha del borde serán "verdes" (nuevamente, considerando el borde izquierdo de la primera imagen), y solo una muestra estará "en" el borde, tomando muestras parcialmente del área "beige". Cuando las muestras se mezclan, el efecto se reducirá.

Soluciones posibles:

En cualquier caso , debe cambiar a GL_CLAMPpara evitar el sangrado del píxel en el lado opuesto de la textura. Entonces tienes tres opciones:

  1. Habilite el suavizado para suavizar un poco la transición entre los mosaicos.

  2. Establece el filtrado de textura en GL_NEAREST. Esto da bordes duros a todos los píxeles, por lo que los bordes de polígono / sprite se vuelven indistinguibles, pero obviamente cambia bastante el estilo del juego.

  3. Agregue el borde de 1px ya discutido, solo asegúrese de que el borde tenga el color del mosaico adyacente (y no el color del borde opuesto).

    • Este también podría ser un buen momento para cambiar a un atlas de texturas (solo hazlo más grande si te preocupa el soporte de NPOT).
    • Esta es la única solución "perfecta", que permite un GL_LINEARfiltrado similar a los bordes de polígonos / sprites.
Jens Nolte
fuente
Oh, gracias, la envoltura alrededor de la textura lo explica. ¡Buscaré esas soluciones!
AshleysBrain
6

Debido a que, por defecto, OpenGL solo puede envolver toda la textura (GL_REPEAT), y no solo una parte, cada mosaico se divide en una textura separada. Luego, las regiones del mismo mosaico se vuelven adyacentes entre sí.

Considere la visualización de un único quad con textura ordinaria en OpenGL. ¿Hay costuras a alguna escala? No nunca. Tu objetivo es juntar todos los mosaicos de tu escena en una sola textura y luego enviarlos a la pantalla. EDITAR Para aclarar esto aún más: si tiene vértices delimitadores discretos en cada cuadrante de baldosas, tendrá costuras aparentemente injustificadas en la mayoría de las circunstancias. Así es como funciona el hardware de la GPU ... los errores de coma flotante crean estas brechas en función de la perspectiva actual ... errores que se evitan en una variedad unificada, ya que si dos caras son adyacentes dentro de la misma variedad(submesh), el hardware los renderizará sin costuras, garantizado. Lo has visto en innumerables juegos y aplicaciones. Debe tener una textura compacta en una sola malla sin vértices duplicados para evitar esto de una vez por todas. Es un problema que surge una y otra vez en este sitio y en otros lugares: si no combina los vértices de las esquinas de sus mosaicos (cada 4 mosaicos comparten un vértice de la esquina), espere uniones.

(Tenga en cuenta que es posible que ni siquiera necesite vértices, excepto en las cuatro esquinas de todo el mapa ... depende de su enfoque del sombreado).

Para resolver: renderice (a 1: 1) todas sus texturas de mosaico en un FBO / RBO sin espacios, luego envíe ese FBO al framebuffer predeterminado (la pantalla). Debido a que el FBO en sí mismo es básicamente una textura única, no puede terminar con brechas en la escala. Todos los límites de texel que no caen en un límite de píxel de la pantalla se combinarán si está utilizando GL_LINEAR ... que es exactamente lo que desea. Este es el enfoque estándar.

Esto también abre una serie de rutas diferentes para escalar:

  • escalar el tamaño del quad al que representará el FBO
  • cambiando los rayos UV en ese quad
  • jugueteando con la configuración de la cámara
Ingeniero
fuente
No creo que esto funcione bien para nosotros. Parece que está proponiendo que con un zoom del 10% renderizamos un área 10 veces más grande. Esto no es un buen augurio para dispositivos con una tasa de llenado limitada. Además, todavía estoy desconcertado por qué GL_REPEAT específicamente solo causa costuras, y ningún otro modo lo hace. ¿Cómo es posible?
AshleysBrain
@AshleysBrain Non sequitur. Estoy desconcertado por su declaración. Por favor, explique cómo aumentar el zoom en un 10% aumenta la tasa de relleno en 10x. ¿O cómo aumenta el zoom en 10x, ya que el recorte de la vista evitaría una tasa de relleno de más del 100% en una sola pasada? Le sugiero que muestre exactamente lo que ya tiene la intención de hacer, ni más ni menos, en lo que probablemente sea una manera más eficiente Y sin problemas, al unificar su salida final utilizando RTT, una técnica común. El hardware se encargará de recortar, escalar, combinar e interpolar ventanas gráficas prácticamente sin costo, dado que este es solo un motor 2D.
Ingeniero
@AshleysBrain He editado mi pregunta para aclarar los problemas con su enfoque actual.
Ingeniero
@ArcaneEngineer Hola, he probado tu enfoque que resuelve perfectamente el problema de las costuras. Sin embargo, ahora, en lugar de tener costuras, tengo errores de píxeles. Puede tener una idea del problema al que me refiero aquí . ¿Tienes alguna idea de cuál podría ser la causa de esto? Tenga en cuenta que estoy haciendo todo en WebGL.
Nemikolh
1
@ArcaneEngineer Haré una nueva pregunta, explicar aquí es demasiado engorroso. Perdón por la molestia.
Nemikolh
1

Si las imágenes deben aparecer como una superficie, renderícelas como una textura de superficie (escala 1x) en una malla / plano 3D. Si renderiza cada uno como un objeto 3D separado, siempre tendrá costuras debido a errores de redondeo.

La respuesta de @ Nick-Wiggill es correcta, creo que la malinterpretaste.

Barry Staes
fuente
0

2 posibilidades que vale la pena probar:

  1. Úselo en GL_NEARESTlugar de GL_LINEARpara filtrar su textura. (Como señaló Andon M. Coleman)

    GL_LINEAR(en efecto) desenfocará la imagen muy ligeramente (interpolando entre los píxeles más cercanos), lo que permite una transición suave del color de un píxel al siguiente. Esto puede ayudar a que las texturas se vean mejor en muchos casos, ya que evita que sus texturas tengan esos píxeles en bloque por todas partes. También permite que un poco de color marrón de un píxel de un mosaico se pueda difuminar al píxel verde adyacente del mosaico adyacente.

    GL_NEARESTsolo encuentra el píxel más cercano y usa ese color. Sin interpolación Como resultado, en realidad es un poco más rápido.

  2. Reduce un poco las coordenadas de textura para cada mosaico.

    Algo así como agregar ese píxel adicional alrededor de cada mosaico como un búfer, excepto que en lugar de expandir el mosaico en la imagen, reduce el mosaico en el software. Mosaicos de 14x14px mientras se renderiza en lugar de mosaicos de 16x16px.

    Incluso podría salirse con la suya con baldosas de 14.5x14.5 sin mezclar demasiado marrón.

--editar--

Visualización de la explicación en la opción # 2

Como se muestra en la imagen de arriba, aún puede usar fácilmente una potencia de dos texturas. Por lo tanto, puede admitir hardware que no admite texturas NPOT. Lo que cambia son tus coordenadas de textura, en lugar de ir de (0.0f, 0.0f)a (1.0f, 1.0f)Irías de (0.03125f, 0.03125f)a (0.96875f, 0.96875f).

Eso coloca sus coordenadas de texto ligeramente dentro del mosaico, lo que reduce la resolución efectiva en el juego (aunque no en el hardware para que aún tenga texturas de potencia de dos), sin embargo, debería tener el mismo efecto de representación que expandir la textura.

Wolfgang Skyler
fuente
Ya mencioné que probé GL_NEAREST (también conocido como filtrado de puntos), y no lo soluciona. No puedo hacer el # 2 porque necesito soportar dispositivos NPOT.
AshleysBrain
Hizo una edición que podría aclarar algunas cosas. (Supongo que "la necesidad de admitir dispositivos NPOT [(sin potencia de dos)] estaba destinado a significar algo similar a lo que usted necesita para mantener las texturas a una potencia de 2?)
Wolfgang Skyler
0

Esto es lo que creo que podría estar sucediendo: donde dos fichas colisionan, el componente x de sus bordes debería ser igual. Si eso no es cierto, podrían redondearse en la dirección opuesta. Así que asegúrese de que tengan exactamente el mismo valor x. ¿Cómo se asegura de que esto suceda? Puede intentar, digamos, multiplicar por 10, redondear y luego dividir por 10 (solo haga esto en la CPU, antes de que sus vértices entren en el sombreador de vértices). Eso debería dar resultados correctos. Además, no dibuje mosaico por mosaico utilizando matrices de transformación, sino colóquelas en un VBO por lotes para asegurarse de que no se obtengan resultados incorrectos del sistema de coma flotante IEEE con pérdida en combinación con multiplicaciones de matrices.

¿Por qué sugiero esto? Porque, según mi experiencia, si los vértices tienen las mismas coordenadas exactas cuando salen del sombreador de vértices, llenar los triángulos correspondientes creará resultados perfectos. Tenga en cuenta que algo que es matemáticamente correcto, podría estar un poco fuera de lugar debido a IEEE. Cuantos más cálculos realice en sus números, menos preciso será el resultado. Y sí, las multiplicaciones matriciales requieren bastantes operaciones, que podrían realizarse solo en una multiplicación y una adición al crear su VBO, lo que dará resultados más precisos.

Lo que también podría ser el problema es que está utilizando una hoja de sprites (también llamada atlas) y que al muestrear la textura, se recogen los píxeles de la textura de mosaico adyacente. Asegurarse de que esto no suceda es crear un pequeño borde en las asignaciones de UV. Entonces, si tiene un mosaico de 64x64, su mapeo UV debería cubrir un poco menos. ¿Cuánto cuesta? Creo que usé en mis juegos 1 cuarto de píxel a cada lado del rectángulo. Por lo tanto, compensa tus UV por 1 / (4 * widthOfTheAtlas) para componentes x y 1 / (4 * heightOfTheAtlas) para componentes y.

Martijn Courteaux
fuente
Gracias, pero como ya noté, la geometría es definitivamente adyacente, de hecho, todas las coordenadas dan como resultado enteros exactos, por lo que es fácil saberlo. No se usan atlas aquí.
AshleysBrain
0

Para resolver esto en el espacio 3D, mezclé matrices de texturas con la sugerencia de Wolfgang Skyler. Puse un borde de 8 píxeles alrededor de mi textura real para cada mosaico (120x120, 128x128 en total) que, para cada lado, podría llenar con una imagen "envuelta" o el lado que se acaba de extender. La muestra lee esta área cuando está interpolando la imagen.

Ahora con filtrado y mipmapping, la muestra aún puede leer fácilmente más allá del borde completo de 8 píxeles. Para detectar ese pequeño problema (digo pequeño porque solo ocurre en unos pocos píxeles cuando la geometría está realmente sesgada o muy lejos), dividí los mosaicos en una matriz de textura, por lo que cada mosaico tiene su propio espacio de textura y puede usar la sujeción en el bordes

En su caso (2D / plano), sin duda elegiría renderizar una escena perfecta de píxeles y luego escalar la parte deseada del resultado en la ventana gráfica, como lo sugirió Nick Wiggil.

mukunda
fuente