-
-
Save SkyTheCoder/9e8baca8c4837924e1c7 to your computer and use it in GitHub Desktop.
Some basic shadow maps.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--# 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