Codea icon Shaders & Meshes

Reference › 3D mesh rendering, materials and GPU shaders

Guides

Shaders and Materials top ↑

Overview

Shaders run on the graphics processor (GPU). Use them to change a surface’s color, texture, or shape, or to update many pixels or particles at once.

Codea shaders are written in GLSL. You can use them with meshes, sprites, backgrounds, and camera effects.

myShader = shader(vsh, fsh)

myMesh.shader = myShader

myMaterial = material(shader)

myMesh.material = myMaterial

sprite(myShader, x, y, w, h)

background(myShader)

Start with shader.builder() to create a material. Use a shader definition when you need a compute shader or direct control over vertex and fragment shaders.

Graphics Pipeline

Drawing a mesh has two main stages: a vertex shader positions its vertices, and a fragment shader colors the pixels covered by its triangles.

Vertex Shaders

A vertex shader sets each vertex’s position on screen. Change it to bend grass, ripple a surface, or move particles. It can also pass values such as colors and texture coordinates to the fragment shader.

Fragment Shaders

The GPU fills in the triangles between vertices and blends the values passed from their corners. A fragment shader uses these values to calculate each covered pixel’s color and, optionally, its depth.

Compute Shaders

Compute shaders update data without drawing triangles. Use them for image effects, particle motion, or simulations.

More info: https://www.khronos.org/opengl/wiki/Compute_Shader

myComputeShader = shader.compute
[[
   #version 430
   #include <codea/common.glsl>

   NUM_THREADS(1, 1, 1)
   void main()
   {
   }
]]

Workgroups

NUM_THREADS(x, y, z) declares the number of threads in each workgroup. gl_GlobalInvocationID identifies a thread across the entire dispatch. Threads in a workgroup can share memory and synchronize with barriers; independent workgroups must not rely on execution order.

Dispatch

shader:dispatch() runs a given number of workgroups. Divide the image dimensions or buffer length by shader:workgroupSize() and round up. Check gl_GlobalInvocationID against the data size in your shader, since the last group may contain extra threads.

Image Properties

Declare an image2D property and attach an image with shader:setImage(). Use image.pixel(width, height, ...) for a simulation grid so its size is in pixels rather than display points. The GLSL format, such as rgba16f, must match the image.

When a simulation reads neighboring pixels, read from one image and write to another, then swap them. Mark them readonly and writeonly in the shader. The reaction-diffusion example below shows this.

Buffers

A shader.buffer holds data that compute and drawing shaders can share. Define a matching bufferLayout in Lua and struct in GLSL. The particle example stores position in xy and velocity in zw of a vec4. Its compute shader updates these values; its vertex shader reads them using gl_InstanceID.

Shader Definitions

A shader definition is a Lua table containing its code and settings.

Anatomy of a Shader Definition

A shader definition is broken into several parts:

  • options - enable and disable specific parts of a shader
  • properties - values passed into the shader (via uniforms)
  • passes - one or more passes that contain shader code and settingsNOTE: shaders can either have an array of passes or a single pass
Show code (27 lines)
shdr = shader
{
   properties =
   {
      {name, type, value, ...}
   },

   options =
   {
      NAME = {default, options}
   },

   passes =
   {
      {
         vertex =
         [[
            ...
         ]],

         fragment =
         [[
            ...
         ]]
      }
   }
}

Properties

Each property is a Lua table with the following form:

{<name>, <type>, <value>, ...}

<name> names the property. Start with a letter or underscore, followed by letters, digits, or underscores.

The property name directly links to a shader uniform in one or more passes

<type> is a string containing the type of property, corresponding to a GLSL uniform type

Property Types
Type NameGLSL type
"float"float
"bool"bool
"vec2"vec2
"vec3"vec3
"vec4"vec4
"texture"sampler2D

<value> is the initial value of the property

Additional property settings are available, such as

  • color = true
  • range = {low, high}
  • onSet = function(...) end

Options

Options turn parts of a shader on or off using #if and #ifdef. Use them for features that you want to skip when they are not needed.

<name> = {<default>, ...}

Options can also depend on properties or conditions.

Passes

Each pass contains shader code and its drawing settings. Use pass for a single pass or passes for a list of passes.

Materials

Objects that share a shader also share its property values. Give each object a separate material to use the same shader with different colors, textures, or other settings.

The Standard Shader

Use the built-in standard shader for common surface effects, including lighting and textures.

For more details see: material.unlit() and material.lit()

Surface Shaders

Surface shaders let you describe a surface’s color, roughness, and other properties while Codea handles the lighting. Set shadingModel in the shader definition, then provide vertex and material functions to change its shape and appearance.

The shading models are based on Google’s Filament: https://google.github.io/filament/Materials.html

Show code (30 lines)
surface = shader
{
   pass =
   {
      shadingModel = "lit",

      vertex =
      [[
         void materialVertex(inout MaterialVertexInputs material)
         {
         }
      ]],

      material =
      [[
         void material(inout MaterialInputs material)
         {
            prepareMaterial(material);

            vec2 uv = getUV0();
            material.metallic = 1.0;
            material.roughness =
               mod(floor(uv.x * 20) + floor(uv.y * 10), 2)
               * 0.5;
            material.baseColor = baseColor;
         }
      ]]
   }

}

Shading Models

The following shading models are supported:

  • unlit Basic unlit shading
  • lit Physically based lit shading model
  • custom Custom shading model

MaterialInputs Struct

The material function can set the various fields within the MaterialInputs struct to control the shading of the surface

This ranges from diffuse surface color, metal/roughness to normal mapping, ambient occlusion and more

Some are only available in specific lighting models and expect values to be within certain ranges

Material Inputs
NameTypeModelsDescription
baseColorvec4AllDiffuse albedo color
metallicfloat [0..1]LitThe metalness of the surface (zero or one)
roughnessfloat [0..1]LitPerceived smoothness (1.0) or roughness (0.0) of a surface
reflectancefloat [0..1]Lit
clearCoatfloat [0..1]Lit
clearCoatRoughnessfloat [0..1]Lit
anisotropyfloat [-1..1]Lit
anisotropyDirectionvec3 [0..1]Lit
ambientOcclusionfloat [0..1]Lit
normalvec3 [0..1]Lit
bentNormalvec3 [0..1]Lit
clearCoatNormalvec3 [0..1]Lit
emissivevec4 [0..n]Lit
postLightingColorvec4 [0..1]Lit
iorfloat [0..1]Lit
transmissionfloat [0..1]Lit
absorptionfloat [0..n]Lit
microThicknessfloat [0..n]Lit
thicknessfloat [0..n]Lit

Shader Builder

Use shader.builder() to assemble a shader by chaining commands. Package reusable pieces with shader.chunk() and combine them with add(). See Shaders for the builder commands and the example below for shader chunks.

Simulating a grid with compute shaders

Drag across the grid to paint. The Controls panel selects the material, brush size, and whether the brush creates a continuing source. One texture holds the current state and another receives the next state; swapping them after dispatch avoids reading partially updated neighbors. The mesh displays that state using the bundled rock, water, and lava images.

Simulating a grid with compute shaders

Download project Open in Codea to run this example.

Show code (628 lines)
shaders = {}

shaders.init = shader {
    options =
    {
    },

    properties =
    {
        {"image", "image2D"},
    },

    pass =
    {
        compute = [[
            #version 430
            #include <codea/common.glsl>

            layout(rgba32f) uniform image2D image;

            NUM_THREADS(16, 16, 1)
            void main()
            {
                ivec2 size = imageSize(image);
                ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);

                if ( all(lessThan(pixel.xy, size)) )
                {
                    // Store exact material IDs, independent of render color space.
                    vec2 p = vec2(pixel);
                    vec2 center = vec2(size) * vec2(0.5, 0.65);
                    vec4 value = vec4(0.0);
                    if (distance(p, center - vec2(20, 0)) < 20.0 ||
                        distance(p, center + vec2(20, 0)) < 20.0 || pixel.y == size.y - 1)
                        value.r = 1.0; // rock
                    else if (distance(p, vec2(size) * vec2(0.5, 0.2)) < 18.0)
                        value = vec4(2.0, 1.0, 0.0, 0.0); // water
                    imageStore(image, pixel, value);
                }
            }
        ]]
    }
}

shaders.constants =
[[
    //The normal, un-pressurized mass of a full water cell
    const float MaxMass = 1.5;
    //How much excess water a cell can store, compared to the cell above it
    const float MaxCompress = 0.1;
    //Ignore cells that are almost dry
    const float MinMass = 0.0001;
    const float MinFlow = 3.0;
    const float MaxSpeed = 10;
    const float SideFlow = 0.6;
    const float DownFlow = 0.99;

    const int AIR = 0;
    const int ROCK = 1;
    const int WATER = 2;
    const int LAVA = 3;
    const int SAND = 4;
    const int OBSIDIAN = 5;

    const int CENTRE = 0;
    const int UP_LEFT = 1;
    const int UP = 2;
    const int UP_RIGHT = 3;
    const int LEFT = 4;
    const int RIGHT = 5;
    const int DOWN_LEFT = 6;
    const int DOWN = 7;
    const int DOWN_RIGHT = 8;

    const float MaxFlowRate[6][6] =
    {
        {0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
        {0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
        {1.0, 0.0, 1.0, 0.0, 0.025, 0.0},
        {1.0, 0.0, 0.0, 1.0, 0.05, 0.0},
        {0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
        {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}
    };

    const float waterMask[6] = { 0, 0, 1, 0, 0, 0 };
    const float lavaMask[6] = { 0, 0, 0, 1, 0, 0 };
]]

shaders.update = shader {
    options =
    {
    },

    properties =
    {
        {"imageIn", "image2D"},
        {"imageOut", "image2D"},
    },

    includes =
    {
        constants = shaders.constants
    },

    pass =
    {
        compute = [[
            #version 430
            #include <codea/common.glsl>

            layout(rgba32f) uniform readonly image2D imageIn;
            layout(rgba32f) uniform writeonly image2D imageOut;

            #define GROUP_SIZE 16

            struct Flow
            {
                float down;
                float left;
                float right;
                float up;
            };
            shared vec4 flowCache[GROUP_SIZE+2][GROUP_SIZE+2];

            #include "constants.glsl"

            vec4 get(int x, int y)
            {
                ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);
                ivec2 neighbor = pixel + ivec2(x, y);
                if (any(lessThan(neighbor, ivec2(0))) || any(greaterThanEqual(neighbor, imageSize(imageIn))))
                    return vec4(ROCK, 0, 0, 0);
                return imageLoad(imageIn, neighbor);
            }

            void setFlow(int x, int y, vec4 v)
            {
                flowCache[gl_LocalInvocationID.x+x+1][gl_LocalInvocationID.y+y+1] = v;
            }

            vec4 getFlow(int x, int y)
            {
                return flowCache[gl_LocalInvocationID.x+x+1][gl_LocalInvocationID.y+y+1];
            }

            float getStableState(float totalMass)
            {
                if (totalMass <= 1)
                    return 1;
                else if ( totalMass < 2*MaxMass + MaxCompress )
                    return (MaxMass*MaxMass + totalMass*MaxCompress)/(MaxMass + MaxCompress);
                else
                return (totalMass + MaxCompress)/2;
            }

            void flowDown(in ivec2 offset, inout vec4 current, inout vec4 flowValues)
            {
                vec4 down = get(offset.x, offset.y + 1);
                float flowRate = MaxFlowRate[int(current.r)][int(down.r)];
                if (flowRate > 0.0)
                {
                    flowValues.r = getStableState(current.g + down.g) - down.g;
                    if (flowValues.r > MinFlow) flowValues.r *= 0.5;
                    flowValues.r = min(flowValues.r + down.g, MaxMass + MaxCompress) - down.g;
                    flowValues.r = clamp(flowValues.r, 0.0, min(MaxSpeed, current.g * DownFlow));
                    flowValues.r = min(flowValues.r, flowRate);
                    current.g -= flowValues.r;
                }
            }

            void flowLeft(in ivec2 offset, inout vec4 current, inout vec4 flowValues)
            {
                vec4 left = get(offset.x - 1, offset.y);
                float flowRate = MaxFlowRate[int(current.r)][int(left.r)];
                if (flowRate > 0.0)
                {
                    flowValues.g = (current.g - left.g) * SideFlow;
                    //if (flowValues.g > MinFlow) flowValues.g *= 0.5;
                    flowValues.g = min(flowValues.g + left.g, MaxMass + MaxCompress) - left.g;
                    flowValues.g = clamp(flowValues.g, 0, current.g);
                    flowValues.g = min(flowValues.g, flowRate);
                    current.g -= flowValues.g;
                }
            }

            void flowRight(in ivec2 offset, inout vec4 current, inout vec4 flowValues)
            {
                vec4 right = get(offset.x + 1, offset.y);
                float flowRate = MaxFlowRate[int(current.r)][int(right.r)];
                if (flowRate > 0.0)
                {
                    flowValues.b = (current.g - right.g) * SideFlow;
                    //if (flowValues.b > MinFlow) flowValues.b *= 0.5;
                    flowValues.b = min(flowValues.b + right.g, MaxMass + MaxCompress) - right.g;
                    flowValues.b = clamp(flowValues.b, 0, current.g);
                    flowValues.b = min(flowValues.b, flowRate);
                    current.g -= flowValues.b;
                }
            }

            void flowUp(in ivec2 offset, inout vec4 current, inout vec4 flowValues)
            {
                vec4 up = get(offset.x, offset.y - 1);
                if (up.r != ROCK)
                {
                    flowValues.a = current.g - getStableState(current.g + up.g);
                    if (flowValues.a > MinFlow) flowValues.a *= 0.5;
                    flowValues.a = clamp(flowValues.a, 0.0, min(MaxSpeed, current.g));
                    current.g -= flowValues.a;
                }
            }

            vec4 liquid(in ivec2 offset, in vec4 current)
            {
                vec4 flowValues = vec4(0, 0, 0, 0);

                flowDown(offset, current, flowValues);
                flowLeft(offset, current, flowValues);
                flowRight(offset, current, flowValues);
                //flowUp(offset, current, flowValues);

                setFlow(offset.x, offset.y, flowValues);

                return current;
            }

            vec4 process(ivec2 offset)
            {
                vec4 current = get(offset.x, offset.y);
                if (current.r == WATER || current.r == LAVA)
                {
                    current = liquid(offset, current);
                }
                else
                {
                    setFlow(offset.x, offset.y, vec4(0.0));
                }

                return current;
            }

            NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1)
            void main()
            {
                uvec2 size = imageSize(imageIn);
                ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);

                // Process workgroup edge cases
                if ( all(lessThan(pixel.xy, size)) )
                {
                    // Bottom edge
                    if (gl_LocalInvocationID.y == 0)
                    {
                        process(ivec2(0, -1));
                    }
                    // Top edge
                    else if (gl_LocalInvocationID.y == GROUP_SIZE-1)
                    {
                        process(ivec2(0, 1));
                    }
                    // Left edge
                    if (gl_LocalInvocationID.x == 0)
                    {
                        process(ivec2(-1, 0));
                    }
                    // Right edge
                    else if (gl_LocalInvocationID.x == GROUP_SIZE-1)
                    {
                        process(ivec2(1, 0));
                    }

                    // Final value
                    vec4 current = process(ivec2(0, 0));

                    barrier();

                    if (current.r == AIR || current.r == WATER || current.r == LAVA || current.r == SAND)
                    {
                        vec4 u = get(0, -1);
                        vec4 l = get(-1, 0);
                        vec4 r = get(1, 0);
                        vec4 d = get(0, 1);
                        float waterFlow = current.g * waterMask[int(current.r)];
                        waterFlow += getFlow(0, -1).r * waterMask[int(u.r)];
                        waterFlow += getFlow(1, 0).g * waterMask[int(r.r)];
                        waterFlow += getFlow(-1, 0).b * waterMask[int(l.r)];
                        waterFlow += getFlow(0, 1).a * waterMask[int(d.r)];
                        bool hasWater = waterFlow > MinMass;

                        float lavaFlow = current.g * lavaMask[int(current.r)];
                        lavaFlow += getFlow(0, -1).r * lavaMask[int(u.r)];
                        lavaFlow += getFlow(1, 0).g * lavaMask[int(r.r)];
                        lavaFlow += getFlow(-1, 0).b * lavaMask[int(l.r)];
                        lavaFlow += getFlow(0, 1).a * lavaMask[int(d.r)];
                        bool hasLava = lavaFlow > MinMass;

                        if (current.r == SAND)
                        {
                            current.g += (lavaFlow + waterFlow) * 0.75;
                            if (current.g >= 1.0)
                            {
                                current.r = hasWater ? WATER : (hasLava ? LAVA : SAND);
                            }
                        }
                        else if (hasWater && waterFlow > lavaFlow)
                        {
                            current.r = WATER;
                            current.g = max(waterFlow, current.a);
                        }
                        else if (hasLava && lavaFlow > waterFlow)
                        {
                            current.r = LAVA;
                            current.g = max(lavaFlow, current.a);
                            //current.b = max(current.b - waterFlow, 0);
                            //if (current.b == 0) current.r = ROCK;
                        }
                        else
                        {
                            current.r = AIR;
                        }
                        current.g = max(current.g, 0.0);
                    }

                    imageStore(imageOut, pixel, current);
                }
            }
        ]]
    }
}

shaders.paint = shader {
    options = {},

    properties =
    {
        {"image", "image2D", format = image.rgba32f},
        {"u_paintPos", "vec2", {0.0, 0.0}},
        {"u_brushSize", "float", 5.0},
        {"u_brushType", "int", 1},
        {"u_brushSource", "bool", false}
    },

    includes =
    {
        constants = shaders.constants
    },

    pass =
    {
        compute = [[
            #version 430
            #include <codea/common.glsl>

            layout(rgba32f) uniform writeonly image2D image;
            uniform vec2 u_paintPos;
            uniform float u_brushSize;
            uniform int u_brushType;
            uniform bool u_brushSource;

            #include "constants.glsl"

            NUM_THREADS(16, 16, 1)
            void main()
            {
                ivec2 size = imageSize(image);
                ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);

                if ( all(lessThan(pixel.xy, size)) )
                {
                    if (distance(u_paintPos, vec2(pixel)) < u_brushSize)
                    {
                        vec4 value = vec4(u_brushType, 0.0, 0.0, 0.0);
                        if (u_brushType == WATER)
                        {
                            value.g = 1.0;
                            if (u_brushSource) value.a = 0.5;
                        }
                        if (u_brushType == LAVA)
                        {
                            value.g = 1.0;
                            value.b = 1.0;
                            if (u_brushSource) value.a = 0.5;
                        }
                        if (u_brushType == SAND)
                        {
                            value.g = 0.0;

                        }


                        imageStore(image, pixel, value);
                    }
                }
            }
        ]]
    }
}

shaders.visualise = shader{
    options =
    {
    },

    properties =
    {
        {"rock", "texture"},
        {"water", "texture"},
        {"lava", "texture"}
    },

    includes =
    {
        constants = shaders.constants
    },


    pass =
    {
        vertex =
        [[
            #version 430
            #include <codea/common.glsl>

            layout (location = POSITION) in vec3 a_position;
            layout (location = COLOR0) in vec4 a_color0;
            layout (location = TEXCOORD0) in vec2 a_texcoord0;

            layout(location = 0) out vec4 v_color0;
            layout(location = 1) out vec2 v_texcoord0;

            void main()
            {
                gl_Position = u_modelViewProj * vec4(a_position, 1.0);
                v_color0 = a_color0 * u_tintColor;
                v_texcoord0 = a_texcoord0;
            }
        ]],

        fragment =
        [[
            #version 430
            #include <codea/common.glsl>

            layout(location = 0) in vec4 v_color0;
            layout(location = 1) in vec2 v_texcoord0;

            #include "constants.glsl"

            out vec4 FragColor;
            uniform sampler2D water;
            uniform sampler2D rock;
            uniform sampler2D lava;

            vec3 permute(vec3 x) { return mod(((x*34.0)+1.0)*x, 289.0); }

            float snoise(vec2 v){
                const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439);
                vec2 i  = floor(v + dot(v, C.yy) );
                vec2 x0 = v -   i + dot(i, C.xx);
                vec2 i1;
                i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
                vec4 x12 = x0.xyxy + C.xxzz;
                x12.xy -= i1;
                i = mod(i, 289.0);
                vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
                + i.x + vec3(0.0, i1.x, 1.0 ));
                vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy),
                dot(x12.zw,x12.zw)), 0.0);
                m = m*m ;
                m = m*m ;
                vec3 x = 2.0 * fract(p * C.www) - 1.0;
                vec3 h = abs(x) - 0.5;
                vec3 ox = floor(x + 0.5);
                vec3 a0 = x - ox;
                m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
                vec3 g;
                g.x  = a0.x  * x0.x  + h.x  * x0.y;
                g.yz = a0.yz * x12.xz + h.yz * x12.yw;
                return 130.0 * dot(m, g);
            }

            void main()
            {
                vec4 value = texture(s_texture, v_texcoord0);
                vec3 c = vec3(0.0);

                vec2 uvRipple = v_texcoord0 * 10.0 + vec2(value.g) * 0.2;
                uvRipple += snoise(v_texcoord0 * 20.0 + u_time.x) * 0.05;

                if (value.r == AIR)
                {
                    // Background rock
                    vec3 bg = texture(rock, v_texcoord0 * 10.0).rgb;
                    c = bg * 0.125;
                }
                else if (value.r == SAND)
                {
                    // Normal rock
                    vec3 r = texture(rock, v_texcoord0 * 5.0).rgb + vec3(0.3, 0.2, 0.0);
                    r *= (1.0 - value.g);
                    c = r;
                }
                else if (value.r == ROCK)
                {
                    // Normal rock
                    vec3 r = texture(rock, v_texcoord0 * 5.0).rgb;
                    c = r;
                }
                else if (value.r == WATER)
                {
                    vec3 waterTex = texture(water, uvRipple).rgb;
                    float alpha = 0.4 + value.g * 0.3;
                    c = c + waterTex * alpha;
                }
                else if (value.r == LAVA)
                {
                    vec3 lavaTex = texture(lava, uvRipple * 2.0).rgb;
                    c = lavaTex;
                }

                FragColor = vec4(c, 1.0);
            }
        ]],
    }
}

function setup()


    Scale = 3

    local w, h = 256, 128 + 64
    buffers =
    {
        image.pixel(w, h, false, 1, image.rgba32f),
        image.pixel(w, h, false, 1, image.rgba32f)
    }

    apply(shaders.init, buffers[1])

    m = mesh()

    m.positions =
    {
        vec3(-w/2, h/2, 0), vec3(w/2, h/2, 0),
        vec3(w/2, -h/2, 0), vec3(-w/2, -h/2, 0)
    }

    m.uvs =
    {
        vec2(0, 0), vec2(1, 0),
        vec2(1, 1), vec2(0, 1)
    }

    m.colors =
    {
    color(255), color(255),
    color(255), color(255)
    }

    m:addElement(3,2,1)
    m:addElement(4,3,1)
    m.shader = shaders.visualise
    shaders.visualise.rock = image.read(asset.Rock)
    shaders.visualise.water = image.read(asset.Water)
    shaders.visualise.lava = image.read(asset.Lava)

    parameter.integer("Material", 0, 4, 2) -- air, rock, water, lava, sand
    parameter.integer("BrushSize", 5, 50, 5)
    parameter.boolean("IsSource", false)
end

function swap(a, b)
    return b, a
end

function apply(cs, buffer)
    local wx, wy = cs:workgroupSize()
    cs:setImage("image", buffer)
    cs:dispatch(math.ceil(buffer.width/wx), math.ceil(buffer.height/wy), 1)
end

function updateStuff()
    if touch.current.state == MOVING then

        local w, h = buffers[1].width * Scale, buffers[1].height * Scale
        local min = vec2(WIDTH/2 - w / 2, HEIGHT/2 - h /2)
        local max = vec2(WIDTH/2 + w / 2, HEIGHT/2 + h /2)
        local x = (touch.current.x - min.x) / (max.x - min.x) * w
        local y = (1 - ((touch.current.y - min.y) / (max.y - min.y))) * h

        shaders.paint.u_paintPos = vec2(x/Scale, y/Scale)
        shaders.paint.u_brushType = Material
        shaders.paint.u_brushSize = BrushSize
        shaders.paint.u_brushSource = IsSource


        apply(shaders.paint, buffers[1])
    end

    local wx, wy = shaders.update:workgroupSize()
    shaders.update:setImage("imageIn", buffers[1])
    shaders.update:setImage("imageOut", buffers[2])
    shaders.update:dispatch(math.ceil(buffers[1].width/wx), math.ceil(buffers[1].height/wy), 1)
    buffers[1], buffers[2] = swap(buffers[1], buffers[2])

end

function draw()
    Scale = math.min(WIDTH / buffers[1].width, HEIGHT / buffers[1].height) * 0.85
    updateStuff()

    background(125)

    style.push().smooth()
    matrix.push().translate(WIDTH/2, HEIGHT/2).scale(Scale)
    m.texture = buffers[1]
    m:draw()
    matrix.pop()
    style.pop()

    sprite(buffers[1], 50, 50, 100, 100)
end

function touched(touch)

end

Composing reusable shader chunks

Drag to orbit a marble sphere with a glowing rim. The ground combines a checkerboard, a normal map, and a moving cloud shadow. Each effect is a reusable shader chunk; try changing the checkerboard colors first.

Composing reusable shader chunks

Download project Open in Codea to run this example.

Show code (209 lines)
-- Shader Builder
-- A utility class that makes modular shaders easy to create

-- Use this function to perform your initial setup
function setup()
    scn = scene.default3d()
    scn.camera:add(camera.rigs.orbit)
    scene.main = scn

    -- Bake spherical noise texture
    noiseTex = image(512, 512, false, 1, image.rgba8)
    context.push(noiseTex)
    sprite(shader.builder():unlit()
    :include("codea/noise/classic/3d.glsl") -- include 3D noise utility (cnoise)
    :include("codea/utilities.glsl") -- include for sphericalToCartesian() helper function
    :material
    [[
        {
            vec3 p = sphericalToCartesian(vec3(3, getUV0().yx * vec2(PI, 2*PI)  ));
            float o1 = cnoise(p*0.5 + vec3(10)) * 1.75;
            float o2 = cnoise(p*0.25 + o1) * 0.5;
            float n = abs(cnoise(p + o2));
            material.baseColor = vec4(0,0,0,1);
            material.baseColor.r = n;
        }

        {
            vec3 p = sphericalToCartesian(vec3(5, getUV0().yx * vec2(PI, 2*PI)  ));
            material.baseColor.g = cnoise(p) * 0.5 + 0.5;
            material.baseColor.b = cnoise(p + vec3(100.423)) * 0.5 + 0.5;
            material.baseColor.a = cnoise(p + vec3(2323.232)) * 0.5 + 0.5;
        }
    ]]
    :build(), 256, 256, 512, 512)
    context.pop()

    -- Shader chunks are reusable pieces of shader functionality
    -- they are able to contain properties, code, functions, includes and more
    -- they can be added to a shader builder via builder:add()
    -- multiple chunks can easily be combined although some may
    -- interfere with each other if they modify the same material parameters

    -- Raymarched parallax shader
    marble = shader.chunk()
        :include("codea/utilities.glsl")
        :texture{"heightMap", value = noiseTex}
        :color{"colorB", value = color.black}
        :color{"colorA", value = color(211, 13, 125)}
        :number{"iterations", value = 5}
        :number{"depth", value = 1}
        :number{"smoothing", value = 0}
        :func
        [[
            vec3 marchMarble(vec3 rayOrigin, vec3 rayDir) {
                float perIteration = 1. / float(iterations);
                vec3 deltaRay = rayDir * perIteration * depth;

                // Start at point of intersection and accumulate volume
                vec3 p = rayOrigin;

                vec3 col = vec3(0);
                for (int i=0; i<iterations; ++i) {
                    // Read heightmap from current spherical direction
                    vec2 uv = equirectUv(normalize(p));

                    vec3 rand = texture(heightMap, uv + vec2(u_time.x * 0.02, 0)).gba;
                    uv.y = 1 - uv.y;
                    vec3 rand2 = texture(heightMap, uv - vec2(u_time.x * 0.05, 0)).gba;

                    uv = equirectUv(normalize(p + rand * 0.025 + rand2 * 0.05));
                    float heightMapVal = texture(heightMap, uv).r;

                    // Take a slice of the heightmap
                    float height = length(p); // 1 at surface, 0 at core, assuming radius = 1
                    float cutoff = 1. - float(i) * perIteration;
                    float slice = smoothstep(cutoff, cutoff + smoothing, heightMapVal);

                    // Accumulate the volume and advance the ray forward one step

                    vec3 col2 = texture(heightMap, vec2(cutoff, 0.5)).gba;

                    col += slice * perIteration * mix(colorA.xyz, col2, 0.2);
                    p += deltaRay;
                }
                //return mix(colorA, colorB, totalVolume).xyz;
                return col;
            }
        ]]
        :material
        [[
            material.roughness = 0.1;
            material.baseColor = vec4(marchMarble(getWorldPosition(), -getWorldViewVector()), 1);
        ]]

    -- Simple emissive fresnel rim effect
    fresnel = shader.chunk()
    :number{"power", value = 4}
    :number{"intensity", value = 1}
    :color{"color", value = color.white}
    :material
    [[
        float f = pow(1.0 - dot(getWorldPosition(), getWorldViewVector()), power);
        material.emissive = (f * intensity) * color;
    ]]

    -- Simple checkerboard pattern
    checkers = shader.chunk()
    :vec2{"size", value = vec2(10, 10)}
    :color{"colorA", value = color.white}
    :color{"colorB", value = color.black}
    :material
    [[
        vec2 uv = getUV0();
        uv = floor(uv * size);
        float c = mod(uv.x + uv.y, 2);
        material.baseColor *= mix(colorA, colorB, c);
    ]]

    cracks = shader.chunk()
    :texture{"normalMap", value = asset.RockyNormals}
    :normals
    [[
        material.normal = texture(normalMap, getUV0() * 0.5).rgb * 2.0 - 1.0;
    ]]

    clouds = shader.chunk()
    :texture{"noiseTex", value = noiseTex}
    :material
    [[
        float n = texture(noiseTex, getWorldPosition().xz * 0.1 + vec2(u_time.x, 0) * 0.1).r;
        material.ambientOcclusion = mix(n, 1.0, 0.5);
    ]]

    ball = material(shader.builder():lit()
    :add(fresnel)
    :add(marble)
    :build())

    parameter.integer("Iterations", 1, 40, 32, function(i)
        ball.iterations = i
    end)

    parameter.number("Depth", 0, 1, 0.5, function(i)
        ball.depth = i
    end)

    parameter.number("Smoothing", 0, 1, 0.2, function(i)
        ball.smoothing = i
    end)

    parameter.number("SkyBlur", 0, 10, 0.0, function(i)
        scn.skyBlur = i
    end)


    ball.intensity = 2
    ball.power = 10
    ball.color = color.yellow

    -- tween the intensity property on the rim highlight
    scn:tween(ball):to{intensity = 0}:time(1):pingpong(-1)

    sphere = scn:entity("sphere")
    sphere:add(mesh.sphere(1.0))
    sphere.material = ball
    sphere.y = 0

    ground = scn:entity("ground")
    ground.y = -1
    ground:add(mesh.box(10, 0.1, 10))

    ground.material = material(shader.builder()
    :lit()
    :texture{"normalMap", value = asset.RockyNormals}
    :normals
    [[
        material.normal = texture(normalMap, getUV0() * 0.1).rgb * 2.0 - 1.0;
        material.normal.xy *= 0.75;
    ]]
    :add(checkers)
    :add(clouds)
    :build())

    ground.material.colorA = color(121, 188, 226)
    ground.material.colorB = color(30, 94, 131)
    ground.material.size = vec2(1,1)

    -- You can also use shader builder to create simple unlit materials for use in sprites
    spriteShader = shader.builder()
        :unlit()
        :material([[
            vec2 uv = getUV0();
            uv = floor(uv * 20);
            float c = mod(uv.x + uv.y, 2);
            material.baseColor = vec4(vec3(c), 1);
        ]])
        :build()

end

function draw()
    matrix.reset().ortho()
    style.shapeMode(CENTER).fontSize(20).stroke(0).strokeWidth(3).textAlign(CENTER)
    text("Modular Shader Builder", WIDTH/2, HEIGHT - 50)

    sprite(spriteShader, WIDTH - 60, HEIGHT/2, 100, 100)
    sprite(noiseTex,WIDTH - 120,HEIGHT/2 + 200,200)
end

Updating neighboring pixels safely

Touch to start a reaction-diffusion pattern. Each step reads one image and writes to another, then swaps them, so neighboring pixels are read before they change. Try changing the feed and kill rates in ReactionDiffusion.lua to explore different patterns.

Updating neighboring pixels safely

Download project Open in Codea to run this example.

Show code (31 lines)
-- Reaction Diffusion via compute shaders
-- The documentation host manages presentation.

-- Use this function to perform your initial setup
function setup()
    print("Hello Reaction Diffusion!")
    rd = ReactionDiffusion(192, 192)
    rd:set(rd.size.x/2, rd.size.y/2, 10)

    cam = camera.ortho(rd.size.y / 2, -1, 1)
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color
    background(40, 40, 50)

    -- Do your drawing here
    cam:apply()
    rd:draw(0, 0, rd.size.x, rd.size.y)
end

function fixedUpdate(dt)
    rd:fixedUpdate(dt)
end

function touched(touch)
    local x, y = cam:screenToWorld(touch.pos)
    rd:set(x + rd.size.x/2, rd.size.y/2 - y, 10)
end

ReactionDiffusion.lua — simulation, texture swapping, and visualization

Show code (244 lines)
function swap(a, b)
    return b, a
end

ReactionDiffusion = class()

function ReactionDiffusion:init(w, h)
    self.rd = shader {
        options =
        {
            INIT = {false, multicompile = true},
        },

        properties =
        {
            {"image", "image2D"},
            {"previous", "image2D"},
            {"u_diffusion", "vec2", {1.0, 0.5}},
            {"u_feed", "float", 0.055},
            {"u_kill", "float", 0.062},
            {"u_dt", "float", 1.0 / 60.0},
        },

        pass =
        {
            compute = [[
            #version 430
            #include <codea/common.glsl>

            layout(rgba16f) uniform image2D image;
            layout(rgba16f) uniform readonly image2D previous;
            uniform vec2 u_diffusion = vec2(1.0, 0.5);
            uniform float u_feed = 0.055;
            uniform float u_kill = 0.062;
            uniform float u_dt = 1.0 / 60.0;

            #ifdef INIT

            float random (vec2 st)
            {
                return fract(sin(dot(st.xy, vec2(12.9898,78.233)))* 43758.5453123);
            }

            NUM_THREADS(16, 16, 1)
            void main()
            {
                ivec2 size = imageSize(image);
                ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);

                if ( all(lessThan(pixel.xy, size)) )
                {
                    vec4 value = vec4(1.0, 0.0, 0.0, 1.0);
                    /*
                    if (pixel.x > size.x/2-10 && pixel.y > size.y/2-10 && pixel.x < size.x/2+10 && pixel.y < size.y/2+10)
                    {
                        value.r = 0.5 + random(pixel.xy) * 0.02 - 0.01;
                        value.g = 0.5 + random(pixel.xy * 5) * 0.02 - 0.01;
                    }*/

                    imageStore(image, pixel, value);
                }
            }

        #else

            vec2 sampleImage(ivec2 pos)
            {
                    return imageLoad(previous, clamp(pos, ivec2(0), imageSize(previous) - 1)).rg;
            }

            NUM_THREADS(16, 16, 1)
            void main()
            {
                ivec2 size = imageSize(image);
                ivec2 id = ivec2(gl_GlobalInvocationID.xy);

                if ( all(lessThan(id.xy, size)) )
                {
                    vec2 pq = sampleImage(id.xy);

                        ivec3 d = ivec3(1, -1, 0);
                        vec2 q = -pq;

                        q += sampleImage(id.xy - d.xx).xy * 0.05; // -1,-1
                        q += sampleImage(id.xy - d.zx).xy * 0.20; // 0, -1
                        q += sampleImage(id.xy - d.yx).xy * 0.05; // +1, -1
                        q += sampleImage(id.xy - d.xz).xy * 0.20; // -1, 0
                        q += sampleImage(id.xy + d.xz).xy * 0.20; // +1, 0
                        q += sampleImage(id.xy + d.yx).xy * 0.05; // -1, +1
                        q += sampleImage(id.xy + d.zx).xy * 0.20; // 0, +1
                        q += sampleImage(id.xy + d.xx).xy * 0.05; // +1, +1

                        float ABB = pq.x * pq.y * pq.y;

                        float a = pq.x + (u_diffusion.x * q.x - ABB + u_feed * (1.0 - pq.x)) * u_dt;
                        float b = pq.y + (u_diffusion.y * q.y + ABB - (u_kill + u_feed) * pq.y) * u_dt;

                        imageStore(image, id, vec4(saturate(a), saturate(b), 0.0, 1.0));
                }
            }
            #endif

        ]]
        }
    }

    self.paint = shader {
        options =
        {
        },

        properties =
        {
            {"image", "image2D"},
            {"u_paintPos", "vec2", {0.0, 0.0}},
            {"u_brushSize", "float", 10.0}
        },

        pass =
        {
            compute = [[
            #version 430
            #include <codea/common.glsl>

            layout(rgba16f) uniform image2D image;
            uniform vec2 u_paintPos;
            uniform float u_brushSize;

            float random (vec2 st)
            {
            return fract(sin(dot(st.xy, vec2(12.9898,78.233)))* 43758.5453123);
            }

            NUM_THREADS(16, 16, 1)
            void main()
            {
            ivec2 size = imageSize(image);
            ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);

            if ( all(lessThan(pixel.xy, size)) )
            {
                if (distance(u_paintPos, vec2(pixel)) < u_brushSize)
                {
                    vec4 value = vec4(1.0, 0.0, 0.0, 0.0);
                    value.r = 0.5 + random(pixel.xy) * 0.02 - 0.01;
                    value.g = 0.5 + random(pixel.xy * 5) * 0.02 - 0.01;
                    imageStore(image, pixel, value);
                }
            }
            }
            ]]
        }
    }

    self.visualise = shader{
        properties =
        {
            {"img", "texture"}
        },
        pass =
        {
            vertex =
            [[
                #version 430
                #include <codea/common.glsl>

                layout (location = POSITION) in vec3 a_position;
                layout (location = TEXCOORD0) in vec2 a_texcoord0;

                layout(location = 0) out vec4 v_color0;
                layout(location = 1) out vec2 v_texcoord0;

                void main()
                {
                    gl_Position = u_modelViewProj * vec4(a_position, 1.0);
                    v_texcoord0 = a_texcoord0;
                }
            ]],

            fragment =
            [[
                #version 430
                #include <codea/common.glsl>

                layout(location = 1) in vec2 v_texcoord0;

                uniform highp sampler2D img;

                out vec4 FragColor;

                const float COLOR_MIN = 0.2, COLOR_MAX = 0.4;

                void main()
                {
                    vec2 ab = texture(img, v_texcoord0).rg;
                    float d = 1.0 - (COLOR_MAX - ab.y) / (COLOR_MAX - COLOR_MIN);
                    FragColor = vec4(vec3(saturate(d)), 1.0);
                }
            ]],
        }
    }

    self.size = vec2(w or 256, h or 256)
    self.buffer = image.pixel(self.size.x, self.size.y, false, 1, image.rgba16f)
    self.nextBuffer = image.pixel(self.size.x, self.size.y, false, 1, image.rgba16f)
    self.buffer.smooth = true
    self:reset()
end

function ReactionDiffusion:draw(x, y, w, h)
    matrix.push().translate(x, y)
    self.visualise.img = self.buffer
    sprite(self.visualise, 0, 0, w, h)
    matrix.pop()
end

function ReactionDiffusion:fixedUpdate(dt)
    self.rd:setOption("INIT", false)
    self.rd.u_diffusion = vec2(1.0, 0.5)
    self.rd.u_feed = 0.0545
    self.rd.u_kill = 0.062
    self.rd.u_dt = dt * 30

    for i = 1, 12 do
        self.rd:setImage("previous", self.buffer)
        self.rd:setImage("image", self.nextBuffer)
        self.rd:dispatch(math.ceil(self.buffer.width / 16), math.ceil(self.buffer.height / 16), 1)
        self.buffer, self.nextBuffer = self.nextBuffer, self.buffer
    end
end

function ReactionDiffusion:reset()
    self.rd:setOption("INIT", true)
    self.rd:setImage("image", self.buffer)
    self.rd:dispatch(math.ceil(self.buffer.width / 16), math.ceil(self.buffer.height / 16), 1)
end

function ReactionDiffusion:set(x, y, size)
    self.paint:setImage("image", self.buffer)
    self.paint.u_paintPos = vec2(x, y)
    self.paint.u_brushSize = size
    self.paint:dispatch(math.ceil(self.buffer.width / 16), math.ceil(self.buffer.height / 16), 1)
end

Sharing a GPU buffer between compute and drawing

Touch to attract ten thousand particles. A compute shader updates their positions and velocities in a shared buffer, which a mesh reads to draw them. Adjust the attraction range and strength in Controls. The shader checks instanceCount to skip unused threads in the last workgroup.

Sharing a GPU buffer between compute and drawing

Download project Open in Codea to run this example.

Show code (224 lines)
-- Compute Shaders using shader.builder()
-- Instanced sprites with curl-noise velocity field

-- Use this function to perform your initial setup
function setup()

    -- Create a reusable shader chunk for the instances buffer that looks like this in GLSL:
    -- struct Instances
    -- {
    --    vec4 position; // (x, y, vx, vy) position and velocity
    -- };


    instancesChunk = shader.chunk()
        :struct{"Instance"}
            :vec4("position")
        :done()
        :buffer{"Instance", "instances", access = "readwrite"}

    -- Create the instanced unlit sprite shader
    instancing = shader.builder()
    :unlit()
    :add(instancesChunk) -- make instances buffer available to shader
    :cull("none"):blend("additive")
    :variable{"posVel"}
    :vertex
    [[
        vec4 posVel = instances[gl_InstanceID].position;
        float speed = max(length(posVel.zw), 0.0001);
        vec2 x = posVel.zw;
        vec2 y = vec2(-x.y, x.x) / speed;
        // Matrix to rotate and stretch along velocity
        mat2 rot = mat2(x * 1, y);

        // Apply translation and rotation to particle
        material.worldPosition.xy = rot * material.worldPosition.xy + posVel.xy;
        material.posVel = posVel;
    ]]
    :func
    [[
        vec3 hueShift( vec3 color, float hueAdjust ){

            const vec3  kRGBToYPrime = vec3 (0.299, 0.587, 0.114);
            const vec3  kRGBToI      = vec3 (0.596, -0.275, -0.321);
            const vec3  kRGBToQ      = vec3 (0.212, -0.523, 0.311);

            const vec3  kYIQToR     = vec3 (1.0, 0.956, 0.621);
            const vec3  kYIQToG     = vec3 (1.0, -0.272, -0.647);
            const vec3  kYIQToB     = vec3 (1.0, -1.107, 1.704);

            float   YPrime  = dot (color, kRGBToYPrime);
            float   I       = dot (color, kRGBToI);
            float   Q       = dot (color, kRGBToQ);
            float   hue     = atan (Q, I);
            float   chroma  = sqrt (I * I + Q * Q);

            hue += hueAdjust;

            Q = chroma * sin (hue);
            I = chroma * cos (hue);

            vec3    yIQ   = vec3 (YPrime, I, Q);

            return vec3( dot (yIQ, kYIQToR), dot (yIQ, kYIQToG), dot (yIQ, kYIQToB) );
        }
    ]]
    :material
    [[
        vec4 posVel = vertex_posVel;
        material.baseColor = vec4(hueShift(vec3(0.2, 0.4, 0.3), length(posVel.zw)), 0.05);
    ]]
    :build()

    -- Compute shader for updating the instances each frame
    updatePositions = shader.builder()
    :add(instancesChunk)
    -- GPU noise functions (see the GPU Noise reference)
    :include("codea/noise/noise.glsl")
    :uint{"instanceCount", value = 0}
    :vec2{"screenSize", value = vec2(WIDTH, HEIGHT)}
    -- TODO: add support for uniform arrays to make this easier
    :vec4{"attractorA", value = vec4(WIDTH/2, HEIGHT/2, 300, 0.001)}
    :vec4{"attractorB", value = vec4(WIDTH/2, HEIGHT/2, 300, 0.001)}
    :vec4{"attractorC", value = vec4(WIDTH/2, HEIGHT/2, 300, 0.001)}
    :threads(512,1,1)
    :compute
    [[
        // Get the current instance to work on
        uint i = gl_GlobalInvocationID.x;

        // Prevent going over the end of the buffer
        if (i < instanceCount)
        {
            vec2 pos = instances[i].position.xy;
            vec2 vel = instances[i].position.zw;


            // Sample two curl-noise fields that scroll in different directions and rates
            vec2 curl1 = SimplexPerlin2D_Deriv(pos * 0.01 + vec2(u_time.x, -u_time.x) * 0.5).yz;
            curl1 = vec2(-curl1.y, curl1.x) * 0.75;

            vec2 curl2 = SimplexPerlin2D_Deriv(pos * 0.05 + vec2(0, u_time.x) * 5.0).yz;
            curl2 = vec2(-curl2.y, curl2.x) * 0.5;

            // Apply curl noise velocity field
            vel += (curl1 + curl2) * u_time.y;

            vec4 attractors[3] =
            {
                attractorA,
                attractorB,
                attractorC,
            };

            for (int i = 0; i < 3; i++)
            {
                vec4 attractor = attractors[i];
                vec2 force = (attractor.xy - pos);
                float dist = length(force);
                force = force / max(dist, 0.0001) * (1 - min(dist / max(attractor.z, 0.0001), 1));
                vel += force * attractor.w;
            }

            // Apply some damping
            vel -= vel * 0.8 * u_time.y;

            // Add velocity to position * delta time
            pos += vel * 30.0 * u_time.y;

            // Bounce instances to screen bounds
            if (pos.x < 0.0)
            {
                vel.x *= -2.0; pos.x = 0.0;
            }
            else if (pos.x > screenSize.x)
            {
                vel.x *= -2.0; pos.x = screenSize.x;
            }
            if (pos.y < 0.0)
            {
                vel.y *= -2.0; pos.y = 0.0;
            }
            else if (pos.y > screenSize.y)
            {
                vel.y *= -2.0; pos.y = screenSize.y;
            }

            instances[i].position = vec4(pos, vel);
        }
    ]]
    :build()

    -- Create a buffer to match the shader buffer (syntax is a bit ugly right now)
    local layout = shader.bufferLayout
    {
        {shader.position, 4, shader.float} -- vec4
    }
    -- Create buffer to hold instance data
    instancesBuffer = shader.buffer(layout)

    -- Create initial instances by appending data to buffer
    -- Change this number to adjust the total particles being created
    for k = 1, 10000 do
        instancesBuffer:append(shader.position, vec4(math.random(0, WIDTH), math.random(0, HEIGHT),
        0, 0)) --math.random() * 0.5 - 0.5, math.random() * 0.5 - 0.5))
    end

    updatePositions:setBuffer("instancesBuffer", instancesBuffer)
    updatePositions.instanceCount = instancesBuffer.size

    instancing:setBuffer("instancesBuffer", instancesBuffer)

    -- Make one tiny little quad for our sprites
    m = mesh()
    m:addRect(0, 0, 1.5, 1.5)
    m.shader = instancing

    attractors =
    {
        {name = "attractorA", params = vec4()},
        {name = "attractorB", params = vec4()},
        {name = "attractorC", params = vec4()}
    }

    parameter.integer("Attractor", 1, 3, 1)
    parameter.number("AttractorRadius", 0, 1000, 300, updateAttractor)
    parameter.number("AttractorStrength", 0, 100, 5, updateAttractor)

    -- Camera effects are documented separately; this demo focuses on GPU buffers.
end

function updateAttractor()
    local a = attractors[Attractor]
    a.params.z = (AttractorRadius or 300)
    a.params.w = (AttractorStrength or 5) / 100
    updatePositions[a.name] = a.params
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color

    updatePositions.screenSize = vec2(WIDTH, HEIGHT)
    local wx = updatePositions:workgroupSize()
    updatePositions:dispatch(math.ceil(instancesBuffer.size / wx), 1, 1)

    --b:push()
    background(40, 40, 50)
    style.push().noStroke().fill(255)
    for k,v in pairs(attractors) do
        ellipse(v.params.x, v.params.y, 5)
    end
    style.pop()
    m:draw(instancesBuffer.size)
    --b:pop()
end

function touched(touch)
    local a = attractors[Attractor]
    a.params.x = touch.x
    a.params.y = touch.y
    updateAttractor()
end

Animating instanced grass on terrain

Drag to orbit the terrain and use Controls to change the wind, grass shape, and colors. Terrain.lua creates a height map; Grass.lua places and bends the grass. Increase density gradually: doubling it creates four times as many blades.

Animating instanced grass on terrain

Download project Open in Codea to run this example.

Show code (99 lines)
-- GPU Grass
-- viewer.displayStats = true

-- Reusable glass properties
GrassColorChunk = shader.chunk()
    :color{"midColor", value = color(61, 161, 25)}
    :color{"baseColor2", value = color(27, 53, 27)}
    :color{"tipColor", value = color(229, 203, 100)}

-- Reusable grass instance buffer settings
InstancesChunk = shader.chunk()
    :struct{"Instance"}
        :vec4("position")
        :vec4("details")
    :done()
    :buffer{"Instance", "instances", access = "readwrite"}

function setup()
    scn = scene.default3d()
    sunlight = scn.sun:get(light)
    sunlight.castShadows = true
    cam = scn.camera:get(camera)
    cam.clearColor = color(47)
    scn.skyBlur = 9
    rig = scn.camera:add(camera.rigs.orbit)
    rig.distance = 75
    rig.angles.x = 45
    rig.angles.y = 45

    groundEnt = scn:entity("ground")
    terrain = groundEnt:add(Terrain)
    grass = groundEnt:add(Grass)

    box = scn:entity("box")
    box.scale = vec3(3, 5, 3)
    box.y = 15
    box:add(mesh.box())

    scene.main = scn

    parameter.color("GrassBaseColor", color(27, 53, 27), function(s)
        Grass.shader.baseColor2 = s
    end)

    parameter.color("GrassMidColor", color(61, 161, 25), function(s)
        Grass.shader.midColor = s
    end)

    parameter.color("GrassTipColor", color(229, 203, 100), function(s)
        Grass.shader.tipColor = s
    end)

    parameter.number("WindNoiseScale", 0, 500, 250, function(s)
        grass.windNoiseScale = s
    end)

    parameter.number("WindPanSpeed", 0, 0.5, 0.04, function(s)
        grass.windPanSpeed = s
    end)

    parameter.number("WindNoiseStrength", 0, 10, 3, function(s)
        grass.windNoiseStrength = s
    end)

    parameter.number("BladeThickness", 0.1, 3, 0.4, function(s)
        grass.bladeThickness = s
    end)

    parameter.number("BladeHeight", 0.1, 10, 4, function(s)
        grass.bladeHeight = s
    end)

    parameter.number("BladeBendFactor", 0.1, 4, 2, function(s)
        grass.bladeBendFactor = s
    end)

    parameter.number("BladeBendStrength", 0.0, 3, 0.5, function(s)
        grass.bladeBendStrength = s
    end)

    parameter.boolean("ShadowsEnabled", false, function(s)
        sunlight.castShadows = s
    end)

    parameter.integer("GrassDensity", 100, 1000, 100, function(d)
    end)

    parameter.action("UpdateGrass", function()
        grass:setDensity(GrassDensity)
    end)

end

function draw()
    matrix.reset().ortho()
    sprite(terrain.heightMap, WIDTH-100, HEIGHT/2, 100)

end

Terrain.lua — generating and displaying the height map

Show code (56 lines)
Terrain = class()

-- HeightMap displacement shader for terrain
Terrain.static.shader = shader.builder():lit()
    :include "codea/utilities.glsl"
    :texture{"heightMap", binding = 5}
    :texture{"baseColor"}
    :number{"terrainSize", value = 50}
    :number{"terrainHeight", value = 20}
    :vertex([[
        material.uv0 = fract(material.worldPosition.xz / (terrainSize * 2 + 1) + 0.5);
        material.worldPosition.y += textureLod(heightMap, material.uv0, 0).x * terrainHeight;
    ]])
    :normals([[
        material.normal = decodeNormal(textureLod(heightMap, fract(getUV0()), 0).yzw);
    ]])
    :material([[
        vec3 bc = texture(baseColor, getUV0() * 2).rgb;
        bc = pow(bc, vec3(2.2));
        material.baseColor = vec4(bc, 1.0);
    ]])
    :build()

function Terrain:created()

    self.mesh = mesh.plane(50, 50, 100, 100)
    self.entity:add(self.mesh)
    self.height = 20
    self.size = 50

    -- Generate a noise-based height map
    self.heightMap = image(512, 512)
    context.push(self.heightMap)
    background(shader.builder():unlit()
    :include "codea/noise/noise.glsl"
    :include "codea/utilities.glsl"
    :material([[
        vec2 uv = getUV0() * 2;
        float n = Perlin2D(uv); // + Perlin2D(uv * 2) * 0.1;
        vec2 d = vec2(dFdx(n), dFdy(n));
        d *= 30;
        float h = n.x * 0.4 + 0.5;
        vec3 nrm = encodeNormal(normalize(vec3(d.x, d.y, 0.5)));
        material.baseColor = vec4(h, nrm);
    ]])
    :build())
    context.pop()

    self.mat = material(Terrain.shader)
    self.mat.heightMap = self.heightMap
    self.mat.terrainHeight = self.height
    self.mat.terrainSize = self.size
    self.mat.baseColor = asset.Ground
    self.entity.material = self.mat
end

Grass.lua — placing and animating instances

Show code (189 lines)
Grass = class()

function Grass:created()

    Grass.static.layout = shader.bufferLayout
    {
        {shader.position, 4, shader.float}, -- position(x, y, z, rot)
        {shader.texcoord0, 4, shader.float} -- details(height, n/a, n/a, n/a)
    }

    Grass.static.shader = shader.builder():unlit()
    :add(InstancesChunk) -- make instances buffer available to shader
    :add(GrassColorChunk)
    :doubleSided(true)
    :texture{"windNoise"}
    :option{"USE_MASK_MAP", value = false}
    :texture{"maskMap", option = "USE_MASK_MAP"}
    :number{"terrainSize", value = 50}
    :number{"terrainHeight", value = 20}
    :vec4{"windParams", value = vec4(250, 0.04, 3, 0)} -- noise scale, panning speed, noise strength
    :vec4{"bladeParams", value = vec4(0.5, 4.0, 2, 0.5)} -- thickness, height, bendFactor, bendStrength
    :variable{"color"}
    :vertex([[
        // Get the instance parameters
        vec4 pos = instances[gl_InstanceID].position;
        vec4 det = instances[gl_InstanceID].details;

        // Use texture as a mask for grass coverage
        #ifdef USE_MASK_MAP
            float m = texture(maskMap, fract(pos.xz / (terrainSize * 2 + 1) + 0.5)).r;
            material.worldPosition.xyz *= 2.0 - m;
        #endif

        // Create  rotation matrix to rotate blades on y axis
        vec2 v = vec2(sin(pos.w), cos(pos.w));
        mat2 rot = mat2(v.x, v.y, -v.y, v.x);

        // Scale blades up based on height
        material.worldPosition.xyz *= vec3(bladeParams.x, det.x * bladeParams.y, bladeParams.x) * det.y;

        // Rotate blades using rotation
        material.worldPosition.xz *= rot;

        // Translate blades to instance location
        material.worldPosition.xyz += pos.xyz;

        // Calculate height-based blend parameter using UVs
        float h = pow(1 - material.uv0.y, bladeParams.z);

        // Initial directional grass bend
        material.worldPosition.xz += rot * vec2(1, 0) * h * bladeParams.w;

        // Rotate normals (if we add lighting at some point)
        material.worldNormal.xz *= rot;

        // View dependent grass bend when viewing from above
        vec3 fwd = vec3(u_view[0][2], u_view[1][2], u_view[2][2]);
        vec3 fwd2 = normalize(vec3(fwd.x, 0.01, fwd.z));
        float downBlend = dot(fwd, vec3(0,-1,0));
        downBlend *= downBlend;
        material.worldPosition.xyz += fwd2 * h * downBlend * 3;

        // Use noise for wind effect
        vec2 noiseUV = fract(pos.xz / windParams.x + u_time.x * windParams.y);
        material.worldPosition.xz -= texture(windNoise, noiseUV).x * h * det.x * windParams.z;

        // Pass additional color variable based on height
        material.color = vec4(-det.x * 0.01, 0, -det.x * 0.05, 0);
    ]])
    :material([[
        vec2 uv = getUV0();
        vec4 fc = mix(baseColor2, midColor, 1 - uv.y);
        fc += mix(vec4(0), tipColor, pow(1 - uv.y, 4)) * 0.25;
        fc.rgb += vertex_color.rgb;
        material.baseColor = vec4(fc.rgb, 1);
    ]])
    :build()

    self.heightMap = self.entity:get(Terrain).heightMap

    self.model = mesh.read(asset.GrassBlade4)
    self.model.shader = Grass.shader
    Grass.shader.windNoise = image.read(asset.WindNoise3)

    self.setupGrass = shader.builder()
        :include "codea/noise/noise.glsl"
        :add(InstancesChunk)
        :texture{"heightMap"}
        :number{"terrainSize", value = 50}
        :number{"terrainHeight", value = 20}
        :uint{"instanceCount"}
        :number{"density"}
        :threads(256, 1, 1)
        :func([[
        vec2 hashwithoutsine22(vec2 p)
        {
            vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973));
            p3 += dot(p3, p3.yzx+33.33);
            return fract((p3.xx+p3.yz)*p3.zy);
        }

        float hashwithoutsine13(vec3 p3)
        {
            p3  = fract(p3 * .1031);
            p3 += dot(p3, p3.yzx + 33.33);
            return fract((p3.x + p3.y) * p3.z);
        }
        ]])
        :compute([[
            uint id = gl_GlobalInvocationID.x;
            if (id < instanceCount)
            {
                vec4 pos = vec4(0);
                pos.x = (((id % int(density)) / density) - 0.5) * (terrainSize * 2);
                pos.y = 0;
                pos.z = (((id / int(density)) / density) - 0.5) * (terrainSize * 2);

                float h = textureLod(heightMap, fract(pos.xz / (terrainSize * 2 + 1) + 0.5), 0).r;
                pos.y += h * 20;

                pos.w = hashwithoutsine13(pos.xyz);

                vec2 o = hashwithoutsine22(pos.zw) - vec2(0.5);
                pos.xz += o * 2;

                float scale = 1;

                float n = (Perlin2D(pos.xz / 30) + 1) * 0.5;
                if (n < 0.05) scale = clamp(n * 20, 0, 1);
                if (pos.w < 0.2) n *= 1.25;

                pos.w *= 3.1415964;

                instances[id].position = pos;
                instances[id].details = vec4(clamp(n, 0, 1), scale, 0, 0);
            }
        ]])
        :build()

    self.windNoiseScale = 250
    self.windPanSpeed = 0.04
    self.windNoiseStrength = 3
    self.bladeThickness = 0.4
    self.bladeHeight = 4
    self.bladeBendFactor = 2
    self.bladeBendStrength = 0.5

    self.wind = vec4()
    self.blade = vec4()

    self:setDensity(100)

end

function Grass:setDensity(density)
    self.density = density
    self:setup()
end

function Grass:setup()
    if self.buffer == nil then
    end
    self.buffer = shader.buffer(Grass.layout)
    self.buffer:resize(self.density*self.density)
    self.setupGrass:setBuffer("instancesBuffer", self.buffer)
    self.setupGrass.heightMap = self.heightMap
    self.setupGrass.instanceCount = self.buffer.size
    self.setupGrass.density = self.density
    self.setupGrass:dispatch(math.ceil(self.buffer.size / 256), 1, 1)
end

function Grass:draw()
    self.wind.x = self.windNoiseScale
    self.wind.y = self.windPanSpeed
    self.wind.z = self.windNoiseStrength

    self.blade.x = self.bladeThickness
    self.blade.y = self.bladeHeight
    self.blade.z = self.bladeBendFactor
    self.blade.w = self.bladeBendStrength

    matrix.push().reset()
    Grass.shader.windParams = self.wind
    Grass.shader.bladeParams = self.blade
    Grass.shader:setBuffer("instancesBuffer", self.buffer)
    self.model:draw(self.buffer.size)
    matrix.pop()
end

Mesh

Using Meshes top ↑

Overview

A mesh draws a shape made of triangles. Use one for custom shapes, pictures mapped onto surfaces, or 3D models. Create it in setup() and draw it each frame.

Choose a starting point

  • For a custom 2D shape, assign vertices and draw with mesh:draw(). Each consecutive group of three vertices makes a triangle.
  • To share vertices between triangles, assign positions and one-based indices. Keep colors and texture coordinates aligned with the vertex list.
  • For a polygon outline, use mesh.triangulate(points) to generate indices.
  • For 3D objects, attach mesh.box(), mesh.sphere(), or a loaded mesh to an entity and give it a material. Let the scene’s camera handle projection.

Draw a colored triangle

This triangle rotates with a different color at each corner. Codea blends the colors across its surface. Open Controls to change its size.

A Rotating Colored Triangle

Download project Open in Codea to run this example.

Show code (59 lines)
-- Mesh Example
-- Author: John Millard


function setup()
    print("Hello Mesh!")

    -- Here we create a mesh, just a single triangle
    parameter.integer("triSize", 10, 300, 100)

    -- Create a new empty mesh
    triMesh = mesh()

    -- This is an array of colours we'll assign to the triMesh vertices
    triCol = { color(255, 0, 0, 255),
    color(255, 255, 0, 255),
    color(0, 100, 255, 255)}

    -- Assign colors to the mesh, there must be the same number of colours as vertices
    triMesh.colors = triCol
end

-- This function gets called once every frame
function draw()
    -- Dark background color
    background(20, 20, 40)

    -- Save the current transform (translation, rotation and scale)
    matrix.push()

    -- Translate coordinates to the middle of the screen
    matrix.translate(WIDTH/2, HEIGHT/2)

    -- Rotate the transform at 100 degrees per second
    matrix.rotate(time.elapsed*100)

    -- Construct equilateral triangle points, this could be any 3 points
    local top = vec2(math.cos(2*math.pi/6), math.sin(2*math.pi/6))
    top = top * triSize

    local left = vec2(math.cos(6*math.pi/6), math.sin(6*math.pi/6))
    left = left * triSize

    local right = vec2(math.cos(10*math.pi/6), math.sin(10*math.pi/6))
    right = right * triSize

    -- Set points on mesh, every 3 points constructs a new triangle
    triMesh.vertices = { top,
                         left,
                         right }

    -- Draw the mesh that we setup
    triMesh:draw()

    -- Restore the previous transform
    -- This is useful if you need to position multiple objects differently
    matrix.pop()
end

Build a polygon from touches

Tap points around a simple outline to turn it into a filled mesh. Add points in boundary order without crossing edges. This is useful for drawing tools, irregular buttons, and generated 2D scenery.

Build a Polygon from Touches

Download project Open in Codea to run this example.

Show code (39 lines)
-- Mesh 2 - Triangulate Example
-- Author: John Millard

function setup()
    print("Hello Triangulate!")

    msh = mesh()
    points = {}
    colors = {}
end

function updateMesh()
    if #points >= 3 then
        msh.positions = points
        msh.colors = colors
        msh.indices = mesh.triangulate(points)
    end
end

-- This function gets called once every frame
function draw()
    -- Dark background color
    background(20, 20, 40)
    msh:draw()

    style.noStroke().fill(255)
    for k, v in pairs(points) do
        ellipse(v, 10)
    end
end

function touched(touch)
    if touch.state == ENDED and touch.tapCount == 1 then
        table.insert(points, touch.pos)
        table.insert(colors, color(touch.x / WIDTH * 255, touch.y / HEIGHT * 255, 128))
        updateMesh()
    end
end

Put a mesh in a 3D scene

This cube rotates in a lit scene. Try replacing mesh.box() with mesh.sphere() or changing its material color.

A Rotating 3D Shape

Download project Open in Codea to run this example.

function setup()
    local scn = scene.default3d()
    scn.camera.z = -6
    local shape = scn:entity("shape")
    shape:add(mesh.box())
    shape.material = material.lit()
    shape.material.color = color(100, 220, 200)
    function shape:update(dt)
        self.ry = self.ry + dt * 35
    end
    scene.main = scn
end

Common next steps

Load models with mesh.read(assetKey). Use uvs to place a texture on the surface, and a material or shader to change its appearance. Submeshes let different parts use different materials. Add a physics body if the object needs to collide or respond to scene hit testing.

mesh top ↑

Type

Syntax

mesh

Represents a drawable shape made of vertices and primitives (typically triangles)

Vertices

Each vertex contains a number of attributes:

  • position (vec3) - the position
  • normal (vec3) - the surface normal orientation
  • tangent (vec4) - the tangent to the surface normal (automatically calculated)
  • uv (vec2) - the texture coordinate (uv) of the vertex
  • color - the color
  • boneWeights - the weight of each bone’s influence on this vertex (used for skinning)
  • boneIndices - the index of each bone that can influence this vertex (used for skinning)

These attributes can be get/set directly by setting all of them at once via tables (i.e. msh.positions = {...}) or by using methods that set attributes one vertex at a time (i.e. msh:position(i, p))

Triangle Meshes

A triangle mesh is drawn using its vertices which are connected using triangles primitives. Every group of consecutive three indices make up an individual triangle

Shaders and Materials

Both shaders and materials can be applied to a mesh. Setting a shader/material will override any previous shaders/materials on the mesh

When no shader/material is set, the default unlit mesh material will be used instead

Sub-Meshes

A mesh consists of one or more sub-meshes, each of which can have a distinct shader/material. The submeshIndex property can be used to switch between the current active submeshes for mesh editing purposes

Skinned Models

Loaded meshes can expose a hierarchy through root and animation data through animations. See those entries when working with animated model assets.

Mesh Drawing

Mesh Manipulation

Mesh Generation

mesh(submeshCount)

Constructor

Syntax

mesh(submeshCount)

Create an empty mesh

Parameters

submeshCount integer, optional.

The number of submeshes that this mesh will be created with (optoinal)

Returns

mesh

mesh.triangulate(points)

Static Method

Syntax

mesh.triangulate(points)

Convert a simple polygon outline into triangle indices. Supply vec2 points in boundary order without crossing edges. Assign the same points to mesh.positions and the returned one-based indices to mesh.indices.

Parameters

points table.

Polygon boundary vertices

Returns

table

Triangle indices into the supplied points

Tap to build a polygon

Download project Open in Codea to run this example.

Show code (39 lines)
-- Mesh 2 - Triangulate Example
-- Author: John Millard

function setup()
    print("Hello Triangulate!")

    msh = mesh()
    points = {}
    colors = {}
end

function updateMesh()
    if #points >= 3 then
        msh.positions = points
        msh.colors = colors
        msh.indices = mesh.triangulate(points)
    end
end

-- This function gets called once every frame
function draw()
    -- Dark background color
    background(20, 20, 40)
    msh:draw()

    style.noStroke().fill(255)
    for k, v in pairs(points) do
        ellipse(v, 10)
    end
end

function touched(touch)
    if touch.state == ENDED and touch.tapCount == 1 then
        table.insert(points, touch.pos)
        table.insert(colors, color(touch.x / WIDTH * 255, touch.y / HEIGHT * 255, 128))
        updateMesh()
    end
end

mesh.read(key)

Static Method

Syntax

mesh.read(key)

Loads a mesh from a file

The following mesh formats are currently supported:

  • GLTF / GLB
  • FBX
  • OBJ
  • STL

Skinned animations are supported but morph targets are currently not

Saving meshes is not currently supported

Parameters

key asset.key.

The asset key to load

mesh.texture

Property

Syntax

mesh.texture

The mesh texture, used by the default mesh shader (and accessible to custom shaders as well) for basic surface textures

mesh.shader

Property

Syntax

mesh.shader

Custom shader for this mesh, used when mesh:draw() is invoked

mesh.material

Property

Syntax

mesh.material

The same as assigning a shader but with a material instead

mesh.submeshIndex

Property

Syntax

mesh.submeshIndex

The current sub-mesh index. Useful for multi-material meshes

mesh.submeshCount

Property

Syntax

mesh.submeshCount

The total number of sub-meshes within this mesh

mesh.vertexCount

Property

Syntax

mesh.vertexCount

The total number of vertices in the currently selected sub-mesh

mesh.indexCount

Property

Syntax

mesh.indexCount

The total number of indices in the currently selected sub-mesh

mesh.bounds

Property

Syntax

mesh.bounds

The local bounds of this mesh (no scaling or rotation applied)

mesh.positions

Property

Syntax

mesh.positions

Gets/sets the positions of the mesh vertices without touching the index buffer.

This is the correct property to use when modifying vertex positions on an already-built mesh (e.g. one created by mesh.sphere(), mesh.box(), or loaded via mesh.read()), since those meshes have optimised index buffers with shared vertices that you want to preserve.

mesh.normals

Property

Syntax

mesh.normals

Gets/sets the normals of the mesh vertices

mesh.colors

Property

Syntax

mesh.colors

Gets/sets the colors of the mesh vertices

mesh.uvs

Property

Syntax

mesh.uvs

Gets/sets the uvs of the mesh vertices

mesh.indices

Property

Syntax

mesh.indices

Gets/sets the indices of the mesh

mesh.vertices

Property

Syntax

mesh.vertices

Gets/sets the positions of the mesh vertices while also ensuring that each set of three indices match their corresponding vertices. This is the preferred way to assign geometry for manually-built meshes because it automatically populates a sequential index buffer matching the vertex order, making the mesh immediately drawable.

mesh.texCoords

Property

Syntax

mesh.texCoords

Gets/sets the uvs of the mesh vertices

mesh.root

Property

Syntax

mesh.root

Meshes loaded via mesh.read() may contain sub-objects and bones used for animations, these can be accessed as entities in a simple scene-like hierarchy

WARNING: do not attempt to delete any nodes within the root as it may have unintended side effects

mesh.animations

Property

Syntax

mesh.animations

Contains the list of all animations for this mesh

mesh:draw(instances)

Method

Syntax

mesh:draw(instances)

Draws the mesh to the screen with the current camera, matrix and context settings

Parameters

instances integer, optional.

Number of instances to draw; defaults to 1.

mesh:drawIndirect(indirectBuffer, start, num)

Method

Syntax

mesh:drawIndirect(indirectBuffer, start, num)

Draws the mesh indirectly using indirectBuffer

Used in combination with compute shaders for indirect drawing operations

Parameters

indirectBuffer shader.indirectBuffer.

GPU buffer containing indirect drawing commands.

start integer, optional, default 0.

Zero-based first indirect command index.

num integer, optional, default 1.

Number of indirect commands to execute.

mesh:addRect(position, size, rotation, uvRect)

Method

Syntax

mesh:addRect(position, size, rotation, uvRect)

Appends a 2D rectangle to this mesh centered at position with the size of size, rotation of rotation (in degrees) and the uv rectangle uvRect

This function supports dynamic number type arguments

Parameters

position vec2.

Center of the rectangle in local coordinates.

size vec2.

Rectangle width and height.

rotation number, optional.

Rectangle rotation in degrees.

uvRect vec4, optional.

Texture rectangle as normalized u, v, width, height values.

Returns

integer

the index of the new rectangle

mesh:setRect(index, position, size, rotation)

Method

Syntax

mesh:setRect(index, position, size, rotation)

Sets existing rectangle position, size and rotation using an index from a previous call to addRect()

Parameters

index integer.

Index of the vertex, index-buffer entry, or rectangle, starting at 1.

position vec2.

Center of the rectangle in local coordinates.

size vec2.

Rectangle width and height.

rotation number, optional.

Rectangle rotation in degrees.

mesh:setRectTex(index, uvRect)

Method

Syntax

mesh:setRectTex(index, uvRect)

Sets existing rectangle uvs using an index from a previous call to addRect()

Parameters

index integer.

Index of the vertex, index-buffer entry, or rectangle, starting at 1.

uvRect vec4.

Texture rectangle as normalized u, v, width, height values.

mesh:setRectColor(index, color)

Method

Syntax

mesh:setRectColor(index, color)

Sets existing rectangle color using an index from a previous call to addRect()

Parameters

index integer.

Index of the vertex, index-buffer entry, or rectangle, starting at 1.

color color.

Vertex or rectangle color.

mesh:resizeVertices(size)

Method

Syntax

mesh:resizeVertices(size)

Sets the number of vertices in the mesh (must be positive)

Parameters

size integer.

New number of vertices or indices.

mesh:resizeIndices(size)

Method

Syntax

mesh:resizeIndices(size)

Sets the number of indices in the mesh (must be positive)

Parameters

size integer.

New number of vertices or indices.

mesh:addElement(p1, p2, p3, ...)

Method

Syntax

mesh:addElement(p1, p2, p3, ...)

Adds a new element to the mesh consisting of N indices (i.e. add three indices for a new triangle)

Parameters

p1 integer.

Vertex index for this primitive, starting at 1.

p2 integer.

Vertex index for this primitive, starting at 1.

p3 integer.

Vertex index for this primitive, starting at 1.

... integer, optional.

Additional vertex indices for the primitive.

mesh:clear()

Method

Syntax

mesh:clear()

Clears the mesh, reducing vertices and indices to zero

mesh:position(index)

Method

Syntax

mesh:position(index)

Parameters

index integer.

Index of the vertex, index-buffer entry, or rectangle, starting at 1.

mesh:position(index, position)

Method

Syntax

mesh:position(index, position)

Gets/sets the position of the vertex at index

Parameters

index integer.

Index of the vertex, index-buffer entry, or rectangle, starting at 1.

position vec2 or vec3.

Vertex position, or the center of a rectangle.

mesh:normal(index)

Method

Syntax

mesh:normal(index)

Parameters

index integer.

Index of the vertex, index-buffer entry, or rectangle, starting at 1.

mesh:normal(index, normal)

Method

Syntax

mesh:normal(index, normal)

Gets/sets the normal of the vertex at index

Parameters

index integer.

Index of the vertex, index-buffer entry, or rectangle, starting at 1.

normal vec3.

Vertex normal vector.

mesh:color(index)

Method

Syntax

mesh:color(index)

Parameters

index integer.

Index of the vertex, index-buffer entry, or rectangle, starting at 1.

mesh:color(index, color)

Method

Syntax

mesh:color(index, color)

Gets/sets the color of the vertex at index

Parameters

index integer.

Index of the vertex, index-buffer entry, or rectangle, starting at 1.

color color.

Vertex or rectangle color.

mesh:uv(index)

Method

Syntax

mesh:uv(index)

Parameters

index integer.

Index of the vertex, index-buffer entry, or rectangle, starting at 1.

mesh:uv(index, uv)

Method

Syntax

mesh:uv(index, uv)

Gets/sets the uv of the vertex at index

Parameters

index integer.

Index of the vertex, index-buffer entry, or rectangle, starting at 1.

uv vec2.

Vertex texture coordinates.

mesh:index(index)

Method

Syntax

mesh:index(index)

Parameters

index integer.

Index of the vertex, index-buffer entry, or rectangle, starting at 1.

mesh:index(index, i)

Method

Syntax

mesh:index(index, i)

Gets/sets the index at index

Parameters

index integer.

Index of the vertex, index-buffer entry, or rectangle, starting at 1.

i integer.

Vertex index to store, starting at 1.

mesh.sphere(radius, slices, segments, sliceStart, sliceSweep, segmentStart, segmentSweep)

Static Method

Syntax

mesh.sphere(radius, slices, segments, sliceStart, sliceSweep, segmentStart, segmentSweep)

Generates a sphere mesh with various settings

Parameters

radius number, optional, default 1.

The radius of the sphere

slices number, optional, default 32.

Subdivisions around the z-azis (longitudes)

segments number, optional, default 16.

Subdivisions along the z-azis (latitudes)

sliceStart number, optional, default 0.

Counterclockwise angle around the z-axis relative to x-axis

sliceSweep number, optional, default 360.

Counterclockwise angle

segmentStart number, optional, default 0.

Counterclockwise angle relative to the z-axis

segmentSweep number, optional, default 180.

Counterclockwise angle

mesh.icoSphere(radius, subdivisions)

Static Method

Syntax

mesh.icoSphere(radius, subdivisions)

Generates an ico-sphere, aka spherical subdivided icosahedron

Parameters

radius number, optional, default 1.

The radius of the ico-sphere

subdivisions number, optional, default 4.

Subdivisions for the ico-sphere

mesh.box(halfSize, segments)

Static Method

Syntax

mesh.box(halfSize, segments)

Rectangular box centered at origin aligned along the x, y and z axis

Parameters

halfSize vec3, optional, default vec3(1, 1, 1).

Half of the side length in x, y and z directions

segments vec3, optional, default vec3(8, 8, 8).

The number of segments in x, y and z directions

mesh.roundedBox(radius, halfSize, slices, segments)

Static Method

Syntax

mesh.roundedBox(radius, halfSize, slices, segments)

Rectangular box with rounded edges centered at origin aligned along the x, y and z axis

Parameters

radius number, optional, default 0.25.

The corner radius of the rounded edges

halfSize vec3, optional, default vec3(1, 1, 1).

Half of the side length in x, y and z directions

slices integer, optional, default 4.

The number of subdivisions in the rounded edges / corners

segments vec3, optional, default vec3(8, 8, 8).

The number of segments in x, y and z directions

mesh.cone(radius, halfLength, slices, segments, rings, start, sweep)

Static Method

Syntax

mesh.cone(radius, halfLength, slices, segments, rings, start, sweep)

A cone with a cap centered at origin pointing towards positive y-axis

Parameters

radius number, optional, default 1.

Radius of the flat (negative z) end along the xz-plane

halfLength number, optional, default 1.

Half of the length of the cone along the y-axis

slices integer, optional, default 32.

Number of subdivisions around the y-axis

segments integer, optional, default 8.

Number of subdivisions along the y-axis

rings integer, optional, default 4.

Number of subdivisions of the cap

start number, optional, default 0.

Counterclockwise angle around the y-axis relative to the positive x-axis

sweep number, optional, default 360.

Counterclockwise angle around the y-axis

mesh.cylinder(radius, halfLength, slices, segments, rings, start, sweep)

Static Method

Syntax

mesh.cylinder(radius, halfLength, slices, segments, rings, start, sweep)

Capped cylinder centered at origin aligned along the y-axis

Parameters

radius number, optional, default 1.

Radius of the flat (negative z) end along the xz-plane

halfLength number, optional, default 1.

Half of the length of the cylinder along the y-axis

slices integer, optional, default 32.

Number of subdivisions around the y-axis

segments integer, optional, default 8.

Number of subdivisions along the y-axis

rings integer, optional, default 4.

Number of subdivisions of the cap

start number, optional, default 0.

Counterclockwise angle around the y-axis relative to the positive x-axis

sweep number, optional, default 360.

Counterclockwise angle around the y-axis

mesh.capsule(radius, halfLength, slices, segments, rings, start, sweep)

Static Method

Syntax

mesh.capsule(radius, halfLength, slices, segments, rings, start, sweep)

Capsule centered at origin aligned along the y-axis

Parameters

radius number, optional, default 1.

Radius of the capsule along the xz-plane

halfLength number, optional, default 1.

Half of the length capsule along the y-axis

slices integer, optional, default 32.

Number of subdivisions around the y-axis

segments integer, optional, default 8.

Number of subdivisions along the z-axis

rings integer, optional, default 4.

Number of subdivisions on the caps

start number, optional, default 0.

Counterclockwise angle around the y-axis relative to x-axis

sweep number, optional, default 360.

Counterclockwise angle around the y-axis

mesh.disk(radius, innerRadius, slices, rings, start, sweep)

Static Method

Syntax

mesh.disk(radius, innerRadius, slices, rings, start, sweep)

A circular disk centered at origin on the xz-plane

Parameters

radius number, optional, default 1.

Outer radius of the disk on the xz-plane

innerRadius number, optional, default 1.

Radius of the inner circle on the xz-plane

slices integer, optional, default 32.

Number of subdivisions around the y-axis

rings integer, optional, default 4.

Number of subdivisions along the radius

start number, optional, default 0.

Counterclockwise angle relative to the y-axis

sweep number, optional, default 360.

Counterclockwise angle

mesh.plane(halfSize, segments)

Static Method

Syntax

mesh.plane(halfSize, segments)

A flat plane centered at the origin on the xy-plane

Parameters

halfSize vec2, optional, default vec2(1, 1).

Half of the side length in x and z direction

segments vec2, optional, default vec2(8, 8).

Number of subdivisions in the x and z directions

mesh.torus(minor, major, slices, segments, minorStart, minorSweep, majorStart, majorSweep)

Static Method

Syntax

mesh.torus(minor, major, slices, segments, minorStart, minorSweep, majorStart, majorSweep)

Generates a torus

Parameters

minor number, optional, default 0.25.

Radius of the minor (inner) ring

major number, optional, default 1.

Radius of the major (outer) ring

slices integer, optional, default 32.

Subdivisions around the minor ring

segments integer, optional, default 8.

Subdivisions around the major ring

minorStart number, optional, default 0.

Counterclockwise angle relative to the xz-plane

minorSweep number, optional, default 360.

Counterclockwise angle around the circle

majorStart number, optional, default 0.

Counterclockwise angle around the y-axis relative to the x-axis

majorSweep number, optional, default 360.

Counterclockwise angle around the y-axis

mesh.torusKnot(p, q, slices, segments)

Static Method

Syntax

mesh.torusKnot(p, q, slices, segments)

Generates a torus knot

Parameters

p number, optional, default 2.

Number of windings around the torus axis.

q number, optional, default 3.

Number of windings around the torus tube.

slices integer, optional, default 8.

Number of subdivisions used to generate the mesh; larger values create more geometry.

segments integer, optional, default 96.

Number of subdivisions used to generate the mesh; larger values create more geometry.

mesh.teapot(subdivisions)

Static Method

Syntax

mesh.teapot(subdivisions)

Generates the Utah teapot using the original data The lid is pointing towards the z axis and the spout towards the x axis

Parameters

subdivisions integer, optional, default 8.

Number of subdivisions used to generate the mesh; larger values create more geometry.

Materials

material top ↑

Type

Syntax

material

Materials apply shaders to renderable objects (meshes, sprites, entities, etc), maintaining separate property values per instance. This allows the same shader to be used many times in different places without incurring additional overhead from copying or compiling shader code again

Materials can also be saved/loaded, although they will need to access to the original shader asset

The Standard Material

The standard material is built on the standard uber-shader, providing a convenient all purpose material

Color properties take ordinary colors, the same as style.fill and color(...) everywhere else in Codea, and are converted for lighting automatically. Reading a color property gives back the color that was set

material(shader)

Constructor

Syntax

material(shader)

Create a new material using the supplied shader

Parameters

shader shader.

Shader used by this material.

Returns

material

material.lit()

Static Method

Syntax

material.lit()

Create a default lit material using the standard shader

material.unlit()

Static Method

Syntax

material.unlit()

Create a default unlit material using the standard shader

Shaders

Writing Shaders top ↑

Overview

A shader controls how geometry is transformed and how pixels are colored. Start with a material builder for surface effects; use vertex and fragment shader source when you need direct control over both stages. The checkerboard below uses texture coordinates to alternate two colors across a sprite.

Drawing a Checkerboard Shader

Show code (37 lines)
function setup()
   solid = shader([[
      #version 430
      #include <codea/common.glsl>

      layout (location = POSITION) in vec3 a_position;
      layout (location = TEXCOORD0) in vec2 a_texcoord0;

      out vec2 uv;

      void main()
      {
         gl_Position = u_modelViewProj * vec4(a_position, 1.0);
         uv = a_texcoord0;
      }
   ]],

   [[
      #version 430
      #include <codea/common.glsl>

      in vec2 uv;
      out vec4 FragColor;

      void main()
      {
         vec2 c = floor(uv * 10);
         float d = mod(c.x + c.y, 2);
         FragColor = vec4(vec3(d), 1);
      }
   ]])
end

function draw()
   background(64)
   sprite(solid, WIDTH/2, HEIGHT/2, 200, 200)
end

shader top ↑

Type

Syntax

shader

A special program that runs per vertex/pixel for drawing and compute operations on the GPU

In Codea shaders can be used in various places to customise rendering including meshes, entities, sprites, backgrounds and post processing effects

shader(vertexSource, fragmentSource)

Constructor

Syntax

shader(vertexSource, fragmentSource)

The simplist form of shader creation, both the vertex and fragment sources are provided directly without any additional settings

This is useful for fairly basic shaders

Parameters

vertexSource string.

GLSL source for the vertex stage.

fragmentSource string.

GLSL source for the fragment stage.

Returns

shader

shader(shaderDefinition)

Constructor

Syntax

shader(shaderDefinition)

A more advanced form of shader creation, with many more options that can be tailored for specific use-cases

Parameters

shaderDefinition table.

Shader definition table containing stage source and configuration.

Returns

shader

shader.compute(source)

Static Method

Syntax

shader.compute(source)

