Skip to content

Instantly share code, notes, and snippets.

@sp4cemonkey
Last active January 8, 2019 16:33
Show Gist options
  • Select an option

  • Save sp4cemonkey/5112003 to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown

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