一、ShaderToy作品
如果你对 Shader 有一定的了解,那么你或多或少听说过 shaderToy 这个网站,这个网站上有很多令人振奋的 shader 效果,而这些效果有可能只用了几行代码来实现。就如同画家绘画,在这里片段着色器就是画笔,屏幕就是画纸:
网站中对于任意一个作品,都提供了完整的 GLSL 片段着手器代码,它们是通过在你的浏览器中运行 WebGl 来展现这些效果的。你也可以通过修改代码,修改变量和输入来直接在网页上查看效果的变幻
把它搬运到 Unity 上并不难,由于它们都是在片段着色器中进行逐像素的绘制和处理,所以在 Unity 中,我们可以用屏幕后处理的方式来处理这些 Shader
二、在Unity中实现
C# 后处理脚本如下:
为了能提高某些高复杂度 Shader 的性能,所以可以指定画布的分辨率
using System;
using UnityEngine;
[ExecuteInEditMode]
public class ForShaderToy: PostEffectsBase
{
public int horizontal = 1920;
public int vertical = 1080;
public Shader shaderToy;
private Material _material;
public Material material
{
get
{
_material = CheckShaderAndCreateMaterial(shaderToy, _material);
return _material;
}
}
void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (material != null)
{
RenderTexture scaled = RenderTexture.GetTemporary(horizontal, vertical, 24);
Graphics.Blit(src, scaled, material);
Graphics.Blit(scaled, dest);
RenderTexture.ReleaseTemporary(scaled);
}
else
Graphics.Blit(src, dest);
}
}
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
//当在物体上添加该脚本时,Camera组件也会被自动被添加上去(如果没有的话)
[RequireComponent(typeof(Camera))]
public class PostEffectsBase: MonoBehaviour
{
protected void Start()
{
//如果显卡支持图像后期处理效果
if (!SystemInfo.supportsImageEffects)
{
//设置当前组件为关闭状态
enabled = false;
}
}
protected Material CheckShaderAndCreateMaterial(Shader shader, Material material)
{
//判断当前着色器是否可在当前显卡设备上使用,如果对应的着色器的所有Fallback都不可支持或者传入的着色器就不存在,返回null
if (shader == null || !shader.isSupported)
return null;
if (material && material.shader == shader)
return material;
else
{
material = new Material(shader);
//设置当前材质不会被保存在场景中
material.hideFlags = HideFlags.HideAndDontSave;
if (material)
return material;
else
return null;
}
}
搞定之后,就是将 ShaderToy 上的 Shader 搬运过来了,如果想要在 UnityShader 中使用 HLSL,那么就需要自己去实现 GLSL 到 HLSL 的转换
这个可以参考 https://alastaira.wordpress.com/2015/08/07/unity-shadertoys-a-k-a-converting-glsl-shaders-to-cghlsl/ 以及 https://msdn.microsoft.com/en-GB/library/windows/apps/dn166865.aspx
没问题了,接下来把上面的 C# 挂到摄像机下,组件指定 Shader 就好,一个例子如下:
//https://www.shadertoy.com/view/XlfGRj
//https://msdn.microsoft.com/en-GB/library/windows/apps/dn166865.aspx
Shader "ShaderToy/StarNest"
{
Properties
{
_Zoom("Zoom", Float) = 0.8 //缩放
_Speed("Speed", Float) = 0.01 //速度
_Volsteps("Volsteps", Int) = 20
_Tile("Tile", Float) = 0.85
_Iterations("Iterations", Int) = 17
_Formuparam("Formuparam", Float) = 0.53
_Brightness("Brightness", Float) = 0.0015
_Darkmatter("Darkmatter", Float) = 0.300
_Distfading("Distfading", Float) = 0.730
_Saturation("Saturation", Float) = 0.850
_StepSize("StepSize", Float) = 0.1
}
SubShader
{
PASS
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float _Zoom;
float _Speed;
int _Volsteps;
float _Tile;
float _Formuparam;
int _Iterations;
float _Brightness;
float _Darkmatter;
float _Distfading;
float _Saturation;
float _StepSize;
struct _2v
{
float4 vertex: POSITION;
};
struct v2f
{
float4 pos: SV_POSITION;
};
v2f vert(_2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag(v2f i): SV_Target
{
//iResolution -> _ScreenParams
//fragCoord -> pos(SV_POSITION)
//iTime:ShaderToy提供的时间函数 -> _Time.y
fixed2 uv = i.pos.xy / _ScreenParams.xy - 0.5;
uv.y *= _ScreenParams.y / _ScreenParams.x;
float3 dir = float3(uv * _Zoom, 1.0);
float time = _Time.y * _Speed + 0.25;
float a1 = 0.5 + 0.5 / _ScreenParams.x * 2.0;
float a2 = 0.8 + 0.5 / _ScreenParams.y * 2.0;
float2x2 Rot1 = float2x2(cos(a1), sin(a1), -sin(a1), cos(a1));
float2x2 Rot2 = float2x2(cos(a2), sin(a2), -sin(a2), cos(a2));
dir.xz = mul(dir.xz, Rot1);
dir.xy = mul(dir.xy, Rot2);
float3 from = float3(1.0, 0.5, 0.5);
from += float3(time * 2.0, time, -2.0);
from.xz = mul(from.xz, Rot1);
from.xy = mul(from.xy, Rot2);
float s = 0.1;
float fade = 1.0;
float3 v = float3(0.0, 0.0, 0.0);
for (int r = 0; r < _Volsteps; r++)
{
float pa = 0.0;
float a = 0.0;
float3 p = from + s * dir * 0.5;
p = abs(float3(_Tile, _Tile, _Tile) - float3(fmod(p.x, _Tile * 2.0), fmod(p.y, _Tile * 2.0), fmod(p.z, _Tile * 2.0))); //tiling fold
for (int i = 0; i < _Iterations; i++)
{
p = abs(p) / dot(p, p) - _Formuparam; //the magic formula
a += abs(length(p) - pa); //absolute sum of average change
pa = length(p);
}
float dm = max(0.0, _Darkmatter - a * a * 0.001); //dark matter
a *= a*a; //add contrast
if (r > 6)
fade *= 1.0 - dm; //dark matter, don't render near
v += fade;
v += float3(s, s * s, s * s * s * s) * a * _Brightness * fade; //coloring based on distance
fade *= _Distfading; //distance fading
s += _StepSize;
}
//mix -> lerp
v = lerp(float3(length(v), length(v), length(v)), v, _Saturation); //color adjust
fixed4 fragColor = float4(v * 0.01, 1.0);
return fragColor;
}
ENDCG
}
}
}
三、支持鼠标输入控制
要知道,ShaderToy 对这些大神级的 Shader 做了额外的支持,其中就包括提供每时每刻鼠标在屏幕中的坐标,也因此,我们可以在效果预览窗口移动鼠标来欣赏动态的效果
同理,我们也可以在 C# 脚本中通过 Input.mousePosition 和 Input.GetMouseButton() API 来传递当前鼠标屏幕坐标到着色器,下面是完整的版本:
using System;
using UnityEngine;
[ExecuteInEditMode]
public class ForShaderToy: PostEffectsBase
{
public int horizontal = 1920;
public int vertical = 1080;
public bool isMouseScreenPos = false;
public float mouseSpeed = 0.4f;
public Shader shaderToy;
private Material _material;
private float mouse_x = 0.5f;
private float mouse_y = 0.5f;
public Material material
{
get
{
_material = CheckShaderAndCreateMaterial(shaderToy, _material);
return _material;
}
}
void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (material != null)
{
RenderTexture scaled = RenderTexture.GetTemporary(horizontal, vertical, 24);
if (Input.GetMouseButton(0))
{
mouse_x = Mathf.Clamp(Input.mousePosition.x / Screen.width, 0, 1);
mouse_y = Mathf.Clamp(Input.mousePosition.y / Screen.height, 0, 1);
if (!isMouseScreenPos)
{
mouse_x = Input.mousePosition.x;
mouse_y = Input.mousePosition.y;
}
mouse_x *= mouseSpeed;
mouse_y *= mouseSpeed;
}
material.SetFloat("_MouseX", mouse_x);
material.SetFloat("_MouseY", mouse_y);
Graphics.Blit(src, scaled, material);
Graphics.Blit(scaled, dest);
RenderTexture.ReleaseTemporary(scaled);
}
else
Graphics.Blit(src, dest);
}
}
//https://www.shadertoy.com/view/XlfGRj
//https://msdn.microsoft.com/en-GB/library/windows/apps/dn166865.aspx
Shader "ShaderToy/StarNest"
{
Properties
{
_Zoom("Zoom", Float) = 0.8 //缩放
_Speed("Speed", Float) = 0.01 //速度
_Volsteps("Volsteps", Int) = 20
_Tile("Tile", Float) = 0.85
_Iterations("Iterations", Int) = 17
_Formuparam("Formuparam", Float) = 0.53
_Brightness("Brightness", Float) = 0.0015
_Darkmatter("Darkmatter", Float) = 0.300
_Distfading("Distfading", Float) = 0.730
_Saturation("Saturation", Float) = 0.850
_StepSize("StepSize", Float) = 0.1
_MouseX("MouseX", Range(0.0, 1.0)) = 0.5
_MouseY("ouseY", Range(0.0, 1.0)) = 0.5
}
SubShader
{
PASS
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float _Zoom;
float _Speed;
int _Volsteps;
float _Tile;
float _Formuparam;
int _Iterations;
float _Brightness;
float _Darkmatter;
float _Distfading;
float _Saturation;
float _StepSize;
float _MouseX;
float _MouseY;
struct _2v
{
float4 vertex: POSITION;
};
struct v2f
{
float4 pos: SV_POSITION;
};
v2f vert(_2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag(v2f i): SV_Target
{
//iResolution -> _ScreenParams
//fragCoord -> pos(SV_POSITION)
//iTime:ShaderToy提供的时间函数 -> _Time.y
fixed2 uv = i.pos.xy / _ScreenParams.xy - 0.5;
uv.y *= _ScreenParams.y / _ScreenParams.x;
float3 dir = float3(uv * _Zoom, 1.0);
float time = _Time.y * _Speed + 0.25;
float a1 = 0.5 + _MouseX / _ScreenParams.x * 2.0;
float a2 = 0.8 + _MouseY / _ScreenParams.y * 2.0;
float2x2 Rot1 = float2x2(cos(a1), sin(a1), -sin(a1), cos(a1));
float2x2 Rot2 = float2x2(cos(a2), sin(a2), -sin(a2), cos(a2));
dir.xz = mul(dir.xz, Rot1);
dir.xy = mul(dir.xy, Rot2);
float3 from = float3(1.0, 0.5, 0.5);
from += float3(time * 2.0, time, -2.0);
from.xz = mul(from.xz, Rot1);
from.xy = mul(from.xy, Rot2);
float s = 0.1;
float fade = 1.0;
float3 v = float3(0.0, 0.0, 0.0);
for (int r = 0; r < _Volsteps; r++)
{
float pa = 0.0;
float a = 0.0;
float3 p = from + s * dir * 0.5;
p = abs(float3(_Tile, _Tile, _Tile) - float3(fmod(p.x, _Tile * 2.0), fmod(p.y, _Tile * 2.0), fmod(p.z, _Tile * 2.0))); //tiling fold
for (int i = 0; i < _Iterations; i++)
{
p = abs(p) / dot(p, p) - _Formuparam; //the magic formula
a += abs(length(p) - pa); //absolute sum of average change
pa = length(p);
}
float dm = max(0.0, _Darkmatter - a * a * 0.001); //dark matter
a *= a*a; //add contrast
if (r > 6)
fade *= 1.0 - dm; //dark matter, don't render near
v += fade;
v += float3(s, s * s, s * s * s * s) * a * _Brightness * fade; //coloring based on distance
fade *= _Distfading; //distance fading
s += _StepSize;
}
//mix -> lerp
v = lerp(float3(length(v), length(v), length(v)), v, _Saturation); //color adjust
fixed4 fragColor = float4(v * 0.01, 1.0);
return fragColor;
}
ENDCG
}
}
}
四、GLHL转HLSL备忘
GLSL(ShaderToy) | HLSL(Unity3D) | |
---|---|---|
iResolution.xy | _ScreenParams.xy | 视口分辨率 |
fragCoord | pos(SV_POSITION) | 当前片元坐标 |
iTime | _Time.y | 时间函数 |
vec3 | float3/fixed3/half3 | 向量 |
mat3 | float3x3 | 矩阵 |
mix() | lerp() | 平滑插值 |
mat *= mat | mul(mat, mat) | 矩阵/向量乘法 |
参考资料: