Shaders
Color surfaces with a ready-made shader, or write a fragment program of your own and feed it values while it runs.
Apply a preset
shader.new makes a shader of one of the built-in kinds, picked by its
type: gradients, stripes, noise, glows, frosted glass and more. One
Shader works on every surface that takes a material: a UI panel, a
shape on a draw canvas and a mesh in a 3D scene. A mesh shows the
shader's color as it is, unlit: the scene's lights do not shade it. On a
mounted canvas or scene as a whole, it processes the finished image.
This program makes one stripe shader and applies it to all three kinds of surface:
local color = datumhue.color
local vec3 = datumhue.math.vec3
local quat = datumhue.math.quat
-- Orange stripes on a dark gap: 8 stripes, tilted 0.6 radians.
local stripes = datumhue.shader.new({
type = "stripe",
color1 = color("#e69875"),
color2 = color("#2d353b"),
param1 = 8,
param2 = 0.6,
})
local row = datumhue.ui.root:panel({
flex_direction = "row",
gap = 24,
padding = 24,
})
-- A UI panel.
row:panel({ width = 160, height = 160, material = stripes })
-- A shape on a draw canvas.
local canvas = datumhue.draw.new({ width = 160, height = 160 })
canvas:mount({ parent = row, width = 160, height = 160 })
canvas:circle({ radius = 70, material = stripes })
-- A mesh in a 3D scene.
local scene = datumhue.scene.new({ width = 160, height = 160 })
scene:mount({ parent = row, width = 160, height = 160 })
scene:cube({
material = stripes,
rotation = quat.from_euler(0.4, 0.6, 0),
})
scene.camera.pos = vec3(0, 0, 2.5)
scene.camera:look_at(vec3(0, 0, 0))
In this example: datumhue.color datumhue.math.vec3 datumhue.math.quat datumhue.shader.new datumhue.ui.root UiElement:panel datumhue.draw.new DrawCanvas:mount DrawCanvas:circle datumhue.scene.new Scene:mount Scene:cube datumhue.math.quat.from_euler SceneCamera.pos Scene.camera SceneCamera:look_at
What color1, color2, param1 and param2 mean depends on the
kind; the theme format's shader table lists them.
Shader:update changes them later, and every surface using the shader
follows.
Write your own
Bytes:shader compiles a fragment program written in WGSL. The program
defines dh_fragment, which takes uv, the position inside the shaded
surface from 0 to 1 on each axis, and returns the color there. It can
read:
dh.time: seconds, for animation;dh.resolution: the size of the surface it is shading;dh.mouse: the pointer over that surface, its position inxyand its two main buttons inzw;u.<name>: the uniforms you declare when you compile it, numbers, vectors or colors;dh_channel0todh_channel3: textures, sampled withdh_sampler0todh_sampler3.
Every surface you apply the shader to gets its own dh.resolution and
dh.mouse, so one shader can react to the pointer over each of them.
On a 3D mesh, a shader of your own can also be the base color of a lit
material: pass it as the shader of material.standard. The scene's
lights, shadows and fog then shade it as they shade a texture, the
material's color multiplies it, and metallic, roughness and the
material's other options apply as they do without a shader.
This program draws two moving sine waves, mixed into two colors, and sets their speed from a slider:
local color = datumhue.color
-- u.speed, u.low and u.high are the uniforms declared below.
local source = datumhue.bytes([[
fn dh_fragment(uv: vec2<f32>) -> vec4<f32> {
let t = dh.time * u.speed;
let v = sin(uv.x * 10.0 + t) + sin(uv.y * 8.0 - t);
let k = 0.5 + 0.25 * v;
return vec4<f32>(mix(u.low.rgb, u.high.rgb, k), 1.0);
}
]])
local plasma = source:shader({
uniforms = {
speed = 1.5,
low = color("#2d353b"),
high = color("#83c092"),
},
})
local box = datumhue.ui.root:panel({
flex_direction = "column",
align_items = "center",
gap = 12,
padding = 16,
})
box:panel({ width = 320, height = 200, material = plasma })
box:label({ text = "Speed" })
box:slider({
width = 240,
min = 0,
max = 6,
value = 1.5,
on_change = function(value)
plasma:set("speed", value)
end,
})
In this example: datumhue.color datumhue.bytes Bytes:shader datumhue.ui.root UiElement:panel UiElement:label UiElement:slider Shader:set
Shader:set writes a uniform, and every surface using the shader
updates. Shader:set_channel binds an image, a draw canvas or a scene
as a channel texture, so a shader can blend layers, distort a picture or
show a scene through a lens. Channels are numbered from 1 in Lua:
set_channel(1, ...) fills dh_channel0.
Edit a shader while it runs
A program that does not compile raises, naming its failing line. So
does a program that nests too deep: more than 127 blocks inside each
other, counting the blocks of @if, @elif and @else, or more than
255 operators and brackets chained in one expression. A program with
more than 198 blocks and brackets open at once can also be too deep to
parse.
In x = -(a + b.y);, the =, -, (, + and . chain five deep,
and an operator of two characters, such as &&, counts once. A ; ends
a chain, and so does a , inside brackets, but after the closing
bracket the chain goes on from the deepest part inside. Each else adds
one level to the rest of its if statement.
Shader:reload compiles new source into the same shader, keeping its
uniforms and channels. When the new source fails, the old program keeps
drawing, so you can reload as you save and fix mistakes as you go.
local dir = datumhue.package:dir()
local glow = dir:read("glow.wgsl"):shader({ uniforms = { level = 1 } })
-- Later, after editing glow.wgsl:
local ok, err = pcall(function()
glow:reload(dir:read("glow.wgsl"))
end)
if not ok then
print("glow.wgsl: " .. tostring(err))
end
Shade the whole UI
A theme file can define shaders and give them to every button or panel, with no code in your app. See the widget shaders of the theme format, and Theme and density for using a theme file.
See also
Reference used on this page: datumhue.color datumhue.math.vec3 datumhue.math.quat datumhue.shader.new datumhue.ui.root UiElement:panel datumhue.draw.new DrawCanvas:mount DrawCanvas:circle datumhue.scene.new Scene:mount Scene:cube datumhue.math.quat.from_euler SceneCamera.pos Scene.camera SceneCamera:look_at datumhue.bytes Bytes:shader UiElement:label UiElement:slider Shader:set datumhue.package Package:dir Dir:read Shader:reload Shader:update datumhue.material.standard Shader:set_channel