【光能蜗牛的图形学之旅】ShaderToy

本篇参考乐乐同学
而ShaderToy,也就是这个网站

ShaderToy简单介绍

Shadertoy的特点就是大家使用程序来产生各种模型、纹理、动画,所以让人惊叹!一个pixel shader+几张固定的简单的纹理,就能得到这么绚丽的结果!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     iTime;                 // shader playback time (in seconds) shader的时间回调
uniform float     iTimeDelta;            // render time (in seconds) 每一帧的渲染时间
uniform int       iFrame;                // shader playback frame 帧号
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中使用呢

之前也说了,因为ShaderToy基本就是对pixShader,即片元着色器的编写
我们可以在这个位置看到右边的着色器输入框的代码

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = fragCoord.xy / iResolution.xy;
    fragColor = vec4(uv,0.5+0.5*sin(iTime),1.0);
}

它的输入是一个类型为vec2的fragCoord,对应输入的屏幕位置;
它的输出是一个vec4的fragColor,对应该pixel的颜色。很简单对不对!
我们可以简单的在网站中修改这段代码预览一些效果,这就是基本思路

但是在unity中,我们要怎么做呢
闲话不说先上代码

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所编写,因此转换到Unity ShaderLab就需要一些衔接,而衔接的方式即是在开头定义一系列宏来对应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();
        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即可。

验证鼠标是否有效
你可以尝试将刚才的shader代码改为

    vec4 main(vec2 fragCoord) {
       // return vec4(1, 1, 1, 1);   
        return normalize(iMouse);
    }

运行程序,可以看到你鼠标移动的时候,物体的颜色发生了改变,

Over~

你可能感兴趣的:(【光能蜗牛的图形学之旅】ShaderToy)