Skip to content

Instantly share code, notes, and snippets.

@lsh
Created November 2, 2020 18:46
Show Gist options
  • Save lsh/b0eeaaa498d20765770a6e86fcae483e to your computer and use it in GitHub Desktop.
Save lsh/b0eeaaa498d20765770a6e86fcae483e to your computer and use it in GitHub Desktop.
### A Pluto.jl notebook ###
# v0.12.6
using Markdown
using InteractiveUtils
# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
quote
local el = $(esc(element))
global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
el
end
end
# ╔═╡ 03e6d8a4-1c0d-11eb-23c7-57525308d771
begin
using Colors
using ImageShow
# On MacOS I also neededed QuartzImageIO installed
#using Pkg;Pkg.add("QuartzImageIO")
end
# ╔═╡ cc2522f4-1d33-11eb-386f-f9530f5a9fbb
using Pipe
# ╔═╡ df7c6b84-1d33-11eb-0a40-112e53d1896d
md"""
# Distance Functions in Julia
I'm a big fan of [The Book of Shaders](https://thebookofshaders.com/). It was a driving force in helping me understand using algorithms for parallelization. I recently realized Julia has a ton of utilities that allow for working in a similar fashion to how I code fragment shaders. Below you'll see me play around with some distance functions like those found in the book.
—Lukas ([gh](https://github.com/lsh)|[tw](https://twitter.com/lukashermann_))
"""
# ╔═╡ 9f27ea9c-1d34-11eb-00ce-c133f2dac376
md"""
The [Colors.jl](https://github.com/JuliaGraphics/Colors.jl) package is gonna do most of the heavy lifting here. It has a few utilities that make it incredibly easy to convert a matrix into an image.
"""
# ╔═╡ 395d9aae-1c7b-11eb-1c1f-e7288114c806
begin
const w = 256
const h = 256
end
# ╔═╡ d57981aa-1d34-11eb-14fa-510a71100a21
md"""For example, we can make a series of random pixels by [broadcasting](https://docs.julialang.org/en/v1/manual/arrays/#Broadcasting) the Gray constructor across a matrix"""
# ╔═╡ 09aa9cfc-1d35-11eb-1752-65d28d61158f
Gray.(rand(w,h))
# ╔═╡ 8a6e0e34-1d35-11eb-3c7d-b92084530089
md"""
To use this functionality like we do in shaders, we need to generate UV coordinates. We divide by the width and height so that our coordinates range from $$[0,1]$$.
_Note: if this wasn't a square image you'd have to aspect adjust_
"""
# ╔═╡ 56806ab6-1c81-11eb-019d-75b4a853f71c
st = [(i/h,j/w) for i in 0:h, j in 0:w]
# ╔═╡ 17c13afc-1d36-11eb-1591-cdb9bc262011
md"""
Now we can access the coordinates with `st[1]` for the x position and `st[2]` for the y position.
We can use the same method of visualization as above to see the coordinates change.
"""
# ╔═╡ 64145664-1d36-11eb-043e-dd63013bac25
(c -> Gray(c[1])).(st)
# ╔═╡ a01df1d8-1d36-11eb-39e5-433d81f6968b
(c -> Gray(c[2])).(st)
# ╔═╡ b2f8c620-1d36-11eb-3cc0-13d285d57b45
md"""
Above you'll notice that we're using [anonymous functions](https://docs.julialang.org/en/v1/manual/functions/#man-anonymous-functions) to exctract the x and y positions. We then broadcast this function across the matrix.
To view these together, we can use the RGB function. We'll just set the blue value to `1`.
"""
# ╔═╡ a2b22428-1c7d-11eb-2d45-31d95434244d
(c -> RGB(c[1], c[2], 1)).(st)
# ╔═╡ 3927ba58-1d37-11eb-0eb3-85f77dc09be9
md"""
Now to draw shapes with distance functions we need a way to actually measure distance. If we want to draw a circle for instance, we measure the distance of points from a radius.
$$c = \sqrt{x^2+y^2}$$
Here we'll define it so it can take both an `xy` pair or `x,y`
"""
# ╔═╡ 460c8e1c-1c7c-11eb-1873-2da2ead2fb32
begin
length(x::Float64, y::Float64)::Float64 = sqrt(x^2 + y^2)
length(xy::Tuple{Float64,Float64})::Float64 = sqrt(xy[1]^2 + xy[2]^2)
end
# ╔═╡ 6454452e-1d38-11eb-2ab6-571f07accff5
md"""
Now let's try it out!
"""
# ╔═╡ 6ceb28da-1c78-11eb-0e10-2dd217346811
Gray.(length.((c -> c.-0.5).(st)))
# ╔═╡ b341c97c-1d38-11eb-0dd8-8dcc6a46287d
md"""We subtract 0.5 from `st` so that it's range is $$[-0.5, 0.5]$$. This means the length is compared to the center. You could change it to just`Gray.(length.(st))`, but it would be measuring from the top left corner instead.
Another useful function for drawing with shapes is `step`, which takes a value and a threshold, then sets the value to 0 or 1 depending on if it's greater than that threshold.
"""
# ╔═╡ dfbc6ece-1c7c-11eb-0558-6525bd0cf44a
step(val::Float64, thresh::Float64)::Float64 = val < thresh ? zero(val) : one(val)
# ╔═╡ 7c9b8c7c-1d39-11eb-32db-c9cfc82b792a
md"""We can use step to cut off the circle at a radius. Here we'll use a Pluto bound slider to play with the radius."""
# ╔═╡ cd5d4fb0-1d39-11eb-3c1d-d33c06938880
rad_slider = @bind rad html"<input type='range' min='0.0' max='1' step='0.01' value='0.15'>"
# ╔═╡ d98a438c-1c7c-11eb-0e3f-e513e5a5e275
Gray.((c -> step(c, rad)).((c -> length(c.-0.5)).(st)))
# ╔═╡ 9b760860-1d3a-11eb-158e-9d7169d4ee63
md"""One way we can visualize a distance function is by taking the sin of it mulitplied by a value."""
# ╔═╡ 4362dfb2-1c7d-11eb-3ae6-a5b52503852d
Gray.((c -> sin(c*50)).((c -> length(c.-0.5)).(st)))
# ╔═╡ cc583b06-1d3a-11eb-0eed-7b77c9a89eff
md"""Another useful trick with distance functions is that we can union them through taking the minimum of two distance functions. For more information on distance function operations, check out [Inigo Quilez's page on it](https://iquilezles.org/www/articles/distfunctions/distfunctions.htm)."""
# ╔═╡ 800e76e8-1c81-11eb-0e7a-ff16cf9d08e2
begin
c1 = (f -> length(f[1]-.45, f[2]-.45)).(st)
c2 = (f -> length(f[1]-.80, f[2]-.70)).(st)
c3 = (f -> length(f[1]-.90, f[2]-.20)).(st)
c4 = (f -> length(f[1]-.25, f[2]-.25)).(st)
fc = min.(min.(min.(c1, c2), c3), c4)
Gray.(fc)
end
# ╔═╡ 0f1e950c-1d3b-11eb-36ee-c7b02872fd3a
md"""Just like length and sin, we can broadcast min and abs across the matrices as well."""
# ╔═╡ 59438c84-1c80-11eb-1f8c-2763d3f0f451
begin
df = abs.(sin.(fc.*100))
Gray.(df)
end
# ╔═╡ 6c280132-1c93-11eb-334f-ad915af4a853
Gray.((f -> step(f, 0.25)).(df))
# ╔═╡ 4954ca84-1d3b-11eb-0e25-7b5c6de2f69c
md"""As a final note, some of these functions are getting a little crazy, and simialarly to how I broke it up above, you can use the [Pipe](https://github.com/oxinabox/Pipe.jl) package to make a clean chain of operations"""
# ╔═╡ e1f80b2e-1c92-11eb-1c6e-8739cb21708d
@pipe st |>
(f -> 1.0-length(f.-0.5)).(_) |>
(f -> sin.(f*25)).(_) |>
(f -> 1.0 - step(f, .5)).(_) |>
(f -> ([0.7, 0.85, 0.25]).*f).(_) |>
(f -> RGB(f[1], f[2], f[3])).(_)
# ╔═╡ Cell order:
# ╟─df7c6b84-1d33-11eb-0a40-112e53d1896d
# ╠═03e6d8a4-1c0d-11eb-23c7-57525308d771
# ╟─9f27ea9c-1d34-11eb-00ce-c133f2dac376
# ╠═395d9aae-1c7b-11eb-1c1f-e7288114c806
# ╠═d57981aa-1d34-11eb-14fa-510a71100a21
# ╟─09aa9cfc-1d35-11eb-1752-65d28d61158f
# ╟─8a6e0e34-1d35-11eb-3c7d-b92084530089
# ╠═56806ab6-1c81-11eb-019d-75b4a853f71c
# ╟─17c13afc-1d36-11eb-1591-cdb9bc262011
# ╠═64145664-1d36-11eb-043e-dd63013bac25
# ╠═a01df1d8-1d36-11eb-39e5-433d81f6968b
# ╟─b2f8c620-1d36-11eb-3cc0-13d285d57b45
# ╠═a2b22428-1c7d-11eb-2d45-31d95434244d
# ╟─3927ba58-1d37-11eb-0eb3-85f77dc09be9
# ╠═460c8e1c-1c7c-11eb-1873-2da2ead2fb32
# ╟─6454452e-1d38-11eb-2ab6-571f07accff5
# ╠═6ceb28da-1c78-11eb-0e10-2dd217346811
# ╟─b341c97c-1d38-11eb-0dd8-8dcc6a46287d
# ╠═dfbc6ece-1c7c-11eb-0558-6525bd0cf44a
# ╟─7c9b8c7c-1d39-11eb-32db-c9cfc82b792a
# ╟─cd5d4fb0-1d39-11eb-3c1d-d33c06938880
# ╠═d98a438c-1c7c-11eb-0e3f-e513e5a5e275
# ╟─9b760860-1d3a-11eb-158e-9d7169d4ee63
# ╠═4362dfb2-1c7d-11eb-3ae6-a5b52503852d
# ╟─cc583b06-1d3a-11eb-0eed-7b77c9a89eff
# ╠═800e76e8-1c81-11eb-0e7a-ff16cf9d08e2
# ╟─0f1e950c-1d3b-11eb-36ee-c7b02872fd3a
# ╠═59438c84-1c80-11eb-1f8c-2763d3f0f451
# ╠═6c280132-1c93-11eb-334f-ad915af4a853
# ╟─4954ca84-1d3b-11eb-0e25-7b5c6de2f69c
# ╠═cc2522f4-1d33-11eb-386f-f9530f5a9fbb
# ╠═e1f80b2e-1c92-11eb-1c6e-8739cb21708d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment