本问题是在开发ar 涂涂乐项目过程中碰到的
(2017.03.15. 更新 (此解决方案有显示上的问题,现已用shader完美解决,请看后面17日更新的内容))
本文的主题是:unity如何动态的修改单个模型已存在的多个uv
首先说明一个事实,untiy没有提供动态修改UV的脚本接口。我的实现方式有些取巧但并不是强行的取巧方式。
大家刚开始查找这个问题的答案的时候会发现unity API中有这样几个东西:
- UVChannelFlags.UV
- Mesh.SetUVs
- Swap UVs(在模型的导入设置里面,也就是模型的Import Setting)
- Generate Lightmap UVs(在模型的导入设置里面,也就是模型的Import Setting)
- UV Set(Standard Shader,也就是unity自带的最基本的shader,的一个参数)
- Mesh.uv
当然,用脚趾头想想,都应该会想到,动态切换模型的uv肯定是要先从shader下手。所以我查看了unity自带的shader,然后就在Standard中发现了uv set这个参数,如下图:
于是,我就决定从uv set开始入手。
首先,我是看到了unity中多套uv set理解和应用,里面有几个思路是对的:
- 先在3D软件中,给模型附上多套UV,然后才能在unity中使用。
- unity貌似可以支持最多4套UV切换(注意这个貌似,因为我最后也没有找到这个切换的接口,只找到了什么资料都查不到的UVChannelFlags.UV)
然后这个文章里面有些东西是有问题的:
- 里面的shader源码并没有存在的意义,首先这个shader源码没有提供切换uv的地方,没有实现切换uv的效果,然后,真正在生产环境中是不可能使用这个shader的,因为你不能去给每一个需要切换uv的模型的shader进行重写
但是查到后面,发现uv set即使切换了,也不能达到切换uv的效果。带着两层uv的模型,随便贴一张贴图,然后使用代码切换了这个shader的uv set之后,发现场景中的模型身上的贴图并没有变化,更别说是否正确的切换了。
随后我又开始转变思路,先后查了下面的几点:
UVChannelFlags.UV:这个结构虽然在render的源码中看到了,而且这个的结构是这样的:
[Flags]
public enum UVChannelFlags
{
UV0 = 1,
UV1 = 2,
UV2 = 4,
UV3 = 8
}Mesh.SetUVs和Mesh.uv 这个Mesh.SetUVs一看到他的API和实现,就知道是用于手动实现uv的,而并不是去切换模型上已存在的多个uv。而Mesh.uv,可以通过给其赋值达到切换uv的效果,但是我并没有找到可以模型上的uv接口以给其赋值。就是说,我没有可以给其赋值的东西。具体的Mesh.uv是一个Vector2[]型的变量,如果说你们找到了可以通过Mesh.uv达到动态切换单个模型中已保存的多个uv的方法,可以在底下留言。
- Swap UVs(在模型的导入设置里面,也就是模型的Import Setting)。
通过勾选Swap UVs这个选项确实可以达到切换uv的效果,这个选项官方的解释就是可以切换导入模型的主uv和次uv。所以我列一下这个选项的几个问题:1。只能在两个uv之间切换,而如果照官方API中uv相关的api(比如UVChannelFlags.UV,Mesh.uv等)可以看出,是有四个uv可以切换的;2。因为这个选项是在model的import setting里面,所以并不能在runtime的时候动态的去修改。所以最后,这个也是条dead end。
- Secondary Maps,这个才是能实现动态修改的关键,它是在standard shader里面的。
只有在使用了Secondary Maps后切换UV Set才能生效。所以我先是把Secondary Maps的Albedo和上面的Main Maps的Albedo分别使用相同的贴图(测试同一张贴图不同uv的显示效果),然后切换UV Set发现切换uv的效果出来了,但是不太对,在切换到UV1的时候,UV0的贴图还在(原因大家可以去查查uv set中UV1的作用)。所以我后面直接将Main Maps的Tilling设置成了(0,0),然后就达到了切换uv的效果。使用的时候,使用脚本来控制这个UV Set就可以了。脚本如下:
private void SetToUV0()
{
MeshRenderer render = GetComponent<MeshRenderer>();
render.material.SetFloat("_UVSec", 0);
}
private void SetToUV1()
{
MeshRenderer render = GetComponent<MeshRenderer>();
render.material.SetFloat("_UVSec", 1);
}
最后,我的实现方式就是Secondary Maps+UV Set(也就是上面的第4点)。
(2017.03.17. 更新)
由于上面的解决方案,用到正式的项目中的时候,发现贴图变成了蓝色(查看standard源码后发现是因为少了两个通道的颜色),所以重新研究standard shader源码,然后发现可以通过shader非常方便的实现。而且是可以进行4个uv的切换。
下面是部分standard shader源码(为了方便理解,我进行了简化):
Properties
{
_MainTex("Albedo", 2D) = "white" {}
[Enum(UV0,0,UV1,1)] _UVSec ("UV Set for secondary textures", Float) = 0 //这就是UVSet
}
struct VertexInput
{
float2 uv0 : TEXCOORD0;
float2 uv1 : TEXCOORD1;
};
float4 TexCoords(VertexInput v)
{
float4 texcoord;
//从下面两行源码可以看出,上面的解决方案(通过切换UVSet(即这里的_UVSec 变量)只能影响到texcoord的zw,而不能影响到xy,所以会出现贴图变蓝的情况)
texcoord.xy = TRANSFORM_TEX(v.uv1, _MainTex);
texcoord.zw = TRANSFORM_TEX(((_UVSec == 0) ? v.uv0 : v.uv1), _DetailAlbedoMap);
return texcoord;
}
所以,在shader中,uv是可以很方便的拿到并且切换的,关键的代码就是(上方中的一行):
texcoord.xy = TRANSFORM_TEX(v.uv1, _MainTex);
但是这个只能影响到texcoord的xy,所以应该写成:
texcoord = TRANSFORM_TEX(v.uv1, _MainTex);
这里的uv1就是模型身上的第二张uv(uv0:第一张,uv1:第二张),为什么是呢?是因为在上面的结构体中,把他们定义成了TEXCOORD0、TEXCOORD1、TEXCOORD2等,如下:
struct VertexInput
{
float2 uv0 : TEXCOORD0;
float2 uv1 : TEXCOORD1;
};
所以,随便拿一个shader过来,我们都可以把它加上切换uv的功能。我这里用了unity内置的”Unlit/Texture” shader来进行修改,因为我们的正式工程这个shader用的比较多。
原来的”Unlit/Texture”:
Shader "Unlit/Texture" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 100
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata_t {
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
};
struct v2f {
float4 vertex : SV_POSITION;
half2 texcoord : TEXCOORD0;
UNITY_FOG_COORDS(1)
};
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);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.texcoord);
UNITY_APPLY_FOG(i.fogCoord, col);
UNITY_OPAQUE_ALPHA(col.a);
return col;
}
ENDCG
}
}
}
下面是修改后的:
Shader "Unlit/Texture-ForUV" {
Properties{
_MainTex("Base (RGB)", 2D) = "white" {}
[Enum(UV0,0,UV1,1)] _UVSet("UV Set for textures", Float) = 0 //增加了_UVSet的切换功能
}
SubShader{
Tags{ "RenderType" = "Opaque" }
LOD 100
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata_t {
float4 vertex : POSITION;
float2 uv0 : TEXCOORD0;//上面这里的变量名是texcoord,我改了个名字,改成了uv0
float2 uv1 : TEXCOORD1;//添加了uv1
};
struct v2f {
float4 vertex : SV_POSITION;
half2 texcoord : TEXCOORD0;
UNITY_FOG_COORDS(1)
};
sampler2D _MainTex;
float4 _MainTex_ST;
half _UVSet;//注册_UVSet
v2f vert(appdata_t v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.texcoord = TRANSFORM_TEX(((_UVSet == 0) ? v.uv0 : v.uv1), _MainTex);//关键是修改了这行,根据_UVSet的值,在uv0和uv1中进行切换
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.texcoord);
UNITY_APPLY_FOG(i.fogCoord, col);
UNITY_OPAQUE_ALPHA(col.a);
return col;
}
ENDCG
}
}
}
最后来张shader截图:
【个人广告】
希望大家可以支持我的个人微信号“小游戏情报局”