Maneira eficiente de desenhar contornos em torno de sprites

21

Estou usando o XNA para programar um jogo e tenho experimentado várias maneiras de obter um efeito 'selecionado' nos meus sprites. O problema que estou tendo é que cada clique clicável que é desenhado no lote de sprites é desenhado usando mais de um único sprite (cada objeto pode ser composto de até 6 sprites).

Eu apreciaria se alguém pudesse me aconselhar sobre como eu poderia adicionar um contorno aos meus sprites de pixels X (para que a largura do contorno possa ser vários números de pixels inteiros).

Desde já, obrigado,

  • Greg.
Greg
fonte

Respostas:

20

De longe, a maneira mais fácil de fazer isso (provavelmente a melhor, a menos que você esteja realmente interessado em desempenho) é ter duas cópias de seus sprites.

  • A versão regular
  • Uma versão "gorda", sem cor - basicamente uma versão branca do seu sprite X muitos pixels "mais gorda" que o original.

Desenhe seu objeto inteiro usando a versão "gorda" e, em seguida, desenhe a versão regular por cima.

Ao tornar a versão "gorda" branca, você pode usar o tingimento de cores interno do SpriteBatch para alterar a cor da seleção dinamicamente.

Para gerar sua versão "gorda", recomendo escrever uma extensão de pipeline de conteúdo que possa executar automaticamente os sprites originais, ler o canal alfa, criar um novo canal alfa, amostrando o canal alfa máximo na imagem original X muitos pixels ao redor de cada pixel, e configurando RGB = (1,1,1).

Você precisará garantir que todos os seus sprites tenham borda transparente suficiente para adicionar o contorno (você pode verificar isso no processador de conteúdo - e até criar espaço, se necessário).

Se você tiver apenas alguns sprites, poderá usar um bom editor de imagens (GIMP, Photoshop) e fazê-lo manualmente: canal alfa para seleção, expandir seleção, seleção para alfa, preencher canais de cores em branco.

Andrew Russell
fonte
4

Eu acho que você precisa desenhar todas as peças que cada objeto primeiro em um sprite. Então eu acho que você teria que escrever um shader para detectar as bordas do sprite e desenhar um pixel onde quer que ele encontre uma borda. Espero que já devam existir alguns shaders disponíveis para fazer isso, que você pode usar ou portar.

Iain
fonte
11
Ouvi dizer que esse tipo de sombreador é surpreendentemente chato de escrever (nós o usamos em um de nossos jogos em 3D e continuamos correndo para casos extremos). Uma coisa que você pode considerar é codificar o contorno diretamente na textura do sprite com uma cor específica como (0, 255, 255, 0) e fazer com que o shader transforme essa cor quando o sprite for selecionado. Em seguida, o sombreador é uma mudança trivial de cor e você tem um alto nível de controle do artista sobre o contorno e todos os detalhes que desejar.
4

Dependendo dos requisitos, o que também pode ser eficaz é apenas criar um esboço sob demanda para o sprite. Suponho que seus sprites tenham transparência e tenham um formato irregular em vez de apenas retângulos (embora isso funcione bem para isso, os retângulos de contorno devem ser triviais).

when selected:
   outline = new sprite canvas of appropriate size
   for sprite in object:
      # use larger numbers for thicker outlines
      for x in (-1, 0, 1) and y in (-1, 0, 1):
         render sprite mask into canvas at x,y with desired color

Observe que você não precisa fazer isso todos os sorteios (embora suponha que sim), mas apenas crie o novo sprite de contorno ao alternar sprites.

traço-tom-bang
fonte
Sim, era isso que eu queria sugerir também. Renderize o sprite mascarado 1 pixel à esquerda, direita, superior e inferior do sprite original, sob o sprite existente. Muitos jogos usavam esse método para descrever o texto renderizado ou com apenas 2 das 4 posições para criar uma sombra projetada.
Kaj
1

A abordagem mais simples de força bruta é criar duas cópias de cada sprite, uma normal e uma destacada. Depois, troque-os quando destacados.

Se você tem memória de sobra, não há necessidade de ficar mais complicado do que isso. Além disso, os artistas têm controle total sobre a aparência quando destacados, para que você possa fazer um esboço ou qualquer outra coisa que desejar.

wkerslake
fonte
Eu acho que parte do problema é que o OP diz que cada objeto pode ser feito de vários sprites. Acho que o contorno deve ser o contorno do objeto combinado, em vez de ter um contorno separado em torno de cada sprite de componente.
Chris Howe
1
Chris, ele não precisa ser o contorno do objeto combinado e funcionará bem para um objeto feito de vários sprites. Faça exatamente o que wkerslake disse, mas certifique-se de desenhar os sprites destacados por trás de todos os sprites normais para esse objeto. Dessa forma, não importa quantos sprites sejam feitos, o destaque usará apenas um pouco mais do que o dobro do tempo de desenho para esse objeto, o que deve ser muito menor do que gerar os contornos em tempo de resposta com os sprites combinados.
AttackingHobo
1

