NGUI中的Button几乎是最常用到的控件之一,并且可以组合各种组件(比如UIButtonColor,UIButtonOffset,UITweenxx),方便设置Button的各种状态下的属性,几乎可以满足我们的所有需求。
但是对于当Button的isEnabled属性设置为false时,根据设置的disableColor属性设置不可点击时的颜色时,虽然我们设置的灰色,但并不是我们想象中的样子!
设置的是灰色,实际运行结果却还是彩色的,只是暗了一点,并不能够很好地表现出其“禁用”的状态!
1.原理
Unity3d中所有的渲染都是基于Shader的,而Shader绑定在Material上,打开一个NGUI例子中自带的Material,得到其使用Shader的文件
NGUI中大部分材质都使用的Unlit/Transparent Colored(PS:虽然在Unlit下,但并不是Unity3d内置的,而是NGUI扩展的)
找到其片段着色器,代码如下:
- fixed4 frag (v2f i) : COLOR
- {
- fixed4 col = tex2D(_MainTex, i.texcoord) * i.color;
- return col;
- }
看到这个代码,就很容易理解为什么是变暗,而不是变成灰色了
顶点的颜色数据是从UISprite之类的面板中传递进来的,其最大值是白色(255,255,255,255),而这里是正交化的,最大值白色对应(1.0,1.0,1.0,1.0),这也是默认值,当采样得到的像素值x1.0,相当于采样得到的纹理值;如果设置一个其他的颜色,正交化后肯定会小于1.0,当采样得到的像素值乘以这个值后,像素值会比之前小,而最小值是(0,0, 0,0)即黑色,也就是说如果设置一个不是白色的颜色,就会使像素值更接近于黑色,这就是变暗的原因!
2.置灰
NGUI只提供了这样一种变暗的功能,用来表现其“禁用”的状态,但是这并不是最好的结果,如果需要介于黑白之间的灰色纹理,难道非要美术对每一个可能会被置灰的纹理重新制作一张纹理吗?
这就更糟了!游戏中纹理是很占空间的,这样做相当于将UI资源翻了一倍!
还是从Shader方面入手吧!
想象一下,如果在着色器处理之前,传递一个bool值,当这个bool值为true时,正常绘制纹理;当这个bool值为false时,绘制灰色纹理。
(Unity3d的Shader中并不支持传递bool值,这里只是举个栗子)
这样看似很合理,也确实可以实现,但是会有一个问题,这个bool值肯定要在顶点着色器阶段传过去,而NGUI提供的“纹理打包”功能(即很多纹理合并成一个Atlas,即节省空间,还可以有一些其他信息,比如九宫格拉伸的参数。。。),当这个bool值为false时,这个Atlas中所有的绘制即全部变为灰色,这是不符合逻辑的,当然可以每张小图单独处理,即相当于损失掉NGUI的“纹理打包”功能
3.解决方案
损失一个颜色值吧,作为“约定”!
选取一个颜色值,作为约定为置灰的标记,当片段着色器检测到这个颜色值之后,执行渲染灰色的shader!
这个颜色值可以任意选择,我这里选取纯黑色作为“约定颜色”,片段着色器代码如下:
- fixed4 frag (v2f i) : COLOR
- {
- fixed4 col;
- if (i.color.r < 0.001)
- {
- col = tex2D(_MainTex, i.texcoord);
- float grey = dot(col.rgb, float3(0.299, 0.587, 0.114));
- col.rgb = float3(grey, grey, grey);
- }
- else
- {
- col = tex2D(_MainTex, i.texcoord) * i.color;
- }
- return col;
- }
我复制了一份NGUI例子的纹理和材质,将此Shader设置到材质中,渲染效果如图
(最上面两个是原始状态下的效果,中间两个是NGUI提供的禁用状态效果,最下面两个分别是修改后Shader渲染同一个Atlas得到的结果)
这才是我想要的灰色!
以上文章转载自NGUI之UIButton"禁用"状态时置灰,感谢oneRain提供好文章
修改Shader让NGUI支持灰色滤镜
修改上面的代码 通,过修改后效率明显大幅提升:
struct v2f
{
float4 vertex : SV_POSITION;
half2 texcoord : TEXCOORD0;
fixed4 color : COLOR;
fixed gray : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata_t v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
o.color = v.color;
o.gray = dot(v.color, fixed4(1,1,1,0));
return o;
}
fixed4 frag (v2f i) : COLOR
{
fixed4 col;
if (i.gray == 0)
{
col = tex2D(_MainTex, i.texcoord);
col.rgb = dot(col.rgb, fixed3(.222,.707,.071));
}
else
{
col = tex2D(_MainTex, i.texcoord) * i.color;
}
return col;
}
设置UISprite.color = Color.black;就能使用了。
原理是在v2f中申请一个寄存器TEXCOORD1放置数据gray,并在顶点程序vert中计算是否灰色,在片段程序frag中用i.gray == 0做if判断。
第一段代码效率低的原因是if (i.color.r < 0.001)这里访问了color的分量r,但在官方文档中明确说了这样做效率低,除非必要情况时才使用。
另外在PC中(fixed gray : TEXCOORD1;)可以改成(bool gray : TEXCOORD1;),但在移动设备(android和ios)中都会有问题,求解。
以上文章转载自修改Shader让NGUI支持灰色滤镜,感谢pw提供好文章