Skip to content

HienaDev/Collision-Shader

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

43 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Computação Gráfica - Relatório

Shader com colisões

Final Shader Images

Tree Shade Image

Statue Shader Image

Trabalho realizado por:

  • António Rodrigues - a22202884.

Link para repositório:

https://github.com/HienaDev/Collision-Shader

Relatório:

Comecei por procurar o “óbvio” e pesquisar como fazer um shader que reagia a colisões, com isso encontrei dois videos:

Shader Graph Forcefield: Update - Acabei por sentir que este vídeo não fazia o que queria, usava raycasts para determinar os impactos, e não colisões, mas retirei de lá a ideia de usar um ripple effect.

Energy Shield Effect in Unity URP Shader Graph - Este fazia quase tudo o que queria e usei como referência para começar.

Depois disto achei que procurar por um ripple effect seria o melhor começo, pois a partir daí, caso conseguisse determinar a posição das colisões, poderia enviar essa informação para o shader, e causar os ripple effects no local da colisão.

Pesquisei então por ripple effects e encontrei este video:

Shockwave Shader Graph - How to make a shock wave shader in Unity URP/HDRP

Este video foi um bom começo, e permitiu-me chegar a um resultado parecido ao efeito que queria:

Efeito ripple em sprite

Demonstração do efeito: Vídeo

Mas depois disto senti que tinha pouco controlo, no primeiro que era específico para sprites senti-me com mais controlo pois o shader permitia mudar o focal point num espaço 2D facilmente, e consegui também fazer um efeito com mais que um ripple:

Efeito ripple em sprite

Demonstração do efeito: Vídeo

Mas no caso da esfera, senti que tinha pouco controlo, e mesmo depois de algumas mudanças, e alterando o focal point para um Vector3, eu conseguia controlá-lo facilmente no eixo do X e do Y, mas estava com dificuldades no eixo do Z, garantidamente que era por não ter entendido tudo o que retirei do vídeo anterior, mas decidi voltar a pesquisar.

Com isso encontrei este vídeo: Unity Shader Graph VFX - Bubble Shield (Tutorial)

Com este tutorial aprendi algus efeitos como o twirl, que achei que seriam interessantes para o shader final, mas este tutorial usava uma esfera com UVs alterados, e eu quero criar um shader que funcione com qualquer objeto, especialmente os default do Unity:

Twirl

Apesar de o material estar a mudar, a esfera em si não mudava, suspeitei que fosse por a esfera do vídeo ter UVs alterados, não tinha Maya como no vídeo mas instalei Blender para confirmar a teoria, após criar a esfera com os UVs como no tutorial, a esfera continuou sem deformação, acabei por não entender o porquê:

Sem deformação

Mas depois disto achei que já tinha material suficiente para começar o meu shader.

Comecei a fazer o meu shader:

Criei um temporizador que me permite ir de 0 a 1 com a função seno: Timer Sin

Em vez de usar diretamente o sine time, usamos o multiply pelo meio, para podemos alterar a frequência do time, e depois fazemos o sine, assim mantemos o intervalo entre 0 e 1 mas mudamos quanto oscila por segundo.

Criei isto com o intuito de começar por ter um bubbling effect, então a oscilação constante do sen era perfeita para isso.

Depois disso criei um grupo para mudar a posição dos vértices: para isso multiplico o resultado do grupo anterior pela normal dos vértices, depois adiciono essa mudança a posição de cada vértices, que vai ser na direção perpendicular ao vértices (o vetor normal de cada vértice), criando este efeito de expansão:

Diagrama Vertices

(Desenho que exemplifica a direção de cada vetor normal para cada vértice)

Nodes Vertices

Com isto consegui o efeito pretendido:

Demonstração do efeito: Vídeo

Usando agora alguns componentes que vi serem usados num dos vídeos, criei o anel que percorre a mesh:

Nodes for ring

Recebemos o valor para a progressão da onda, ou seja onde está o anel, e usamos o componente fraction que nos dá a parte decimal do valor, depois pegamos neste valor e adicionamos-lhe o size, e noutro node subtraímos o size a ele, um exemplo disto: caso o valor estivesse no 0.6, e o size fosse 0.1, teríamos os valores 0.7 e 0.5, tendo assim uma wave de tamanho 0.2:

Fraction node

Depois disso, crio uma secção à parte, esta secção recebe o ponto de impacto (FocalPoint), normaliza este vetor para obter a direção.

Neste caso, como estamos a aplicar uma esfera de tamanho 1, dividimos por 2, pois queremos o comprimento de vetor a 0.5, já que o raio da esfera seria 0.5 e o diâmetro 1, as esferas neste caso teriam de ter sempre tamanho 1.

Depois subtraímos a direção pela posição no objeto, para termos a posição inicial da shockwave no objeto. O node da length dá-nos a distância até esta posição inicial no objeto:

Nodes for focal point

Criei um node smoothstep para receber estes inputs, ao receber o add e o subtract, obtemos os limites do nosso anel e a length dá-nos o ponto inicial a partir de qual o anel se afasta e expande ao longo da mesh:

Smoothstep Node

Dou este resultado a um One Minus node, como o smoothstep vai estar sempre compreendido entre 0 e 1, o one minus acaba por nos dar a diferença do seu input. Dando o valor oposto ao smoothstep.

Quando multiplicamos os dois resultados, tudo o que está a 0 em ambos é removido no outro, e onde não houver zeros, ficamos com um gradiente, que é mais forte no centro (onde ambos os inputs têm valor mais alto):

One minus node

Multiplicamos este valor pela nossa amplitude, o que altera o tamanho dos nossos valores que não são zero, fazendo assim com que a onda aumente ou diminua de tamanho:

Amplitude

Finalmente, damos este resultado ao nosso grupo que altera a posição dos vértices e obtemos a deformação:

Final node connection

Distortion Effect

Demonstração do efeito: Vídeo

Temos agora o efeito pretendido, mas não o queremos a repetir com o tempo como está agora, ou seja o valor da progressão da onda vai deixar de ser oscilante, mas sim um valor que o programa controla, e queremos que comece onde haja colisões, para isso temos que criar um script que trate de dar os valores corretos ao shader.

Antes disso, criei um subshader mais organizado para o efeito. Este subshader recebe 4 variaveis:

  • Progression: Distância do ponto de impacto entre 0 e 1, em que 0 é no ponto de impacto e 1 o ponto final;
  • Focalpoint: O ponto inicial de impacto;
  • Amplitude: A altura da onda;
  • Size: O tamanho da onda;

O subshader devolve o anel para ser desenhado.

SubShader

Agora com o subshader feito, vamos criar o script que deteta as colisões e dá os dados da mesma ao shader:

No nosso script temos 6 variáveis:

  • material: O material do nosso objeto, que neste caso tem que ter o shader criado anteriormente;
  • defaultFocalPoint: Esta variável é criada como a posição nula, para quando a onda termina, voltarmos a posição inicial para evitar deformações;
  • maxFocalPoints: Isto define quantas ondas permitimos ao mesmo tempo no nosso objeto, terá sempre um hard limit definido pelo shader;
  • index: Quando temos mais que uma onda, o index permite percorrer cada onda para ser desenhada, caso cheguemos ao limite definido pelo maxFocalPoints, a primeira onda será substituida pela nova onda;
  • destroyCollidedObjects: Um booleano que da a opção ao utilizar de destruir os objetos que colidem com o objeto ou não;
  • frequency: Quão rapido a onda se propaga.

Variables script

Método Start

Assim que o programa corre, recebemos o material no objeto, definimos o defaultFocalPoint para dar reset as ondas, e damos este defaultFocalPoint para o focal point de cada onda para que comece tudo sem ondas. Inicializamos também o índice a 0.

Start Method

Método Update

Durante o update, percorremos todos as ondas possíveis, crio uma variavel auxiliar para guardar a progressão da onda atual.

Depois verifico se esta progressão está compreendida entre 0 e 1, se sim, incrementamos e multiplicamos a incrementação pela frequencia e pelo Time.deltaTime para que seja constante entre dispositivos, e não depender da frame rate.

Depois atualizamos o valor da progressão da onda atual por este valor.

