Skip to content

Instantly share code, notes, and snippets.

@SkyTheCoder
Created May 6, 2015 18:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SkyTheCoder/9e8baca8c4837924e1c7 to your computer and use it in GitHub Desktop.
Save SkyTheCoder/9e8baca8c4837924e1c7 to your computer and use it in GitHub Desktop.
Some basic shadow maps.
--# Main
-- ShadowMap
displayMode(FULLSCREEN)
function setup()
scene = {main = {}, depth = {}}
boxA, boxAdepth = box(0, -4, 0, 8)
boxA.texture = readImage("Cargo Bot:Crate Yellow 2")
boxA:setColors(color(255, 255))
table.insert(scene.main, boxA)
table.insert(scene.depth, boxAdepth)
local numBoxes = 10 -- The number of boxes to fly around the scene
for i = 1, numBoxes do
math.randomseed(i)
boxB, boxBdepth = box(0, 0, 0, 1)--box(math.random(-5, 5), math.random(0, 5), math.random(-5, 5), 0.5)
boxB.texture = readImage("Cargo Bot:Crate Red 2")
boxB:setColors(color(255, 255))
table.insert(scene.main, boxB)
table.insert(scene.depth, boxBdepth)
end
local s = 1024 -- The size of the shadowmap (512, 1024, or 2048 offer good FPS, but you can go up to 4096)
depth = image(s / ContentScaleFactor, s / ContentScaleFactor)
light = vec3(5, 5, 5)
lightm = mesh() -- A cube to show where the light is
lightm.vertices = Primitive:Cube(0.5, 0.5, 0.5, light.x, light.y, light.z)
lightm:setColors(color(0))
pos = vec3(0, 1, 0) -- Variables to handle camera movement and direction
ang = vec2(0, 1)
ang2 = vec2(1, -1):normalize()
tpos = vec3(pos.x, pos.y, pos.z)
tang = vec2(ang.x, ang.y)
tang2 = vec2(ang2.x, ang2.y)
mId = 0
lId = 0
end
function move(k) -- Basic function to move and rotate the cubes to show off the shadowmaps
if k > 1 then
math.randomseed(k - 1)
translate(math.random(-5, 5), math.random(0, 5), math.random(-5, 5))
local dist = 4
local rot = 360
local speed = 0.25
translate(noise(ElapsedTime * speed + k, 0, k * 16) * dist, noise(ElapsedTime * speed + k, 16, k * 16) * dist, noise(ElapsedTime * speed + k, 32, k * 16) * dist)
rotate(noise(ElapsedTime * speed + k, 48, k * 16) * rot, 1, 0, 0)
rotate(noise(ElapsedTime * speed + k, 64, k * 16) * rot, 0, 1, 0)
rotate(noise(ElapsedTime * speed + k, 80, k * 16) * rot, 0, 0, 1)
end
end
function draw()
background(178, 211, 223, 255)
local pmix = 0.0375 -- Smoothes the camera movement
pos.x = pos.x * (1 - pmix) + tpos.x * pmix
pos.y = pos.y * (1 - pmix) + tpos.y * pmix
pos.z = pos.z * (1 - pmix) + tpos.z * pmix
local amix = 0.1
ang.x = ang.x * (1 - amix) + tang.x * amix
ang.y = ang.y * (1 - amix) + tang.y * amix
ang2.x = ang2.x * (1 - amix) + tang2.x * amix
ang2.y = ang2.y * (1 - amix) + tang2.y * amix
setContext(depth, true) -- Drawing into the depth map
background(255, 255)
camera(light.x, light.y, light.z, 0, 0, 0, 0, 1, 0)
perspective(150, depth.width / depth.height, 0.001, 65536)
local lightModelViewProjection = modelMatrix() * viewMatrix() * projectionMatrix()
for k, v in ipairs(scene.depth) do
pushMatrix()
move(k)
v.shader.lightPos = light
v.shader.model = modelMatrix()
v:draw()
popMatrix()
end
setContext() -- Drawing onto the screen
camera(pos.x, pos.y, pos.z, pos.x + ang.x * ang2.x, pos.y + ang2.y, pos.z + ang.y * ang2.x, 0, 1, 0)
--camera(light.x, light.y, light.z, 0, 0, 0, 0, 1, 0) -- Uncomment to show the scene from the light's position
perspective(80, WIDTH / HEIGHT, 0.001, 65536)
lightm:draw() -- Comment out if you are looking at the scene from the light's position
for k = #scene.main, 1, -1 do
local v = scene.main[k]
pushMatrix()
move(k)
v.shader.lightModelViewProjection = lightModelViewProjection
v.shader.shadowMap = depth
v.shader.lightPos = light
v.shader.model = modelMatrix()
v:draw()
popMatrix()
end
ortho()
viewMatrix(matrix())
resetMatrix()
tint(255, 127) -- Small preview of the light's perspective (depth map)
sprite(depth, 100, 100, 200, 200)
fill(255)
font("HelveticaNeue-Light")
fontSize(24)
local w, h = textSize(FPS)
text(FPS, w / 2, HEIGHT - h / 2)
collectgarbage()
collectgarbage()
collectgarbage()
end
function touched(touch) -- Handles camera movement and direction
if touch.state == BEGAN then
if touch.x < WIDTH / 2 and mId == 0 then
mId = touch.id
elseif touch.x >= WIDTH / 2 and lId == 0 then
lId = touch.id
end
end
if touch.id == mId then
local speed = 1 / 64
tpos.x = tpos.x + tang:rotate(math.pi / 2).x * touch.deltaX * speed
tpos.z = tpos.z + tang:rotate(math.pi / 2).y * touch.deltaX * speed
tpos.x = tpos.x + tang.x * tang2.x * touch.deltaY * speed
tpos.z = tpos.z + tang.y * tang2.x * touch.deltaY * speed
tpos.y = tpos.y + tang2.y * touch.deltaY * speed
elseif touch.id == lId then
tang = tang:rotate(math.rad(touch.deltaX / 2))
tang2 = tang2:rotate(math.rad(touch.deltaY / 2))
if tang2.x < 0.01 then
if tang2.y < 0 then
tang2 = vec2(0.01, -1):normalize()
else
tang2 = vec2(0.01, 1):normalize()
end
end
end
if touch.state == ENDED then
if touch.id == mId then
mId = 0
elseif touch.id == lId then
lId = 0
end
end
end
function box(px, py, pz, pw, ph, pl)
local x = px or 0
local y = py or x
local z = pz or x
local w = pw or 1
local h = ph or w
local l = pl or w
local main = mesh()
main.vertices = Primitive:Cube(w, h, l, x, y, z)
main.texCoords = Primitive:CubeTexCoords()
main:setColors(color(255))
main.shader = shader("Shadow")
local depth = mesh()
depth.vertices = Primitive:Cube(w, h, l, x, y, z)
depth.texCoords = Primitive:CubeTexCoords()
depth.shader = shader("Depth")
return main, depth
end
-- Function tweak to make shaders easier, so I can call shader("Depth") instead of shader(Shaders.Depth.vS, Shaders.Depth.fS)
local _shader = shader
function shader(...)
if select("#", ...) == 1 then
local data = Shaders[select(1, ...)]
if data ~= nil then
return _shader(data.vS, data.fS)
else
return _shader(...)
end
else
return _shader(...)
end
end
--# Shaders
-- Shaders
-- "Depth" shader encodes the distance between a fragment and the light
-- "Shadow" shader takes the encoded distance and calculates shadows with it
Shaders = {
--shaderstart:Depth
Depth = {
vS = [[
//
// A basic vertex shader
//
//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;
uniform mat4 model;
//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;
varying vec4 vPosition;
void main()
{
//Pass the mesh color to the fragment shader
vColor = color;
vTexCoord = texCoord;
vPosition = model * position;
//Multiply the vertex position by our combined transform
gl_Position = modelViewProjection * position;
}
]],
fS = [[
//
// A basic fragment shader
//
//Default precision qualifier
precision highp float;
//This represents the current texture on the mesh
uniform lowp sampler2D texture;
uniform vec3 lightPos;
//The interpolated vertex color for this fragment
varying lowp vec4 vColor;
//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;
varying vec4 vPosition;
void main()
{
highp float dist = distance(lightPos, vPosition.xyz);
lowp vec4 col = vec4(mod(dist, 1.0), mod(floor(dist) / 50.0, 1.0), floor(dist / 50.0) / 50.0, 1.0);
// Encoded RGB data:
// full red = 1 unit
// full green = 50 units
// full blue = 2500 units
// Add that all together and you get the encoded distance
//Set the output color to the texture color
gl_FragColor = col;
}
]],
},
--shaderend:Depth
--shaderstart:Shadow
Shadow = {
vS = [[
//
// A basic vertex shader
//
//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;
uniform mat4 model;
//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;
varying vec4 vPosition;
void main()
{
//Pass the mesh color to the fragment shader
vColor = color;
vTexCoord = texCoord;
vPosition = model * position;
//Multiply the vertex position by our combined transform
gl_Position = modelViewProjection * position;
}
]],
fS = [[
//
// A basic fragment shader
//
//Default precision qualifier
precision highp float;
uniform mat4 lightModelViewProjection;
//This represents the current texture on the mesh
uniform lowp sampler2D texture;
uniform lowp sampler2D shadowMap;
uniform vec3 lightPos;
//The interpolated vertex color for this fragment
varying lowp vec4 vColor;
//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;
varying vec4 vPosition;
void main()
{
if (!gl_FrontFacing) discard;
//Sample the texture at the interpolated coordinate
lowp vec4 col = texture2D( texture, vTexCoord );
vec4 m = lightModelViewProjection * vPosition;
vec2 lightTexCoord = vec2((m[0] / m[3] + 1.0) / 2.0, (m[1] / m[3] + 1.0) / 2.0); // The texture coordinate of this fragment from the light's perspective
highp vec4 lightCol = texture2D(shadowMap, lightTexCoord.xy); // The encoded distance
highp float lightDist = lightCol.r + lightCol.g * 50.0 + floor(lightCol.b * 2500.0); // The decoded distance between whatever fragment in this direction is closest to the light and the light itself
highp float dist = distance(vPosition.xyz, lightPos); // Distance between this fragment and the light
if (dist > lightDist + 0.2) col.rgb *= 0.5; // You can change the effect here to get different shadows
//Set the output color to the texture color
gl_FragColor = col * vColor;
}
]],
},
--shaderend:Shadow
}
--# Primitive
Primitive = class()
-- Primitive class originally by @spacemonkey
-- Edited by @SkyTheCoder to add options for width, height, length, and position, also to generate texture coordinates for the cube
--primitves gives basic mesh building for cubes and isospheres
--triangles are wound consistently to avoid gl_facing issues
function Primitive:Cube(w, h, l, x, y, z)
local s = 1
w = w or 1
h = h or w
l = l or h
x = x or 0
y = y or 0
z = z or 0
local vertices = {
vec3(-0.5*s, -0.5*s, 0.5*s), -- Left bottom front
vec3( 0.5*s, -0.5*s, 0.5*s), -- Right bottom front
vec3( 0.5*s, 0.5*s, 0.5*s), -- Right top front
vec3(-0.5*s, 0.5*s, 0.5*s), -- Left top front
vec3(-0.5*s, -0.5*s, -0.5*s), -- Left bottom back
vec3( 0.5*s, -0.5*s, -0.5*s), -- Right bottom back
vec3( 0.5*s, 0.5*s, -0.5*s), -- Right top back
vec3(-0.5*s, 0.5*s, -0.5*s), -- Left top back
}
-- now construct a cube out of the vertices above
v = {
-- Front
vertices[1], vertices[2], vertices[3],
vertices[1], vertices[3], vertices[4],
-- Right
vertices[2], vertices[6], vertices[7],
vertices[2], vertices[7], vertices[3],
-- Back
vertices[6], vertices[5], vertices[8],
vertices[6], vertices[8], vertices[7],
-- Left
vertices[5], vertices[1], vertices[4],
vertices[5], vertices[4], vertices[8],
-- Top
vertices[4], vertices[3], vertices[7],
vertices[4], vertices[7], vertices[8],
-- Bottom
vertices[5], vertices[6], vertices[2],
vertices[5], vertices[2], vertices[1],
}
for i, j in ipairs(v) do
v[i] = vec3(j.x * w + x, j.y * h + y, j.z * l + z)
end
return v
end
function Primitive:CubeTexCoords(w, h, x)
w = w or 1
h = h or w
x = x or Primitive:Cube(1)
local ret = {}
for i = 1, #x / 3 do
table.insert(ret, vec2(0.0, 0.0))
table.insert(ret, vec2(w, 0.0))
table.insert(ret, vec2(w, h))
table.insert(ret, vec2(0.0, 0.0))
table.insert(ret, vec2(w, h))
table.insert(ret, vec2(0.0, h))
end
return ret
end
--# FPS
FPS = 0 -- Code to calculate accurate frames per secoond
local frames = 0
local time = 0
tween.delay(0.001, function()
local d = draw
draw = function()
frames = frames + 1
if math.floor(ElapsedTime) ~= math.floor(time) then
FPS = frames - 1
frames = 1
end
time = ElapsedTime
d()
end
end)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment