Iluminação Básica - Phong Shading
Home XNA Shaders Iluminação Básica - Phong Shading
Olá, este segundo tutorial irá mostrar um pouco sobre Phong Shading. Inicialmente farei uma leve introdução teórica descrevendo um pouco do modelo matemático usado e em seguida o implementarei usando shaders.
Diferente do primeiro tutorial, desta vez o artigo focará mais no Shader e não no XNA. Usarei alguns módulos da PloobsEngine (engine 3D para XNA que desenvolvo com amigos) para auxiliar em tarefas como manipulação de câmeras e de telas. Não usarei NENHUMA funcionalidade gráfica da engine, toda a parte de shader será implementada do zero. Como pré requisito, aconselho que o pessoal tenha lido o primeiro tutorial da série.
Teoria – Phong Lighting
Esta técnica para o cálculo de iluminação foi desenvolvida por Bui Tuong Phong na universidade de Utah em 1973. Na época de sua concepção ela foi considerada radical (pois era simples demais comparado com os modelos da época), entretanto hoje é o mais utilizado em aplicações de tempo real (o modelo originalmente proposto foi sendo alterado no decorrer dos anos).
O modelo Phong é essencialmente uma aproximação empírica de um sistema de iluminação local (sistemas que consideram apenas a luz que incide diretamente nos objetos). Ele descreve como uma superfície reflete a luz, sendo uma combinação da reflexão difusa, da reflexão especular e da reflexão ambiente. Para uma explicação mais física (incluindo conceitos de Irradiancia, radiancia ... ) recomendo fortemente a segunda parte do livro Principles of Digital Image Synthesis que foi liberado FREE na net. A primeira parte do livro é um excelente resumo da matemática e física usada na computação gráfica. A leitura é extremamente densa, mas vale a pena!!! Um dos melhores livros na área :).
Abaixo temos uma figura que mostra a combinação das reflexões para a geração da imagem final.
Matematicamente, o modelo de Phong é quebrado nas três componentes aditivas citadas acima conforme mostra a equação a seguir:
Intensidade da Cor Final = Ia + Id + Is
Ia = Componente ambiente
Id = Componente difusa
Is = Componente especular
A seguir discutirei como obter cada um destes componentes. Lembrar que cada uma das variáveis mostradas (como La) na verdade é um Vetor/Cor (r,g,b). Vale ressaltar que não há uma modelagem padrão 100% aceita para o phong shading, usarei uma versão que prioriza o entendimento (obviamente não esta otimizada).
O primeiro fator corresponde ao que chamamos de iluminação indireta (luz que é refletida diversas vezes no ambiente e chega até o objeto em questão). Ela é necessária, pois alguns objetos não são iluminados diretamente por nenhuma fonte de luz e mesmo assim são vistos.
Modelos de iluminação de tempo real não conseguem simular este fator de maneira apropriada (alta complexidade / alto custo computacional), então se usa uma constante Ka (solução bastante conveniente :) ) para representá-lo.
Ka constante de luz ambiente (Definido pelo usuário, Propriedade do material/luz)
Iambiente é a cor do objeto (Definido pelo usuário, Propriedade do material)
Corresponde à luz que o objeto reflete igualmente em todas as direções.
A reflexão é calculada segundo a lei de Lambert que infere que a energia que reflete de uma superfície é proporcional ao cosseno do ângulo entre a direção da luz e à normal da superfície. Equacionando temos:
Idiffuse representa a intensidade da cor difusa do objeto (definida pelo usuário,propriedade do material - existem alguns livros que consideram apenas a intensidade da luz (Ilight, apresentado a seguir) e uma constante Kd (apresentado a seguir também) na equação da luz difusa, eu preferi uma abordagem que separa a cor do objeto da constante Kd por ser mais fácil de compreender ).
Kd representa o coeficiente de reflexão difusa da superfície. (definida pelo usuário, propriedade do material/luz)
Alpha é o ângulo entre a normal do objeto no ponto em questão e a direção da luz. (Propriedade da cena). Notar que o vetor Direção da Luz aponta para o objeto e a Normal aponta para fora.
Costumamos reduzir o cosseno acima a um produto escalar (já que a normal e o vetor de direção da luz são unitários), desta forma a equação resultante é:
N sendo o vetor normal no ponto em questão da superfície
L o vetor de incidência da luz (invertido, ou seja, apontando para a luz ao invés de apontar para o objeto)
Normalmente Adicionamos um fator a mais nesta equação chamado de ILuz que representa a cor da luz que esta iluminando o objeto (as vezes não representamos ILuz na equação pois consideramos a cor da luz como branca (r=1,g=1,b=1) ).
A figura a seguir mostra um objeto sendo iluminado por uma fonte de luz com apenas componente difuso (variando a direção):
O terceiro componente da equação do modelo Phong é resultado de uma característica comum a todos os objetos reflexivos, que são pequenas regiões de brilho mais intenso com tamanho maior ou menor dependendo do tipo de material. Esse efeito é dependente da posição do observador.
O fator especular é meramente a reflexão da luz projetada na superfície do objeto. O modelo Phong não reflete fielmente o formato da luz, pois considera que todos os pontos de luz são representadas por formas cônicas. Apesar da simplificação nota-se que o efeito gerado é suficiente para "enganar" a percepção humana.
A figura abaixo mostra os vetores envolvidos e a equação deste fator:
Ks é uma constante especular do material (Definida pelo usuario, propriedade do material/luz)
ILight é a intensidade do raio especular que atinge o objeto (definido pelo usuario, propriedade do material)
n (que esta elevando o cos)é chamado de Specular Power e controla o "tamanho" do brilho especular. (definido pelo usuario, propriedade do material). Quanto maior o valor deste parametro menor será a região de brilho gerada.
Beta é o ângulo entre o raio refletido R e o Vetor Eye V (vetor entre observador e o ponto iluminado, apontando para o observador) (Propriedade da cena)
Uma maneira usual de calcular este ângulo é usando o que chamamos de Half Vector. O metodo é mais rapido do que calcular o Refletion, quem quiser saber mais sobre o assunto dê uma olhada aqui.
Como vimos, a equação de Phong é dada por:
Substituindo cada um dos componentes vistos temos:
Os parâmetros N,L,R e Eye provêem da Camera, Da Luz e da Geometria do Objeto, não são definidos diretamente pelo usuário.
Os parâmetros Ka,Kd,Ks,Iambiente,Idiffuse,Ispecular, Ilight e n são definidos pelo usuário (propriedades do material e da luz) e nos shaders são chamados de:
Ka: Ambient Intensity
Kd: Diffuse Intensity
Ks: Specular Intensity
n: Specular Power
Iambiente: Ambient Color
Idiffuse: DiffuseColor
Ispecular: Specular Color
Ilight: Light Color
Usarei esta nomenclatura acima no shader desenvolvido.
Entendendo e usando a Equação
Para iluminar uma cena devemos, para cada ponto da superfície de cada uns dos objetos, calcular esta equação. (O valor que aparecera no monitor é a saída desta equação)
Em uma cena normalmente temos diversas luzes, a cor final de um objeto é obtida através da soma dos efeitos de cada uma das luzes individualmente.
Conforme mostrado, o modelo de Phong é um sistema de iluminação local, pois a intensidade de um ponto depende apenas dele, não precisamos de informações de pontos vizinhos (modelos de iluminação como Radiosity e Raytracing precisam).
Existem diversos tipos de luzes, as mais comuns são: Point, Directional e Spot. Elas se diferenciam principalmente quanto ao alcance (por exemplo: uma point light tem um raio de alcance igual ao de uma esfera) e quanto à atenuação (Ka,Kd,Ks). (Por exemplo, uma point light é mais intensa no centro e menos nas bordas).
Para este tutorial, implementaremos uma Directional Light apenas, sua atenuação é constante em toda a cena e o seu raio de alcance é global (afeta todos os objetos da cena). Matematicamente, Ka,Kd e Ks não variam (apesar de serem chamadas de constantes, elas variam no caso de Point Lights ... muitas pessoas preferem criar uma nova variável chamada LightAttenuation para este propósito) e todos os objetos são afetados pela equação.
Implementação - Shader
A conversão entre um modelo matemático para shaders não é sempre uma tarefa trivial. Devemos entender como que as equações funcionam e encaixá-las na arquitetura da GPU. No caso de Phong Shading este processo é bastante simples.
Conforme visto, temos uma equação que calcula a cor final de um ponto de um objeto. Em shaders "ponto" significa pixel gerado na rasterização do modelo. Desta forma, calcularemos a equação de Phong para cada pixel de cada objeto do mundo 3D.
A seguir descreverei o shader responsável por implementar o Phong Shading.
As constantes necessárias para este shader são:
float3       LightDirection;
float4        LightColor;
float3       camPosition;
float4        SpecularColor;
float        SpecularPower;
float        SpecularIntensity;
float4       AmbientColor;
float        AmbientIntensity;
float        DiffuseIntensity;
texture      DiffuseTexture;
Estes parâmetros são definidos pelo usuário, alguns diretamente como a SpecularPower e outros indiretamente como a camPosition.
A entrada do vertex shader é bastante simples e recebe apenas a posição, coordenada de textura e normal de cada vértice, conforme mostrado a seguir:
A saída contém os dados da entrada transformados mais o nosso Eye Vector:
float3 N                : TEXCOORD0;
float2 TextureCoord     : TEXCOORD1;
float3 V                : TEXCOORD2;
O vertex Shader transforma os dados do vértice e calcula o Eye Vector:
output.V = camPosition -  worldPosition;
O Eye Vector é o vetor entre a posição da camera e o vértice atual que estamos processando.
Todo o cálculo da equação de Phong é feito no pixel shader (pode-se fazer no Vertex Shader, mas a aparência não fica legal – ver Flat Shading):
float3 LightDir = -normalize(LightDirection);
// I = Dc*A + Dcolor * Dintensity * N.L + Sintensity * Scolor * (R.V)n
return AmbientColor*AmbientIntensity + LightColor * DiffuseIntensity *    diffuseColor * Diff + SpecularIntensity * SpecularColor * Specular;
Inicialmente normalizam-se os vetores (o Rasterizer não garante que os vetores sairão normalizados após a rasterização). Estou negando o vetor LightDirection, pois, conforme dito anteriormente, precisamos do vetor apontando do objeto para a luz e não o contrario.
Após as normalizações, leio a cor difusa da textura associada e armazeno na variável difuseColor. (usando a função intrínsica do HLSL text2D, que recebe um TextureSampler e uma coordenada de textura, conforme visto no primeiro artigo da série.
O cálculo da equação de phong começa em:
A operação dot efetua um produto escalar, operação básica em computação gráfica que consiste em multiplicar cada elemento do primeiro vetor pelo elemento correspondente do segundo vetor e depois somar os valores obtidos. No nosso caso esta operação esta calculando o Cosseno entre os vetores Normal e LightDir.
A operação saturate garante que o valor passado como argumento esteja entre 0 e 1.
Calcula o reflection vetor. Para entender o porquê de esta equação funcionar sugiro este livro (Algebra Linear ).
Que implementa a parte . (saturate usado para garantir que o Cos Teta permanece no intervalo 0-1)
Por fim temos a implementação da equação completa:
return AmbientColor*AmbientIntensity + LightColor * DiffuseIntensity * diffuseColor * Diff + SpecularIntensity * SpecularColor * Specular;
Como vimos, este shader é bastante simples e o resultado obtido é interessante (veja as Screenshots no fim do artigo).
A implementação vista é didática porém pouco prática. Em um universo mais real temos várias luzes de vários tipos (spot, directional ...) afetando cada uns dos objetos.
Para atender estas necessidades existem algumas "arquiteturas" de sistema de iluminação. As mais usadas são: Single Pass Multi-Lightning e Multiple Pass Multi-Lightning para o caso de Forward Rendering e Deferred Shading e Inferred Shading para o caso de Deferred Render. (A PloobsEngine suporta nativamente Deferred Shading e Single Pass Multi-Lightning)
Implementação - XNA
No Lado XNA as coisas mudam um pouco em relação ao último tutorial, pois utilizarei a PloobsEngine ao invés de XNA puro.
A seguir farei um Mini Pseudo tutorial da PloobsEngine para o pessoal poder ler/entender o código. Estarei usando a versão 0.3 da engine (que ainda está em fase de testes e não está disponível como public Release, apenas como código fonte).
Todos os arquivos necessários para rodar/compilar o tutorial estão no pacote disponível para download no fim do artigo (é só abrir, compilar e rodar :)).
Para os propósitos deste tutorial podemos ver a PloobsEngine como o diagrama de classes a seguir:
Figura 1: Diagrama de Classes Simplificado (Todos as classes que iniciam com Ixxxx São interfaces ou classes abstratas)
A EngineStuff é o nosso "Entry Point", devemos inicialmente criá-lo e configurá-lo. Em seguida devemos implementar a interface IScene para criar a nossa cena 3D. Essa IScene contem um IWorld que não passa de um Container de IObjects (e outras coisas não mostradas como Partículas, triggers ...) e uma IRenderTechnic que é responsável por desenhar os objetos na tela.
Para este exemplo usaremos a ForwardTechnic. Um amigo que participa do desenvolvimento da engine fez dois pequenos artigos introdutórios sobre iluminação 3D, vale a pena dar uma olhada e entender o que significa Forward Rendering e Deferred Rendering.
Cada IObject contém um IModelo (container de informações geométricas e de texturas), um IPhysicObject (representação física do objeto) e um IMaterial (container de shaders usados na renderizacao do objeto em questão).
A engine provê diversas implementações para cada uma das interfaces discutidas, para o nosso caso devemos apenas extender a classe IShader e implementar os métodos MaterialType, Initialize e Draw. Quem estiver interessado em entender a arquitetura da engine com mais profundidade sugiro nossa página de documentação online ou o documento offline. (Lembrar que os documentos são referentes à versão 0.2 Xna 3.1)
O exemplo criado para este tutorial contém:
Três Modelos da deusa grega Venus, o primeiro usa apenas uma textura branca sem iluminação. O segundo utiliza apenas iluminação, e o último utiliza uma textura vermelha e iluminação.
Dois caminhões Low-Poly, um com apenas textura e outro com textura e iluminação.
Por Default, a direção da luz Direcional move circularmente ao redor da cena (como nosso sol), o usuário pode pausar esta funcionalidade usando alguns controles (A demo contem uma tela explicando quais são e como funcionam).
Como Bônus =P eu coloquei um Bloom Effect que pode ser ativado também pelo controle.
A PloobsEngine utiliza Deferred Rendering com Phong Shading internamente e suporta Spot, Directional e Point Light com sombra dinâmica.
Quem estiver interessado em conhecer um sistema de iluminação mais "real" com diversos tipos de luzes aconselho dar uma olhada no código fonte da Engine (Versao 0.2 acompanha uma documentação para iniciantes). Qualquer problema referente a engine entre em contato com a gente pelo nosso fórum.
Qualquer duvida em relação ao artigo deixe uma mensagem abaixo!
Planejo fazer o próximo tutorial sobre PostEffects, mas ainda esta em aberto. Sugestões são sempre bem vindas
O código fonte e projeto exemplo podem ser baixados clicando-se na figura abaixo:
27/04/2011 13:40:58 | Bruno Crivelari Sanches
Já que esta em aberto, que tal sobre point lights, spot lights e atenuação ??
27/04/2011 14:10:13 | Jonathan Araujo
Bem dessa vez nao tenho dúvidas...
Vo implementa em alguns do meus projetos e de Ogre e depois posto as duvidas/resultados.
Sugestão de próximo artigo: Bump, Normal, Diffuse, ou Specular Mapping
29/04/2011 01:53:42 | Thiago Dias Pastor
Acho q combinarei as duas ideias.
Falarei sobre Point e SpotLight usando Normal e Specular mapping, os topicos combinam.
Provavelmente ficara meio grande.
02/05/2011 07:49:12 | Jonathan Araujo
Ou também pode fazer pequenos sobre cada tópico, veja o que você achar melhor.
Uma dúvida eu que não achei ou faltou o download?
02/05/2011 16:00:53 | Thiago Dias Pastor
Ops, faltou mesmo, irei atualizar o site, enquanto isso vai um link provisorio http://dl.dropbox. com/u/45309/Tutorial1.rar
02/05/2011 16:17:21 | Bruno Crivelari Sanches
Atualizei o artigo com o link para download direto do ftp do PontoV.
02/05/2011 16:18:07 | Thiago Dias Pastor
12/05/2011 03:40:48 | Jonathan Araujo
Tentei rodar o projeto disponivel para download e não consegui.
Sou totalmente noob em XNA...
provavelmente deve ser algum include dir ou lib q ta faltando...
ele da erro em todos using que tem using PloobsEngine
15/05/2011 16:31:52 | Thiago Dias Pastor - ploobs.com.br
Voce tem que ter instalado o Visual Studio 2010 e o XNA framework 4.0, conforme o post do kleber http://www.pontov.com.br/site/index.php/xna/53-xna/259-conc eitos-e-instalacao-do-xna-40 (se puder instale o Visual Studio Completo)
Veja se a PloobsEngineDebug.dll esta adicionada no references do seu projeto conforme a imagem [url]https://picasaweb.google.com/lh/photo/VkXXhvxhy-eppqhU7QCN50R0dNY yuUvYIRwPA-arf8s?feat=directlink[/url]
Se nao funcionar, mande um screenshot do visual studio que mostre o erro. Se preferir use o nosso forum ploobs.com.br/forum
Testei o arquivo q ta para downlaod e aq funcionou.
29/06/2013 02:09:15 |177.207.124.xxx| Fred - Como implementar o mesmo código sem usar a engine
Fica mais didático a implementação usando o XNA puro, já que as pessoas poderão implementa-lo em qualquer máquina não ficando dependentes dessa egine ainda em desenvolvimento.
29/06/2013 12:14:20 |189.101.67.xxx| ViniGodoy
O foco desse tutorial são os shaders, que são idênticos seja na engine, direto no XNA ou no DirectX.
Sem a engine, haveria muito texto para mostrar como carregar os modelos, controlar a câmera, criar buffers, coisa que iria acabar tornando o tutorial mais longo e confuso.
Quanto a limitação da máquina, a Engine também se baseia exclusivamente em XNA, portanto, pode ser usada nas mesmas plataformas que o XNA usa. A engine é de autoria do autor do artigo.
Se você quiser seguir um bom tutorial sobre shaders em XNA, leia:
http://www.riemers.net/eng/tutorials.php
30/06/2013 15:14:33 |186.212.130.xxx| Fred
muito obrigado pela indicação. Me desculpa por ter falado da engine. Mas estava querendo aprender do zero mesmo. Na verdade meu propósito e aprender programação gráfica. Ou seja, ainda não tenho muito conhecimento da área, muito menos de XNA, por isso pensei que se fazendo com engine eu iria aprender a mexer na engine e não no xna. Mas agora entendi o propósito do artigo.
Agradeço pela indicação do tutorial. E mais uma vez me desculpe a mensagem anterior.
01/07/2013 18:43:52 |186.212.130.xxx| Fred - re:
eu tentei acessar o site que vc indicou na mensagem acima, mas não consegui achar muita coisa nele. Naveguei pelo menu a direita e só achei links quebrados e descrição de tutoriais mas nem um tutorial de fato.