Прямое сопоставление текселей с пикселями (Direct3D 9)

При отрисовке двухd-выходных данных с помощью предварительно преобразованных вершин необходимо соблюдать осторожность, чтобы каждая область текселя правильно соответствовала одной области пикселей, в противном случае может возникнуть искажение текстуры. Понимая основы процесса, которым следует Direct3D при растеризации и форматировании треугольников, вы можете убедиться, что приложение Direct3D правильно отрисовывает 2D-выходные данные.

Иллюстрация дисплея с разрешением 6x6

На предыдущей схеме показаны пиксели, которые смоделированы в виде квадратов. Однако в действительности пиксели являются точками, а не квадратами. Каждый квадрат на предыдущей схеме обозначает область, освещенную пикселем, но пиксель всегда является лишь точкой в центре квадрата. Это различие, хотя и кажется небольшим, имеет важное значение. На следующей схеме показана более удобная иллюстрация того же дисплея.

иллюстрация дисплея, состоящего из пикселей

На предыдущей схеме каждый физический пиксель правильно показан как точка в центре каждой ячейки. Координата экранного пространства (0, 0) находится непосредственно в левом верхнем пикселе и, следовательно, в центре левой верхней ячейки. Поэтому верхний левый угол экрана имеет значение (-0,5, -0,5), так как он составляет 0,5 ячейки слева и 0,5 ячейки от верхнего левого пикселя. Direct3D отрисовывает четырехугольник с углами (0, 0) и (4, 4), как показано на следующем рисунке.

иллюстрация контура нерастеризованного четырехугольника между (0, 0) и (4, 4)

На предыдущем рисунке показано, где математический четырехугольник находится по отношению к экрану, но не показывает, как он будет выглядеть после того, как Direct3D растеризует его и отправит на дисплей. На самом деле растровый дисплей не может заполнить четырехугольник точно так, как показано, так как края четырехугольника не совпадают с границами между ячейками пикселей. Другими словами, так как каждый пиксель может отображать только один цвет, каждая ячейка пикселя заполняется только одним цветом; Если бы дисплей отрисовывал четырехугольник точно так, как показано, пиксельные ячейки вдоль края четырехугольника должны были бы отображать два различных цвета: синий, где покрыт квадроциклом, и белый, где виден только фон.

Вместо этого графическому оборудованию поручено определить, какие пиксели должны быть заполнены для приближения к четырехугольнику. Этот процесс называется растеризацией и подробно описан в разделе Правила растеризации (Direct3D 9). В этом конкретном случае растровый четырехугольник показан на следующем рисунке.

Иллюстрация нетекстурированного четырехугольника, нарисованная от (0,0) до (4,4)

Обратите внимание, что четырехугольник, передаваемый в Direct3D, имеет углы в (0, 0) и (4, 4), но растрированные выходные данные (на предыдущем рисунке) имеют углы в (-0,5,-0,5) и (3,5,3,5). Сравните предыдущие две иллюстрации для отрисовки различий. Вы можете видеть, что отображение на самом деле является правильным размером, но было смещено на -0,5 ячейки в направлении x и y. Однако, за исключением методов множественной выборки, это наиболее возможное приближение к четырехугольнику. (Подробное описание множественной выборки см. в примере Antialias .) Имейте в виду, что если растеризатор заполнил каждую ячейку, пересекаемую четырьмя, результирующая область будет иметь размерность 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 quad на -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); Таким образом, координаты текстуры в этом пикселе являются просто координатами текстуры, хранящимися в этой вершине, УФ (0,0, 0,0). Для пикселя в (3, 1) интерполированные координаты являются УФ (0,75, 0,25), так как этот пиксель находится в трех четвертях ширины текстуры и одной четверти ее высоты. Эти интерполированные координаты передаются в шейдер пикселей.

Тексели не выстраивается в пиксели в этом примере; каждый пиксель (и, следовательно, каждая точка выборки) располагается в углу четырех текселей. Так как для режима фильтрации задано значение Линейный, средство выборки усреднёт цвета четырех текселей, совместно использующих этот угол. Это объясняет, почему пиксель, как ожидается, будет красным на самом деле три четверти серого плюс одна четверти красного цвета, пиксель, как ожидается, будет зеленым наполовину серым плюс одна четвертая красная плюс одна четвертая зеленая, и так далее.

Чтобы устранить эту проблему, достаточно правильно сопоставить четырехугольник с пикселями, в которых он будет растеризован, и, таким образом, правильно сопоставить тексели с пикселями. На следующем рисунке показаны результаты рисования одного и того же четырехугольника между (-0,5, -0,5) и (3,5, 3,5), который является четырехзначным с самого начала.

иллюстрация текстурированного четырехугольника, соответствующего растровой четверке

На приведенном выше рисунке показано, что четырехугольник (показан от (-0,5, -0,5) до (3,5, 3,5)) точно соответствует растеризированной области.

Сводка

Таким образом, пиксели и тексели на самом деле являются точками, а не сплошными блоками. Экранное пространство начинается с верхнего левого пикселя, а координаты текстуры — в левом верхнем углу сетки текстуры. Самое главное, не забывайте вычитать 0,5 единицы из компонентов x и y позиций вершин при работе в преобразованном пространстве экрана, чтобы правильно выровнять тексели по пикселям.

Следующий код представляет собой пример смещения вершин квадрата 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;
}

Координаты текстуры