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で動作確認