Поделиться через


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

При отрисовке 2D-выходных данных с помощью предварительно преобразованных вершин необходимо обеспечить правильность соответствия каждой области текселя одной области пикселей, в противном случае может возникнуть искажение текстур. Понимая основы процесса, которому 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. Однако за исключением методов многократной выборки, это лучшее возможное приближение к квадрату. (См. пример устранения сглаживания для тщательного охвата мультисемплирования.) Помните, что если растризатор заполнял каждую ячейку, которую пересекает квадрат, результирующая область будет иметь размер 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), так как этот пиксель расположен на трех четвертях ширины текстуры и одной четверти ее высоты. Эти интерполированные координаты — это то, что передается шейдеру пикселей.

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

Чтобы устранить эту проблему, все, что необходимо сделать, правильно сопоставить квадрат с пикселями, с которыми он будет растеризован, и таким образом правильно сопоставить тексели с пикселями. На следующем рисунке показаны результаты рисования одного и того же квадрата между (-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;
}

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