Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dermotbalson/77e11615930da32156c64a077ee63441 to your computer and use it in GitHub Desktop.
Save dermotbalson/77e11615930da32156c64a077ee63441 to your computer and use it in GitHub Desktop.
Step by step lighting
--# Notes
--[[
This project shows how to add different types of lighting to 3D scenes (in this case, a cube)
--HOW TO USE THIS PROJECT
There are a number of tabs at the top. Press on a tab to see its code.
Work through the tabs from left to right, to see the project develop
You can run the code in any of the project tabs, by
1. running the program, and
2. selecting the tab number using the controls at the upper left of the screen
The program will remember your selection after that.
This enables you to work with one tab at a time, make changes and see the effects by running the program
Each tab has all the code for that demo, so you can copy the code in any tab to your own project and adapt it
IMPORTANT NOTES
1. Lighting requires the use of shaders. While you can use this code as a black box, it is
preferable that you understand how it works, so you can make changes or fix problems yourself.
2. It may be difficult to learn how lighting works just from looking at these demos. Ideally, you should
try to learn the basics of diffuse and specular light first.
--]]
--# Ambient
--Ambient light
--[[
Ambient light is the general lighting level
You could call it the minimum light that falls on every surface
if it is 0, then anything that is not lit will be pitch black
if it is 1, then everything is fully lit all the time
As you might guess, it is pretty simple - you just multiply the light level by the ambient factor
There's just one thing - the ambient light doesn't have to be white, so we specify the light colour
and everything will be tinted by this colour (see the extra parameter provided for this)
You are unlikely ever to use just ambient light on its own.
--]]
function setup()
block=CreateBlock(1,0.5,0.75,"Platformer Art:Block Brick")
currentModelMatrix=modelMatrix() --this is used for touches, not lighting
SetupShader()
end
--All the lighting code is kept in these two functions to make it easier for you to see it and use it yourself
function SetupShader() --set up the lighting
--in these demos, you get to change some of the lighting options using parameters
--in your own apps, you will probably just set the lighting values yourself
parameter.number("ambientStrength", 0, 1, 0.3) -- level of ambient light (0=dark, 1=light)
parameter.color("ambientColor", color(255,255,255,255)) --- colour of ambient light
block.shader=shader(AmbientShader.vertexShader,AmbientShader.fragmentShader)
end
function UpdateShader() --update the lighting details before drawing
--if you aren't going to be changing ambient colour or strength, you can put this line in setup
block.shader.ambient = ambientColor*ambientStrength
end
--this shader has ambient lighting
AmbientShader = {
vertexShader = [[
uniform mat4 modelViewProjection;
uniform vec4 ambient; // ambient light
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
void main()
{
vColor = color*ambient; // apply ambient light to object colour and pass to fragment shader
vTexCoord = texCoord;
gl_Position = modelViewProjection * position;
}
]],
fragmentShader = [[
precision highp float;
uniform lowp sampler2D texture;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
void main()
{
lowp vec4 col = texture2D(texture, vTexCoord) * vColor; //apply ambient light to texture
col.a = 1.0;
gl_FragColor = col;
}
]]
}
--UTILITY CODE - IT WILL NOT CHANGE FROM HERE ON --
function draw()
background(0)
perspective()
camera(-1,1,2,0,0,0)
HandleTouches() --allows you to rotate the cube with your fingers
UpdateShader()
block:draw()
end
--this code manages touches so you can rotate the cube
function HandleTouches()
modelMatrix(currentModelMatrix) --apply the stored settings
--do rotation for touch
if CurrentTouch.state == MOVING then --only rotate while fingers are moving on the screen
rotate(CurrentTouch.deltaX,0,1,0) --rotate by the x change, on the y axis (see note below)
rotate(CurrentTouch.deltaY,1,0,0) --and by the y change, on the x axis (see note below)
currentModelMatrix = modelMatrix() --store the resulting settings for next time
end
end
--this function creates a cube
function CreateBlock(w,h,d,tex,col,pos,ms) --width,height,depth,texture,colour,position,mesh (if existing already)
pos=pos or vec3(0,0,0)
local x,X,y,Y,z,Z=pos.x-w/2,pos.x+w/2,pos.y-h/2,pos.y+h/2,pos.z-d/2,pos.z+d/2
local v={vec3(x,y,Z),vec3(X,y,Z),vec3(X,Y,Z),vec3(x,Y,Z),vec3(x,y,z),vec3(X,y,z),vec3(X,Y,z),vec3(x,Y,z)}
local vert={v[1],v[2],v[3],v[1],v[3],v[4],v[2],v[6],v[7],v[2],v[7],v[3],v[6],v[5],v[8],v[6],v[8],v[7],
v[5],v[1],v[4],v[5],v[4],v[8],v[4],v[3],v[7],v[4],v[7],v[8],v[5],v[6],v[2],v[5],v[2],v[1]}
local texCoords
if tex then
local t={vec2(0,0),vec2(1,0),vec2(0,1),vec2(1,1)}
texCoords={t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],
t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3]}
end
local norm={} --calculate normals
for i=1,#vert,3 do
local n=GetNormal(vert[i],vert[i+1],vert[i+2])
norm[i],norm[i+1],norm[i+2]=n,n,n
end
if not ms then ms=mesh() end
if ms.size==0 then
ms.vertices=vert
ms.normals=norm
ms.texture,ms.texCoords=tex,texCoords
else
for i=1,#vert do
table.insert(ms.vertices,vert[i])
table.insert(ms.normals,norm[i])
if tex then table.insert(ms.texCoords,texCoords[i]) end
end
end
ms:setColors(col or color(255))
return ms
end
function GetNormal(v1,v2,v3)
return ((v1-v3):cross(v2-v3)):normalize()
end
function PrintExplanation()
output.clear()
print("Ambient light is the general background light that shines equally on all sides of an object")
print("Use a finger to rotate the cube")
end
--# Diffuse
--Diffuse
--[[
The sun is so far away that its light comes from the same direction for every part of your scene, and the
strength of the light is also the same across your scene. This is diffuse light - coming from a single direction
with a constant colour strength.
Diffuse light can have any colour, but because it is added to ambient light, the total of the two should not
generally exceed 255. In other words, ambient + diffuse <= color(255)
For example, you might use
ambient = color(255) * 0.3 --dim white light
diffuse = color(255,0,0) * 0.7 --red light
When you add these together, the maximum light is color(255,76,76)
Diffuse light is caused by reflection, and is brightest for any object directly facing the light, reducing if the
light hits the object at an angle.
Our objects are made from triangles of vertices. These triangles are flat surfaces, and the direction they are
facing is at right angles to that surface. This direction is called a "normal" (based on a latin word meaning 'at right angles'). So if you imagine lying flat on your back on a triangle of vertices, you are looking in the
'normal' direction - and if that is facing the diffuse light source, the light will be brightest.
You need two things to calculate the amount of diffuse light for any triangle
1. its 'normal' direction
2. a way of measuring how similar this is to the direction that the diffuse light is coming from
This demo provides a function for calculating the normal of any set of three vertices, and Codea meshes have a
normals property, so if you have a mesh m and a table of normals n, you can write
m.normals = n
(see the CreateBlock function below for an example)
The dot function is a convenient way of measuring the similarity of two directions, and that is used in the
shader to calculate the amount of reflection.
Note that because the normals are the same for all the vertices in each triangle, they will be the same for each
point in that triangle. So we can calculate diffuse light in the vertex shader, rather than the fragment shader,
which is much more efficient.
To set the direction the diffuse light is coming from, imagine a line running from (0,0,0) towards the diffuse
light. Take any point on that line, and "normalise" it to give it a length of 1. So if I am facing toward -z as
usual, here are some examples
vec3(-1,0,0) --a light that shines from left to right (no need to normalize, it already has length 1)
vec3(0,1,0) --a light that shines down vertically (again, no need to normalise)
vec3(-1,0.5,1):normalize() --a light that comes from behind me over my left shoulder
The absolute size of the numbers you use for x,y,z doesn't matter, since
vec3(1,2,-3):normalize() = vec3(10,20,-30):normalize()
but of course the relative size (how big x is, relative to y and z, for example) does matter..
--]]
function setup()
block=CreateBlock(1,0.5,0.75,"Platformer Art:Block Brick")
currentModelMatrix=modelMatrix() --this is used for touches, not lighting
SetupShader()
end
--All the lighting code is kept in these two functions to make it easier for you to see it and use it yourself
function SetupShader() --set up the lighting
--in your own apps, you will probably just set these lighting values yourself instead of using parameters
parameter.number("diffuseStrength", 0, 1, 0.7) --brightness of light source -- ***** NEW
parameter.color("lightColor", color(255)) -- ***** NEW
block.shader=shader(DiffuseShader.vertexShader,DiffuseShader.fragmentShader)
block.shader.ambientColor = color(255)*0.3
block.shader.directDirection = vec3(-1,0,1):normalize() -- ****** NEW
end
function UpdateShader() --update the lighting details before drawing
--if you aren't going to be changing colours or light strength, you can put the next line in setup
block.shader.directColor=diffuseStrength*lightColor -- ****** NEW
block.shader.mModel=modelMatrix() --this must be updated each time you draw
end
--shader follows
DiffuseShader = {
vertexShader = [[
uniform mat4 modelViewProjection;
uniform vec4 ambientColor;
uniform vec3 directDirection;
uniform vec4 directColor;
uniform mat4 mModel;
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
attribute vec3 normal;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
void main()
{
vec4 norm = normalize(mModel * vec4( normal, 0.0 )); //transform normal to world speace
float diffuse = max( 0.0, dot( norm.xyz, directDirection )); //calculate strength of reflection
vColor = ambientColor + diffuse * directColor; //total color
vColor.a = 1.0;
vTexCoord = texCoord;
gl_Position = modelViewProjection * position;
}
]],
fragmentShader = [[
precision highp float;
uniform lowp sampler2D texture;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
void main()
{
gl_FragColor = texture2D(texture, vTexCoord) * vColor;
}
]]
}
--EVERYTHING BELOW IS UNCHANGED --
function draw()
background(0)
perspective()
camera(-1,1,2,0,0,0)
HandleTouches()
UpdateShader()
block:draw()
end
function HandleTouches()
modelMatrix(currentModelMatrix) --apply the stored settings
--do rotation for touch
if CurrentTouch.state == MOVING then --only rotate while fingers are moving on the screen
rotate(CurrentTouch.deltaX,0,1,0) --rotate by the x change, on the y axis (see note below)
rotate(CurrentTouch.deltaY,1,0,0) --and by the y change, on the x axis (see note below)
currentModelMatrix = modelMatrix() --store the resulting settings for next time
end
end
function CreateBlock(w,h,d,tex,col,pos,ms) --width,height,depth,texture,colour,position,mesh (if existing already)
pos=pos or vec3(0,0,0)
local x,X,y,Y,z,Z=pos.x-w/2,pos.x+w/2,pos.y-h/2,pos.y+h/2,pos.z-d/2,pos.z+d/2
local v={vec3(x,y,Z),vec3(X,y,Z),vec3(X,Y,Z),vec3(x,Y,Z),vec3(x,y,z),vec3(X,y,z),vec3(X,Y,z),vec3(x,Y,z)}
local vert={v[1],v[2],v[3],v[1],v[3],v[4],v[2],v[6],v[7],v[2],v[7],v[3],v[6],v[5],v[8],v[6],v[8],v[7],
v[5],v[1],v[4],v[5],v[4],v[8],v[4],v[3],v[7],v[4],v[7],v[8],v[5],v[6],v[2],v[5],v[2],v[1]}
local texCoords
if tex then
local t={vec2(0,0),vec2(1,0),vec2(0,1),vec2(1,1)}
texCoords={t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],
t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3]}
end
local norm={} --calculate normals
for i=1,#vert,3 do
local n=GetNormal(vert[i],vert[i+1],vert[i+2])
norm[i],norm[i+1],norm[i+2]=n,n,n
end
if not ms then ms=mesh() end
if ms.size==0 then
ms.vertices=vert
ms.normals=norm
ms.texture,ms.texCoords=tex,texCoords
else
for i=1,#vert do
table.insert(ms.vertices,vert[i])
table.insert(ms.normals,norm[i])
if tex then table.insert(ms.texCoords,texCoords[i]) end
end
end
ms:setColors(col or color(255))
return ms
end
function GetNormal(v1,v2,v3)
return ((v1-v3):cross(v2-v3)):normalize()
end
function PrintExplanation()
output.clear()
print("Diffuse light comes from a specific direction, so far away that it is in the same direction (and has the same strength) for every part of the object you are lighting.\n\nExample - the sun")
print("The part of the object facing the light direction will be lit the most")
print("Use a finger to rotate the cube")
end
--# Specular
--Specular
--[[
Specular light is the reflection from a light source into your eye (the bright spot!)
It is too complex to explain fully here, but it needs the camera position
The extra shader settings below are
* specularPower = how focussed and concentrated the light is. Use a power of 2, eg 1,2,4,8,16,32,64,...
(the demo has a Focus parameter which is from 1-10. It is converted to powers of 2, so 3 becomes 2^3 = 8)
* shine - how shiny the object is (0 to 1)
* eyePosition - (vec3(x,y,z) position of camera
--]]
function setup()
block=CreateBlock(1,0.5,0.75,"Platformer Art:Block Brick")
currentModelMatrix=modelMatrix() --this is used for touches, not lighting
camPos=vec3(0,0,2) -- ***** NEW
z,zd=0,-.01 --to move block back and forward
SetupShader()
end
--All the lighting code is kept in these two functions to make it easier for you to see it and use it yourself
function SetupShader() --set up the lighting
--set position of light source
block.shader=shader(SpecularShader.vertexShader,SpecularShader.fragmentShader)
block.shader.ambientColor = color(255) *0.5
block.shader.directColor=color(255,255,0) *0.7
block.shader.directDirection = vec3(1,0,1):normalize()
block.shader.shine=1 --very shiny -- ***** NEW
--you can play with the size and focus of the spot using this parameter (make sure it is a power of 2)
--convert the user choice to a power of 2
parameter.integer("Focus",1,10,5,function() block.shader.specularPower=2^Focus end)
end
function UpdateShader() --update the lighting details before drawing
block.shader.mModel=modelMatrix() --this must be updated each time you draw
block.shader.eyePosition=camPos -- ***** NEW
block.shader.mModel=modelMatrix()
end
function draw()
background(0)
perspective()
currentModelMatrix[15]=z
camera(camPos.x,camPos.y,camPos.z,0,0,0)
HandleTouches()
UpdateShader()
block:draw()
z=z+zd
if z<-3 or z>0 then zd=-zd end
end
--shader follows
SpecularShader={
vertexShader=[[
uniform mat4 modelViewProjection;
uniform mat4 mModel;
uniform vec4 directColor;
uniform vec3 directDirection;
uniform vec4 ambientColor;
attribute vec4 position;
attribute vec2 texCoord;
attribute vec3 normal;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
varying lowp vec4 vPosition;
varying lowp vec4 vNormal;
varying vec4 vLight;
void main()
{
vTexCoord = texCoord;
vPosition = mModel * position;
gl_Position = modelViewProjection * position;
vNormal = mModel * vec4(normal, 0.0); //transform normal to world space
vLight = ambientColor+directColor * max( 0.0, dot(normalize(vNormal.xyz), directDirection));
}
]],
fragmentShader=[[
precision highp float;
uniform lowp sampler2D texture;
uniform vec4 directColor;
uniform vec3 directDirection;
uniform vec3 eyePosition;
uniform float specularPower;
uniform float shine;
varying highp vec2 vTexCoord;
varying lowp vec4 vPosition;
varying lowp vec4 vNormal;
varying vec4 vLight;
vec4 norm = normalize(vNormal);
vec4 eye = vec4(eyePosition, 1.0);
vec4 direct = vec4(directDirection, 0.0);
void main()
{
//calculate specular light
vec4 cameraDirection = normalize( eye - vPosition );
vec4 halfAngle = normalize( cameraDirection + direct );
float spec = pow( max( 0.0, dot( norm, halfAngle)),specularPower);
vec4 specLight = min(directColor + 0.5, 1.0) * spec * shine;
vec4 totalColor=texture2D(texture, vTexCoord)*(vLight+specLight);
totalColor.a= 1.;
gl_FragColor=totalColor;
}
]]
}
--EVERYTHING BELOW IS UNCHANGED --
function HandleTouches()
modelMatrix(currentModelMatrix) --apply the stored settings
--do rotation for touch
if CurrentTouch.state == MOVING then --only rotate while fingers are moving on the screen
rotate(CurrentTouch.deltaX,0,1,0) --rotate by the x change, on the y axis (see note below)
rotate(CurrentTouch.deltaY,1,0,0) --and by the y change, on the x axis (see note below)
currentModelMatrix = modelMatrix() --store the resulting settings for next time
end
end
function CreateBlock(w,h,d,tex,col,pos,ms) --width,height,depth,texture,colour,position,mesh (if existing already)
pos=pos or vec3(0,0,0)
local x,X,y,Y,z,Z=pos.x-w/2,pos.x+w/2,pos.y-h/2,pos.y+h/2,pos.z-d/2,pos.z+d/2
local v={vec3(x,y,Z),vec3(X,y,Z),vec3(X,Y,Z),vec3(x,Y,Z),vec3(x,y,z),vec3(X,y,z),vec3(X,Y,z),vec3(x,Y,z)}
local vert={v[1],v[2],v[3],v[1],v[3],v[4],v[2],v[6],v[7],v[2],v[7],v[3],v[6],v[5],v[8],v[6],v[8],v[7],
v[5],v[1],v[4],v[5],v[4],v[8],v[4],v[3],v[7],v[4],v[7],v[8],v[5],v[6],v[2],v[5],v[2],v[1]}
local texCoords
if tex then
local t={vec2(0,0),vec2(1,0),vec2(0,1),vec2(1,1)}
texCoords={t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],
t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3]}
end
local norm={} --calculate normals
for i=1,#vert,3 do
local n=GetNormal(vert[i],vert[i+1],vert[i+2])
norm[i],norm[i+1],norm[i+2]=n,n,n
end
if not ms then ms=mesh() end
if ms.size==0 then
ms.vertices=vert
ms.normals=norm
ms.texture,ms.texCoords=tex,texCoords
else
for i=1,#vert do
table.insert(ms.vertices,vert[i])
table.insert(ms.normals,norm[i])
if tex then table.insert(ms.texCoords,texCoords[i]) end
end
end
ms:setColors(col or color(255))
return ms
end
function GetNormal(v1,v2,v3)
return ((v1-v3):cross(v2-v3)):normalize()
end
function PrintExplanation()
output.clear()
print("Specular light adds a bright spot to the reflection")
print("The brightness depends on how much the light reflects off the surface toward the camera")
print("Play with the focus setting to make the spot larger or smaller")
print("Use a finger to rotate the cube")
end
--# Point
--Point
--[[
Point light is light from a point in space (rather than from a direction, like diffuse light). The light shines in all directions, like a lightbulb.
This means that the light may hit two points on a triangle at different angles, so we can't calculate it
in the vertex shader. Additionally, point lights often have a limited range, so the brightness reduces
with distance.
To be clear about the differences with directional light, which we have worked with until now..
Directional light (eg the sun)
- light comes from the same direction for every part of the scene
- the strength is constant
Point light (eg light bulb)
- the light is (usually) inside the scene, so it hits every part of the scene at different angles
- the light shines in every direction from the position of the light
- the strength of the light may vary with distance
The shader in this demo allows you to have both types of light, and the shader variables have a "direct" or "point" prefix to make it clear which type they refer to.
You can add more point lights if you want, just
add extra shader variables (you'll note the point light variables all end in 1, so just duplicate all the variables
with 2 on the end, for a second light (and adjust the shader to do the extra calculations)
NOTE that directional light (as shown in the previous three demos) is included in this demo, but its light has
been set to nil so that you can see the point light clearly
--]]
function setup()
block=CreateBlock(1,0.5,0.75,"Platformer Art:Block Brick")
currentModelMatrix=modelMatrix() --this is used for touches, not lighting
camPos=vec3(0,0,2)
z,zd=0,-.01
SetupShader()
end
--All the lighting code is kept in these two functions to make it easier for you to see it and use it yourself
function SetupShader() --set up the lighting
--set position of light source
block.shader=shader(PointShader.vertexShader,PointShader.fragmentShader)
block.shader.ambientColor = color(255) *0.2
block.shader.directColor=color(255) * 0 --NOTE-turned off so you can see the point light
block.shader.directDirection = vec3(-1,0,1):normalize()
block.shader.shine=0 --set this to 0 if you don't want specular lighting for your directional light
block.shader.specularPower=32
--point light variables needed by the shader --- ****** NEW
block.shader.point1Position=vec3(2,0,1) --this is an actual position to the right of us
block.shader.point1Color=color(255) --multiply by a factor (eg 0.5) if you want to reduce brightness
block.shader.point1Range=6 --light reduces to nil over this distance
end
function UpdateShader() --update the lighting details before drawing
block.shader.mModel=modelMatrix()
block.shader.eyePosition=camPos
end
function draw()
background(0)
perspective()
camera(camPos.x,camPos.y,camPos.z,0,0,0)
currentModelMatrix[15]=z
HandleTouches()
UpdateShader()
block:draw()
z=z+zd
if z<-6 or z>0 then zd=-zd end
end
--shader follows
PointShader={
vertexShader=[[
uniform mat4 modelViewProjection;
uniform mat4 mModel;
uniform vec4 directColor;
uniform vec3 directDirection;
uniform vec4 ambientColor;
uniform vec3 point1Position;
uniform vec4 point1Color;
attribute vec4 position;
attribute vec2 texCoord;
attribute vec3 normal;
varying highp vec2 vTexCoord;
varying lowp vec4 vPosition;
varying lowp vec4 vNormal;
varying vec4 vAmbientDiffuse;
varying vec4 vPoint1Diffuse;
void main()
{
vTexCoord = texCoord;
vPosition = mModel * position;
gl_Position = modelViewProjection * position;
vNormal = mModel * vec4(normal, 0.0);
//calculate diffuse directional light
vec3 norm = normalize(vNormal.xyz);
vAmbientDiffuse = ambientColor+directColor*max(0.0, dot(norm, directDirection));
//calculate diffuse point light
vec3 d = normalize( point1Position - vPosition.xyz );
vPoint1Diffuse = point1Color * max(0.0, dot(norm, d));
}
]],
fragmentShader=[[
precision highp float;
uniform lowp sampler2D texture;
uniform vec4 directColor;
uniform vec3 directDirection;
uniform vec3 eyePosition;
uniform float specularPower;
uniform float shine;
uniform vec3 point1Position;
uniform float point1Range;
uniform vec4 point1Color;
varying highp vec2 vTexCoord;
varying lowp vec4 vPosition;
varying lowp vec4 vNormal;
varying vec4 vAmbientDiffuse;
varying vec4 vPoint1Diffuse;
vec3 norm = normalize(vNormal).xyz;
vec4 specColor = min(directColor + 0.5, 1.0) * shine;
void main()
{
vec3 cameraDirection = normalize(eyePosition - vPosition.xyz);
//calculate specular light for directional light
vec3 halfAngle = normalize(cameraDirection + directDirection);
vec4 specDirect = specColor*pow(max(0.0, dot(norm, halfAngle)),specularPower);
//calculate specular light for point light
halfAngle = normalize(cameraDirection + normalize(point1Position - vPosition.xyz));
vec4 specPoint = specColor*pow(max(0.0, dot(norm, halfAngle)),specularPower);
//calculate strength of point light
float point1Strength = max(0.0, 1.0-length(point1Position-vPosition.xyz) / point1Range);
//add it up and apply to texture pixel
vec4 col=texture2D(texture, vTexCoord);
col=col*(vAmbientDiffuse+specDirect+point1Strength * (vPoint1Diffuse+specPoint));
col.a= 1.;
gl_FragColor=col;
}
]]
}
--EVERYTHING BELOW IS UNCHANGED --
function HandleTouches()
modelMatrix(currentModelMatrix) --apply the stored settings
--do rotation for touch
if CurrentTouch.state == MOVING then --only rotate while fingers are moving on the screen
rotate(CurrentTouch.deltaX,0,1,0) --rotate by the x change, on the y axis (see note below)
rotate(CurrentTouch.deltaY,1,0,0) --and by the y change, on the x axis (see note below)
currentModelMatrix = modelMatrix() --store the resulting settings for next time
end
end
function CreateBlock(w,h,d,tex,col,pos,ms) --width,height,depth,texture,colour,position,mesh (if existing already)
pos=pos or vec3(0,0,0)
local x,X,y,Y,z,Z=pos.x-w/2,pos.x+w/2,pos.y-h/2,pos.y+h/2,pos.z-d/2,pos.z+d/2
local v={vec3(x,y,Z),vec3(X,y,Z),vec3(X,Y,Z),vec3(x,Y,Z),vec3(x,y,z),vec3(X,y,z),vec3(X,Y,z),vec3(x,Y,z)}
local vert={v[1],v[2],v[3],v[1],v[3],v[4],v[2],v[6],v[7],v[2],v[7],v[3],v[6],v[5],v[8],v[6],v[8],v[7],
v[5],v[1],v[4],v[5],v[4],v[8],v[4],v[3],v[7],v[4],v[7],v[8],v[5],v[6],v[2],v[5],v[2],v[1]}
local texCoords
if tex then
local t={vec2(0,0),vec2(1,0),vec2(0,1),vec2(1,1)}
texCoords={t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],
t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3]}
end
local n={vec3(0,0,1),vec3(1,0,0),vec3(0,0,-1),vec3(-1,0,0),vec3(0,1,0),vec3(0,-1,0)}
local norm={}
for i=1,6 do for j=1,6 do norm[#norm+1]=n[i] end end
if not ms then ms=mesh() end
if ms.size==0 then
ms.vertices=vert
ms.normals=norm
ms.texture,ms.texCoords=tex,texCoords
else
for i=1,#vert do
table.insert(ms.vertices,vert[i])
table.insert(ms.normals,norm[i])
if tex then table.insert(ms.texCoords,texCoords[i]) end
end
end
ms:setColors(col or color(255))
return ms
end
function PrintExplanation()
output.clear()
print("A point light is at a specific place in the scene and usually has limited range. \n\nRotate the cube with your finger to see the effect.")
end
--# Spot
--Spot
--[[
A spot light is like a point light, except that the light doesn't shine in all directions. A spot light is like - well a spotlight, or torch, with a radius for the beam of light.
The shader needs two extra items, the direction in which the light is pointing, and the radius of the beam
The demo includes an optional extra, a flicker, which is not part of the shader. Flickering is produced simply
by varying the visibility of the spot light using the noise function (see the UpdateShader function).
--]]
function setup()
block=CreateBlock(1,0.5,0.75,"Platformer Art:Block Brick")
currentModelMatrix=modelMatrix() --this is used for touches, not lighting
camPos=vec3(0,0,2)
z,zd=0,-.01
SetupShader()
end
--All the lighting code is kept in these two functions to make it easier for you to see it and use it yourself
function SetupShader() --set up the lighting
--set position of light source
block.shader=shader(SpotShader.vertexShader,SpotShader.fragmentShader)
block.shader.ambientColor = color(255) *0.1
block.shader.directColor=color(255,255,0) *0
block.shader.directDirection = vec3(-1,0,1):normalize()
block.shader.shine=0 --set this to 0 if you don't want specular lighting for your directional light
block.shader.specularPower=32
--point light variables needed by the shader --- ****** NEW
block.shader.point1Position=vec3(0,0,2) --this is an actual position to the right of us
block.shader.point1Color=color(255) *0.7 --multiply by a factor (eg 0.5) if you want to reduce brightness
block.shader.point1Range=20 --light reduces to nil over this distance
block.shader.point1Direction=vec3(0,0,-1):normalize()
block.shader.spotAngle=5 --width of light beam in degrees
parameter.number("Flicker",0,0.6,0)
end
function UpdateShader() --update the lighting details before drawing
block.shader.mModel=modelMatrix()
block.shader.eyePosition=camPos
block.shader.point1Range=20*(1-Flicker+noise(ElapsedTime*5)*Flicker)
end
function draw()
background(0)
perspective()
camera(camPos.x,camPos.y,camPos.z,0,0,0)
currentModelMatrix[15]=z
HandleTouches()
UpdateShader()
block:draw()
z=z+zd
if z<-6 or z>0 then zd=-zd end
end
--shader follows
SpotShader={
vertexShader=[[
uniform mat4 modelViewProjection;
uniform mat4 mModel;
uniform vec4 directColor;
uniform vec3 directDirection;
uniform vec4 ambientColor;
uniform vec3 point1Position;
uniform vec4 point1Color;
attribute vec4 position;
attribute vec2 texCoord;
attribute vec3 normal;
varying highp vec2 vTexCoord;
varying lowp vec4 vPosition;
varying lowp vec4 vNormal;
varying vec4 vAmbientDiffuse;
varying vec4 vPoint1Diffuse;
void main()
{
vTexCoord = texCoord;
vPosition = mModel * position;
gl_Position = modelViewProjection * position;
vNormal = mModel * vec4(normal, 0.0);
//calculate diffuse light
vec3 norm = normalize(vNormal.xyz);
vAmbientDiffuse = ambientColor+directColor*max(0.0, dot(norm, directDirection));
vec3 d = normalize(point1Position - vPosition.xyz);
vPoint1Diffuse = point1Color * max(0.0, dot(norm, d));
}
]],
fragmentShader=[[
precision highp float;
uniform lowp sampler2D texture;
uniform vec4 directColor;
uniform vec3 directDirection;
uniform vec3 eyePosition;
uniform float specularPower;
uniform float shine;
uniform vec3 point1Position;
uniform float point1Range;
uniform vec4 point1Color;
uniform vec3 point1Direction;
uniform float spotAngle;
varying highp vec2 vTexCoord;
varying lowp vec4 vPosition;
varying lowp vec4 vNormal;
varying vec4 vAmbientDiffuse;
varying vec4 vPoint1Diffuse;
vec3 norm = normalize(vNormal).xyz;
vec4 specColor = min(directColor + 0.5, 1.0) * shine;
float radius = cos(radians(spotAngle));
void main()
{
vec3 cameraDirection = normalize(eyePosition - vPosition.xyz);
//calculate specular light for directional light
vec3 halfAngle = normalize(cameraDirection + directDirection);
vec4 specDirect = specColor*pow(max(0.0, dot(norm, halfAngle)),specularPower);
//calculate specular light for point light
halfAngle = normalize(cameraDirection + normalize(point1Position - vPosition.xyz));
vec4 specPoint = specColor*pow(max(0.0, dot(norm, halfAngle)),specularPower);
//calculate strength of point light
float point1Strength = max(0.0, 1.0-length(point1Position-vPosition.xyz) / point1Range);
//calculate if pixel is within light radius
float fracSpot;
float cos = dot(point1Direction, normalize(vPosition.xyz - point1Position));
if (cos>=radius && cos<=1.0) fracSpot = max(0.0, 1.0 - (1.0 - cos) / (1.0 - radius));
else fracSpot = 0.0;
//add it up and apply to texture pixel
vec4 col=texture2D(texture, vTexCoord);
col=col*(vAmbientDiffuse+specDirect+point1Strength * fracSpot * (vPoint1Diffuse+specPoint));
col.a= 1.;
gl_FragColor=col;
}
]]
}
--EVERYTHING BELOW IS UNCHANGED --
function HandleTouches()
modelMatrix(currentModelMatrix) --apply the stored settings
--do rotation for touch
if CurrentTouch.state == MOVING then --only rotate while fingers are moving on the screen
rotate(CurrentTouch.deltaX,0,1,0) --rotate by the x change, on the y axis (see note below)
rotate(CurrentTouch.deltaY,1,0,0) --and by the y change, on the x axis (see note below)
currentModelMatrix = modelMatrix() --store the resulting settings for next time
end
end
function CreateBlock(w,h,d,tex,col,pos,ms) --width,height,depth,texture,colour,position,mesh (if existing already)
pos=pos or vec3(0,0,0)
local x,X,y,Y,z,Z=pos.x-w/2,pos.x+w/2,pos.y-h/2,pos.y+h/2,pos.z-d/2,pos.z+d/2
local v={vec3(x,y,Z),vec3(X,y,Z),vec3(X,Y,Z),vec3(x,Y,Z),vec3(x,y,z),vec3(X,y,z),vec3(X,Y,z),vec3(x,Y,z)}
local vert={v[1],v[2],v[3],v[1],v[3],v[4],v[2],v[6],v[7],v[2],v[7],v[3],v[6],v[5],v[8],v[6],v[8],v[7],
v[5],v[1],v[4],v[5],v[4],v[8],v[4],v[3],v[7],v[4],v[7],v[8],v[5],v[6],v[2],v[5],v[2],v[1]}
local texCoords
if tex then
local t={vec2(0,0),vec2(1,0),vec2(0,1),vec2(1,1)}
texCoords={t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],
t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3]}
end
local n={vec3(0,0,1),vec3(1,0,0),vec3(0,0,-1),vec3(-1,0,0),vec3(0,1,0),vec3(0,-1,0)}
local norm={}
for i=1,6 do for j=1,6 do norm[#norm+1]=n[i] end end
if not ms then ms=mesh() end
if ms.size==0 then
ms.vertices=vert
ms.normals=norm
ms.texture,ms.texCoords=tex,texCoords
else
for i=1,#vert do
table.insert(ms.vertices,vert[i])
table.insert(ms.normals,norm[i])
if tex then table.insert(ms.texCoords,texCoords[i]) end
end
end
ms:setColors(col or color(255))
return ms
end
function PrintExplanation()
output.clear()
print("A spot light is at a specific place in the scene, points in a specific direction, and usually has limited range. \n\nThis demo also shows how to add flicker (vary it with the parameter above).\n\nRotate the cube with your finger to see the effect.")
end
--# Main
-- MultiStep
function setup()
steps = listProjectTabs()
if steps[1]=="Notes" then table.remove(steps,1) end --remove first tab if named Notes
table.remove(steps) --remove the last tab
startStep()
global = "select a step"
end
function showList()
output.clear()
for i=1,#steps do print(i,steps[i]) end
end
function startStep()
resetMatrix()
if cleanup then cleanup() end
lastStep=Step or readProjectData("lastStep") or 1
lastStep=math.min(lastStep,#steps)
saveProjectData("lastStep",lastStep)
parameter.clear()
parameter.integer("Step", 1, #steps, lastStep,showList)
parameter.action("Run", startStep)
loadstring(readProjectTab(steps[Step]))()
if PrintExplanation then PrintExplanation() end
setup()
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment