2017/12/12(火) [n年前の日記]
#1 [love2d] love2dのShaderを使ってパレット書き換えっぽいことをする
love2dのShaderを使えば特定の色だけをぽわーんぽわーんと点滅させたりできるのではないか、つまりは大昔の2Dゲーム画面でよく見かけたパレット書き換えっぽいソレがビミョーに再現できるのではないかと思えてきたので、そのあたりを実験してみたり。環境は Windows10 x64 + love2d 0.10.2。
こんな感じに。
こんな感じに。
◎ 画像とソース。 :
使用画像は以下。
_colorbar_circle01.png
ソースは以下。
_conf.lua
_main.lua
特定色が見つかったら、その時だけピクセルのRGB値を変更、てな処理をしてる。
ただ、こういうGLSLの書き方はあまりよくないらしい。GLSL関連の説明ページを見ると、「条件分岐(if文)はコストがかかる」と言われてたりするわけで。とは言え、if文を使わずにこういう処理を書けるのか、そこらへんよく分からんわけで。この場合、違う書き方はできるのかな…?
_colorbar_circle01.png
ソースは以下。
_conf.lua
/function love.conf(t) t.window.title = "Shader test 04 palette change modoki" t.window.vsync = true t.window.resizable = true t.window.width = 640 t.window.height = 480 -- t.window.fullscreen = true -- t.window.fullscreentype = "exclusive" end
_main.lua
-- Shader test 04
-- palette change modoki
function love.load()
love.graphics.setDefaultFilter("nearest", "nearest")
scr_w, scr_h = 640, 480
canvas = love.graphics.newCanvas(scr_w, scr_h)
-- load image
img = love.graphics.newImage("colorbar_circle01.png")
-- make shader
myshader = love.graphics.newShader(
[[
extern number factor;
extern vec3 checkcolor;
extern vec3 replacecolor;
vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords ){
vec4 pixel = Texel(texture, texture_coords);
if (pixel.r == checkcolor.r && pixel.g == checkcolor.g && pixel.b == checkcolor.b) {
pixel.r = pixel.r * (1.0 - factor) + replacecolor.r * factor;
pixel.g = pixel.g * (1.0 - factor) + replacecolor.g * factor;
pixel.b = pixel.b * (1.0 - factor) + replacecolor.b * factor;
}
return pixel * color;
}
]]
)
myshader:send("checkcolor", {0.0, 1.0, 0.0}) -- R,G,B
myshader:send("replacecolor", {0.0, 0.0, 0.0}) -- R,G,B
angle = 0
end
function love.update(dt)
angle = (angle + 90 * dt) % 360.0
local v = 1.0 - math.abs(math.sin(math.rad(angle)))
myshader:send("factor", v) -- set 0.0 - 1.0
px = (scr_w - img:getWidth()) / 2
py = (scr_h - img:getHeight()) / 2
end
function love.draw()
love.graphics.setCanvas(canvas)
love.graphics.clear(0, 0, 0, 255)
love.graphics.setShader(myshader)
love.graphics.setColor(255, 255, 255)
love.graphics.draw(img, px, py)
love.graphics.setShader()
love.graphics.setCanvas()
-- draw canvas to window
wdw_w, wdw_h = love.graphics.getDimensions()
scr_scale = math.min((wdw_w / scr_w), (wdw_h / scr_h))
scr_ofsx = (wdw_w - (scr_w * scr_scale)) / 2
scr_ofsy = (wdw_h - (scr_h * scr_scale)) / 2
love.graphics.setColor(255, 255, 255)
love.graphics.draw(canvas, scr_ofsx, scr_ofsy, 0, scr_scale, scr_scale)
love.graphics.print("FPS: "..tostring(love.timer.getFPS()), 10, 10)
end
function love.keypressed(key, isrepeat)
if key == "escape" then
-- ESC to exit
love.event.quit()
end
end
特定色が見つかったら、その時だけピクセルのRGB値を変更、てな処理をしてる。
ただ、こういうGLSLの書き方はあまりよくないらしい。GLSL関連の説明ページを見ると、「条件分岐(if文)はコストがかかる」と言われてたりするわけで。とは言え、if文を使わずにこういう処理を書けるのか、そこらへんよく分からんわけで。この場合、違う書き方はできるのかな…?
◎ hsv変換をして色を変えてみる。 :
上記のソースを書いて動作確認しているうちに、もしかして、RGBをHSV(色相、彩度、明度)に変換して色々処理ができるのでは、と思えてきたわけで。
しかし、GLSLでRGB to HSV や HSV to RGB ってどうやるのかな…。と思ってググってみたら、以下のページで「このやり方が速いよ!」てな書き方が公開されてた。
_Blog: Fast branchless RGB to HSV conversion in GLSL - Lol Engine
また、HSV to RGB なら、love2dの公式wikiにも説明があった。
_HSV color (日本語) - LOVE
ありがたい。試しに使わせてもらったり。
結果はこんな感じに。
使用画像は以下。
_hsvbar.png
ソースは以下。
_conf.lua
_main.lua
RGB値からHSVを求めて、H(色相)を少し変化させて、またRGB値に戻して描画する、みたいな。
しかし、GLSLでRGB to HSV や HSV to RGB ってどうやるのかな…。と思ってググってみたら、以下のページで「このやり方が速いよ!」てな書き方が公開されてた。
_Blog: Fast branchless RGB to HSV conversion in GLSL - Lol Engine
また、HSV to RGB なら、love2dの公式wikiにも説明があった。
_HSV color (日本語) - LOVE
ありがたい。試しに使わせてもらったり。
結果はこんな感じに。
使用画像は以下。
_hsvbar.png
ソースは以下。
_conf.lua
function love.conf(t) t.window.title = "Shader test 05 hsv" t.window.vsync = true t.window.resizable = true t.window.width = 640 t.window.height = 480 -- t.window.fullscreen = true -- t.window.fullscreentype = "exclusive" end
_main.lua
-- Shader test 05 hsv
--
-- reference
-- Blog: Fast branchless RGB to HSV conversion in GLSL - Lol Engine
-- http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
function love.load()
love.graphics.setDefaultFilter("nearest", "nearest")
scr_w, scr_h = 640, 480
canvas = love.graphics.newCanvas(scr_w, scr_h)
-- load image
img = love.graphics.newImage("hsvbar.png")
-- make shader
myshader = love.graphics.newShader(
[[
extern number factor;
vec3 rgb2hsv(vec3 c) {
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy);
vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx);
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords ){
vec4 pixel = Texel(texture, texture_coords);
vec3 hsv = rgb2hsv(pixel.rgb);
hsv.x = mod(hsv.x + factor, 1.0);
vec3 rgb = hsv2rgb(hsv);
pixel.r = rgb.r;
pixel.g = rgb.g;
pixel.b = rgb.b;
return pixel * color;
}
]]
)
factor = 0.0
angle = 0
end
function love.update(dt)
factor = factor + 0.25 * dt
myshader:send("factor", factor) -- set 0.0 - 1.0
px = (scr_w - img:getWidth()) / 2
py = (scr_h - img:getHeight()) / 2
end
function love.draw()
love.graphics.setCanvas(canvas)
love.graphics.clear(0, 0, 0, 255)
love.graphics.setShader(myshader)
love.graphics.setColor(255, 255, 255)
love.graphics.draw(img, px, py)
love.graphics.setShader()
love.graphics.setCanvas()
-- draw canvas to window
wdw_w, wdw_h = love.graphics.getDimensions()
scr_scale = math.min((wdw_w / scr_w), (wdw_h / scr_h))
scr_ofsx = (wdw_w - (scr_w * scr_scale)) / 2
scr_ofsy = (wdw_h - (scr_h * scr_scale)) / 2
love.graphics.setColor(255, 255, 255)
love.graphics.draw(canvas, scr_ofsx, scr_ofsy, 0, scr_scale, scr_scale)
love.graphics.print("FPS: "..tostring(love.timer.getFPS()), 10, 10)
end
function love.keypressed(key, isrepeat)
if key == "escape" then
-- ESC to exit
love.event.quit()
end
end
RGB値からHSVを求めて、H(色相)を少し変化させて、またRGB値に戻して描画する、みたいな。
[ ツッコむ ]
以上です。

