Skip to content

Commit

Permalink
move camera-relative light dir calc to backends
Browse files Browse the repository at this point in the history
  • Loading branch information
ffreyer committed Oct 11, 2023
1 parent 0d74694 commit 4343b62
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 48 deletions.
26 changes: 17 additions & 9 deletions CairoMakie/src/primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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]
Expand All @@ -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
Expand Down
29 changes: 21 additions & 8 deletions GLMakie/src/drawing_primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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

Expand Down
6 changes: 6 additions & 0 deletions RPRMakie/src/scene.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 14 additions & 1 deletion WGLMakie/src/Camera.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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;
Expand Down
20 changes: 19 additions & 1 deletion WGLMakie/src/Serialization.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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();

Expand All @@ -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);
}
Expand Down
10 changes: 5 additions & 5 deletions WGLMakie/src/serialization.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)]
Expand Down
27 changes: 25 additions & 2 deletions WGLMakie/src/wglmakie.bundled.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -21135,15 +21155,18 @@ 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) {
const [view, projection, resolution, eyepos] = camera_matrices;
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);
}
Expand Down
11 changes: 10 additions & 1 deletion src/lighting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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[]

Expand Down
26 changes: 8 additions & 18 deletions src/scenes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 4 additions & 3 deletions src/theming.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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[
Expand Down

0 comments on commit 4343b62

Please sign in to comment.