山水堂
B001.シェーダサンプル
AパートとBパートを分けてみました!
Bパートは実践的なプログラムを記述していくことにします。
今回は、シェーダのサンプルです。
シェーダとはグラフィックボード上に展開された画像に対して直接計算を走らせて描画を変更する処理です。
今回のソースは3つに分けます。
①conf.lua
今までで説明したとおり、設定のための情報を記述しておきます。
②shader.lua
シェーダを利用するためのライブラリです、公式フォーラムのソースを利用します。
③main.lua
②のソースを利用したサンプルを記述していきます。
まずは、conf.luaです。
-- conf.lua function love.conf(t) t.version = "0.10.2" t.window.resizable = true end
リサイズ可能にしているだけです。
次に、shader.luaを記述しておきます。
公式のLove2dサイトにアップされていたシェーダのライブラリです。
動作確認したら動かなかったので、こちらで勝手に一部を改変しました、オリジナルはソース内のurlをご覧ください。
-- shader.lua -- https://love2d.org/forums/viewtopic.php?f=4&t=3733&sid=5c35f2e8687834d1c0d52e39ad13b073&start=290#p201412 local table_values = table.values or function(hashtable) local values = {} for k, v in pairs(hashtable) do values[#values + 1] = v end return values end local list = { noise = { body = [[ p += rand(p, noise_rnd)/(1000/noise_amount); finTex = Texel(tex, p); ]], externs = {amount = 10, rnd = 1}, funcs = {"rand"} }, waves = { body = [[ p.x += sin(p.y*waves_amount+waves_time*6)*0.03; finTex = Texel(tex, p); ]], externs = {time = 0, amount = 5} }, bulge = { body = [[ np = vec2(p.x - bulge_pos.x, p.y - bulge_pos.y); a = length(np); b = atan(np.y, np.x); a = (a*a); np = a * vec2(cos(b), sin(b)); p = np + bulge_pos; finTex = Texel(tex, p); ]], externs = {pos = {0.5,0.5}} }, pinch = { body = [[ np = vec2(p.x - pinch_pos.x, p.y - pinch_pos.y); a = length(np); b = atan(np.y, np.x); a = sqrt(a); np = a * vec2(cos(b), sin(b)); p = np + pinch_pos; finTex = Texel(tex, p); ]], externs = {pos = {0.5,0.5}} }, pixel = { body = [[ p = floor(p*(100/pixel_amount))/(100/pixel_amount); finTex = Texel(tex, p); ]], externs = {amount = 4} }, sucker = { body = [[ a = atan(sucker_pos.x - p.y, sucker_pos.y - p.x); p += -vec2(cos(a)/(100/(sucker_amount*sucker_amount)), sin(a)/(100/(sucker_amount*sucker_amount))); finTex = Texel(tex, p); ]], externs = {amount = 1, pos = {0.5, 0.5} } }, insidespin = { body = [[ a = atan(0.5 - p.y, 0.5 - p.x); p += vec2(cos(a)/insidespin_amount, sin(a)/insidespin_amount); finTex = Texel(tex, p); ]], externs = {amount = 1} }, curve = { body = [[ a = abs(p.x - 0.5); p.y -= (a*a)*3; p.x += (p.x > 0.5 ? a : -a)*(p.y/2); finTex = Texel(tex, p); ]], externs = {} }, rgb = { body = [[ a = 0.0025 * rgb_amount; finTex.r = texture2D(tex, vec2(p.x + a * rgb_dirs[0], p.y + a * rgb_dirs[1])).r; finTex.g = texture2D(tex, vec2(p.x + a * rgb_dirs[2], p.y + a * rgb_dirs[3])).g; finTex.b = texture2D(tex, vec2(p.x + a * rgb_dirs[4], p.y + a * rgb_dirs[5])).b; ]], externs = {dirs = {1,0,0,1,-1,-1}, amount = 3} }, tvnoise = { body = [[ finTex.r -= -tvnoise_light + 1 + rand(p, tvnoise_rnd); finTex.g -= -tvnoise_light + 1 + rand(p, tvnoise_rnd); finTex.b -= -tvnoise_light + 1 + rand(p, tvnoise_rnd); ]], externs = {light = 1, rnd = 1}, funcs = {"rand"} }, invert = { body = [[ finTex.r = 1-finTex.r; finTex.g = 1-finTex.g; finTex.b = 1-finTex.b; ]], externs = {} }, distortion = { body = [[ finTex.r = (sin(finTex.r * distortion_amount) + 1.) * .5; finTex.g = (sin(finTex.g * distortion_amount) + 1.) * .5; finTex.b = (sin(finTex.b * distortion_amount) + 1.) * .5; ]], externs = {amount = 20}, funcs = {"wave"} }, spinsucker = { body = [[ a = atan(p.y - 0.5, 0.5 - p.x); p.x += sin(a) * spinsucker_amount; p.y += cos(a) * spinsucker_amount; finTex = Texel(tex, p); ]], externs = {amount = 1} }, circle = { body = [[ a = sqrt(abs(p.x - circle_pos[0])*abs(p.x - circle_pos[0]) + abs(p.y - circle_pos[1])*abs(p.y - circle_pos[1])); if (circle_soft) { finTex.a = 1 - a*2 / circle_amount; } else { finTex.a = floor( 1 + (1 - a*2 * circle_amount)); } ]], externs = {amount = 1, soft = true, pos = {0.5, 0.5}} }, color = { body = [[ finTex.r *= color_color[0]/255.0; finTex.g *= color_color[1]/255.0; finTex.b *= color_color[2]/255.0; ]], externs = {color = {255, 255, 255}} }, scan = { body = [[ if (p.y > scan_y && p.y < scan_y + scan_height) { finTex.r -= -scan_light + 1 + rand(p, scan_rnd); finTex.g -= -scan_light + 1 + rand(p, scan_rnd); finTex.b -= -scan_light + 1 + rand(p, scan_rnd); } ]], externs = {y = 0, height = 1,light = 1, rnd = 1}, funcs = {"rand"} } } local functions = { rand = [[ float rand(vec2 co, float v) { return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453 * v); } ]], wave = [[ float wave(float x, float amount) { return (sin(x * amount) + 1.) * .5; } ]] } local function getExternType(a) local t = type(a) if t == "number" then return "float" end if t == "boolean" then return "bool" end if t == "table" then local t2 = type(a[1]) if t2 == "number" then if #a <= 4 then return "vec" .. #a else return "float[" .. #a .. "]" end elseif t2 == "table" then return "vec" .. #a[1] .. "[" .. #a .. "]" end end end local Shader = {} function Shader.new(...) local names = {...} local funcs = {} local externs = {} local header = [[ vec4 effect(vec4 color, Image tex, vec2 p, vec2 pc) { vec4 finTex = Texel(tex, p); vec2 np; float a, b, c; ]] local bodies = {} for i,v in ipairs(names) do local shader = list[v] table.insert(bodies, shader.body) for k,ext in pairs(shader.externs) do table.insert(externs, "extern " .. getExternType(ext) .. " " .. v .. "_" .. k .. ";\n") end if shader.funcs then for i,func in ipairs(shader.funcs) do funcs[func] = functions[shader.funcs[i]] end end end local extern_string = table.concat(externs, "") local funcs_string = table.concat(table_values(funcs), "") local body_string = table.concat(bodies, "") local footer = "\treturn finTex;\n}" local final = extern_string .. funcs_string .. header .. body_string .. footer -- print(final) local s = love.graphics.newShader(final) for i,v in ipairs(names) do for k,ext in pairs(list[v].externs) do if type(ext) == "table" and #ext > 4 then s:send(v.."_"..k, unpack(ext)) else s:send(v.."_"..k, ext) end end end return s end --check whether a shader has a certain extern function Shader.has(name, extern) return list[name].externs[extern] end return Shader
初見では、ちょっと難しいと思いますので、コピー&ペーストで利用してください。
様々なシェーダを利用できるようにするために、工夫してあります。
では、最後にmain.luaです。
-- main.lua local Shader = require "shader" local g_shader local g_image local g_x, g_y local g_time = 0 function love.load() -- 画像の読み込み g_image = love.graphics.newImage("love-app-icon.png") -- シェーダの読み込み g_shader = Shader.new("pixel", "waves") -- 位置を計算 love.resize(love.graphics.getDimensions()) end function love.update(dt) g_time = g_time + dt -- シェーダに経過時間を渡す g_shader:send("waves_time", g_time) end function love.resize(w, h) -- 中央位置を算出しとく local ww, wh = love.graphics.getDimensions() local iw, ih = g_image:getDimensions() g_x = (ww - iw) * 0.5 g_y = (wh - ih) * 0.5 end function love.draw() love.graphics.setShader(g_shader) love.graphics.draw(g_image, g_x, g_y) love.graphics.setShader() end
love.graphics.newImage("love-app-icon.png")
という箇所で、画像を指定します。
サンプルでは、Love2dのロゴをダウンロードして使っています。
https://love2d.org/wiki/L%C3%B6ve_Logo_Graphics
画像は何でも良いので、必要に応じて変更してください。
画像がきちんと読み込まれて実行されると、中央でドットの荒い画像がゆらゆらと揺れますね!
シェーダを使い、画像に変化をもたらしました。
love.drawを見てください。
setShader内に影響を及ぼすシェーダをセットし、再度setShaderでリセットする間にある描画処理が影響を受けます。
実際に使ってみると、シンプルな構造ですね。
今回はshader.lua内の"pixel"と"waves"をサンプルとして使ってみましたが、listに並んでいるものすべてが利用可能です。
いろいろといじってみてください。
[2017.06.26] 新規:0.10.2で動作確認