Created
July 27, 2016 02:42
-
-
Save dermotbalson/77e11615930da32156c64a077ee63441 to your computer and use it in GitHub Desktop.
Step by step lighting
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
--# 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