前言
Unity的URP和HDRP针对LensFlare也就是炫光,使用的是同一套逻辑(LensFlare(SRP))。但就是不支持VR,无论是单通道还是多通道。
Unity这样做也有一定的道理,本来这个效果就是模拟镜头的一种缺陷,人眼一般不会看到这种炫光,所以VR不支持也是理所当然的。
但本着没事找事的心态,权当练习,我翻看了这部分的源码,做了一点修改,使之能兼容VR。
效果如下:
这里是仓库地址
在我给官方提了这个BUG后,回复是就是不支持。
论坛上也有有说这个问题已经存在很久了。
分析
先从看它的shader入手吧,很简单,就一个文件 LensFlareDataDriven.shader,分了4个pass,每个pass的区别仅仅在于Blend的方式,分别对应了Additive、Screen、Premultiply、Lerp四种BlendMode。
关键的逻辑在LensFlareCommon.hlsl中,输入有两张贴图,颜色一张,遮挡一张,然后是一个颜色和5个向量,分别定义了颜色,旋转、缩放、屏幕位置、偏移、宽高比、遮挡半径、遮挡采样次数、是否离屏渲染以及边缘偏移、羽化范围、多边形属性等等。
而且都通过定义宏的方式来增强了可读性,学到了。
再往下就发现了从shadertoy拿来的工具函数float4 ComputeCircle(float2 uv)
float4 ComputePolygon(float2 uv_)
,用来程序化直接从UV绘制椭圆和多边形的。
这里看到绘制分成三种,圆,多边形,贴图。对应了这里的Type
最后来看看vert函数
用传入的屏幕坐标来缩放、旋转、乘上宽高比,加上UV偏移和另一个传入的偏移量直接写入positionCS。这里的positionCS是裁剪空间坐标,因为w设置为1,那xy范围就是(-1,1),(0,0)表示屏幕中心。
看到这就已经猜到它为什么不支持VR了,因为屏幕坐标是事先用C#脚本通过Camera的属性计算好的,而且估计计算时没有考虑VR下有两个摄像机,导致最终的屏幕位置是错误的。
修改
那修改的思路就确定了,增加一个向量,记录炫光分别在左右眼下的屏幕位置,依据USING_STEREO_MATRICES和unity_StereoEyeIndex来判断当前到底该使用哪一个屏幕坐标。
脚本修改
回到代码中,从LensFlareComponent一路往回推,发现主要的逻辑代码都在LensFlareCommonSRP中,用单例来管理,而调用则是在URP的UniversalRenderer中,在后处理时,由PostProcessPass调用,通过FrameDebugger也能看到。
URP的代码没法修改,只好全部另起炉灶,自定义RenderFeature、pass、Component等,把原来的逻辑先复制一遍,开始修改。
从PostProcessPass的DoLensFlareDatadriven中可以看到
这里是用的camera.worldToCameraMatrix来直接拿的矩阵,但在VR环境下,应该要用camera.GetStereoViewMatrix(eye);的方式分别来取左右眼的矩阵。所以这里添加上左右眼的VP矩阵,传入DoLensFlareDataDrivenCommon中。
来到DoLensFlareDataDrivenCommon函数,这里必须吐槽下这个函数,近16个参数就不说了,函数体也有500多行。。。
在这里增加一个函数来计算炫光在左右眼中的屏幕位置
在使用相机位置的时候也分别要用左右眼球的位置
计算出来后,添加进去
Shader修改
然后是shader的修改
增加一行输入,并定义好宏
vert中根据再unity提供的宏来判断一下
就完工了。
这里其实还是有个小问题,这种效果其实不符合物理。
因为如果在左右眼分别放置一个相机,拍出来的炫光的偏移位置(#define _PositionTranslate _FlareData0.zw
)其实应该是各不相同的。
但这会造成体验VR的人视觉混乱,所以为了体验效果,这里使用的偏移还是之前相机(也就是两眼中间位置)的偏移,只是修复了炫光原点的屏幕坐标位置。