Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement multiple lights and more Light types #3246

Merged
merged 91 commits into from
Oct 30, 2023
Merged

Conversation

ffreyer
Copy link
Collaborator

@ffreyer ffreyer commented Sep 21, 2023

Description

Change Summary

This pr adds/changes:

  • (breaking) backlight now affects normal instead of lightdir. This means specular reflections show up with backlight
  • (breaking) changed default light direction (position), light intensities, diffuse and specular attributes
  • (minor breaking) change default light to DirectionalLight (closes Directional light as new default #3239)
  • (minor breaking) added smoothing of diff_coeff around 0. This softens the edge where normals are perpendicular to the light direction)
  • (non-breaking change) shading attribute is now :none/:fast/:verbose instead of true or false
  • (internal/maybe breaking) moved scaling in Axis3 from view to model matrix and stopped connecting scenes/plots with different spaces. The latter is something I want to do for Cleanup projection code #3013.
  • (new) support for multiple light sources (need not be unique, ref More lights #1592)
  • (new) PointLight now has attenuation parameters (distance based intensity falloff, off by default)
  • (new) added DirectionalLight (parallel light rays)
  • (new) added SpotLight (light source illuminating a cone)
  • (fix) the light color of PointLight (and all others) is now connected (fixes Point light color is ignored in GLMakie #3238)
  • (internal) colorbuffer() now polls events to trigger render_tick so that it can cause lighting updates
  • (internal) light calculation moved to world space which shouldn't change anything visually
  • (internal) loosen type restriction on lights vector (fix Issue with typing on LScene with lights  #2985)

Type of change

  • breaking (backlight, default light, smoothing around 0 intensity)

TODO

  • Hook up updates (for individual lights and the lights array)
  • implement more simple light types (SpotLight, DirectionalLight, PointLight with attentuation)
  • Hook up shading
  • Update other backends to work with changes made to the lighting interface
  • Implement new lights in RPRMakie
  • lighting in volume
  • backlight
  • record updates
  • Added an entry in NEWS.md (for new features and breaking changes)
  • Added or changed relevant sections in the documentation
  • Added unit tests for new algorithms, conversion methods, etc.
  • Added reference image tests for new plotting functions, recipes, visual options, etc.
  • check/fix missing normals for matcap with NoShading
  • fix WGLMakie
  • fix laggy directional light in WGLMakie (move camera-space light direction calculation to javascript)
  • fix Axis3
  • fix images in docs (MultiLightShading and SSAO fail to render correctly)

@ffreyer ffreyer added the GLMakie This relates to GLMakie.jl, the OpenGL backend for Makie. label Sep 21, 2023
@ffreyer
Copy link
Collaborator Author

ffreyer commented Sep 21, 2023

I'm following the attenuation from learnopengl/ogre3d for now, which is effectively 1 / (1 + a * distance + b * distance^2). Example:

fig = Figure(backgroundcolor = :black)
sl = Slider(fig[1, 1], range = 0.05:0.05:5.0)

lights = Makie.AbstractLight[
    AmbientLight(RGBf(0.1, 0.1, 0.1)),
    PointLight(RGBf(1,0,0), Point3f(2, 2, 0), 1.0),
    PointLight(RGBf(0,1,0), Point3f(-2, 2, 0), 10.0),
    PointLight(RGBf(0,0,1), Point3f(-2, -2, 0), 100.0),
    PointLight(RGBf(1,1,1), Point3f(2, -2, 0), 10.0),
]
scene = LScene(
    fig[2, 1], show_axis = false,
    scenekw=(camera = cam3d!, lights = lights, backgroundcolor = :black, center = false),
)

p = mesh!(scene, Rect3f(Point3f(-10, -10, 0.01), Vec3f(20, 20, 0.02)), color = :white)
map(z -> translate!(p, 0, 0, -z), sl.value)

update_cam!(scene.scene, Vec3f(0, 0, 10), Vec3f(0, 0, 0), Vec3f(0, 1, 0))

idxs = vcat(1:length(sl.range[]), length(sl.range[])-1:-1:2)
record(fig, "attenuation.webm", idxs, framerate = 30) do idx
    sl.selected_index[] = idx
end
attenuation.webm

I also tried just a / distance^3 as an attenuation factor beforehand, with a weak directional blue light above:

lights = [
    AmbientLight(RGBf(0.1, 0.1, 0.1)),
    PointLight(RGBf(0,1,0), Point3f(0, -3, 1)),
    PointLight(RGBf(1,0,0), Point3f(-3, 0, 1)),
    PointLight(RGBf(0,1,0), Point3f(0,  3, 1)),
    PointLight(RGBf(1,0,0), Point3f( 3, 0, 1)),
    Makie.DirectionalLight(RGBf(0, 0, 0.5), Vec3f(0, 0, 1)),
]
scene = Scene(camera = cam3d!, lights = lights)
mesh!(scene, Rect3f(Point3f(-1), Vec3f(2)), color = :white)
r = range(-10, 10, length=101)
surface!(scene, r, r, [0.5 * (sin(x+y) * sin(2x) * sin(3y)) - 1 for x in r, y in r], colormap = [:white, :white])
ps = [light.position[] for light in lights if light isa PointLight]
scatter!(scene, ps, color = :yellow, strokewidth = 1, strokecolor = :black)

screen = display(scene)

Screenshot from 2023-09-21 18-46-22

@MakieBot
Copy link
Collaborator

MakieBot commented Sep 21, 2023

Compile Times benchmark

Note, that these numbers may fluctuate on the CI servers, so take them with a grain of salt. All benchmark results are based on the mean time and negative percent mean faster than the base branch. Note, that GLMakie + WGLMakie run on an emulated GPU, so the runtime benchmark is much slower. Results are from running:

using_time = @ctime using Backend
# Compile time
create_time = @ctime fig = scatter(1:4; color=1:4, colormap=:turbo, markersize=20, visible=true)
display_time = @ctime Makie.colorbuffer(display(fig))
# Runtime
create_time = @benchmark fig = scatter(1:4; color=1:4, colormap=:turbo, markersize=20, visible=true)
display_time = @benchmark Makie.colorbuffer(display(fig))
using create display create display
GLMakie 5.54s (5.40, 5.82) 0.16+- 465.57ms (446.37, 502.91) 22.09+- 894.62ms (871.64, 924.93) 19.63+- 13.53ms (13.33, 13.77) 0.14+- 134.22ms (131.38, 140.57) 3.33+-
master 5.47s (5.32, 5.79) 0.18+- 461.17ms (441.35, 486.42) 20.04+- 0.91s (0.86, 1.03) 0.06+- 13.68ms (13.31, 14.57) 0.44+- 136.14ms (133.03, 139.43) 2.30+-
evaluation +1.25%, 0.07s invariant (0.41d, 0.45p, 0.17std) +0.94%, 4.39ms invariant (0.21d, 0.70p, 21.06std) -2.16%, -19.29ms invariant (-0.42d, 0.46p, 40.98std) -1.12%, -0.15ms invariant (-0.46d, 0.41p, 0.29std) -1.43%, -1.92ms invariant (-0.67d, 0.23p, 2.81std)
CairoMakie 4.69s (4.62, 4.79) 0.06+- 450.29ms (424.98, 468.91) 13.56+- 417.68ms (407.83, 430.88) 7.68+- 11.57ms (11.41, 11.99) 0.21+- 5.84ms (5.49, 6.36) 0.33+-
master 4.56s (4.40, 4.70) 0.09+- 439.73ms (428.38, 449.00) 7.30+- 409.46ms (400.71, 418.74) 5.84+- 11.37ms (11.20, 11.49) 0.10+- 6.15ms (5.49, 6.91) 0.49+-
evaluation +2.71%, 0.13s slower X (1.61d, 0.01p, 0.08std) +2.35%, 10.56ms invariant (0.97d, 0.10p, 10.43std) +1.97%, 8.23ms slower X (1.21d, 0.05p, 6.76std) +1.74%, 0.2ms slower X (1.25d, 0.05p, 0.15std) -5.30%, -0.31ms noisy🤷‍♀️ (-0.74d, 0.19p, 0.41std)
WGLMakie 6.71s (6.53, 7.07) 0.19+- 1000.35ms (964.81, 1062.69) 37.34+- 14.98s (14.68, 15.49) 0.27+- 24.49ms (21.41, 36.91) 5.52+- 1.19s (1.11, 1.30) 0.07+-
master 6.75s (6.56, 7.14) 0.21+- 1002.12ms (958.68, 1117.46) 53.25+- 15.02s (14.47, 16.19) 0.56+- 22.35ms (20.54, 23.95) 1.33+- 1.21s (1.15, 1.28) 0.05+-
evaluation -0.67%, -0.04s invariant (-0.22d, 0.69p, 0.20std) -0.18%, -1.77ms invariant (-0.04d, 0.94p, 45.30std) -0.29%, -0.04s invariant (-0.10d, 0.86p, 0.41std) +8.77%, 2.15ms noisy🤷‍♀️ (0.53d, 0.35p, 3.42std) -1.34%, -0.02s invariant (-0.28d, 0.61p, 0.06std)

Comment on lines 11 to 14
updater = screen.render_tick
MAX_PRIORITY = typemax(Int)

attr[:lights_length] = map(updater, priority = -1000) do _
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updating on render_tick doesn't work with record.

Updating (many) lights is kind of complicated in general, because we want to react to updates of each field for each light and also when lights are added or deleted to the lights vector.

Listening to each field individually would require attaching and removing listeners when the lights vector changes. Detecting that would either require some kind of Vector wrapper or an update on draw.

Instead of bothering with all of that I thought it would be easier to just check the lights vector once per frame and update it then. I'm using render_tick for that atm, but I guess we don't want that to update in record to avoid triggering events?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I adjusted colorbuffer to trigger render_tick now. That seems to be fine for rendering beyond screen resolution and will be useful if we add an on_render_start event in the future.

@ffreyer
Copy link
Collaborator Author

ffreyer commented Sep 22, 2023

Moving, colored `PointLight`s with different attenuation + white SpotLight.
begin
    fig = Figure(backgroundcolor = :black)
    sl = Slider(fig[1, 1], range = 0.05:0.05:5.0)

    # Prepare lights
    angle = Observable(0)
    angle2pos(phi) = Point3f(3cosd(phi), 3sind(phi), 0)
    lights = Makie.AbstractLight[
        AmbientLight(RGBf(0.1, 0.1, 0.1)),
        PointLight(RGBf(1,0,0), map(phi -> angle2pos(phi + 0), angle), 10.0),
        PointLight(RGBf(0,1,0), map(phi -> angle2pos(phi + 120), angle), 100.0),
        PointLight(RGBf(0,0,1), map(phi -> angle2pos(phi + 240), angle), 1000.0),
        SpotLight(RGBf(1,1,1), Point3f(0, 0, 1), Vec3f(0, 0, -1), pi/6),
    ]

    # Set scene
    scene = LScene(
        fig[2, 1], show_axis = false,
        scenekw=(camera = cam3d!, lights = lights, backgroundcolor = :black, center = false),
    )

    # floor
    p = mesh!(scene, Rect3f(Point3f(-10, -10, 0.01), Vec3f(20, 20, 0.02)), color = :white)
    map(z -> translate!(p, 0, 0, -z), sl.value)

    # Random balls
    N_balls = 2_000
    random_colors = !true
    meshscatter!(
        scene, [Point3f(20rand()-10, 20rand()-10, 10rand()-5) for _ in 1:N_balls],
        color = random_colors ? rand(RGBf, N_balls) : (:white)
    )

    # Visualize light source positions
    ps = Observable([light.position[] for light in lights if light isa Union{PointLight, SpotLight}])
    cs = [light.color[]    for light in lights if light isa Union{PointLight, SpotLight}]
    scatter!(scene, ps, color = cs, strokewidth = 1, strokecolor = :white)

    # Place camera above
    # update_cam!(scene.scene, Vec3f(0, 0, 10), Vec3f(0, 0, 0), Vec3f(0, 1, 0))

    # place camera at angle nearby
    update_cam!(scene.scene, Vec3f(8, 1, 4), Vec3f(0, 0, 0), Vec3f(0, 0, 1))

    # record
    idxs = vcat(1:length(sl.range[]), length(sl.range[])-1:-1:2)
    record(fig, "point_lights_and_spot_light.webm", idxs, framerate = 30) do idx
        sl.selected_index[] = idx
        angle[] = angle[] + 4
        ps[] = [light.position[] for light in lights if light isa Union{PointLight, SpotLight}]
        notify(fig.scene.current_screens[1].render_tick)
    end

    # interactive
    # display(fig)
    # @async while isopen(fig.scene)
    #     angle[] = angle[] + 2
    #     ps[] = [light.position[] for light in lights if light isa Union{PointLight, SpotLight}]
    #     sleep(1/60)
    # end
end
point_lights_and_spot_light.webm

@ffreyer
Copy link
Collaborator Author

ffreyer commented Sep 26, 2023

Added refimgs:

Ambient + SpotLights + PointLights w/o specular reflections:
Screenshot from 2023-09-26 17-11-52

DirectionalLights + specular reflections:
Screenshot from 2023-09-26 16-43-27

Comment on lines 688 to 730
# polling may change window size, when its bigger than monitor!
# we still need to poll though, to get all the newest events!
# GLFW.PollEvents()
pollevents(screen)
# keep current buffer size to allows larger-than-window renders
render_frame(screen, resize_buffers=false) # let it render
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added pollevents() here so lighting updates from render_tick can take effect before drawing the frame. Given the note I would assume this breaks output sizes with save and record, however I have no problems saving/recording at resolution = (4000, 4000).

@ffreyer ffreyer changed the title Lighting experiments Implement multiple lights and more Light types Sep 27, 2023
Comment on lines 264 to 267
// direction to light for :fast shading
// (normalizing this will result in incorrect lighting)
vec4 view_light_pos = view*vec4(lightposition, 1.0);
o_lightdir = view_light_pos.xyz / view_light_pos.w - view_pos.xyz;
Copy link
Collaborator Author

@ffreyer ffreyer Sep 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted normalizing here results in incorrect light directions/perceived positions. To illustrate this, consider a light source shining on a rect viewed from the side:

Screenshot from 2023-09-27 16-43-18

If we don't normalize o_lightdir it will be a vector pointing from the light source to the vertex handled by the vertex shader. Since I placed the light source at (0, 0) here they'll match p1 and p2 in the example. On the way to the fragment shader o_lightdir will then get interpolated between different vertices. For example, halfway between the two vertices the vector will point to the green "Vertex center". Normalizing in the fragment shader will then give correct light-to-vertex-center direction.

If we do normalize o_lightdir in the vertex shader as before, we will be interpolating the normalized o_lightdir vectors. In the sketch above those point to n1 (equal to p1) and n2. Doing the same half-way interpolation for vertices as above, we end up with a o_lightdir halfway between n1 and n2, which is marked as "Normalized center". This is not a vector passing through the vertex center position and thus the wrong light direction.

I'm pretty sure the test failure in Order Independent transparency is a result of this change. There are likely quite a few more failures that didn't get caught.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switched to doing lighting in world space so this is not really relevant to lightdir anymore. Though it probably still applies to camdir. (World vs view/camera space shouldn't matter for lighting. It was using view/camera space because SSAO needed it and lighting could reuse it. Merging lighting between volume and the rest of Makie requires world space though.)

@ffreyer
Copy link
Collaborator Author

ffreyer commented Sep 28, 2023

Volume with multiple lights:

volume.webm

Volume plots don't differentiate inwards facing vs outwards facing surfaces, so lighting is a bit weird here. I'm not going to try to change this here though.

@ffreyer ffreyer changed the base branch from master to beta-0.20 September 28, 2023 16:01
@ffreyer ffreyer changed the base branch from beta-0.20 to sd/beta-20 September 28, 2023 16:02
@ffreyer ffreyer mentioned this pull request Oct 16, 2023
5 tasks
@ffreyer
Copy link
Collaborator Author

ffreyer commented Oct 27, 2023

Fixed this in WGLMakie (even though it should be unrelated to this pr)
Screenshot from 2023-10-27 14-45-18

@ffreyer
Copy link
Collaborator Author

ffreyer commented Oct 27, 2023

Seems like this too 🤷
Screenshot from 2023-10-27 14-51-51

There also seem to be some issues with line (and maybe scatter) depth order:
Screenshot from 2023-10-27 14-44-42

@ffreyer ffreyer mentioned this pull request Oct 28, 2023
16 tasks
@ffreyer
Copy link
Collaborator Author

ffreyer commented Oct 28, 2023

As far as I can tell the only issue left here that isn't also on #3113 is with "contour labels 3D". With Axis3 scaling being moved to model the text is now being scaled, which makes it tiny. (You can see it with fontsize ~ 100.) The gaps are also too small I think. This requires some changes to transform_marker/scale_primitive to be fixed, and probably text bounding boxes

@SimonDanisch SimonDanisch reopened this Oct 30, 2023
@SimonDanisch SimonDanisch merged commit b82b6f7 into sd/beta-20 Oct 30, 2023
25 of 28 checks passed
@SimonDanisch SimonDanisch deleted the ff/lighting branch October 30, 2023 19:56
SimonDanisch added a commit that referenced this pull request Nov 17, 2023
Continues #2831 !
Still needs to check, if I rebased correctly and didn't incorrectly
apply some of the changes!

## Merged PRs
- #2598
- #2746
- #2346
- #2544
- #3082
- #2868
- #3062
- #3106
- #3281
- #3246

## TODOS

- [x] fix flaky test `@test GLMakie.window_size(screen.glscreen) ==
scaled(screen, (W, H))`
- [x] Merge axis type inferences from #2220 
- [x] Test on different resolution screens, IJulia, Pluto, VSCode,
Windowed
- [x] rebase to only have merge commits from the PRs 
- [x] investigate unexpected speed ups
- [x] reset camera settings from tests
- [ ] check doc image generation
- [x] rethink default near/far in Camera3D (compatability with OIT)
- [x] merge #3246
- [x] fix WGLMakie issues/tests:
- [x] fix line depth issues (see tests: ~~hexbin colorrange~~ (not new),
LaTeXStrings in Axis3, Axis3 axis reversal)
  - [x] fix lighting of surface with nan points (fixed in #3246)
- ~~volume/3D contour artifacts (see 3D Contour with 2D contour
slices)~~ not new
  - ~~artifacting in "colorscale (lines)"~~ not new
- [x] GLMakie:
  - [x] slight outline in "scatter image markers" test
  - ~~clipping/z-fighting in "volume translated"~~ not new
- [x] CairoMakie:
  -  ~~Artfiacting in `colorscale (lines)"~~ not new
  - ~~markersize in "scatter rotations" changed?~~ not new
  - ~~color change in "colorscale (poly)"~~ not new
  - ~~transparency/render order of "OldAxis + Surface"~~ not new
  - ~~render order in "Merged color mesh"~~ not new
  - ~~render order of "Surface + wireframe + contour"~~ not new
- [x] Check "SpecApi in convert_arguments" (colors swapped?)


## Fixes the following errors

- fixes #2721 via #2746
- fixes #1600 via #2746
- fixes #1236 via #2746
- fixes MakieOrg/GeoMakie.jl#133 via #2598
- closes #2522
- closes #3239 via #3246
- fixes #3238 via #3246
- fixes #2985 via #3246
- fixes #3307 via #3281
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
GLMakie This relates to GLMakie.jl, the OpenGL backend for Makie.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Point light color is ignored in GLMakie
5 participants