WIP: EEVEE-Next: Add light spread support #119574

Draft
Clément Foucault wants to merge 12 commits from fclem/blender:eevee-next-lightspread into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.

This is just a proof of concept / weekend experiment for
now.

Test file:
https://projects.blender.org/attachments/8115fdff-bf45-47e2-a0b7-cbc0cb7480c5

Results

Note that these are preleminary results.

Cycles EEVEE

This has energy problem as the cycles version does
focuses the light to keep the light power constant.
This version only masks the lighting. Which still
gives (somewhat) correct lighting intensity around
the light but it decays too fast.
However, this raises question about the bounds of the
light: Should they be increased to account for the
focussing? Should we also cull them better in the
light culling phase?

Generalizations

Supporting rectangle area light (and square) should
be relatively simple (with just 2 circular segments
computation).

Ellipses on the other hand might be more difficult.
An idea is to compute the spherical segment on the
unstretched ellipse (because areas are proportional
under 2D scaling), but this has still many cases
to consider and we still need to compute the angle
of the .
Another idea is to approximate the ellipse using
another shape. A 2D bounding capsule can converge
to the disk result if both sides are equal but can
be also more difficult to compute. A bounding 2D
box is going exhibit masking inconsistent with
respect to the light shape.
Yet another idea is to replace the ellipse by
a well chosen disk that is placed to minimise the
difference with the real result.

For now no solution appear clearly easier/better.

Performance

The two acos are quite expensive. We might want
to have a more simpler approximation.
I already approximated the circular segment area
computation using smoothstep function in the overlay
antialiasing and it proved quite good in practice
(see [2]). The difference here is that we might need
a slightly more precise expression, and that we
need to run it twice.

TODOs

  • Disk implementation
  • Rectangle implementation
  • Ellipse approximation
  • smoothstep optimization
  • Evaluate other optimizations
  • Correct focusing term
  • Cleanup codestyle
  • Use UI value instead of hardcoded value
  • (optional) Add better culling

References

I added a link to the old cycles task [1] written by @weizhen
which has nice diagrams and describes the problem in
great detail.

[3], [4] and [5] are references about the area calculation we need to do.

[6] show the approximated calculation of the circular segment area.

[1] https://archive.blender.org/developer/D16694
[2] https://projects.blender.org/blender/blender/src/branch/main/source/blender/draw/engines/overlay/shaders/overlay_antialiasing_frag.glsl
[3] https://mathworld.wolfram.com/CircularSegment.html
[4] https://mathworld.wolfram.com/Circle-CircleIntersection.html
[5] https://math.stackexchange.com/questions/458523/how-to-find-the-area-of-a-segment-of-an-ellipse
[6] https://www.desmos.com/calculator/vbfy0msins

This is just a proof of concept / weekend experiment for now. Test file: https://projects.blender.org/attachments/8115fdff-bf45-47e2-a0b7-cbc0cb7480c5 ### Results Note that these are preleminary results. | Cycles | EEVEE | | -------- | -------- | | ![](https://projects.blender.org/attachments/f4b6dfd0-d9d9-43d2-a0d7-f719b3a9cf10)| ![](https://projects.blender.org/attachments/bbce2b89-e077-4e25-b1db-51cdbe83a631)| This has energy problem as the cycles version does focuses the light to keep the light power constant. This version only masks the lighting. Which still gives (somewhat) correct lighting intensity around the light but it decays too fast. However, this raises question about the bounds of the light: Should they be increased to account for the focussing? Should we also cull them better in the light culling phase? ### Generalizations Supporting rectangle area light (and square) should be relatively simple (with just 2 circular segments computation). Ellipses on the other hand might be more difficult. An idea is to compute the spherical segment on the unstretched ellipse (because areas are proportional under 2D scaling), but this has still many cases to consider and we still need to compute the angle of the . Another idea is to approximate the ellipse using another shape. A 2D bounding capsule can converge to the disk result if both sides are equal but can be also more difficult to compute. A bounding 2D box is going exhibit masking inconsistent with respect to the light shape. Yet another idea is to replace the ellipse by a well chosen disk that is placed to minimise the difference with the real result. For now no solution appear clearly easier/better. ### Performance The two `acos` are quite expensive. We might want to have a more simpler approximation. I already approximated the circular segment area computation using `smoothstep` function in the overlay antialiasing and it proved quite good in practice (see [2]). The difference here is that we might need a slightly more precise expression, and that we need to run it twice. ### TODOs - [x] Disk implementation - [x] Rectangle implementation - [x] Ellipse approximation - [x] `smoothstep` optimization - [x] Evaluate other optimizations - [ ] Correct focusing term - [ ] Cleanup codestyle - [x] Use UI value instead of hardcoded value - [ ] (optional) Add better culling ### References I added a link to the old cycles task [1] written by @weizhen which has nice diagrams and describes the problem in great detail. [3], [4] and [5] are references about the area calculation we need to do. [6] show the approximated calculation of the circular segment area. [1] https://archive.blender.org/developer/D16694 [2] https://projects.blender.org/blender/blender/src/branch/main/source/blender/draw/engines/overlay/shaders/overlay_antialiasing_frag.glsl [3] https://mathworld.wolfram.com/CircularSegment.html [4] https://mathworld.wolfram.com/Circle-CircleIntersection.html [5] https://math.stackexchange.com/questions/458523/how-to-find-the-area-of-a-segment-of-an-ellipse [6] https://www.desmos.com/calculator/vbfy0msins
Clément Foucault added the
Interest
EEVEE
label 2024-03-16 20:10:29 +01:00
Clément Foucault added 1 commit 2024-03-16 20:10:38 +01:00
6c291c05a8 EEVEE-Next: Add light spread support
This is just a proof of concept.

