Skip to content

Instantly share code, notes, and snippets.

@sp4cemonkey
Last active January 8, 2019 16:33
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 sp4cemonkey/5112003 to your computer and use it in GitHub Desktop.
Save sp4cemonkey/5112003 to your computer and use it in GitHub Desktop.
Example 2 pass shadow mapping in Codea and OpenGL SL
--# Main
-- Light Map
-- Use this function to perform your initial setup
function setup()
displayMode(FULLSCREEN)
--initialise the world
world = World()
--add a box with internally facing normals for the world
world:addObject(vec3(16,16,16), vec3(0,8,0),3)
--add a box in the centre
world:addObject(vec3(1,1,1), vec3(0,0.5,0), 1)
--columns
world:addObject(vec3(0.5,1.5,0.5), vec3(-2,0.75,-2),1)
world:addObject(vec3(0.5,1.5,0.5), vec3(-2,0.75,2),1)
world:addObject(vec3(0.5,1.5,0.5), vec3(2,0.75,2),1)
world:addObject(vec3(0.5,1.5,0.5), vec3(2,0.75,-2),1)
--spheres
world:addObject(vec3(0.25,0.25,0.25), vec3(-2,1.75,-2),2)
world:addObject(vec3(0.25,0.25,0.25), vec3(-2,1.75,2),2)
world:addObject(vec3(0.25,0.25,0.25), vec3(2,1.75,2),2)
world:addObject(vec3(0.25,0.25,0.25), vec3(2,1.75,-2),2)
--create an image for our first pass shadowmap and assign it as a texture for our second pass
shadowMap = image(1000, 1000)
world.sphere.shader.shadowMap = shadowMap
world.block.shader.shadowMap = shadowMap
world.worldBox.shader.shadowMap = shadowMap
lightPos = vec2(0,7)
camPos = vec2(0,-5)
-- a global to hold our light view*projection matrix
lightMVP = matrix()
end
-- This function gets called once every frame
function draw()
--print(1/DeltaTime)
--each frame rotate the light position around the origin
lightPos = lightPos:rotate(math.rad(1))
camPos = camPos:rotate(math.rad(-1))
--first pass, render to the shadow map
setContext(shadowMap)
background(0,0,0,0)
--camera is from the lights point of view
camera(lightPos.x,3,lightPos.y,0,0,0)
perspective(60)
--store the view*projection matrix as we will need that in the second pass
lightMVP = viewMatrix()*projectionMatrix()
world:draw(vec3(lightPos.x,4,lightPos.y), vec3(lightPos.x,4,lightPos.y), 2)
--second pass render the scene using the shadowmap from the first pass
setContext()
camera(camPos.x,3,camPos.y,0,0,0)
perspective(70)
world:draw(vec3(camPos.x,3,camPos.y), vec3(lightPos.x,4,lightPos.y),1)
end
--# World
World = class()
function World:init(x)
--create our meshes for the final render
self.block = Primitive:Cube()
self.sphere = Primitive:Sphere(2)
self.worldBox = Primitive:Cube()
--create a duplicate set of meshes for the first pass depth render
self.dblock = Primitive:Cube()
self.dsphere = Primitive:Sphere(2)
self.dworldBox = Primitive:Cube()
--because we will be inside the worldBox I will need to rewind the triangles to face inwards
local vertexBuffer = self.worldBox:buffer("position")
local dvertexBuffer = self.dworldBox:buffer("position")
for i=1,self.worldBox.size/3 do
local holdv = vertexBuffer[i*3-1]
vertexBuffer[i*3-1] = vertexBuffer[i*3]
vertexBuffer[i*3] = holdv
dvertexBuffer[i*3-1] = vertexBuffer[i*3-1]
dvertexBuffer[i*3] = vertexBuffer[i*3]
end
--setup the meshes with normals and shaders
self:initObject(self.block,"B", "L")
self:initObject(self.dblock,"B", "D")
self:initObject(self.worldBox,"B", "L")
self:initObject(self.dworldBox,"B", "D")
self:initObject(self.sphere,"S", "L")
self:initObject(self.dsphere,"S", "D")
self.Objects = {}
self.frame = 1
end
function World:initObject(m, type, shadertype)
m:setColors(255,255,255,255)
local normalBuffer = m:buffer("normal")
normalBuffer:resize(m.size)
local vertexBuffer = m:buffer("position")
if type == "S" then
--if it's a sphere we want to take a simple sphere normal approach as this makes them smooth
self:deriveSphereN(self.sphere.size, vertexBuffer, normalBuffer)
else
--general purpose function from a previous project for deriving normals (and in other projects tangents)
self:deriveVertexNTB(false, false, m.size, vertexBuffer, normalBuffer)
end
if shadertype == "D" then
--first pass shader, effectively just renders a color based on distance from the light
m.shader = shader(World.DepthShader.vertexShader, World.DepthShader.fragmentShader)
else
--second pass does ADS lighting, but additionally applies shadows from the first pass texture
m.shader = shader(World.ADSLightingColor.vertexShader, World.ADSLightingColor.fragmentShader)
end
--ADS lighting settings for the second pass, but it doesn't matter just set it for all
m.shader.vAmbientMaterial = 0.1
m.shader.vDiffuseMaterial = 1.0
m.shader.vSpecularMaterial = 1.0
m.shader.lightColor = color(255,255,255,255)
end
function World:deriveSphereN(numVertexes, vertexBuffer, normalBuffer)
for i=1,numVertexes do
normalBuffer[i] = vertexBuffer[i]
end
end
function World:draw(eye, light, shaderType)
if shaderType == 2 then
--depth shader needs to know the light position
self.dblock.shader.vLightPosition = light
self.dsphere.shader.vLightPosition = light
self.dworldBox.shader.vLightPosition = light
end
if shaderType == 1 then
--ads shader needs to know camera and light position
self.block.shader.vEyePosition = eye
self.block.shader.vLightPosition = light
self.sphere.shader.vEyePosition = eye
self.sphere.shader.vLightPosition = light
self.worldBox.shader.vEyePosition = eye
self.worldBox.shader.vLightPosition = light
--ads shader also needs the view*projection matrix we stored from the shadow pass
self.block.shader.lightMVP = lightMVP
self.sphere.shader.lightMVP = lightMVP
self.worldBox.shader.lightMVP = lightMVP
end
--loop through my objects rendering each one
for k,v in ipairs(self.Objects) do
pushMatrix()
translate(v.position.x, v.position.y, v.position.z)
scale(v.vScale.x, v.vScale.y, v.vScale.z)
if v.type < 3 then
rotate(self.frame,0,1,0)
end
if v.type == 1 then -- block
if shaderType == 1 then --render the rigt mesh depending on pass
self.block.shader.mInvModel = modelMatrix():inverse():transpose()
self.block.shader.mModel = modelMatrix()
self.block:draw()
else
self.dblock.shader.mModel = modelMatrix()
self.dblock:draw()
end
end
if v.type == 2 then -- sphere
if shaderType == 1 then
self.sphere.shader.mInvModel = modelMatrix():inverse():transpose()
self.sphere.shader.mModel = modelMatrix()
self.sphere:draw()
else
self.dsphere.shader.mModel = modelMatrix()
self.dsphere:draw()
end
end
if v.type == 3 then -- worldbox
if shaderType == 1 then
self.worldBox.shader.mInvModel = modelMatrix():inverse():transpose()
self.worldBox.shader.mModel = modelMatrix()
self.worldBox:draw()
else
self.dworldBox.shader.mModel = modelMatrix()
self.dworldBox:draw()
end
end
popMatrix()
end
if shaderType == 1 then
--rotate objects 1 degree each frame
self.frame = self.frame + 1
end
end
function World:deriveVertexNTB(isTextured, isBump, numVertices, vertexBuffer, normalBuffer, textureBuffer, tangentBuffer)
--this will calculate the tangent, binormal and from those the normal for each vertex, these will all be stored in buffers
--assumes that the surfaces have their texture coordinates set (even if being left untextured)
--tangent is the X axis on the plane of the surface relative to textures
--binormal is the Y axis on the plane of the surface relative to textures
--normal is the surface normal
if isTextured == true then
useTexCoords = true
else
useTexCoords = false
end
local tangent,binormal, normal
for i=1, numVertices/3 do
--calculate the surface vectors
local v1 = vertexBuffer[i*3-1] - vertexBuffer[i*3-2]
local v2 = vertexBuffer[i*3] - vertexBuffer[i*3-2]
--calculate the texture space vectors
if useTexCoords then
local tuV = vec2(textureBuffer(i*3-1).x - textureBuffer(i*3-2).x, textureBuffer(i*3).x - textureBuffer(i*3-2).x)
local tvV = vec2(textureBuffer(i*3-1).y - textureBuffer(i*3-2).y, textureBuffer(i*3).y - textureBuffer(i*3-2).y)
--calculate denominator
local den=1/(tuV.x*tvV.y - tuV.y*tvV.x)
--tangent
tangent = vec3((tvV.y*v1.x - tvV.x*v2.x)*den, (tvV.y*v1.y - tvV.x*v2.y)*den, (tvV.y*v1.z - tvV.x*v2.z)*den):normalize()
binormal = vec3((tuV.x*v2.x - tuV.y*v1.x)*den, (tuV.x*v2.y - tuV.y*v1.y)*den, (tuV.x*v2.z - tuV.y*v1.z)*den):normalize()
normal = tangent:cross(binormal):normalize()
else
tangent = v1:normalize()
normal = v1:normalize():cross(v2:normalize()):normalize()
binormal = normal:cross(tangent):normalize()
end
for j=i*3-2,i*3 do
normalBuffer[j] = normal
--binormalBuffer[j] = binormal
if self.isBump == true then
tangentBuffer[j] = tangent
end
end
end
end
function World:addObject(vScale, position, type)
table.insert(self.Objects, {vScale = vScale, position = position, type = type })
end
--second pass lighting shader
World.ADSLightingColor = {
vertexShader = [[
uniform highp mat4 modelViewProjection;
uniform highp mat4 mInvModel;
uniform highp mat4 mModel;
uniform highp mat4 lightMVP;
uniform mediump vec3 vEyePosition;
uniform mediump vec3 vLightPosition;
attribute vec4 position;
attribute vec4 color;
attribute vec3 normal;
varying highp vec4 lightPosition;
varying highp vec4 worldVertex;
varying lowp vec4 vColor;
varying highp vec3 lightDirection;
varying mediump vec3 eyeDirection;
varying mediump vec3 vNormal;
//special matrix to move the camera positions from -1 to 1 into 0 to 1
const mat4 ScaleMatrix = mat4(0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.5, 0.5, 0.5, 1.0);
void main()
{
//convert the positions of the eye and light from world space to object space and then make a vector from position for ADS lighting
lightDirection = ((vec4(vLightPosition,1) * mInvModel) - position).xyz;
eyeDirection = ((vec4(vEyePosition,1) * mInvModel) - position).xyz;
//normal and color to fragment
vNormal = normal;
vColor = color;
//convert the position from object to world for matching calculation to depth shader
worldVertex = mModel * position;
//convert the world location into the context of the camera for texture lookups
lightPosition = ScaleMatrix * lightMVP * worldVertex;
gl_Position = modelViewProjection * position;
}
]],
fragmentShader = [[
precision lowp float;
uniform lowp sampler2D shadowMap;
varying highp vec4 lightPosition;
varying lowp vec4 vColor;
varying mediump vec3 lightDirection;
varying mediump vec3 eyeDirection;
varying mediump vec3 vNormal;
varying highp vec4 worldVertex;
uniform mediump vec3 vLightPosition;
uniform float vAmbientMaterial;
uniform float vDiffuseMaterial;
uniform float vSpecularMaterial;
uniform lowp vec4 lightColor;
const float c_zero = 0.0;
const float c_one = 1.0;
const float c_two = 2.0;
void main()
{
if (!gl_FrontFacing) discard;
//ADS lighting
vec3 curNormal = normalize(vNormal);
lowp vec4 curCol = vColor;
vec3 vLightDirection = normalize(lightDirection);
vec3 vCameraDirection = normalize(eyeDirection);
lowp vec4 vAmbientColor = curCol * lightColor * vAmbientMaterial;
// Calculate Diffuse intensity
float fDiffuseIntensity = max( c_zero, dot( curNormal, vLightDirection ));
lowp vec4 vDiffuseColor = curCol * lightColor * fDiffuseIntensity * vDiffuseMaterial;
// Calculate the reflection vector between the incoming light and the
// normal (incoming angle = outgoing angle)
vec3 vReflection = reflect( -vLightDirection, curNormal );
// Calculate specular component
// Based on the dot product between the reflection vector and the camera
// direction.
float spec = pow( max( c_zero, dot( vCameraDirection, vReflection )), 32.0 );
lowp vec4 vSpecularColor = lightColor * spec * vSpecularMaterial;
vAmbientColor.a = c_one;
vDiffuseColor.a = c_one;
vSpecularColor.a = c_one;
//adjust out position in light space and lookup on our shadow map
highp vec3 tlp = lightPosition.xyz / lightPosition.w;
float shadowMapDepth = texture2D(shadowMap, tlp.xy).r;
//calculate distance to compare to our shadow map
//1.003 <- avoid shadow acne compared to 1.0
float lightDistance = 1.003 - length((worldVertex.xyz/worldVertex.w) - vLightPosition)/30.0;
//if the depth from the shadow map is higher (closer) than the current depth just use ambience otherwise normal ADS
//additionally we'll check whether we are out of bounds on the texture as the shadow map only covers 60 degrees
if (shadowMapDepth > lightDistance && tlp.x < 1.0 && tlp.x > 0.0) {
gl_FragColor = vAmbientColor;
}
else {
gl_FragColor = vAmbientColor + vDiffuseColor + vSpecularColor;
}
}
]]
}
--first pass distance from light shader
World.DepthShader = {
vertexShader = [[
uniform highp mat4 modelViewProjection;
uniform highp mat4 mModel;
attribute vec4 position;
varying vec4 worldPosition;
void main()
{
//convert the vertex into world coordinates (same as the light)
worldPosition = mModel * position;
gl_Position = modelViewProjection * position;
}
]],
fragmentShader = [[
#extension GL_EXT_shader_framebuffer_fetch : require
precision lowp float;
uniform mediump vec3 vLightPosition;
varying mediump vec4 worldPosition;
const float c_one = 1.0;
const float depthConstant = 30.0;
void main()
{
if (!gl_FrontFacing) discard;
//adjust the world position to vec3
highp vec3 tlp = worldPosition.xyz / worldPosition.w;
//take the distance from the worldPosition to the light and adjust it to
//1.0 to 0.0 representing close to far
float col = max(1.0 - length(tlp.xyz - vLightPosition)/30.0, gl_LastFragData[0].x);
//set the colour, we could encode this better for more depth levels, but this is fine for now
gl_FragColor = vec4(col, col, col, c_one);
}
]]
}
--# Primitive
Primitive = class()
--primitves gives basic mesh building for cubes and isospheres
--triangles are wound consistently to avoid gl_facing issues
function Primitive:Cube()
m = mesh()
local vertices = {
vec3(-0.5, -0.5, 0.5), -- Left bottom front
vec3( 0.5, -0.5, 0.5), -- Right bottom front
vec3( 0.5, 0.5, 0.5), -- Right top front
vec3(-0.5, 0.5, 0.5), -- Left top front
vec3(-0.5, -0.5, -0.5), -- Left bottom back
vec3( 0.5, -0.5, -0.5), -- Right bottom back
vec3( 0.5, 0.5, -0.5), -- Right top back
vec3(-0.5, 0.5, -0.5), -- Left top back
}
-- now construct a cube out of the vertices above
m.vertices = {
-- 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],
}
return m
end
function Primitive:Sphere(depth)
m = mesh()
local t = (1 + math.sqrt(5)) / 2
--all the vertices of an icosohedron
local vertices = {
vec3(-1 , t, 0):normalize(),
vec3(1 , t, 0):normalize(),
vec3(-1 , -t, 0):normalize(),
vec3(1 , -t, 0):normalize(),
vec3(0 , -1, t):normalize(),
vec3(0 , 1, t):normalize(),
vec3(0 , -1, -t):normalize(),
vec3(0 , 1, -t):normalize(),
vec3(t , 0, -1):normalize(),
vec3(t , 0, 1):normalize(),
vec3(-t , 0, -1):normalize(),
vec3(-t , 0, 1):normalize()
}
--20 faces
icovertices = {
-- 5 faces around point 0
vertices[1], vertices[12], vertices[6],
vertices[1], vertices[6], vertices[2],
vertices[1], vertices[2], vertices[8],
vertices[1], vertices[8], vertices[11],
vertices[1], vertices[11], vertices[12],
-- 5 adjacent faces
vertices[2], vertices[6], vertices[10],
vertices[6], vertices[12], vertices[5],
vertices[12], vertices[11], vertices[3],
vertices[11], vertices[8], vertices[7],
vertices[8], vertices[2], vertices[9],
-- 5 faces around point 3
vertices[4], vertices[10], vertices[5],
vertices[4], vertices[5], vertices[3],
vertices[4], vertices[3], vertices[7],
vertices[4], vertices[7], vertices[9],
vertices[4], vertices[9], vertices[10],
--5 adjacent faces
vertices[5], vertices[10], vertices[6],
vertices[3], vertices[5], vertices[12],
vertices[7], vertices[3], vertices[11],
vertices[9], vertices[7], vertices[8],
vertices[10], vertices[9], vertices[2]
}
local finalVertices = {}
--divide each triangle into 4 sub triangles to make an isosphere
--this can be repeated (based on depth) for higher res spheres
for j=1,depth do
for i=1,#icovertices/3 do
midpoint1 = ((icovertices[i*3-2] + icovertices[i*3-1])/2):normalize()
midpoint2 = ((icovertices[i*3-1] + icovertices[i*3])/2):normalize()
midpoint3 = ((icovertices[i*3] + icovertices[i*3-2])/2):normalize()
--triangle 1
table.insert(finalVertices,icovertices[i*3-2])
table.insert(finalVertices,midpoint1)
table.insert(finalVertices,midpoint3)
--triangle 2
table.insert(finalVertices,midpoint1)
table.insert(finalVertices,icovertices[i*3-1])
table.insert(finalVertices,midpoint2)
--triangle 3
table.insert(finalVertices,midpoint2)
table.insert(finalVertices,icovertices[i*3])
table.insert(finalVertices,midpoint3)
--triangle 4
table.insert(finalVertices,midpoint1)
table.insert(finalVertices,midpoint2)
table.insert(finalVertices,midpoint3)
end
icovertices = finalVertices
finalVertices = {}
end
m.vertices = icovertices
return m
end
@rolantin
Copy link

rolantin commented Jan 8, 2019

very cool thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment