unity3D技术分享

关于ComputeScreenPos和ComputeGrabSc

2020-01-03  本文已影响0人  恶毒的狗

一个Bug

今天QA报了一个渲染相关的bug:一个用了 扭曲 效果的翅膀特效在场景相机下显示正常,但是在UI相机上却有问题,截图如下:

screenshot1.png

扭曲背景 上下颠倒 了。


Bug的修正

这里用的 扭曲shader 是我们的美术同学从他们前项目搬过来的,代码很简单:

问题是,这里采样 GrabTexture 的时候用的是 screenUV 而非 grabUV,代码如下:

顶点着色器:

    o.screenPos = ComputeScreenPos (o.pos);

像素着色器:

    float2 sceneUVs = (i.screenPos.xy / i.screenPos.w) + (_Value * diffuseTex.a * float2(diffuseTex.r, diffuseTex.g) * i.vertexColor.a);
    half4 sceneColor = tex2D(_GrabTexture, sceneUVs);

修正这个问题很简单,把 ComputeScreenPos 换成 ComputeGrabScreenPos 即可,修正后的代码如下:

顶点着色器:

o.screenPos = ComputeGrabScreenPos (o.pos);

调整完之后就正常了,如下图:

screenshot2.png

关于ComputeScreenPos和ComputeGrabScreenPos的差别

修正容易,但是搞清楚 ComputeScreenPosComputeGrabScreenPos 的差别却要费一些功夫。

我们看一下相关代码:

inline float4 ComputeNonStereoScreenPos(float4 pos) {
    float4 o = pos * 0.5f;    
    o.xy = float2(o.x, o.y*_ProjectionParams.x) + o.w;
    o.zw = pos.zw;
    return o;
}

inline float4 ComputeScreenPos(float4 pos) {
    float4 o = ComputeNonStereoScreenPos(pos);
#if defined(UNITY_SINGLE_PASS_STEREO)
    o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w);
#endif
    return o;
}

inline float4 ComputeGrabScreenPos (float4 pos) {
    #if UNITY_UV_STARTS_AT_TOP
    float scale = -1.0;
    #else
    float scale = 1.0;
    #endif
    float4 o = pos * 0.5f;    
    o.xy = float2(o.x, o.y*scale) + o.w;
#ifdef UNITY_SINGLE_PASS_STEREO
    o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w);
#endif
    o.zw = pos.zw;
    return o;
}

通过分析可以发现,这两个函数的主要差别就是 UNITY_UV_STARTS_AT_TOP_ProjectionParams.x 的差别。

我们知道 RenderTexture 的纹理坐标在 Direct3D-like 平台和 OpenGL-like 平台存在差异:

当渲染到纹理的时,Unity遵从 OpenGL-like 平台的标准。

当工作在 Direct3D-like 平台时,为了兼容这个平台差异,Unity会 翻转投影矩阵 从而翻转 RenderTexture,这样既遵从了 OpenGL-like 平台的约定,又可以获取正确的采样结果。

_ProjectionParams.x 标识了投影矩阵是否经过翻转。

那么,是不是 Direct3D-like 平台下 RenderTexture 一定会进行翻转操作呢?如果没有翻转,而Unity又采用了 OpenGL-like 平台的约定,这种情况要怎么处理呢?

事实上,Unity在一些情况下确实不会翻转 RenderTexture,它的帮助文档 Platform-specific rendering differences 这一章节列举了 Direct3D-like 平台下不翻转 RenderTexture 的几种情况:

对于 GrabPass,Unity文档做了特别说明:在 Direct3D-like 平台下,GrabPass 不会进行 RenderTexture 的翻转操作,因此我们需要在shader中手工翻转uv以获取正确的采样结果。

ComputeGrabScreenPos 这里只需要判断 UNITY_UV_STARTS_AT_TOP 的取值:

inline float4 ComputeGrabScreenPos (float4 pos) {
    #if UNITY_UV_STARTS_AT_TOP
    float scale = -1.0;
    #else
    float scale = 1.0;
    #endif
    float4 o = pos * 0.5f;    
    o.xy = float2(o.x, o.y*scale) + o.w;
#ifdef UNITY_SINGLE_PASS_STEREO
    o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w);
#endif
    o.zw = pos.zw;
    return o;
}

Direct3D-like 平台下,如果我们用 _ProjectionParams.x 来判断是否需要手工翻转uv就错了,因为 RenderTexture 并未发生翻转,此时 _ProjectionParams.x = 1。


关于投影矩阵的翻转

bug是修正了,也知道了原因:对于GrabPass,我们应该用 UNITY_UV_STARTS_AT_TOP 而非 _ProjectionParams.x 去判断是否要手工进行uv翻转。

但是还有一个疑问没有解开:前文说了,这个 扭曲 特效在场景相机下工作正常,在UI相机下才有问题,这又是为什么呢?

说到相机,我们游戏内一共3个相机,渲染顺序如下:

场景相机(开后处理) --> UI相机1(关后处理) --> UI相机2(关后处理)

出现问题的相机是 UI相机2,此时我们并没有开 抗锯齿,并且 场景相机 处于 关闭 状态。

如果我们打开 场景相机,或者把 UI相机1 的后处理打开,又或者把 UI相机2 的后处理打开,这些情况下这个bug都不会出现。

似乎 多相机 以及 Image Effect的开关 也会影响 _ProjectionParams.x 的设值。

可惜的是,Unity文档对 投影矩阵的翻转 语焉不详,只是告诉你 _ProjectionParams.x = -1 即代表了翻转:

x is 1.0 (or –1.0 if currently rendering with a flipped projection matrix)

没有源码的情况下,何时翻转投影矩阵就比较难说清楚了。

不过我们只需要记得:

早前在写 Fantastic SSR Water 这个插件的时候,我也遇到过类似的问题。

Fantastic SSR Water 是一款关于水的插件,用 屏幕空间反射 实现水的反射。

当时,我错误的把 screenUVgrabUV 等同了,然后发现只有在特定的设置选项下渲染才正确,设置选项包括:

后面,我用 ComputeScreenPos 去计算 screenUV,用 ComputeGrabScreenPos 去计算 grabUV,问题就解决了,在各种设置组合下渲染都正确。

最后,附 Fantastic SSR Water 截图一张:

screenshot3.jpg

个人主页

本文的个人主页链接:https://baddogzz.github.io/2020/01/02/GrabUV-Bug/

好了,拜拜。

上一篇 下一篇

猜你喜欢

热点阅读