概述
我做了这个效果。
残影仅在屏幕边缘创建,因此您可以保持屏幕中心的可见性,这在玩赛车游戏等时很重要,同时营造出速度感。
动图
静止图像
源代码
着色器
Shader "Hidden/MotionBlur"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_BlurSize ("Blur Size", Float) = 0
_EdgeCoeff ("Edge Coefficient", Float) = 1
_SpeedCoeff ("Speed Coefficient", Float) = 0
_BlurCenterPoint ("Blur Center Point", Vector) = (0.5, 0.5, 0.0)
}
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
sampler2D _MainTex;
half _BlurSize;
half _EdgeCoeff; // 端の方だけに効果をかけるために使用する係数。大きいほど端のみに効果が表れる
half _SpeedCoeff; // スピードに応じて増減させる係数(0~1)。大きくするほど効果が強くなる。ゲーム側の最高速度で1に、停止中は0にする。
half2 _BlurCenterPoint; // ブラーの中心となる点
static const int BLUR_SAMPLE_COUNT = 8;
// 近い点から遠い点に向かってサンプリングする際の重みづけ係数を設定していく
// 総和が1になるようにする
static const float BLUR_WEIGHTS[BLUR_SAMPLE_COUNT] = {
1.0 / BLUR_SAMPLE_COUNT,
1.0 / BLUR_SAMPLE_COUNT,
1.0 / BLUR_SAMPLE_COUNT,
1.0 / BLUR_SAMPLE_COUNT,
1.0 / BLUR_SAMPLE_COUNT,
1.0 / BLUR_SAMPLE_COUNT,
1.0 / BLUR_SAMPLE_COUNT,
1.0 / BLUR_SAMPLE_COUNT
};
float magnitude(float2 vec){
return max(abs(vec.x), abs(vec.y));
}
// 画面上におけるブラーの中心点までの最大距離を計算する
float calcMaxDistance()
{
// どの点が中心点だったとしても、最大距離を取るのは四角のどれか
float distance1 = magnitude(float2(0, 0) - _BlurCenterPoint);
float distance2 = magnitude(float2(1, 0) - _BlurCenterPoint);
float distance3 = magnitude(float2(0, 1) - _BlurCenterPoint);
float distance4 = magnitude(float2(1, 1) - _BlurCenterPoint);
float maxDistance = max(distance1, max(distance2, max(distance3, distance4)));
return maxDistance;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = 0;
// ブラーの中心から該当ピクセルまでの方向ベクトル。このベクトルに沿ってサンプリングを行う
float2 dir = i.uv - _BlurCenterPoint;
// ブラーの中心からの距離。距離が遠いほど強くブラーがかかるようにする。
// ここでアスペクト比を考慮すれば画面上下端に左右端と同じだけの効果を与えることが出来るかもしれない(今回は純粋に距離だけを見たいのでそうしない)
float distance = magnitude(dir);
// 方向ベクトルを正規化
dir /= sqrt(dir.x * dir.x + dir.y * dir.y);
// 画面の中心から最も遠い点までの距離が1になるように距離を正規化
distance /= calcMaxDistance();
distance = pow(distance, _EdgeCoeff); // distanceは0~1の範囲を取るので、累乗することで、より端の方だけを効果の対象にする事が出来る。
for(int j = 0; j < BLUR_SAMPLE_COUNT; j++){
float2 samplePoint = i.uv - dir / BLUR_SAMPLE_COUNT * j * distance * _SpeedCoeff *_BlurSize;
col += tex2D(_MainTex, samplePoint) * BLUR_WEIGHTS[j] ;
}
return col;
}
ENDCG
}
}
}
C# 脚本
using UnityEngine;
public class MotionBlur : MonoBehaviour
{
[SerializeField] Material motionBlurMaterial;
[SerializeField] float speed;
private void Update()
{
// 十字キーの上下 or W,Sキーで前後に移動する処理
float vertical = Input.GetAxis("Vertical");
transform.position += Vector3.forward * vertical * speed * Time.deltaTime;
motionBlurMaterial.SetFloat("_SpeedCoeff", Mathf.Abs(vertical));
}
private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
Graphics.Blit(src, dest, motionBlurMaterial);
}
}
存储库
源代码和示例场景放置在此存储库中。由于它不是 Unity 项目,因此将其导入到单独创建的项目中。
评论
我将解释着色器代码。
范围
使用以下五个属性。
sampler2D _MainTex;
half _BlurSize;
half _EdgeCoeff; // 端の方だけに効果をかけるために使用する係数。大きいほど端のみに効果が表れる
half _SpeedCoeff; // スピードに応じて増減させる係数(0~1)。大きくするほど効果が強くなる。ゲーム側の最高速度で1に、停止中は0にする。
half2 _BlurCenterPoint; // ブラーの中心となる点
_MainTex 在应用效果之前会自动传递渲染结果。_BlurSize,顾名思义,代表模糊造成的残像大小。增加尺寸会增加残像。_EdgeCoeff 是影响残像发生区域的参数。越小越宽创建残像。_SpeedCoeff是从脚本传入的参数,设置当前速度占最大速度的百分比,范围为0~1。_BlurCenterPoint可以操控出现残像的中心点,影响出现残像的区域和残像的方向。下图显示了将屏幕底部边缘的中心设置为中心点时的状态。可以看到残像向上延伸。
以下两个用作常数。
static const int BLUR_SAMPLE_COUNT = 8;
// 近い点から遠い点に向かってサンプリングする際の重みづけ係数を設定していく
// 総和が1になるようにする
static const float BLUR_WEIGHTS[BLUR_SAMPLE_COUNT] = {
1.0 / BLUR_SAMPLE_COUNT,
1.0 / BLUR_SAMPLE_COUNT,
1.0 / BLUR_SAMPLE_COUNT,
1.0 / BLUR_SAMPLE_COUNT,
1.0 / BLUR_SAMPLE_COUNT,
1.0 / BLUR_SAMPLE_COUNT,
1.0 / BLUR_SAMPLE_COUNT,
1.0 / BLUR_SAMPLE_COUNT
};
BLUR_SAMPLE_COUNT,顾名思义,是一个数字,表示从多少点获取颜色并合成模糊。越精细,负载越高,但残像质量越高。BLUR_WEIGHTS 是一个系数数组,用于乘以从每个采样点获取的颜色。数字 0 是乘以当前受影响像素颜色的系数,数字 7 是乘以离该像素最远的采样点颜色的系数。默认情况下,一切都是平等的,但如果您设计一种方法,例如减少更远的采样点的值,您可能会创建一个漂亮的残像。如果所有系数的总和不设置为 1,则屏幕整体会变亮或变暗。
片段着色器
在片段着色器中,我们首先计算从模糊中心到对应像素的距离和方向。
float2 scale = _BlurSize;
fixed4 col = 0;
// ブラーの中心から該当ピクセルまでの方向ベクトル。このベクトルに沿ってサンプリングを行う
float2 dir = i.uv - _BlurCenterPoint;
// ブラーの中心からの距離。距離が遠いほど強くブラーがかかるようにする。
// ここでアスペクト比を考慮すれば画面上下端に左右端と同じだけの効果を与えることが出来るかもしれない(今回は純粋に距離だけを見たいのでそうしない)
float distance = magnitude(dir);
// 方向ベクトルを正規化
dir /= sqrt(dir.x * dir.x + dir.y * dir.y);
// 画面の中心から最も遠い点までの距離が1になるように距離を正規化
distance /= calcMaxDistance();
distance = pow(distance, _EdgeCoeff); // distanceは0~1の範囲を取るので、累乗することで、より端の方だけを効果の対象にする事が出来る。
稍后将使用方向向量dir 来确定要采样的点。
将距离distance乘以后面要采样的距离,distance越大,采样点越远=模糊越强。这里distance 归一化,使得模糊中心和最远点之间的距离为 1。calcMaxDistance函数内容如下。
float calcMaxDistance()
{
// どの点が中心点だったとしても、最大距離を取るのは四角のどれか
float distance1 = magnitude(float2(0, 0) - _BlurCenterPoint);
float distance2 = magnitude(float2(1, 0) - _BlurCenterPoint);
float distance3 = magnitude(float2(0, 1) - _BlurCenterPoint);
float distance4 = magnitude(float2(1, 1) - _BlurCenterPoint);
float maxDistance = max(distance1, max(distance2, max(distance3, distance4)));
return maxDistance;
}
无论中心点在哪里,离中心点最远的点应该是正方形之一,所以我正在计算从中心点到每个角的距离并返回其中最大的一个。其中magnitude 是一个计算矢量幅度的函数。
float magnitude(float2 vec){
return max(abs(vec.x), abs(vec.y));
}
这里我使用 L1 范数而不是 L2 范数。这是因为使用 L2 范数会扭曲残像。
- L2 标准
- L1 规范
我认为这是一个品味问题,但是对于 L2 范数,角落越近,残像变得越强,产生弯曲的残像。感觉L1范数的残影比较自然。但是,如果您想让它看起来像以超高速移动,L2 规范可能会更酷。
计算出距中心点的距离和方向后,进行采样和模糊。
for(int j = 0; j < BLUR_SAMPLE_COUNT; j++){
float2 samplePoint = i.uv - dir / BLUR_SAMPLE_COUNT * j * distance * _SpeedCoeff *_BlurSize;
col += tex2D(_MainTex, samplePoint) * BLUR_WEIGHTS[j] ;
}
首先,将朝向中心点的方向向量-dir除以BLUR_SAMPLE_COUNT的数量,再乘以j。也就是说,它代表-dir方向上的jth点。将其乘以距中心的距离、当前速度和模糊幅度以确定采样点。本来残像是向着中心出现的,所以要采样的点应该是远离中心的dir方向的点(因为外面的点颜色向着中心模糊了)。此处使用指向中心的-dir,因为远离中心的采样会在屏幕边缘引入伪影。如果你看下面的 GIF 图片,你可以看到最左边的座位是被设计用来吸进去的。这是屏幕外的采样点造成的现象。因此,向屏幕中心采样可以防止这种情况发生。至于残像的方向,老实说,我不认为有太多的不协调感,因为无论采样方向如何,两边的颜色都会模糊。
概括
我通常不会写很多着色器代码,所以如果我在做一些无用的事情或者如果有更好的方法,请告诉我。
原创声明:本文系作者授权爱码网发表,未经许可,不得转载;
原文地址:https://www.likecs.com/show-308626996.html