__单通道立体渲染__功能适合基于 PC 和 Playstation 4 的 VR 应用。该功能可将两只眼睛的图像同时渲染到同一个经打包的渲染纹理,该纹理的宽度是单眼纹理宽度的两倍。Unity 针对每个包含渲染器组件的游戏对象使用 2 个绘制调用,从而将场景渲染两次,但是当同时针对左眼和右眼渲染时,Unity 仅对场景图迭代一次。在单通道立体渲染期间,双眼共享剔除和阴影计算所需要的工作。图形命令状态更改切换也会减少,因为 GPU 以往返方式(在双眼之间交替渲染对象)渲染每个游戏对象。
单通道立体渲染允许 GPU 针对双眼共享剔除。为了进行剔除,GPU 只需要对场景中的所有游戏对象迭代一次,然后渲染在剔除过程之后仍然存在的游戏对象。
下面的比较图显示了普通 VR 渲染和单通道立体渲染之间的区别。
普通 VR 渲染:
单通道立体 VR 渲染:
To enable this feature, open PlayerSettings (menu: Edit > Project Settings > Player). In PlayerSettings, navigate to XR Settings, ensure the Virtual Reality Supported checkbox is ticked, then select the Single Pass option from the Stereo Rendering Method dropdown.
Unity 的内置渲染功能和标准资源都支持该功能。但是,定制着色器以及从 Asset Store 下载的着色器可能需要进行修改(例如,可能需要缩放和偏移屏幕空间坐标,以访问大约一半的打包渲染纹理),然后才能添加对单通道立体渲染的支持。
UnityCG.cginc
中现有的 helper 方法支持单通道立体渲染。无论应用程序是否为 XR 应用,仍然需要对顶点执行变换。例如,在创建任何类型的应用程序时,顶点以模型空间进入顶点着色器阶段,并以裁剪空间退出。顶点着色器必须输出裁剪空间顶点坐标。受着色器影响的顶点集合通常以模型空间开始,然后顶点着色器将它们变换为裁剪空间。但是,为了使这些顶点以裁剪空间出现,顶点着色器首会先将这些顶点变换为世界空间,然后再变换为视口空间。
在 XR 应用中会有多个视图矩阵:一个视图矩阵同时用于左眼和右眼。可以使用内置方法 UnityWorldToClipPos
来确保 Unity 会考虑计算是否需要处理多个视图矩阵。如果使用 UnityWorldToClipPos
方法,无论应用程序在哪个平台上运行,着色器都会自动正确地执行变换计算。
UnityCG.cginc
还包含有助于创作立体着色器的以下 helper 方法:
属性 | 参数 | 描述 |
---|---|---|
UnityStereoScreenSpaceUVAdjust(uv, sb) |
uv :UV 纹理坐标。类型为 float2 或 float4,前者表示标准 UV,后者表示一对打包的 UV(两个)。sb :类型为 float4,其中包含由着色器应用于 UV 的 2D 缩放和 2D 偏置(缩放以 xy 表示,偏置以 zw 表示)。 |
如果定义了 UNITY_SINGLE_PASS_STEREO ,则会返回将 sb 中的缩放和偏置应用于 uv 中的纹理坐标后产生的结果。否则,将返回未经修改的纹理坐标。仅在单通道立体渲染模式下才应使用此属性来应用每个眼睛的缩放和偏置。 |
UnityStereoTransformScreenSpaceTex(uv) |
uv :UV 纹理坐标。类型为 float2 或 float4,前者表示标准 UV,后者表示一对打包的 UV(两个)。 |
如果定义了 UNITY_SINGLE_PASS_STEREO ,则会返回将当前眼睛的缩放和偏置应用于 uv 中的纹理坐标后产生的结果。否则,将原封不动返回纹理坐标。 |
UnityStereoClamp(uv, sb) |
uv :UV 纹理坐标。类型为 float2 或 float4,前者表示标准 UV,后者表示一对打包的 UV(两个)。sb :类型为 float4,其中包含由着色器应用于 UV 的 2D 缩放和 2D 偏置(缩放以 xy 表示,偏置以 zw 表示)。 |
如果定义了 UNITY_SINGLE_PASS_STEREO ,则会返回将钳制应用于 x 值(使用宽度)以及将偏置(由 sb 提供)应用于 uv 中的纹理坐标后产生的结果。否则,将返回未经修改的纹理坐标。使用此属性可在单通道立体渲染模式下应用每个眼睛的钳制,从而避免双眼之间出现渗色现象。 |
着色器会公开恒定的内置变量“unity_StereoEyeIndex”,这样 Unity 便可以执行与眼睛相关的计算。对于左眼渲染,unity_StereoEyeIndex
的值为 0,对于右眼渲染,值为 1。
下面是 UnityCG.cginc
中的一个示例,演示了如何使用 unity_StereoEyeIndex
修改屏幕空间坐标:
float2 TransformStereoScreenSpaceTex(float2 uv, float w)
{
float4 scaleOffset = unity_StereoScaleOffset[unity_StereoEyeIndex];
return uv.xy * scaleOffset.xy + scaleOffset.zw * w;
}
在大多数情况下,无需修改着色器。然而,在有些情况下,可能需要采样一个单视场纹理以作为单通道立体渲染的源(例如,如果正在创建一个全屏胶片颗粒或噪点效果,此时两只眼睛看到的源图像应该是相同的,而不应该打包到一个立体图像中)。在这种情况下,请使用 ComputeNonStereoScreenPos()
(而不是 ComputeScreenPos()
)从完整的源纹理中计算位置。
后期处理效果需要一些额外的工作才能支持单通道立体渲染。每个后期处理效果在打包的渲染纹理(同时包含左眼和右眼图像)上运行一次,但对于在后期处理过程中运行的所有绘制命令,这种效果将应用两次:一次是左眼的一半目标渲染纹理,一次是右眼的一半。
后期处理效果不会自动检测单通道立体渲染,所以您需要调整打包的渲染纹理的任何读取操作,使得这些读取操作仅读取对于正在渲染的眼睛来说正确的一面。根据后期处理效果的渲染方式,有两种方法可以实现这一点:
在没有做上述调整的情况下,每条绘制命令读取整个源渲染纹理(同时包含左眼和右眼视图),并将整个对图像同时输出到输出渲染纹理的左眼侧和右眼侧,导致在每只眼睛上错误地重现源图像。
使用 Graphics.Blit
或带有纹理贴图的全屏多边形来绘制每个后期处理效果时,就会发生这种情况。这两种方法都会引用链条中前一个后期处理效果的整个输出。在引用打包的立体渲染纹理中的某个区域时,这两种方法会引用整个打包的渲染纹理,而不是仅仅引用相关的一半。
使用 Blit()
渲染的后期处理效果不会自动引用打包的立体渲染纹理的正确部分。默认情况下,引用的是整个纹理。这会错误地在两眼之间拉伸后期处理效果。
对于使用 Blit()
的单通道立体渲染,着色器中的纹理采样器有一个额外的自动计算变量,能够根据正在绘制的眼睛来引用打包的立体渲染纹理的正确一半。该变量包含缩放值和偏移值,允许您将目标坐标变换为正确的位置。
要访问此变量,请在与采样器同名的着色器中声明一个 half4
,并添加 suffix _ST
(参阅下文中的代码示例)。要调整 UV 坐标,请将 _ST
变量传递给 scaleAndOffset
,并使用 UnityStereoScreenSpaceUVAdjust(uv, scaleAndOffset)
。此方法在非单通道立体构建中的编译结果为空,这意味着为支持此模式而进行了修改的着色器仍然兼容非单通道立体构建。
下面的示例演示了需要在片元着色器代码中更改哪些内容才能支持单通道立体渲染。
无立体渲染:
uniform sampler2D _MainTex;
fixed4 frag (v2f_img i) : SV_Target
{
fixed4 myTex = tex2D(_MainTex, i.uv);
...
}
有立体渲染:
uniform sampler2D _MainTex;
half4 _MainTex_ST;
fixed4 frag (v2f_img i) : SV_Target
{
fixed4 myTex = tex2D(_MainTex, UnityStereoScreenSpaceUVAdjust(i.uv, _MainTex_ST));
...
}
如果使用网格来渲染后期处理效果(例如,使用低级图形 API 以即时模式绘制四边形),那么在绘制每只眼睛时还需要调整目标纹理上的 UV 坐标。要在此类情况下调整坐标,请使用 UnityStereoTransformScreenSpaceTex(uv)
。在单通道立体渲染模式下,该方法可针对打包的立体渲染纹理进行正确调整,而如果已禁用单通道立体渲染模式,那么可针对非打包的渲染纹理进行自动编译。但是,如果您打算在相同模式下将某个着色器同时用于打包和非打包的渲染纹理,则需要有两个单独的着色器。
屏幕空间效果是指在预先渲染的图像上绘制的视觉效果。屏幕空间效果的例子包括环境光遮挡、景深和泛光。
例如,假设有一种屏幕空间效果要求在屏幕上绘制一幅图像(也许您正在绘制飞溅到屏幕上某种污垢)。只需要应用这种效果两次:每只眼睛应用一次,而不必将效果应用于整个输出显示,否则将使污垢图像在两只眼睛之间拉伸。在这种情况下,需要从引用整个打包渲染纹理的纹理坐标转换为引用每只眼睛的坐标。
下面的代码示例显示了一个表面着色器。此着色器可在输出图像上重复绘制输入纹理(名为 _Detail__)8 x 6 次。在第二个示例中,着色器在单通道立体模式下变换目标坐标,以引用表示当前正在渲染的眼睛的输出纹理部分。
示例 1:不支持单通道立体渲染的细节纹理
void surf(Input IN, inout SurfaceOutput o)
{
o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
float2 screenUV = IN.screenPos.xy / IN.screenPos.w;
screenUV *= float2(8,6);
o.Albedo *= tex2D(_Detail, screenUV).rgb * 2;
}
示例 2:支持单通道立体渲染的细节纹理
void surf(Input IN, inout SurfaceOutput o)
{
o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
float2 screenUV = IN.screenPos.xy / IN.screenPos.w;
#if UNITY_SINGLE_PASS_STEREO
// 如果已激活单通道立体渲染模式,则变换
//坐标以获取当前眼睛的正确输出 UV。
float4 scaleOffset = unity_StereoScaleOffset[unity_StereoEyeIndex];
screenUV = (screenUV - scaleOffset.zw) / scaleOffset.xy;
#endif
screenUV *= float2(8,6);
o.Albedo *= tex2D(_Detail, screenUV).rgb * 2;
}