Packages

shader

Write GPU shaders in OSL's syntax and turn them into GLSL for raylib windows.

import "std:shader"

A shader is a small program that runs on the graphics card once for every pixel. GPUs read a language called GLSL. std:shader lets you write the shader in OSL-style syntax (parentheses, number, no semicolons) and turns it into GLSL, with the usual Shadertoy variables already declared (iTime, iResolution, iMouse and friends).

It only translates text. To run the result you use raylib, which compiles it and draws with it.

A shader in OSL style#

Write def mainImage(fragCoord) and return the colour of the pixel at fragCoord. transpile returns the GLSL. This prints the lines that changed:

main.osl
import "std:shader"

string code = `def mainImage(fragCoord) (
  vec2 uv = fragCoord / iResolution.xy
  number t = iTime * 1.5
  return vec4(uv, 0.5 + 0.5 * sin(t), 1.0)
)`

string glsl = shader.transpile(code)
for line in glsl.split("\n") (
  if line.contains("vec2 uv") or line.contains("float t") or line.contains("fragColor = vec4") (
    log line.trim()
  )
)
Click the code to edit it

number became float, the blocks got braces, every line got its semicolon, and the return became an assignment to fragColor. Rename t to time and run it again. The output follows.

Plain statements#

A shader doesn't need a function at all. Write statements that set fragColor, and uv is there for you (the pixel's position from 0 to 1):

main.osl
import "std:shader"

string glsl = shader.transpile("fragColor = vec4(uv, 0.0, 1.0)")
log glsl.startsWith("#version 330")
log glsl.contains("fragColor = vec4(uv, 0.0, 1.0);")
log glsl.contains("uniform float iTime;")
Click the code to edit it

Real GLSL passes through#

Code that already has #version and a main is returned unchanged, so you can mix: hand-written GLSL for the hard cases, OSL style for the rest.

main.osl
import "std:shader"

string handwritten = "#version 330\nvoid main() {}"
log shader.transpile(handwritten) == handwritten
Click the code to edit it

Vertex shaders and the built-ins#

shader.transpile(code, {stage: "vertex"}) makes a vertex shader. shader.vertexDefault() is raylib's standard one, which passes the texture coordinate and colour on. Two example fragment shaders are included:

main.osl
import "std:shader"

log shader.vertexDefault().contains("gl_Position")
log shader.plasma().contains("mainImage")
log shader.gradient().contains("iTime")
Click the code to edit it
Quick check

What does shader.transpile do with the code you give it?

Drawing with it#

Pass the code to raylib.compileShader. It accepts OSL style or GLSL, so you rarely call transpile yourself. shader.use(draw) runs the drawing calls inside it, and uniforms like iTime are set each frame. This needs a window, so it doesn't run here:

main.osl
import "std:raylib"
import "std:shader"

raylib.initWindow(640, 360, "Plasma").unwrap()
raylib.setTargetFPS(60)

auto plasma = raylib.compileShader(shader.plasma()).unwrap()

while !raylib.windowShouldClose() (
  plasma.setUniform("iTime", raylib.elapsed() / 1000).unwrap()
  plasma.setUniform("iResolution", raylib.vec(640, 360)).unwrap()
  raylib.draw(def() (
    plasma.drawFullscreen().unwrap()
  ))
)
raylib.closeWindow()
Click the code to edit it

Swap shader.plasma() for shader.gradient() and run it on your machine. A shader that the graphics driver rejects fails with a ParseError, and raylib prints the driver's message.

Your turn

Check your shader

Translate this shader, then print whether the GLSL starts with #version 330 and whether it contains float.

main.osl
Ctrl Enter runs
Expected output
true
true

API reference#

7 members, generated from the std:shader source of OSL 9.3.0.

Functions

#shader.eval(any expr, any ...args) result<number, error>

Deprecated. Deprecated: shader expressions can't be evaluated on the CPU.

eval was meant to evaluate a shader expression, but it returned sin(t), cos(t) or t depending on words in the text. It now fails with UnsupportedError.

#shader.gradient() string

gradient returns an example fragment shader in OSL style: a colour gradient that cycles with iTime.

#shader.plasma() string

plasma returns an example fragment shader in OSL style: animated plasma bands driven by iTime.

#shader.renderImage(any code, any width, any height, any ...args) result<*img.Image, error>

Deprecated. Deprecated: shaders only run on the GPU; use raylib.compileShader(code) and draw into a raylib.renderTexture.

renderImage was meant to run a shader on the CPU, but it drew one of three built-in effects chosen by words in the code instead. It now fails with UnsupportedError; run shaders on the GPU with raylib.compileShader.

#shader.toGLSL(any code, any ...args) string

Deprecated. Deprecated: use shader.transpile(code, {stage: "vertex"}) instead.

toGLSL turns OSL-style shader code into GLSL; a second argument of "vert" or "vertex" makes a vertex shader.

#shader.transpile(string code, object? options) string

transpile turns OSL-style shader code into GLSL 330 for raylib. GLSL that already has #version and main is returned unchanged. Options: stage ("fragment" or "vertex"). Defines mainImage or plain statements with fragCoord, uv and fragColor, and declares the usual Shadertoy uniforms. Throws ValueError for an unknown stage.

#shader.vertexDefault() string

vertexDefault returns raylib's standard vertex shader as GLSL 330, which passes fragTexCoord and fragColor to the fragment shader.