From 4343b62677cb37efa1e7810c77464a4d1414f8c2 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Wed, 11 Oct 2023 16:40:28 +0200 Subject: [PATCH] move camera-relative light dir calc to backends --- CairoMakie/src/primitives.jl | 26 +++++++++++++++++--------- GLMakie/src/drawing_primitives.jl | 29 +++++++++++++++++++++-------- RPRMakie/src/scene.jl | 6 ++++++ WGLMakie/src/Camera.js | 15 ++++++++++++++- WGLMakie/src/Serialization.js | 20 +++++++++++++++++++- WGLMakie/src/serialization.jl | 10 +++++----- WGLMakie/src/wglmakie.bundled.js | 27 +++++++++++++++++++++++++-- src/lighting.jl | 11 ++++++++++- src/scenes.jl | 26 ++++++++------------------ src/theming.jl | 7 ++++--- 10 files changed, 129 insertions(+), 48 deletions(-) diff --git a/CairoMakie/src/primitives.jl b/CairoMakie/src/primitives.jl index 76693b959cf..4cea059e670 100644 --- a/CairoMakie/src/primitives.jl +++ b/CairoMakie/src/primitives.jl @@ -919,10 +919,18 @@ function draw_mesh3D( # Light math happens in view/camera space dirlight = Makie.get_directional_light(scene) - lightdirection = if !isnothing(dirlight) - normalize(dirlight.direction[]) + if !isnothing(dirlight) + lightdirection = if dirlight.camera_relative + T = inv(scene.camera.view[][Vec(1,2,3), Vec(1,2,3)]) + normalize(T * dirlight.direction[]) + else + normalize(dirlight.direction[]) + end + c = dirlight.color[] + light_color = Vec3f(red(c), green(c), blue(c)) else - Vec3f(-2/3, -2/3, 1/3) + lightdirection = Vec3f(0,0,-1) + light_color = Vec3f(0) end ambientlight = Makie.get_ambient_light(scene) @@ -955,23 +963,23 @@ function draw_mesh3D( # Face culling zorder = filter(i -> any(last.(ns[meshfaces[i]]) .> faceculling), zorder) - draw_pattern(ctx, zorder, shading, meshfaces, ts, per_face_col, ns, vs, lightdirection, shininess, diffuse, ambient, specular) + draw_pattern(ctx, zorder, shading, meshfaces, ts, per_face_col, ns, vs, lightdirection, light_color, shininess, diffuse, ambient, specular) return end -function _calculate_shaded_vertexcolors(N, v, c, lightdir, ambient, diffuse, specular, shininess) +function _calculate_shaded_vertexcolors(N, v, c, lightdir, light_color, ambient, diffuse, specular, shininess) L = lightdir diff_coeff = max(dot(L, -N), 0f0) H = normalize(L + v) spec_coeff = max(dot(H, -N), 0f0)^shininess c = RGBAf(c) # if this is one expression it introduces allocations?? - new_c_part1 = (ambient .+ diff_coeff .* diffuse) .* Vec3f(c.r, c.g, c.b) #.+ - new_c = new_c_part1 .+ specular * spec_coeff + new_c_part1 = (ambient .+ light_color .* diff_coeff .* diffuse) .* Vec3f(c.r, c.g, c.b) #.+ + new_c = new_c_part1 .+ light_color .* specular * spec_coeff RGBAf(new_c..., c.alpha) end -function draw_pattern(ctx, zorder, shading, meshfaces, ts, per_face_col, ns, vs, lightdir, shininess, diffuse, ambient, specular) +function draw_pattern(ctx, zorder, shading, meshfaces, ts, per_face_col, ns, vs, lightdir, light_color, shininess, diffuse, ambient, specular) for k in reverse(zorder) f = meshfaces[k] @@ -994,7 +1002,7 @@ function draw_pattern(ctx, zorder, shading, meshfaces, ts, per_face_col, ns, vs, N = ns[f[i]] v = vs[f[i]] c = facecolors[i] - _calculate_shaded_vertexcolors(N, v, c, lightdir, ambient, diffuse, specular, shininess) + _calculate_shaded_vertexcolors(N, v, c, lightdir, light_color, ambient, diffuse, specular, shininess) end else c1, c2, c3 = facecolors diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index 790d942d79b..56eb8f7ad66 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -3,7 +3,7 @@ using Makie: attribute_per_char, FastPixel, el32convert, Pixel using Makie: convert_arguments function handle_lights(attr::Dict, screen::Screen, lights::Vector{Makie.AbstractLight}) - @inline function push_back!(trg, idx, src) + @inline function push_inplace!(trg, idx, src) for i in eachindex(src) trg[idx + i] = src[i] end @@ -72,14 +72,20 @@ function handle_lights(attr::Dict, screen::Screen, lights::Vector{Makie.Abstract for i in 1:n_lights light = lights[i] if light isa PointLight - idx = push_back!(parameters, idx, light.position[]) - idx = push_back!(parameters, idx, light.attenuation[]) + idx = push_inplace!(parameters, idx, light.position[]) + idx = push_inplace!(parameters, idx, light.attenuation[]) elseif light isa DirectionalLight - idx = push_back!(parameters, idx, normalize(light.direction[])) + if light.camera_relative + T = inv(attr[:view][][Vec(1,2,3), Vec(1,2,3)]) + dir = normalize(T * light.direction[]) + else + dir = normalize(light.direction[]) + end + idx = push_inplace!(parameters, idx, dir) elseif light isa SpotLight - idx = push_back!(parameters, idx, light.position[]) - idx = push_back!(parameters, idx, normalize(light.direction[])) - idx = push_back!(parameters, idx, cos.(light.angles[])) + idx = push_inplace!(parameters, idx, light.position[]) + idx = push_inplace!(parameters, idx, normalize(light.direction[])) + idx = push_inplace!(parameters, idx, cos.(light.angles[])) end end notify(attr[:light_parameters]) @@ -245,7 +251,14 @@ function cached_robj!(robj_func, screen, scene, x::AbstractPlot) if shading == FastShading dirlight = Makie.get_directional_light(scene) if !isnothing(dirlight) - gl_attributes[:light_direction] = map(normalize, dirlight.direction) + gl_attributes[:light_direction] = if dirlight.camera_relative + map(gl_attributes[:view], dirlight.direction) do view, dir + return normalize(inv(view[Vec(1,2,3), Vec(1,2,3)]) * dir) + end + else + map(normalize, dirlight.direction) + end + gl_attributes[:light_color] = dirlight.color end diff --git a/RPRMakie/src/scene.jl b/RPRMakie/src/scene.jl index 4c2da2aef60..909bd2f97db 100644 --- a/RPRMakie/src/scene.jl +++ b/RPRMakie/src/scene.jl @@ -65,6 +65,12 @@ end function to_rpr_light(context::RPR.Context, light::Makie.DirectionalLight) directionallight = RPR.DirectionalLight(context) map(light.direction) do dir + if light.camera_relative + T = inv(scene.camera.view[][Vec(1,2,3), Vec(1,2,3)]) + dir = normalize(T * dir) + else + dir = normalize(dir) + end quart = Makie.rotation_between(dir, Vec3f(0,0,-1)) transform!(directionallight, Makie.rotationmatrix4(quart)) end diff --git a/WGLMakie/src/Camera.js b/WGLMakie/src/Camera.js index f2624ddeb28..bc859cfd140 100644 --- a/WGLMakie/src/Camera.js +++ b/WGLMakie/src/Camera.js @@ -39,7 +39,7 @@ function in_scene(scene, mouse_event) { } // Taken from https://andreasrohner.at/posts/Web%20Development/JavaScript/Simple-orbital-camera-controls-for-THREE-js/ -export function attach_3d_camera(canvas, makie_camera, cam3d, scene) { +export function attach_3d_camera(canvas, makie_camera, cam3d, light_dir, scene) { if (cam3d === undefined) { // we just support 3d cameras atm return; @@ -71,6 +71,8 @@ export function attach_3d_camera(canvas, makie_camera, cam3d, scene) { [width, height], [x, y, z] ); + + makie_camera.update_light_dir(light_dir.value); } cam3d.resolution.on(update); @@ -240,6 +242,10 @@ export class MakieCamera { // Lazy calculation, only if a plot type requests them // will be of the form: {[space, markerspace]: THREE.Uniform(...)} this.preprojections = {}; + + // For camera-relative light directions + // TODO: intial position wrong... + this.light_direction = new THREE.Uniform(new THREE.Vector3(-1, -1, -1).normalize()); } calculate_matrices() { @@ -275,6 +281,13 @@ export class MakieCamera { return; } + update_light_dir(light_dir) { + const T = new THREE.Matrix3().setFromMatrix4(this.view.value).invert(); + const new_dir = new THREE.Vector3().fromArray(light_dir); + new_dir.applyMatrix3(T).normalize(); + this.light_direction.value = new_dir; + } + clip_to_space(space) { if (space === "data") { return this.projectionview_inverse.value; diff --git a/WGLMakie/src/Serialization.js b/WGLMakie/src/Serialization.js index fdd2b39f756..9b7ca8fc7a7 100644 --- a/WGLMakie/src/Serialization.js +++ b/WGLMakie/src/Serialization.js @@ -209,6 +209,21 @@ export function add_plot(scene, plot_data) { markerspace.value ); } + + if (scene.camera_relative_light) { + plot_data.uniforms.light_direction = cam.light_direction; + scene.light_direction.on(value => { + cam.update_light_dir(value); + }) + } else { + // TODO how update? + const light_dir = new THREE.Vector3().fromArray(scene.light_direction.value); + plot_data.uniforms.light_direction = new THREE.Uniform(light_dir); + scene.light_direction.on(value => { + plot_data.uniforms.light_direction.value.fromArray(value); + }) + } + const p = deserialize_plot(plot_data); plot_cache[plot_data.uuid] = p; scene.add(p); @@ -510,6 +525,8 @@ export function deserialize_scene(data, screen) { scene.backgroundcolor = data.backgroundcolor; scene.clearscene = data.clearscene; scene.visible = data.visible; + scene.camera_relative_light = data.camera_relative_light; + scene.light_direction = data.light_direction; const camera = new Camera.MakieCamera(); @@ -521,9 +538,10 @@ export function deserialize_scene(data, screen) { } update_cam(data.camera.value); + camera.update_light_dir(data.light_direction.value); if (data.cam3d_state) { - Camera.attach_3d_camera(canvas, camera, data.cam3d_state, scene); + Camera.attach_3d_camera(canvas, camera, data.cam3d_state, data.light_direction, scene); } else { data.camera.on(update_cam); } diff --git a/WGLMakie/src/serialization.jl b/WGLMakie/src/serialization.jl index d47d204c5cd..d88004daeb9 100644 --- a/WGLMakie/src/serialization.jl +++ b/WGLMakie/src/serialization.jl @@ -292,10 +292,15 @@ function serialize_scene(scene::Scene) children = map(child-> serialize_scene(child), scene.children) + dirlight = Makie.get_directional_light(scene) + light_dir = isnothing(dirlight) ? Observable(Vec3f(1)) : dirlight.direction + serialized = Dict(:pixelarea => pixel_area, :backgroundcolor => lift(hexcolor, scene.backgroundcolor), :clearscene => scene.clear, :camera => serialize_camera(scene), + :light_direction => light_dir, + :camera_relative_light => dirlight.camera_relative, :plots => serialize_plots(scene, scene.plots), :cam3d_state => cam3d_state, :visible => scene.visible, @@ -333,11 +338,6 @@ function serialize_three(scene::Scene, plot::AbstractPlot) dirlight = Makie.get_directional_light(scene) if !isnothing(dirlight) - uniforms[:light_direction] = serialize_three(normalize(dirlight.direction[])) - on(dirlight.direction) do value - updater[] = [:light_direction, serialize_three(normalize(value))] - return - end uniforms[:light_color] = serialize_three(dirlight.color[]) on(dirlight.color) do value updater[] = [:light_color, serialize_three(value)] diff --git a/WGLMakie/src/wglmakie.bundled.js b/WGLMakie/src/wglmakie.bundled.js index 6b7019392a2..1fa06325744 100644 --- a/WGLMakie/src/wglmakie.bundled.js +++ b/WGLMakie/src/wglmakie.bundled.js @@ -20232,7 +20232,7 @@ function in_scene(scene, mouse_event) { const [sx, sy, sw, sh] = scene.pixelarea.value; return x >= sx && x < sx + sw && y >= sy && y < sy + sh; } -function attach_3d_camera(canvas, makie_camera, cam3d, scene) { +function attach_3d_camera(canvas, makie_camera, cam3d, light_dir, scene) { if (cam3d === undefined) { return; } @@ -20258,6 +20258,7 @@ function attach_3d_camera(canvas, makie_camera, cam3d, scene) { y, z ]); + makie_camera.update_light_dir(light_dir.value); } cam3d.resolution.on(update); function addMouseHandler(domObject, drag, zoomIn, zoomOut) { @@ -20414,6 +20415,7 @@ class MakieCamera { this.resolution = new Eu(new J()); this.eyeposition = new Eu(new A()); this.preprojections = {}; + this.light_direction = new Eu(new A(-1, -1, -1).normalize()); } calculate_matrices() { const [w, h] = this.resolution.value; @@ -20436,6 +20438,12 @@ class MakieCamera { this.calculate_matrices(); return; } + update_light_dir(light_dir) { + const T = new kt().setFromMatrix4(this.view.value).invert(); + const new_dir = new A().fromArray(light_dir); + new_dir.applyMatrix3(T).normalize(); + this.light_direction.value = new_dir; + } clip_to_space(space) { if (space === "data") { return this.projectionview_inverse.value; @@ -20886,6 +20894,18 @@ function add_plot(scene, plot_data) { const { space , markerspace } = plot_data; plot_data.uniforms.preprojection = cam.preprojection_matrix(space.value, markerspace.value); } + if (scene.camera_relative_light) { + plot_data.uniforms.light_direction = cam.light_direction; + scene.light_direction.on((value)=>{ + cam.update_light_dir(value); + }); + } else { + const light_dir = new mod.Vector3().fromArray(scene.light_direction.value); + plot_data.uniforms.light_direction = new mod.Uniform(light_dir); + scene.light_direction.on((value)=>{ + plot_data.uniforms.light_direction.value.fromArray(value); + }); + } const p = deserialize_plot(plot_data); plot_cache[plot_data.uuid] = p; scene.add(p); @@ -21135,6 +21155,8 @@ function deserialize_scene(data, screen) { scene.backgroundcolor = data.backgroundcolor; scene.clearscene = data.clearscene; scene.visible = data.visible; + scene.camera_relative_light = data.camera_relative_light; + scene.light_direction = data.light_direction; const camera = new MakieCamera(); scene.wgl_camera = camera; function update_cam(camera_matrices) { @@ -21142,8 +21164,9 @@ function deserialize_scene(data, screen) { camera.update_matrices(view, projection, resolution, eyepos); } update_cam(data.camera.value); + camera.update_light_dir(data.light_direction.value); if (data.cam3d_state) { - attach_3d_camera(canvas, camera, data.cam3d_state, scene); + attach_3d_camera(canvas, camera, data.cam3d_state, data.light_direction, scene); } else { data.camera.on(update_cam); } diff --git a/src/lighting.jl b/src/lighting.jl index 20f64a075d5..f2e7f573fe5 100644 --- a/src/lighting.jl +++ b/src/lighting.jl @@ -92,8 +92,17 @@ Availability: struct DirectionalLight <: AbstractLight color::Observable{RGBf} direction::Observable{Vec3f} -end + # Usually a light source is placed in world space, i.e. unrelated to the + # camera. As a default however, we want to make sure that an object is + # always reasonably lit, which requires the light source to move with the + # camera. To keep this in sync in WGLMakie, the calculation needs to happen + # in javascript. This flag notives WGLMakie and other backends that this + # calculation needs to happen. + camera_relative::Bool + + DirectionalLight(col, dir, rel = false) = new(col, dir, rel) +end light_type(::DirectionalLight) = LightType.DirectionalLight light_color(l::DirectionalLight) = l.color[] diff --git a/src/scenes.jl b/src/scenes.jl index 35201531156..f3cb641c7ae 100644 --- a/src/scenes.jl +++ b/src/scenes.jl @@ -243,33 +243,23 @@ function Scene(; end if lights isa Automatic - haskey(m_theme, :lightposition) && @warn("`lightposition` is deprecated. Set `lightdirection` instead.") + haskey(m_theme, :lightposition) && @warn("`lightposition` is deprecated. Set `light_direction` instead.") if haskey(m_theme, :lights) copyto!(scene.lights, m_theme.lights[]) else - @assert haskey(m_theme, :lightdirection) "Theme must contain `lightdirection`!" - @assert haskey(m_theme, :lightcolor) "Theme must contain `lightcolor`!" + haskey(m_theme, :light_direction) || error("Theme must contain `light_direction::Vec3f` or an explicit `lights::Vector`!") + haskey(m_theme, :light_color) || error("Theme must contain `light_color::RGBf` or an explicit `lights::Vector`!") + haskey(m_theme, :camera_relative_light) || @warn("Theme should contain `camera_relative_light::Bool`.") if haskey(m_theme, :ambient) push!(scene.lights, AmbientLight(m_theme[:ambient][])) end - if m_theme[:lightdirection][] === automatic - lightdirection = map(scene.camera.view) do view - T = inv(view[Vec(1,2,3), Vec(1,2,3)]) - # Vec is equvalent to 36° right/east, 39° up/north from camera position - return T * Vec3f(-0.6287243, -0.45679495, -0.6293204) - end - elseif m_theme[:lightdirection][] in (:viewdir, :view_direction, :camdir, :camera_direction) - lightdirection = map(-, scene.camera.lookat, scene.camera.eyeposition) - elseif m_theme[:lightdirection][] isa VecTypes{3} - lightdirection = m_theme[:lightdirection] - else - error("Wrong lightdirection type, use `:viewdir` or `Vec3f(...)`") - end - - push!(scene.lights, DirectionalLight(m_theme[:lightcolor][], lightdirection)) + push!(scene.lights, DirectionalLight( + m_theme[:light_color][], m_theme[:light_direction], + to_value(get(m_theme, :camera_relative_light, false)) + )) end end diff --git a/src/theming.jl b/src/theming.jl index 37afeb582de..a379f44830d 100644 --- a/src/theming.jl +++ b/src/theming.jl @@ -70,10 +70,11 @@ const MAKIE_DEFAULT_THEME = Attributes( ), inspectable = true, - - lightdirection = automatic, + # Vec is equvalent to 36° right/east, 39° up/north from camera position + light_direction = Vec3f(-0.6287243, -0.45679495, -0.6293204), + camera_relative_light = true, # Only applies to default DirectionalLight + light_color = RGBf(0.5, 0.5, 0.5), ambient = RGBf(0.35, 0.35, 0.35), - lightcolor = RGBf(0.5, 0.5, 0.5), # Note: this can be set too # lights = AbstractLight[