欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Unity 實(shí)現(xiàn)貼花效果的制作教程

 更新時(shí)間:2021年11月23日 09:35:58   作者:心之凌兒  
有些游戲中的戰(zhàn)斗痕跡的效果會(huì)通過貼花來實(shí)現(xiàn)的,貼花的方式多種多樣。而在Unity中,有一種給官方文檔提供代碼的解決方案。本文將這些代碼的基礎(chǔ)上做一個(gè)繪圖的貼花效果,感興趣的童鞋可以參考一下

一、前言

在云艾爾登法環(huán)時(shí),看到地面上的血跡時(shí),發(fā)現(xiàn)某些地方脫離的地面,似乎是通過面片的方式實(shí)現(xiàn)的效果。但是同時(shí)某些,不過這種類型的血跡有道具的效果,估計(jì)是為了實(shí)現(xiàn)碰撞檢測(cè)的功能才選擇了面片的方式

而其他的戰(zhàn)斗痕跡的效果似乎是通過貼花來實(shí)現(xiàn)的,貼花的方式多種多樣。而在Unity中,有一種給官方文檔提供代碼的解決方案。這里就在這些代碼的基礎(chǔ)上做一個(gè)繪圖的貼花效果,最終效果如圖所示:

二、實(shí)現(xiàn)方式介紹

簡(jiǎn)單的來說就是通過發(fā)射一條射線與物體發(fā)生碰撞來獲取物體的基本的信息,然后提取出碰撞處該物體的UV坐標(biāo)點(diǎn),然后進(jìn)行一個(gè)計(jì)算得到物體對(duì)應(yīng)Texture2D的像素信息,然后對(duì)這些像素進(jìn)行一個(gè)顏色的替換,最后就可以得到一張貼花效果的Texture2D

這種方式的第一步就是需要通過發(fā)射一條射線,然后得到碰撞檢測(cè)點(diǎn)的信息,這里用到的API為:

使用該API的返回結(jié)果是物體網(wǎng)格對(duì)應(yīng)的UV坐標(biāo)點(diǎn),沒有辦法直接的去使用,需要先通過坐標(biāo)轉(zhuǎn)換,即通過UV坐標(biāo)來獲取到其Texture2D對(duì)應(yīng)的像素點(diǎn)。在Unity中,我們知道UV坐標(biāo)對(duì)應(yīng)的范圍為0到1,這樣來說,只要將其與對(duì)應(yīng)Texture2D的像素?cái)?shù)量與UV坐標(biāo)進(jìn)行一個(gè)乘法計(jì)算就可以得到最后對(duì)應(yīng)像素的下標(biāo)位置

在得到檢測(cè)位置的像素下表后,就可以根據(jù)被貼圖的Texture2D的像素的寬高做一個(gè)計(jì)算,得到物體貼圖的替換范圍與下標(biāo),然后執(zhí)行一遍遍歷,對(duì)于所有替換的像素顏色一一對(duì)應(yīng),然后執(zhí)行一個(gè)像素顏色的計(jì)算,做一個(gè)混合即可

三、實(shí)現(xiàn)過程

檢測(cè)UV位置并替換像素顏色:

首先查閱Unity官方文檔,得到射線檢測(cè)UV坐標(biāo)的代碼,核心圍繞RaycastHit對(duì)應(yīng)的API來得到檢測(cè)的UV坐標(biāo)并進(jìn)行處理,代碼如下:

public class ExampleClass : MonoBehaviour
{
    public Camera cam;

    void Start()
    {
        cam = GetComponent<Camera>();
    }

