Aula 15 Shaders
A aula acompanhou os primeiros modulos do tuturial de rendering do catlike coding (https://catlikecoding.com/unity/tutorials/) segue abaixo as notas de aula:
Voces ja sabem o que sao meshes e como eles se posicionam numa cena. Como esse posicionamento funciona? Como os shaders sabem onde desenhar? Eh verdade que o unity cuida de todas essas funcoes para nos, mas compreender os processos em que as imagens sao desenhadas na tela eh vital para ganharmos controle sobre eles. Para isso, vamos comecar criando nossa propria implementacao.
Operacoes como mover, rotacionar e mudar o tamanho de um mesh consistem nada mais nada menos que a manipulacao de seus vertices. Transformacoes do espaco. Para melhor visualizarmos isso, precisamos primeiro tornar o espaco visivel, um jeito de cuidar disso eh utilizando um grid tridimensional de pontos, que podem consistir de qualquer tipo de prefab.
Existem inumeras transformacoes que podem ser aplicadas ao nosso grid, mas vamos nos ater apenas a 3 transformacoes basicas: posicao; rotacao; escala. Vamos criar um componente para cada tipo de transformacao que, embora diferentes, so precisam de um metodo para se aplicarem aos pontos do espaco. Usaremos, portanto, uma classe abstrata para todas as transformacoes com um metodo apply e cada transformacao que formos implementar basta herdar da classe original e alterar seu metodo.
Por motivos de simplicidade, as transformacoes sao aplicadas aos pontos individualmente utilizando suas coordenadas originais como referencia. Isso evita acumulo de transformacoes entre frames.
Nossa primeira transformacao sera a translacao: atualizar um offset local no posicionamento do grid. Basta somar a posicao original com o offset para obter a translacao.
Seguimos para a escala que eh similar a translacao, mas os valores sao multiplicados ao inves de somados.
Notem que ao mudar escala quando a posicao possui offset tambem causa alteracoes na posicao dos pontos. Isso acontece pois estamos primeiro realizando uma translacao para depois escalarmos. O unity aborta essas transformacoes de forma muito mais conveniente, na ordem inversa e, para arrumarmos isso, basta mudar a posicao dos componentes no inspector.
Por fim, vamos para a rotacao, que, como voces ja devem imaginar, eh um pouco mais complexa que as duas transformacoes anteriores.
Como funciona rotacao entao? Se nos limitarmos a rotacoes de apenas um eixo, o eixo Z, rotacionar um ponto equivale a uma roda girando. Por convencao, adota-se um sistema anti-horario para rotacoes, regra da mao direita. E o que acontece quando os pontos rotacionam? A maneira mais facil de abordar essa situacao eh usando um circulo unitario, famoso das aulas de trigonometria, e, aproveitando a deixa, rotacionaremos tudo em 90 graus, ou pi/2 para manter as coordenadas dos pontos em x e y alternando entre -1, 0 e 1. Sempre, a cada iteracao, se uma coordenada vale 1 ela passara a valer 0, se ela valia 0 passara a -1, se valia -1 passa pra 0 e se valia 0 passa pra 1, nesta ordem. E, se pegarmos uma roda, colocarmos canetas nos pontos dos eixos x e y e rotacionarmos essa roda por uma parede, poderemos obter um grafico muito simples de interpretar. Senos no eixo x e cossenos no eixo y.
E em resumo isso eh o mesmo que normalizar os pontos pro circulo, rotacionar eles e devolver os pontos pra sua distancia radial original.
A rotacao deve ser inserida entre a translacao e a escala.
Licao de casa 1: fazer o full rotations
Finalmente, assunto da aula: Shaders. Ou talvez nao... primeiro vamos discutir um pouco sobre a essencia de toda renderizacao, isso mesmo, a luz. Abrindo o painel de iluminacao do unity podemos ver uma serie de configuracoes para a luz. A primeira delas, skybox serve para calculos de iluminacao ambiente, voces verao mais detalhes sobre isso na aula de post processing (eu espero). Vamos deletar a skybox, pois nao queremos nenhuma iluminacao ambiente atrapalhando nossos trabalhos. Em seguda, vamos tambem desativar a luz direcional. Pronto, agora podemos ver perfeitamente o que esta acontecendo na cena, primeiro a tela eh preenchida com uma cor solida de fundo e em seguida a silhueta da esfera eh desenhada por cima. Mas como o unity sabe que ele precisa desenhar uma esfera, ou, mais precisamente, nesse caso, um circulo? A resposta curta eh: ela nao sabe. Mas nossa esfera contem um componente chamado mesh renderer, esse componente eh responsavel verificar se o volume dos objetos estao contidos na visao da camera e renderizar eles de acordo com a posicao dos seus vertices. Na primeira etapa da aula vimos como o componente Transform interage com objetos e o espaco. Se um objeto se encontra dentro da visao da camera, entao, o unity manda para a GPU cuidar dos desenhos na tela.
vale ressaltar que o mesh e material na CPU representam as informacoes a respeito do mesh e material. O shader, que eh um programa especial que roda nas GPUs, pega esses dados e converte nas informacoes que vao para a tela.
Vamos entao para a escrita do nosso primeiro shader, do zero. Criem um novo Unlit Shader e deletem tudo que tem no arquivo. Salvem. O unity vai alegar que tem erros no projeto, mas nao importa. Mesmo com os erros podemos criar um novo material com esse shader e adicionar esse material na nossa esfera, que agora tem um tom solido rosa choque. Se olharmos a mensagem de erro veremos que a incompatiblidade do shader se da pela ausencia de subshaders. Nada mais natural que comecarmos adicionando um. Subshaders servem para agrupar diferentes variantes de um mesmo shader, permitindo diferentes configuracoes de acordo com a plataforma que ele sera executado ou os niveis de detalhes exigidos. Cada subshader exige ao menos um passe, os passes definem quando a GPU deve desenhar algo na tela e como.
Assim que adicionamos o passe podemos ver que os erros sumiram e agora nossa esfera esta branca solida.
Com o shader funcionando, vamos iniciar a escrita do programa. O unity utiliza sua propria linguagem de shaders que eh uma variante da HLSL. Para indicar inicio e fim do codigo usamos CGPROGRAM e ENDCG full caps. A sintaxe para programas de computacao grafica tem alguns detalhes diferentes da programacao tradicional e nao raramente vamos encontrar algumas convencoes pouco usuais em computacao. E novamente quebramos nosso shader. Se observarmos os erros, podemos ver que falta os programas Vertex e Fragment, que sao responsaveis pelo processamento dos vertices do mesh e suas posicoes no espaco (igual vimos na parte 1); e colorir os pixels que se encontram dentro de cada triangulo do mesh. Adicionamos entao os #pragma, em minusculas, vertex e fragment e, novamente, o unity reclama que os metodos declarados nao possuem implementacao. Vamos, entao, implementa-los.
Vertex e Fragment sao escritos como metodos, funcoes, muito similar a sintaxe do C#. Uma vez implementados agora nao tem mais erros mas nossa esfera sumiu, ou ainda tem erros pq depende de qual plataforma grafica esta sendo utilizada no computador (Direct3D 9 provavelmente dara erros). Isso acontece pois o compilador de shaders transforma nossos programas de acordo com a plataforma, cada uma com sua propria solucao (Direct3D, OpenGL) nao estamos lidando apenas com um simples compilador mas varios.
Os resultados podem variar muito entre as diferentes plataformas entao eh sempre bom estudar previamente sua plataforma alvo ao codar shaders. O unity permite compilar e mostrar o codigo compilado para analises de eventuais erros, muito util (se voce souber ler). Eh possivel tambem selecionar quais plataformas voce deseja compilar manualmente.
Para ter um shader funcional, precisamos de muitas linhas de codigo, definicao de variaveis, funcoes, entre outros. Em programacao tradicional, poderiamos simplesmente segmentar em diferentes classes e lidar somente com as informacoes necessarias a cada momento. Infelizmente shaders nao possuem classes. Eh tudo um bloco enorme de codigo, sem os agrupamentos das classes.
Por sorte (e muito amor de deus), eh possivel segmentar o codigo em diferentes arquivos usando a diretiva #include
Licao de casa 2: terminar os modulos de shader, e dar uma olhada em custom render pipeline