Depois de programar o Ray Tracer para que ele pegue a informação e cor de onde o raio bate, vamos aplicar a verdadeira, legítima equação de rendring para gerarmos imagens muito mais realistas, já que vamos considerar a refletância e a emitância, assim a iluminação virá naturalmente. No fim, teremos uma cena gerada simulando os "raios" de luz da vida real.
A primeira etapa é programar o pixel sampling, que significa atirar vários raios de um mesmo pixel, com cada raio saindo com direções levemente diferentes entre si. O tipo de pixel sampling utilizado foi o uniforme: cada raios sai com um offset aleatório máximo de ±0.5 pixel em x e y em relação ao centro do pixel. Com isso, a imagem gerada na versão anterior já vem mais bela pois o pixel sampling reduz o aliasing:
![]() |
| 1 sample por pixel(utilizando offset aleatorio) vs. vários samples por pixel. Número de samples perdidos no tempo. Desconsidere a compressão sebosa. |
Mas isso é apenas o primeiro passo. Antes de continuar vamos dar uma investigada na equação de rendering:
| Fonte: slides do Prof. Christian |
A equação nos diz que a radiância(energia luminosa) de um raio de luz que sai de uma pequena superfície(representado na equação como Lo) é igual à soma da radiância emitida(Le) por essa mesma superfície mais o somatório de todas as radiâncias dos raios de luz que chegam nessa superfície(Li).
A radiância emitida, ou emitância, pode ser entendida como a quantidade de luz que a superfície emite. Uma superfície que emite luz poderia ser, por exemplo, o bulbo de uma lâmpada acesa ou o Sol. A maioria das superfícies não emite nenhum tipo de luz, por isso as coisas são pretas no escuro(quando não há nenhuma fonte de luz que gere "raios" que podem refletir de superfícies).
A radiância incidente é a radiância que vem de outras direções(Ω): pode ser diretamente de uma fonte de luz ou pode ter sido refletida diversas vezes até chegar no ponto em questão.
No entanto, a radiância incidente depende também do material da superfície(como este reflete/absorve a luz) e também do ângulo de incidência do raio(θ, quanto mais perpendicular o raio de luz da superfície, menor fluxo radiante, e assim, menor a radiância).
O material da superfície é definido e representado pelo fr(ωi, ωr), que é uma função de descreme como raios vindos de uma direção ωi é refletido para uma direção ωr. Cada material possui uma função fr(), chamada mais popularmente de BRDF. É uma descrição matemática de como o material reflete luz.
O cálculo dessa integral é feito através do método de Monte Carlo, um método que utiliza probabilidades. Na nossa implementação, só tomamos um raio incidente por parte de superficie, o que facilita as contas e implementação.
Como o Ray Tracer funciona jogando raios da câmera, é mais simples ter apenas um raio incidente por superfície. Quando um raios é jogado, calcula-se o Lo pegando a emitância do material de onde o raio interseccionou e somamos com a radiância do raio incidente. Já que o Ray Tracer funciona "ao contrário", geramos um raio saindo desse ponto de interseção a uma direção aleatória e pegamos o Lo dele. Notamos que aqui já entramos em recursão. Para não simularmos os raios refletindo infinitamente(e em consequência rodarmos um programa sem fim) definimos um número máximo de vezes(max depth) que um raio saindo da câmera pode refletir nas superfícies. Cada vez que um raio é refletido, há uma possibilidade que ele chegue à fonte de luz, então aquele ponto é iluminado e suas informações de cor são calculadas através de sua BRDF. Se um raios refletir o numero máximo de vezes e não chegar em uma fonte de luz, a radiância daquele raio será nula.
Como o Ray Tracer funciona jogando raios da câmera, é mais simples ter apenas um raio incidente por superfície. Quando um raios é jogado, calcula-se o Lo pegando a emitância do material de onde o raio interseccionou e somamos com a radiância do raio incidente. Já que o Ray Tracer funciona "ao contrário", geramos um raio saindo desse ponto de interseção a uma direção aleatória e pegamos o Lo dele. Notamos que aqui já entramos em recursão. Para não simularmos os raios refletindo infinitamente(e em consequência rodarmos um programa sem fim) definimos um número máximo de vezes(max depth) que um raio saindo da câmera pode refletir nas superfícies. Cada vez que um raio é refletido, há uma possibilidade que ele chegue à fonte de luz, então aquele ponto é iluminado e suas informações de cor são calculadas através de sua BRDF. Se um raios refletir o numero máximo de vezes e não chegar em uma fonte de luz, a radiância daquele raio será nula.
É nesse ponto que vemos a importância do pixel sampling: A chance de um raio chegar em uma fonte de luz pode ser baixa, então vários raios de luz são jogado por pixel, e no final, quando todos os raios já tiverem terminado de refletir na cena, a cor do pixel será uma média das radiâncias de cada raio. Quanto mais samples por pixel, mais provável será que algum raio daquele pixel chegará na luz, gerando uma imagem proporcionalmente mais realista.
O algoritmo, em pseudo linguagem, da equação de rendering, retirados dos slides do Prof. Christian:
| Essa parte joga os raios da câmera já com o offset. |
Essa é a parte recursiva: o raio é gerado e o Lo é somado. O 2*PI é multiplicado pois faz parte do integrador de Monte Carlo.
|
Utilizando como cena uma caixa estilo Cornell box, geramos as seguintes imagens, com 64, 256 e 2048 samples por pixel, respectivamente. Todas elas tem um max depth 5.
![]() |
| 🔝🔝🔝🔝🔝 |
Tivemos alguns problemas misteriosos que no final das contas eram uma má implementação da câmera e dos algoritmos dos triângulos. Resolvidos estes, o programa funcionou com toda sua elegância.
Para melhorar os resultados, além de aumentar o número de samples por pixel e o max depth, podemos implementar roleta russa, e para facilitar a configuração de cena, implementar a importação de objetos já com as informações de refletância e emitância.
Álbuns da vez(esse foi longo)





