查看原文
其他

一个小技巧,解决DirectX 9半像素偏移问题

2016-09-06 译者:赵菁菁 Gad-腾讯游戏开发者平台
摘要:Direct3D 9 的“半像素偏移”问题困扰了很多人,其实可以在一个位置有效地解决这个问题,并且是用一种你不必反复思考的方法解决。只需为所有顶点着色器自动加上两个说明。
……在这里,我很想知道,在全世界,我2016年发布的关于D3D9的博客的受众是否超过了7个人。呃,无所谓了!service@tizikeji.com


背景

与OpenGL,D3D10+, Metal等平台相比,版本10之前的Direct3D有其特殊性——“半像素偏移”,其中视口坐标改变了半像素。这造成了各种各样的问题,特别是在图像后处理或者UI渲染方面,但是在其它地方也出问题。

官方文档(“直接将纹素映射到像素”),尽管技术上是正确的,但也不能完全只总结成简单的三点。

典型的建议多种多样:“将四个顶点的位置移动半个像素”或者“将纹理坐标移动半个纹素”等等。它们大多数都是只是谈论关于渲染图像处理或者UI的屏幕空间。

所有的问题在于:你得记住在每个小地方做过的事情。你的后处理代码需要注意,你的UI需要注意,你的烘焙代码需要注意,你的一些着色器需要注意……当你的代码有20个地方需要记得处理这个问题时,你发现你有问题了。


3D也有半像素问题!

尽管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年后我还需要这段代码,我会非常伤心!

基本的想法是:


  1. 找到那个寄存器是“输出位置”(在着色器模型2.0中有明确定义;在着色器模型3.0中可以是任意寄存器),让我们把这叫做oPos。


  2. 找到未用的临时寄存器,让我们把它们叫做tmpPos。

  3. 把所有oPos换成tmpPos。

  4. 在结尾加上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小妹

【直播报名】点击阅读原文

御龙主策:揭开御龙端游历久弥新的秘诀





近期热文

C++程序员必读:Protocol Buffer 基础指南

Unity资源商店里的免费资源,你一定要知道!


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存