解决UGUI的图集导致Shader采样时UV错误的问题

这篇具有很好参考价值的文章主要介绍了解决UGUI的图集导致Shader采样时UV错误的问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

大家好,我是阿赵。
在我们用UGUI的时候,很多时候需要通过在UI上面挂材质球,写Shader,来实现一些特殊的效果。
这里句一个很简单的例子,只为说明问题。

一、简单例子说明

这个例子是这样的,我想在某个Image上面加一个渐变遮罩,只显示角色的头像。
这里我准备了一张角色贴图,然后根据角色头像的位置画了个遮罩。
解决UGUI的图集导致Shader采样时UV错误的问题
解决UGUI的图集导致Shader采样时UV错误的问题

接下来的实现很简单,通过图片的UV采样遮罩贴图,然后和原来的图片叠加透明度,之后就得到了这样的效果:
解决UGUI的图集导致Shader采样时UV错误的问题

这个例子的shader是这样的:

Shader "azhao/UIAlphaMask"
{
	Properties
	{
		[PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
		_Color("Tint", Color) = (1,1,1,1)

		_StencilComp("Stencil Comparison", Float) = 8
		_Stencil("Stencil ID", Float) = 0
		_StencilOp("Stencil Operation", Float) = 0
		_StencilWriteMask("Stencil Write Mask", Float) = 255
		_StencilReadMask("Stencil Read Mask", Float) = 255

		_ColorMask("Color Mask", Float) = 15
		[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip("Use Alpha Clip", Float) = 0
		_MaskMap("MaskMap",2D) = "white"{}
	}

		SubShader
	{
		Tags
		{
			"Queue" = "Transparent"
			"IgnoreProjector" = "True"
			"RenderType" = "Transparent"
			"PreviewType" = "Plane"
			"CanUseSpriteAtlas" = "True"
		}

		Stencil
		{
			Ref[_Stencil]
			Comp[_StencilComp]
			Pass[_StencilOp]
			ReadMask[_StencilReadMask]
			WriteMask[_StencilWriteMask]
		}

		Cull Off
		Lighting Off
		ZWrite Off
		ZTest[unity_GUIZTestMode]
		Blend SrcAlpha OneMinusSrcAlpha
		ColorMask[_ColorMask]
		Pass
		{
			Name "Default"
		CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma target 2.0

			#include "UnityCG.cginc"
			#include "UnityUI.cginc"

			#pragma multi_compile __ UNITY_UI_ALPHACLIP

			struct appdata_t
			{
				float4 vertex   : POSITION;
				float4 color    : COLOR;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float4 vertex   : SV_POSITION;
				fixed4 color : COLOR;
				half2 uv  : TEXCOORD0;
				float4 worldPosition : TEXCOORD1;
			};

			fixed4 _Color;
			fixed4 _TextureSampleAdd;
			float4 _ClipRect;
			sampler2D _MaskMap;

			v2f vert(appdata_t i)
			{
				v2f o;
				o.worldPosition = i.vertex;
				o.vertex = UnityObjectToClipPos(o.worldPosition);

				o.uv = i.uv;

				#ifdef UNITY_HALF_TEXEL_OFFSET
				o.vertex.xy += (_ScreenParams.zw - 1.0) * float2(-1,1) * o.vertex.w;
				#endif

				o.color = i.color * _Color;
				return o;

			}

			sampler2D _MainTex;

			half4 frag(v2f i) : SV_Target
			{
				half4 color = (tex2D(_MainTex, i.uv) + _TextureSampleAdd) * i.color;

				half4 maskCol = tex2D(_MaskMap, i.uv);
				color.a = maskCol.r*color.a;
				return color;
			}
		ENDCG
		}
	}
}

二、打图集之后,遇到的问题。

由于这个图片是用在UI的image上的,所以它的类型是Sprite。然后一般来说,使用UGUI的Sprite都会打成图集来使用。
于是我对这张图片设置一下PackingTag,让它成为一张图集的一部分。
解决UGUI的图集导致Shader采样时UV错误的问题

然后把图片打包成AssetBundle,再加载使用,这个时候,发现刚才显示很正常的例子,变得不正常了。
解决UGUI的图集导致Shader采样时UV错误的问题

三、分析问题

我修改一下shader,单独把这种图片的颜色显示出来。

half4 frag(v2f i) : SV_Target
{
	half4 color = (tex2D(_MainTex, i.uv) + _TextureSampleAdd) * i.color;

	half4 maskCol = tex2D(_MaskMap, i.uv);
	color.a = maskCol.r*color.a;
	return maskCol;
}

如果没有打包AssetBundle加载的时候,我们的遮罩图的位置应该是这样的。
解决UGUI的图集导致Shader采样时UV错误的问题

但如果通过AssetBundle加载之后,遮罩的位置会变成这样:
解决UGUI的图集导致Shader采样时UV错误的问题

比较容易就能看出来,出现问题的原因是,采样遮罩图的UV似乎变得不正确了。
由于刚才用于例子的两张图都太居中,不利于分析问题,于是我把角色图和遮罩图都修改了一下位置,变成这样:
解决UGUI的图集导致Shader采样时UV错误的问题
解决UGUI的图集导致Shader采样时UV错误的问题

如果正常显示,现在的效果应该是这样的:
解决UGUI的图集导致Shader采样时UV错误的问题

由于Unity的图集为了能适配硬件的图片压缩,会自动变成2的次幂,所以会对原图做一定的修改,先去掉空白的地方,然后再补到最接近的2的次幂的大小。

用工具打开AssetBundle,可以看到,这张图片去除了空白之后,实际sprite的像素是256*577
解决UGUI的图集导致Shader采样时UV错误的问题

而由于577已经超过了512,所以下一级是1024,于是这个贴图的完整尺寸是256*1024
解决UGUI的图集导致Shader采样时UV错误的问题

所以,实际上这张图片在AssetBundle里面是长这样的:
解决UGUI的图集导致Shader采样时UV错误的问题

这张图在Unity里面打开SpritePacker可以看到
解决UGUI的图集导致Shader采样时UV错误的问题

然后,把场景渲染模式改成渲染+线框
解决UGUI的图集导致Shader采样时UV错误的问题

可以看到,虽然原始的Image的范围是在红框这么大,但实际上,生成的网格只有绿框的范围。
解决UGUI的图集导致Shader采样时UV错误的问题

网上很多文章在介绍到这一步的时候,为了说明原理,就开始看UGUI的源码了,不过我认为,源码估计很多人都不想去看,或者说看不太懂。所以我我直接说结论。
一般图集的优化,包括我以前自己写的引擎,对于图集的实现方式,都是这样,在父级设置一个和原图大小一样的范围,然后在里面,只在有效像素范围内生成网格模型,这个网格模型的UV就不能是0-1,而是有效像素实际占有原图的比例。
这个UV比例,Unity是提供了方法给我们获取的

Vector4 outerUV = UnityEngine.Sprites.DataUtility.GetOuterUV(sprite);

这个例子,把outerUV打印出来,发现值是:
outerUV:(0,0,1,0.56)
这是怎么理解呢?
解决UGUI的图集导致Shader采样时UV错误的问题

由于图片本身是256宽,而有效像素也是256,所以uv的x坐标是完全使用了这张贴图的整个宽度,所以取值范围是0-1
由于图片高度是1024,而有效的像素范围只到577,所以只用到了图片的0.56。
于是,生成的小网格的UV坐标,实际上是这样的一个取值,宽度0-1,高度只取到0-0.56;

那是不是我们只需要重新计算一下UV坐标,就能解决问题呢?
其实并不是的,由于实际生成的网格只占原图的一部分,那么网格的四条边,离整个Image的四条边分别是多远呢?Unity同样提供了方法给我们查询

Vector4 padding = UnityEngine.Sprites.DataUtility.GetPadding(sp);

把padding打印出来:
padding:(388,9,54,28)
这里padding的含义是如下图所示的:
解决UGUI的图集导致Shader采样时UV错误的问题

通过打印sprite.rect,可以得到这个sprite在没有去掉空白之前的像素是698*698
所以通过padding,我们就可以算出这个小网格的4条边离Image的四条边的距离在UV上的比例

Vector4 customUV = new Vector4();
customUV.x = padding.x/ sp.rect.width;
customUV.y = padding.y / sp.rect.height;
customUV.z = padding.z / sp.rect.width;
customUV.w = padding.w / sp.rect.height;

由于上面的例子只有一张图片,不够复杂,所以下面把图集再扩充一下,打多几张图进去:
解决UGUI的图集导致Shader采样时UV错误的问题

我们挑了其中一个图作为渲染,打印出的outerUV是:
outerUV:(0.378,0.282,0.5,0.56)
实际上,在整张图集里面,它的UV范围会是这样的:
解决UGUI的图集导致Shader采样时UV错误的问题

虽然打多了很多张图进去,但padding是不会变的,sp.rect也是不会变的。

四、解决问题

通过上面的一堆分析,我们知道了这几个事情:
1.打成图集之后,生成的网格是只有在有效像素范围的,所以UV并不是0-1,而是截取一小部分
2.我们要算出离边缘的UV比例,然后把采样的范围从网格内部延伸到整个原始图片的大小。

1、重新映射UV范围:

实际上我们要做的事情,是从原来的0-1的uv范围,截取出一个当前小网格对应的部分
解决UGUI的图集导致Shader采样时UV错误的问题
解决UGUI的图集导致Shader采样时UV错误的问题
解决UGUI的图集导致Shader采样时UV错误的问题
解决UGUI的图集导致Shader采样时UV错误的问题

比如这个例子,原始的UV是0-1,但小网格里面的UV范围
x轴是0.378-0.5
y轴是0.282-0.56
为了能让这个小范围映射回0-1,在shader里面可以这样处理
先写一个Remap方法

float Remap(float val,float minOld,float maxOld,float minNew,float maxNew)
{
	return (minNew + (val - minOld) * (maxNew - minNew) / (maxOld - minOld));
}

然后

float minNew = 0;
float maxNew = 1;
float minOld = _OuterUV.x;
float maxOld = _OuterUV.z;
float tx = Remap(i.uv.x, minOld, maxOld, minNew, maxNew);
float _minOld = _OuterUV.x;
float _maxOld = _OuterUV.z;
float ty = Remap(i.uv.y, minOld, maxOld, minNew, maxNew);

这样,UV就从小网格映射回大Image范围了。

2、计算Padding

要计算边缘的范围,实际上就是把刚才重新映射的UV,再添加一个Padding的开始和结束位置的偏移,具体是这样算的:

tx = _Padding.x + (tx * (1 - _Padding.z - _Padding.x));
ty = _Padding.y + (ty * (1 - _Padding.w - _Padding.y));

通过这两步计算之后,UV坐标的映射和偏移都做完了,为了让大家看得更清楚,我准备了一张数字网格图(本来是打算用来做另外一个例子的)。计算后,通过对同一张图进行采样,背后比较暗的是原图,前面比较亮的是通过小网格反过来推算UV的结果。可以看出来,两张图的接缝处是完全重叠的,没有一点点偏差。
解决UGUI的图集导致Shader采样时UV错误的问题

到了这一步,其实问题已经解决完成了。把计算出来的新UV采样遮罩图,然后叠加颜色,就能得出正确的结果。下面是裁剪后的图和原遮罩图的对比,可以看出,裁剪的范围刚刚好是在原图的白色区域。
解决UGUI的图集导致Shader采样时UV错误的问题文章来源地址https://www.toymoban.com/news/detail-499131.html

五、源码

1、C#端获取outerUV和padding的代码

Sprite sp = imgAB.LoadAsset<Sprite>("longzhu");
image.sprite = sp;
Vector4 outerUV = UnityEngine.Sprites.DataUtility.GetOuterUV(sp);
Vector4 padding = UnityEngine.Sprites.DataUtility.GetPadding(sp);
Vector4 customUV = new Vector4();

customUV.x = padding.x/ sp.rect.width;
customUV.y = padding.y / sp.rect.height;
customUV.z = padding.z / sp.rect.width;
customUV.w = padding.w / sp.rect.height;

image.material.SetVector("_Padding", customUV);
image.material.SetVector("_OuterUV", outerUV);

2、完整Shader

Shader "azhao/UIAlphaMask"
{
	Properties
	{
		[PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
		_Color("Tint", Color) = (1,1,1,1)

		_StencilComp("Stencil Comparison", Float) = 8
		_Stencil("Stencil ID", Float) = 0
		_StencilOp("Stencil Operation", Float) = 0
		_StencilWriteMask("Stencil Write Mask", Float) = 255
		_StencilReadMask("Stencil Read Mask", Float) = 255

		_ColorMask("Color Mask", Float) = 15
		_Padding("Padding",Vector) = (0,0,0,0)
		_OuterUV("OuterUV",Vector) = (0,0,1,1)
		[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip("Use Alpha Clip", Float) = 0
		_MaskMap("MaskMap",2D) = "white"{}
	}

		SubShader
	{
		Tags
		{
			"Queue" = "Transparent"
			"IgnoreProjector" = "True"
			"RenderType" = "Transparent"
			"PreviewType" = "Plane"
			"CanUseSpriteAtlas" = "True"
		}

		Stencil
		{
			Ref[_Stencil]
			Comp[_StencilComp]
			Pass[_StencilOp]
			ReadMask[_StencilReadMask]
			WriteMask[_StencilWriteMask]
		}

		Cull Off
		Lighting Off
		ZWrite Off
		ZTest[unity_GUIZTestMode]
		Blend SrcAlpha OneMinusSrcAlpha
		ColorMask[_ColorMask]
		Pass
		{
			Name "Default"
		CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma target 2.0

			#include "UnityCG.cginc"
			#include "UnityUI.cginc"

			#pragma multi_compile __ UNITY_UI_ALPHACLIP

			struct appdata_t
			{
				float4 vertex   : POSITION;
				float4 color    : COLOR;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float4 vertex   : SV_POSITION;
				fixed4 color : COLOR;
				half2 uv  : TEXCOORD0;
				float4 worldPosition : TEXCOORD1;
			};

			fixed4 _Color;
			fixed4 _TextureSampleAdd;
			float4 _ClipRect;
			sampler2D _MaskMap;
			float4 _Padding;
			float4 _OuterUV;

			v2f vert(appdata_t i)
			{
				v2f o;
				o.worldPosition = i.vertex;
				o.vertex = UnityObjectToClipPos(o.worldPosition);

				o.uv = i.uv;

				#ifdef UNITY_HALF_TEXEL_OFFSET
				o.vertex.xy += (_ScreenParams.zw - 1.0) * float2(-1,1) * o.vertex.w;
				#endif

				o.color = i.color * _Color;
				return o;

			}

			sampler2D _MainTex;

			float Remap(float val,float minOld,float maxOld,float minNew,float maxNew)
			{
				return (minNew + (val - minOld) * (maxNew - minNew) / (maxOld - minOld));
			}

			half4 frag(v2f i) : SV_Target
			{
				half4 color = (tex2D(_MainTex, i.uv) + _TextureSampleAdd) * i.color;
				float minNew = 0;
				float maxNew = 1;
				float minOld = _OuterUV.x;
				float maxOld = _OuterUV.z;
				float tx = Remap(i.uv.x, minOld, maxOld, minNew, maxNew);
				tx = _Padding.x + (tx * (1 - _Padding.z - _Padding.x));
				float _minOld = _OuterUV.x;
				float _maxOld = _OuterUV.z;
				minOld = _OuterUV.y;
				maxOld = _OuterUV.w;
				float ty = Remap(i.uv.y, minOld, maxOld, minNew, maxNew);
				ty = _Padding.y + (ty * (1 - _Padding.w - _Padding.y));
				float2 maskUV = float2(tx, ty);
				half4 maskCol = tex2D(_MaskMap, maskUV);
				color.a = maskCol.r*color.a;
				return color;
			}
		ENDCG
		}
	}
}

到了这里,关于解决UGUI的图集导致Shader采样时UV错误的问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 《Unity的URP项目中使用自定义shader导致材质消失的解决办法》

            在Unity中使用URP时,会有需求使用自定义的一些shader来实现特殊效果,这时如果我们直接使用新建材质与无光照着色器(Unlit shader),可能会发生一个对于新手而言意料之外的问题—— 物体!消失了!         打开你正在使用的的 通用渲染器(Universal Rendere

    2024年02月06日
    浏览(76)
  • Unity地面交互效果——1、局部UV采样和混合轨迹

    回到目录   大家好,我是阿赵。   这期开始,打算介绍一下地面交互的一些做法。 比如: Unity引擎制作沙地实时凹陷网格的脚印效果 或者: Unity引擎制作雪地效果   这些效果的实现,需要基于一些基础的知识。所以这一篇先介绍一下简单的局部UV采样,然后映射纹理

    2024年02月06日
    浏览(45)
  • Unity 解决SpriteAtlas图集打包AssetBundle白图问题

    之前文档上说勾选了Include in build之后,就不需要自己增加一个Binding脚本,但是仍然会弹出警告,并且会出现白图现象 Tight Packing 勾选之后当Sprite有透明通道可能会出现错位 不推荐勾选 选项取消勾选之后打包AssetBundle会报警告 并且出现白图现象 那是因为没有进行bing  Sprite

    2024年02月11日
    浏览(41)
  • Unity中Shader的扭曲(同样使用了UV的扭曲)

    Unity中Shader的扭曲 注意:扭曲效果比较消耗手机性能 类似于透过 火焰 看火焰后的物体,火焰后的物体扭曲 类似于透过 水 看水中的物体,水中物体的扭曲

    2024年02月06日
    浏览(54)
  • Unity SpriteAtlas 打图集流程,与遇到的问题和解决方法

    在unity里面使用 SpriteAtlas ,把需要用到的游戏贴图资源,创建成对应的图集。 贴图资源没有打进包里面,如果以文本的方式打开图集,会发现文本里面关于m_PackedSprites的数组和m_PackedSpriteNamesToIndex的数组都是为空的,没有值在里面,没有值的这两个变量,是用不了对应的贴图

    2024年02月16日
    浏览(53)
  • Unity中Shader序列图动画(UV流动的通用起始点)

    我们在Shader中实现序列帧动画。可以实现一些简单特效或动画节省性能用。 我们在这篇文章中,实现一下UV流动的通用起始点。 先左到右,再从上到下 Unity中 URP Shader 的纹理与采样器的分离定义 属性面板 _MainTex(“MainTex”,2D) = “white”{} 定义纹理 TEXTURE2D(_MainTex); 定义采样器

    2024年01月17日
    浏览(59)
  • Cocos creator(2d) 使用 shader + uv 实现单张图片衔接滚动效果

    在游戏中,当我们需要让背景图片无缝衔接无限滚动时(打飞机这种背景一直滚动,或者肉鸽游戏地图一直在走等等),通常的做法是 在游戏中放两个背景node,在update中控制这两张背景图片的移动,并让其收尾衔接即可。(具体代码忽略) 可是在肉鸽类游戏中,玩家的走向是全方

    2024年02月13日
    浏览(96)
  • 【Unity细节】为什么加载精灵图集直接导致Unity引擎崩溃

    👨‍💻个人主页 :@元宇宙-秩沅 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 本文由 秩沅 原创 😶‍🌫️收录于专栏 :unity细节和bug 😶‍🌫️优质专栏 ⭐【软件设计师高频考点暴击】 点击运行,Unity直接崩溃退出 在加载打包后的图集时,可能图集里面没有相应的资源,

    2024年02月05日
    浏览(57)
  • Unity shader - 纹理采样

    目录 1.什么是UV   2.凹凸纹理 3.渐变纹理映射 4.遮罩纹理 对于三维模型,有两个最重要的坐标系统,一是顶点的位置(X,Y,Z)坐标,另一个就是UV坐标。什么是UV?简单的说,就是贴图影射到模型表面的依据。 完整的说,其实应该是UVW(因为XYZ已经用过了,所以另选三个字

    2024年02月07日
    浏览(35)
  • Unity的UGUI的Shader代码

    可能是由于UGUI有自己单独的渲染管线,所以UGUI的shader的代码不用顾及Unity单枪所使用的渲染管线。不知道这么理解对不对,如果不对,请大神批评指正。下面是Unity大部分的UGUI的Shader代码。 UI/Default UI/Lit/Bumped UI/Lit/Detail UI/Lit/Refraction UI/Lit/Refraction Detail UI/Lit/Transparent Overdraw

    2024年02月16日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包