天天看點

用shader在love2d裡實作精靈描邊效果

精靈描邊的效果在遊戲裡面很普及,效果參照下圖

沒有描邊:

用shader在love2d裡實作精靈描邊效果

有描邊:

用shader在love2d裡實作精靈描邊效果

今天用 love2d 也實作一個這樣的效果,記錄一下過程中遇到的問題和解決辦法。

一開始想的辦法是,把圖檔繪制兩次.

一次正常繪制, 一次用純色繪制 , 這樣兩個疊加起來,再把純色繪制的 圖檔 ,變的面積"胖" 一些 ,就能實作了精靈描邊的效果。

于是寫了 純色繪制的 像素着色器:

vec4 effect(vec4 color,Image texture,vec2 texture_coords,vec2 screen_coords)
		{
			vec4 pixel = Texel(texture,texture_coords);
			if(pixel.a == 0)
			{
				discard;
			}
			return vec4(1.0,1.0,0.0,1.0);
		}
           

效果是把圖檔 不透明的區域,繪制成同一種顔色。效果是這樣的:

用shader在love2d裡實作精靈描邊效果

但是這樣隻是實作了 用純色繪制,還沒有 把圖檔"邊胖" ,是以 如果隻是這樣的話, 和 正常繪制疊加起來,

會看不到 描邊的效果, 因為 純色繪制的内容 大小和 正常繪制的内容一樣大小,被完全擋住了。

于是打算換個辦法解決問題:

在  pixel shader 裡, 讓每個透明的像素檢測 上下左右 周邊的像素,如果 它周圍存在不透明的像素,則把這個像素

繪制成 純色像素 。 其他 不透明的部分正常繪制。這樣隻需要用這個shader繪制圖檔1次,也能夠實作描邊的效果。

于是寫了這樣的 像素着色器代碼:

vec4 effect(vec4 color,Image texture,vec2 texture_coords,vec2 screen_coords)
		{
			vec4 pixel = Texel(texture,texture_coords);
			if (pixel.a == 0 )
			{
				vec2 utc = vec2(texture_coords.x,texture_coords.y - 0.0001);
				vec2 dtc = vec2(texture_coords.x,texture_coords.y + 0.0001);
				vec2 ltc = vec2(texture_coords.x - 0.0001,texture_coords.y);
				vec2 rtc = vec2(texture_coords.x + 0.0001,texture_coords.y);

				vec4 up = Texel(texture,utc);
				vec4 dp = Texel(texture,dtc);
				vec4 lp = Texel(texture,ltc);
				vec4 rp = Texel(texture,rtc);

				if(up.a != 0 || dp.a != 0 || lp.a != 0 || rp.a != 0)
				{
					vec4 result = vec4(1.0,1.0,0.0,1.0);
					return result;
				}
			}
			return pixel;
		}
           

效果是這樣的:

用shader在love2d裡實作精靈描邊效果

上面這樣,就實作了 精靈描邊的效果。

上面這段shader 代碼裡面,需要注意的是 texture_coords 是 [0,1]區間數字,

是以 取旁邊 像素 的顔色時,使用了 0.0001 這樣的資料,目的是取小一些,争取能

取到相鄰像素的顔色值。

很顯然,這樣的 辦法 雖然實作了效果,但是這樣實作起來,

無論是從個思路上(有悖于起初的想法),還是代碼實作上(出現了無法預估的魔數0.001) ,都顯得有點山寨。

于是打算 用最開始的思路, 重新實作。

那麼,接下來的問題就是,如何使得圖檔 “變胖” 一些呢?

想到的辦法是 ,讓精靈的4個頂點,分别往各自所處的方位挪動一些,這樣整個精靈就被 抻開了,就變得胖了一些。

這件事情需要在  vertex shader 裡面做。

但是,vertex shader 裡,如何判斷 這個頂點該往 哪個方位移動呢?

這就需要在調用 shader 的時候,給 vertex shader 傳一個參數,即 精靈中心點 的位置。

這樣一來,就可以在 vertex shader 裡 ,用 vertex_position 和 這個中心點比較,就知道該往哪個方向挪動這個頂點了。

于是有了這樣的 頂點着色器 代碼:

extern number centerX;
		extern number centerY;
		vec4 position(mat4 transform_projection,vec4 vertex_position)
		{
			if(vertex_position.x < centerX) {
				vertex_position.x = vertex_position.x - 1;
			}
			else
			{
				vertex_position.x = vertex_position.x + 1;
			}
			if(vertex_position.y < centerY) {
				vertex_position.y = vertex_position.y - 1;
			}
			else
			{
				vertex_position.y = vertex_position.y + 1;
			}			
			return transform_projection * vertex_position;
		}
           

上面這段代碼,需要注意的是, 

每次調用 這段shader 的時候,都需要 給shader 裡面的 extern 變量傳精靈中心位置的值。

在  love2d 的 lua 代碼是這樣的 :

myShader3:send("centerX",x + 10);
	myShader3:send("centerY",y + 10);
	love.graphics.setShader(myShader3)	
	lg.draw(self.img, curQuad,x,y)
           

如果把 shader 裡面的 偏移量 1 寫得誇張一些,效果是這樣的:

用shader在love2d裡實作精靈描邊效果

可以看到 ,精靈果然 "胖了"不少。

這樣一來,就可以用最開始的思路 ,即:

1.把原圖 變胖(靠vertex shader),再 單色繪制一遍 (用單色繪制的 pixel shader),繪制出影子

2. 把原圖正常繪制一次

這樣就可以實作最初的效果了 

最終的代碼如下:

單色胖影子 的 shader:

local ps = [[
		vec4 effect(vec4 color,Image texture,vec2 texture_coords,vec2 screen_coords)
		{
			vec4 pixel = Texel(texture,texture_coords);
			if(pixel.a == 0)
			{
				discard;
			}
			return vec4(1.0,1.0,0.0,1.0);
		}
	]]

	local vs = 	[[
		extern number centerX;
		extern number centerY;
		vec4 position(mat4 transform_projection,vec4 vertex_position)
		{
			if(vertex_position.x < centerX) {
				vertex_position.x = vertex_position.x - 1;
			}
			else
			{
				vertex_position.x = vertex_position.x + 1;
			}
			if(vertex_position.y < centerY) {
				vertex_position.y = vertex_position.y - 1;
			}
           

用上面的 shader 先繪制影子,再不用 shader 正常繪制 精靈:

-- draw shadow
	myShader3:send("centerX",x + 10);
	myShader3:send("centerY",y + 10);
	love.graphics.setShader(myShader3)	
	lg.draw(self.img, curQuad,x,y)
	-- draw self
	love.graphics.setShader(nil);
	lg.draw(self.img, curQuad,x,y)	
           

這樣就達公告成了。效果如下:

用shader在love2d裡實作精靈描邊效果

這樣做的好處是,可以在 vertex shader 裡面精确控制 描邊 的寬度(比如現在是1個像素),而不用再 pixel shader 裡寫 0.001 這樣的 寫死。

并且,把邏輯從 pixel shader 轉移到 vertex shader 裡,效率應該也會更高一些。

以上就是 在 love2d 裡用 shader 實作簡單效果的全過程,記錄于此備忘。

繼續閱讀