Create a compute shader without any extra settings

Parameters

source string.

GLSL compute shader source.

shader.read(asset)

Static Method

Syntax

shader.read(asset)

Read a shader asset from the filesystem

Parameters

asset asset.key.

Asset key identifying the shader to load.

shader:setOption(name, on)

Method

Syntax

shader:setOption(name, on)

Parameters

name string.

Name of the shader option.

on boolean.

Whether to enable the named option.

shader:setImage(name, image, mip)

Method

Syntax

shader:setImage(name, image, mip)

Bind an image to a declared image property for compute access. Its format must match the GLSL layout qualifier (for example, image.rgba16f with layout(rgba16f)). The mip index is zero-based. Read/write access comes from the GLSL readonly/writeonly qualifiers, not an additional Lua argument.

Parameters

name string.

Name of the image property

image image.

Texture to bind

mip integer, optional, default 0.

Mipmap level to bind

shader:setBuffer(name, buffer)

Method

Syntax

shader:setBuffer(name, buffer)

Bind a shader buffer to a declared buffer property. The Lua buffer layout must match the GLSL struct. With shader.builder():buffer{“Instance”, “instances”}, the generated property is named “instancesBuffer”.

Parameters

name string.

Name of the buffer property

buffer shader.buffer.

Buffer to bind

shader:workgroupSize(passIndex)

Method

Syntax

shader:workgroupSize(passIndex)

Return the local workgroup dimensions declared by NUM_THREADS() or shader.builder():threads(). The optional pass index is zero-based. Divide the number of elements by these dimensions and round up to calculate dispatch counts.

Parameters

passIndex integer, optional, default 0.

Compute pass to inspect

Returns

integer

Local X, Y, and Z thread counts

integer
integer

shader:dispatch(wx, wy, wz)

Method

Syntax

shader:dispatch(wx, wy, wz)

Dispatch counts workgroups, not individual invocations. Divide the buffer length by the shader workgroup size and round up; check the index against instanceCount inside the shader. This example updates a shared buffer before drawing its instances.

Parameters

wx integer.

Number of workgroups to dispatch along the corresponding axis.

wy integer.

Number of workgroups to dispatch along the corresponding axis.

wz integer.

Number of workgroups to dispatch along the corresponding axis.

Compute-driven particles

Download project Open in Codea to run this example.

Show code (224 lines)
-- Compute Shaders using shader.builder()
-- Instanced sprites with curl-noise velocity field

-- Use this function to perform your initial setup
function setup()

    -- Create a reusable shader chunk for the instances buffer that looks like this in GLSL:
    -- struct Instances
    -- {
    --    vec4 position; // (x, y, vx, vy) position and velocity
    -- };


    instancesChunk = shader.chunk()
        :struct{"Instance"}
            :vec4("position")
        :done()
        :buffer{"Instance", "instances", access = "readwrite"}

    -- Create the instanced unlit sprite shader
    instancing = shader.builder()
    :unlit()
    :add(instancesChunk) -- make instances buffer available to shader
    :cull("none"):blend("additive")
    :variable{"posVel"}
    :vertex
    [[
        vec4 posVel = instances[gl_InstanceID].position;
        float speed = max(length(posVel.zw), 0.0001);
        vec2 x = posVel.zw;
        vec2 y = vec2(-x.y, x.x) / speed;
        // Matrix to rotate and stretch along velocity
        mat2 rot = mat2(x * 1, y);

        // Apply translation and rotation to particle
        material.worldPosition.xy = rot * material.worldPosition.xy + posVel.xy;
        material.posVel = posVel;
    ]]
    :func
    [[
        vec3 hueShift( vec3 color, float hueAdjust ){

            const vec3  kRGBToYPrime = vec3 (0.299, 0.587, 0.114);
            const vec3  kRGBToI      = vec3 (0.596, -0.275, -0.321);
            const vec3  kRGBToQ      = vec3 (0.212, -0.523, 0.311);

            const vec3  kYIQToR     = vec3 (1.0, 0.956, 0.621);
            const vec3  kYIQToG     = vec3 (1.0, -0.272, -0.647);
            const vec3  kYIQToB     = vec3 (1.0, -1.107, 1.704);

            float   YPrime  = dot (color, kRGBToYPrime);
            float   I       = dot (color, kRGBToI);
            float   Q       = dot (color, kRGBToQ);
            float   hue     = atan (Q, I);
            float   chroma  = sqrt (I * I + Q * Q);

            hue += hueAdjust;

            Q = chroma * sin (hue);
            I = chroma * cos (hue);

            vec3    yIQ   = vec3 (YPrime, I, Q);

            return vec3( dot (yIQ, kYIQToR), dot (yIQ, kYIQToG), dot (yIQ, kYIQToB) );
        }
    ]]
    :material
    [[
        vec4 posVel = vertex_posVel;
        material.baseColor = vec4(hueShift(vec3(0.2, 0.4, 0.3), length(posVel.zw)), 0.05);
    ]]
    :build()

    -- Compute shader for updating the instances each frame
    updatePositions = shader.builder()
    :add(instancesChunk)
    -- GPU noise functions (see the GPU Noise reference)
    :include("codea/noise/noise.glsl")
    :uint{"instanceCount", value = 0}
    :vec2{"screenSize", value = vec2(WIDTH, HEIGHT)}
    -- TODO: add support for uniform arrays to make this easier
    :vec4{"attractorA", value = vec4(WIDTH/2, HEIGHT/2, 300, 0.001)}
    :vec4{"attractorB", value = vec4(WIDTH/2, HEIGHT/2, 300, 0.001)}
    :vec4{"attractorC", value = vec4(WIDTH/2, HEIGHT/2, 300, 0.001)}
    :threads(512,1,1)
    :compute
    [[
        // Get the current instance to work on
        uint i = gl_GlobalInvocationID.x;

        // Prevent going over the end of the buffer
        if (i < instanceCount)
        {
            vec2 pos = instances[i].position.xy;
            vec2 vel = instances[i].position.zw;


            // Sample two curl-noise fields that scroll in different directions and rates
            vec2 curl1 = SimplexPerlin2D_Deriv(pos * 0.01 + vec2(u_time.x, -u_time.x) * 0.5).yz;
            curl1 = vec2(-curl1.y, curl1.x) * 0.75;

            vec2 curl2 = SimplexPerlin2D_Deriv(pos * 0.05 + vec2(0, u_time.x) * 5.0).yz;
            curl2 = vec2(-curl2.y, curl2.x) * 0.5;

            // Apply curl noise velocity field
            vel += (curl1 + curl2) * u_time.y;

            vec4 attractors[3] =
            {
                attractorA,
                attractorB,
                attractorC,
            };

            for (int i = 0; i < 3; i++)
            {
                vec4 attractor = attractors[i];
                vec2 force = (attractor.xy - pos);
                float dist = length(force);
                force = force / max(dist, 0.0001) * (1 - min(dist / max(attractor.z, 0.0001), 1));
                vel += force * attractor.w;
            }

            // Apply some damping
            vel -= vel * 0.8 * u_time.y;

            // Add velocity to position * delta time
            pos += vel * 30.0 * u_time.y;

            // Bounce instances to screen bounds
            if (pos.x < 0.0)
            {
                vel.x *= -2.0; pos.x = 0.0;
            }
            else if (pos.x > screenSize.x)
            {
                vel.x *= -2.0; pos.x = screenSize.x;
            }
            if (pos.y < 0.0)
            {
                vel.y *= -2.0; pos.y = 0.0;
            }
            else if (pos.y > screenSize.y)
            {
                vel.y *= -2.0; pos.y = screenSize.y;
            }

            instances[i].position = vec4(pos, vel);
        }
    ]]
    :build()

    -- Create a buffer to match the shader buffer (syntax is a bit ugly right now)
    local layout = shader.bufferLayout
    {
        {shader.position, 4, shader.float} -- vec4
    }
    -- Create buffer to hold instance data
    instancesBuffer = shader.buffer(layout)

    -- Create initial instances by appending data to buffer
    -- Change this number to adjust the total particles being created
    for k = 1, 10000 do
        instancesBuffer:append(shader.position, vec4(math.random(0, WIDTH), math.random(0, HEIGHT),
        0, 0)) --math.random() * 0.5 - 0.5, math.random() * 0.5 - 0.5))
    end

    updatePositions:setBuffer("instancesBuffer", instancesBuffer)
    updatePositions.instanceCount = instancesBuffer.size

    instancing:setBuffer("instancesBuffer", instancesBuffer)

    -- Make one tiny little quad for our sprites
    m = mesh()
    m:addRect(0, 0, 1.5, 1.5)
    m.shader = instancing

    attractors =
    {
        {name = "attractorA", params = vec4()},
        {name = "attractorB", params = vec4()},
        {name = "attractorC", params = vec4()}
    }

    parameter.integer("Attractor", 1, 3, 1)
    parameter.number("AttractorRadius", 0, 1000, 300, updateAttractor)
    parameter.number("AttractorStrength", 0, 100, 5, updateAttractor)

    -- Camera effects are documented separately; this demo focuses on GPU buffers.
end

function updateAttractor()
    local a = attractors[Attractor]
    a.params.z = (AttractorRadius or 300)
    a.params.w = (AttractorStrength or 5) / 100
    updatePositions[a.name] = a.params
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color

    updatePositions.screenSize = vec2(WIDTH, HEIGHT)
    local wx = updatePositions:workgroupSize()
    updatePositions:dispatch(math.ceil(instancesBuffer.size / wx), 1, 1)

    --b:push()
    background(40, 40, 50)
    style.push().noStroke().fill(255)
    for k,v in pairs(attractors) do
        ellipse(v.params.x, v.params.y, 5)
    end
    style.pop()
    m:draw(instancesBuffer.size)
    --b:pop()
end

function touched(touch)
    local a = attractors[Attractor]
    a.params.x = touch.x
    a.params.y = touch.y
    updateAttractor()
end

shader:dispatchIndirect(indirectBuffer, start, num)

Method

Syntax

shader:dispatchIndirect(indirectBuffer, start, num)

Parameters

indirectBuffer shader.indirectBuffer.

GPU buffer containing indirect dispatch commands.

start integer.

Zero-based index of the first indirect command.

num integer.

Number of indirect commands to execute.

shader.builder

Type

Syntax

shader.builder

Build a surface shader by chaining commands to choose lighting, define properties, and add shader code.

checkers = shader.builder()
   :lit()
   :vec2{"size", value = vec2(10, 10)}
   :color{"colorA", value = color.black}
   :color{"colorB", value = color.white}
   :material
   [[
      vec2 uv = getUV0();
      uv = floor(uv * size);
      float c = mod(uv.x + uv.y, 2);
      material.baseColor = mix(colorA, colorB, c);
   ]]
   :build()

shader.buffer

Type

Syntax

shader.buffer

shader.buffer.size

Property

Syntax

shader.buffer.size

Number of elements currently stored in this buffer. Use resize() to change its length.

shader.buffer(layout, size)

Constructor

Syntax

shader.buffer(layout, size)

Create a GPU buffer based on a layout

myBuffer = shader.buffer(layout)

Parameters

layout shader.bufferLayout.

Layout describing the attributes in each buffer element.

size integer, optional.

Number of elements in the buffer.

Returns

shader.buffer

shader.buffer:resize(size)

Method

Syntax

shader.buffer:resize(size)

Resize the buffer

Parameters

size integer.

Number of elements in the buffer.

shader.buffer:clear()

Method

Syntax

shader.buffer:clear()

Clear the buffer

shader.buffer:append(attribute1, value1, attribute2, value2, ...)

Method

Syntax

shader.buffer:append(attribute1, value1, attribute2, value2, ...)

Append a new element to the buffer using pairs of attributes and values

Parameters

attribute1 integer.

Attribute constant from the layout, such as shader.position.

value1 number or vec2 or vec3 or vec4.

Value matching the named attribute in the buffer layout.

attribute2 integer.

Another attribute constant from the layout.

value2 number or vec2 or vec3 or vec4.

Value matching the named attribute in the buffer layout.

... any, optional.

Additional attribute/value pairs, or integer indices for an index buffer.

shader.indexBuffer

Type

Syntax

shader.indexBuffer

shader.indexBuffer.size

Property

Syntax

shader.indexBuffer.size

Number of elements currently stored in this buffer. Use resize() to change its length.

shader.indexBuffer(type, size)

Constructor

Syntax

shader.indexBuffer(type, size)

Create a GPU index buffer (array of unsigned integers). Usually used for mesh primitive indices

myBuffer = shader.indexBuffer(shader.indexBuffer.i32|i16)

Buffer type can be 32 bit (shader.indexBuffer.i32) or 16 bit (shader.indexBuffer.i16)

Parameters

type integer.

Index format: shader.indexBuffer.i16 or shader.indexBuffer.i32.

size integer, optional.

Number of elements in the buffer.

Returns

shader.indexBuffer

shader.indexBuffer:resize(size)

Method

Syntax

shader.indexBuffer:resize(size)

Resize the buffer

Parameters

size integer.

Number of elements in the buffer.

shader.indexBuffer:clear()

Method

Syntax

shader.indexBuffer:clear()

Clear the buffer

shader.indexBuffer:append(i1, i2, i3, ...)

Method

Syntax

shader.indexBuffer:append(i1, i2, i3, ...)

Appends multiple elements to the buffer

Parameters

i1 integer.

Index value to append to the buffer.

i2 integer.

Index value to append to the buffer.

i3 integer.

Index value to append to the buffer.

... any.

Additional attribute/value pairs, or integer indices for an index buffer.

shader.bufferLayout

Type

Syntax

shader.bufferLayout

Represents the layout of a shader buffer, used to store arrays of structured data within a shader

shader.bufferLayout(definition)

Constructor

Syntax

shader.bufferLayout(definition)

Create a new buffer layout using a definition table

The buffer layout will need to match the target shader struct layout exactly in order to work with calls to shader:setBuffer()

The buffer definition table contains is an array of tables, each one containing the following format:

{attribute, count, type[, normalized]}

Each attribute maps to a field in the buffer struct to a specific type, where count maps to scalar and vector types respectively

Attributes

  • position
  • normal
  • tangent
  • bitangent
  • color0..3
  • indices
  • weight
  • texcoord0..9

Types

  • uint8
  • uint10
  • int16
  • half
  • float

Parameters

definition table.

Attribute definitions describing the memory layout of each buffer element.

Returns

shader.bufferLayout
Show code (40 lines)
updatePosVel = shader.compute
[[
   #version 430
   #include <codea/common.glsl>

   struct Instance
   {
      vec4 position;
   };

   BUFFER_RW(instances, Instance, BUFFER0);

   uniform uint instanceCount;

   NUM_THREADS(64, 1, 1)
   void main()
   {
      uint id = gl_GlobalInvocationID.x;
      if (id < instancesCount)
      {
         instances[id].position = vec4(0,1,2,3);
      }
   }
]]

function setup()
   layout = shader.bufferLayout{ {shader.position, 4, shader.float} }

   buffer = shader.buffer(layout)
   for i = 1, 10000 do
      local posVel = vec4()
      posVel.x = math.random(0, WIDTH)
      posVel.y = math.random(0, HEIGHT)
      posVel.z = math.random() * 0.5 - 0.5
      posVel.w = math.random() * 0.5 - 0.5
      buffer:append(shader.position, posVel)
   end
   updatePosVel:setBuffer("instancesBuffer", buffer)
   updatePosVel.instanceCount = posVel.size
end

shader.indirectBuffer

Type

Syntax

shader.indirectBuffer

A GPU buffer containing indirect draw or dispatch commands. Use it with mesh:drawIndirect() or shader:dispatchIndirect(). The command offset used by these methods starts at zero.

shader.indirectBuffer(size)

Constructor

Syntax

shader.indirectBuffer(size)

Allocates space for the specified number of GPU commands. Fill the commands on the GPU before using the buffer for drawing or dispatch.

Parameters

size integer.

Number of indirect commands the buffer can hold.

Returns

shader.indirectBuffer

The new indirect command buffer.

GPU Noise Library

Using GPU Noise top ↑

Overview

Use Brian Sharpe’s GPU noise library (http://briansharpe.wordpress.com) to create patterns and natural-looking variation in shaders.

Include it in GLSL with #include <codea/noise/noise.glsl>.

An example using this library can be found here

//
//  Code repository for GPU noise development blog
//  http://briansharpe.wordpress.com
//  https://github.com/BrianSharpe
//
//  I'm not one for copyrights.  Use the code however you wish.
//  All I ask is that credit be given back to the blog or myself when appropriate.
//  And also to let me know if you come up with any changes, improvements, thoughts or interesting uses for this stuff. :)
//  Thanks!
//
//  Brian Sharpe
//  brisharpe CIRCLE_A yahoo DOT com
//  http://briansharpe.wordpress.com
//  https://github.com/BrianSharpe
//

Value Noise top ↑

Overview
Documentation media
//  Value Noise 
//  Return value range of 0.0->1.0
//  http://briansharpe.files.wordpress.com/2011/11/valuesample1.jpg
//
float Value2D( vec2 P )
//  Value Noise 
//  Return value range of 0.0->1.0
//  http://briansharpe.files.wordpress.com/2011/11/valuesample1.jpg
//
float Value3D( vec3 P )
//  Value Noise 
//  Return value range of 0.0->1.0
//
float Value4D( vec4 P )
Documentation media
//  Perlin Noise   ( gradient noise )
//  Return value range of -1.0->1.0
//  http://briansharpe.files.wordpress.com/2011/11/perlinsample.jpg
//
float Perlin2D( vec2 P )
//  Perlin Noise   ( gradient noise )
//  Return value range of -1.0->1.0
//  http://briansharpe.files.wordpress.com/2011/11/perlinsample.jpg
//
float Perlin3D( vec3 P )
// Perlin Noise  ( gradient noise )
// Return value range of -1.0->1.0
//
float Perlin4D( vec4 P )
Documentation media
//  ValuePerlin Noise     ( value gradient noise )
//  A uniform blend between value and perlin noise
//  Return value range of -1.0->1.0
//  http://briansharpe.files.wordpress.com/2011/11/valueperlinsample.jpg
//
float ValuePerlin2D( vec2 P, float blend_val )
//  ValuePerlin Noise     ( value gradient noise )
//  A uniform blend between value and perlin noise
//  Return value range of -1.0->1.0
//  http://briansharpe.files.wordpress.com/2011/11/valueperlinsample.jpg
//
float ValuePerlin3D( vec3 P, float blend_val )
Documentation media
//  Cubist Noise 
//  http://briansharpe.files.wordpress.com/2011/12/cubistsample.jpg
//
//  Generates a noise which resembles a cubist-style painting pattern.  Final Range 0.0->1.0
//  NOTE:  contains discontinuities.  best used only for texturing.
//  NOTE:  Any serious game implementation should hard-code these parameter values for efficiency.
//
float Cubist2D( vec2 P, vec2 range_clamp )  // range_clamp.x = low, range_clamp.y = 1.0/(high-low).  suggest value low=-2.0  high=1.0
//  Cubist Noise 
//  http://briansharpe.files.wordpress.com/2011/12/cubistsample.jpg
//
//  Generates a noise which resembles a cubist-style painting pattern.  Final Range 0.0->1.0
//  NOTE:  contains discontinuities.  best used only for texturing.
//  NOTE:  Any serious game implementation should hard-code these parameter values for efficiency.
//
float Cubist3D( vec3 P, vec2 range_clamp )  // range_clamp.x = low, range_clamp.y = 1.0/(high-low).  suggest value low=-2.0  high=1.0

Cellular Noise top ↑

Overview
Documentation media
//  Cellular Noise 
//  Based off Stefan Gustavson's work at http://www.itn.liu.se/~stegu/GLSL-cellular
//  http://briansharpe.files.wordpress.com/2011/12/cellularsample.jpg
//
//  Speed up by using  search window instead of 
//  produces a range of 0.0->1.0
//
float Cellular2D(vec2 P)
//  Cellular Noise 
//  Based off Stefan Gustavson's work at http://www.itn.liu.se/~stegu/GLSL-cellular
//  http://briansharpe.files.wordpress.com/2011/12/cellularsample.jpg
//
//  Speed up by using  search window instead of 
//  produces range of 0.0->1.0
//
float Cellular3D(vec3 P)
Documentation media
Documentation media
//  PolkaDot Noise 
//  http://briansharpe.files.wordpress.com/2011/12/polkadotsample.jpg
//  http://briansharpe.files.wordpress.com/2012/01/polkaboxsample.jpg
//  TODO, these images have random intensity and random radius.  This noise now has intensity as proportion to radius.  Images need updated.  TODO
//
//  Generates a noise of smooth falloff polka dots.
//  Allow for control on radius.  Intensity is proportional to radius
//  Return value range of 0.0->1.0
//
float PolkaDot2D(   vec2 P,
                    float radius_low,               //      radius range is 0.0->1.0
                    float radius_high       )
//  PolkaDot Noise 
//  http://briansharpe.files.wordpress.com/2011/12/polkadotsample.jpg
//  http://briansharpe.files.wordpress.com/2012/01/polkaboxsample.jpg
//  TODO, these images have random intensity and random radius.  This noise now has intensity as proportion to radius.  Images need updated.  TODO
//
//  Generates a noise of smooth falloff polka dots.
//  Allow for control on radius.  Intensity is proportional to radius
//  Return value range of 0.0->1.0
//
float PolkaDot3D(   vec3 P,
                    float radius_low,               //      radius range is 0.0->1.0
                    float radius_high       )
Documentation media
//  Stars2D
//  http://briansharpe.files.wordpress.com/2011/12/starssample.jpg
//
//  procedural texture for creating a starry background.  ( looks good when combined with a nebula/space-like colour texture )
//  NOTE:  Any serious game implementation should hard-code these parameter values for efficiency.
//
//  Return value range of 0.0->1.0
//
float Stars2D(      vec2 P,
                float probability_threshold,                //      probability a star will be drawn  ( 0.0->1.0 )
                float max_dimness,                                  //      the maximal dimness of a star ( 0.0->1.0   0.0 = all stars bright,  1.0 = maximum variation )
                float two_over_radius )                             //      fixed radius for the stars.  radius range is 0.0->1.0  shader requires 2.0/radius as input.

Simplex Noise top ↑

Overview
Documentation media
//  SimplexPerlin2D  ( simplex gradient noise )
//  Perlin noise over a simplex (triangular) grid
//  Return value range of -1.0->1.0
//  http://briansharpe.files.wordpress.com/2012/01/simplexperlinsample.jpg
//
//  Implementation originally based off Stefan Gustavson's and Ian McEwan's work at...
//  http://github.com/ashima/webgl-noise
//
float SimplexPerlin2D( vec2 P )
Documentation media
//  SimplexPolkaDot2D
//  polkadots over a simplex (triangular) grid
//  Return value range of 0.0->1.0
//  http://briansharpe.files.wordpress.com/2012/01/simplexpolkadotsample.jpg
//
float SimplexPolkaDot2D(    vec2 P,
                            float radius,           //      radius range is 0.0->1.0
                            float max_dimness )     //      the maximal dimness of a dot ( 0.0->1.0   0.0 = all dots bright,  1.0 = maximum variation )
Documentation media
//  SimplexCellular2D
//  cellular noise over a simplex (triangular) grid
//  Return value range of 0.0->~1.0
//  http://briansharpe.files.wordpress.com/2012/01/simplexcellularsample.jpg
//
//  TODO:  scaling of return value to strict 0.0->1.0 range
//
float SimplexCellular2D( vec2 P )
//  SimplexPerlin3D  ( simplex gradient noise )
//  Perlin noise over a simplex (tetrahedron) grid
//  Return value range of -1.0->1.0
//  http://briansharpe.files.wordpress.com/2012/01/simplexperlinsample.jpg
//
//  Implementation originally based off Stefan Gustavson's and Ian McEwan's work at...
//  http://github.com/ashima/webgl-noise
//
float SimplexPerlin3D(vec3 P)
//  SimplexCellular3D
//  cellular noise over a simplex (tetrahedron) grid
//  Return value range of 0.0->~1.0
//  http://briansharpe.files.wordpress.com/2012/01/simplexcellularsample.jpg
//
//  TODO:  scaling of return value to strict 0.0->1.0 range
//
float SimplexCellular3D( vec3 P )
//  SimplexPolkaDot3D
//  polkadots over a simplex (tetrahedron) grid
//  Return value range of 0.0->1.0
//  http://briansharpe.files.wordpress.com/2012/01/simplexpolkadotsample.jpg
//
float SimplexPolkaDot3D(    vec3 P,
                            float radius,           //      radius range is 0.0->1.0
                            float max_dimness )     //      the maximal dimness of a dot ( 0.0->1.0   0.0 = all dots bright,  1.0 = maximum variation )

Hermite Noise top ↑

Overview
Documentation media
//  Hermite2D
//  Return value range of -1.0->1.0
//  http://briansharpe.files.wordpress.com/2012/01/hermitesample.jpg
//
float Hermite2D( vec2 P )
//  Hermite3D
//  Return value range of -1.0->1.0
//  http://briansharpe.files.wordpress.com/2012/01/hermitesample.jpg
//
float Hermite3D( vec3 P )
Documentation media
//  ValueHermite2D
//  Return value range of -1.0->1.0
//  ( allows for a blend between value and hermite noise )
//  http://briansharpe.files.wordpress.com/2012/01/valuehermitesample.jpg
//
float ValueHermite2D(       vec2 P,
                        float value_scale,                  //      value_scale = 2.0*MAXVALUE
                        float gradient_scale,               //      gradient_scale = 2.0*MAXGRADIENT
                        float normalization_val )   //      normalization_val = ( 1.0 / ( MAXVALUE + MAXGRADIENT * 0.3125 * 2.0 ) )
//  ValueHermite3D
//  Return value range of -1.0->1.0
//  ( allows for a blend between value and hermite noise )
//  http://briansharpe.files.wordpress.com/2012/01/valuehermitesample.jpg
//
float ValueHermite3D(       vec3 P,
                    float value_scale,                      //      value_scale = 2.0*MAXVALUE
                    float gradient_scale,           //      gradient_scale = 2.0*MAXGRADIENT
                    float normalization_val )       //      normalization_val = ( 1.0 / ( MAXVALUE + MAXGRADIENT * 0.3125 * 3.0 ) )

Noise Derivatives top ↑

Overview
//
//  Value2D_Deriv
//  Value2D noise with derivatives
//  returns vec3( value, xderiv, yderiv )
//
vec3 Value2D_Deriv( vec2 P )
//  Value3D_Deriv
//  Value3D noise with derivatives
//  returns vec4( value, xderiv, yderiv, zderiv )
//
vec4 Value3D_Deriv( vec3 P )
//  Perlin2D_Deriv
//  Classic Perlin  noise with derivatives
//  returns vec3( value, xderiv, yderiv )
//
vec3 Perlin2D_Deriv( vec2 P )
//  Perlin3D_Deriv
//  Classic Perlin  noise with derivatives
//  returns vec4( value, xderiv, yderiv, zderiv )
//
vec4 Perlin3D_Deriv( vec3 P )
//  PerlinSurflet2D_Deriv
//  Perlin Surflet  noise with derivatives
//  returns vec3( value, xderiv, yderiv )
//
vec3 PerlinSurflet2D_Deriv( vec2 P )
//  PerlinSurflet3D_Deriv
//  Perlin Surflet  noise with derivatives
//  returns vec4( value, xderiv, yderiv, zderiv )
//
vec4 PerlinSurflet3D_Deriv( vec3 P )
//  Cellular2D_Deriv
//  Cellular  noise with derivatives
//  returns vec3( value, xderiv, yderiv )
//
vec3 Cellular2D_Deriv(vec2 P)
//  Cellular3D Deriv
//  Cellular3D noise with derivatives
//  returns vec3( value, xderiv, yderiv )
//
vec4 Cellular3D_Deriv(vec3 P)
//  SimplexPerlin2D_Deriv
//  SimplexPerlin2D noise with derivatives
//  returns vec3( value, xderiv, yderiv )
//
vec3 SimplexPerlin2D_Deriv( vec2 P )
//  SimplexPerlin3D_Deriv
//  SimplexPerlin3D noise with derivatives
//  returns vec3( value, xderiv, yderiv, zderiv )
//
vec4 SimplexPerlin3D_Deriv(vec3 P)
//  Hermite2D_Deriv
//  Hermite2D noise with derivatives
//  returns vec3( value, xderiv, yderiv )
//
vec3 Hermite2D_Deriv( vec2 P )
//  Hermite3D_Deriv
//  Hermite3D noise with derivatives
//  returns vec3( value, xderiv, yderiv, zderiv )
//
vec4 Hermite3D_Deriv( vec3 P )