Unity 通過 Secondary Maps + UV Set 動態修改UV
本問題是在開發ar 塗塗樂專案過程中碰到的
(2017.03.15. 更新 (此解決方案有顯示上的問題,現已用shader完美解決,請看後面17日更新的內容))
本文的主題是:unity如何動態的修改單個模型已存在的多個uv
首先說明一個事實,untiy沒有提供動態修改UV的指令碼介面。我的實現方式有些取巧但並不是強行的取巧方式。
大家剛開始查詢這個問題的答案的時候會發現unity API中有這樣幾個東西:
- Swap UVs(在模型的匯入設定裡面,也就是模型的Import Setting)
- Generate Lightmap UVs(在模型的匯入設定裡面,也就是模型的Import Setting)
- UV Set(Standard Shader,也就是unity自帶的最基本的shader,的一個引數)
當然,用腳趾頭想想,都應該會想到,動態切換模型的uv肯定是要先從shader下手。所以我查看了unity自帶的shader,然後就在Standard中發現了uv set這個引數,如下圖:
於是,我就決定從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截圖:
【個人廣告】
希望大家可以支援我的個人微訊號“小遊戲情報局”