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.
Drawing a mesh has two main stages: a vertex shader positions its vertices, and a fragment shader colors the pixels covered by its triangles.
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.
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 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()
{
}
]]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.
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.
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.
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.
A shader definition is a Lua table containing its code and settings.
A shader definition is broken into several parts:
passes or a single passshdr = shader
{
properties =
{
{name, type, value, ...}
},
options =
{
NAME = {default, options}
},
passes =
{
{
vertex =
[[
...
]],
fragment =
[[
...
]]
}
}
}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
| Type Name | GLSL 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 = truerange = {low, high}onSet = function(...) endOptions 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.
Each pass contains shader code and its drawing settings. Use pass for a single pass or passes for a list of passes.
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.
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 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
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;
}
]]
}
}The following shading models are supported:
unlit Basic unlit shadinglit Physically based lit shading modelcustom Custom shading modelMaterialInputs 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
| Name | Type | Models | Description |
|---|---|---|---|
| baseColor | vec4 | All | Diffuse albedo color |
| metallic | float [0..1] | Lit | The metalness of the surface (zero or one) |
| roughness | float [0..1] | Lit | Perceived smoothness (1.0) or roughness (0.0) of a surface |
| reflectance | float [0..1] | Lit | |
| clearCoat | float [0..1] | Lit | |
| clearCoatRoughness | float [0..1] | Lit | |
| anisotropy | float [-1..1] | Lit | |
| anisotropyDirection | vec3 [0..1] | Lit | |
| ambientOcclusion | float [0..1] | Lit | |
| normal | vec3 [0..1] | Lit | |
| bentNormal | vec3 [0..1] | Lit | |
| clearCoatNormal | vec3 [0..1] | Lit | |
| emissive | vec4 [0..n] | Lit | |
| postLightingColor | vec4 [0..1] | Lit | |
| ior | float [0..1] | Lit | |
| transmission | float [0..1] | Lit | |
| absorption | float [0..n] | Lit | |
| microThickness | float [0..n] | Lit | |
| thickness | float [0..n] | Lit |
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.
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.
Download project Open in Codea to run this example.
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
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.
Download project Open in Codea to run this example.
-- 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
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.
Download project Open in Codea to run this example.
-- 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
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
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.
Download project Open in Codea to run this example.
-- 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
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.
Download project Open in Codea to run this example.
-- 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
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
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
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
vertices and draw with mesh:draw(). Each consecutive group of three vertices makes a triangle.positions and one-based indices. Keep colors and texture coordinates aligned with the vertex list.mesh.triangulate(points) to generate indices.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.
Download project Open in Codea to run this example.
-- 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.
Download project Open in Codea to run this example.
-- 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.
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
Represents a drawable shape made of vertices and primitives (typically triangles)
Vertices
Each vertex contains a number of attributes:
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)
Create an empty mesh
| submeshCount | integer, optional. The number of submeshes that this mesh will be created with (optoinal) |
mesh |
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.
| points | table. Polygon boundary vertices |
table |
Triangle indices into the supplied points |
Download project Open in Codea to run this example.
-- 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)
Loads a mesh from a file
The following mesh formats are currently supported:
GLTF / GLBFBXOBJSTLSkinned animations are supported but morph targets are currently not
Saving meshes is not currently supported
| key | asset.key. The asset key to load |
mesh.texture
The mesh texture, used by the default mesh shader (and accessible to custom shaders as well) for basic surface textures
mesh.shader
Custom shader for this mesh, used when mesh:draw() is invoked
mesh.material
The same as assigning a shader but with a material instead
mesh.submeshIndex
The current sub-mesh index. Useful for multi-material meshes
mesh.submeshCount
The total number of sub-meshes within this mesh
mesh.vertexCount
The total number of vertices in the currently selected sub-mesh
mesh.indexCount
The total number of indices in the currently selected sub-mesh
mesh.bounds
The local bounds of this mesh (no scaling or rotation applied)
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
Gets/sets the normals of the mesh vertices
mesh.colors
Gets/sets the colors of the mesh vertices
mesh.uvs
Gets/sets the uvs of the mesh vertices
mesh.indices
Gets/sets the indices of the mesh
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
Gets/sets the uvs of the mesh vertices
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
Contains the list of all animations for this mesh
mesh:draw(instances)
Draws the mesh to the screen with the current camera, matrix and context settings
| instances | integer, optional. Number of instances to draw; defaults to 1. |
mesh:drawIndirect(indirectBuffer, start, num)
Draws the mesh indirectly using indirectBuffer
Used in combination with compute shaders for indirect drawing operations
| 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)
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
| 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. |
integer |
the index of the new rectangle |
mesh:setRect(index, position, size, rotation)
Sets existing rectangle position, size and rotation using an index from a previous call to addRect()
| 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)
Sets existing rectangle uvs using an index from a previous call to addRect()
| 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)
Sets existing rectangle color using an index from a previous call to addRect()
| index | integer. Index of the vertex, index-buffer entry, or rectangle, starting at 1. |
| color | color. Vertex or rectangle color. |
mesh:resizeVertices(size)
Sets the number of vertices in the mesh (must be positive)
| size | integer. New number of vertices or indices. |
mesh:resizeIndices(size)
Sets the number of indices in the mesh (must be positive)
| size | integer. New number of vertices or indices. |
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)
| 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()
Clears the mesh, reducing vertices and indices to zero
mesh:position(index)
| index | integer. Index of the vertex, index-buffer entry, or rectangle, starting at 1. |
mesh:position(index, position)
Gets/sets the position of the vertex at index
| 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)
| index | integer. Index of the vertex, index-buffer entry, or rectangle, starting at 1. |
mesh:normal(index, normal)
Gets/sets the normal of the vertex at index
| index | integer. Index of the vertex, index-buffer entry, or rectangle, starting at 1. |
| normal | vec3. Vertex normal vector. |
mesh:color(index)
| index | integer. Index of the vertex, index-buffer entry, or rectangle, starting at 1. |
mesh:color(index, color)
Gets/sets the color of the vertex at index
| index | integer. Index of the vertex, index-buffer entry, or rectangle, starting at 1. |
| color | color. Vertex or rectangle color. |
mesh:uv(index)
| index | integer. Index of the vertex, index-buffer entry, or rectangle, starting at 1. |
mesh:uv(index, uv)
Gets/sets the uv of the vertex at index
| index | integer. Index of the vertex, index-buffer entry, or rectangle, starting at 1. |
| uv | vec2. Vertex texture coordinates. |
mesh:index(index)
| index | integer. Index of the vertex, index-buffer entry, or rectangle, starting at 1. |
mesh:index(index, i)
Gets/sets the index at index
| 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)
Generates a sphere mesh with various settings
| 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)
Generates an ico-sphere, aka spherical subdivided icosahedron
| 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)
Rectangular box centered at origin aligned along the x, y and z axis
| 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)
Rectangular box with rounded edges centered at origin aligned along the x, y and z axis
| 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)
A cone with a cap centered at origin pointing towards positive y-axis
| 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)
Capped cylinder centered at origin aligned along the y-axis
| 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)
Capsule centered at origin aligned along the y-axis
| 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)
A circular disk centered at origin on the xz-plane
| 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)
A flat plane centered at the origin on the xy-plane
| 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)
Generates a torus
| 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)
Generates a torus knot
| 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)
Generates the Utah teapot using the original data The lid is pointing towards the z axis and the spout towards the x axis
| subdivisions | integer, optional, default 8. Number of subdivisions used to generate the mesh; larger values create more geometry. |
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)
Create a new material using the supplied shader
| shader | shader. Shader used by this material. |
material |
material.lit()
Create a default lit material using the standard shader
material.unlit()
Create a default unlit material using the standard shader
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
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)
endshader
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)
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
| vertexSource | string. GLSL source for the vertex stage. |
| fragmentSource | string. GLSL source for the fragment stage. |
shader |
shader(shaderDefinition)
A more advanced form of shader creation, with many more options that can be tailored for specific use-cases
| shaderDefinition | table. Shader definition table containing stage source and configuration. |
shader |
shader.compute(source)
Create a compute shader without any extra settings
| source | string. GLSL compute shader source. |
shader.read(asset)
Read a shader asset from the filesystem
| asset | asset.key. Asset key identifying the shader to load. |
shader:setOption(name, on)
| name | string. Name of the shader option. |
| on | boolean. Whether to enable the named option. |
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.
| 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)
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”.
| name | string. Name of the buffer property |
| buffer | shader.buffer. Buffer to bind |
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.
| passIndex | integer, optional, default 0. Compute pass to inspect |
integer |
Local X, Y, and Z thread counts |
integer |
|
integer |
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.
| 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. |
Download project Open in Codea to run this example.
-- 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)
| 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
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
shader.buffer.size
Number of elements currently stored in this buffer. Use resize() to change its length.
shader.buffer(layout, size)
Create a GPU buffer based on a layout
myBuffer = shader.buffer(layout)
| layout | shader.bufferLayout. Layout describing the attributes in each buffer element. |
| size | integer, optional. Number of elements in the buffer. |
shader.buffer |
shader.buffer:resize(size)
Resize the buffer
| size | integer. Number of elements in the buffer. |
shader.buffer:clear()
Clear the buffer
shader.buffer:append(attribute1, value1, attribute2, value2, ...)
Append a new element to the buffer using pairs of attributes and values
| 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
shader.indexBuffer.size
Number of elements currently stored in this buffer. Use resize() to change its length.
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)
| type | integer. Index format: shader.indexBuffer.i16 or shader.indexBuffer.i32. |
| size | integer, optional. Number of elements in the buffer. |
shader.indexBuffer |
shader.indexBuffer:resize(size)
Resize the buffer
| size | integer. Number of elements in the buffer. |
shader.indexBuffer:clear()
Clear the buffer
shader.indexBuffer:append(i1, i2, i3, ...)
Appends multiple elements to the buffer
| 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
Represents the layout of a shader buffer, used to store arrays of structured data within a shader
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
positionnormaltangentbitangentcolor0..3indicesweighttexcoord0..9Types
uint8uint10int16halffloat| definition | table. Attribute definitions describing the memory layout of each buffer element. |
shader.bufferLayout |
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
endshader.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)
Allocates space for the specified number of GPU commands. Fill the commands on the GPU before using the buffer for drawing or dispatch.
| size | integer. Number of indirect commands the buffer can hold. |
shader.indirectBuffer |
The new indirect command buffer. |
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
// 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 )
// 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 )
// 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 )
// 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
// 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)

// 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 )
// 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.

// 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 )
// 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 )
// 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 )

// 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 )
// 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 ) )
//
// 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 )