本教程是关于一个Shader满天飞的项目,涉及到的知识点有:
- 摄像头获取
- 颜色过滤
- 多摄像头图像拼接
- 带高度图的调色板
- 像素捕捉定位
- 动态水效果
- 燃烧融化效果
如果对图形学吃不消的同学,可以先去买冯乐乐的Shader入门书籍:
https://book.douban.com/subject/26821639/?from=tag
最近有朋友联系我,需要做一个VR版的消费演习项目。这已经是这个月第三个朋友找我做逃生演习项目,难道最近广州又有土豪老板开始千万级的融资???反正不关我事,我就是程序员,负责做好项目就行。把项目需求拿过来,需求很奇葩,不使用Kinect不使用Slam算法做一个消防逃生演习项目。具体需求是这样子滴:
- 需要用3个摄像头对真实世界的人物进行捕捉,并把人物绘制到Unity的主场景
- 使用图像识别技术,判断人物在场景里面的位置,并做相对应的触发时间
- 人物走到左边沙发开始燃烧
- 人物走到中间天花板掉落
- 人物走到右边有水的涟漪产生
- 人物走到终点门打开,有烟雾散发进来
- 不使用Kinect和Slam,将开发成本压缩在2W左右。
- 场景布置图如下
- 时限是两周的时间
咋看一下,功能很简单,无非就是Unity截获Camera的图像,显示在面片上,场景搭个面片把截获的信息显示出来。2W的项目妥妥的接。
但是后来的事实证明了,根本不是我想的那么简单,开始的需求是折纸飞机,后面开始让我弄波音747,没办法,10万的项目2万的接,自己揽的锅跪下都要背。
首先我们来看看具体需求:
1。需要用3个摄像头对真实世界的人物进行捕捉,并把人物绘制到Unity的主场景。
Unity是最大支持4个摄像头,但是我测试只需要拿一个就行了。思路就是把摄像头截取,把画面显示在Quad上面
代码:
IEnumerator Start()
{
yield return Application.RequestUserAuthorization(UserAuthorization.WebCam);
if (Application.HasUserAuthorization(UserAuthorization.WebCam))
{
WebCamDevice[] devices = WebCamTexture.devices;
WebCamTexture tex = new WebCamTexture(devices[0].name, 400, 400, 12);
mCameraTex.GetComponent<MeshRenderer>().material.mainTexture = tex;
tex.Play();
}
}
重写了Start,因为Unity底层使用反射调用Start,所以需要把void Start删掉。为啥要用携程,因为截获摄像头的图像有延时,必须在每次
渲染完摄像头才能截取,所以必须用协程把时间错开。
出来的结果是这样的:
我们人物在场景出来了,背景也出来了。但是我们要的效果是只要人物,那么背景怎么去掉呢?
我第一想到的是用抠颜色的方法,把背景抠掉。但是这样就有疑问,如果把人物也抠了怎么办?
多方研究发现,绿色是和人体颜色相似度最差的颜色,我们使用绿色背景打底,人物走过就能把绿色抠掉了。但是如果有人穿绿色的衣服,那我也没办法,这年头穿绿色套装出社会本来就有危险= =!
绿色布搞来了,下面开始动工!!!
现在要做的事情就是抠图!抠图!抠图!
网上找了个抠图插件很难用,很多依赖弄不掉,决定自己写!!
思路无非就是找到颜色匹配的那块像素,把他变成透明,同时Shader要支持透明通道还有透明队列。
颜色匹配的算法我想了很久,最后还是决定使用方差公式,即Color1和Color2的相似度等于。
sqrt(pow((Color1.r - Color2.r),2) + pow((Color1.g - Color2.g),2) + pow((Color1.b - Color2.b),2))
得出来的值越小,说明和目标颜色越接近,很明显,上面的Color2就是我决定使用的绿色
Shader "MyShader/FilterfColor" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_FilterfColor("Ridof (RGB)",Color)=(1,1,1,1)
_Range("Range",range(0,1))=0
_X("X",range(0,1))=0
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Transparent"}
Blend SrcAlpha OneMinusSrcAlpha
pass
{
CGPROGRAM
#pragma vertex vertext_convert
#pragma fragment fragment_convert
#include "UnityCG.cginc"
sampler2D _MainTex;
sampler2D _MainTex1;
float4 _FilterfColor;
float4 _MainTex_ST;
float _Range;
float _X;
struct Inputvrite
{
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};
struct Inputfragment
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
float ColorLerp(float3 tmp_nowcolor,float3 tmp_FilterfColor)
{
float3 dis = float3(abs(tmp_nowcolor.x - tmp_FilterfColor.x),abs(tmp_nowcolor.y - tmp_FilterfColor.y),abs(tmp_nowcolor.z - tmp_FilterfColor.z));
float dis0 =sqrt(pow(dis.x,2)+pow(dis.y,2)+pow(dis.z,2));
float maxdis = sqrt(3);
float dis1 = lerp(0,maxdis,dis0);
if(dis1 < _Range){
dis1 = dis1 *dis1 *dis1;
}
return dis1;
}
Inputfragment vertext_convert(Inputvrite i)
{
Inputfragment o;
o.pos = mul(UNITY_MATRIX_MVP,i.vertex);
o.uv = TRANSFORM_TEX(i.texcoord, _MainTex);
return o;
}
float4 fragment_convert(Inputfragment o) : COLOR
{
// o.uv.x *= _X;
float4 c = tex2D(_MainTex,o.uv);
c.a *=ColorLerp(c.rgb,_FilterfColor.rgb);
// c.a *= c.a;
return c;
}
ENDCG
}
}
FallBack "Diffuse"
}
稍作调整可以看到下面的效果,绿色被我抠掉了!!
但是后面还有更多艰巨的任务等着我,一个是调色板方便客户抠图,一个是要判断小人走到地图哪个位置。
两周时间第一天就只做了个抠图。其实好戏还在后头= =*
下一期要讲的内容是:像素遍历判断小人的位置。
(程序是个永无止境的东西,必须每天保持学习)
版权所有,违责必究
菜鸟在线 林老师 2017.7.29