https://docs.unity3d.com/Manual/SL-PlatformDifferences.html
特定于平台的渲染差异 [Platform-specific rendering differences]
Unity运行在各种图形库平台上:Open GL、Direct3D、Metal和游戏控制台。在某些情况下,图形渲染方式存在差异
在平台和着色器之间的行为
语言语义学。大多数情况下,unity编辑器会隐藏差异,但有些情况下编辑器无法为您这样做。在这些情况下,您需要确保消除平台之间的差异。下面列出了这些情况以及发生时需要采取的措施。
渲染纹理坐标 [Render Texture coordinates]
垂直纹理坐标约定在两种类型的平台之间有所不同:类Direct3D平台和类OpenGL平台。
- 像Direct3D一样:坐标在顶部为0,向下增加。这适用于Direct3D、Metal和控制台。
- 类似OpenGL:坐标在底部为0,向上增加。这适用于OpenGL和OpenGL ES。
这种差异不会对项目产生任何影响,除非渲染到渲染纹理中
.在类direct3d平台上渲染到纹理时,unity会在内部颠倒渲染。这使得约定在平台之间匹配,而类似opengl的平台约定是标准。
在着色器中,图像效果和在uv空间中渲染是两种常见的情况,您需要采取措施确保不同的坐标约定不会在项目中造成问题。
图像效果 [Image Effects]
使用“图像效果”和“抗锯齿”时,图像效果的结果源纹理不会翻转以匹配类似OpenGL的平台约定。在这种情况下,unity渲染到屏幕以获得抗锯齿效果,然后将渲染解析为渲染纹理,以便使用图像效果进行进一步处理。
如果您的图像效果是一次处理一个渲染纹理Render Texture的简单效果,Graphics.Blit将处理不一致的坐标。但是,如果在图像效果中同时处理多个渲染纹理,则在类似Direct3D的平台中以及在使用抗锯齿时,渲染纹理可能以不同的垂直方向出现。要使坐标标准化,需要在顶点着色器中手动“翻转”屏幕纹理
使其符合类似OpenGL的坐标标准。
下面的代码示例演示如何执行此操作:
// Flip sampling of the Texture: // The main Texture // texel size will have negative Y). #if UNITY_UV_STARTS_AT_TOP if (_MainTex_TexelSize.y < 0) uv.y = 1-uv.y; #endif
参考边缘检测场景
在unity的shader replacement示例项目(请参阅Unity’s Learn resources)中了解有关此的更详细示例。该项目中的边缘检测同时使用屏幕纹理和相机的深度+法线纹理。
GrabPass也有类似的情况。生成的渲染纹理实际上可能不会在类似Direct3D(非类似OpenGL)的平台上颠倒。如果着色器代码对grabbass纹理进行采样,请使用UnityCG include文件中的ComputeGrabScreenPos
函数。
在uv空间中渲染 [Rendering in UV space]
在纹理坐标(uv)空间中渲染特殊效果或工具时,可能需要调整着色器,以便在类似Direct3D的系统和类似OpenGL的系统之间进行一致的渲染。您可能还需要在渲染到屏幕和渲染到纹理之间调整渲染。通过将类Direct3D的投影倒置来调整它们,使其坐标与类OpenGL的投影坐标匹配。
内置变量
ProjectionParams.x
包含一个+1或–1值。-1表示投影已颠倒以匹配类似OpenGL的投影坐标,+1表示投影尚未翻转。可以在着色器中检查此值,然后执行不同的操作。下面的示例检查投影是否已翻转,如果已翻转,则翻转并返回要匹配的UV坐标。float4 vert(float2 uv : TEXCOORD0) : SV_POSITION { float4 pos; pos.xy = uv; // This example is rendering with upside-down flipped projection, // so flip the vertical UV coordinate too if (_ProjectionParams.x < 0) pos.y = 1 - pos.y; pos.z = 0; pos.w = 1; return pos; }
剪辑空间坐标 [Clip space coordinates]
与纹理坐标类似,剪辑空间坐标(也称为投影后空间坐标)在类似Direct3D的平台和类似OpenGL的平台之间有所不同:
- 类Direct3D:剪辑空间深度从近平面的0.0到远平面的+1.0。这适用于Direct3D、Metal和控制台。
- 类似OpenGL:剪辑空间深度从近平面的-1.0到远平面的+1.0。这适用于OpenGL和OpenGL ES。
在着色器代码中,可以使用内置宏
UNITY_NEAR_CLIP_VALUE
获取基于平台的近平面值。
在脚本代码中,使用GL.GetGPUProjectionMatrix将unity的坐标系(遵循类似OpenGL的约定)转换为类似Direct3D的坐标,如果这是平台所期望的。
着色器计算的精度 [Precision of Shader computations]
为了避免精度问题,请确保在目标平台上测试着色器。移动设备和个人电脑中的GPUs在处理浮点类型方面有所不同。PC GPUs将所有浮点类型(float、half和fixed)视为相同的类型——它们使用完全32位精度进行所有计算,而许多移动设备GPUs不这样做。
有关详细信息,请参阅有关数据类型和精度的文档。
着色器中的常量声明 [Const declarations in Shaders]
const
的使用在Microsoft HSL(参见msdn.microsoft.com)和OpenGL’s GLSL(参见Wikipedia)着色器语言之间有所不同。
- 微软的HLSL
const
与它在C和C++中的含义相同,因为声明的变量在其范围内是只读的,但可以以任何方式初始化。- OpenGL’s GLSL
const
意味着该变量实际上是一个编译时常量,因此必须使用编译时约束(文本值或其他const上的计算)对其进行初始化。
最好遵循OpenGL’s GLSL语义,只有在变量真正不变时才将其声明为const。避免用其他可变变量初始化常量变量
值(例如,作为函数中的局部变量)。这也适用于微软的HLSL,因此以这种方式使用const可以避免某些平台上的混淆错误。
着色器使用的语义 [Semantics used by Shaders]
要使着色器在所有平台上工作,某些着色器值应使用以下语义:
- 顶点着色器输出(剪辑空间)位置:
SV_POSITION
。有时着色器使用POSITION语义使着色器在所有平台上工作。请注意,这不适用于索尼PS4或tessellation。- 片段着色器输出颜色:
SV_Target
。有时着色器使用COLOR
或COLOR0
使着色器在所有平台上工作。请注意,这在索尼PS4上不起作用。
将网格渲染为点时,从顶点着色器输出
PSIZE
语义(例如,将其设置为1)。某些平台(如OpenGL ES或Metal)在未从着色器写入点大小时将其视为“未定义”。
有关详细信息,请参见有关着色器语义的文档。
Direct3D着色器编译器语法 [Direct3D Shader compiler syntax]
Direct3D平台使用Microsoft的HLSL着色器编译器。HLSL编译器在各种细微的着色器错误方面比其他编译器更严格。例如,它不接受未正确初始化的函数输出值。
最常见的情况是:
曲面着色器
具有out参数的顶点修改器。按如下方式初始化输出:
void vert (inout appdata_full v, out Input o) { **UNITY_INITIALIZE_OUTPUT(Input,o);** // ... }
- 部分初始化值。例如,函数返回float4,但代码只设置它的.xyz值。设置所有值,如果只需要三个值,则更改为float3。
- 在顶点着色器中使用tex2d。这是无效的,因为在顶点着色器中不存在UV衍生物。您需要对显式mip级别进行采样;例如,使用tex2dlod(tex,float4(uv,0,0))。还需要添加pragma target 3.0,因为tex2dlod是着色器模型3.0功能。
着色器中的DirectX 11(DX11)HLSL语法 [DirectX 11 (DX11) HLSL syntax in Shaders]
Surface Shader编译管道的某些部分不理解DirectX 11特定HLSL(Microsoft的着色器语言)语法。
如果使用的是hlsl功能,如StructuredBuffers
,RWTextures
和其他非directx 9语法,请将它们包装在仅限directx x11的预处理器宏中,如下例所示。#ifdef SHADER_API_D3D11 // DirectX11-specific code, for example StructuredBuffer<float4> myColors; RWTexture2D<float4> myRandomWriteTexture; #endif
使用着色器帧缓冲区获取 [Using Shader framebuffer fetch]
一些gpu(最显著的是基于ios的powervr)允许您通过提供当前片段颜色作为片段着色器的输入来进行某种形式的可编程混合。
Fragment Shader
(seeEXT_shader_framebuffer_fetch
on khronos.org)
可以在unity中编写使用帧缓冲区获取功能的着色器。为此,在使用HLSL(Microsoft’s shading language - see msdn.microsoft.com)或Cg (the shading language by Nvidia - see nvidia.co.uk).编写片段着色器时,请使用inout color参数。
下面的例子是用cg表示的。
CGPROGRAM // only compile Shader for platforms that can potentially // do it (currently gles,gles3,metal) #pragma only_renderers framebufferfetch void frag (v2f i, inout half4 ocol : SV_Target) { // ocol can be read (current framebuffer color) // and written into (will change color to that one) // ... } ENDCG
着色器中的深度(Z)方向 [The Depth (Z) direction in Shaders]
深度(Z)方向在不同的着色器平台上不同
DirectX 11, DirectX 12, PS4, Xbox One, Metal:反向
- 深度(Z)缓冲区在近平面为1.0,在远平面为0.0。
- 剪辑空间范围为[近,0](表示近平面处的近平面距离,远平面处减小到0.0)。
其他平台:传统方向
The depth (Z) buffer value is 0.0 at the near plane and 1.0 at the far plane.
Clip space depends on the specific platform:
- 深度(Z)缓冲区值在近平面为0.0,在远平面为1.0。
- 剪辑空间取决于特定的平台:
- 在类似Direct3D的平台上,范围为[0,far](表示在近平面上为0.0,在远平面上增加到远平面距离)。
- 在类似opengl的平台上,范围是[-近,远](意思是减去近平面上的近平面距离,增加到远平面上的远平面距离)。
请注意,反向深度(z)与浮点深度缓冲区相结合
,与传统方向相比,显著提高了深度缓冲精度。这样做的优点是z坐标的冲突更少,阴影更好,特别是在使用小近平面和大远平面时。
因此,当使用深度(z)反转的平台中的着色器时:
- UNITY_REVERSED_Z is defined.
_CameraDepth
Texture texture range is 1 (near) to 0 (far).- Clip space range is within “near” (near) to 0 (far).
但是,以下宏和函数会自动计算出深度(Z)方向上的任何差异:
Linear01Depth(float z)
LinearEyeDepth(float z)
- UNITY_CALC_FOG_FACTOR(coord)
提取深度缓冲区 [Fetching the depth Buffer]
如果手动获取深度(z)缓冲区值,则可能需要检查缓冲区方向。下面是一个例子:
float z = tex2D(_CameraDepthTexture, uv); #if defined(UNITY_REVERSED_Z) z = 1.0f - z; #endif
使用剪辑空间 [Using clip space]
如果手动使用剪辑空间(Z)深度,还可以使用以下宏抽象平台差异:
float clipSpaceRange01 = UNITY_Z_0_FAR_FROM_CLIPSPACE(rawClipSpace);
注意:这个宏不会改变opengl或opengl es平台上的剪辑空间,所以它会在这些平台上从“-near”1(near)返回到far(far)
投影矩阵 [Projection matrices]
如果您在深度(z)反转的平台上,则GL.GetGPUProjectionMatrix()返回一个z反转矩阵。但是,如果手动从投影矩阵合成(例如,对于自定义阴影或深度渲染),则需要通过脚本还原深度(z)方向。
下面是一个例子:var shadowProjection = Matrix4x4.Ortho(...); //shadow camera projection matrix var shadowViewMat = ... //shadow camera view matrix var shadowSpaceMatrix = ... //from clip to shadowMap texture space //'m_shadowCamera.projectionMatrix' is implicitly reversed //when the engine calculates device projection matrix from the camera projection m_shadowCamera.projectionMatrix = shadowProjection; //'shadowProjection' is manually flipped before being concatenated to 'm_shadowMatrix' //because it is seen as any other matrix to a Shader. if(SystemInfo.usesReversedZBuffer) { shadowProjection[2, 0] = -shadowProjection[2, 0]; shadowProjection[2, 1] = -shadowProjection[2, 1]; shadowProjection[2, 2] = -shadowProjection[2, 2]; shadowProjection[2, 3] = -shadowProjection[2, 3]; } m_shadowMatrix = shadowSpaceMatrix * shadowProjection * shadowViewMat;
深度(Z)偏差 [Depth (Z) bias]
unity自动处理深度(z)偏差,以确保它与unity的深度(z)方向匹配。但是,如果您使用的是本地代码呈现插件,则需要在C或C++代码中否定(反向)深度(Z)偏置。
检查深度(Z)方向的工具
使用SystemInfo.usesReversedZBuffer查看您是否在使用反向深度(z)的平台上。