您當前的位置:首頁 > 動漫

Unity仿Splatoon噴漆效果(一)

作者:由 冰心夜雨 發表于 動漫時間:2018-08-29

前言

專欄也開通了一個多月了,從業也好幾年了,從來也不知道寫點什麼。最近寫了幾篇文章,感覺寫文章有了一點心得,所以趁著在狀態再寫幾篇文章。

在上家公司的時候,16年開始轉型unity手遊開發,整個公司都沒有什麼經驗,當時splatoon大火,領導決定做個手機版本的splatoon, 領導把splatoon噴漆效果的實現交給我實現。

折騰出了splatoon的噴漆效果。專案做了幾個月就停了,後來團隊各奔東西。2年多了,想把以前做的噴漆效果分享出來。

這個不是好的解決方案,分享出來也就是想有專業的圖形程式設計師能給點改進的意見。

下面是這個Demo的影片

Unity仿Splatoon噴漆效果(一)

https://www。zhihu。com/video/1017888543019925504

Unity實現

(1)記錄碰撞資訊

當主角在場景中移動的時候,如果發射子彈,就從發射點向目標點發射一條射線,如果這條射線擊中地面,就把這個碰撞資訊記錄下來。

這個Demo中定義了一個叫HitObj的結構體資訊

public

class

HitObj

{

public

GameObject

obj

public

Vector2

texcoord

public

Color

color

public

int

coltype

public

string

decalname

=

“Gun_H100”

public

float

decalwidth

=

1。0f

public

float

decalheight

=

1。0f

public

float

decalrot

=

0。0f

};

當按住鍵盤空格鍵Space鍵的時候,會發射一條射線,射線擊中地面,就把這個碰撞資訊加入到陣列列表中。

public void CreatePaint()

{

int coltype = mUseTeam0 ? (int)xEnumDefine。TeamFlag。Team_0 : (int)xEnumDefine。TeamFlag。Team_1;

Color color = mUseTeam0 ? CustomColorData_0。Color_Team_0 : CustomColorData_0。Color_Team_1;

RaycastHit rayhit;

Ray ray = new Ray(mGunPos。position, -Vector3。up);

if (Physics。Raycast(ray, out rayhit, 10。0f))

{

CameraRender。HitObj obj = new CameraRender。HitObj();

obj。obj = rayhit。collider。gameObject;

obj。texcoord = rayhit。textureCoord2;

obj。coltype = coltype;

obj。color = color;

obj。decalname = “Gun_H100”;

obj。decalwidth = 2。5f;

obj。decalheight = 2。5f;

obj。decalrot = 0。0f;

CameraRender。Instance。M_StaticObjArray。Add(obj);

}

}

對於結構體的資訊

texcoord是擊中點的二次uv座標

顏色型別是程式碼中定義的,定義了兩種, 代表兩個陣營。

public enum TeamFlag

{

Invalid = -1,

Team_0 = 0,

Team_1 = 1,

}

顏色也對應了有兩種。

public class CustomColorData_0

{

public static Color Color_Team_0 = new Color(93 / 255。0f, 25 / 255。0f, 231 / 255。0f);

public static Color Color_Team_1 = new Color(233 / 255。0f, 106 / 255。0f, 173 / 255。0f);

}

decalname 就是使用的噴射的貼圖的名字。這邊測試用了一個“Gun_H100”

decalwidth和decalheight定義的是噴射的貼花尺寸

(2)攝像機後處理

對於程式碼中的CameraRender類,他應該掛在Camera元件下面,如圖所示。

Unity仿Splatoon噴漆效果(一)

只有掛載在Camera元件下,Unity的

OnPostRender

函式才會呼叫

OnPostRender

就是在攝像機完成渲染的時候呼叫,如圖所示

Unity仿Splatoon噴漆效果(一)

在OnPostRender函式中,對於收集的碰撞資訊,如果這個碰撞物體掛載了StaticObj_Render指令碼,則呼叫StaticObj_Render的Render函式渲染貼花。

然後再把陣列清空

void OnPostRender()

{

for (int i = 0; i < mStaticObjArray。Count; i++ )

{

HitObj obj = mStaticObjArray[i];

if (obj == null)

continue;

StaticObj_Render render = obj。obj。GetComponent();

if (render == null)

continue;

render。Render(obj。texcoord, mQuadMaterial, obj。color, obj。coltype, obj。decalname, obj。decalwidth, obj。decalheight, obj。decalrot);

}

mStaticObjArray。Clear();

}

(3)掛載StaticObj_Render的物體渲染

對於場景中可渲染物體的說明

對於當前的Demo場景,美術在製作的時候分成了三部分。如圖所示

Unity仿Splatoon噴漆效果(一)

分別為不可渲染的外圍物件(外圍的修飾場景), 不可渲染的物件(主場景內的物件),可渲染的物件

對於可渲染的物件,如下圖所示, 每一個可渲染GameObject都掛載了StaticObj_Render指令碼

Unity仿Splatoon噴漆效果(一)

對於這些可渲染的GameObject, 美術在3dmax中製作的時候是需要按照一定的要求

斬二次uv

的。

StaticObj_Render的渲染

對於每一個StaticObj_Render都會建立一個RenderTexture

private int mWidth = 128;

private int mHeight = 128;

if (mDecalTex == null)

{

mWidth = PaintResourceManager。Instance。mDecalWidth;

mHeight = PaintResourceManager。Instance。mDecalHeight;

mDecalTex = new RenderTexture(mWidth, mHeight, 0);

mDecalTex。Create();

}

看看StaticObj_Render的Render函式

首先看看第一段, 當這個物件第一次被噴射的時候,替換shader, 然後設定貼花的法線圖,

環境貼圖。

// 使用新材質 。。。

if (!mNewMat)

{

for (int i = 0; i < mMats。Length; i++ )

{

Material mat = mMats[i];

if (mat == null)

continue;

SetMaterial(mat);

mat。SetTexture(“_DecalTex”, mDecalTex);

Texture _Normal = PaintResourceManager。Instance。GetDecalBumpTex(“T_Detail_Rocky_N”);

mat。SetTexture(“_DecalBump”, _Normal);

Cubemap cube = PaintResourceManager。Instance。GetCubemap(“SkyboxCube”);

mat。SetTexture(“_DecalSky”, cube);

}

mNewMat = true;

}

void SetMaterial(Material mat)

{

mat。shader = Shader。Find(“SpraySoldier/Mobile/StaticObjDecal”);

}

然後是下面一段,把當前的硬體的RenderTarget設定為這個Obj建立的RenderTexture, 這樣下面所有的DrawCall都會畫到這張RenderTexture上

Graphics。SetRenderTarget(mDecalTex);

再接下來一段, 這個函式也只調用一次,就是用GL函式畫一次全屏RT, 這個RT設定為白色。

(GL是unity提供的底層介面,用來繪製基礎圖元。)

void OnceRenderGlobalScreenRT(Material quadmat)

{

if (mOnceRenderGScnRT)

return;

quadmat。SetPass(1);

GL。PushMatrix();

GL。LoadOrtho();

GL。Begin(GL。QUADS);

GL。Vertex3(0, 0, 0);

GL。Vertex3(0, 1, 0);

GL。Vertex3(1, 1, 0);

GL。Vertex3(1, 0, 0);

GL。End();

GL。PopMatrix();

mOnceRenderGScnRT = true;

}

下一步就是主要的繪製函數了。透過傳入HitObj的資料,設定到材質中,然後用GL函式繪製出來。

PaintResourceManager。DecalInfo info = PaintResourceManager。Instance。GetDecalTex(decalname);

if (info。texture == null)

return;

quadmat。SetTexture(“_MainTex”, info。texture);

quadmat。SetColor(“_Color”, quadcolor);

quadmat。SetFloat(“_Rotation”, decalrot);

quadmat。SetVector(“_CenterPos”, texcoord);

float width = decalwidht / 20。0f;

float height = decalheight / 20。0f;

int decalRow = info。row;

int decalCol = info。column;

int randomx = Random。Range(0, decalRow - 1);

int randomy = Random。Range(0, decalCol - 1);

float unitx = (1。0f / decalRow);

float unity = (1。0f / decalCol);

float startuvx = unitx * randomx;

float startuvy = unity * randomy;

quadmat。SetPass(0);

GL。PushMatrix();

GL。LoadOrtho();

GL。Begin(GL。QUADS);

if (mUV)

GL。TexCoord2(startuvx, startuvy);

else

GL。TexCoord2(0, 0);

GL。Vertex3(texcoord。x - width, texcoord。y - height, 0);

if (mUV)

GL。TexCoord2(startuvx, startuvy + unity);

else

GL。TexCoord2(0, 1);

GL。Vertex3(texcoord。x - width, texcoord。y + height, 0);

if (mUV)

GL。TexCoord2(startuvx + unitx, startuvy + unity);

else

GL。TexCoord2(1, 1);

GL。Vertex3(texcoord。x + width, texcoord。y + height, 0);

if (mUV)

GL。TexCoord2(startuvx + unitx, startuvy);

else

GL。TexCoord2(1, 0);

GL。Vertex3(texcoord。x + width, texcoord。y - height, 0);

GL。End();

GL。PopMatrix();

上圖注意到,對於傳入的貼花貼圖,是否序列圖。如果是序列圖的話,會從圖中隨機取一塊影象。這樣做的好處是噴射貼花的形狀是變化的。(如下圖所示。)

Unity仿Splatoon噴漆效果(一)

再下步就是恢復攝像機的RenderTarget

Graphics。SetRenderTarget(Camera。main。targetTexture);

最後就是會把噴射資料寫入到一個數組中,這樣可以在邏輯判斷的時候,就可以透過資料,而不是透過影象判斷腳下的這個點是否在貼圖中(就如Splatoon一樣,是否需要扣血,是否可以潛行。)

public int mSimuBufWidth = 200; // 模擬rendertexture的畫素資訊

public int mSimuBufHeight = 200; // 模擬rendertexture的畫素資訊

private List mSimuBuf = new List();

private void WriteSimuBufData(float decalwid, float decalhei, Vector2 decaluv, int coltype)

{

int centerx = (int)(decaluv。x * mSimuBufWidth);

int centery = (int)(decaluv。y * mSimuBufHeight);

int width = (int)(decalwid * mSimuBufWidth);

int height = (int)(decalhei * mSimuBufHeight);

for (int j = (centery - height); j < (centery + height); j++)

{

for(int i = (centerx - width); i < (centerx + width); i++)

{

if (i < 0)

continue;

if (i > (mSimuBufWidth - 1))

continue;

if (j < 0)

continue;

if (j > (mSimuBufHeight - 1))

continue;

int pos = ((mSimuBufHeight - 1) - j) * mSimuBufWidth + i;

if (pos > (mSimuBufWidth * mSimuBufHeight - 1))

continue;

mSimuBuf[pos] = coltype;

}

}

(4)貼圖shader的實現

對於視屏中貼花表面的效果的實現,這裡我想說一句,當初的實現是比較暴力的。當初是美術在Shader Forge中調出這樣的效果,然後透過生成的程式碼,把實現效果部份的程式碼,複製到貼花shader中,使貼花也有相應的效果。

// diffuse :

loat

NdotL

=

dot

normalDirection

lightDirection

);

float3

diffuse

=

max

0。0

NdotL

*

attenColor

+

UNITY_LIGHTMODEL_AMBIENT

rgb

// gloss :

float

gloss

=

_DecalGloss

float

specPow

=

exp2

gloss

*

10。0

+

1。0

);

// specular :

NdotL

=

max

0。0

NdotL

);

float4

node_74

=

texCUBE

_DecalSky

viewReflectDirection

);

float3

specularColor

=

float3

_DecalSpecular

_DecalSpecular

_DecalSpecular

);

float3

specularAmb

=

node_74

rgb

*

node_74

a

*

_DecalSpecAmbient

*

specularColor

看過程式碼會發現,最初的物件Shader和替換後的噴漆Shader, 都是用頂點/片源著色器寫的。當時想用Surface Shader來寫的,設想Surface Shader只要實現一個顏色函式就可以了。但是發現美術除錯的效果,沒法很好的整合進Surface Shader中(可能是理解的不夠好。), 所以為了有lightmap, 和燈光效果,重寫了內建Shader(Mobile/Diffuse和Mobile/Bumped Diffuse), 使用自定義的頂點/片源著色器,就可以整合進Shader Forge的效果了。

(5)關於二次uv

開啟3dmax, 找到渲染物件區域。

Unity仿Splatoon噴漆效果(一)

可以看到,一個整塊的地板被切割成很多小塊。這樣做是為了斬二次uv的需要,定義了一個基礎的尺寸,例如圖中地形塊的一部分,那麼大的模型他的二次uv是全斬的。以這個模型大小為基準來斬其他模型的二次uv。

專案github地址

https://

github。com/xieliujian/U

nityDemo_Splatoon2

關於這個實現的幾點不足

(1)邊緣效果處理不好。

如下圖所示, 在把Decal影象畫到RenderTexture上的時候,沒有處理好邊緣顏色的溢位現象。

Unity仿Splatoon噴漆效果(一)

(2)每一個噴射會有切邊,在物件於物件的交界處。

如圖所示,理論上應該是一個完整的形狀,(透過查詢邊緣的塊,這個應該可以在邏輯層解決這個問題。)

Unity仿Splatoon噴漆效果(一)

(3)RenderTexture使用太多了。

如下圖所示,有多少個可渲染物件,就要建立個多少個RenderTexture

Unity仿Splatoon噴漆效果(一)

展望

收藏了一篇關於splatoon的文章,裡面有提到splatoon的技術實現,還有Unity商城有牛B的外掛。

破曉:《Splatoon 2》繪製效果的簡單實現

可能有時間研究的話,希望再深入研究下Splatoon噴漆實現。

標簽: GL  int  obj  0f  texCoord