This only supports disk lights. Computing for square and
elipses might prove to be more difficult.

This has energy problem as the cycles version does
focuses the light to keep the light power constant.
This version only masks the lighting. Which still
gives correct lighting intensity around the light
but it decays too fast.
However, this raises question about the bounds of the
light: Should they be increased to account for the
focussing? Should we also cull them better in the
light culling phase?

Also the two `acos` are quite expensive. We might want
to have a more simpler expression.
Clément Foucault changed title from EEVEE-Next: Add light spread support to WIP: EEVEE-Next: Add light spread support 2024-03-16 20:37:08 +01:00
Member

This has energy problem as the cycles version does focuses the light to keep the light power constant

This constant is normalize_spread found in intern/cycles/scene/light.cpp

The two acos are quite expensive

If you don't like acos() and acos_fast() has numerical issues, you can replace it with the fast_acosf() in intern/cycles/util/math_fast.h, which is more precise :)
Not sure if the sqrt() there is fast enough, otherwise you can also try fast_atanf().

> This has energy problem as the cycles version does focuses the light to keep the light power constant This constant is `normalize_spread` found in `intern/cycles/scene/light.cpp` > The two acos are quite expensive If you don't like `acos()` and `acos_fast()` has numerical issues, you can replace it with the `fast_acosf()` in `intern/cycles/util/math_fast.h`, which is more precise :) Not sure if the `sqrt()` there is fast enough, otherwise you can also try `fast_atanf()`.
Clément Foucault added 4 commits 2024-03-17 00:56:32 +01:00
Author
Member

Thanks for the tips. I will measure the speed vs. accuracy of your method, but the multiple acos are not my only concern, and the smoothstep approximation does a good job in practice and is quite cheap.

The energy problem is a bit more involved though. It is because cycles definitely treat 0° spread as a sun light (i.e: without any decay). So we need to set-up the LTC configuration to mimic that. And I believe the cases between 0° and and 180° are just some interpolation between the two (or close enough) that we can compute (using and empirical fit or derive the right equation) on the CPU.

So should be fairly easy but requires some more researching.

Thanks for the tips. I will measure the speed vs. accuracy of your method, but the multiple `acos` are not my only concern, and the `smoothstep` approximation does a good job in practice and is quite cheap. The energy problem is a bit more involved though. It is because cycles definitely treat 0° spread as a sun light (i.e: without any decay). So we need to set-up the LTC configuration to mimic that. And I believe the cases between 0° and and 180° are just some interpolation between the two (or close enough) that we can compute (using and empirical fit or derive the right equation) on the CPU. So should be fairly easy but requires some more researching.
Author
Member

So I poked around the idea a bit more and had some realization:

