【Unity Shaders】學習筆記——SurfaceShader(五)讓紋理動起來
轉載請注明出處:http://www.cnblogs.com/-867259206/p/5611222.html
寫作本系列文章時使用的是Unity5.3。
寫代碼之前:
-
當然啦,如果Unity都沒安裝的話肯定不會來學Unity Shaders吧?
-
閱讀本系列文章之前你需要有一些編程的概念。
-
在VS里面,Unity Shaders是沒有語法高亮顯示和智能提示的,VS黨可以參考一下這篇文章使代碼高亮顯示,也可以下載
shaderlabvs
或NShader
之類的插件使代碼高亮顯示。 -
這是針對小白的Unity Shaders的基礎知識,如果你已經有了基礎或者你是大神,那么這些文章不適合你。
-
由於作者水平的局限,文中或許會有謬誤之處,懇請指出。
通過更改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"
}
效果如下:
使用的是這張紋理:
原理很簡單,看過前面的文章,你一定可以看懂這段代碼,原理就是隨着時間增加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。這是優化性能的一個方法。