diff --git a/examples/short.lua b/examples/short.lua index 76edf92..f3af352 100644 --- a/examples/short.lua +++ b/examples/short.lua @@ -1,5 +1,6 @@ -- Example: Short Example local LightWorld = require "lib" +local normal_map = require "lib/normal_map" function love.load() testShader = 0 diff --git a/examples/test.lua b/examples/test.lua new file mode 100644 index 0000000..29043cc --- /dev/null +++ b/examples/test.lua @@ -0,0 +1,59 @@ +-- Example: normal map generation testing +local LightWorld = require "lib" +local util = require "lib/util" +local normal_map = require "lib/normal_map" + +function love.load() + z = 1 + lightWorld = LightWorld({ + drawBackground = drawBackground, + drawForeground = drawForeground, + ambient = {55,55,55}, + refractionStrength = 32.0, + reflectionVisibility = 0.75, + }) + + -- create light + lightMouse = lightWorld:newLight(0, 0, 255, 127, 63, 300) + lightMouse:setGlowStrength(0.3) + + radius = 50 + circle_canvas = love.graphics.newCanvas(radius*2, radius*2) + util.drawto(circle_canvas, 0, 0, 1, function() + love.graphics.circle('fill', radius, radius, radius) + end) + circle_image = love.graphics.newImage(circle_canvas:getImageData()) + + local t = lightWorld:newImage(circle_image, 150, 150) + t:setNormalMap(normal_map.generateFlat(circle_image, "top")) + t:setShadowType('circle', 50) +end + +function love.mousepressed(x, y, c) + if c == "wu" then + z = z + 1 + elseif c == "wd" then + z = z - 1 + end +end + +function love.update(dt) + love.window.setTitle("Light vs. Shadow Engine (FPS:" .. love.timer.getFPS() .. ")") + lightMouse:setPosition(love.mouse.getX(), love.mouse.getY(), z) +end + +function love.draw() + lightWorld:draw() +end + +function drawBackground(l,t,w,h) + love.graphics.setColor(255, 255, 255) + love.graphics.rectangle("fill", l, t, w, h) +end + +function drawForeground(l,t,w,h) + love.graphics.setColor(0, 255, 0) + love.graphics.circle('fill', 150, 150, 50) +end + + diff --git a/lib/init.lua b/lib/init.lua index 3d453ca..443a987 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -101,10 +101,10 @@ function light_world:draw(l,t,s) local w, h = love.graphics.getWidth(), love.graphics.getHeight() util.drawto(self.render_buffer, l, t, s, function() self.drawBackground( l,t,w,h,s) - self:drawShadow( l,t,w,h,s) + --self:drawShadow( l,t,w,h,s) self.drawForeground( l,t,w,h,s) self:drawMaterial( l,t,w,h,s) - self:drawShine( l,t,w,h,s) + --self:drawShine( l,t,w,h,s) self:drawNormalShading( l,t,w,h,s) self:drawGlow( l,t,w,h,s) self:drawRefraction( l,t,w,h,s) diff --git a/lib/light.lua b/lib/light.lua index 55f1b37..cbae775 100644 --- a/lib/light.lua +++ b/lib/light.lua @@ -7,6 +7,7 @@ local light = class() light.shineShader = love.graphics.newShader(_PACKAGE.."/shaders/shine.glsl") light.normalShader = love.graphics.newShader(_PACKAGE.."/shaders/normal.glsl") +light.shadowShader = love.graphics.newShader(_PACKAGE.."/shaders/shadow.glsl") function light:init(x, y, r, g, b, range) self.direction = 0 @@ -32,6 +33,7 @@ function light:refresh(w, h) self.shadow = love.graphics.newCanvas(w, h) self.shine = love.graphics.newCanvas(w, h) self.normalShader:send('screenResolution', {w, h}) + self.shadowShader:send('screenResolution', {w, h}) end -- set position @@ -200,14 +202,15 @@ end function light:drawNormalShading(l,t,w,h,s, normalMap, canvas) if self.visible and self:inRange(l,t,w,h,s) then - self.normalShader:send('lightColor', {self.red / 255.0, self.green / 255.0, self.blue / 255.0}) - self.normalShader:send("lightPosition", {(self.x + l/s) * s, (h/s - (self.y + t/s)) * s, (self.z * 10) / 255.0}) - self.normalShader:send('lightRange',{self.range}) - self.normalShader:send("lightSmooth", self.smooth) - self.normalShader:send("lightAngle", math.pi - self.angle / 2.0) - self.normalShader:send("lightDirection", self.direction) - self.normalShader:send("invert_normal", self.normalInvert == true) - util.drawCanvasToCanvas(normalMap, canvas, {shader = self.normalShader}) + self.shadowShader:send('lightColor', {self.red / 255.0, self.green / 255.0, self.blue / 255.0}) + self.shadowShader:send("lightPosition", {(self.x + l/s) * s, (h/s - (self.y + t/s)) * s, (self.z * 10) / 255.0}) + self.shadowShader:send('lightRange',{self.range}) + self.shadowShader:send("lightSmooth", self.smooth) + self.shadowShader:send("lightGlow", {1.0 - self.glowSize, self.glowStrength}) + self.shadowShader:send("lightAngle", math.pi - self.angle / 2.0) + self.shadowShader:send("lightDirection", self.direction) + self.shadowShader:send("invert_normal", self.normalInvert == true) + util.drawCanvasToCanvas(normalMap, canvas, {shader = self.shadowShader}) end end diff --git a/lib/normal_map.lua b/lib/normal_map.lua index 04e5a04..c1b318d 100644 --- a/lib/normal_map.lua +++ b/lib/normal_map.lua @@ -64,12 +64,10 @@ function normal_map.generateFlat(img, mode) color = {223, 0, 127} end - for i = 0, self.imgHeight - 1 do - for k = 0, self.imgWidth - 1 do + for i = 0, img:getHeight() - 1 do + for k = 0, img:getWidth() - 1 do local r, g, b, a = imgData:getPixel(k, i) - if a > 0 then - imgNormalData:setPixel(k, i, color[1], color[2], color[3], 255) - end + imgNormalData:setPixel(k, i, color[1], color[2], color[3], a) end end diff --git a/lib/shaders/shadow.glsl b/lib/shaders/shadow.glsl new file mode 100644 index 0000000..557681b --- /dev/null +++ b/lib/shaders/shadow.glsl @@ -0,0 +1,90 @@ +#define PI 3.1415926535897932384626433832795 + +extern vec2 screenResolution; +extern vec3 lightPosition; +extern vec3 lightColor; +extern float lightRange; +extern float lightSmooth; +extern vec2 lightGlow; +extern float lightDirection; +extern float lightAngle; +extern bool invert_normal; + +float getHeightAt(Image texture, vec2 texture_coords) { + vec4 pixel = Texel(texture, texture_coords); + if(pixel.a > 0.0){ + return 0.0; + } else { + return pixel.g; + } +} + +bool is_in_shadow(Image texture, vec2 texture_coords, vec3 lightPosition, vec2 pixel_coords) { + vec3 coords = vec3(pixel_coords, 0.0); + vec3 lightVec = normalize(lightPosition - coords); + + float startHeight = getHeightAt(texture, texture_coords); + vec2 tx; + float currentHeight; + + for(int i = 0; i < 100; ++i) { + tx = texture_coords + lightVec.xy; + currentHeight = getHeightAt(texture, tx); + if(startHeight < currentHeight){ + return true; + } + } + return false; +} + +vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) { + vec4 pixelColor = Texel(texture, texture_coords); + + //if the light is a slice and the pixel is not inside + if(lightAngle > 0.0) { + float angle2 = atan(lightPosition.x - pixel_coords.x, pixel_coords.y - lightPosition.y) + PI; + if(lightDirection - lightAngle > 0 && lightDirection + lightAngle < PI * 2) { + if(angle2 < mod(lightDirection + lightAngle, PI * 2) && angle2 > mod(lightDirection - lightAngle, PI * 2)) { + return vec4(0.0, 0.0, 0.0, 1.0); + } + } else { + if(angle2 < mod(lightDirection + lightAngle, PI * 2) || angle2 > mod(lightDirection - lightAngle, PI * 2)) { + return vec4(0.0, 0.0, 0.0, 1.0); + } + } + } + + vec3 normal; + //if on the normal map ie there is normal map data + if(pixelColor.a > 0.0) { + if(invert_normal == true) { + normal = normalize(vec3(pixelColor.r, 1 - pixelColor.g, pixelColor.b) * 2.0 - 1.0); + } else { + normal = normalize(pixelColor.rgb * 2.0 - 1.0); + } + } else { + normal = vec3(0.0, 0.0, 1.0); + } + float dist = distance(lightPosition, vec3(pixel_coords, normal.b)); + if(dist < lightRange) { + float att = clamp((1.0 - dist / lightRange) / lightSmooth, 0.0, 1.0); + if(pixelColor.a == 0.0) { + vec4 val = pixelColor; + val.a = 1.0; + if (lightGlow.x < 1.0 && lightGlow.y > 0.0) { + val.rgb = clamp(lightColor * pow(att, lightSmooth) + pow(smoothstep(lightGlow.x, 1.0, att), lightSmooth) * lightGlow.y, 0.0, 1.0); + } else { + val.rgb = lightColor * pow(att, lightSmooth); + } + return val; + } else { + vec3 dir = vec3((lightPosition.xy - pixel_coords.xy) / screenResolution.xy, lightPosition.z); + dir.x *= screenResolution.x / screenResolution.y; + vec3 diff = lightColor * max(dot(normalize(normal), normalize(dir)), 0.0); + return vec4(diff * att, 1.0); + } + } else { + return vec4(0.0, 0.0, 0.0, 1.0); + } +} +