Add vertex shader support

#19 Merged
  1. Alex Szpakowski

Vertex shaders, while not as useful as fragment shaders in 2D games, are still important for improving shader performance (why do a calculation 200,000 times when you can do it 4 times and interpolate the result over the pixels?), for using vertex positions in shaders (interpolated or otherwise), or any number of other things.

Using a vertex shader, you could even make LÖVE do hardware-accelerated 3D perspective projection, although that's certainly not a major reason to add vertex shaders in LÖVE.

The changes in this fork do several things:

  • Support for vertex shaders has been added
  • All instances of 'PixelEffect' have been renamed to 'Shader' (but there are compatibility functions with the old names). For example, is now
  • Performance when sending values, especially images and canvases, to shaders has been improved
  • Bugs in LÖVE's shader code related to sending images and canvases to shaders have been fixed
  • Shader errors now always produce a proper line number
  • Performance when drawing graphics primitives such as points or polygons has been improved (regardless of whether a shader is active)
  • Shaders now have the capability to access extern textures when untextured graphics primitives are drawn
  • Using the Texel (texture2D) function in a shader when drawing a graphics primitive now returns pure white instead of pure black, so people don't always have to use separate shaders when drawing primitives vs images
  • Some shader syntax has been standardized to be less related to which GLSL version the shader is using, which means shader code written by lovers will have almost guaranteed cross-compatibility with future OpenGL ES 2+ implementations of LÖVE
  • GLee (the OpenGL extension loader used by LÖVE) has been updated to the latest available version so it can recognize OpenGL versions and extensions up to GL ~4.2. The previously used version only recognized up to GL ~3.0
  • Adding additional shader stages (geometry, tessellation, or compute shaders) to future LÖVE versions has been made easier, should the want ever arise
  • Shader:send (and extern values in shaders) now support boolean, boolean array, and boolean vector values

The shader API has all the old PixelEffect functions (which now point to the new stuff). The new stuff has been named "Shader" instead of "PixelEffect". newShader has been changed like so: shader =[vertexcode], [fragmentcode]) where either argument is optional but at least one is necessary.

LÖVE will automatically detect whether the text passed into either parameter of newShader is fragment or vertex shader code, and act accordingly. For example, and will both work, although it is better to stick to the proper argument order.

Additionally, vertex and fragment shader code can be combined into a single text string (or file) and passed as a single argument, and LÖVE will parse it properly as long as it's formatted correctly.

The default "pass-through" LÖVE vertex shader (mimics no vertex shader) is as follows:

vec4 position(mat4 mvp_matrix, vec4 vertex)
    return mvp_matrix * vertex;

It simply projects the incoming vertex from its local coordinates to "clip-space", as required by OpenGL.

As mentioned, vertex and fragment shaders can be combined into a single argument, provided there is #ifdef VERTEX around the position function, and #ifdef PIXEL around the effect function. It needs the ifdef's due to how OpenGL compiles and links shader code into shader program objects.

You can see an example of a combined shader here:

LÖVE's wrapper for the vertex shader code makes sure the vertex color and texture coordinates always get sent properly to the fragment shader, since it's required for most uses of LÖVE. The lover's vertex shader code is called after this happens, so it can overwrite the assigned color/texcoord values if it wants.

Here is another example of a simple vertex shader:

extern float time;

vec4 position(mat4 mvp_matrix, vec4 vertex)
    vec4 projectedvertex = mvp_matrix * vertex;

    projectedvertex.x *= 0.5 + 0.5 * cos(time + vertex.x + vertex.y);
    projectedvertex.y *= 0.5 + 0.5 * sin(time + vertex.y + vertex.x);

    return projectedvertex;

GLSL 1.2 comes with some built-in uniform (extern) values, which are more useful in the vertex shader than the fragment shader. I have #define'd new names for them to keep compatibility with potential GLES2 implementations.

The built in uniforms are named as follows:

mat4 ModelViewMatrix;
mat4 ProjectionMatrix;
mat4 ModelViewProjectionMatrix;
mat3 NormalMatrix;

I have also standardized the names for the default vertex attribute and interpolated varying variables, which are normally only used internally by LÖVE but can also be used by lovers in shader code, if they want.

Vertex shader:

vec4 VertexPosition;
vec4 VertexTexCoord;
vec4 VertexColor;

Both shaders:

vec4 VaryingTexCoord;
vec4 VaryingTexColor;

Comments (1)