入门图形学:VR畸变后处理

      最近真的忙,项目需求大改,晚上还要复习成人英语考试。
      新增了“一键生成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依旧相同
0
      上面的计算就是将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,今天到这里,估计只有五一能闲一下了。

猜你喜欢

转载自blog.csdn.net/yinhun2012/article/details/124208313