直接将纹素映射到 Direct3D 9 (像素)

使用预转换顶点呈现 2D 输出时,必须小心确保每个纹素区域正确对应于单个像素区域,否则可能会出现纹理失真。 通过了解 Direct3D 在光栅化和纹理三角形时遵循的过程的基础知识,可以确保 Direct3D 应用程序正确呈现 2D 输出。

6x6 分辨率显示器的插图

上图显示了建模为正方形的像素。 然而,在现实中,像素是点,而不是方块。 上图中的每个正方形都指示由像素点燃的区域,但像素始终只是正方形中心的一个点。 这种区别虽然看起来很小,但很重要。 下图显示了相同显示器的更好图示。

由像素组成的显示器的插图

上图正确地将每个物理像素显示为每个单元格中心的点。 屏幕空间坐标 (0, 0) 直接位于左上角像素,因此位于左上角单元格的中心。 因此,屏幕左上角位于 (-0.5,-0.5) ,因为它向左显示 0.5 个单元格,从左上角像素向上 0.5 个单元格。 Direct3D 将呈现角在 (0、 0) 和 (4、4) 的象限,如下图所示。

 (0) 到 (4、4) 之间的未振化象限轮廓的插图

上图显示了数学象限相对于显示器的位置,但未显示 Direct3D 光栅化四边形并将其发送到显示器后四边形图的外观。 事实上,光栅显示器不可能完全按所示填充四边形,因为四边形的边缘与像素单元格之间的边界不一致。 换句话说,由于每个像素只能显示一种颜色,因此每个像素单元格只填充一种颜色:如果显示器要完全按所示呈现四边形,则沿象限边缘的像素单元格需要显示两种不同的颜色:蓝色(由四边形覆盖)和白色(只显示背景)。

相反,图形硬件的任务是确定应填充哪些像素以近似四边形。 此过程称为光栅化,在 Direct3D 9) (光栅化规则中 详细介绍。 对于此特定情况,下图中显示了光栅化的四边形。

从 (0,0) 到 (4,4) 绘制的无纹理象限的插图

请注意,传递给 Direct3D 的象限的角位于 (0,) , (4,4) ,但光栅化输出 (上图) 角位于 (-0.5,-0.5) 和 (3.5,3.5) 。 比较上述两个图示的呈现差异。 可以看到显示器实际呈现的内容是正确大小,但在 x 和 y 方向上已移动了 -0.5 个单元格。 但是,除了多采样技术外,这是四边形的最佳近似值。 (请参阅 抗锯齿样本 ,全面覆盖多采样。) 请注意,如果光栅器填充了四边形交叉的每个单元格,则生成的区域将是 5 x 5,而不是所需的 4 x 4。

如果假定屏幕坐标源自显示网格的左上角而不是左上角的像素,则四边形显示与预期完全一致。 但是,当为四边形提供纹理时,差异就变得明显了。 下图显示了将直接映射到象限的 4 x 4 纹理。

4x4 纹理的插图

由于纹理为 4 x 4 纹素,四边形为 4 x 4 像素,因此,无论绘制四边形的屏幕上的位置如何,纹理四边形的显示效果可能都与纹理完全相同。 但是,情况并非如此:甚至位置的轻微变化也会影响纹理的显示方式。 下图显示了在光栅化和纹理化后,如何显示介于 (0, 0) 和 (4, 4) 之间的象限。

从 (0、0) 和 (4、4) 绘制的纹理象限的插图

在上图中绘制的四边形显示具有线性筛选模式和固定寻址模式的纹理输出 (,) 叠加光栅化轮廓。 本文的其余部分确切解释了输出的外观,而不是纹理的外观,但对于想要解决方案的用户来说,下面是:输入四边形的边缘需要位于像素单元格之间的边界线上。 只需将 x 和 y 四边坐标移动 -0.5 个单位,纹素单元格即可完美地覆盖像素单元格,并且可以在屏幕上完美地重新创建四边形。 (本主题的最后一张图显示了更正坐标处的四边形图。)

光栅化输出仅与输入纹理略有相似之处的详细信息与 Direct3D 寻址和采样纹理的方式直接相关。 接下来的内容假设你对 纹理坐标空间双线性纹理筛选有很好的了解。

回到我们对奇怪像素输出的调查,将输出颜色跟踪回像素着色器是有意义的:对于选择为光栅化形状的一部分的每个像素调用像素着色器。 前面图中描绘的蓝色实心四边形可能有一个特别简单的着色器:

float4 SolidBluePS() : COLOR
{ 
    return float4( 0, 0, 1, 1 );
} 

对于纹理象限,必须稍微更改像素着色器:

texture MyTexture;

sampler MySampler = 
sampler_state 
{ 
    Texture = <MyTexture>;
    MinFilter = Linear;
    MagFilter = Linear;
    AddressU = Clamp;
    AddressV = Clamp;
};

float4 TextureLookupPS( float2 vTexCoord : TEXCOORD0 ) : COLOR
{
    return tex2D( MySampler, vTexCoord );
} 

该代码假定 4 x 4 纹理存储在 MyTexture 中。 如图所示,MySampler 纹理采样器设置为在 MyTexture 上执行双线性筛选。 对于每个光栅化像素,像素着色器将调用一次,每次返回的颜色都是 vTexCoord 处的采样纹理颜色。 每次调用像素着色器时,vTexCoord 参数都设置为该像素处的纹理坐标。 这意味着着色器要求纹理采样器在像素的确切位置获取筛选的纹理颜色,如下图所示。

纹理坐标采样位置的插图

叠加) 显示的纹理 (直接在像素位置采样, (显示为黑点) 。 纹理坐标不受光栅化的影响, (它们保留在原始四边形) 的投影屏幕空间中。 黑点显示光栅化像素的位置。 可以通过内插存储在每个顶点上的坐标轻松确定每个像素的纹理坐标: (0,0) 的像素与 (0, 0) 处的顶点重合;因此,该像素处的纹理坐标只是存储在该顶点处的纹理坐标,UV (0.0、0.0) 。 对于 (3、1) 的像素,内插坐标为 UV (0.75,0.25) ,因为该像素位于纹理宽度的四分之三和高度的四分之一处。 这些内插坐标是传递给像素着色器的内容。

纹素不与此示例中的像素对齐;每个像素 (,因此) 的每个采样点都位于四个纹素的角处。 由于筛选模式设置为“线性”,因此采样器将平均共享该角的四个纹素的颜色。 这解释了为什么预期为红色的像素实际上是四分之三的灰色加四分之一的红色,预期为绿色的像素是半灰色加上四分之一的红色加上四分之一的绿色,等等。

若要解决此问题,只需将象限正确映射到要光栅化的像素,从而将纹素正确映射到像素。 下图显示了在 (-0.5、-0.5) 和 (3.5、 3.5) 之间绘制相同象限的结果,这是从一开始就打算的象限。

与光栅化象限匹配的纹理象限的插图

上图演示了从 (-0.5, -0.5) 到 (3.5, 3.5) ) 的四边形 (与光栅化区域完全匹配。

总结

总之,像素和纹素实际上是点,而不是实心块。 屏幕空间源自左上角的像素,但纹理坐标源自纹理网格的左上角。 最重要的是,在转换的屏幕空间中工作时,请记住从顶点位置的 x 和 y 分量中减去 0.5 个单位,以便将纹素与像素正确对齐。

以下代码是偏移 256 乘 256 平方的顶点以在转换的屏幕空间中正确显示 256 乘 256 纹理的示例。

//define FVF with vertex values in transformed screen space
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_TEX1)

struct CUSTOMVERTEX
{
    FLOAT x, y, z, rhw; // position
    FLOAT tu, tv;       // texture coordinates
};

//unadjusted vertex values
float left = 0.0f;
float right = 255.0f;
float top = 0.0f;
float bottom = 255.0f;


//256 by 256 rectangle matching 256 by 256 texture
CUSTOMVERTEX vertices[] =
{
    { left,  top,    0.5f, 1.0f, 0.0f, 0.0f}, // x, y, z, rhw, u, v
    { right, top,    0.5f, 1.0f, 1.0f, 0.0f},
    { right, bottom, 0.5f, 1.0f, 1.0f, 1.0f},
    { left,  top,    0.5f, 1.0f, 0.0f, 0.0f},
    { right, bottom, 0.5f, 1.0f, 1.0f, 1.0f},
    { left,  bottom, 0.5f, 1.0f, 0.0f, 1.0f},
    
};
//adjust all the vertices to correctly line up texels with pixels 
for (int i=0; i<6; i++)
{
    vertices[i].x -= 0.5f;
    vertices[i].y -= 0.5f;
}

纹理坐标