The idea of simplifying the problem to a 2D problem on the area light plane is bogus. Moreover, the current solution for finding Sphere-Rectangle intersection area (or ratio) is not correct and the real one is quite involved (https://math.stackexchange.com/questions/1450961/overlapping-area-between-a-circle-and-a-square & https://math.stackexchange.com/questions/1920170/area-of-intersection-of-a-circle-with-a-rectangle).

What we really need is to compute solid angles and their intersection. This is alike the Specular Ambient Occlusion problem described inside the GTAO paper, but easier since we are only interested in the solid angle ratio and not the lighting integral with the cosine term. The cone-cone solid angle intersection is also approximated using smoothstep by Chris Oat in https://advances.realtimerendering.com/s2006/Oat-AmbientApetureLighting.pdf .

But we can go further. Since the problem has not as many variables as the Ambient occlusion, we can simplify it quite a bit:

First lets transform the problem into light space aligned basis around the shading point.

  • The spread cone is always facing up and has a precise aperture angle (and solid angle).
  • The light shape is always facing the same direction, so it's solid angle is a function of its elevation angle and 2D rotation on itself and it's 2D size.

So we have a multi-variate function to solve. I believe we can reduce the number of dimensions of the problem and pre-compute / split some part of this function into either lookup tables or other approximations.

Disk case

This one simplifies all the way to a 1D problem since it's solid angle is only a function of the elevation angle. I believe there is an analytical solution for this.

Square case

This one simplifies to a 2D problem since it's solid angle is only a function of the elevation angle and its 2D rotation. The analytical solution might be too costly.

General solution

A general solution would be to find a cone which produces the same intersection ratio as the correct light shape. However, this might become quite involved, would require offline fitting and the resulting table might be 3d or 4d, but would only contain the elevation angle of the cone and the aperture.

So I poked around the idea a bit more and had some realization: The idea of simplifying the problem to a 2D problem on the area light plane is bogus. Moreover, the current solution for finding Sphere-Rectangle intersection area (or ratio) is not correct and the real one is quite involved (https://math.stackexchange.com/questions/1450961/overlapping-area-between-a-circle-and-a-square & https://math.stackexchange.com/questions/1920170/area-of-intersection-of-a-circle-with-a-rectangle). What we really need is to compute solid angles and their intersection. This is alike the Specular Ambient Occlusion problem described inside the GTAO paper, but easier since we are only interested in the solid angle ratio and not the lighting integral with the cosine term. The cone-cone solid angle intersection is also approximated using `smoothstep` by Chris Oat in https://advances.realtimerendering.com/s2006/Oat-AmbientApetureLighting.pdf . But we can go further. Since the problem has not as many variables as the Ambient occlusion, we can simplify it quite a bit: First lets transform the problem into light space aligned basis around the shading point. - The spread cone is always facing up and has a precise aperture angle (and solid angle). - The light shape is always facing the same direction, so it's solid angle is a function of its elevation angle and 2D rotation on itself and it's 2D size. So we have a multi-variate function to solve. I believe we can reduce the number of dimensions of the problem and pre-compute / split some part of this function into either lookup tables or other approximations. ### Disk case This one simplifies all the way to a 1D problem since it's solid angle is only a function of the elevation angle. I believe there is an analytical solution for this. ### Square case This one simplifies to a 2D problem since it's solid angle is only a function of the elevation angle and its 2D rotation. The analytical solution might be too costly. ### General solution A general solution would be to find a cone which produces the same intersection ratio as the correct light shape. However, this might become quite involved, would require offline fitting and the resulting table might be 3d or 4d, but would only contain the elevation angle of the cone and the aperture.
Clément Foucault added 2 commits 2024-03-22 00:35:27 +01:00
Author
Member

So I have a working solution for both ellipses and rectangle lights.

It is far from being ready and need a lot of optimization.

Rectangle

We split the cone vs. spherical-rectangle intersection problem for each axis as two cone vs. spherical-lune intersection, then each spherical-lune as two cone vs. hemisphere intersection.

For each side, we compute the how much the hemisphere behind the spherical quad edge intersect with the spread cone. However this doesn't converge to 1 if spread cone aperture is PI (in which all the light is covered, so should converge to 1). So we have to remap the maximum by dividing by the result of the intersection function when spread cone aperture is at its maximum value. This ensure the case of spread cone fully opened does not mask anything.

For each axis we then combine the visibility of each opposite side by taking the min visibility. I am not sure about this. I used the same rule for mixing ambient occlusion at different scale where different visibility level needs to be min'ed. But from a geometric point of view we should subtract each edge-disk intersection area from the disk area. This need the signed angle of the edge hemisphere but it is doable. At least the min version seem to work.

Then for the final visibility we just take the product of each axis. This is an approximation since this is what you would do for two axis aligned spherical-quad intersection. But since we already did a somewhat correct approximation for cone vs. spherical-lune for each axis, this doesn't seems to be a huge issue, and it is less an issue with spherical quad because of the deformation. This approximation over estimate the energy when the quad is not intersecting the cone at the corners. I still consider this a good enough approximation.

Disk

Disk projected on hemisphere will result in a spherical ellipse. Intersection of spherical ellipse-cone intersection area computation is way too complex for the budget we have. But the function of the intersection area (A(x) which, for a given configuration of the disk, only depends on x the spread cone angle). This function have a minimum (argmin(A(x))) and a maximum (argmin(A(x))) point that we can use to approximate the effect.

If we project the problem onto the light plane the problem becomes 2D and thus easier. We have a new intersection function A'(X) with different argument X. We can find the argmin/argmax(A'(X)) on the projected plane and just convert these two values back to the spherical domain.

We then parametrize a cone that have the same argmin/argmax values for its intersection function and compute the intersection between this cone and with the light spread cone. The projected disk shape (the spherical ellipse) is close enough that the intersection area functions area not too different and result in very good looking approximation.

Ellipse

Now, Ellipse are quite another beast. If the disk case was not complicated enough now we have to deal with arbitrary oriented ellipses. But we already have a good approximation for regular spherical ellipses. We would like to reuse this one if possible. But this requires finding the min and max of the intersection-area function.

We reuse the same trick to project the problem onto the light plane and find the argmin/argmax(A'(X)) for the oriented ellipse. Then we reuse the same approximation for A(x) we did for disk.

Finding argmin/argmax(A'(X)) means finding the nearest and farthest point on the ellipse from the center of the cone intersection (the projected shading point onto light plane). It is still involved but doable. https://iquilezles.org/articles/ellipsedist/ has code for finding the nearest point. I managed to change the Method 2 to find the other root we are interested in (the farthest point).

This Method 2 is still costly but not as much as the quartic solver that would give the correct result. It exhibits some discontinuity issues that I think we can either live with or try to improve. The good thing is, since both roots use the same solver and use 2d coordinates, we can run them in parallel using vec4, hopefully resulting in less ALU.

So I have a working solution for both ellipses and rectangle lights. It is far from being ready and need a lot of optimization. #### Rectangle We split the **cone vs. spherical-rectangle** intersection problem for each axis as **two cone vs. spherical-lune** intersection, then each **spherical-lune** as two **cone vs. hemisphere** intersection. For each side, we compute the how much the hemisphere behind the spherical quad edge intersect with the spread cone. However this doesn't converge to 1 if spread cone aperture is PI (in which all the light is covered, so should converge to 1). So we have to remap the maximum by dividing by the result of the intersection function when spread cone aperture is at its maximum value. This ensure the case of spread cone fully opened does not mask anything. For each axis we then combine the visibility of each opposite side by taking the min visibility. I am not sure about this. I used the same rule for mixing ambient occlusion at different scale where different visibility level needs to be `min`'ed. But from a geometric point of view we should subtract each edge-disk intersection area from the disk area. This need the signed angle of the edge hemisphere but it is doable. At least the `min` version seem to work. Then for the final visibility we just take the product of each axis. This is an approximation since this is what you would do for two axis aligned spherical-quad intersection. But since we already did a somewhat correct approximation for cone vs. spherical-lune for each axis, this doesn't seems to be a huge issue, and it is less an issue with spherical quad because of the deformation. This approximation over estimate the energy when the quad is not intersecting the cone at the corners. I still consider this a good enough approximation. #### Disk Disk projected on hemisphere will result in a spherical ellipse. Intersection of spherical ellipse-cone intersection area computation is way too complex for the budget we have. But the function of the intersection area (`A(x)` which, for a given configuration of the disk, only depends on `x` the spread cone angle). This function have a minimum (`argmin(A(x))`) and a maximum (`argmin(A(x))`) point that we can use to approximate the effect. If we project the problem onto the light plane the problem becomes 2D and thus easier. We have a new intersection function `A'(X)` with different argument `X`. We can find the `argmin/argmax(A'(X))` on the projected plane and just convert these two values back to the spherical domain. We then parametrize a cone that have the same `argmin/argmax` values for its intersection function and compute the intersection between this cone and with the light spread cone. The projected disk shape (the spherical ellipse) is close enough that the intersection area functions area not too different and result in very good looking approximation. #### Ellipse Now, Ellipse are quite another beast. If the disk case was not complicated enough now we have to deal with arbitrary oriented ellipses. But we already have a good approximation for regular spherical ellipses. We would like to reuse this one if possible. But this requires finding the min and max of the intersection-area function. We reuse the same trick to project the problem onto the light plane and find the `argmin/argmax(A'(X))` for the oriented ellipse. Then we reuse the same approximation for `A(x)` we did for disk. Finding `argmin/argmax(A'(X))` means finding the nearest and farthest point on the ellipse from the center of the cone intersection (the projected shading point onto light plane). It is still involved but doable. https://iquilezles.org/articles/ellipsedist/ has code for finding the nearest point. I managed to change the Method 2 to find the other root we are interested in (the farthest point). This Method 2 is still costly but not as much as the quartic solver that would give the correct result. It exhibits some discontinuity issues that I think we can either live with or try to improve. The good thing is, since both roots use the same solver and use 2d coordinates, we can run them in parallel using `vec4`, hopefully resulting in less ALU.
Clément Foucault added 1 commit 2024-03-22 20:57:45 +01:00
Clément Foucault added 1 commit 2024-03-24 12:07:38 +01:00
Clément Foucault added 2 commits 2024-03-24 22:27:52 +01:00
Clément Foucault added 1 commit 2024-03-24 22:38:32 +01:00
Author
Member

I added the correct cone - hemisphere function (which is quite off when the aperture is quite big contrary to what Oat et. al suggested). It could be approximated by a mix of linearstep and smoothstep but at least we have the reference function.

Capture d’écran 2024-03-24 à 22.45.41.png

For reference Cycles
Capture d’écran 2024-03-24 à 22.47.08.png

Unfortunately, using the same approximation as before with the correct intersection area produces more artifacts because of abrupt changes in the function.

Capture d’écran 2024-03-24 à 22.47.59.png

So I tried another idea by using the form factor technique used by LTC to compute a new cone that fits the same solid angle. It works without visible artifact, gives good contact point when the light is crossing the hemisphere, but it morphs to a sphere light at longer distances. I do not think this is good enough.

Capture d’écran 2024-03-24 à 22.44.35.png

So my best bet about the way to proceed is to first plot the cone quad intersection area function and see how it behaves and how it can be approximated. This is already a bit involved as there is no closed form solution.

Unfortunately it is a 5d function (or 4d if we parametrize the spherical rectangle using only one corner point on the spherical coordinate and reconstruct the other 3 points).

I added the correct cone - hemisphere function (which is quite off when the aperture is quite big contrary to what Oat et. al suggested). It could be approximated by a mix of linearstep and smoothstep but at least we have the reference function. ![Capture d’écran 2024-03-24 à 22.45.41.png](/attachments/e8b1a9dd-b2c9-4809-b0a0-680b42cbe961) For reference Cycles ![Capture d’écran 2024-03-24 à 22.47.08.png](/attachments/b97119b1-8b84-40ee-8ed6-391dc60288c3) Unfortunately, using the same approximation as before with the correct intersection area produces more artifacts because of abrupt changes in the function. ![Capture d’écran 2024-03-24 à 22.47.59.png](/attachments/55577d10-dadc-4826-abb5-d700caac1f5f) So I tried another idea by using the form factor technique used by LTC to compute a new cone that fits the same solid angle. It works without visible artifact, gives good contact point when the light is crossing the hemisphere, but it morphs to a sphere light at longer distances. I do not think this is good enough. ![Capture d’écran 2024-03-24 à 22.44.35.png](/attachments/ff120f1e-071b-4049-a60a-ea4dcd631b72) So my best bet about the way to proceed is to first plot the cone quad intersection area function and see how it behaves and how it can be approximated. This is already a bit involved as there is no closed form solution. Unfortunately it is a 5d function (or 4d if we parametrize the spherical rectangle using only one corner point on the spherical coordinate and reconstruct the other 3 points).
This pull request has changes conflicting with the target branch.
  • scripts/startup/bl_ui/properties_data_light.py
  • source/blender/draw/engines/eevee_next/eevee_light.cc
  • source/blender/draw/engines/eevee_next/eevee_shader_shared.hh
  • source/blender/draw/engines/eevee_next/shaders/eevee_light_lib.glsl

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u eevee-next-lightspread:fclem-eevee-next-lightspread
git checkout fclem-eevee-next-lightspread
Sign in to join this conversation.
No reviewers
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset Browser
Interest
Asset Browser Project Overview
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
EEVEE & Viewport
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overlay
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Interest: X11
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
EEVEE & Viewport
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Priority
High
Priority
Low
Priority
Normal
Priority
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: blender/blender#119574
No description provided.