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:
No entanto, assim que você introduz a escala fracionária, as costuras aparecem:
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_REPEAT
deve 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_CLAMP
vez deGL_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_REPEAT
definidas. Por alguma razão apenas neste modo, nas bordas da geometria há algum sangramento / vazamento / transparência. Como pode ser? Toda a textura é opaca.
GL_NEAREST
amostragem naR
direçã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.Respostas:
As costuras são o comportamento correto para amostragem com GL_REPEAT. Considere o seguinte bloco:
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:
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_CLAMP
para evitar o sangramento do pixel no lado oposto da textura. Então você tem três opções:Ative o anti-aliasing para suavizar um pouco a transição entre os blocos.
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.Adicione a borda de 1px já discutida, apenas verifique se a borda tem a cor do bloco adjacente (e não a borda oposta).
GL_LINEAR
filtragem semelhante nas bordas dos polígonos / sprites.fonte
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:
fonte
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.
fonte
2 possibilidades que valem a pena tentar:
Use em
GL_NEAREST
vez deGL_LINEAR
para 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_NEAREST
apenas encontra o pixel mais próximo e usa essa cor. Sem interpolação. Como resultado, é realmente um pouco mais rápido.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--
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.
fonte
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.
fonte
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.
fonte