Caso a progressão seja maior ou igual a 1, voltamos a pôr a onda na posição inicial, e mudamos a progressão para 0 que é também o valor inicial da progressão

Update Method

Deteção de colisões:

Quando detetamos uma colisão, recebemos todos os contact points desta colisão, e onde eles ocorrerem queremos iniciar uma onda.

Para isso, usamos o InverseTransformPoint que nos dá a posição local, em relação ao transform do objeto em que o script está, do ponto de colisão. Damos este posição ao primeiro FocalPoint disponível, que no caso duma primeira colisão seria o 0.

Uso aqui também a expressão "index % maxFocalPoints" que me dará apenas o resto da divisão pelos focalPoints, garantido assim que apenas verificaremos o número de waves maximas definidas pelo script. Se por exemplo tivéssemos maxFocalPoints = 3, teríamos sempre os valores 0, 1 e 2.

Depois disto inicializo o valor da progressão a 0.001, para que o Update saiba que tem que começar a incrementar a progressão naquele nível.

Pensei também que poderia ser necessário, caso o programa corresse durante muito tempo, haver uma verificação pela valor máximo de int, caso houvesse 2147483647 colisões, mas para esta situação assumi que isto seria um caso extremamente raro, então deixei apenas comentado para que não fosse completamente descartado.

Decidi dar a opção ao utilizador se quer que os objetos colididos sejam destruídos ou não.

Collision Detection

Depois disto, para que o shader permitisse mais que uma onda, tive que criar mais variaveis:

Variables Shader

Decidi que o shader permitira um máximo de 7 ondas simultâneas:

7 Waves

Criei também uma nova esfera com mais triangulos com probuilder, para poder ter waves mais pequenas, no caso da esfera default do unity, se ficasse muito pequena, levantava apenas um vértice e ficava um conjunto de pirâmides em vez de uma onda:

Pyramide Waves

Unity Default Sphere:

Unity Sphere

ProBuild Sphere:

Probuild Sphere

Depois disso adaptei o shader a nova esfera e fiquei com este efeito com as 7 ondas (Esfera do ProBuilder à esquerda e Esferda do Unity à direita):

Demonstração do efeito: Vídeo

Demonstração do efeito com rotação: Vídeo

Efeito escudo:

Em seguida decidi que já estava farto dos tijolos, então comecei a trabalhar para fazer um efeito para o escudo, primeiro deparei-me com este tutorial:

Unity Shader Graph - Sci-Fi Barrier / Shield Tutorial - Repliquei o tutorial e percebi melhor como funcionava o ruído e os padrões procedimentais.

Depois de muitos testes e exprimentações criei o seguinte shader:

Cell Shader

Criei um node com o padrão Truchet, que recebe duas variaveis que apenas controlam o tiling e complexidade do padrão, depois multiplico este padrão por uma cor para obter a cor do escudo:

Turchet

Depois criei um node de ruído Voronoi, que recebe a variavel CellDensity que controla quantas celulas o ruído tem. Depois temos duas variaveis:

  • NoiseRotationSpeed: controla quão rapido o ruído se mexe no eixo do X ao longo do tempo;
  • CellMovementSpeed: controla a velocidade ao longo do tempo da rotação que as celulas fazem em sua volta.

Cell Noise

Por fim multiplico o padrão colorido pelo ruído e fico com o shader final:

Final Noise

Demonstração do efeito: Vídeo

Cor na onda:

De seguida queria adicionar uma cor à onda, assim para além da deformação fisica, dava uma certa "deformação" da cor.

Para isto multipliquei as ondas recebidas por uma cor que pode ser definida pelo utilizador, que por usa vez pode ser multiplicada por uma itensidade para que esta cor seja mais distinguível, e depois adiciono esta cor ao efeito previamente criado:

Final color

Intersecção:

Com isto feito, queria tentar fazer um efeito de intersecção da esfera com objetos na cena. Para isso encontrei este video:

Unity Shader Graph - Intersection Effect Tutorial

Resultado após o tutorial:

Intersection Shader

Apesar de conseguir replicar o efeito no video, não consegui aplicá-lo ao meu shader, decidi então deixar aqui como honorable mention e repliquei o efeito pretendido com duas esferas, uma com o shader de intersecão e outra com o shader de ondas:

Efeito com 2 esferas: Vídeo

No dia seguinte, por sorte, apareceu-me este vídeo, que mostrava um "museu" de shaders, e neste vídeo tinha o shader de intersecção outra vez, mas que funcionava de outra maneira: 10 Shaders in 10 Minutes - Unity Shader Graph

Com este vídeo, consegui finalmente fazer a intersecção no meu shader, ja que este recebia a base color, e aplicava o efeito por cima da base color:

Intersection shader

Primeiro comparamos a profundidade da cena, com o node scene depth este node dá-nos a profundidade de cada objeto que esta a ser renderizado, e usando a sample eye vemos esta profundidade da prespetiva da câmara.

Depois removemos a este valor a posição do objeto com o node screen position, e ficamos com um "buraco" na prespetiva da câmara, depois ao invertermos isto com um one minus node ficamos apenas com este "buraco" ficando com a posição do objeto.

A variável IntersectionDepth, permite regular o tamanho da nossa interseção, usamos o node remap, que faz a nossa escala que seria de 0 a 1, passar a ser de 1 a 0, para que faça mais sentido, pois assim ao aumentar o valor da variavel, o tamanho da interseção aumenta:

Shader intersection 1

Tudo até este ponto tinha visto no vídeo anterior, é neste novo vídeo que foi introduzida a diferença, ao invês de receber o valor alpha da cor e o inserir no campo alpha do nosso shader, que removia a cor do resto do escudo (o que fazia o efeito não funcionar, pois removia o escudo por inteiro), este shader faz um lerp, ou seja, onde for transparente, fica com a cor do escudo, caso não seja, fica a cor da interseção.

A variavel IntersectionStrenght permite-nos controlar o node power que nos dá o valor elevado a esta IntersectionStrength, que nos dá mais controlo sobre a intensidade do escudo:

Shader intersection 2

Por fim, o ruído e cor da intersecção, este ruído é extremamente parecido com o ruído do escudo em si em termos de lógica, então não acho que precise de grande explicação, tem apenas algumas variaveis para controlar quão intenso é o movimento, e quão denso o ruído é, depois, como tinha referido, tudo isto é dado a um lerp, que faz então a interpolação como referido anteriormente:

Shader intersection 3

Efeito com a intersecção final: Vídeo

O único ponto negativo é que com este shader, a cor do escudo é substituida pela da intersecção, ao invés de ficar por cima:

Efeito antigo:

Old Intersection

Novo efeito:

New Intersection

Para terminar fiz uma demo scene a mostar o shader em diferentes cenários e diferentes padrões e efeitos que se consegues com o shader:

Demo scene

Shader final com o nome ShieldCollisionEffect:

Final shader

Fazer o shader funcionar com qualquer mesh:

Até este ponto achava que estava concluído e estava a acabar o relatório, mas estava determinado a perceber porque não funcionava em mesh's diferentes de esferas, em cubos funcionava, mas mal.

Inicialmente achei que era porque os cubos têm apenas 6 vértices, e era verdade que era por isso que a deformação da mesh era estranha, porque só deformava nos cantos:

Cubo a deformar apenas nos cantos: Vídeo

Depois voltei ao meu subshader de impacto e comecei a mexer em alguns valores, e mudei a divisão para 1.5:

Shader division

Depois disto a onda ficou quase perfeita nos cantos do cubo, mas no centro não:

Cubo com onda boa nos cantos: Vídeo

Lembrei-me que nesta face do cubo, temos um quadrado e caso o cubo tivesse 1 de tamanho (que é o caso), estas seriam as medidas:

Cube size

Logo nos cantos a distância seria aproximadamente 0.7, e quando dividimos 1 por 1.5 temos 0.67, que é bastante perto de 0.7.

Isto fez-me perceber que o problema poderia estar aí, pensei inicalmente que cada mesh precisaria de uma divisão diferente, e que isto funcionaria apenas para esferas porque são as unicas com o mesmo tamanho em todas as direções.