Que tal para cada sprite, também tem outro sprite que é um esboço do sprite base. Ao desenhar um objeto delineado, desenhe os sprites de base, faça uma máscara da renderização combinada e desenhe os sprites de contorno, excluindo a máscara.

cinzento
fonte
2
Por que não desenhar primeiro os sprites de contorno e desenhar os sprites regulares sobre eles?
Chris Howe
Um motivo para não fazer isso (ou a sugestão de Chris) é porque ele usa duas vezes mais memória de textura; outro motivo é que o fluxo de trabalho do artista é uma porcaria, porque você precisa atualizar dois arquivos sempre que alterar um sprite.
2
Bem, sim, você idealmente não teria dois recursos reais de sprite. Se você estiver usando o teste alfa para seus sprites, poderá colocar o contorno no sprite e atribuir um alfa um pouco mais alto que suas áreas transparentes. Então, controlando o valor de referência do teste alfa, você pode controlar se o contorno é visível ou não. Tudo o que eu estava dizendo é que, se você tem 2 versões dos sprites (sejam 2 ativos ou 2 estados do mesmo ativo), primeiro desenhe a versão de estrutura de tópicos e depois a versão normal. Dessa forma, você não precisa de shaders, máscaras ou qualquer outra coisa.
21810 Chris Howe
1
A ideia de Chris tem mérito; além disso, é possível evitar que o artista atualize 2 arquivos (ou mesmo o mesmo ativo) se você produzir uma ferramenta para gerar o alfa para os sprites de estrutura de tópicos executados como parte do seu pipeline de ativos / conteúdo. A memória de textura pode ou não ser um problema, mas sprites de contorno separados devem ser altamente compactáveis ​​em DXT; possivelmente 1/6 do tamanho da textura original na memória de vídeo e sem perda de fidelidade.
jpaver
1

Algumas soluções diferentes com diferentes trade-offs.

Mais fácil: renderize o objeto usando uma cor plana várias vezes e instale a posição (deslocamento para a esquerda, para cima, para baixo, para a direita etc.), isso criará uma versão de contornos do que você renderizar sobre ele, mas tem custos de desempenho e não permita bordas grossas sem muitas renderizações extras. Uma borda de um ou dois pixels pode ser boa com 4x.

Mais rápido: pré-processe a textura e tenha uma cópia que já esteja com borda ou que seja apenas a borda ou que seja uma máscara plana de 8 bits em escala de cinza que você pode colorir em um sombreador. Provavelmente será rápido com o custo da memória.

Melhor: minha opinião, mas gerar uma representação SDF do seu objeto provavelmente seria a melhor solução. Essas texturas podem ser muito menores que a textura de origem e ainda capturar dados úteis. Essencialmente, cada pixel codifica a que distância está do objeto usado para gerá-lo. Com esses dados em mãos, você pode escrever todos os tipos de efeitos, desde brilhos a contornos. A borda pode mudar de tamanho e cor, etc., e ainda é um sombreador relativamente barato e apenas um empate extra. A desvantagem é o ferramental e o pré-processamento.

Aku
fonte
0

Não tenho certeza da eficiência, mas a maneira mais fácil de ver seria desenhar uma versão maior do sprite na cor que você deseja selecionar primeiro. Desenhe o sprite em cima disso. Você verá apenas a borda do primeiro sprite, dando o efeito de seleção.

EDIT: No entanto, como você pode ver nos comentários, essa não é uma boa ideia.

O Pato Comunista
fonte
1
Apenas ampliação um sprite não dá um verdadeiro esboço
Iain
2
Eu não acho que seria, mas seria fácil.
The Duck comunista
2
Muitas coisas são fáceis, mas não resolvem o problema. Para sprites com qualquer tipo de silhueta interessante, um aumento de escala produzirá lixo absoluto.
Aumentar a escala só ficará bom para objetos quadrados ou circulares sólidos. Se a forma for muito larga, alta, ou com lacunas, ela parecerá muito ruim.
AttackingHobo
3
Aumentar a escala dará melhores resultados do que não fazer nada e também seria um passo útil no caminho da "perfeição". Embora os resultados gráficos não sejam perfeitos, se for para uma ferramenta interna, pode ser bom o suficiente.
dash-tom-bang
0

Eu concordo em aumentar o sprite. De longe, a rota mais fácil, e você pode aplicá-la para selecionar QUALQUER sprite sem precisar criar sprites adicionais, especialmente para esse fim.

  • ie spriteOutline.Scale = spriteOriginal.Scale * 0.1f; spriteOutline.Color = new Color (255, 0, 0, 255);

fonte
0

Substitua a cor do sprite original pela cor do contorno (ou mesmo tinga, se desejar). Renderize esse sprite sombreado ou colorido quatro vezes com um deslocamento de 1 pixel: em x, y = (- 1, -1), depois (+ 1, -1), depois (-1, + 1) e depois (+1 , +1). Repita o procedimento para todos os sprites que compõem o objeto.

Depois disso, renderize os sprites originais na ordem correta no topo em (0,0).

Ilya Bossov
fonte