    void Update()
    {
        if (!Input.GetMouseButton(0))
            return;

        RaycastHit hit;
        if (!Physics.Raycast(cam.ScreenPointToRay(Input.mousePosition), out hit))
            return;

        Renderer rend = hit.transform.GetComponent<Renderer>();
        MeshCollider meshCollider = hit.collider as MeshCollider;

        if (rend == null || rend.sharedMaterial == null || rend.sharedMaterial.mainTexture == null || meshCollider == null)
            return;

        Texture2D tex = rend.material.mainTexture as Texture2D;
        Vector2 pixelUV = hit.textureCoord;
        pixelUV.x *= tex.width;
        pixelUV.y *= tex.height;

        tex.SetPixel((int)pixelUV.x, (int)pixelUV.y, Color.black);
        tex.Apply();
    }
}

然后在場(chǎng)景中創(chuàng)建一個(gè)Quad作為射線被檢測(cè)的物體,但是同時(shí)需要注意,對(duì)于物體執(zhí)行操作時(shí),需要理解一個(gè)細(xì)節(jié),就是物體只有在掛載網(wǎng)格碰撞體時(shí)候,才能夠獲取到對(duì)應(yīng)物體的UV信息,具體的細(xì)節(jié)在官方文檔中也有提到,如圖:

創(chuàng)建完成物體后,需要通過一個(gè)材質(zhì)來賦予該物體一張貼圖,用來作為像素替換的貼圖,我這里用了一張白色的圖片,但是注意,在使用該圖片時(shí)候,注意修改該圖片的導(dǎo)入設(shè)置中的Read/Write Enabled為開啟狀態(tài),這樣才可以進(jìn)行后續(xù)的修改:

如果你測(cè)試這段代碼,可能發(fā)現(xiàn)在點(diǎn)擊后并沒有發(fā)生什么變化,因?yàn)檫@一段代碼只會(huì)對(duì)一個(gè)像素點(diǎn)執(zhí)行替換操作,運(yùn)行效果看起來并不明顯。為了提升顯示效果,這里可以先做一個(gè)簡(jiǎn)單的計(jì)算,來設(shè)計(jì)一個(gè)像素塊作為替換的基本單元,以便于結(jié)果的觀察。而計(jì)算方式為通過這個(gè)像素點(diǎn)的下表位置來計(jì)算出一個(gè)大小合適的方格區(qū)域,定義一個(gè)Vector2的屬性,命名為replaceRange,然后修改像素替換區(qū)域的代碼:

 	for (int i = 0; i < replaceRange.x; i++)
        {
            for (int j = 0; j < replaceRange.y; j++)
            {
                tex.SetPixel((int)pixelUV.x+i- (int)replaceRange.x/2, (int)pixelUV.y+j-(int)replaceRange.y/2, Color.black);
            }

        }

然后運(yùn)行整個(gè)場(chǎng)景,如果腳本執(zhí)行成功的話,就可以看到正確的顯示效果:

修改替換信息為圖片信息:

上面我們對(duì)于每一個(gè)像素的顏色值進(jìn)行替換時(shí),使用的是指定的顏色數(shù)字。接下來就需要進(jìn)行一定的擴(kuò)展,將信息的提取方式修改為圖片提取的方式。

同樣定義一個(gè)Texture2D屬性命名為:coverTex,然后提取這張Texture2D的信息,并覆蓋掉對(duì)應(yīng)點(diǎn)擊點(diǎn)的像素信息,這里定義一個(gè)Draw方法來單獨(dú)的處理這件事情:

	public void Draw(Texture2D orginTex,Texture2D coverTex,Vector2 pixelUV)
    {
        for (int i = 0; i < coverTex.width; i++)
        {
            for (int j = 0; j < coverTex.height; j++)
            {
                Color colorOriginal = orginTex.GetPixel((int)pixelUV.x + i - (int)coverTex.width / 2, (int)pixelUV.y + j - (int)coverTex.height / 2);
                Color colorCover = coverTex.GetPixel(i, j);
                Color colorResult = colorCover * colorCover.a + (1 - colorCover.a) * colorOriginal;
                orginTex.SetPixel((int)pixelUV.x + i - (int)coverTex.width / 2, (int)pixelUV.y + j - (int)coverTex.height / 2, colorResult);
                
            }
        }
    }

注意,在上面的代碼中,我們對(duì)于兩個(gè)顏色值有一個(gè)簡(jiǎn)單的計(jì)算用來混合兩個(gè)有透明通道的顏色值。假設(shè)顏色A要覆蓋掉顏色B,這里使用的計(jì)算公式為:

A*A.a+(1-A.a)*B

通過上面的公式,可以簡(jiǎn)單的處理兩個(gè)顏色的a通道的覆蓋結(jié)果,也許這種方式不是很準(zhǔn)確,但是對(duì)于完全透明的像素或者完全不透明的像素的混合還是比較有效的,這樣就很方便 的處理不規(guī)則形狀的貼花

將上面的顏色快的方式替換掉,可以觀察一下效果:

當(dāng)我們使用到一張圓形的貼圖后,我們就可以看到成功的執(zhí)行了替換

運(yùn)行時(shí)使用復(fù)制貼圖:

如果我們直接使用本體的貼圖來修改材質(zhì),就會(huì)發(fā)現(xiàn)本地的資源也被執(zhí)行了修改,這樣會(huì)造成下次進(jìn)入游戲,整個(gè)貼圖的狀態(tài)也不會(huì)刷新。為了避免這個(gè)問題,可以在每次執(zhí)行像素替換時(shí),復(fù)制一份貼圖來作為被貼畫的材質(zhì)貼圖,不過這里就不進(jìn)行演示,可以在自己的項(xiàng)目中,根據(jù)需要來決定是否執(zhí)行該操作

修改幀檢測(cè)斷觸問題:

上面的一個(gè)代碼,有一個(gè)特點(diǎn),就是可以通過連續(xù)的繪制來做出一些圖案,有一些類似于江南白景圖游戲中抽卡前的繪制效果,但是通過上面的代碼來實(shí)現(xiàn)時(shí),就會(huì)發(fā)現(xiàn)如果鼠標(biāo)移動(dòng)的過快,相鄰的兩個(gè)繪制點(diǎn)之間會(huì)產(chǎn)生空隙,如圖所示:

為了解決這樣一個(gè)問題,這里在每一幀執(zhí)行繪制之后都緩存本幀的UV坐標(biāo),同時(shí)在繪制時(shí)與上一幀的UV坐標(biāo)進(jìn)行距離對(duì)比,如果超出一定的距離。就在中間執(zhí)行插值的操作

同時(shí)為了保證性能,需要固定距離的執(zhí)行插值操作,為了簡(jiǎn)化計(jì)算,將兩幀坐標(biāo)的距離分為X與Y方向分別進(jìn)行判斷,同時(shí)為了保證斜率,得到最大偏差的方向進(jìn)行等距的插值,具體的邏輯代碼為:

		if (!isClick)
        {
            Draw(tex, coverTex, pixelUV);
            catchPos = pixelUV;
            isClick = true;
        }
        else
        {

            if (Vector2.Distance(pixelUV, catchPos) < coverTex.width / 4)
            {
                Draw(tex, coverTex, pixelUV);
            }
            else
            {
                Vector2 pixelCatchUV = catchPos;
                float lerpNum=0;
                float interval = 1 / (Mathf.Max(Mathf.Abs(pixelUV.x - pixelCatchUV.x), Mathf.Abs(pixelUV.y - pixelCatchUV.y)) / (coverTex.width/4));
                while (lerpNum<=1)
                {
                    lerpNum += interval;
                    catchPos = Vector2.Lerp(pixelCatchUV, pixelUV, InterpolationCalculation(lerpNum));
                    Draw(tex, coverTex, catchPos);
                }
                catchPos = pixelUV;
                Draw(tex, coverTex, catchPos);
            }
        }

執(zhí)行代碼的顯示結(jié)果為:

總結(jié)

