Last active
December 14, 2015 11:19
-
-
Save sp4cemonkey/5078136 to your computer and use it in GitHub Desktop.
Vintage 3d Tile Based w lighting and bump mapping
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
--# Main | |
--# Main | |
-- Use this function to perform your initial setup | |
displayMode(FULLSCREEN) | |
function setup() | |
stick = Stick() | |
pylon = Wall("Cargo Bot:Crate Red 2") | |
wall = Wall("Cargo Bot:Crate Yellow 2") | |
floor = Floor("Cargo Bot:Crate Green 2") | |
world = World() | |
hero = Hero(3,3) | |
TO_DEG = 180/math.pi | |
worldRenderImage = image(WIDTH/2, HEIGHT/2) | |
filmEffect = Vintage(worldRenderImage, WIDTH, HEIGHT) | |
end | |
-- This function gets called once every frame | |
function draw() | |
--output.clear() | |
print(1/DeltaTime) | |
--print(ElapsedTime) | |
setContext(worldRenderImage) | |
background(0) | |
local TO_DEG = TO_DEG | |
local hero = hero | |
perspective(60) | |
camera(hero.x, 3, 1 + hero.z, hero.x, 0, hero.z, 0, 1, 0) | |
eye = vec3(hero.x, 3, 1 + hero.z) | |
--fixed light | |
--light = vec3(7, 3, 7) | |
--light just above the hero's head | |
light = vec3(hero.x, 0.5, hero.z) | |
-- Draw world | |
pushMatrix() | |
world:draw(eye, light) | |
popMatrix() | |
-- Draw hero | |
translate(hero.x, hero.y, hero.z) | |
rotate(stick.direction*TO_DEG, 0, 1, 0) | |
-- roll animation | |
if stick.active then | |
rotate(-ElapsedTime*2*TO_DEG, 0, 0, 1) | |
end | |
scale(.25, .25, .25) | |
hero:draw(eye, light) | |
-- Restore orthographic projection | |
ortho() | |
viewMatrix(matrix()) | |
resetMatrix() | |
setContext() | |
background(0) | |
filmEffect:draw() | |
-- fade out overlay | |
--sprite("Cargo Bot:Background Fade", WIDTH/2, HEIGHT/2, WIDTH, HEIGHT) | |
if stick.active then | |
local ceil = math.ceil | |
stick:draw() | |
-- move hero based on stick direction | |
local mvtx = math.cos(stick.direction)/50*stick.dist | |
local mvtz = -math.sin(stick.direction)/50*stick.dist | |
hero.x = hero.x + mvtx | |
hero.z = hero.z + mvtz | |
-- convert to table coordinates | |
hero.px = ceil(hero.x - .5) | |
hero.py = ceil(hero.z - .5) | |
-- lazy collision check | |
if world.data[hero.py][hero.px] ~= 0 then | |
hero.x = hero.x - mvtx | |
hero.z = hero.z - mvtz | |
hero.px = ceil(hero.x - .5) | |
hero.py = ceil(hero.z - .5) | |
end | |
end | |
end | |
function touched(touch) | |
stick:touched(touch) | |
end | |
--# World | |
World = class() | |
function World:init() | |
-- define the world | |
self.data = | |
{ | |
{1, 1, 1, 1, 1, 1, 1, 1}, | |
{1, 2, 0, 0, 0, 0, 2, 1}, | |
{1, 0, 0, 0, 0, 0, 0, 1}, | |
{1, 0, 0, 1, 2, 0, 0, 1}, | |
{1, 0, 0, 2, 1, 0, 0, 1}, | |
{1, 0, 0, 0, 0, 0, 0, 1}, | |
{1, 2, 0, 0, 0, 0, 2, 1}, | |
{1, 1, 1, 1, 1, 1, 1, 1} | |
} | |
end | |
function World:draw(eye, light) | |
local floor, wall, pylon = floor, wall, pylon | |
local offSet = 3 | |
local px, py = hero.px, hero.py | |
-- look around the hero to draw whatever is around him | |
translate(px - offSet, 0, py - offSet) | |
for y = py - offSet, py + offSet do | |
for x = px - offSet, px + offSet do | |
if self.data[y] then | |
local val = self.data[y][x] | |
if val == 0 then | |
floor:draw(eye, light) | |
elseif val == 1 then | |
wall:draw(eye, light) | |
elseif val == 2 then | |
pylon:draw(eye, light) | |
end | |
end | |
translate(1,0,0) | |
end | |
translate(-(1 + 2 * offSet), 0, 1) | |
end | |
end | |
--# Hero | |
Hero = class() | |
function Hero:init(x, z) | |
self.x, self.y, self.z = x,0,z | |
self.px, self.py = math.ceil(.5+x), math.ceil(.5+z) | |
self.mdl = Wall("Cargo Bot:Crate Blue 2") | |
end | |
function Hero:draw(eye, light) | |
self.mdl:draw(eye, light) | |
end | |
--# Wall | |
Wall = class() | |
function Wall:init(tex) | |
-- all the unique vertices that make up a cube | |
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 | |
local verts = | |
{ | |
-- 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] | |
} | |
-- all the unique texture positions needed | |
local texvertices = | |
{ | |
vec2(0.02,0.02), | |
vec2(0.98,0.02), | |
vec2(0.02,0.98), | |
vec2(0.98,0.98) | |
} | |
-- apply the texture coordinates to each triangle | |
local texCoords = | |
{ | |
-- Front | |
texvertices[1], texvertices[2], texvertices[4], | |
texvertices[1], texvertices[4], texvertices[3], | |
-- Right | |
texvertices[1], texvertices[2], texvertices[4], | |
texvertices[1], texvertices[4], texvertices[3], | |
-- Back | |
texvertices[1], texvertices[2], texvertices[4], | |
texvertices[1], texvertices[4], texvertices[3], | |
-- Left | |
texvertices[1], texvertices[2], texvertices[4], | |
texvertices[1], texvertices[4], texvertices[3], | |
-- Top | |
texvertices[1], texvertices[2], texvertices[4], | |
texvertices[1], texvertices[4], texvertices[3], | |
-- Bottom | |
texvertices[1], texvertices[2], texvertices[4], | |
texvertices[1], texvertices[4], texvertices[3] | |
} | |
self.model = LitMesh() | |
self.model.litMesh.vertices = verts | |
theTexture = readImage(tex) | |
self.model.litMesh.texCoords = texCoords | |
self.model:deriveVertexNTB() | |
self.model:setTexture(theTexture) | |
local tempText = readImage(tex) | |
theBumpMap = self.model:generateTextureBumpMap(tempText,0.007) | |
self.model:setBumpMap(theBumpMap) | |
end | |
function Wall:draw(eye, light) | |
self.model:setLight(light, 0.2, 1.0, 1.0, color(255)) | |
self.model:setEye(eye) | |
self.model:draw() | |
end | |
--# Floor | |
Floor = class() | |
function Floor:init(tex) | |
-- all the unique vertices that make up a cube | |
local vertices = | |
{ | |
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), -- Right top back | |
vec3(-0.5, -0.5, -0.5), -- Left top back | |
} | |
-- now construct a cube out of the vertices above | |
local verts = | |
{ | |
-- Bottom | |
vertices[3], vertices[4], vertices[2], | |
vertices[3], vertices[2], vertices[1], | |
} | |
-- all the unique texture positions needed | |
local texvertices = | |
{ | |
vec2(0.02,0.02), | |
vec2(0.98,0.02), | |
vec2(0.02,0.98), | |
vec2(0.98,0.98) | |
} | |
-- apply the texture coordinates to each triangle | |
local texCoords = | |
{ | |
-- Bottom | |
texvertices[1], texvertices[2], texvertices[4], | |
texvertices[1], texvertices[4], texvertices[3], | |
} | |
self.model = LitMesh() | |
self.model.litMesh.vertices = verts | |
theTexture = readImage(tex) | |
self.model.litMesh.texCoords = texCoords | |
self.model:deriveVertexNTB() | |
self.model:setTexture(theTexture) | |
local tempText = readImage(tex) | |
theBumpMap = self.model:generateTextureBumpMap(tempText,0.007) | |
self.model:setBumpMap(theBumpMap) | |
end | |
function Floor:draw(eye, light) | |
self.model:setLight(light, 0.2, 1.0, 0.6, color(255)) | |
self.model:setEye(eye) | |
self.model:draw() | |
end | |
--# Stick | |
Stick = class() | |
function Stick:init() | |
self.direction = 0 | |
self.dist = 0 | |
self.active = false | |
self.origin = vec2(150, 150) | |
self.center = self.origin | |
self.pos = self.origin | |
self.stick_bg = readImage("Small World:Dialog Icon") | |
self.stick = readImage("Small World:Bush") | |
-- self.stick_bg = readImage("Space Art:Eclipse") | |
-- self.stick = readImage("Space Art:UFO") | |
end | |
function Stick:draw() | |
sprite(self.stick_bg, self.center.x, self.center.y) | |
sprite(self.stick, self.pos.x, self.pos.y) | |
end | |
function Stick:touched(touch) | |
if touch.state == BEGAN then | |
self.center = vec2(touch.x, touch.y) | |
self.active = true | |
end | |
self.pos = vec2(touch.x, touch.y) | |
self.direction = math.atan2(self.pos.y - self.center.y, self.pos.x - self.center.x) | |
self.dist = math.min(2, self.pos:dist(self.center)/32) | |
if touch.state == ENDED then | |
self.center = self.origin | |
self.pos = self.center | |
self.active = false | |
end | |
end | |
--# LitMesh | |
LitMesh = class() | |
--[[ | |
LitMesh provides a bumpmappable ADS (ambient/diffuse/specular) lighting enhanced class | |
for meshes. | |
usage: | |
setup() | |
myObject = LitMesh() | |
myObject.litMesh.vertices = some table of vertices | |
-- this must be a multiple of 3 representing the triangles | |
-- this is the normal mesh vertices array and can be manipulated as per docs | |
-- eg myObject.litMesh:vertex(i, vec3(x,y,z)) | |
-- myObject.litMesh:setRect(i,x,y,w,h) | |
myObject.litMesh.texCoords = some table of texture coordinates for the vertices | |
-- this is the normal mesh texCoords array and can be | |
manipulated as per the docs | |
-- eg myObject.litMesh:texCoord(i, x, y) | |
-- myObject.litMesh:setRectTex(i,s,t,w,h) | |
--Once all vertexes and texture coordinates have been set call: | |
myObject:deriveVertexNTB() | |
-- you will need to make this call again if you modify any vertexes or texture coordinates | |
--setup lighting | |
--lightPosition is the location of the light in world space | |
--eyePosition is the location of the camera in world space this is the eye vector in your camera() call | |
myObject:setLight(vec3(lightPosition), ambient, diffuse, specular, lightColor) | |
--set the eye position which is the eye vector from your camera statement | |
myObject:setEye(vec3(eyePosition)) | |
--then either color or texture your mesh with one of | |
myObject:setColor(color) | |
myObject:setTexture(texture) | |
--finally if you wish to use bumpmapping set a bumpmap | |
myObject:setBumpMap(bumpMap) | |
--a bumpMap is just an image with normal vectors encoded into the colors, these can be generated in tools like blender or photoshop | |
--Alternately the LitMesh class contains (a poor performing) helper method for generating bumpMaps from your texture: - strength is how aggressive the edge mapping is 0.01 is a good start | |
myObject:generateTextureBumpMap(texture, strength) | |
--this could be used as a shortcut by: | |
bumpTexture = cube:generateTextureBumpMap(cubeTexture, 0.01) | |
myObject:setBumpMap(bumpTexture) | |
draw() | |
--do all your normal drawing activity | |
background(40, 40, 50) | |
camera(eyex, eyey, eyez, lookatx, lookaty, lookatz) | |
--if the camera eye is moving remember to do | |
myObject:setEye(vec3(eyex, eyey, eyez)) | |
perspective() | |
--object drawing | |
pushMatrix() | |
translate(x,y,z) --move centre of your mesh wherever | |
rotate(angle,x,y,z) -- rotate your object about it's centre | |
myObject:draw() | |
popMatrix() | |
]] | |
function LitMesh:init() | |
self.litMesh = mesh() | |
self.litMesh.shader = shader(LitMesh.ADSLighting.vertexShader, LitMesh.ADSLighting.fragmentShader) | |
self.litMesh.shader.useTexture = false | |
self.litMesh.shader.useBumpMap = false | |
end | |
function LitMesh:draw() | |
self.litMesh.shader.mInvModel = modelMatrix():inverse():transpose() | |
self.litMesh:draw() | |
end | |
function LitMesh:setLight(lightPosition, ambient, diffuse, specular, lightColor) | |
self.litMesh.shader.vLightPosition = lightPosition | |
self.litMesh.shader.vAmbientMaterial = ambient | |
self.litMesh.shader.vDiffuseMaterial = diffuse | |
self.litMesh.shader.vSpecularMaterial = specular | |
self.litMesh.shader.lightColor = lightColor | |
end | |
function LitMesh:setEye(eyePosition) | |
self.litMesh.shader.vEyePosition = eyePosition | |
end | |
function LitMesh:setTexture(texture) | |
self.litMesh.shader.useTexture = true | |
self.litMesh.texture = texture | |
end | |
function LitMesh:disableBumpMap() | |
self.litMesh.shader.useBumpMap = false | |
end | |
function LitMesh:setBumpMap(bumpMap) | |
self.litMesh.shader.useBumpMap = true | |
self.litMesh.shader.bumpMap = bumpMap | |
end | |
function LitMesh:setColor(surfaceColor) | |
--assumes the reason you are setting the color is because you don't want to use a texture | |
self.litMesh.texture = nil | |
self.litMesh.shader.useTexture = false | |
self.litMesh:setColors(surfaceColor) | |
end | |
function LitMesh:deriveVertexNTB() | |
--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 | |
local texCoordBuffer = self.litMesh:buffer("texCoord") | |
if texCoordBuffer.length == self.litMesh.size then | |
useTexCoords = true | |
else | |
useTexCoords = false | |
end | |
local normalBuffer = self.litMesh:buffer("normal") | |
normalBuffer:resize(self.litMesh.size) | |
local tangentBuffer = self.litMesh:buffer("tangent") | |
tangentBuffer:resize(self.litMesh.size) | |
--local binormalBuffer = self.litMesh:buffer("binormal") | |
--binormalBuffer:resize(self.litMesh.size) | |
local tangent,binormal, normal | |
for i=1, self.litMesh.size/3 do | |
--calculate the surface vectors | |
local v1 = self.litMesh:vertex(i*3-1) - self.litMesh:vertex(i*3-2) | |
local v2 = self.litMesh:vertex(i*3) - self.litMesh:vertex(i*3-2) | |
--calculate the texture space vectors | |
if useTexCoords then | |
local tuV = vec2(self.litMesh:texCoord(i*3-1).x - self.litMesh:texCoord(i*3-2).x, self.litMesh:texCoord(i*3).x - self.litMesh:texCoord(i*3-2).x) | |
local tvV = vec2(self.litMesh:texCoord(i*3-1).y - self.litMesh:texCoord(i*3-2).y, self.litMesh:texCoord(i*3).y - self.litMesh:texCoord(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 | |
tangentBuffer[j] = tangent | |
end | |
end | |
end | |
function LitMesh:generateTextureBumpMap(source, strength) | |
local t = image(source.width, source.height) | |
for y=2,source.height-1 do | |
for x=2,source.width-1 do | |
r,g,b,a = source:get(x-1,y) | |
xLeft = (0.3*r+0.59*g+0.11*b)*strength | |
r,g,b,a = source:get(x+1,y) | |
xRight = (0.3*r+0.59*g+0.11*b)*strength | |
r,g,b,a = source:get(x,y-1) | |
yUp = (0.3*r+0.59*g+0.11*b)*strength | |
r,g,b,a = source:get(x,y+1) | |
yDown = (0.3*r+0.59*g+0.11*b)*strength | |
xDelta = ((xLeft-xRight)+1)*127.5 | |
yDelta = ((yUp - yDown)+1)*127.5 | |
t:set(x,y,color(xDelta,yDelta,255,255)) | |
end | |
end | |
for y=1,source.height do | |
t:set(1,y,color(0,0,255,255)) | |
t:set(source.width,y,color(0,0,255,255)) | |
end | |
for x=1,source.width do | |
t:set(x,1,color(0,0,255,255)) | |
t:set(x,source.height,color(0,0,255,255)) | |
end | |
return t | |
end | |
LitMesh.ADSLighting = { | |
--shader() | |
vertexShader = [[ | |
uniform lowp mat4 modelViewProjection; | |
uniform lowp mat4 mInvModel; | |
uniform lowp vec3 vEyePosition; | |
uniform lowp vec3 vLightPosition; | |
attribute vec4 position; | |
attribute vec4 color; | |
attribute vec2 texCoord; | |
attribute vec3 normal; | |
attribute vec3 tangent; | |
varying lowp vec2 vTexCoord; | |
varying lowp vec3 lightDirection; | |
varying lowp vec3 eyeDirection; | |
void main() | |
{ | |
lowp mat3 tangentMatrix = mat3(tangent, cross(normal, tangent), normal); | |
//convert the positions to object space, then get direction, then convert to tangent space | |
lightDirection = (((vec4(vLightPosition,1) * mInvModel) - position).xyz | |
* tangentMatrix).xyz; | |
eyeDirection = (((vec4(vEyePosition,1) * mInvModel) - position).xyz | |
* tangentMatrix).xyz; | |
vTexCoord = texCoord; | |
gl_Position = modelViewProjection * position; | |
} | |
]], | |
fragmentShader = [[ | |
precision lowp float; | |
uniform lowp sampler2D texture; | |
uniform lowp sampler2D bumpMap; | |
varying lowp vec2 vTexCoord; | |
varying lowp vec3 lightDirection; | |
varying lowp vec3 eyeDirection; | |
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; | |
vec3 curNormal = normalize(texture2D( bumpMap, vTexCoord ).xyz*c_two-vec3(c_one,c_one,c_one)); | |
lowp vec4 curCol = texture2D( texture, vTexCoord); | |
vec3 vLightDirection = normalize(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; | |
//Set the output color to the texture color | |
gl_FragColor = vAmbientColor + vDiffuseColor + vSpecularColor; | |
} | |
]] | |
} | |
--# Vintage | |
Vintage = class() | |
function Vintage:init(texture, tw, th) | |
self.mesh = mesh() | |
self.mesh.texture = texture | |
self.mesh.shader = shader(Vintage.FilmShader.FilmVertex, Vintage.FilmShader.FilmFragment) | |
--local tw,th = texture.width, texture.height | |
self.mesh:addRect(tw/2, th/2, tw, th) | |
-- configuration of the filter | |
self.mesh.shader.SepiaValue = 0.6 | |
self.mesh.shader.NoiseValue = 0.2 | |
self.mesh.shader.ScratchValue = 0.5 | |
self.mesh.shader.InnerVignetting = 0.8 | |
self.mesh.shader.OuterVignetting = 0.9 | |
self.mesh.shader.RandomValue = math.random() | |
self.mesh.shader.TimeLapse = math.random() | |
self.frameRate = 10 | |
self.timeSinceLastDraw = 0 | |
end | |
function Vintage:draw() | |
self.timeSinceLastDraw = self.timeSinceLastDraw + DeltaTime | |
--even though redraw is as fast as possible, the noise etc will only shift when we do this update... | |
if self.timeSinceLastDraw > 1/self.frameRate then | |
self.mesh.shader.RandomValue = math.random() | |
self.timeSinceLastDraw = 0 | |
if math.random() > 0.9 then | |
self.mesh.shader.TimeLapse = math.random() | |
end | |
end | |
-- Draw the mesh | |
self.mesh:draw() | |
end | |
Vintage.FilmShader = { | |
FilmVertex = [[ | |
// | |
// A basic vertex shader | |
// | |
//This is the current model * view * projection matrix | |
// Codea sets it automatically | |
uniform mat4 modelViewProjection; | |
//uniform bool isCamera; | |
//This is the current mesh vertex position, color and tex coord | |
// Set automatically | |
attribute vec4 position; | |
attribute vec4 color; | |
attribute vec2 texCoord; | |
//This is an output variable that will be passed to the fragment shader | |
varying highp vec2 vTexCoord; | |
void main() | |
{ | |
//Pass the mesh color to the fragment shader | |
vTexCoord = texCoord; | |
//Camera isn't oriented correctly, so this code would fix it | |
//if (isCamera) { | |
// vTexCoord.y = 1.0 - vTexCoord.y; | |
//} | |
//Multiply the vertex position by our combined transform | |
gl_Position = modelViewProjection * position; | |
} | |
]], | |
FilmFragment = [[ | |
precision highp float; | |
/// Uniform variables. | |
uniform vec4 Colour; | |
uniform sampler2D texture; | |
uniform float SepiaValue; | |
uniform float NoiseValue; | |
uniform float ScratchValue; | |
uniform float InnerVignetting; | |
uniform float OuterVignetting; | |
uniform float RandomValue; | |
uniform float TimeLapse; | |
/// Varying variables. | |
varying vec2 vTexCoord; | |
/// Computes the overlay between the source and destination colours. | |
vec3 Overlay (vec3 src, vec3 dst) { | |
// if (dst <= Ω) then: 2 * src * dst | |
// if (dst > Ω) then: 1 - 2 * (1 - dst) * (1 - src) | |
return vec3((dst.x <= 0.5) ? (2.0 * src.x * dst.x) : (1.0 - 2.0 * (1.0 - dst.x) * (1.0 - src.x)), (dst.y <= 0.5) ? (2.0 * src.y * dst.y) : (1.0 - 2.0 * (1.0 - dst.y) * (1.0 - src.y)), (dst.z <= 0.5) ? (2.0 * src.z * dst.z) : (1.0 - 2.0 * (1.0 - dst.z) * (1.0 - src.z))); | |
} | |
/// 2D Noise by Ian McEwan, Ashima Arts. | |
vec3 mod289(vec3 x) { | |
return x - floor(x * (1.0 / 289.0)) * 289.0; | |
} | |
vec2 mod289(vec2 x) { | |
return x - floor(x * (1.0 / 289.0)) * 289.0; | |
} | |
vec3 permute(vec3 x) { | |
return mod289(((x*34.0)+1.0)*x); | |
} | |
float snoise (vec2 v) { | |
const vec4 C = vec4( | |
0.211324865405187, // (3.0-sqrt(3.0))/6.0 | |
0.366025403784439, // 0.5*(sqrt(3.0)-1.0) | |
-0.577350269189626, // -1.0 + 2.0 * C.x | |
0.024390243902439); // 1.0 / 41.0 | |
// First corner | |
vec2 i = floor(v + dot(v, C.yy) ); | |
vec2 x0 = v - i + dot(i, C.xx); | |
// Other corners | |
vec2 i1; | |
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); | |
vec4 x12 = x0.xyxy + C.xxzz; x12.xy -= i1; | |
// Permutations | |
i = mod289(i); | |
// Avoid truncation effects in permutation | |
vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) + i.x + vec3(0.0, i1.x, 1.0 )); | |
vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0); | |
m = m*m ; | |
m = m*m ; | |
// Gradients: 41 points uniformly over a line, mapped onto a diamond. | |
// The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287) | |
vec3 x = 2.0 * fract(p * C.www) - 1.0; | |
vec3 h = abs(x) - 0.5; | |
vec3 ox = floor(x + 0.5); | |
vec3 a0 = x - ox; | |
// Normalise gradients implicitly by scaling m | |
// Approximation of: m *= inversesqrt( a0*a0 + h*h ); | |
m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); | |
// Compute final noise value at P | |
vec3 g; | |
g.x = a0.x * x0.x + h.x * x0.y; | |
g.yz = a0.yz * x12.xz + h.yz * x12.yw; | |
return 130.0 * dot(m, g); | |
} | |
/// Fragment shader entry. | |
void main () { | |
// Sepia RGB value | |
vec3 sepia = vec3(112.0 / 255.0, 66.0 / 255.0, 20.0 / 255.0); | |
// Step 1: Convert to grayscale | |
vec3 colour = texture2D(texture, vTexCoord).xyz; | |
float gray = (colour.x + colour.y + colour.z) / 3.0; | |
vec3 grayscale = vec3(gray); | |
// Step 2: Appy sepia overlay | |
vec3 finalColour = Overlay(sepia, grayscale); | |
// Step 3: Lerp final sepia colour | |
finalColour = grayscale + SepiaValue * (finalColour - grayscale); | |
// Step 4: Add noise | |
float noise = snoise(vTexCoord * vec2(1024.0 + RandomValue * 512.0, 1024.0 + RandomValue * 512.0)) * 0.5; | |
finalColour += noise * NoiseValue; | |
// Optionally add noise as an overlay, simulating ISO on the camera | |
//vec3 noiseOverlay = Overlay(finalColour, vec3(noise)); | |
//finalColour = finalColour + NoiseValue * (finalColour - noiseOverlay); | |
// Step 5: Apply scratches | |
if ( RandomValue < ScratchValue ) { | |
// Pick a random spot to show scratches | |
float dist = 1.0 / ScratchValue; | |
float d = distance(vTexCoord, vec2(RandomValue * dist, RandomValue * dist)); | |
if ( d < 0.4 ) { | |
// Generate the scratch | |
float xPeriod = 8.0; | |
float yPeriod = 1.0; | |
float pi = 3.141592; | |
float phase = TimeLapse; | |
float turbulence = snoise(vTexCoord * 2.5); | |
float vScratch = 0.5 + (sin(((vTexCoord.x * xPeriod + vTexCoord.y * yPeriod + turbulence)) * pi + phase) * 0.5); | |
vScratch = clamp((vScratch * 10000.0) + 0.35, 0.0, 1.0); | |
finalColour.xyz *= vScratch; | |
} | |
} | |
// Step 6: Apply vignetting | |
// Max distance from centre to corner is ~0.7. Scale that to 1.0. | |
float d = distance(vec2(0.5, 0.5), vTexCoord) * 1.414213; | |
float vignetting = clamp((OuterVignetting - d) / (OuterVignetting - InnerVignetting), 0.0, 1.0); | |
finalColour.xyz *= vignetting; | |
// Apply colour | |
gl_FragColor.xyz = finalColour; | |
gl_FragColor.w = 1.0; | |
} | |
]] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment