So if you liked my previous balls physics app… here’s a new version using shaders to make them shiny.
Don’t let youtubes preview still put you off, it’s much shinier than that…
The most interesting thing in here for me, is I got it working with fake bump mapping. Ie, the mesh is actually a single rectangle, but I use a texture which encodes the normals to drive lighting which means I can fake true 3d. In real modeling terms, it’s a way to increase the “resolution” without having too many triangles in your mesh.
The next thing to try and learn from here is whether I can bind multiple textures to a mesh so I can use one as a texture and the other for bump mapping…
function setup()
displayMode(FULLSCREEN)
--myFPSReporter = FPSReporter(4)
parameter.number("ambient", 0, 1, 0.1)
parameter.number("diffuse", 0, 1, 1.0)
parameter.number("specular", 0, 1, 1)
parameter.color("lightColor", color(255,255,255,255))
--defaults are 10,8 so not sure why the following is necessary
--physics.iterations(40,40)
-- physics.gravity(0,16)
noSmooth()
balls = {}
touches = {}
base = physics.body(EDGE, vec2(100,0), vec2(WIDTH-100,0))
nextball = 0
textureSize = 150
ballTexture = genCircularNormals(textureSize)
ballMesh = mesh()
ballMesh:addRect(0,0,1,1,0)
ballMesh.texture = ballTexture
ballMesh:setRectTex(1,0,0,1,1)
ballMesh.shader = shader(bumpLighting.vertexShader, bumpLighting.fragmentShader)
end
function genCircularNormals(w)
local t = image(w, w)
r = w/2
for x=1,w do
for y=1,w do
d = math.sqrt((r-x)^2+((r-y)^2))
if d <= r then
angle = math.acos(d/r)
z = r*math.sin(angle)
v = (vec3(x-r,y-r,z):normalize()+vec3(1,1,1))/2*255
t:set(x, y, v.x, v.y, v.z, 255)
end
end
end
return t
end
function touched(touch)
if touch.state == ENDED then
touches[touch.id] = nil
else
--if touches[touch.id] == nil then
touches[touch.id] = touch
--end
end
end
function touchActions()
for k,v in pairs(touches) do
if CurrentTouch.state == ENDED then
--if there are no current touches then we kill all current touches to avoid bugged ball producers
touches[k] = nil
else
--add a new ball at the touch location
size = math.random(1,20)
tspot = physics.body(CIRCLE, size)
tspot.position = vec2(v.x+math.random(-1,1), v.y+math.random(-1,1))
tspot.restitution = 0.95
balls[nextball] = { tspot = tspot, size = size * 2, r = math.random(30,255), g = math.random(30,255), b = math.random(30,255) }
nextball = nextball + 1
end
end
end
function draw()
background(0, 0, 0, 255)
--myFPSReporter:draw(3)
strokeWidth(0)
ballMesh.shader.vAmbientMaterial = ambient
ballMesh.shader.vDiffuseMaterial = diffuse
ballMesh.shader.vSpecularMaterial = specular
ballMesh.shader.lightColor = lightColor
ballMesh.shader.mView = viewMatrix()
ballMesh.shader.mProjection = projectionMatrix()
ballMesh.shader.mModel = modelMatrix()
ballMesh.shader.vEyePosition = vec4(0,0,500,0)
ballMesh.shader.vLightPosition = vec4(500,-500,500,0)
for k,v in pairs(balls) do
if v.tspot.x < -20 or v.tspot.x > WIDTH + 20 or v.tspot.y < -20 then
balls[k].tspot:destroy()
balls[k] = nil
else
resetMatrix()
translate(v.tspot.x,v.tspot.y)
scale(v.size)
--[[
ballMesh.shader.mModel = modelMatrix()
ballMesh.shader.vEyePosition = vec4(v.tspot.x,v.tspot.y,250,0)
ballMesh.shader.vLightPosition = vec4(v.tspot.x,v.tspot.y,250,0)
]]
ballMesh:setColors(color(v.r,v.g,v.b,255))
ballMesh:draw()
--fill(v.r, v.g, v.b, 255)
--ellipse(v.tspot.x, v.tspot.y, v.size)
end
end
touchActions()
end
bumpLighting = {
vertexShader = [[
//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;
//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
varying highp vec4 vPosition;
void main()
{
//Pass the mesh color to the fragment shader
vColor = color;
vTexCoord = vec2(texCoord.x, 1.0 - texCoord.y);
vPosition = position;
//Multiply the vertex position by our combined transform
gl_Position = modelViewProjection * position;
}
]],
fragmentShader = [[
//
// A basic fragment shader
//
//Default precision qualifier
precision highp float;
//This represents the current texture on the mesh
uniform lowp sampler2D texture;
//The interpolated vertex color for this fragment
varying lowp vec4 vColor;
//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;
varying highp vec4 vPosition;
//****new attributes
// Our Model, View and Projection matrices
// we need them to transform the incoming vertices and for lighting
// calculation
uniform mat4 mModel;
uniform mat4 mView;
uniform mat4 mProjection;
// Position of the "Camera" and the Light
// in Model space
uniform vec4 vEyePosition;
uniform vec4 vLightPosition;
// The Colors of the material
uniform float vAmbientMaterial;
uniform float vDiffuseMaterial;
uniform float vSpecularMaterial;
uniform vec4 lightColor;
// Returns the specular component of the color
vec4 GetSpecularColor(vec3 vVertexNormal, vec4 vVertexPosition)
{
// Transform the Vertex and corresponding Normal into Model space
vec4 vTransformedNormal = mModel * vec4( vVertexNormal, 1 );
vec4 vTransformedVertex = mModel * vVertexPosition;
// Get the directional vector to the light and to the camera
// originating from the vertex position
vec4 vLightDirection = normalize( vLightPosition - vTransformedVertex );
vec4 vCameraDirection = normalize( vEyePosition - vTransformedVertex );
// Calculate the reflection vector between the incoming light and the
// normal (incoming angle = outgoing angle)
// We have to use the invert of the light direction because "reflect"
// expects the incident vector as its first parameter
vec4 vReflection = reflect( -vLightDirection, vTransformedNormal );
// Calculate specular component
// Based on the dot product between the reflection vector and the camera
// direction.
//
// hint: The Dot Product corresponds to the angle between the two vectors
// hint: if the angle is out of range (0 ... 180 degrees) we use 0.0
float spec = pow( max( 0.0, dot( vCameraDirection, vReflection )), 32.0 );
return vec4( lightColor.r * spec, lightColor.g * spec, lightColor.b * spec, 1.0 ) * vSpecularMaterial;
}
// Ambient color component of vertex
vec4 GetAmbientColor()
{
vec4 vAmbientColor;
vAmbientColor.xyz = vColor.rgb * lightColor.rgb * vAmbientMaterial;
vAmbientColor.a = 1.0;
return vAmbientColor;
}
// Diffuse Color component of vertex
vec4 GetDiffuseColor(vec3 vVertexNormal)
{
// Transform the normal from Object to Model space
// we also normalize the vector just to be sure ...
vec4 vTransformedNormal = normalize( mModel * vec4( vVertexNormal, 1 ));
// Get direction of light in Model space
vec4 vLightDirection = normalize( vLightPosition - vTransformedNormal );
// Calculate Diffuse intensity
float fDiffuseIntensity = max( 0.0, dot( vTransformedNormal, vLightDirection ));
// Calculate resulting Color
vec4 vDiffuseColor;
vDiffuseColor.xyz = vColor.rgb * lightColor.rgb * fDiffuseIntensity * vDiffuseMaterial;
vDiffuseColor.a = 1.0;
return vDiffuseColor;
}
void main()
{
//Sample the texture at the interpolated coordinate
lowp vec4 col = texture2D( texture, vTexCoord );
if (col.x == 0.0 && col.y == 0.0 && col.z == 0.0) discard;
vec3 vNormal = normalize(vec3(col.x, col.y, col.z)*2.0-vec3(1.0,1.0,1.0));
vec4 ambientColor = GetAmbientColor();
vec4 diffuseColor = GetDiffuseColor(vNormal);
vec4 specularColor = GetSpecularColor(vNormal, vPosition);
// Combine into final color
vec4 outColor;
outColor = ambientColor + diffuseColor + specularColor;
//Set the output color to the texture color
gl_FragColor = outColor;
}
]]}