Here’s my “Game of Life” shader, with the shaders built in as strings for ease of import/export. Note the comment about retina displays - I forget, is there some setting that can test for this?
If anyone has any ideas for better starting conditions, I’d love to hear them.
It’s basically the program in:
http://www.youtube.com/watch?v=tZTIiKcqdtI
except without the torus as that needs some stuff from my library so it makes it hard to make into a self contained program.
There are actually two shaders here. One is the basic Game of Life shader. The idea of this is to have two images and to alternate between them, reading from one and drawing on the other. The colours of pixels determine whether or not it is alive, but as we can pack more information than simply “alive - dead”, we also record the time since it died (if it is dead), and whether or not it has ever been alive. The main technical issue in this shader is that we want to know exactly which pixel we are dealing with, but the fragment shader works on a scaled basis where the image is viewed as having size 1x1 so we have to know the image’s actual width and height to know how far across the next pixel lies. Loops and arrays are a little problematic in shaders (or at least, I haven’t yet figured out how to do them properly), so I unrolled the obvious loop.
The second shader is a renderer. It takes the raw GoL data and “pretty prints” it. So it uses all the information to decide how to colour a given pixel. This leads to the “firey” effect as pixels die. In the full program, I use the “was once alive” to do a “reveal”. If a pixel is dead but has been alive then the colour is taken from an image. Then it looks as though there’s a picture with a cover that is being eaten away by the GoL pixels.
The noSmooth() is essential to ensuring that the GoL shader works pixel-by-pixel and not fuzzy pixel-by-fuzzy pixel. You can put a smooth() before the m:draw() to get a more smooth rendering.
For those with retina displays, there’s an interesting lesson on precision in shaders in this code. If you change the vfract function to have either mediump or lowp precision (for the return value), then you get very bizarre behaviour:
http://www.youtube.com/watch?v=Nn21l7OAkXA
displayMode(FULLSCREEN)
function setup()
local w,h = WIDTH,HEIGHT
source = image(w,h)
target = image(w,h)
gol = mesh()
local s = shader()
s.vertexProgram, s.fragmentProgram = golshader()
gol.shader = s
gol.texture = source
gol:addRect(w/2,h/2,w,h)
-- Factor of 2 is for retina displays
gol.shader.width = 2*w
gol.shader.height = 2*h
local nf = 50 + 5*math.random()
local xd = math.random()
local yd = math.random()
for x = 1,w do
for y = 1,h do
if noise(nf*x/w + xd,nf*y/h + yd) > .4 then
source:set(x,y,color(0,255,0,255))
else
source:set(x,y,color(0,0,0,255))
end
end
end
mm = mesh()
mm:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
s = shader()
s.vertexProgram, s.fragmentProgram = golrshader()
mm.shader = s
mm.texture = source
mm.shader.colour = color(36, 45, 69, 255)
mm.shader.alive = color(30, 164, 23, 255)
mm:setColors(255,255,255,255)
end
function draw()
background(56, 62, 61, 255)
noSmooth()
setContext(target)
gol:draw()
setContext()
source, target = target, source
gol.texture = source
mm.texture = source
mm:draw()
end
function touched(touch)
end
function golshader()
return [[
//
// A Game of Life vertex shader, does almost nothing.
//
//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;
//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
//This is an output variable that will be passed to the fragment shader
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
void main()
{
//Pass the everything to the fragment shader
vColor = color;
vTexCoord = texCoord;
gl_Position = modelViewProjection * position;
}
]],
[[
//
// A Game of Life fragment shader, does the hard work.
//
//This represents the current texture on the mesh
uniform lowp sampler2D texture;
//The interpolated vertex color for this fragment
varying lowp vec4 vColor;
//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;
// width and height of the texture for finding neighbours
uniform highp float width;
uniform highp float height;
lowp float colstep = .05;
highp float w = 1./width;
highp float h = 1./height;
highp vec2 vfract(highp vec2 v)
{
return vec2(fract(v.x),fract(v.y));
}
// get the neighbours' states
lowp int neighbours (lowp sampler2D s, highp vec2 p)
{
lowp int alive;
alive = 0;
if (texture2D(s,vfract(p + vec2(w,0.))).g > .5)
alive += 1;
if (texture2D(s,vfract(p + vec2(w,h))).g > .5)
alive += 1;
if (texture2D(s,vfract(p + vec2(0.,h))).g > .5)
alive += 1;
if (texture2D(s,vfract(p + vec2(-w,h))).g > .5)
alive += 1;
if (texture2D(s,vfract(p + vec2(-w,0.))).g > .5)
alive += 1;
if (texture2D(s,vfract(p + vec2(-w,-h))).g > .5)
alive += 1;
if (texture2D(s,vfract(p + vec2(0.,-h))).g > .5)
alive += 1;
if (texture2D(s,vfract(p + vec2(w,-h))).g > .5)
alive += 1;
return alive;
}
void main()
{
lowp int count;
lowp vec4 col;
count = neighbours(texture,vTexCoord);
col = texture2D(texture,vTexCoord);
if (col.r > colstep) {
col.r -= colstep;
} else {
col.r = 0.;
}
if (col.g > .5) {
// alive
if (count < 2 || count > 3) {
// lonely or overcrowded, kill it
col.g = 0.;
col.r = 1.;
}
} else {
// dead
if (count == 3) {
// born
col.g = 1.;
col.r = 1.;
col.b = 1.;
}
}
//Set the output color to the texture color
gl_FragColor = col;
}
]]
end
function golrshader()
return [[
//
// Rendering vertex shader, does almost nothing.
//
//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;
//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
//This is an output variable that will be passed to the fragment shader
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
void main()
{
//Pass the everything to the fragment shader
vColor = color;
vTexCoord = texCoord;
gl_Position = modelViewProjection * position;
}
]],[[
//
// Rendering fragment shader, interprets the raw GoL data.
//
//This is the raw data
uniform lowp sampler2D texture;
//Colours for rendering
uniform lowp vec4 colour;
uniform lowp vec4 alive;
//The interpolated vertex color for this fragment
varying lowp vec4 vColor;
//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;
void main()
{
//Sample the data at the interpolated coordinate
lowp vec4 col;
col = texture2D( texture, vTexCoord );
if (col.g == 1.) {
// alive, fade from yellow to blue
col.b = 0.;
col = col.r*col + (1.-col.r)*alive;
} else {
// dead
if (col.r > 0.) {
// but recently so
col.g = 0.;
col.b = 0.;
col.rgb = col.rgb + (1. - col.r)*colour.rgb;
} else {
col.rgb = colour.rgb;
}
}
//Set the output color
gl_FragColor = col;
}
]]
end