Apesar de achar que normalizar o vetor era necessário para obter a direção, decidi remover este componente, já que esta divisão me estava a dar problemas, e só era necessária por causa da normalização, que no final de contas estava apenas a por os vetores todos com o mesmo tamanho, que não era ideal para qualquer objeto que não uma esfera.

Liguei o focal point diretamente a subtração da posição no objeto e agora funcionava em qualquer mesh:

Efeito em qualquer mesh: Vídeo

Agora tinha um novo problema:

Como visto no vídeo anterior a onda não propaga até ao final do objeto, apercebi-me que, de novo, ambos no shader de impacto e no script, eu limitava a progressão até 1, o que funcionava perfeitamente para esferas, e se quisesse o efeito a funcionar em esferas de tamanho 1, era ideal.

Mas por exemplo na estátua de cavalo do vídeo anterior com tamanho maior que 1 não iria funcionar.

Esta limitação era feita pelo node fraction que me devolvia sempre os valores decimais da progressão, logo nunca chegaria a maior que 1, e caso o valor da progressão continuasse a subir para além de 1, a onda iria repetir, removi o node fraction e aumentei o limite de progressão no script:

Removing fraction node

Removing progression limit

Com isto finalmente consegui a propagação da onda por toda a mesh:

Onda completa em qualquer mesh: Vídeo

O problema desta resolução, é que, caso um objeto seja maior que 5 vai dar problemas outra vez, mudei o máximo para 50 no script, assumindo que a maior parte dos objetos não precisariam de mais, mas é um valor alterável caso necessário, a partir do script.

Para terminar dei a opção ao utilizador de usar uma textura em vez do escudo e ruído feito anteriorimente, adicionando mais duas properidades, um booleano que indica se usamos textura ou não, e uma textura, este booleano é usado como predicado antes de serem dados os valores à Base Color, mas mantém as ondas:

Adding texture node

Resultado final: Vídeo

Shader final:

Final shader

Testei ainda com um cubo com mais polígonos para confirmar o problema que referi anteriormente, em que os cubos têm apenas 6 vértices e só deformava nos cantos, enquanto que este cubo tem mais e agora deformaria como deveria:

Cube with more triangles

Cubo com mais vértices: Vídeo

Mudar o ponto default no script:

Enquanto adicionava novas meshs à demo scene, apercebi-me de um novo problema. O ponto default para diferentes meshs poderia ser diferente, no caso do plano, caso assumissemos que o focal point "nulo" é (0, 0, 0), isto acontecia:

Plano com pico

Não arranjei solução correta para este problema, então alterei a posição default para um número bastante grande que assumi que muitos poucos modelos teriam, que, mais uma vez, caso fosse necessário, poderia ser alterado no script:

Script com default diferente

Demo scene final

Final demo scene

Conclusões finais:

Durante este trabalho ganhei bastante conhecimento sobre shaders, antes sentia que muitas coisas eram "magia" porque não percebia como funcionavam e também nao sabia sequer as possibilidade que um shader tem, por serem muitas, depois disto descobri muito do que se pode fazer, e apercebi-me quão extenso é.

Relembrei-me também que não devo tentar perceber as coisas sem as questionar, se tivesse questionado a normalização e o node fraction mais cedo teria poupado muita dor de cabeça.

Felizmente consegui obter o shader exatamente como queria, e ainda vejo que há muito espaço para expansão, como por exemplo adicionar a opção de textura no final, é possível adicionar muito mais coisas ainda, e penso continuar a trabalhar neste shader e em outros, pois acabei por gostar mais de shaders do que esperava.

Aprendi também que ao gravar os objetos como prefabs ao invés de os ter apenas na scene, reduz imenso o tamanho que a scene ocupa no ficheiro, pois ao invés de gravar os objetos na scene, grava-os como ficheiro, o que foi uma grande salvação quando a scene tinha mais de 100mb e já estava a usar o git lfs e o github não permitia mais que isto: Link para a discussão onde descobri isto

Um obrigado adicional a:

  • David Brás: por me ajudar em alguns conceitos iniciais para o shader, especialmente a compreender melhor o Vertext Displacement;
  • João Silva (a22004451): por me fornecer alguns modelos 3D feitos por ele com mais e menos polígonos para testar o shader.

Bibliografia:

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published