Skip to content

Commit

Permalink
Implement multiple lights and more Light types (#3246)
Browse files Browse the repository at this point in the history
* lighting experiments [skip ci]

* remove intermediate type, increase number of lights [skip ci]

* add directional light, move lightpos transform to cpu

* add attenuation

* hook up interactivity to render_tick [skip ci]

* squash parameters into one array

* add SpotLight [skip ci]

* reimplement shading [skip ci]

* minor cleanup, avoid allocs [skip ci]

* update RPRMakie, tweak SpotLight

* switch to smoothstep for SpotLight

* readd old lighting code

* work around for Observables#110

* update `shading` attribute

* more shading attribute updates

* update more shading attributes

* merge updates into one call, normalize directions

* fix directions

* add refimg tests

* fix and test specular reflections

* update docs

* remove specular reflection

* add pollevents in colorbuffer & tweak render_tick priority

* make number of lights adjustable

* cleanup some comments, todos, etc

* update NEWS [skip ci]

* fixes

* fix screen size test

* rework backlight to affect normals and apply to verbose shading

* switch to world space lighting

* integrate lighting with volume

* minor cleanup

* update backlight in other backends

* minor cleanup [skip ci]

* reduce allocations

* fix typo

* fix #2985

* minor cleanup [skip ci]

* set default shading based on number of lights

* switch shading to enum

* update some more shading attributes

* fix WGLMakie errors

* fix docs

* derive default shading at plot insertion

* update shading conversion tests

* switch to DirectionalLight by default

* update test

* fix specular reflection

* smooth out lighting edge

* fix exponent

* minor optimization [skip ci]

* update defaults

* fix matcap

* update docs

* fix WGLMakie

* cleanup print

* fix meshscatter [skip ci]

* move camera-relative light dir calc to backends

* add light edge smoothing to WGLMakie

* fix error

* move scale_matrix to model, fix dir

* do not connect transformations in different spaces

* allow scene to report multiple spaces

* allow empty lights vectors

* fix fast lighting with incomplete light setup

* move docs to references

* minor cleanup

* fix folder link

* fix typo, try fix docs

* improve example

* fix missing import

* clean up LScene reset_limit, fix test

* fix makie tests

* update WGLMakie bundle

* fix nan surface in WGLMakie

* consider center when updating camera

* fix some merge errors

* disable centering after explicit camera placement

* connect transform_marker to marker/char offsets

* fix axsi3 test (CairoMakie meshscatter transformation)

* minor performance tweak

---------

Co-authored-by: Simon <sdanisch@protonmail.com>
  • Loading branch information
ffreyer and SimonDanisch committed Oct 30, 2023
1 parent 7160cad commit b82b6f7
Show file tree
Hide file tree
Showing 80 changed files with 1,572 additions and 549 deletions.
110 changes: 65 additions & 45 deletions CairoMakie/src/primitives.jl
Expand Up @@ -496,13 +496,14 @@ end
function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Text{<:Tuple{<:Union{AbstractArray{<:Makie.GlyphCollection}, Makie.GlyphCollection}}}))
ctx = screen.context
@get_attribute(primitive, (rotation, model, space, markerspace, offset))
transform_marker = to_value(get(primitive, :transform_marker, true))::Bool
position = primitive.position[]
# use cached glyph info
glyph_collection = to_value(primitive[1])

draw_glyph_collection(
scene, ctx, position, glyph_collection, remove_billboard(rotation),
model, space, markerspace, offset, primitive.transformation
model, space, markerspace, offset, primitive.transformation, transform_marker
)

nothing
Expand All @@ -511,21 +512,23 @@ end

function draw_glyph_collection(
scene, ctx, positions, glyph_collections::AbstractArray, rotation,
model::Mat, space, markerspace, offset, transformation
model::Mat, space, markerspace, offset, transformation, transform_marker
)

# TODO: why is the Ref around model necessary? doesn't broadcast_foreach handle staticarrays matrices?
broadcast_foreach(positions, glyph_collections, rotation, Ref(model), space,
markerspace, offset) do pos, glayout, ro, mo, sp, msp, off

draw_glyph_collection(scene, ctx, pos, glayout, ro, mo, sp, msp, off, transformation)
draw_glyph_collection(scene, ctx, pos, glayout, ro, mo, sp, msp, off, transformation, transform_marker)
end
end

_deref(x) = x
_deref(x::Ref) = x[]

function draw_glyph_collection(scene, ctx, position, glyph_collection, rotation, _model, space, markerspace, offsets, transformation)
function draw_glyph_collection(
scene, ctx, position, glyph_collection, rotation, _model, space,
markerspace, offsets, transformation, transform_marker)

glyphs = glyph_collection.glyphs
glyphoffsets = glyph_collection.origins
Expand All @@ -537,7 +540,7 @@ function draw_glyph_collection(scene, ctx, position, glyph_collection, rotation,
strokecolors = glyph_collection.strokecolors

model = _deref(_model)
model33 = model[Vec(1, 2, 3), Vec(1, 2, 3)]
model33 = transform_marker ? model[Vec(1, 2, 3), Vec(1, 2, 3)] : Mat3f(I)
id = Mat4f(I)

glyph_pos = let
Expand Down Expand Up @@ -859,7 +862,7 @@ end
nan2zero(x) = !isnan(x) * x


function draw_mesh3D(scene, screen, attributes, mesh; pos = Vec4f(0), scale = 1f0)
function draw_mesh3D(scene, screen, attributes, mesh; pos = Vec4f(0), scale = 1f0, rotation = Mat4f(I))
@get_attribute(attributes, (shading, diffuse, specular, shininess, faceculling))

matcap = to_value(get(attributes, :matcap, nothing))
Expand All @@ -876,44 +879,61 @@ function draw_mesh3D(scene, screen, attributes, mesh; pos = Vec4f(0), scale = 1f
model = attributes.model[]::Mat4f
space = to_value(get(attributes, :space, :data))::Symbol
func = Makie.transform_func(attributes)

# TODO: assume Symbol here after this has been deprecated for a while
if shading isa Bool
@warn "`shading::Bool` is deprecated. Use `shading = NoShading` instead of false and `shading = FastShading` or `shading = MultiLightShading` instead of true."
shading_bool = shading
else
shading_bool = shading != NoShading
end

draw_mesh3D(
scene, screen, space, func, meshpoints, meshfaces, meshnormals, per_face_col, pos, scale,
model, shading::Bool, diffuse::Vec3f,
scene, screen, space, func, meshpoints, meshfaces, meshnormals, per_face_col,
pos, scale, rotation,
model, shading_bool::Bool, diffuse::Vec3f,
specular::Vec3f, shininess::Float32, faceculling::Int
)
end

function draw_mesh3D(
scene, screen, space, transform_func, meshpoints, meshfaces, meshnormals, per_face_col, pos, scale,
scene, screen, space, transform_func, meshpoints, meshfaces, meshnormals, per_face_col,
pos, scale, rotation,
model, shading, diffuse,
specular, shininess, faceculling
)
ctx = screen.context
view = ifelse(is_data_space(space), scene.camera.view[], Mat4f(I))
projection = Makie.space_to_clip(scene.camera, space, false)
projectionview = Makie.space_to_clip(scene.camera, space, true)
eyeposition = scene.camera.eyeposition[]
i = Vec(1, 2, 3)
normalmatrix = transpose(inv(view[i, i] * model[i, i]))

# Mesh data
# transform to view/camera space
normalmatrix = transpose(inv(model[i, i]))

local_model = rotation * Makie.scalematrix(Vec3f(scale))
# pass transform_func as argument to function, so that we get a function barrier
# and have `transform_func` be fully typed inside closure
vs = broadcast(meshpoints, (transform_func,)) do v, f
# Should v get a nan2zero?
v = Makie.apply_transform(f, v, space)
p4d = to_ndim(Vec4f, scale .* to_ndim(Vec3f, v, 0f0), 1f0)
view * (model * p4d .+ to_ndim(Vec4f, pos, 0f0))
p4d = to_ndim(Vec4f, to_ndim(Vec3f, v, 0f0), 1f0)
model * (local_model * p4d .+ to_ndim(Vec4f, pos, 0f0))
end

ns = map(n -> normalize(normalmatrix * n), meshnormals)

# Light math happens in view/camera space
pointlight = Makie.get_point_light(scene)
lightposition = if !isnothing(pointlight)
pointlight.position[]
dirlight = Makie.get_directional_light(scene)
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(0)
lightdirection = Vec3f(0,0,-1)
light_color = Vec3f(0)
end

ambientlight = Makie.get_ambient_light(scene)
Expand All @@ -924,11 +944,9 @@ function draw_mesh3D(
Vec3f(0)
end

lightpos = (view * to_ndim(Vec4f, lightposition, 1.0))[Vec(1, 2, 3)]

# Camera to screen space
ts = map(vs) do v
clip = projection * v
clip = projectionview * v
@inbounds begin
p = (clip ./ clip[4])[Vec(1, 2)]
p_yflip = Vec2f(p[1], -p[2])
Expand All @@ -938,38 +956,41 @@ function draw_mesh3D(
return Vec3f(p[1], p[2], clip[3])
end

# vs are used as camdir (camera to vertex) for light calculation (in world space)
vs = map(v -> normalize(v[i] - eyeposition), vs)

# Approximate zorder
average_zs = map(f -> average_z(ts, f), meshfaces)
zorder = sortperm(average_zs)

# Face culling
zorder = filter(i -> any(last.(ns[meshfaces[i]]) .> faceculling), zorder)

draw_pattern(ctx, zorder, shading, meshfaces, ts, per_face_col, ns, vs, lightpos, 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, lightpos, ambient, diffuse, specular, shininess)
L = normalize(lightpos .- v[Vec(1,2,3)])
diff_coeff = max(dot(L, N), 0f0)
H = normalize(L + normalize(-v[Vec(1, 2, 3)]))
spec_coeff = max(dot(H, N), 0f0)^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, lightpos, 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]
# avoid SizedVector through Face indexing
t1 = ts[f[1]]
t2 = ts[f[2]]
t3 = ts[f[3]]

# skip any mesh segments with NaN points.
if isnan(t1) || isnan(t2) || isnan(t3)
continue
Expand All @@ -984,7 +1005,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, lightpos, ambient, diffuse, specular, shininess)
_calculate_shaded_vertexcolors(N, v, c, lightdir, light_color, ambient, diffuse, specular, shininess)
end
else
c1, c2, c3 = facecolors
Expand All @@ -995,7 +1016,7 @@ function draw_pattern(ctx, zorder, shading, meshfaces, ts, per_face_col, ns, vs,
# c1 = RGB(n1...)
# c2 = RGB(n2...)
# c3 = RGB(n3...)

pattern = Cairo.CairoPatternMesh()

Cairo.mesh_pattern_begin_patch(pattern)
Expand Down Expand Up @@ -1067,25 +1088,24 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Maki

)

if !(rotations isa Vector)
R = Makie.rotationmatrix4(to_rotation(rotations))
submesh[:model] = model * R
end
submesh[:model] = model
scales = primitive[:markersize][]
for i in zorder
p = pos[i]
if color isa AbstractVector
submesh[:calculated_colors] = color[i]
end
if rotations isa Vector
R = Makie.rotationmatrix4(to_rotation(rotations[i]))
submesh[:model] = model * R
end
scale = markersize isa Vector ? markersize[i] : markersize
rotation = if rotations isa Vector
Makie.rotationmatrix4(to_rotation(rotations[i]))
else
Makie.rotationmatrix4(to_rotation(rotations))
end

draw_mesh3D(
scene, screen, submesh, marker, pos = p,
scale = scale isa Real ? Vec3f(scale) : to_ndim(Vec3f, scale, 1f0)
scale = scale isa Real ? Vec3f(scale) : to_ndim(Vec3f, scale, 1f0),
rotation = rotation
)
end

Expand Down
4 changes: 2 additions & 2 deletions GLMakie/assets/shader/fragment_output.frag
Expand Up @@ -13,7 +13,7 @@ layout(location=1) out uvec2 fragment_groupid;


in vec3 o_view_pos;
in vec3 o_normal;
in vec3 o_view_normal;

void write2framebuffer(vec4 color, uvec2 id){
if(color.a <= 0.0)
Expand All @@ -34,7 +34,7 @@ void write2framebuffer(vec4 color, uvec2 id){
// // if transparency == false && ssao = true
// fragment_color = color;
// fragment_position = o_view_pos;
// fragment_normal_occlusion.xyz = o_normal;
// fragment_normal_occlusion.xyz = o_view_normal;

// // else
// fragment_color = color;
Expand Down
4 changes: 2 additions & 2 deletions GLMakie/assets/shader/heatmap.vert
Expand Up @@ -13,7 +13,7 @@ out vec2 o_uv;
flat out uvec2 o_objectid;

out vec3 o_view_pos;
out vec3 o_normal;
out vec3 o_view_normal;

ivec2 ind2sub(ivec2 dim, int linearindex){
return ivec2(linearindex % dim.x, linearindex / dim.x);
Expand All @@ -22,7 +22,7 @@ ivec2 ind2sub(ivec2 dim, int linearindex){
void main(){
//Outputs for ssao, which we don't use for 2d shaders like heatmap/image
o_view_pos = vec3(0);
o_normal = vec3(0);
o_view_normal = vec3(0);

int index = gl_InstanceID;
vec2 offset = vertices;
Expand Down

0 comments on commit b82b6f7

Please sign in to comment.