圖集上的uv是什麼原理?


拿我個人舉例,我第一次接觸遊戲開發的時候遇到圖集感覺非常的納悶。好好的一張一張圖片拿來顯示圖片按鈕啥的多好,你非要把N多圖片打到一張大圖裡面,然後又一張一張拆回去的,為什麼要多此一舉啊?

還有如果你工作了非常久,要顯示一張圖片就只知道:引擎都會有一個叫Sprite的組件,然後我把圖片的Name賦值給這個Sprite的Name屬性的話,圖片就會顯示。引擎在賦值之後又幹了什麼?

這篇文章就重點解釋,為什麼要打圖集這麼多此一舉的事情,以及引擎在我給sprite 的name賦值之後又幹了啥,這兩個問題。

理論基礎

其實Unity顯示任何內容都是先用頂點勾勒出要顯示面,然後再給這個面塗上顏色。也是上一篇文章代碼中主要打下的概念,這一篇文章就是把純粹的頂點色換成從圖片顏色。

先介紹下UV坐標,如下圖所示。

&" data-caption="" data-size="normal" dw="512" dh="404" class="origin_image zh-lightbox-thumb lazy" w="512" data-original="https://pic2.zhimg.com/v2-638221aea09d2b6b597ffb5dcda543c8_r.jpg" data-actualsrc="//i1.wp.com/pic2.zhimg.com/50/v2-638221aea09d2b6b597ffb5dcda543c8_hd.jpg">

很多網上的說法感覺對於萌新來說都很籠統,我直觀的打個比方。我要畫一張畫,一般情況下都是先用畫筆勾勒出大概的輪廓,接下需求就來了,給這個輪廓上色

好的,一般情況下計算機是不知道哪些像素點要塗上什麼顏色,這時候我們可以拿策劃的數據表思路來解決了,策劃同學設計了一張拿像素點的x、y坐標做ID的數據表,接著勤奮的美術同學填好一張數據表,最後程序同學 寫上程序遍歷每個像素點,並利用遍歷時候的x,y兩個像素坐標,然後根據坐標取出了對應的數據,賦值給對應的對象。oh~yeah 輪廓的草圖就上色好了。

補充:寫一段偽C代碼

const int pictureWidth = xxx;//跟策劃商量好的長度
const int pictureHeight = xxx;//跟策劃商量好的高度
for(int x = 0;x &< pictureWidth;x++){ for(int y = 0;y &< pictureHeight;y++){ Color c = LookUpFromConfig(x, y);//從配置表裡面查到顏色 DrawToScreen(x, y, c);//畫到屏幕上 } }

工作著,工作著。程序同學老發現,我去美術和策劃沒事老把表填錯了,幾乎一填錯,顯示就異常。測試妹子就要提bug,查肯定是程序員查啊....額、別人幹壞事,我TMD一天到晚被你折騰死。好的程序就想辦法來避免這些異常,想著我只要是張數據表不管輪廓佔用多少像素,我都能利用數據表把輪廓給上色了。於是就把索引的ID重新設計一下,將像素坐標除以長寬,使他們的值範圍處於(0,1)之間,接下來如果用這個ID查詢不到顏色,我就取這個ID旁邊的幾個查得到的顏色,給他勻一下算出來一個新顏色值。做了這種處理之後,誒管你數據表長啥樣,我程序都不會報錯,之後測試妹子就再也沒去找程序員了。

偽代碼

const int pictureWidth = xxx;//跟策劃商量好的長度
const int pictureHeight = xxx;//跟策劃商量好的高度

//先根據實際的圖片的長高與輪廓的長高 插值出實際長高的圖片(演算法 point,BiLinear, TriLinear)

for(int x = 0;x &< pictureWidth;x++){ for(int y = 0;y &< pictureHeight;y++){ float u = (float)x / (float)pictureWidth; float v = (float)y / (float)pictureHeight; Color c = LookUpFromConfig(u, v);//從配置表裡面查到顏色 DrawToScreen(x, y, c);//畫到屏幕上 } }

OK,這時候策劃又來新需求了,我要給兩個輪廓上色。勤奮的同學想著搞兩張表,策劃跟美術感覺都無所謂。這時候苦逼的程序同學就蛋疼了,麻痹目前老子給兩個輪廓上色複製粘貼下就完事了,要是哪天策劃腦子被驢踢了要給1000個輪廓上色,那老子咋維護代碼啊。程序員當然要抗議,策劃美術你們憑什麼不把幾個輪廓和數據合成一個輪廓和一張數據表給我。策劃美術:喵喵喵?

好吧,苦逼的程序員只好想著自己造輪子吧,把他們丟過來的輪廓啊,數據表啊,寫個工具丟給他們,一鍵把N多輪廓和數據整理到一起總行了把。這尼瑪一鍵都不點的,那老子.....也就只好自己看著點一下吧.....

從上述的例子中,我們就可以看到其實圖集就是整理到一起的數據表的,而輪廓其實就是遊戲中的一張張圖片顯示範圍構成的矩形,uv坐標就是數據表對應的ID了。因為顯卡是並行運算的,不過它只要大概知道輪廓的範圍就能幫你算出每個像素對應好的ID,我們就可以在像素點著色器中查詢到像素點顏色,給圖片上色了。

上機實驗

實驗2-1,圖1是一張確定按鈕和取消按鈕組合成的圖片,現在要分別單獨顯示這兩個按鈕。

&" data-size="normal" dw="220" dh="204" class="content_image lazy" w="220" data-actualsrc="//i1.wp.com/pic1.zhimg.com/50/v2-378b7cddfa0b10c5386d8166d0ac7902_hd.jpg">
圖1

思路,先確定好確定取消的UV坐標,然後賦值給對應的MeshFilter即可。

&" data-caption="" data-size="normal" dw="494" dh="449" class="origin_image zh-lightbox-thumb lazy" w="494" data-original="https://pic4.zhimg.com/v2-aff5c0186e41eceab969d681d331395f_r.jpg" data-actualsrc="//i1.wp.com/pic4.zhimg.com/50/v2-aff5c0186e41eceab969d681d331395f_hd.jpg">

c#代碼

/*
#########
############
#############
## ###########
### ###### #####
### ####### ####
### ########## ####
#### ########### ####
#### ########### #####
##### ### ######## #####
##### ### ######## ######
###### ### ########### ######
###### #### ############## ######
####### ##################### ######
####### ###################### ######
####### ###### ################# ######
####### ###### ###### ######### ######
####### ## ###### ###### ######
####### ###### ##### #####
###### ##### ##### ####
##### #### ##### ###
##### ### ### #
### ### ###
## ### ###
__________#_______####_______####______________

我們的未來沒有BUG
* ==============================================================================
* Filename: TestSprite
* Created: 2017/11/12 22:15:43
* Author: HaYaShi ToShiTaKa
* Purpose:
* ==============================================================================
*/
using UnityEngine;

public class TestSprite : MonoBehaviour {
public int width = 220;
public int height = 102;
public Material spriteMaterial;
// 注意這裡通過編輯器把算好的UV賦值進來
public Vector2 uv1 = new Vector2(0, 0f);
public Vector2 uv2 = new Vector2(0, 0.5f);
public Vector2 uv3 = new Vector2(1, 0);
public Vector2 uv4 = new Vector2(1, 0.5f);

private MeshFilter meshFilter;
private MeshRenderer meshRenderer;

void Start() {
//得到MeshFilter對象//
meshFilter = gameObject.GetComponent&();
meshRenderer = null;
if (meshFilter == null) {
//為null時,自動添加//
meshFilter = gameObject.AddComponent&();
meshRenderer = gameObject.AddComponent&();
meshRenderer.sharedMaterial = spriteMaterial;
}
Fill();
}

void OnValidate() {
if (meshFilter Application.isPlaying) {
Fill();
}
}

void Fill() {
//得到對應的網格對象//
Mesh mesh = new Mesh();
meshFilter.mesh = mesh;

//三角形頂點的坐標數組//
Vector3[] vertices = new Vector3[4];
//三角形頂點數組//
int[] triangles = new int[6];
//顏色數組//
Color[] colors = new Color[4];
//uv貼圖坐標//
Vector2[] uv = new Vector2[4];

float glWidth = (float)width / 2;
float glHeight = (float)height / 2;
//以當前對象的中心坐標為標準//
vertices[0] = new Vector3(-glWidth, -glHeight, 0);
vertices[1] = new Vector3(-glWidth, glHeight, 0);
vertices[2] = new Vector3(glWidth, -glHeight, 0);
vertices[3] = new Vector3(glWidth, glHeight, 0);

//綁定頂點順序//
triangles[0] = 0;
triangles[1] = 2;
triangles[2] = 1;
triangles[3] = 2;
triangles[4] = 3;
triangles[5] = 1;

//設置頂點顏色//
colors[0] = Color.white;
colors[1] = Color.white;
colors[2] = Color.white;
colors[3] = Color.white;

//綁定貼圖UV//
uv[0] = uv1;
uv[1] = uv2;
uv[2] = uv3;
uv[3] = uv4;

//給mesh賦值//
mesh.vertices = vertices;
mesh.triangles = triangles;
mesh.colors = colors;
mesh.uv = uv;

}
}

Shader代碼

Shader "Unlit/Transparent Sprite"
{
Properties
{
_MainTex ("Base (RGB), Alpha (A)", 2D) = "black" {}
}

SubShader
{
LOD 200

Tags
{
"Queue" = "Transparent"
"IgnoreProjector" = "True"
"RenderType" = "Transparent"
}

Pass
{
Cull Off
Lighting Off
ZWrite Off
Fog { Mode Off }
Offset -1, -1
Blend SrcAlpha OneMinusSrcAlpha

CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"

sampler2D _MainTex;
float4 _MainTex_ST;

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

struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 uv : TEXCOORD0;
};

v2f o;

v2f vert (appdata_t v)
{
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.color = v.color;
// 給輪廓的邊緣點賦值好ID,就可以算出每個具體像素的ID
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}

fixed4 frag (v2f IN) : COLOR
{
// tex2D 就是拿id去查詢數據表的函數
fixed4 color = tex2D(_MainTex, IN.uv) * IN.color;
return color;
}
ENDCG
}
}

//備胎設為Unity自帶的普通漫反射
Fallback" Diffuse "
}

最後運行的截圖

&" data-caption="" data-size="normal" dw="897" dh="586" class="origin_image zh-lightbox-thumb lazy" w="897" data-original="https://pic1.zhimg.com/v2-b4e4e4f8ff97a38f355fab7dcdb23121_r.jpg" data-actualsrc="//i1.wp.com/pic1.zhimg.com/50/v2-b4e4e4f8ff97a38f355fab7dcdb23121_hd.jpg">

github工程地址

ElPsyCongree/Emoji


模型每個頂點也有個uv坐標,會和圖片上的uv對應。在光柵化的三角形遍歷階段會根據頂點的uv插值出每個像素的uv,最後在片元著色階段每個像素的uv對應圖片的uv,從而得出每個像素的顏色,最終很多像素在一起就變成了一張圖片。


推薦閱讀:

【插畫】請教各位插畫師大神,如圖的臟臟紋理是怎麼做的?

TAG:Unity遊戲引擎 | 計算機圖形學 | 紋理 | 虛幻引擎 | shader |