您的位置:首页 > 其它

用shader在love2d里实现精灵描边效果

2016-10-12 19:21 671 查看
精灵描边的效果在游戏里面很普及,效果参照下图

没有描边:



有描边:



今天用 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);
}


效果是把图片 不透明的区域,绘制成同一种颜色。效果是这样的:



但是这样只是实现了 用纯色绘制,还没有 把图片"边胖" ,所以 如果只是这样的话, 和 正常绘制叠加起来,

会看不到 描边的效果, 因为 纯色绘制的内容 大小和 正常绘制的内容一样大小,被完全挡住了。

于是打算换个办法解决问题:

在  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 代码里面,需要注意的是 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 写得夸张一些,效果是这样的:



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

这样一来,就可以用最开始的思路 ,即:

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)

这样就达公告成了。效果如下:



这样做的好处是,可以在 vertex shader 里面精确控制 描边 的宽度(比如现在是1个像素),而不用再 pixel shader 里写 0.001 这样的 硬编码。

并且,把逻辑从 pixel shader 转移到 vertex shader 里,效率应该也会更高一些。

以上就是 在 love2d 里用 shader 实现简单效果的全过程,记录于此备忘。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: