【ShaderToy】開篇
寫在前面
嗚呼,好久沒有寫部落格了,好慚愧。題外話,感覺越大就越想家,希望可以一直和家人在一起,哪怕只是坐在一起不說話也覺得很溫暖,一想到要分開眼睛就開始酸,哎。開學還是爬上來老實更新部落格學習吧~
今天爬上來一看,發現CSDN的部落格編輯終於更新了!進步總是好的,以前的編輯器實在很捉急。使用這種標記語言的確方便了很多,但好像顏色字型沒法設定?程式設計師果然對美觀沒什麼追求。
ShaderToy
如果你還沒聽過ShaderToy,那你就真的錯過了一個很好的shader學習網站。我是在群裡有一次聽到小夥伴們提到這個網站的。點進去就會發現裡面有很多很絢麗的shader展示。
說簡單點,這個網站是專門讓人們分享和編寫GLSL的pixel shaders的。換句話說,裡面那些絢麗的效果僅僅依靠pixel shaders就可以完成了(當然還有紋理的配合),是不是很強大?裡面的強人很多,就像頭腦風暴一樣,讓你一次次發出驚歎,原來還可以這樣做!但是,這裡面也蘊含了很多數學和演算法知識,所以你可能會經常發現自己腦袋不夠用,跟不上作者的思路。。。不過,腦袋都是靠鍛鍊的嘛,沒有捷徑可走,多看多寫總是沒錯的~
強烈建議大家先去逛一逛,有很多很好玩的東西,例如這個人寫了一個莫比烏斯帶,而這個人寫了一個耀眼的小太陽!一開始你很難相信這些完全都是用shader計算出來的。
那些效果是怎麼寫出來的
很多人都在好奇那些絢麗的效果是怎麼來的,比如iq剛寫的這個效果:
這個效果用了什麼Maya或3ds Max做出的模型嗎?答案其實是沒有的,沒有任何外部的模型輸入,紋理和模型都是由程式生成的。當你開啟它的介面時,其實所有的輸入和程式都一目瞭然:
Shadertoy的特點就是大家使用程式來產生各種模型、紋理、動畫,所以讓人驚歎!一個pixel shader+幾張固定的簡單的紋理,就能得到這麼絢麗的結果!iq的這個新效果更是展示了這種方法能做到的程度——照片級的效果。
那麼,他們到底是怎麼做到的呢?這裡簡單提到一下,這種看似有建模效果的畫面大部分都使用了raymarching的方法,iq的部落格裡有很多文章,大家可以去學習下。
特點
之前說了,這個網上的所有shader都是GLSL的pixel shaders。那麼什麼是pixel shader呢?如果我們需要渲染一個剛好鋪蓋整個螢幕的全屏的方形平板,那麼這個方形的fragment shader就是一個pixel shader。這是因為此時,每一個fragment對應了螢幕上的一個pixel。也因此,pixel shader的很多輸入都是相同的。在ShaderToy的每個shader上方,你都可以看到一個Shader Input:
uniform vec3 iResolution; // viewport resolution (in pixels)
uniform float iGlobalTime; // shader playback time (in seconds)
uniform float iChannelTime[4]; // channel playback time (in seconds)
uniform vec3 iChannelResolution[4]; // channel resolution (in pixels)
uniform vec4 iMouse; // mouse pixel coords. xy: current (if MLB down), zw: click
uniform samplerXX iChannel0..3; // input channel. XX = 2D/Cube
uniform vec4 iDate; // (year, month, day, time in seconds)
uniform float iSampleRate; // sound sample rate (i.e., 44100)
這些就是ShaderToy提供的公共變數,我們可以直接訪問。例如,iResolution儲存了螢幕解析度,iGlobalTime提供了shader執行時間,iMouse提供了滑鼠點選位置等等。
由於ShaderToy針對的是pixel shaders,這也意味著它們的vertex shaders都是一樣的,只需要計算基本的頂點位置和螢幕位置即可。
在Unity中實踐
Unity的出現的確給很多shader學習者一個方便的編譯和展示平臺,我們不再需要每次都用很多程式碼準備好頂點資料,每次都設定紋理,每次都設定MVP矩陣等等。Unity提供給我們更方便的視覺化操作。
ShaderToy中很多shader都可以經過一點修改後在Unity中編譯執行。當然,也有人這麼幹過了。例如這位博主。
為了方便後面的編寫,我們可以寫一個針對ShaderToy的基本模板shader。在這之前,我們有必要弄清楚ShaderToy的Shaders長什麼樣。如果你仔細觀察,其實ShaderToy中的所有shader只有一個必不可少的函式:
void mainImage( out vec4 fragColor, in vec2 fragCoord )
它的輸入是一個型別為vec2的fragCoord,對應輸入的螢幕位置;輸出一個vec4的fragColor,對應該pixel的顏色。很簡單對不對!
那麼現在我們就可以寫出這個模板shader了。
Shader "Shadertoy/Template" {
Properties{
iMouse ("Mouse Pos", Vector) = (100, 100, 0, 0)
iChannel0("iChannel0", 2D) = "white" {}
iChannelResolution0 ("iChannelResolution0", Vector) = (100, 100, 0, 0)
}
CGINCLUDE
#include "UnityCG.cginc"
#pragma target 3.0
#define vec2 float2
#define vec3 float3
#define vec4 float4
#define mat2 float2x2
#define mat3 float3x3
#define mat4 float4x4
#define iGlobalTime _Time.y
#define mod fmod
#define mix lerp
#define fract frac
#define texture2D tex2D
#define iResolution _ScreenParams
#define gl_FragCoord ((_iParam.scrPos.xy/_iParam.scrPos.w) * _ScreenParams.xy)
#define PI2 6.28318530718
#define pi 3.14159265358979
#define halfpi (pi * 0.5)
#define oneoverpi (1.0 / pi)
fixed4 iMouse;
sampler2D iChannel0;
fixed4 iChannelResolution0;
struct v2f {
float4 pos : SV_POSITION;
float4 scrPos : TEXCOORD0;
};
v2f vert(appdata_base v) {
v2f o;
o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
o.scrPos = ComputeScreenPos(o.pos);
return o;
}
vec4 main(vec2 fragCoord);
fixed4 frag(v2f _iParam) : COLOR0 {
vec2 fragCoord = gl_FragCoord;
return main(gl_FragCoord);
}
vec4 main(vec2 fragCoord) {
return vec4(1, 1, 1, 1);
}
ENDCG
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
ENDCG
}
}
FallBack Off
}
程式碼比較簡單。主要是在開頭定義了一系列巨集來和ShaderToy中的GLSL銜接。其中main函式對應了之前的mainImage函式。在後面,我們只需要在CGINCLUDE中定義其他函式,並填充main函式即可。
為了可以響應滑鼠操作,我們還可以寫一個C#指令碼,以便在滑鼠進行拖拽時將滑鼠位置傳遞給shader。
using UnityEngine;
using System.Collections;
public class ShaderToyHelper : MonoBehaviour {
private Material _material = null;
private bool _isDragging = false;
// Use this for initialization
void Start () {
Renderer render = GetComponent<Renderer>();
if (render != null) {
_material = render.material;
}
_isDragging = false;
}
// Update is called once per frame
void Update () {
Vector3 mousePosition = Vector3.zero;
if (_isDragging) {
mousePosition = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 1.0f);
} else {
mousePosition = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0.0f);
}
if (_material != null) {
_material.SetVector("iMouse", mousePosition);
}
}
void OnMouseDown() {
_isDragging = true;
}
void OnMouseUp() {
_isDragging = false;
}
}
程式碼很簡單,在有滑鼠拖拽時,mousePositon的Z分量為1,否則為0。這跟ShaderToy中判斷滑鼠的方式一致。
使用時,只要把該指令碼拖拽到材質所在的物體上,同時保證該物體上有繫結Collider即可。
寫在最後
ShaderToy絕大部分程式碼都是依靠強大的數學計算來完成的,因此真的是一次次頭腦風暴。當然,一些人會覺得它對於現在火爆的移動終端來說用處不大,因為支援不了唄~不過,我還沒工作,覺得鍛鍊下挺好的~在後面我會盡量定期每週更新一篇ShaderToy中的shader。總之,希望大家have fun~
P.S. 總覺得用新編輯器寫出來的文章沒原來好看。。。