從實(shí)現(xiàn)過程中面臨的一些問題來看,這種貼畫效果的實(shí)現(xiàn)限制條件很多,性能表現(xiàn)上也是比較差的,適合做一些局部的貼畫效果實(shí)現(xiàn),比如百景圖的抽卡繪制的效果

而若想實(shí)現(xiàn)全局的效果,在UV平鋪方面與貼圖的緩存方面都有很大的挑戰(zhàn),還是建議嘗試一下其他方式,最后,貼上完整的代碼:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Test : MonoBehaviour
{
    public Camera cam;
    public Texture2D coverTex;

    private Texture2D catchTexture;
    private Vector2 catchPos;
    private bool isFirst=true;
    private bool isClick = false;

    void Awake()
    {
        Application.targetFrameRate = 200;
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            isFirst = true;
        }
        DrawSticker();

    }
    

    public void DrawSticker()
    {
        if (!Input.GetMouseButton(0))
        {
            isClick = false;
            return;
        }            
        RaycastHit hit;
        if (!Physics.Raycast(cam.ScreenPointToRay(Input.mousePosition), out hit))
            return;
        Renderer rend = hit.transform.GetComponent<Renderer>();
        MeshCollider meshCollider = hit.collider as MeshCollider;
        if (rend == null || rend.sharedMaterial == null || rend.sharedMaterial.mainTexture == null || meshCollider == null)
            return;
        if (isFirst)
        {
            if (catchTexture == null)
            {
                catchTexture = rend.material.mainTexture as Texture2D;
            }
            rend.material.mainTexture = Instantiate(catchTexture);
            isFirst = false;
        }
        Texture2D tex = rend.material.mainTexture as Texture2D;
        Vector2 pixelUV = hit.textureCoord;
        pixelUV.x *= tex.width;
        pixelUV.y *= tex.height;
        if (!isClick)
        {
            Draw(tex, coverTex, pixelUV);
            catchPos = pixelUV;
            isClick = true;
        }
        else
        {
            if (Vector2.Distance(pixelUV, catchPos) < coverTex.width / 4)
            {
                Draw(tex, coverTex, pixelUV);
            }
            else
            {
                Vector2 pixelCatchUV = catchPos;
                float lerpNum=0;
                float interval = 1 / (Mathf.Max(Mathf.Abs(pixelUV.x - pixelCatchUV.x), Mathf.Abs(pixelUV.y - pixelCatchUV.y)) / (coverTex.width/4));
                while (lerpNum<=1)
                {
                    lerpNum += interval;
                    catchPos = Vector2.Lerp(pixelCatchUV, pixelUV, InterpolationCalculation(lerpNum));
                    Draw(tex, coverTex, catchPos);
                }
                catchPos = pixelUV;
                Draw(tex, coverTex, catchPos);
            }
        }
        tex.Apply();
    }

    float InterpolationCalculation(float num)
    {
        return 3 * Mathf.Pow(num, 2) - 2 * Mathf.Pow(num, 3);
    }

    public void Draw(Texture2D orginTex,Texture2D coverTex,Vector2 pixelUV)
    {
        for (int i = 0; i < coverTex.width; i++)
        {
            for (int j = 0; j < coverTex.height; j++)
            {
                Color colorOriginal = orginTex.GetPixel((int)pixelUV.x + i - (int)coverTex.width / 2, (int)pixelUV.y + j - (int)coverTex.height / 2);
                Color colorCover = coverTex.GetPixel(i, j);
                Color colorResult = colorCover * colorCover.a + (1 - colorCover.a) * colorOriginal;

                orginTex.SetPixel((int)pixelUV.x + i - (int)coverTex.width / 2, (int)pixelUV.y + j - (int)coverTex.height / 2, colorResult);
                
            }
        }
    }

}
 

以上就是Unity 實(shí)現(xiàn)貼花效果的制作教程的詳細(xì)內(nèi)容,更多關(guān)于Unity的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論