1. 程式人生 > >【ShaderToy】開篇

【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. 總覺得用新編輯器寫出來的文章沒原來好看。。。