Goshido / android-vulkan

This repository is a project for learning Vulkan API, constraint based 3D physics, Lua scripting, spatial sound rendering, HTML+CSS UI rendering.
30 stars 3 forks source link

Incorrect point light calculations #59

Closed Goshido closed 1 year ago

Goshido commented 1 year ago

It was found that point light pass produces INF values. It's needed to fix.

issue-001

issue-002

Steps

Goshido commented 1 year ago

I found that reference implementation does not follow rule of conservation of energy. Long story short I found the case when point light with brightness of 11 units produces specular reflection with brightness of 26 units. You could validate it by plug in the following numbers into implementation:

albedo = (1.0, 1.0, 1.0, 1.0)
viewer location = (0.0, 0.0, 0.0)
fragment location = (45.0901, 1.4374, 34.7226)
fragment normal = (-0.957000, 0.014640, 0.288080)
light location = (23.59422, 4.62833, 88.95494)
light intensity = 11.31312
roughness = 0.2
metallic = 1.0

specular part will be (26.767, 26.767, 26.767) which is impossible. Result brightness is bigger that light source brightness. The fragment IS NOT LOCATED closer that 1 unit from the source. So attenuation is fine.

If you calculate all in float16 types you even get INF values.

Goshido commented 1 year ago

I was suggested to try this old paper from 2012. I saw this before. I gonna give it second chance.

Goshido commented 1 year ago

The problem source: BRDF by design should be used with integration math. Calculation of the direct value from viewer direction, fragment normal and light position does not make much sense. D and G component is a normal-like distributions. There are peaks at infinity with extreme input values.

image

The volume under the distribution graph makes real sense.

Of course direct integration per each fragment is too computation heavy for runtime. The solution is pre-integrate D and GGXShick terms as LUT table. Such LUT will be used in runtime at directional lighting stage.

Goshido commented 1 year ago

After some research and asking in "Graphics Programming" Discord server I found solution. All materials could be found in questions-forum category. The name Cook-Torrance don't conserve energy for direct lighting

So it turns out that only problem is in D term. There are several general solutions and combinations:

  1. clamping roughness
  2. clamping to 0-1 range
  3. clamping final energy to be not greater than light source

The clamped graph of D looks like this

image

In case of G term turns out it's already in range of 0-1:

image

G - term, roughness 0.000, max 1.000
G - term, roughness 0.143, max 1.000
G - term, roughness 0.286, max 1.000
G - term, roughness 0.429, max 1.000
G - term, roughness 0.571, max 1.000
G - term, roughness 0.714, max 1.000
G - term, roughness 0.857, max 1.000
G - term, roughness 1.000, max 1.000

After clamping D term all INF values gone from HDR image. And there are no any pixel which will be brighter than light source. My last concern is only about discontinuity. It could introduce new artifacts on different scenes.

But result is good enough at this scene.

image

Idea about LUT and pre-integrating is incorrect for point light source. It's still valid to directly plug values into BRDF equation and get final pixel color without any loops.