最近真的忙,项目需求大改,晚上还要复习成人英语考试。
新增了“一键生成VR”需求,在现有框架上预编译同步普通键鼠和VR手柄的操作、同步PC和VR的各种接口使用等,业务开发人员通过派生、调用新的基类和API做流程功能,然后使用编辑器功能一键生成VR和PC双端场景并完整运行,同时业务功能也是大改,耳边还回响部门领导对我们项目经理喷的“都是错的!做的都是错的!!!”。
作为大头兵也没办法,只能按照新要求来改,就是做无用功浪费的时间回不来了。
现在VR上有个需求就是画面畸变,这VR头盔真是一家一个样,就算同一个头盔不同的人用都说看到的效果千奇百怪,特别是空间感上的差别,于是需要一个方法解决这种问题。我准备使用画面后处理畸变解决。
看看传统画面畸变的方法:桶形畸变
百度 桶形畸变
wiki 畸变
纹理uv网格上看,像个那种存酒的木桶一样,就类似这种:
图片看得出来,uv从中心(0.5,0.5)到四端有一种“膨胀”的感觉,我们可以猜想想到是否有一种计算方法可以将uv变换成这样。我网上找了个算法再结合了一点自己的修改,如下:
首先是网上的算法:
Shader "VRDistort/VRDistortImageEffectShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {
}
_Distort("Distort",Range(-4,4)) = 0
_Adjust("Adjust",Range(0,4)) = 1
}
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;
float _Distort; //畸变系数
float _Adjust; //矫正系数
//桶形畸变
float2 barreldistort(float2 uv)
{
//得到pixeluv相对center坐标d
float2 d = uv.xy-float2(0.5,0.5);
//d的长度方(0<d<0.5)
float2 d2 = d.x*d.x+d.y*d.y;
//畸变权重(-1<f<3)
float f = 1.0+d2*_Distort;
//畸变uv
//(f*_Adjust)用于修正权重
//再*d后“还原”相对坐标d
//再+center还原pixeluv
float2 buv = f*_Adjust*d+float2(0.5,0.5);
return buv;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, barreldistort(i.uv));
return col;
}
ENDCG
}
}
}
这个算法设计很巧妙,利用pixeluv与centeruv相对坐标d,进行变换后还原到pixeluv,再调整畸变参数和修正参数,就可以得到桶形畸变的效果,如下:
这里使用OnRenderImage进行后处理画面,可以看的出来畸变效果ok。
同时会发现一个问题:uv区间[0,1]的改变,畸变后需要手动调整修正参数才能将纹理“四个角”再次匹配。
所以我们还要修改一下算法,去掉_Adjust参数,我们的算法要保证:
1.自动计算_Adjust参数
2.4个uv端点变换后uv依旧相同
上面的计算就是将P0(0,0)、P1(1,1)带入原BarrelDistort算法,就可以得到_Adjust = 1/(1+0.5*_Distort)。
修改代码:
float2 barrel(float2 uv)
{
float j = 1/(1+0.5*_Distort);
float2 d = uv.xy-float2(0.5,0.5);
float d2 = d.x*d.x+d.y*d.y;
float f = 1.0+d2*_Distort;
float2 buv = f*j*d+float2(0.5,0.5);
return buv;
}
效果如下:
可以看的出来算法在_Distort>0的区间内没问题,但是_Distort<0的区间内就出问题了,问题如下:
1.画面变“凹”后,屏幕四边“凹陷”的uv不能“补全”。
2.当_Distort=-2时,分母=0,则计算错误。
那么我们还要思考_Distort<0时,正确的uv映射关系,如下:
四边中心点pixeluv经过变换后还是原pixeluv,计算如下:
计算方法就是反推,简单的一元一次方程。
修改代码:
float2 barrel(float2 uv)
{
float j = _Distort >= 0 ? (1/(1+0.5*_Distort)) : (1/(1+0.25*_Distort));
float2 d = uv.xy-float2(0.5,0.5);
float d2 = d.x*d.x+d.y*d.y;
float f = 1.0+d2*_Distort;
float2 buv = f*j*d+float2(0.5,0.5);
return buv;
}
效果如下:
这样就达到效果了,用户设置界面就只需要给一个“凹凸”参数微调画面就行了。
当然建议_Distort和_Adjust由c#计算后传递,节省shader运算时间,如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class VRDistortSetting : MonoBehaviour
{
public Slider slider;
public Material mat;
void Start()
{
slider.onValueChanged.AddListener(SliderValueChange);
}
private void SliderValueChange(float val)
{
float distort = val;
float adjust = distort >= 0 ? (1 / (1 + 0.5f * distort)) : (1 / (1 + 0.25f * distort));
mat.SetFloat("_Distort", distort);
mat.SetFloat("_Adjust", adjust );
}
}
ok,今天到这里,估计只有五一能闲一下了。