【Unity Shaders】學習筆記——SurfaceShader(五)讓紋理動起來


【Unity Shaders】學習筆記——SurfaceShader(五)讓紋理動起來


轉載請注明出處:http://www.cnblogs.com/-867259206/p/5611222.html

寫作本系列文章時使用的是Unity5.3。
寫代碼之前:

  1. 當然啦,如果Unity都沒安裝的話肯定不會來學Unity Shaders吧?

  2. 閱讀本系列文章之前你需要有一些編程的概念。

  3. 在VS里面,Unity Shaders是沒有語法高亮顯示和智能提示的,VS黨可以參考一下這篇文章使代碼高亮顯示,也可以下載shaderlabvsNShader之類的插件使代碼高亮顯示。

  4. 這是針對小白的Unity Shaders的基礎知識,如果你已經有了基礎或者你是大神,那么這些文章不適合你。

  5. 由於作者水平的局限,文中或許會有謬誤之處,懇請指出。


通過更改Input結構體里UV坐標,我們可以讓紋理動起來。
這是一段河流的簡易Shader:

Shader "Custom/ScrollUVs" {
    Properties {        
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _ScrollXSpeed ("X Scroll Speed", Range(0, 10)) = 2  
        _ScrollYSpeed ("Y Scroll Speed", Range(0, 10)) = 2 
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
        
        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows

        sampler2D _MainTex;
        fixed _ScrollXSpeed;
        fixed _ScrollYSpeed;

        struct Input {
            float2 uv_MainTex;
        };

        void surf (Input IN, inout SurfaceOutputStandard o) {
            fixed2 scrolledUV = IN.uv_MainTex; 

            fixed xScrollValue = _ScrollXSpeed * _Time.y;
            fixed yScrollValue = _ScrollYSpeed * _Time.y;

            scrolledUV += fixed2(xScrollValue,yScrollValue);

            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, scrolledUV);
            o.Albedo = c.rgb;       
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

效果如下:
img
使用的是這張紋理:
img
原理很簡單,看過前面的文章,你一定可以看懂這段代碼,原理就是隨着時間增加UV坐標x和y值。
這里有一個_Time變量我們沒有見過,它是Unity內置的變量。
這是Unity對它的定義:

float4  _Time : Time (t/20, t, t*2, t*3), use to animate things inside the shaders

Unity官網Shaderlab內置變量介紹
_Time和C#里的Time.time變量類似,不同的是,它是一個四維向量,而不是標量。它包含四個不同大小的時間值,_Time.x就是二十分之一的時間。_Time.y、_Time.z、_Time.w同理。根據自己的需要選擇某個分量。


現在介紹一下Sprite Animation。
有Unity經驗的人,或許有用C#腳本寫過精靈動畫,那么用Shader寫精靈動畫原理也是一樣的,也是使紋理坐標的Offset隨着時間階梯式變化。
代碼如下:

Shader "Custom/AnimateSprites" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {} 
        _CellAmount ("Cell Amount", float) = 0.0  
        _Speed ("Speed", Range(0.01, 32)) = 12  
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
        
        CGPROGRAM
        #pragma surface surf Lambert

        sampler2D _MainTex;
        float _CellAmount;  
        float _Speed;

        struct Input {
            float2 uv_MainTex;
        };

        void surf (Input IN, inout SurfaceOutput o) {
            //Lets store our UVs in a seperate variable 
            float2 spriteUV = IN.uv_MainTex;  
              
            //Lets calculate the width of a singe cell in our 
            //sprite sheet and get a uv percentage that each cel takes up. 
            float cellUVPercentage = 1.0 / _CellAmount;  
              
            //Lets get a stair step value out of time so we can increment 
            //the uv offset 
            float timeVal = fmod(_Time.y * _Speed, _CellAmount);  
            timeVal = ceil(timeVal); 
            
            //Animate the uv's forward by the width precentage of 
            //each cell 
            float xValue = spriteUV.x;  
            xValue += timeVal;  
            xValue *= cellUVPercentage;  
              
            spriteUV = float2(xValue, spriteUV.y);  
              
            half4 c = tex2D (_MainTex, spriteUV);  
            o.Albedo = c.rgb;  
            o.Alpha = c.a;  
        }
        ENDCG
    } 
    FallBack "Diffuse"
}

關鍵的地方也有注釋,所以是很容易讀懂的。
fmod是取余,ceil是向上取整。
需要說明一下的地方應該就是:
UV坐標乘某個數,相當於修改紋理的Tilling,UV坐標加上某個數,相當於修改紋理的Offset。

優化

上面的那個腳本只實現了x方向上的偏移,當然你也可以實現更多維度的精靈動畫,只要添加y方向上的偏移即可。
但是這樣做可能會使Shader進行大量的計算,影響程序性能。所以我們可以將計算坐標偏移的代碼轉移到C#中,讓CPU分擔一部分計算。
這是C#中的代碼:

using UnityEngine;
using System.Collections;

public class SpriteAnimator : MonoBehaviour 
{
    
    public float speed = 0.0f;
    public int cellAmount = 0;

    float timeValue = 0.0f;

    void start() {
        transform.renderer.material.SetFloat("_cellAmount",cellAmount);
    }
    
    // Update is called once per frame
    void FixedUpdate () {
        timeValue = Mathf.Ceil(Time.time * speed % cellAmount);
        transform.renderer.material.SetFloat("_TimeValue", timeValue);
    }
}

這就是在C#里將變量傳遞給shader的方法。這樣就將一部分計算轉移到了C#腳本中。
因為CPU更擅長復雜的、有邏輯的運算,而GPU擅長並行運算,所以對於頂點、像素之類的計算(通常是幾百幾千個相同但不復雜的計算),應該讓GPU進行,而比較復雜的計算可以轉移到CPU中,再把數據喂給GPU。這是優化性能的一個方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM