前面
有时很想自己做一些很酷炫的image effect,但又不知道要从何开始,不妨先学习学习官方的Image effect是怎么实现的。这一篇主要探究鱼眼效果是如何实现的。下附两幅图看看效果是啥样的:
主要
我们现在正式看看它是怎么实现的,一个完整的屏幕特效分为两部分,一是cpu部分,它负责决定何时执行,并把rendertexture和相关参数传递给gpu,二是gpu部分,也就是处理rendertexture的shader,它负责处理当前rendertexture并输出。
先附上C#代码:
using System;
using UnityEngine;
namespace UnityStandardAssets.ImageEffects
{
[ExecuteInEditMode]
[RequireComponent (typeof(Camera))]
[AddComponentMenu ("Image Effects/Displacement/Fisheye")]
class Fisheye : PostEffectsBase
{
public float strengthX = 0.05f;
public float strengthY = 0.05f;
public Shader fishEyeShader = null;
private Material fisheyeMaterial = null;
public override bool CheckResources ()
{
CheckSupport (false);
fisheyeMaterial = CheckShaderAndCreateMaterial(fishEyeShader,fisheyeMaterial);
if (!isSupported)
ReportAutoDisable ();
return isSupported;
}
void OnRenderImage (RenderTexture source, RenderTexture destination)
{
if (CheckResources()==false)
{
Graphics.Blit (source, destination);
return;
}
float oneOverBaseSize = 80.0f / 512.0f; // to keep values more like in the old version of fisheye
float ar = (source.width * 1.0f) / (source.height * 1.0f);
fisheyeMaterial.SetVector ("intensity", new Vector4 (strengthX * ar * oneOverBaseSize, strengthY * oneOverBaseSize, strengthX * ar * oneOverBaseSize, strengthY * oneOverBaseSize));
Graphics.Blit (source, destination, fisheyeMaterial);
}
}
}
解释:
[ExecuteInEditMode]的作用是使其直接在编辑窗口中运行,这样我们就不用点Play了。
Fisheye类继承的是PostEffectsBase,PostEffectsBase这个类也是导入effect包自带的,我们可以看到它重载了CheckResources()方法,这个方法用来初始化,初始化分为两部分,一是创建一个隐藏的material(用来使用FisheyeShader),二是检测设备是否支持此屏幕特效。然后调用它的是在PostEffectsBase中的如下代码:
protected void Start ()
{
CheckResources ();
}OnRenderImage(RenderTexture source, RenderTexture destination)可以在挂在Camera上的所有脚本中使用,它有两个参数,sourse是当前屏幕所要显示的RenderTexture,destination是目标RenderTexture ,函数作用用通俗的话来说就是本来正常渲染出的画面现在不直接放到屏幕上了,我们把它传递给shader,然后把处理过后的画面再显示在屏幕上,如果一个摄像机上有多个屏幕特效的话,那么上一个的输出将作为下一个的输入。
Graphics.Blit (source, destination); 当初始化不通过的时候,使用此函数将source直接作为destination输出
- Graphics.Blit (source, destination, fisheyeMaterial); 此函数将把source作为material的_MainTex,把shader的输出作为destination。
接下来是shader代码:
Shader "Hidden/FisheyeShader" {
Properties {
_MainTex ("Base (RGB)", 2D) = "" {}
}
// Shader code pasted into all further CGPROGRAM blocks
CGINCLUDE
#include "UnityCG.cginc"
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
sampler2D _MainTex;
float2 intensity;
v2f vert( appdata_img v )
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.texcoord.xy;
return o;
}
half4 frag(v2f i) : Color
{
half2 coords = i.uv;
coords = (coords - 0.5) * 2.0;
half2 realCoordOffs;
realCoordOffs.x = (1-coords.y * coords.y) * intensity.y * (coords.x);
realCoordOffs.y = (1-coords.x * coords.x) * intensity.x * (coords.y);
half4 color = tex2D (_MainTex, i.uv - realCoordOffs);
return color;
}
ENDCG
Subshader {
Pass {
ZTest Always Cull Off ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
Fallback off
} // shader
解释:shader部分基础内容太多,我们只关注主体计算部分,如有疑问或纠错,欢迎在下面评论,我将迅速回复。
首先需要提醒的是,脚本暴露的两个参数strengthX和strengthY分别控制着屏幕纵向和横向的拉伸。。。。具体我也不知道为什么。因此在前面的C#代码中,strengthX*(宽长比)的作用是:若是屏幕比较扁,那么纵向的strengthX增长速率将加快,这样纵横变化速率将显得一致。
coords = (coords - 0.5) * 2.0;这一句我们得到了一个该像素与中心点相关的值,下面将要用到。
half2 realCoordOffs;
realCoordOffs.x = (1-coords.y * coords.y) * intensity.y * (coords.x);
realCoordOffs.y = (1-coords.x * coords.x) * intensity.x * (coords.y);这两句定义并计算了偏移量,可能我们不太知道它为什么要这样计算,但我们可以从中看出偏移量与像素点到中心点的距离成正比,与像素点到轴的距离成正比。
还有一个需要注意的地方就是如下这一句
half4 color = tex2D (_MainTex, i.uv - realCoordOffs);
你会发现这一句使最终的纹理始终向中心点靠近,这会让人疑惑,其实这就是所谓的拉伸,即相同数目的像素点对应更少数目的纹理像素点,这也就造成了当你拉伸较大将导致视野变小的现象。这也意味着可能有两个像素点对应着相同的纹理像素 。
最后
就这样了,希望大家能够指出错误,相互交流。