Catlike學(xué)習(xí)筆記(1.3)-使用Unity畫(huà)更復(fù)雜的3-創(chuàng)新互聯(lián)

第三篇來(lái)了~今天去參加了 Unite 2018 Berlin,感覺(jué)就是。。。。非常困。。?;貋?lái)以后稍微睡了下清醒了覺(jué)得是時(shí)候認(rèn)真學(xué)習(xí)下了,不過(guò)講的很多東西都是還沒(méi)有發(fā)布或者只有 Preview 的版本,按照 Unity 的習(xí)慣肯定 Bug 多到令人發(fā)指,最近不太想折騰所以就先繼續(xù)寫文章把。。按照慣例奉上『原文鏈接』

成都創(chuàng)新互聯(lián)公司秉承實(shí)現(xiàn)全網(wǎng)價(jià)值營(yíng)銷的理念,以專業(yè)定制企業(yè)官網(wǎng),成都網(wǎng)站建設(shè)、網(wǎng)站建設(shè),成都小程序開(kāi)發(fā),網(wǎng)頁(yè)設(shè)計(jì)制作,成都手機(jī)網(wǎng)站制作,成都全網(wǎng)營(yíng)銷幫助傳統(tǒng)企業(yè)實(shí)現(xiàn)“互聯(lián)網(wǎng)+”轉(zhuǎn)型升級(jí)專業(yè)定制企業(yè)官網(wǎng),公司注重人才、技術(shù)和管理,匯聚了一批優(yōu)秀的互聯(lián)網(wǎng)技術(shù)人才,對(duì)客戶都以感恩的心態(tài)奉獻(xiàn)自己的專業(yè)和所長(zhǎng)。

PART 1 概述

首先大概介紹一下什么是『Catlike教程』,大家自行訪問(wèn)一下就會(huì)發(fā)現(xiàn)是這位『大神』寫的一個(gè) Unity 系列教程,里面由淺至深的以一個(gè)個(gè)有趣的小課題來(lái)引導(dǎo)大家學(xué)習(xí) Unity 的方方面面~回想自己畢業(yè)三年都在做 Unity 游戲開(kāi)發(fā),然而看了大神的教程以后發(fā)現(xiàn)自己欠缺的東西非常多~真正對(duì)引擎的掌握程度非常低只是在不停的拼 UI 寫業(yè)務(wù)邏輯。做這個(gè)系列呢也是希望自己可以堅(jiān)持把大神的教程學(xué)完讓自己變得更厲害~就醬。。

那么言歸正傳我們本期節(jié)目的最終目標(biāo)是實(shí)現(xiàn)作者配圖中的看起來(lái)很屌的圖形,像是這樣的。。。

Catlike學(xué)習(xí)筆記(1.3)-使用Unity畫(huà)更復(fù)雜的3

對(duì)比上一篇文章的函數(shù)圖像,大概有以下幾個(gè)關(guān)鍵點(diǎn)需要實(shí)現(xiàn)。

  • 支持多函數(shù)疊加
  • 從一條曲線變成一個(gè)曲面
  • 由曲面擴(kuò)展成真正的三維圖形

PART 2 支持多函數(shù)疊加

首先我們的目標(biāo)是可以通過(guò)一個(gè)滑桿來(lái)控制「上一篇」中的曲線顯示的函數(shù),因此先復(fù)制之前的代碼改改名字比如 Graph4DController.cs 再修改類名與文件名一致。然后我們的關(guān)鍵是需要修改這一行

var pos = new Vector3(x, Calc(x), 0);

使其變成根據(jù)滑桿中的 int 值選擇 delegate 中的某個(gè)函數(shù),如下所示,代碼中主要修改的地方用注釋稍微解釋了下。

// 新的 deleagate
public delegate float Function(float x, float t);

// 記得修改類名與文件名一致否則不能掛在 gameobject 上
public class Graph4DController : MonoBehaviour
{
    [Range(10, 100), SerializeField] private int _resolution;
    [SerializeField] private GameObject _cube;
    // 添加新的滑桿
    [Range(0, 1), SerializeField] private int _function;
    // 一個(gè) delegate 數(shù)組用于保存我們接下來(lái)使用的兩個(gè)函數(shù)
    private Function[] _functions;

    ...

    // Use this for initialization
    private void Start()
    {
        // 初始化 _functions 
        _functions = new Function[] {SineFunction, MultiSineFunction};      
        ...
    }

    private void Update()
    {
        _startX = -1f;
        for (int i = 0; i < _resolution; i++)
        {
            var x = _startX + i * _step;
            // 此處修改調(diào)用方法
            var pos = new Vector3(x, _functions[_function](x, Time.time), 0);
            var point = _points[i];
            point.transform.localPosition = pos;
        }
    }

    private float SineFunction(float x, float t)
    {
        return Mathf.Sin(Mathf.PI * (x + t));
    }

    private float MultiSineFunction(float x, float t)
    {
        float y = Mathf.Sin(Mathf.PI * (x + t));
        y += Mathf.Sin(2f * Mathf.PI * (x + 2f * t)) / 2f;
        y *= 2f / 3f;
        return y;
    }
}

于是我們實(shí)現(xiàn)了如下的效果~

Catlike學(xué)習(xí)筆記(1.3)-使用Unity畫(huà)更復(fù)雜的3

不過(guò)作者在原文中還添加了 Enum 然后可以不用滑桿而是改用一個(gè)下拉菜單來(lái)改變要顯示的函數(shù)圖像。最終效果沒(méi)什么不同就不再贅述了感興趣的同學(xué)可以自行找到『原文鏈接』查看更詳細(xì)的步驟~

PART 3 畫(huà)出水滴的波紋

那么接下來(lái)開(kāi)始要真正的繪制一個(gè)3D曲面了~那么首先是創(chuàng)建更多的小方塊~我們?cè)诔跏蓟牡胤礁某梢粋€(gè)二維的 List 來(lái)保存所有的小方塊

private void Start()
{
    ...
    for (int i = 0; i < _resolution; i++)
    {
        _points.Add(new List<Transform>());
        for (int j = 0; j < _resolution; j++)
        {
            var point = Instantiate(_cube, transform);
            _points[i].Add(point.transform);
            point.transform.localScale = scale;
            point.SetActive(true);
        }
    }
}

在后續(xù)的遍歷也對(duì)該二維數(shù)組進(jìn)行遍歷。

private void Update()
{
    for (int i = 0; i < _points.Count; i++)
    {
        for (int j = 0; j < _points[i].Count; j++)
        {
            var posX = i * _step - 1;
            var posZ = j * _step - 1;
            var pos = new Vector3(posX, _functions[(int) _function](posX, posZ, Time.time), posZ);
            var point = _points[i][j];
            point.localPosition = pos;
        }
    }
}

最后再稍微修改下兩個(gè)函數(shù)的參數(shù)就完成了從 2D 到 3D 的跳躍~如圖所示

Catlike學(xué)習(xí)筆記(1.3)-使用Unity畫(huà)更復(fù)雜的3

不過(guò)我們并不應(yīng)該滿足于此,感覺(jué)這樣其實(shí)并沒(méi)有充分利用 Z 軸啊,完全就是復(fù)制了很多條曲線排在一起。所以我們新建兩個(gè)這樣的函數(shù)。

private float Sine2DFunction(float x, float z, float t)
{
    float y = Mathf.Sin(Mathf.PI * (x + t));
    y += Mathf.Sin(Mathf.PI * (z + t));
    y *= 0.5f;
    return y;
}

