True, I shouldn't say "couldn't", I would have to test on the target hardware
Though of course it's not nearly as efficient as true palette swapping
Dude, I have to thank you. I got what I wanted in the end, with this video tutorial to get my head around what's going on, your example for some guidance, and my own programming experience.Stifu wrote: ↑Sun Jul 01, 2018 11:41 am We have color swaps in our game, and I think that's pretty much the same idea. We use shaders for that.
Our color swap definitions are here: https://github.com/thomasgoldstein/zabu ... haders.lua
Code: Select all
local shader_code = [[
extern vec4[16] base_palette;
extern vec4[16] live_palette;
vec4 effect(vec4 colour, Image image, vec2 uvs, vec2 screen_coords) {
vec4 pixel = Texel(image, uvs);
for(int i = 0; i < 16; i++){
vec4 colour = base_palette[i];
if(pixel == colour)
return live_palette[i];
}
return pixel;
}
]]
function love.load()
--SHADER STUFF
--load Pico8 style palette for shader
local palette_img = love.image.newImageData('assets/palette.png')
for x = 1, palette_img:getWidth() do
local r,g,b,a = palette_img:getPixel(x - 1, 0)
base_palette[#base_palette + 1] = {r,g,b,a}
end
live_palette = {unpack(base_palette)}--quick way to copy table
shader = love.graphics.newShader(shader_code)
shader:send("base_palette", unpack(base_palette))
shader:send("live_palette", unpack(live_palette))
end
function love.draw(dt)
--START LOW RES
push:start()
--set the 16 colour palette shader
love.graphics.setShader(shader)
--Draw some text in the low res window
love.graphics.print("Hello World", 0, 0)
love.graphics.print("Again-------", 0, 48)
--Draw a sprite in the low res window
love.graphics.draw(player.img, player.x, player.y)
--affect the palette of one enemy
pal(10,11)
love.graphics.draw(enemy.img, enemy.x, enemy.y)
pal_reset()
love.graphics.draw(enemy2.img, enemy2.x, enemy2.y)
--Clear the shader
love.graphics.setShader()
--end the low res stuff, reset to desktop res
push:finish()
--END LOW RES
end
--source: index to change
--destination: index of colour to change it to
--[[ Uses the base pallete to get colours, so you don't
have to worry about the destination colour being
different than you expect]]
function pal(s,d)
live_palette[s] = base_palette[d]
shader:send("live_palette", unpack(live_palette))
end
--quick version of above, doesn't send the whole array every time.
--Use when making a number of changes, then finish with pal_update()
function pal_q(s,d)
live_palette[s] = base_palette[d]
end
function pal_update()
shader:send("live_palette", unpack(live_palette))
end
--resets pallete to default
function pal_reset()
for i=1,#base_palette do
live_palette[i]=base_palette[i]
end
shader:send("live_palette", unpack(live_palette))
end
--sets entire palette to the one colour, good for flashing a sprite when hit
function pal_flood(d)
for i=1,#base_palette do
live_palette[i]=base_palette[d]
end
shader:send("live_palette", unpack(live_palette))
end
Nice. You're welcome.Domarius wrote: ↑Sun Jul 08, 2018 10:42 am Dude, I have to thank you. I got what I wanted in the end, with this video tutorial to get my head around what's going on, your example for some guidance, and my own programming experience.
I recommend against this approach. Long ago, I was told that shaders don't like conditional jumps (which if's and for's compile to).Domarius wrote: ↑Sun Jul 08, 2018 10:42 am Below is the key parts - the shader that refers to the palettes, creating the palette, and using the pal functions to adjust the colours of the sprites (this code won't run on it's own, it's just for an overview).Code: Select all
local shader_code = [[ ... for(int i = 0; i < 16; i++){ vec4 colour = base_palette[i]; if(pixel == colour) return live_palette[i]; }
Code: Select all
local paletteShader = love.graphics.newShader[[
extern vec4[16] base_palette;
extern vec4[16] live_palette;
vec4 effect(vec4 colour, Image image, vec2 uvs, vec2 screen_coords) {
vec4 pixel = Texel(image, uvs) * colour;
for(int i = 0; i < 16; i++){
vec4 colour = base_palette[i];
if(pixel == colour)
return live_palette[i];
}
return pixel;
}
]]
do
local base_palette = {}
for i = 0, 15 do
base_palette[i + 1] = {i % 4 / 3, math.floor(i / 4) / 3, 0, 1}
end
paletteShader:send('base_palette', unpack(base_palette))
end
local function changePalette(t)
paletteShader:send('live_palette', unpack(t))
end
local palette =
{{0, 0, 0, 1}, {0, 0, .7, 1}, {.7, 0, 0, 1}, {.7, 0, .7, 1},
{0, .7, 0, 1}, {0, .7, .7, 1}, {.7, .7, 0, 1}, {.7, .7, .7, 1},
{.3,.3,.3, 1}, {0, 0, 1, 1}, { 1, 0, 0, 1}, { 1, 0, 1, 1},
{0, 1, 0, 1}, {0, 1, 1, 1}, { 1, 1, 0, 1}, { 1, 1, 1, 1}}
function love.load(args)
changePalette(palette)
end
local function setColourIndex(index)
love.graphics.setColor(index%4 / 3, math.floor(index/4) / 3, 0, 1)
end
function love.draw()
love.graphics.setShader(paletteShader)
local w, h = love.graphics.getWidth()/4, love.graphics.getHeight()/4
for index = 0, 15 do
setColourIndex(index)
local x, y = index % 4, math.floor(index / 4)
love.graphics.rectangle("fill", x*w, y*h, w, h)
end
love.graphics.setShader()
love.graphics.print(love.timer.getFPS())
end
function love.keypressed(k)
if k == "escape" then return love.event.quit() end
end
Code: Select all
local paletteShader = love.graphics.newShader[[
extern Image palette;
vec4 effect(vec4 colour, Image tex, vec2 pos, vec2 scr)
{
vec4 pixel = Texel(tex, pos) * colour;
return Texel(palette, pixel.xy);
}
]]
local paletteData = love.image.newImageData(4, 4)
local paletteImg-- = love.graphics.newImage(paletteData)
--paletteImg:setFilter("nearest", "nearest")
--love.graphics.setShader(paletteShader) -- this causes replacePixels() to cease working
local function changePalette(t)
for y = 0, 3 do
for x = 0, 3 do
local pix = t[y * 4 + x + 1]
paletteData:setPixel(x, y, pix[1], pix[2], pix[3], pix[4])
end
end
-- paletteImg:replacePixels(paletteData)
paletteImg = love.graphics.newImage(paletteData)
paletteImg:setFilter("nearest", "nearest")
paletteShader:send('palette', paletteImg)
end
local palette =
{{0, 0, 0, 1}, {0, 0, .7, 1}, {.7, 0, 0, 1}, {.7, 0, .7, 1},
{0, .7, 0, 1}, {0, .7, .7, 1}, {.7, .7, 0, 1}, {.7, .7, .7, 1},
{.3,.3,.3, 1}, {0, 0, 1, 1}, { 1, 0, 0, 1}, { 1, 0, 1, 1},
{0, 1, 0, 1}, {0, 1, 1, 1}, { 1, 1, 0, 1}, { 1, 1, 1, 1}}
function love.load(args)
changePalette(palette)
end
local function setColourIndex(index)
love.graphics.setColor((index%4 + 0.5) / 4, (math.floor(index/4) + 0.5) / 4, 0, 1)
end
function love.draw()
love.graphics.setShader(paletteShader)
local w, h = love.graphics.getWidth()/4, love.graphics.getHeight()/4
for index = 0, 15 do
setColourIndex(index)
local x, y = index % 4, math.floor(index / 4)
love.graphics.rectangle("fill", x*w, y*h, w, h)
end
love.graphics.setShader()
love.graphics.print(love.timer.getFPS())
end
function love.keypressed(k)
if k == "escape" then return love.event.quit() end
end
Yes absolutely! I totally understand.DarkShroom wrote: ↑Tue Jul 10, 2018 2:42 pm i could do palette animations fast, i could just save the frames in advance... however it depends what you need of course
Heheh, well it's exactly what @Stifu is doing in his zabayuki game too
Maybe Stifu can take advantage of that technique too But his purpose is a bit different, so maybe not.
You need to encode the palette index somehow as a colour, in a way that you can easily decode it in the shader. I chose an encoding that uses the R and G components, but any encoding works. For example, using 16 different intensities of a single component would work as well. Using R, G, B and A as if they were binary digits would work too (but it would be harder to work with in a drawing program). Or any other encoding.Domarius wrote: ↑Wed Jul 11, 2018 2:26 am 1. In your example, you use only the R and G components to look up a colour reference. But that wouldn't perfectly identify the colour being used of course, because it's missing B. What's a reliable way I can use R G and B as some sort of direct look up, into a texture?
That's the idea. The replacePixels bug hinders it somewhat, though. You can still use an array instead of an image for palette lookup. I probably was overcomplicating things by using an image.
Code: Select all
local paletteShader = love.graphics.newShader[[
extern vec4 palette[16];
vec4 effect(vec4 colour, Image tex, vec2 pos, vec2 scr)
{
vec4 pixel = Texel(tex, pos) * colour;
return palette[int(pixel.x * 16.0 + 0.5)];
}
]]
love.graphics.setShader(paletteShader)
local function changePalette(t)
paletteShader:send('palette', unpack(t))
end
local palette =
{{0, 0, 0, 1}, {0, 0, .7, 1}, {.7, 0, 0, 1}, {.7, 0, .7, 1},
{0, .7, 0, 1}, {0, .7, .7, 1}, {.7, .7, 0, 1}, {.7, .7, .7, 1},
{.3,.3,.3, 1}, {0, 0, 1, 1}, { 1, 0, 0, 1}, { 1, 0, 1, 1},
{0, 1, 0, 1}, {0, 1, 1, 1}, { 1, 1, 0, 1}, { 1, 1, 1, 1}}
function love.load(args)
changePalette(palette)
end
local function setColourIndex(index)
love.graphics.setColor((index + 0.5) / 16, 0, 0, 1)
end
function love.draw()
love.graphics.setShader(paletteShader)
local w, h = love.graphics.getWidth()/4, love.graphics.getHeight()/4
-- Change red component of index 3 every frame
palette[4][1] = love.timer.getTime() % 1
changePalette(palette)
for index = 0, 15 do
setColourIndex(index)
local x, y = index % 4, math.floor(index / 4)
love.graphics.rectangle("fill", x*w, y*h, w, h)
end
love.graphics.setShader()
love.graphics.setColor(1,1,1,1)
love.graphics.print(love.timer.getFPS())
end
function love.keypressed(k)
if k == "escape" then return love.event.quit() end
end
Ahh, you have opened my eyes! I'm going to have to try this out!pgimeno wrote: ↑Wed Jul 11, 2018 7:52 am By using 16 intensities, you could just make the images in greyscale, which would probably be the simplest, and the translation to colour index is more intuitive and easier to work with in a drawing program (assuming there's no gamma or colour management interfering).
I'll see about that with the game programmer, because although I can code, I'm actually the graphic artist for that game. Thanks for the heads up.
Code: Select all
local palette =
{{0, 0, 0, 1}, {0, 0, .7, 1}, {.7, 0, 0, 1}, {.7, 0, .7, 1},
{0, .7, 0, 1}, {0, .7, .7, 1}, {.7, .7, 0, 1}, {.7, .7, .7, 1},
{.3,.3,.3, 1}, {0, 0, 1, 1}, { 1, 0, 0, 1}, { 1, 0, 1, 1},
{0, 1, 0, 1}, {0, 1, 1, 1}, { 1, 1, 0, 1}, { 1, 1, 1, 1}}
function love.load( )
end
function love.draw()
local w, h = love.graphics.getWidth()/4, love.graphics.getHeight()/4
-- Change red component of index 3 every frame
palette[4][1] = love.timer.getTime() % 1
for index = 0, 15 do
love.graphics.setColor(unpack(palette[index+1]))
local x, y = index % 4, math.floor(index / 4)
love.graphics.rectangle( 'fill', x*w, y*h, w, h )
end
love.graphics.setColor( 1,1,1,1 )
love.graphics.print(love.timer.getFPS())
end
function love.keypressed( k )
if k == "escape" then return love.event.quit() end
end
Users browsing this forum: No registered users and 2 guests