Renderização de mapa de blocos sem costura (imagens adjacentes sem borda)

18

Eu tenho um mecanismo de jogo 2D que desenha mapas de peças desenhando peças a partir de uma imagem de conjunto de peças. Como, por padrão, o OpenGL só pode agrupar toda a textura ( GL_REPEAT), e não apenas parte dela, cada bloco é dividido em uma textura separada. Em seguida, as regiões do mesmo bloco são renderizadas adjacentes uma à outra. Aqui está o que parece quando está funcionando como pretendido:

Tilemap sem costuras

No entanto, assim que você introduz a escala fracionária, as costuras aparecem:

Tilemap com costuras

Por que isso acontece? Eu pensei que era devido à filtragem linear misturando as bordas dos quadriláteros, mas ainda acontece com a filtragem de pontos. A única solução que encontrei até agora é garantir que todo posicionamento e escalonamento ocorram apenas em valores inteiros e usar a filtragem de pontos. Isso pode degradar a qualidade visual do jogo (particularmente que o posicionamento sub-pixel não funciona mais, portanto, o movimento não é tão suave).

Coisas que tentei / considerei:

  • antialiasing reduz, mas não elimina completamente, as costuras
  • desativar o mipmapping, não tem efeito
  • renderize cada ladrilho individualmente e expulse as arestas em 1px - mas isso é uma des otimização, pois não pode mais renderizar regiões de ladrilhos de uma só vez e cria outros artefatos ao longo das arestas das áreas de transparência
  • adicione uma borda de 1px ao redor das imagens de origem e repita os últimos pixels - mas eles não serão mais dois, causando problemas de compatibilidade com sistemas sem suporte a NPOT
  • escrevendo um sombreador personalizado para lidar com imagens lado a lado - mas o que você faria de diferente? GL_REPEATdeve pegar o pixel do lado oposto da imagem nas bordas e não escolher transparência.
  • a geometria é exatamente adjacente, não há erros de arredondamento de ponto flutuante.
  • se o sombreador do fragmento for codificado para retornar a mesma cor, as costuras desaparecerão .
  • se as texturas estiverem definidas como em GL_CLAMPvez de GL_REPEAT, as costuras desaparecerão (embora a renderização esteja incorreta).
  • se as texturas estiverem definidas GL_MIRRORED_REPEAT, as costuras desaparecerão (embora a renderização esteja incorreta novamente).
  • se eu tornar o fundo vermelho, as costuras ainda estão brancas. Isso sugere que está amostrando branco opaco de algum lugar, em vez de transparência.

Portanto, as costuras aparecem apenas quando GL_REPEATdefinidas. Por alguma razão apenas neste modo, nas bordas da geometria há algum sangramento / vazamento / transparência. Como pode ser? Toda a textura é opaca.

AshleysBrain
fonte
Você pode tentar configurar o seu amostrador de textura para fixar ou ajustar a cor da borda, como suspeito que possa estar relacionado a isso. Além disso, GL_Repeat simplesmente envolve as coordenadas UV, por isso estou pessoalmente um pouco confuso quando você diz que só pode repetir toda a textura, em vez de uma parte dela.
Evan
11
É possível que você esteja introduzindo rachaduras na sua malha de alguma forma? Você pode testá-lo configurando o sombreador para retornar apenas uma cor sólida em vez de experimentar uma textura. Se você ainda vê rachaduras nesse ponto, elas estão na geometria. O fato de o antialiasing reduzir as costuras sugere que esse pode ser o problema, pois o antialiasing não deve afetar a amostragem de textura.
Nathan Reed
Você pode implementar a repetição de blocos individuais em um atlas de textura. Você precisa fazer algumas matemáticas de coordenadas de textura extra e inserir texels de borda em torno de cada lado. Este artigo explica muito disso (embora principalmente com foco na convenção de coordenadas de textura irritante do D3D9). Como alternativa, se sua implementação for nova o suficiente, você poderá usar texturas de matriz para seu mapa de blocos; supondo que cada bloco tenha as mesmas dimensões, isso funcionará muito bem e não exigirá nenhuma matemática extra de coordenadas.
Andon M. Coleman
Texturas 3D com GL_NEARESTamostragem na Rdireção de coordenadas também funcionam tão bem quanto texturas de matriz para a maioria das coisas neste cenário. O mapeamento de mip não vai funcionar, mas, a julgar pelo seu aplicativo, você provavelmente não precisa de mipmaps.
Andon M. Coleman
11
De que maneira a renderização está "errada", quando definida como CLAMP?
Martijn Courteaux

Respostas:

1

As costuras são o comportamento correto para amostragem com GL_REPEAT. Considere o seguinte bloco:

ladrilho de borda

A amostragem nas bordas do ladrilho usando valores fracionários mistura cores da borda e da borda oposta, resultando em cores incorretas: A borda esquerda deve ser verde, mas é uma mistura de verde e bege. A borda direita deve ser bege, mas também é de cor mista. Especialmente as linhas bege no fundo verde são muito visíveis, mas se você olhar de perto, poderá ver o verde sangrando na borda:

ladrilho em escala

antialiasing reduz, mas não elimina completamente, as costuras

O MSAA trabalha coletando mais amostras em torno das bordas dos polígonos. As amostras tiradas à esquerda ou à direita da borda serão "verdes" (novamente, considerando a borda esquerda da primeira foto), e apenas uma amostra estará "na" borda, amostrando parcialmente a área "bege". Quando as amostras são misturadas, o efeito será reduzido.

Soluções possíveis:

Em qualquer caso, você deve mudar para GL_CLAMPpara evitar o sangramento do pixel no lado oposto da textura. Então você tem três opções:

  1. Ative o anti-aliasing para suavizar um pouco a transição entre os blocos.

  2. Defina a filtragem de textura como GL_NEAREST. Isso dá a todos os pixels arestas duras, para que as arestas de polígono / sprite se tornem indistinguíveis, mas obviamente muda o estilo do jogo.

  3. Adicione a borda de 1px já discutida, apenas verifique se a borda tem a cor do bloco adjacente (e não a borda oposta).

    • Também pode ser um bom momento para mudar para um atlas de textura (apenas aumente se você estiver preocupado com o suporte ao NPOT).
    • Esta é a única solução "perfeita", permitindo uma GL_LINEARfiltragem semelhante nas bordas dos polígonos / sprites.
Jens Nolte
fonte
Oh, obrigado - o envolvimento da textura explica isso. Vou procurar essas soluções!
AshleysBrain
6

Como, por padrão, o OpenGL pode quebrar apenas a textura inteira (GL_REPEAT), e não apenas parte dela, cada bloco é dividido em uma textura separada. Em seguida, as regiões do mesmo bloco são renderizadas adjacentes uma à outra.

Considere a exibição de um único quad texturizado comum no OpenGL. Existem costuras, em alguma escala? Não nunca. Seu objetivo é reunir todos os blocos de cena em uma única textura e enviá - los para a tela. EDIT Para esclarecer ainda mais: Se você tem vértices delimitadores discretos em cada quadra de blocos, você terá costuras aparentemente injustificadas na maioria das circunstâncias. É como o hardware da GPU funciona ... erros de ponto flutuante criam essas lacunas com base na perspectiva atual ... erros que são evitados em um coletor unificado, pois se duas faces estão adjacentes no mesmo coletor(submesh), o hardware as renderizará sem costuras, garantidas. Você já viu isso em inúmeros jogos e aplicativos. Você deve ter uma textura compactada em uma única submesh sem vértices duplicados para evitar isso de uma vez por todas. É uma questão que surge repetidamente neste site e em outros lugares: se você não mesclar os vértices de canto de seus blocos (a cada 4 blocos compartilham um vértice de canto), espere costuras.

(Considere que você pode nem precisar de vértices, exceto nos quatro cantos do mapa inteiro ... depende da sua abordagem ao sombreamento.)

Para resolver: renderize (em 1: 1) todas as texturas de seu bloco em um FBO / RBO sem intervalos, depois envie esse FBO para o framebuffer padrão (a tela). Como o próprio FBO é basicamente uma textura única, não é possível acabar com lacunas na escala. Todos os limites texel que não caem em um limite de pixel da tela serão combinados se você estiver usando GL_LINEAR .... que é exatamente o que você deseja. Essa é a abordagem padrão.

Isso também abre várias rotas diferentes para o dimensionamento:

  • escalando o tamanho do quad para o qual você renderizará o FBO
  • mudando os UVs naquele quad
  • mexendo nas configurações da câmera
Engenheiro
fonte
Eu não acho que isso funcionará bem para nós. Parece que você está propondo que, com um zoom de 10%, renderizamos uma área 10 vezes maior. Isso não é um bom presságio para dispositivos com taxa de preenchimento limitada. Também estou confuso por que especificamente GL_REPEAT só causa costuras, e nenhum outro modo faz. Como poderia ser?
AshleysBrain
@AshleysBrain Non sequitur. Estou confuso com sua declaração. Por favor, explique como aumentar o zoom em 10% aumenta a taxa de preenchimento em 10x? Ou o aumento do zoom em 10x - já que o recorte da viewport impediria mais de 100% de taxa de preenchimento em uma única passagem? Estou sugerindo que você exiba exatamente o que você já pretende - nem mais, nem menos - do que provavelmente é uma maneira mais eficiente e uniforme, unificando sua saída final usando o RTT, uma técnica comum. O hardware manipulará o corte, a escala, a mesclagem e a interpolação da janela de visualização praticamente sem nenhum custo, pois esse é apenas um mecanismo 2D.
Engenheiro de
@AshleysBrain Eu editei minha pergunta para esclarecer os problemas com sua abordagem atual.
Engineer
@ArcaneEngineer Oi, eu tentei sua abordagem que resolve perfeitamente o problema das costuras. No entanto, agora, em vez de costuras, tenho erros de pixel. Você pode ter uma idéia do problema que estou me referindo aqui . Você tem alguma idéia do que pode ser a causa disso? Observe que estou fazendo tudo no WebGL.
Nemikolh 5/05
11
@ArcaneEngineer Vou fazer uma nova pergunta, explicar aqui é muito complicado. Sinto muito pelo aborrecimento.
Nemikolh 5/05
1

Se as imagens aparecerem como uma superfície, renderize-as como uma textura de superfície (escala 1x) em uma malha / plano 3D. Se você renderizar cada um como um objeto 3D separado, ele sempre terá costuras devido a erros de arredondamento.

A resposta de @ Nick-Wiggill está correta, acho que você entendeu errado.

Barry Staes
fonte
0

2 possibilidades que valem a pena tentar:

  1. Use em GL_NEARESTvez de GL_LINEARpara a sua textura filtrando. (Como apontado por Andon M. Coleman)

    GL_LINEAR(com efeito) desfocará a imagem levemente (interpolando entre os pixels mais próximos), o que permite uma transição suave na cor de um pixel para o próximo. Isso pode ajudar a melhorar a aparência das texturas em muitos casos, pois evita que essas texturas tenham todos os pixels em blocos. Também faz com que um pouco de marrom de um pixel de um ladrilho possa ficar embaçado para o pixel verde adjacente do ladrilho adjacente.

    GL_NEARESTapenas encontra o pixel mais próximo e usa essa cor. Sem interpolação. Como resultado, é realmente um pouco mais rápido.

  2. Reduza um pouco as coordenadas de textura de cada peça.

    Mais ou menos como adicionar esse pixel extra ao redor de cada bloco como um buffer, exceto que, em vez de expandir o bloco na imagem, você reduz o bloco no software. Blocos de 14x14px durante a renderização, em vez de blocos de 16x16px.

    Você pode até conseguir usar ladrilhos de 14,5x14,5 sem misturar muito marrom.

--editar--

Visualização da explicação na opção # 2

Como mostrado na imagem acima, você ainda pode usar facilmente uma potência de duas texturas. Assim, você pode oferecer suporte a hardware que não suporta texturas NPOT. O que muda são suas coordenadas de textura, em vez de passar de (0.0f, 0.0f)para (1.0f, 1.0f)Você passaria de (0.03125f, 0.03125f)para (0.96875f, 0.96875f).

Isso coloca suas cordas texas levemente dentro do bloco, o que reduz a resolução efetiva no jogo (embora não no hardware, para que você ainda tenha duas texturas), no entanto, deve ter o mesmo efeito de renderização que a expansão da textura.

Wolfgang Skyler
fonte
Eu já mencionei que tentei o GL_NEAREST (também conhecido como filtro de pontos) e não o corrige. Não consigo fazer o número 2 porque preciso oferecer suporte a dispositivos NPOT.
AshleysBrain
Foi feita uma edição que pode esclarecer algumas coisas. (Estou supondo que "a necessidade de suportar dispositivos NPOT [(sem poder de dois)]]" significasse algo no sentido de que você precisa manter as texturas com algum poder de 2?)
Wolfgang Skyler
0

Aqui está o que eu acho que pode estar acontecendo: Onde duas peças colidem, o componente x de suas bordas deve ser igual. Se isso não for verdade, eles podem ser arredondados na direção oposta. Portanto, verifique se eles têm exatamente o mesmo valor x. Como você garante que isso aconteça? Você pode tentar, digamos, multiplicar por 10, arredondar e depois dividir por 10 (faça isso apenas na CPU, antes que seus vértices entrem no shader de vértice). Isso deve dar resultados corretos. Além disso, não desenhe bloco a bloco usando matrizes de transformação, mas coloque-os em um VBO em lote para garantir que os resultados incorretos não sejam provenientes do sistema de ponto flutuante IEEE com perda em combinação com multiplicações de matriz.

Por que eu sugiro isso? Porque, pela minha experiência, se os vértices tiverem exatamente as mesmas coordenadas quando saírem do sombreador de vértices, o preenchimento dos triângulos correspondentes criará resultados contínuos. Lembre-se de que algo matematicamente correto pode estar um pouco errado devido ao IEEE. Quanto mais cálculos você fizer nos seus números, menos preciso será o resultado. E sim, as multiplicações de matriz exigem algumas operações, que podem ser feitas apenas em uma multiplicação e uma adição ao criar sua VBO, o que fornecerá resultados mais precisos.

O que também pode ser o problema é que você está usando uma planilha (também chamada de atlas) e, ao amostrar a textura, os pixels da textura adjacente do bloco são escolhidos. Garantir que isso não aconteça é criar uma pequena borda nos mapeamentos UV. Portanto, se você tiver um bloco de 64x64, seu mapeamento UV deverá cobrir um pouco menos. Quanto? Acho que usei nos meus jogos 1/4 de pixel de cada lado do retângulo. Portanto, compense seus UVs em 1 / (4 * widthOfTheAtlas) para componentes x e 1 / (4 * heightOfTheAtlas) para componentes y.

Martijn Courteaux
fonte
Obrigado, mas como já observei, a geometria é definitivamente exatamente adjacente - na verdade, todas as coordenadas resultam em números inteiros exatos, por isso é fácil dizer. Nenhum atlas é usado aqui.
AshleysBrain
0

Para resolver isso no espaço 3D, misturei matrizes de textura com a sugestão de Wolfgang Skyler. Coloquei uma borda de 8 pixels em torno da minha textura real para cada bloco (total 120x120, 128x128) que, para cada lado, eu poderia preencher com a imagem "empacotada" ou o lado que estava sendo estendido. O amostrador lê essa área quando interpola a imagem.

Agora, com filtragem e mipmapping, o amostrador ainda pode ler facilmente além de toda a borda de 8 pixels. Para capturar esse pequeno problema (digo pequeno porque só acontece em alguns pixels quando a geometria está realmente distorcida ou distante), divido os ladrilhos em uma matriz de textura, para que cada ladrilho tenha seu próprio espaço de textura e possa usar a fixação no arestas.

No seu caso (2D / plano), eu certamente iria renderizar uma cena perfeita em pixels e depois escalar a parte desejada do resultado na janela de visualização, conforme sugerido por Nick Wiggil.

mukunda
fonte