这些屏幕特效是咋实现的

这些屏幕特效是咋实现的

随着图形卡以及图像 API 的升级换代,越来越多的特效使用可编程渲染实现,也是就是所谓的着色器。不同实时渲染接口的着色器语法不尽相同,在英伟达的统一下,CG 语言成为行业标准(下列片元着色器代码片段中均使用 CG 语法),着色器代码在实时渲染的地位越来越高,许多经典的屏幕特效也源于着色器

那些常用特效实现方法

1. 数字图像处理来实现简单特效

  • 负片效果

    这个效果是我接触数字图像处理的第一个例子,那时候很流行冈萨雷斯的那本书,基本方法就是把图像的 rgb 通道反色,在口碑不好的拳皇 2001 中几乎所有 MAX 超必杀都有背景负片效果

    color.rgb = 1 - col.rgb;
    
    拳皇 2001 拉尔夫超级机炮拳
  • 模糊与锐化效果

    模糊效果是美术最常用的的效果,在游戏开发中,根据像素深度来重建场景深度,远景使用模糊效果,近景清晰来模拟人眼专注效果;也可以模拟失去或恢复意识的相机效果。在使命召唤 8 现代战争 3 游戏中,有类似的模糊效果

    // 高斯模糊
    /*
    	1	2	1
    	2	4	2
    	1	2	1
    */
    fixed3 col = tex2D(_MainTex,i.uv + _MainTex_TexelSize.xy*r*fixed2(-1,-1)).rgb * 1;
    col += tex2D(_MainTex,i.uv + _MainTex_TexelSize.xy*r*fixed2(0,-1)).rgb * 2;
    col += tex2D(_MainTex,i.uv + _MainTex_TexelSize.xy*r*fixed2(1,-1)).rgb * 1;
    col += tex2D(_MainTex,i.uv + _MainTex_TexelSize.xy*r*fixed2(-1,0)).rgb * 2;
    col += tex2D(_MainTex,i.uv + _MainTex_TexelSize.xy*r*fixed2(0,0)).rgb * 4;
    col += tex2D(_MainTex,i.uv + _MainTex_TexelSize.xy*r*fixed2(1,0)).rgb * 2;
    col += tex2D(_MainTex,i.uv + _MainTex_TexelSize.xy*r*fixed2(-1,1)).rgb * 1;
    col += tex2D(_MainTex,i.uv + _MainTex_TexelSize.xy*r*fixed2(0,1)).rgb * 2;
    col += tex2D(_MainTex,i.uv + _MainTex_TexelSize.xy*r*fixed2(1,1)).rgb * 1;
    col = col / 16;
    
    使命召唤 8 现代战争 3 单人剧情开始
  • Bloom 效果
    Bloom 效果是一种高亮度侵入,来实现昏暗光照切换到明亮光照的人眼不适应的效果。具体实现是先判断亮度较高的区域生产像素贴图,然后对该贴图经行高斯模糊,将亮度扩散到四周,最后和原图混合。下图为《杀手6》的光线效果,非常震撼

    杀手 6 迈阿密赛车关卡
  • 其他形态学检测
    边缘检测在图像处理中比较重要,但是在游戏开发中,边缘检测一般不在屏幕特效中完成,往往在单个模型的着色器中完成描边效果,所以这里不介绍

2. 噪声算法辅助完成复杂特效

柏林噪声的发明者 Ken Perlin 因此算法获得奥斯卡科技成果奖,该噪声符合自然规律,可以模拟很多自然现象,比如云雾、消融、火焰等效果。基本思想是在两两随机数之间进行平滑差值,让随机数生成具有过渡性,而不是断落变化

平滑插值
插值是图形学的基础运算 lerp(a,b,c) = a * c + (1 - c)*b
说到平滑插值,最有必要聊聊贝塞尔曲线,做过动画的小伙伴就知道,动画进度如果与时间成正比,动画会很单调,我们常常看到的 UI 弹窗动画都会“俏皮”回弹一下,这样的动画玩家更会买账,根据贝塞尔曲线模拟出响应的动画速度,比如先慢后快,比如回弹,比如越来越快等等

先慢后快的贝塞尔曲线回弹的贝塞尔曲线
float rand(float2 p)
{
	return frac(sin(dot(p ,float2(12.9898,78.23673))) * 43758.5453);
}

// 二维柏林噪声
float noise(float2 x)
{
	float2 i = floor(x);
	float2 f = frac(x);

	float a = rand(i);
	float b = rand(i + float2(1.0, 0.0));
	float c = rand(i + float2(0.0, 1.0));
	float d = rand(i + float2(1.0, 1.0));
	float2 u = f * f * f * (f * (f * 6 - 15) + 10);

	float x1 = lerp(a,b,u.x);
	float x2 = lerp(c,d,u.x);
	return lerp(x1,x2,u.y);
}
// 多噪声叠加,代码可以优化成不用循环的
float fbm(float2 x)
{
	float scale = 0.2;
	float res = 0;
	float w = 5;
	for(int i=0;i<4;++i)
	{
		res += noise(x * w);
		w *= 1.5;
	}
	return res*scale;
}

3. 修改 uv 的小技巧

顶点着色器传递给片元着色器的纹理坐标 uv, 是片元着色器的最基础参数,我们适当加以扰动可以做出很多绚丽的效果。比如水波纹效果,用的是正/余函数的扰动来模拟水波效果


sampler2D _MainTex;
float _Amount;
float _W;
float _Speed;

fixed4 frag (v2f i) : SV_Target
{
	float2 center_uv = {0.8,0.08};
	float2 uv = i.uv;
	float2 dt = center_uv - uv;
	float len = sqrt(dot(dt, dt));
	
	float amount = min(_Amount,_Amount / (0.0001 + len*len*_Speed));

	if(amount < 0.005) // 使用 step 优化
	{
		amount = 0;
	}

	uv.y += amount * cos(len * _W *UNITY_PI + UNITY_PI/2);

	fixed4 col = tex2D(_MainTex, uv);
	
	return col;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值