项目开发笔记(十二)
哈,又一年了。在小时候过年总是非常有感觉,也满心期待。长大一点到了17 18岁的阶段,慢慢感觉过年失去味道 慢慢变成形式化,觉得过年传统做的节目都太无聊。又长大一点 到了25 26岁,过着常年背井离乡的生活 反而小时候对过年的期待又回来了,这几年我甚至非常热忱的参与到 杀猪 贴门神 拜天地等活动中…说回正题,我最近遇到一个需求.
一个小需求
对纹理中的指定色块,使用另一个纹理替换,如下图,这个图我们叫它原纹理
途中指定的颜色例如红色替换成如下纹理 ,这个图我们叫它目标纹理
效果大概变成如下图 ,这个图我们叫它效果纹理
原纹理中所有的色块均需用不同的目标纹理替换。(最终效果图可以拉到文末观看)
解决方案
要解决这个问题 需分两步。
- 对纹理进行分色
其实就是找到纹理中主要的色块具体的颜色值,例如上图中的红色 RGBA(1,0,0,1).这里做了一回拷贝先生,朋友推荐了一个很好的实现方案 – C#版图片分色.经过很少的修改之后 让它适应于Unity。细微修改后的源码被我放到了github上,需要的请传送 – EICTextureColor,简单的使用代码如下:
//-------------------------------------------------------------------------
private void __ParserTextureColor()
{
// m_txtMain 为 Texture,请自行赋值
if (null == m_txtMain)
{
return;
}
List<int ?[]> pColors = EICTextureColor.GetPalette(m_txtMain as Texture2D);
if (null == pColors || 0 == pColors.Count)
{
return;
}
List<color> txtColors = new List</color><color>();
List<string> strColors = new List</string><string>();
foreach (var v in pColors)
{
if (null == v || 3 < v.Length)
{
continue;
}
txtColors.Add(new Color((float)v[0] / 255.0f, (float)v[1] / 255.0f, (float)v[2] / 255.0f));
strColors.Add(__GetColor(v));
}
StringBuilder sb = new StringBuilder();
int nIndex = 0;
sb.AppendLine("该纹理的主要颜色为:");
foreach (var v in txtColors)
{
sb.Append("<color=#" + strColors[nIndex ++ ] + ">");
sb.Append(v.ToString());
sb.Append("</string></color>,");
}
Debug.LogWarning(sb.ToString());
if (null != m_text)
{
m_text.text = sb.ToString();
}
}
//-------------------------------------------------------------------------
简单的效果如图:
注意,在Unity中如果需要对Texture的像素进行读取,需要设置原图片的属性,如图所示.
- 设置Texture Type为Advanced
- 勾选 Read/Write Enabled
详细看上图提示信息,基本可以找到纹理中主要的颜色值。
- 使用目标纹理的颜色值 替换 原纹理的颜色值
根据需要 要实现这个效果,我想到一下两个方案
0x01.多个纹理叠加
第一反应想到的解决方案就是 每一种颜色做一个纹理,然后根据不同色块进行一个深度排列,最后把所有纹理根据排列一个一个贴上去。这个方案其实在3D游戏中叫地表贴花,典型的多层纹理混合。但是这种方案有一个明显的缺陷,越复杂的花需要的纹理层数越多。这对于手机上的应用程序来说 性能上是一个灾难。
0x02.使用shader进行颜色判断和替换
大家都知道通过shader可以在渲染之前获得每一个像素的RGBA和UV值,我们可以在这个地方 根据UV和颜色值去取得目标纹理对应的颜色值,然后替换之。中和考虑后 我决定使用这种方式。
使用shader进行颜色判断和替换
基本步骤流程如下:
0x01. 通过EICTextureColor进行纹理分色取得原纹理某个颜色A
0x02. 通过shader获取原纹理的每个像素的颜色B 和 UV
0x03. 通过对AB颜色进行对比,如果符合则进入 0x04
0x04. 通过UV获取 目标纹理 的颜色值C
0x05. 用目标纹理上的颜色C 替换 原文里上的颜色A
0x06. Over
以下是Shader代码详解:
我们知道在shader中可以轻易的获取原纹理的每个像素的颜色 和 UV,如下代码中
//-------------------------------------------------------------------------
// _MainTex 为当前对象的纹理
fixed4 frag(v2f IN) : SV_Target
{
half4 color = (tex2D(_MainTex, IN.texcoord)) * IN.color;
return color;
}
//-------------------------------------------------------------------------
当前像素的的UV为 IN.texcoord,当前像素点的颜色值为 color,根据这个我们可以对每一个像素的的颜色值进行对比,和什么对比?和我们上一步通过纹理分色获得的颜色值进行对比,代码大概如下:
//-------------------------------------------------------------------------
// source - 原纹理的颜色,就是在frag函数中获取的那个color,由调用者传入
// avg - 通过EICTextureColor进行纹理分色取得的某个颜色,由调用者传入
// _fTolerance - 容差值,由外部传入
bool __CMPPiexl(fixed4 source, fixed4 avg)
{
float r = abs(source.r - avg.r);
float g = abs(source.g - avg.g);
float b = abs(source.b - avg.b);
return r < _fTolerance && g < _fTolerance && b < _fTolerance;
}
//-------------------------------------------------------------------------
这里值得注意的是容差变量 _fTolerance , 所谓容差,就是误差。由于我们纹理分色取得的颜色值是一个大概的平均值,而原纹理中的色块中的颜色不可能绝对平整,这个时候这个误差就非常有用了。如果这个对比返回了true 则可以获取目标纹理身上的颜色值了,代码大概如下:
//-------------------------------------------------------------------------
// _nUVTileCount - 平铺参数
// _AvgColor001 - 通过EICTextureColor进行纹理分色取得的某个颜色,由外部传入
// _TagTex001 - 目标纹理 ,由外部传入
fixed4 __CheckPiexl(float2 uv, fixed4 c)
{
bool bChange = false;
float2 f = (float2)(uv * _nUVTileCount);
bChange = __CMPPiexl( c, _AvgColor001);
if (true == bChange)
{
fixed4 target = tex2D(_TagTex001, f) * _Color;
return target;
}
return c;
}
//-------------------------------------------------------------------------
关键部分完整串联起来就是如此:
//-------------------------------------------------------------------------
// source - 原纹理的颜色,就是在frag函数中获取的那个color,由调用者传入
// avg - 通过EICTextureColor进行纹理分色取得的某个颜色,由调用者传入
// _fTolerance - 容差值,由外部传入
bool __CMPPiexl(fixed4 source, fixed4 avg)
{
float r = abs(source.r - avg.r);
float g = abs(source.g - avg.g);
float b = abs(source.b - avg.b);
return r < _fTolerance && g < _fTolerance && b < _fTolerance;
}
//-------------------------------------------------------------------------
// _nUVTileCount - 平铺参数
// _AvgColor001 - 通过EICTextureColor进行纹理分色取得的某个颜色,由外部传入
// _TagTex001 - 目标纹理 ,由外部传入
fixed4 __CheckPiexl(float2 uv, fixed4 c)
{
bool bChange = false;
float2 f = (float2)(uv * _nUVTileCount);
bChange = __CMPPiexl( c, _AvgColor001);
if (true == bChange)
{
fixed4 target = tex2D(_TagTex001, f) * _Color;
return target;
}
return c;
}
//-------------------------------------------------------------------------
// 容差,此参数由外部传入
float _fTolerance;
// 纹理平铺数量,此参数由外部传入
int _nUVTileCount;
// 原纹理,此参数由外部传入
sampler2D _MainTex;
// 分色出来的颜色值,此参数由外部传入
fixed4 _AvgColor001;
// 目标纹理,此参数由外部传入
sampler2D _TagTex001;
//-------------------------------------------------------------------------
fixed4 frag(v2f IN) : SV_Target
{
half4 color = (tex2D(_MainTex, IN.texcoord)) * IN.color;
color = __CheckPiexl(IN.texcoord, color);
return color;
}
//-------------------------------------------------------------------------
到此基本实现了整个流程,Shader关键部分和关键代码已经分享,稍微了解一点shader的同学应该很快可以照着源码写一个出来,由于是公司内部项目导致暂时无法完整分享Shader源码,请见谅。最终的效果如图
2D UI 上的效果图
3D 物体上的效果图
参考的文章们
- http://www.cnblogs.com/xinglizhenchu/p/5164816.html
- http://blog.csdn.net/u011416077/article/details/47665383
EICTextureColor 还在吗?
可否看看
可否看看谢谢
可以参考一下吗
哈喽 是否读懂了文章呢