Last active
January 8, 2019 16:33
-
-
Save sp4cemonkey/5112003 to your computer and use it in GitHub Desktop.
Example 2 pass shadow mapping in Codea and OpenGL SL
This file contains hidden or 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 | |
-- 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
very cool thanks