This is a crude way of combining GLSL shaders for use with Elm webgl.
GLSLPasta allows you to extract common functionality from shaders into reusable Components, then combine these to generate specific shaders.
This package also includes a library of common routines for generating lighting shaders, with or without normal and diffuse texture maps.
Some terminology, then a quick example.
GLSLPasta components contain blocks of code which get pasted together to form a shader. These include:
- Globals: declarations of attributes, uniforms, varyings and const values
- Functions: the text of entire functions
- Splices: snippets of code that get spliced into main()
Each component additionally specifies:
- Dependencies: a list of other Components which GLSLPasta will automatically include
- Provides: a list of Features, given as arbitrary Strings, which this component provides
- Requirements: a list of Features which this component needs
You combine a list of components together with a call to GLSLPasta.combine
, which will automatically
pull in dependencies and will check that requirements are satisfied:
combine : List Component -> String
the output of which you can pass to WebGL.unsafeShader
.
Various lighting components are provided in the module GLSLPasta.Lighting
. Some preliminary imports:
import GLSLPasta
import GLSLPasta.Lighting exposing (..)
import WebGL
You could construct a shader that interpolates between vertex normals using:
interpolateNormals =
GLSLPasta.combine [ vertex_gl_Position, vertexNoTangent ]
|> WebGL.unsafeShader
or you could construct a similar shader that uses a texture to provide normals:
textureNormals =
GLSLPasta.combine [ vertex_gl_Position, vertex_vTexCoord, vertexNoTangent ]
|> WebGL.unsafeShader
Alternatively you could construct a vertex shader that generates normals via a Tangent, Bitangent, Normal (TBN) matrix:
textureNormals =
GLSLPasta.combine [ vertex_gl_Position, vertex_vTexCoord, vertexTBN ]
|> WebGL.unsafeShader
GLSL.Lighting
provides components which implement variations of the Phong shading model, such as
fragment_lambert
, fragment_diffuse
and fragment_specular
.
You may have alternative ways of providing input to these. For example, fragment_lambert
requires
a pixelNormal
. If we are using any of the vertex shaders above, we could calculate these via
interpolation between vertices:
[ fragment_interpolatedNormal, fragment_lambert ]
However if we additionally have a normal texture map available, and we use one of the above shaders that
generates vTexCoord
, we can modify this to:
[ fragment_textureNormal, fragment_lambert ]
which will include the extra globals:
, globals =
[ Uniform "sampler2D" "textureNorm"
, Varying "vec2" "vTexCoord"
]
Similarly for diffuse maps, we could specify
[ fragment_textureDiffuse, fragment_diffuse ]
for objects with diffuse maps, or
[ fragment_constantDiffuse, fragment_diffuse ]
to generate a simple shader with constant diffuseColor
.
Note that fragment_diffuse
simply requires that some earlier Component has generated diffuseColor
. What if
this requirement is not met?
> import GLSLPasta exposing (..)
> import GLSLPasta.Lighting exposing (..)
> combine [ fragment_diffuse ]
Missing requirement diffuseColor, needed by lighting.fragment_diffuse: "<<GLSLPasta>>"
""
: String
GLSLPasta.combine
will log errors to the Javascript console, and return an empty String.
A complete Phong lighting shader generates gl_FragColor
with fragment_phong
, which requires
that earlier components have generated "ambient", "diffuse", "specular" and "attenuation".
A complete shader that supports normal and diffuse maps looks like:
normalDiffuseTextures =
GLSLPasta.combine
[ fragment_textureNormal
, fragment_lambert
, fragment_textureDiffuse
, fragment_diffuse
, fragment_ambient_03
, fragment_specular
, fragment_attenuation
, fragment_phong
]
|> WebGL.unsafeShader
whereas a simpler shader that interpolates normals and uses a constant diffuseColor looks like:
simplePhong =
GLSLPasta.combine
[ fragment_interpolatedNormal
, fragment_lambert
, fragment_constantDiffuse
, fragment_diffuse
, fragment_ambient_02
, fragment_specular
, fragment_attenuation
, fragment_phong
]
|> WebGL.unsafeShader
You define a part of a shader with type Component
:
type alias Component =
{ id : ComponentId -- used in error messages
, dependencies : Dependencies
, provides : List Feature
, requires : List Feature
, globals : List Global
, functions : List Function
, splices : List Splice
}
Most of these fields are String
s, apart from dependencies (which wraps a list of other Component
s) and
Global
s, which are:
type Global
= Attribute Type Name
| Uniform Type Name
| Varying Type Name
| Const Type Name Value
where, again, Type
, Name
and Value
are just String
s.
Once all dependencies and requirements are resolved, GLSLPasta simply generates code for the globals and fills in the functions and main() splices according to a template. The default template is:
defaultTemplate : String
defaultTemplate =
"""
precision mediump float
__PASTA_GLOBALS__
__PASTA_FUNCTIONS__
void main()
{
__PASTA_SPLICES__
}
"""
Here, __PASTA_GLOBALS__
is replaced with a all the globals from all the Components (with duplicates removed),
__PASTA_FUNCTIONS__
is replaced with all the functions from all the Components,
and __PASTA_SPLICES__
is replaced with all the splices from all the Components, in the order the list of Components.
Note that the functions and splices are replaced as arbitrary strings, and glsl-pasta makes no attempt to parse or sanity-check these.
The code in GLSLPasta.Lighting is extracted from @Zinggi's example code in elm-object-loader.