游戏中因为有时怪物太多,常常需要对攻击的怪物高亮,对于游戏体验都有很大的提升,也增加了玄幻色彩,如下所示:
网上也有不少实现这种效果的例子,《introduce to DirectX3D game programming》 书中介绍有法线放大的方法,这种方法对于规则模型,比如茶壶(teapot)是有很好的效果,见博客中所示, 但是游戏中也有不少NPC 是穿有衣服,衣袂飘飘, 此时这种片状的模型在边缘是没有法线的,所以往往无法描边,最后的效果必然是不完整的。
鉴于此,重新思考法线放大方法。因为法线放大最终的效果就是为了实现把单色渲染的模型背景图沿周围扩大,我们可以采用别的方法达到同样的效果。对图像熟悉的应该会想到膨胀(inflation)。所以下面所讲的模糊高亮效果就是通过下面几个步骤实现:
1. 单色渲染模型到一张RTT_1
2. 设置currentTarget 为RTT_2, getTargetTexture(RTT_1), 进行膨胀
3. 设置currentTarget 为RTT_1, getTargetTexture(RTT_2), 进行高斯模糊
3. 最后一步就是把处理后的纹理贴到游戏中
下面具体讲解下shader 书写:
1. 单色渲染pass, 在顶点pass 中,主要是做坐标转换
// rimback pass
struct Rimback_VS_OUTPUT
{
float4 pos : POSITION0;
float2 uv : TEXCOORD0;
};
Rimback_VS_OUTPUT Rimback_vs(VertexInput input)
{
// localspace转换到worldspace,并且蒙皮
WorldVertex wv = VertexInputToWorld(input);
Rimback_VS_OUTPUT output;
float3 pos = wv.worldPos.xyz;
output.pos = mul(float4(pos, 1), vp_mat);
output.uv = input.uv0;
return output;
}
float4 Rimback_ps(float2 uv : TEXCOORD0): color0
{
float4 color = tex2D(modelTex, uv);
clip(color.a-0.5);
color.xyz= edgeColor.xyz;
//clip(color.a);
return float4(color.rgb,1);
}
其中 VertexInputToWorld(VertexInput input) 函数实现描边随人物移动而移动,相当于给描边加上骨骼,函数中主要也是实现input 和 骨骼矩阵 之间的运算
2. 膨胀,关于膨胀原理,就是通过周围搜索,增加像素,填补空白。本例子中,是通过求取周围像素的alpha 值,因为步骤1得到单色渲染纹理的alpha为1,周围取到的alpha值是0,这样通过计算,得到边缘部分的alpha 在0~1 之间。通过阈值可以控制膨胀效果。
float4 pengzhang_ps(float2 uv : TEXCOORD0) : color0
{
const float2 flation[25] =
{
-2.70,-2.70,
-2.70,-1.70,
-2.70, 0.70,
-2.70, 1.70,
-2.70, 2.70,
-1.70,-2.70,
-1.70,-1.70,
-1.70, 0.70,
-1.70, 1.70,
-1.70, 2.70,
0.70,-2.70,
0.70,-1.70,
0.70, 0.70,
0.70, 1.70,
0.70, 2.70,
1.70,-2.70,
1.70,-1.70,
1.70, 0.70,
1.70, 1.70,
1.70, 2.70,
2.70,-2.70,
2.70,-1.70,
2.70, 0.70,
2.70, 1.70,
2.70, 2.90,
};
float4 outColor = tex2D(blurTex, uv);
for(int i = 0; i< 25; i++)
{
float4 color = tex2D(blurTex, uv + flation[i].xy * viewport_inv_size.xy);
// add color to flation
if(color.a >= 0.98)
outColor = color;
}
return outColor;
}
float4 edegblur_ps(float2 uv : TEXCOORD0) : color0
{
// 0.7 is for pixel bias
const float4 samples[9] = {
-3.70, -3.70, 0, 1.0/16.0,
-3.70, 3.70, 0, 1.0/16.0,
3.70, -3.70, 0, 1.0/16.0,
3.70, 3.70, 0, 1.0/16.0,
-3.70, 0.70, 0, 2.0/16.0,
3.70, 0.70, 0, 2.0/16.0,
0.70, -3.70, 0, 2.0/16.0,
0.70, 3.70, 0, 2.0/16.0,
0.70, 0.70, 0, 4.0/16.0
};
float4 col = float4(0, 0, 0, 0);
//Sample and output the averaged colors
for(int i=0;i<9;i++)
col += samples[i].w * tex2D(blurTex, uv + samples[i].xy * viewport_inv_size.xy);
return float4(edgeColor.r,edgeColor.g,edgeColor.b,col.a);
}
struct SceneBlend_VS_OUTPUT
{
float4 pos : POSITION0;
float2 uv : TEXCOORD0;
float4 uvProj : TEXCOORD1;
};
VS_OUTPUT scene_blend__vs(VS_INPUT vert)
{
VS_OUTPUT vsout;
vsout.pos = float4(vert.pos,1);
vsout.uv = vert.uv;
vsout.pos.z = 1.0f;
return vsout;
}
float4 scene_blend_ps(VS_OUTPUT input):color0
{
float alpha = tex2D(blurTex,input.uv).a;
clip(1.0 - (alpha + 0.2f));// 此处是实现显示轮廓边缘,中间部分alpha>0.8 的不显示
return tex2D(blurTex, input.uv);
}
最后,关于遮挡描边的效果,在步骤1,即单色渲染的时候,只需要打开深度检测,这样渲染的纹理,仅仅是模型可见部分,而不可见部分就没有渲染进去,当然后面的膨胀,模糊都没有它们的事情了。最后的效果图,如下所示:
关于pass最后的效率情况,如果bur效果不明显,可以通过重复blur。如果pass影响游戏帧数,耗内存太多,可以考虑对blur 进行横向以及纵向分开pass。
以上思路仅供参考,希望对游戏中开发有所启示。
参考信息:
http://www.gamedev.net/topic/655543-outline-glow-effect/
http://wiki.unity3d.com/index.php/Silhouette-Outlined_Diffuse
https://en.wikipedia.org/wiki/Gaussian_blurhttps://software.intel.com/en-us/blogs/2014/07/15/an-investigation-of-fast-real-time-gpu-based-image-blur-algorithms