一个小技巧,解决DirectX 9半像素偏移问题
摘要:Direct3D 9 的“半像素偏移”问题困扰了很多人,其实可以在一个位置有效地解决这个问题,并且是用一种你不必反复思考的方法解决。只需为所有顶点着色器自动加上两个说明。
……在这里,我很想知道,在全世界,我2016年发布的关于D3D9的博客的受众是否超过了7个人。呃,无所谓了!service@tizikeji.com
与OpenGL,D3D10+, Metal等平台相比,版本10之前的Direct3D有其特殊性——“半像素偏移”,其中视口坐标改变了半像素。这造成了各种各样的问题,特别是在图像后处理或者UI渲染方面,但是在其它地方也出问题。
官方文档(“直接将纹素映射到像素”),尽管技术上是正确的,但也不能完全只总结成简单的三点。
典型的建议多种多样:“将四个顶点的位置移动半个像素”或者“将纹理坐标移动半个纹素”等等。它们大多数都是只是谈论关于渲染图像处理或者UI的屏幕空间。
所有的问题在于:你得记住在每个小地方做过的事情。你的后处理代码需要注意,你的UI需要注意,你的烘焙代码需要注意,你的一些着色器需要注意……当你的代码有20个地方需要记得处理这个问题时,你发现你有问题了。
尽管D3D9半像素偏移上大部分材质都是关于屏幕空间的操作,但在3D中也同样存在问题!与OpenGL、D3D10+ 或者Metal相比较,D3D9对3D物体3D 的渲染稍有变化。
这里是一个低分辨率的场景对比,分别用D3D9和D3D11渲染:
这是更低的分辨率,按比例放大了很多倍,D3D9 vs D3D11:
根本原因是:视口不在我们希望的位置上,它移动了半像素。不幸的是,我们不能通过改变所有传入SetViewport 的坐标来修复这个问题,我们不能让坐标都移半像素(坐标成员都是整型)。
但是,我们有顶点着色器。顶点着色器输出视频的空间位置。我们可以调整视频空间位置,来把所有内容都移动半个视口像素。基本上我们需要这样做:
就是这样。没有其他事情要做。在所有你的顶点着色器中这样写,设置包括视口尺寸着色器常量就大功告成了。
我必须强调全都这么做,不只是在后处理或者UI着色器中这么做。全部!这修复了3D栅格化不匹配的问题,还有后处理和UI等问题。
哈哈,结果是,他们做!
在2003年写过关于此问题的博客:“怎样修复DirectX的光栅规则”。
WebGL 使用这个,并且在“ANGLE项目:在Direct3D上实现OpenGL ES 2.0”文章中写到了相关内容,也在2012年《OpenGL思考》这本书中写了一部分。
微软的Direct3D 11 在幕后做这件事;着色器编译器插入补丁,运行时设置着色器常量。Avery Lee在2012年在博客“ 的像素中心定位”写了这件事。.
可能这是常识,只有我点困惑吗?那对不起了!我本该在去年就意识到的……
如果你是在自己编写或者生成着色器源文件,“把这行HLSL代码加到你所有着色器中”是很好的。但是如果你不是呢?(比如:Unity落入这一阵营,不计其数的着色器已经写好了)
结果是,在D3D9字节码水平上这样做并不困难,不需要修正HLSL着色器代码。在你将HLSL代码编译为D3D9字节码之后(通过D3D编译或者fxc),只需要稍微改动就可以了。
D3D9字节码在MSDN中有相关文档——“Direct3D着色器代码”。我曾想是否应该做一些灵活的/通用的事情(从字节码解析“指令”,研究它,再编码成字节码),或者至少编写补丁所需的最少量代码。我决定采用后者;运气好的话D3D6即将过时。我需要进行更多D3D9字节码操作的可能性很小。如果5年后我还需要这段代码,我会非常伤心!
基本的想法是:
找到那个寄存器是“输出位置”(在着色器模型2.0中有明确定义;在着色器模型3.0中可以是任意寄存器),让我们把这叫做oPos。
找到未用的临时寄存器,让我们把它们叫做tmpPos。
把所有oPos换成tmpPos。
在结尾加上oPos.xy, tmpPos.w, constFixup,tmpPos 和 mov oPos.zw, tmpPos。
这是代码的一个要点。
在运行时,每个视口都会变化,把顶点着色器常量(我设置的是c255)设置成包括(-1.0f/width, 1.0f/height, 0, 0)。
就是这样!
很少:) 整个的问题修复需要着色器:
有一个未用的常量寄存器。我们的大部分着色器是着色器模型3.0,我还没有看过用光32个临时寄存器的顶点着色器。如果出现问题了,“找未用寄存器”的分析更聪明——只需在最早和最晚编写位置之间寻找一个未用寄存器。我还没有这么做过。
一些index上有一个未用的常量寄存器。着色器模型2.0和3.0的基本规则都是顶点着色器有256个常量寄存器,所以我挑选了最后一个来盛装修复数据。
有指令槽空间来再加入两个指令。着色器模型3.0有512指令槽闲置,很有可能使用超过510。
要点:
不再需要考虑D3D9 半像素偏移。
3D 光栅化的位置恰好与D3D9和其他(D3D11, GL, Metal等)之间匹配。
修复了D3D9vs D3D11。现在匹配了:
我运行了现有的所有图形测试,研究了产生的所有差异,并且将结果与D3D11进行比较。结果是:这显示了我们之前在着色器/代码中存在半像素偏移错误的几个小地方。所以下面是几个彩蛋(都是针对Unity的):
一些GrabPass的案例是在像素中采样,也就是说结果有点模糊。
一些阴影瑕疵略有减少;现在与DX11匹配。
一些图像后处理效果的案例中物体与屏幕边缘有一像素距离,但物体本应与屏幕严丝合缝,这个问题已经解决了。现在与DX11匹配。
这些全部会加进Unity5.5。我还没想好在此阶段加进5.4是否太快或者太冒险。
【版权声明】
原文作者未做权利声明,视为共享知识产权进入公共领域,自动获得授权;
GAD小妹时间
GAD小妹
【直播报名】点击阅读原文
御龙主策:揭开御龙端游历久弥新的秘诀
近期热文