private float MultiSine2DFunction(float x, float z, float t)
{
    float y = 4f * Mathf.Sin(Mathf.PI * (x + z + t * 0.5f));
    y += Mathf.Sin(Mathf.PI * (x + t));
    y += Mathf.Sin(2f * Mathf.PI * (z + 2f * t)) * 0.5f;
    y *= 1f / 5.5f;
    return y;
}

那么Sine2DFunction可以很明顯的看出是兩個(gè)完全一樣的正弦波分別沿 x 軸和 Z 軸傳播并且直接疊加,那么第二個(gè)。。。反正很復(fù)雜語(yǔ)言解釋不清楚大概就是 3 個(gè)波疊加起來(lái)的,大家可以一行一行注釋掉看看效果就知道了~

那么如何畫(huà)出一個(gè)波紋呢,首先波紋是由原點(diǎn)也就是(0, 0)點(diǎn)開(kāi)始均勻擴(kuò)散的,那么可能是一個(gè)從原點(diǎn)向周圍擴(kuò)散的正弦波。那么直覺(jué)上來(lái)說(shuō)這個(gè)函數(shù)可能長(zhǎng)這樣。。

private float Ripple (float x, float z, float t) 
{
    float d = Mathf.Sqrt(x * x + z * z);
    float y = Mathf.Sin(Mathf.PI * (d - t));
    return y;
}

運(yùn)行下會(huì)發(fā)現(xiàn)完全不像,主要是因?yàn)樗ㄔ跀U(kuò)散的過(guò)程中是要衰減的,正弦波完全不會(huì),因此我們需要加上衰減的控制。既然是衰減的話顯然距離越大衰減的越多嘍所以我們讓 y 除以 1 + 2 * Mathf.PI * d試一試,之所以加1是為了防止在距離原點(diǎn)過(guò)于近的時(shí)候結(jié)果趨近于無(wú)窮大。所以現(xiàn)在代碼變成了這樣~

private float Ripple(float x, float z, float t)
{
    float d = Mathf.Sqrt(x * x + z * z);
    float y = Mathf.Sin(Mathf.PI * (d - t));
    y = y / (1 + 2 * Mathf.PI * d);
    return y;
}

跑起來(lái)看一下會(huì)發(fā)現(xiàn)。。。emmmm

Catlike學(xué)習(xí)筆記(1.3)-使用Unity畫(huà)更復(fù)雜的3

所以我們?cè)偌由弦恍﹨?shù)比如_velocity傳播速度,frequency水波頻率,_amplitude振幅,_attenuation衰減。代碼如下。(這些參數(shù)并不是數(shù)值越大就直觀意義上越大,雖然這樣不太好但是懶得整理了。。。大家大概意思理解就好)

private float Ripple(float x, float z, float t)
{
    float d = Mathf.Sqrt(x * x + z * z);
    float y = Mathf.Sin(_frequency * Mathf.PI * (d - t / _velocity));
    y *= 1 / (_amplitude + _attenuation * 2 * Mathf.PI * d);
    return y;
}

然后將這些參數(shù)調(diào)整到合適的值,就完成一個(gè)完美的水波了~如圖所示

Catlike學(xué)習(xí)筆記(1.3)-使用Unity畫(huà)更復(fù)雜的3

PART 4 畫(huà)出三維圖形

顯然我們不能滿足于此,傳入 x 和 z 來(lái)計(jì)算出唯一的 y 導(dǎo)致了無(wú)法有兩個(gè)點(diǎn)擁有相同的 x 和 z,這極大的限制了我們的發(fā)揮~比如說(shuō)畫(huà)出一個(gè)球體之類的。所以我們接下來(lái)的目標(biāo)是畫(huà)出真正的三維圖形~

在開(kāi)始之前,我們首先要放棄傳入 x 和 z 來(lái)計(jì)算 y 的設(shè)想,所以應(yīng)該把所有的函數(shù)的返回值改成 Vector3,并且為了區(qū)分我們將函數(shù)的參數(shù)變成 u,v,t。

public delegate Vector3 Function(float u, float v, float t);

public enum GraphFunctionName {
    Sine,
    MultiSine,
    Sine2D,
    MultiSine2D,
    Ripple,
}

public class Graph4DController : MonoBehaviour
{
    [Range(10, 100), SerializeField] private int _resolution;
    [SerializeField] private GameObject _cube;
    [SerializeField] public GraphFunctionName _function;

    [SerializeField] private float _amplitude = 3;
    [SerializeField] private float _frequency = 4;
    [SerializeField] private float _velocity = 2;
    [SerializeField] private float _attenuation = 6;

    private List<List<Transform>> _points;
    private float _step;

    private Function[] _functions;

    // Use this for initialization
    private void Start()
    {
        _functions = new Function[] {SineFunction, MultiSineFunction, Sine2DFunction, MultiSine2DFunction, Ripple};

        _cube.SetActive(false);
        _points = new List<List<Transform>>();
        _step = 2f / _resolution;

        var scale = Vector3.one * _step;

        for (int i = 0; i < _resolution; i++)
        {
            _points.Add(new List<Transform>());
            for (int j = 0; j < _resolution; j++)
            {
                var point = Instantiate(_cube, transform);
                _points[i].Add(point.transform);
                point.transform.localScale = scale;
                point.SetActive(true);
            }
        }

    }

    private void Update()
    {
        for (int i = 0; i < _points.Count; i++)
        {
            for (int j = 0; j < _points[i].Count; j++)
            {
                var u = i * _step - 1;
                var v = j * _step - 1;
                var point = _points[i][j];
                point.localPosition = _functions[(int) _function](u, v, Time.time);
            }
        }
    }

    private Vector3 SineFunction(float u, float v, float t)
    {
        var x = u;
        var y = Mathf.Sin(Mathf.PI * (u + t));
        var z = v;
        return new Vector3(x, y, z);
    }

    private Vector3 MultiSineFunction(float u, float v, float t)
    {
        var x = u;
        float y = Mathf.Sin(Mathf.PI * (u + t));
        y += Mathf.Sin(2f * Mathf.PI * (u + 2f * t)) / 2f;
        y *= 2f / 3f;
        var z = v;
        return new Vector3(x, y, z);
    }

    private Vector3 Sine2DFunction(float u, float v, float t)
    {
        var x = u;
        float y = Mathf.Sin(Mathf.PI * (u + t));
        y += Mathf.Sin(Mathf.PI * (v + t));
        y *= 0.5f;
        var z = v;
        return new Vector3(x, y, z);
    }

    private Vector3 MultiSine2DFunction(float u, float v, float t)
    {
        var x = u;
        float y = 4f * Mathf.Sin(Mathf.PI * (u + v + t * 0.5f));
        y += Mathf.Sin(Mathf.PI * (u + t));
        y += Mathf.Sin(2f * Mathf.PI * (v + 2f * t)) * 0.5f;
        y *= 1f / 5.5f;
        var z = v;
        return new Vector3(x, y, z);
    }

    private Vector3 Ripple(float u, float v, float t)
    {
        var x = u;
        float d = Mathf.Sqrt(u * u + v * v);
        float y = Mathf.Sin(_frequency * Mathf.PI * (d - t / _velocity));
        y *= 1 / (_amplitude + _attenuation * 2 * Mathf.PI * d);
        var z = v;
        return new Vector3(x, y, z);
    }
}

圓柱體

那么如何組成一個(gè)圓柱體呢,首先我們知道圓柱體可以認(rèn)為是由許多個(gè)圓環(huán)組成的,那么如何構(gòu)成一個(gè)圓環(huán)呢?我們知道 u 的取值范圍是[-1, 1],將 u PI 即可獲得 [-PI, PI] 即剛好一個(gè)圓周的弧度,對(duì)應(yīng)的坐標(biāo)即是`(x = sin(PI u), z = cos(PI * u))`,按照以上思路我們完成以下代碼。然后每一個(gè)點(diǎn)的縱座標(biāo) y 就直接取 v 的值即可形成「每個(gè)水平的圓周上有100個(gè)點(diǎn),共100個(gè)圓縱向排列組成的圓柱體」了好吧感覺(jué)表述的不是特別清楚寫出來(lái)跑跑看就知道了。。。

private Vector3 Cylinder(float u, float v, float t)
{
    var x = Mathf.Sin(Mathf.PI * u);
    var y = v;
    var z = Mathf.Cos(Mathf.PI * u);
    return new Vector3(x, y, z);
}

運(yùn)行一下發(fā)現(xiàn)果然是一個(gè)圓柱體,如果想要控制圓柱體的半徑和高直接在 x 和 z 乘以 R,y 乘以 H 即可,如下圖所示。代碼就不貼了大家都會(huì)自己乘~

Catlike學(xué)習(xí)筆記(1.3)-使用Unity畫(huà)更復(fù)雜的3

那么如何讓這個(gè)圓柱體動(dòng)起來(lái)呢~比如說(shuō)隨便對(duì) R 做一些手腳像下面這樣

private Vector3 InterestingCylinder(float u, float v, float t)
{
    var r = _radius * (0.8f + Mathf.Sin(Mathf.PI * (6f * u + 2f * v + t)) * 0.2f);
    var x = r * Mathf.Sin(Mathf.PI * u);
    var y = _height * v;
    var z = r * Mathf.Cos(Mathf.PI * u);
    return new Vector3(x, y, z);
}

嘗試改變 u 和 v 的系數(shù)可以看到很多有趣的現(xiàn)象哦~懶得自己寫的可以打開(kāi)我的「Github Repo」直接運(yùn)行時(shí)修改 FactorU 和 FactorV 的值查看結(jié)果~最終我們可以達(dá)到類似這樣的效果

Catlike學(xué)習(xí)筆記(1.3)-使用Unity畫(huà)更復(fù)雜的3

球體

我們?cè)趫A柱體的基礎(chǔ)上稍加修改就可以獲得一個(gè)球體,首先,球體跟圓柱體一樣也可以認(rèn)為是很多半徑不同的圓環(huán)組成的,那么圓環(huán)的半徑呈現(xiàn)怎樣的變化呢,我們想象球體沿經(jīng)線切開(kāi)后,可以觀察到一圈緯線的半徑和緯線的縱座標(biāo)分別對(duì)應(yīng)Cos(PI / 2 * v)Sin(PI / 2 * v),按照這個(gè)思路我們嘗試寫出如下代碼。

private Vector3 Sphere(float u, float v, float t)
{
    var r = _radius * Mathf.Cos(Mathf.PI / 2 * v);
    var x = r * Mathf.Sin(Mathf.PI * u);
    var y = _radius * Mathf.Sin(Mathf.PI / 2 * v);
    var z = r * Mathf.Cos(Mathf.PI * u);
    return new Vector3(x, y, z);
}

運(yùn)行一下發(fā)現(xiàn)完全沒(méi)有問(wèn)題~如圖所示。。。

Catlike學(xué)習(xí)筆記(1.3)-使用Unity畫(huà)更復(fù)雜的3

所以想要讓球體動(dòng)起來(lái)我們可以使用同樣地思路對(duì) r 的計(jì)算進(jìn)行一點(diǎn)點(diǎn)魔改,比如說(shuō)這樣的一個(gè)參數(shù)factor

private Vector3 InterestingSphere(float u, float v, float t)
{
    var factor = 0.8f + Mathf.Sin(Mathf.PI * (_factorU * u + t)) * 0.1f;
    factor += Mathf.Sin(Mathf.PI * (_factorV * v + t)) * 0.1f;
    var r = factor * _radius * Mathf.Cos(Mathf.PI / 2 * v);
    ...
}

調(diào)一些奇怪的參數(shù)。。。然后就出現(xiàn)了一坨嚅動(dòng)的,。。球體。。。

Catlike學(xué)習(xí)筆記(1.3)-使用Unity畫(huà)更復(fù)雜的3

圓環(huán)體

那么想象下一個(gè)圓環(huán)體和球體到底有什么區(qū)別呢,針對(duì)每左半條或者右半條經(jīng)線圈,如果直接變成一個(gè)環(huán),那么球體不就變成圓環(huán)了么。。。那么怎么變成圓環(huán)呢,我們之前提到

一圈緯線的半徑和緯線的縱座標(biāo)分別對(duì)應(yīng)Cos(PI / 2 * v)和`Sin(PI / 2 * v)

所以我們把半個(gè)周期的 cos 和 sin 變成完整周期就可以了,不要除以 2 就好。。于是我們嘗試著寫下如下代碼

private Vector3 Torus(float u, float v, float t)
{
    var r = _radius * Mathf.Cos(Mathf.PI * v);
    var x = r * Mathf.Sin(Mathf.PI * u);
    var y = _radius * Mathf.Sin(Mathf.PI * v);
    var z = r * Mathf.Cos(Mathf.PI * u);
    return new Vector3(x, y, z);
}

運(yùn)行一下發(fā)現(xiàn)還是球體啊。。這是為什么呢,仔細(xì)觀察發(fā)現(xiàn)似乎小方塊比以前稀疏了,是因?yàn)榘霔l經(jīng)線被擴(kuò)展到整個(gè)周期以后變成了一整圈經(jīng)線,所以和對(duì)面的那半條完全重疊了。。所以怎么解決這個(gè)問(wèn)題呢?就是擴(kuò)大緯線圈讓相對(duì)的兩個(gè)半條經(jīng)線不會(huì)相互重疊甚至完全分離就可以了。所以這樣修改下試試

private Vector3 Torus(float u, float v, float t)
{
    var r = _radius * Mathf.Cos(Mathf.PI * v) + _radius2;
    ...
}

這里之所以是加一個(gè)_radius2在最外面是為了達(dá)到「無(wú)論 v 如何變化都可以是的半徑無(wú)條件增加 _radius2」的效果。。。運(yùn)行下會(huì)發(fā)現(xiàn)嗯果然沒(méi)問(wèn)題了。。

Catlike學(xué)習(xí)筆記(1.3)-使用Unity畫(huà)更復(fù)雜的3

所以最后也順便讓它動(dòng)起來(lái)吧。。。

Catlike學(xué)習(xí)筆記(1.3)-使用Unity畫(huà)更復(fù)雜的3

PART 5 總結(jié)

好吧這篇真的好長(zhǎng),而且寫的好累并且在公式功能壞掉的情況下又很難講清楚~大家把「Github Repo」下載下來(lái)自己運(yùn)行稍微修改下就很容易理解了~總之我們把簡(jiǎn)單的圖像擴(kuò)展到了三維的圖形的過(guò)程還是很有趣的~雖然不知道暫時(shí)有什么用處不過(guò)對(duì)于培養(yǎng)數(shù)學(xué)思維也還是挺有幫助的~好吧希望下一篇早日更新~就醬。。。


原文鏈接:https://snatix.com/2018/06/20/021-mathematical-surfaces/

本文由 sNatic 發(fā)布于『大喵的新窩』 轉(zhuǎn)載請(qǐng)保留本申明

另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)cdcxhl.cn,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。

標(biāo)題名稱:Catlike學(xué)習(xí)筆記(1.3)-使用Unity畫(huà)更復(fù)雜的3-創(chuàng)新互聯(lián)
轉(zhuǎn)載來(lái)于:http://bm7419.com/article8/gohop.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計(jì)、定制開(kāi)發(fā)、商城網(wǎng)站、網(wǎng)站內(nèi)鏈服務(wù)器托管、響應(yīng)式網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都做網(wǎng)站