This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Extending Shader Code
Shader code can be extended the way Lisp is: with macros, and with other names for types. Together they let shader code use a Lisp library's names, which is what the cl-gsl/gmath system does for cl-gmath. When the shader language lacks something GLSL has, glsl writes GLSL by hand.
Shader macros
define-macro defines a shader macro, as defmacro defines a Lisp macro. A form naming it is replaced by what its body returns, and the replacement is checked in its place.
(define-macro squared (x)
"X times itself, evaluated once."
(let ((value (gensym)))
`(let ((,value ,x)) (* ,value ,value))))
(define-function falloff ((d :float))
(/ 1 (+ 1 (squared d))))
- The lambda list is a full macro lambda list:
&optional,&key,&rest,&bodyand destructuring work. - The body is Lisp, run when shader code that uses the macro is checked.
- The body may start with a docstring, which
shader-documentationreturns. - A form whose arguments do not fit the lambda list is a
shader-error. - Shader macros live in their own namespace. Defining one does not define a Lisp macro, and Lisp macros are not expanded in shader code: a call of one is taken for a shader function that is not defined yet.
Local macros. In shader code, macrolet defines local shader macros and symbol-macrolet defines symbol macros, as in Lisp:
(define-function doubled ((v :vec3))
(macrolet ((twice (a) `(* 2 ,a)))
(twice v)))
(define-function set-red ((v :vec3))
(symbol-macrolet ((red (x v)))
(setf red 1)
v))
Type aliases
(define-type-alias alias type) makes the symbol alias another name for a shader type, in parameter lists and the:
(define-type-alias colour :vec3)
(define-function grey ((c colour))
(vec3 (dot c (vec3 0.2126 0.7152 0.0722))))
Inline GLSL
glsl is the escape hatch for what the shader language lacks:
(glsl type control argument ...)
type is the shader type of the result, or :void for a statement. control is a string written in the code, with a ~A for the GLSL of each argument, in order, and ~~ for a tilde. cl-gsl checks the arguments but not the GLSL.
(define-function self-dot ((v :vec3))
(glsl :float "dot(~A, ~A)" v v))
GLSL that glslang rejects becomes a glslang-error, which carries glslang's log and the GLSL.
cl-gsl/gmath
Loading cl-gsl/gmath lets shader code use cl-gmath's names, meaning what they mean in Lisp:
- cl-gmath's types become type aliases:
gm:vec2,gm:vec3andgm:vec4for:vec2,:vec3and:vec4,gm:mat3andgm:mat4for:mat3and:mat4, andgm:quatfor:vec4. - Its vector, matrix and quaternion functions become shader macros, with the same arguments, less the
resultargument that Lisp callers may pass.
Code written with cl-gmath's names is then shader code too:
(asdf:load-system :cl-gsl/gmath)
(defpackage #:example
(:use #:cl #:cl-gsl)
(:local-nicknames (#:gm #:cl-gmath)))
(in-package #:example)
(define-function lambert ((normal gm:vec3) (light gm:vec3))
(max (gm:vec3-dot (gm:vec3-normalize normal) light) 0))
What is covered
| cl-gmath | In shader code |
|---|---|
make-vec2, make-vec3, make-vec4 |
constructors of the components given by keyword, which default to 0, as in Lisp |
vec2-x to vec4-w, quat-x to quat-w |
x, y, z, w, places |
vecN+, vecN-, vecN*, vecN/ |
+, -, * and / per component |
vecN-scale, vecN-negate |
(* v s), (- v) |
vecN-dot, vec3-cross |
dot, cross |
vecN-length, vecN-length-squared, vecN-distance |
length, the dot product with itself, distance |
vecN-normalize |
normalize |
vecN-lerp, vecN-min, vecN-max |
mix, min, max |
make-mat3, make-mat4, mat3-identity, mat4-identity |
constructors, column by column |
mat3*, mat4*, mat3*vec3, mat4*vec4 |
* |
mat3-transpose, mat4-transpose, mat3-determinant, mat4-determinant, mat3-inverse, mat4-inverse |
transpose, determinant, inverse |
mat3-from-mat4, mat3-normal-matrix |
the upper left 3×3, its inverse transpose |
mat3-transform-point-2d, mat3-transform-direction-2d |
2D transforms through a 3×3 |
mat4-transform-point, mat4-transform-direction, mat4-project-point |
3D transforms through a 4×4 |
make-quat, quat-identity, quat*, quat-conjugate, quat-dot, quat-length, quat-normalize, quat-rotate |
quaternions as :vec4s of X, Y, Z and W |
radians, degrees |
radians, degrees |
(shader-documentation 'gm:vec3-normalize) describes each.
Where GLSL differs
Some results that cl-gmath defines in Lisp are undefined on the GPU, and shader-documentation says so:
vecN-normalizeof a zero vector;mat3-inverseandmat4-inverseof a singular matrix;mat4-transform-pointgives the same result as in Lisp only for an affine matrix, whose bottom row is(0 0 0 1).