blend mode (?) issue / canvas rendering problem
Posted: Wed Dec 13, 2023 2:59 pm
a project im working on is extremely graphics involved so ive learned a lot about the love api, but just now ive encountered a problem I feel like shouldnt be a problem; I have a scene going to be on the window, made up of many small aspects, some polygons, some images, some canvases. but before it is drawn, a series of shaders are applied so (I think) everything becomes part of one, screen sized canvas draw to the window, but afterwards I want to apply another canvas that is black and white (all pixels are : (n, n, n, 1)) to add shadows, so id assume I could use setBlendMode('multiply', 'premultiplied') and draw the canvas but when I do it turns the screen black.
window without blendmode or shadow canvas drawn: (its just a scene from the game)
window with the shadows drawn but blendMode is still 'alpha' :
window with the shadow draw and blandMode set to 'multiplied, 'premultiplied' : (just a black screen)
the shadow is claculated with polygons and is drawn to a canvas, the canvas is then drawn to another canvas (combine all lights to one screen), then this canvas is draw atop the already rendered window after everything has been draw.
love.draw function() looks like the following:
simplified version :
real version :
and the geometryShadows:drawLights() function looks like this :
simplified version :
real version :
this is the second itteration of the shadows script, the first one worked properly and here was the order of drawing for that one:
no, rendering it before stats:draw() doesnt change the outcome, I saw that difference between the scripts.
and I would like this to be an alteration of code (hopefully I just made a mistake somewhere in the drawing cast), making a new shader and redrawing the current screen and giving the shadow canvas as an extern Image may be a solution but I'm in testing phase for the feature so altering the betterRender:drawAll() code to have this would be more effort than its worth and would make it less efficient at larger drawing scales with many lights since it'd have to be before the ui is drawn so it would be applied to more than one canvas (just the way its worked out so far sadly)
here is code for individual lights making canvases before they are drawn to finalCanvas that is then to the screen:
simplified version :
real version :
window without blendmode or shadow canvas drawn: (its just a scene from the game)
window with the shadows drawn but blendMode is still 'alpha' :
window with the shadow draw and blandMode set to 'multiplied, 'premultiplied' : (just a black screen)
the shadow is claculated with polygons and is drawn to a canvas, the canvas is then drawn to another canvas (combine all lights to one screen), then this canvas is draw atop the already rendered window after everything has been draw.
love.draw function() looks like the following:
simplified version :
Code: Select all
function love.draw()
walls:sendDrawToRend()
entities:sendDrawToRend()
player:sendDrawToRend()
UI:sendDrawToRend()
-- all viewable items are sent to one script to be drawn with shaders
betterRender:drawAll()
--combine all drawables into a canvases, apply shaders, and draw to screen
stats:draw()
--draw debug info (cpu time, gpu time, texture memory on screen)
geometryShadows:drawLights()
--draw shadows (problem)
end
Code: Select all
function love.draw()
local startTime = love.timer.getTime()
Walls:draw()
BetterEntity:draw()
Player:draw()
UserInterface:draw()
Camera:draw()
Blood:update(dt)
Gore:draw()
AnimatedNonentity:draw()
ps3d:draw('foreGround')
if paused then
Settings:draw()
end
BetterRender:draw()
local poses = Camera:getPos()
for i = 1, #splines do
splines[i]:draw(poses.x, poses.y, poses.zoom)
end
if debugmode then
love.graphics.print('cpu run time. ms : ' .. math.floor((cpuTime) * 1000000) / 1000, 30, 0)
love.graphics.print('gpu run time. ms : ' .. math.floor((gpuTime) * 1000000) / 1000, 30, 20)
love.graphics.print('total run time. ms : ' .. math.floor((totalTime) * 1000000) / 1000, 30, 40)
love.graphics.print(string.format("%.2f MB texture memory", love.graphics.getStats().texturememory / 1000 / 1000), 30, 60)
love.graphics.print('FPS : ' .. love.timer.getFPS(), CommonVars.screenWidth - 250, 0)
for i = 1, #totalTimeGraph - 1 do
love.graphics.line(350 + i, 60 - totalTimeGraph[i], 350 + (i + 1), 60 - totalTimeGraph[i + 1])
end
love.graphics.print('screen refresh : ' .. screenItems.refreshrate, CommonVars.screenWidth - 250, 20)
end
love.graphics.setBlendMode("alpha")
BetterGeometryShadow:drawLights()
-- draw lights and shadows
gpuTime = love.timer.getTime() - startTime
totalTime = love.timer.getTime() - totalStart
table.insert(totalTimeGraph, totalTime * 1000000 / 1000)
table.remove(totalTimeGraph, 1)
local curTime = love.timer.getTime()
if nextTime <= curTime then
nextTime = curTime
return
end
love.timer.sleep(nextTime - curTime)
end
simplified version :
Code: Select all
function geometryShadows:drawLights()
love.graphics.setShader(inverseSquareRootShader)
for i = 1, #allLights do
allLights[i]:drawAsLight() -- each light calculates shadows and draws them to its canvas
end
love.graphics.setCanvas(finalCanvas)
love.graphics.setShader()
for i = 1, #allLights do
love.graphics.draw(allLights[i].canvas) -- draw lights canvases to one canvas to draw on screen
end
love.graphics.setCanvas()
love.graphics.setBlendMode("multiply", "premultiplied") -- blend mode
love.graphics.draw(finalCanv) -- draw shadows
end
Code: Select all
function BetterGeometryShadow:drawLights()
love.graphics.setShader(BetterShaders:getShader("betterGeometryShadowInvSqroot"))
love.graphics.setBlendMode('replace')
for i = 1, #allLights do
allLights[i]:drawAsLight()
end
love.graphics.setCanvas(finalCanv)
love.graphics.clear()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setBlendMode('alpha')
love.graphics.setShader()
local camPos = Camera:getPos()
for i = 1, #allLights do
love.graphics.draw(allLights[i].canv, -allLights[i].power + (allLights[i].x - camPos.x + CommonVars.screenWidth / camPos.zoom - CommonVars.screenWidth) * camPos.zoom, -allLights[i].power + (allLights[i].y - camPos.y + CommonVars.screenHeight / camPos.zoom - CommonVars.screenHeight) * camPos.zoom, 0, camPos.zoom, camPos.zoom)
end
love.graphics.setCanvas()
love.graphics.setBlendMode("multiply", "premultiplied")
love.graphics.draw(finalCanv)
end
Code: Select all
function love.draw()
walls:sendDrawToRend()
entities:sendDrawToRend()
player:sendDrawToRend()
UI:sendDrawToRend()
-- all viewable items are sent to one script to be drawn with shaders
betterRender:drawAll()
--combine all drawables into a canvases, apply shaders, and draw to screen
local shadows = GeometryShadow:drawShadows() -- returns the fincal canvas
love.graphics.setShader(BetterShaders:getShader("geometryShadow")
-- set shader to a root function
-- lights were adding 0.1 to pixels they touched and this would make the lights additive but non-linear
-- shader turned lights from a / shaped graph 0-1 into a r shape graph 0-1
love.graphics.setBlendMode("multiply", "premultiplied") -- set blend mode
love.graphics.setCanvas() -- draw to window
love.graphics.draw(shadows) -- draw shadow canvas to canvas
stats:draw()
end
and I would like this to be an alteration of code (hopefully I just made a mistake somewhere in the drawing cast), making a new shader and redrawing the current screen and giving the shadow canvas as an extern Image may be a solution but I'm in testing phase for the feature so altering the betterRender:drawAll() code to have this would be more effort than its worth and would make it less efficient at larger drawing scales with many lights since it'd have to be before the ui is drawn so it would be applied to more than one canvas (just the way its worked out so far sadly)
here is code for individual lights making canvases before they are drawn to finalCanvas that is then to the screen:
simplified version :
Code: Select all
function geometryShadow:drawAsLight()
--shader is already inverse square root shader (was set before function is run)
love.graphics.setCanvas(self.canv)
love.graphics.clear(0, 0, 0, 1)
love.graphics.polygon('fill', self.lightVertices)
end
Code: Select all
function BetterGeometryShadow:drawAsLight()
-- shader was set to the inverse square root shader before this function was called
love.graphics.push()
love.graphics.setCanvas(self.canv) -- draw to its own canvas
love.graphics.clear(0, 0, 0, 1)
-- set screen to black
love.graphics.translate(-self.x + self.power, -self.y + self.power)
-- draw in the center of the canvas
for i = 2, #self.shadowVertices - 1, 2 do
local p1 = {x = self.shadowVertices[i].x, y = self.shadowVertices[i].y}
local p2 = {x = self.shadowVertices[i + 1].x, y = self.shadowVertices[i + 1].y}
if p1.x ~= p2.x or p1.y ~= p2.y then
love.graphics.polygon('fill', {p1.x, p1.y, p2.x, p2.y, self.x, self.y})
end
end
-- draws a triangle fan from the center of the light to all visible areas (draws the light not the shadow)
local p1 = {x = self.shadowVertices[#self.shadowVertices].x, y = self.shadowVertices[#self.shadowVertices].y}
local p2 = {x = self.shadowVertices[1].x, y = self.shadowVertices[1].y}
-- gets last and first point in the triangle fan (rendering issue fix)
if (math.atan2(p2.y - self.y, p2.x - self.x) - math.atan2(p1.y - self.y, p1.x - self.x) + math.pi) % (math.pi * 2) > math.pi then
love.graphics.polygon('fill', {p1.x, p1.y, p2.x, p2.y, self.x, self.y})
end
-- draws triangle between the last and first vertice only if it wont draw over the other triangles (defunct because of faux polygon at edge of light distance)
love.graphics.pop()
end