Unity 通过 Secondary Maps + UV Set 动态修改UV

本文介绍在Unity中如何实现在运行时动态地切换模型的多个UV集。通过分析Unity的Shader源码,给出了一个可行的解决方案,并提供了修改Unity内置Shader的具体步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本问题是在开发ar 涂涂乐项目过程中碰到的


(2017.03.15. 更新 (此解决方案有显示上的问题,现已用shader完美解决,请看后面17日更新的内容))

本文的主题是:unity如何动态的修改单个模型已存在的多个uv

首先说明一个事实,untiy没有提供动态修改UV的脚本接口。我的实现方式有些取巧但并不是强行的取巧方式。

大家刚开始查找这个问题的答案的时候会发现unity API中有这样几个东西:

  1. UVChannelFlags.UV
  2. Mesh.SetUVs
  3. Swap UVs(在模型的导入设置里面,也就是模型的Import Setting)
  4. Generate Lightmap UVs(在模型的导入设置里面,也就是模型的Import Setting)
  5. UV Set(Standard Shader,也就是unity自带的最基本的shader,的一个参数)
  6. Mesh.uv

当然,用脚趾头想想,都应该会想到,动态切换模型的uv肯定是要先从shader下手。所以我查看了unity自带的shader,然后就在Standard中发现了uv set这个参数,如下图:
这里写图片描述

于是,我就决定从uv set开始入手。

首先,我是看到了unity中多套uv set理解和应用,里面有几个思路是对的:

  1. 先在3D软件中,给模型附上多套UV,然后才能在unity中使用。
  2. unity貌似可以支持最多4套UV切换(注意这个貌似,因为我最后也没有找到这个切换的接口,只找到了什么资料都查不到的UVChannelFlags.UV)

然后这个文章里面有些东西是有问题的:

  1. 里面的shader源码并没有存在的意义,首先这个shader源码没有提供切换uv的地方,没有实现切换uv的效果,然后,真正在生产环境中是不可能使用这个shader的,因为你不能去给每一个需要切换uv的模型的shader进行重写

但是查到后面,发现uv set即使切换了,也不能达到切换uv的效果。带着两层uv的模型,随便贴一张贴图,然后使用代码切换了这个shader的uv set之后,发现场景中的模型身上的贴图并没有变化,更别说是否正确的切换了。

随后我又开始转变思路,先后查了下面的几点:

  1. UVChannelFlags.UV:这个结构虽然在render的源码中看到了,而且这个的结构是这样的:
    [Flags]
    public enum UVChannelFlags
    {
    UV0 = 1,
    UV1 = 2,
    UV2 = 4,
    UV3 = 8
    }
    ,所以我以为我找到了unity可以切换uv的关键,忽然相信了unity可以方便的切换4个uv,但是随后我并没有找到任何和UVChannelFlags相关的资料和引用它的地方。并宣布这是个dead end。

  2. Mesh.SetUVsMesh.uv 这个Mesh.SetUVs一看到他的API和实现,就知道是用于手动实现uv的,而并不是去切换模型上已存在的多个uv。而Mesh.uv,可以通过给其赋值达到切换uv的效果,但是我并没有找到可以模型上的uv接口以给其赋值。就是说,我没有可以给其赋值的东西。具体的Mesh.uv是一个Vector2[]型的变量,如果说你们找到了可以通过Mesh.uv达到动态切换单个模型中已保存的多个uv的方法,可以在底下留言。

  3. 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。
  4. 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截图:
这里写图片描述


【个人广告】
希望大家可以支持我的个人微信号“小游戏情报局

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

耳朵里有只风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值