Skip to content

Creating custom g3d shaders

groverburger edited this page Dec 24, 2021 · 2 revisions

Creating custom g3d shaders

Written for g3d version 1.5.1

When working in 3D, you need to understand the concept of vertex and fragment shaders. Vertex shaders operate on the vertices of the mesh, and allow you to use the GPU to operate on and transform many vertices in parallel. Fragment shaders allow you to change how the mesh gets rendered into pixels on the screen.

When your GPU goes to render a mesh, the vertex shader operates on the vertices first and tells the GPU where the mesh should end up on the screen. After vertex shader is finished, the fragment shader describes to the GPU how to render the pixels of the mesh.

Programming shaders in 3D is especially fun as you can mix and match different vertex and fragment shaders to create unique effects.

Creating a custom fragment shader

A g3d fragment shader can be written just like a normal LOVE fragment shader, although you have access to more varying variables created in g3d's vertex shader.

These variables include:

  • [vec4 worldPosition] stores the x, y, z, and w coordinates of the current pixel in world-space
  • [vec4 viewPosition] stores the x, y, z, and w coordinates of the current pixel in view-space (camera-relative space)
  • [vec4 screenPosition] stores the x, y, z, and w coordinates of the current pixel in screen-space (where the pixel is on the screen)
  • [vec3 vertexNormal] stores the normal of the current pixel (interpolated between the normals of the vertices of the face it's on)
  • [vec4 vertexColor] stores the color of the current pixel (interpolated between the colors of the vertices of the face it's on)

I recommend putting each shader in its own file, as then you can use GLSL syntax highlighting in your editor and more easily mix and match vertex and fragment shaders.

Loading a custom fragment shader

When loading a shader for g3d, specify the path to the vertex shader first and the path to the fragment shader second. Unless you have a custom vertex shader that you want to use, reference g3d's built-in vertex shader by using g3d.shaderpath as the vertex shader argument as shown.

local myShader = love.graphics.newShader(g3d.shaderpath, "path/to/my/shader.frag")

You can then use your shader to render a g3d model by specifying the shader as an argument to the model's draw function.

myModel:draw(myShader)

Custom fragment shaders can be used for many interesting effects in 3D, such as lighting, shadows, or procedural textures. To learn more about creating advanced features such as lighting, I'd recommend checking out this website: https://learnopengl.com/Lighting/Basic-Lighting

Creating a custom vertex shader

Creating vertex shaders in 3D is slightly more complicated than fragment shaders. This is because no matter what 3D vertex shader you want to write, it must project your 3D vertex onto your 2D screen.

To accomplish this, it's a good idea to implement the same four uniform variables that g3d does:

uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
uniform bool isCanvasEnabled;

Implementing these four variables allows the vertex shader to automatically work with g3d. Furthermore, the vertex position should be transformed by these matrices to result in the screen position like so:

vec4 screenPosition = projectionMatrix * viewMatrix * modelMatrix * vertexPosition;

if (isCanvasEnabled) {
    screenPosition.y *= -1.0;
}

return screenPosition;

The screen position should also be mirrored vertically when the canvas is enabled, and returned as the final result of the shader.

Custom vertex shaders can be used for many interesting and useful effects in 3D such as ocean wave simulation, making leaves sway in the wind, and skeletal animation.

Loading a custom vertex shader

Loading a custom vertex shader happens just like loading a custom fragment shader, except you specify your custom vertex shader in the first argument. One thing to note is that you must specify a fragment shader, even if you don't want to use a custom one.

local myShader = love.graphics.newShader("path/to/my/vertex/shader.vert", "some/fragment/shader/here.frag")

If you don't want to use a custom fragment shader, just load this default template in instead:

vec4 effect(vec4 color, Image tex, vec2 texcoord, vec2 pixcoord) {
    vec4 texcolor = Texel(tex, texcoord);
    
    // get rid of transparent pixels
    if (texcolor.a == 0.0) {
        discard;
    }
    
    return texcolor * color;
}

You can then use your shader to render a g3d model by specifying the shader as an argument to the model's draw function.

myModel:draw(myShader)