Direktes Zuordnen von Texels zu Pixeln (Direct3D 9)

Beim Rendern der 2D-Ausgabe mit vortransformationierten Scheitelpunkten muss darauf geachtet werden, dass jeder Texelbereich ordnungsgemäß einem einzelnen Pixelbereich entspricht, andernfalls kann es zu Texturverzerrungen kommen. Wenn Sie die Grundlagen des Prozesses verstehen, den Direct3D beim Rastern und Texturieren von Dreiecken befolgt, können Sie sicherstellen, dass Ihre Direct3D-Anwendung die 2D-Ausgabe ordnungsgemäß rendert.

Abbildung einer Anzeige mit einer Auflösung von 6 x 6

Das obige Diagramm zeigt Pixel, die als Quadrate modelliert sind. In Der Realität sind Pixel jedoch Punkte, keine Quadrate. Jedes Quadrat im vorherigen Diagramm gibt den Bereich an, der vom Pixel beleuchtet wird, aber ein Pixel ist immer nur ein Punkt in der Mitte eines Quadrats. Diese Unterscheidung ist zwar klein, aber wichtig. Eine bessere Abbildung derselben Anzeige ist im folgenden Diagramm dargestellt.

Abbildung einer Anzeige, die aus Pixeln besteht

Das obige Diagramm zeigt jedes physische Pixel korrekt als Punkt in der Mitte jeder Zelle. Die Bildschirmraumkoordinate (0, 0) befindet sich direkt am oberen linken Pixel und damit in der Mitte der zelle oben links. Die obere linke Ecke des Displays befindet sich daher bei (-0,5, -0,5), da es sich um 0,5 Zellen nach links und 0,5 Zellen vom oberen linken Pixel handelt. Direct3D rendert ein Quad mit Ecken an (0, 0) und (4, 4), wie in der folgenden Abbildung gezeigt.

Abbildung eines Umrisses eines unrasterisierten Quads zwischen (0, 0) und (4, 4)

Die obige Abbildung zeigt, wo sich das mathematische Quad im Verhältnis zur Anzeige befindet, zeigt jedoch nicht, wie das Quad aussieht, sobald Direct3D es rastert und an die Anzeige sendet. In der Tat ist es für eine Rasteranzeige unmöglich, das Quad genau wie gezeigt zu füllen, da die Kanten des Quads nicht mit den Grenzen zwischen Pixelzellen übereinstimmen. Anders ausgedrückt: Da jedes Pixel nur eine einzelne Farbe anzeigen kann, wird jede Pixelzelle nur mit einer einzigen Farbe gefüllt; Wenn die Anzeige das Quad genau wie dargestellt rendern würde, müssten die Pixelzellen am Rand des Quads zwei unterschiedliche Farben anzeigen: Blau, wo vom Quad bedeckt ist, und Weiß, wo nur der Hintergrund sichtbar ist.

Stattdessen soll die Grafikhardware bestimmen, welche Pixel gefüllt werden sollen, um das Quad anzunähern. Dieser Prozess wird als Rasterung bezeichnet und wird in Rasterungsregeln (Direct3D 9) ausführlich beschrieben. In diesem speziellen Fall ist das rasterte Quad in der folgenden Abbildung dargestellt.

Abbildung eines untexturierten Quads, gezeichnet von (0,0) bis (4,4)

Beachten Sie, dass das an Direct3D übergebene Quad Ecken mit (0, 0) und (4, 4) aufweist, die gerasterte Ausgabe (die obige Abbildung) jedoch Ecken mit (-0,5,-0,5) und (3,5,3,5). Vergleichen Sie die beiden vorherigen Abbildungen, um Unterschiede zu rendern. Sie können sehen, dass das, was die Anzeige tatsächlich rendert, die richtige Größe ist, aber um -0,5 Zellen in x- und y-Richtung verschoben wurde. Mit Ausnahme von Multi-Sampling-Verfahren ist dies jedoch die bestmögliche Annäherung an das Quad. (Ausführliche Informationen zu Multi-Sampling finden Sie im Antialias-Beispiel .) Beachten Sie, dass, wenn der Rasterisierer jede Zelle gefüllt hat, die das Quad gekreuzt hat, der resultierende Bereich die Dimension 5 x 5 anstelle der gewünschten 4 x 4 hat.

Wenn Sie davon ausgehen, dass Bildschirmkoordinaten in der oberen linken Ecke des Anzeigerasters anstelle des oberen linken Pixels entstehen, wird das Quad genau wie erwartet angezeigt. Der Unterschied wird jedoch deutlich, wenn dem Quad eine Textur gegeben wird. Die folgende Abbildung zeigt die 4 x 4-Textur, die Sie direkt dem Quad zuordnen.

Abbildung einer 4x4-Textur

Da die Textur 4 x 4 Texels ist und das Quad 4 x 4 Pixel beträgt, können Sie erwarten, dass das strukturierte Quad genau wie die Textur aussieht, unabhängig von der Position auf dem Bildschirm, an dem das Quad gezeichnet wird. Dies ist jedoch nicht der Fall; selbst geringfügige Positionsänderungen beeinflussen die Darstellung der Textur. Die folgende Abbildung zeigt, wie ein Quad zwischen (0, 0) und (4, 4) angezeigt wird, nachdem sie gerastert und strukturiert wurde.

Abbildung eines strukturierten Quads aus (0, 0) und (4, 4)

Das in der obigen Abbildung gezeichnete Quad zeigt die texturierte Ausgabe (mit einem linearen Filtermodus und einem Clamp-Adressierungsmodus) mit der überlagerten gerasterten Gliederung. Im Rest dieses Artikels wird genau erläutert, warum die Ausgabe so aussieht, wie sie aussieht, anstatt wie die Textur auszusehen, aber für diejenigen, die die Lösung wünschen, ist es hier: Die Kanten des Eingabequaders müssen auf den Begrenzungslinien zwischen Pixelzellen liegen. Durch einfaches Verschieben der x- und y-Quad-Koordinaten um -0,5 Einheiten werden texel-Zellen perfekt Pixelzellen abdecken und das Quad kann perfekt auf dem Bildschirm neu erstellt werden. (Die letzte Abbildung in diesem Thema zeigt das Quad an den korrigierten Koordinaten.)

Die Details, warum die gerasterte Ausgabe nur geringfügige Ähnlichkeiten mit der Eingabetextur aufweist, hängen direkt mit der Art und Weise zusammen, wie Direct3D-Adressen und Beispieltexturen aussehen. Was folgt, setzt voraus, dass Sie über ein gutes Verständnis des Texturkoordinatenraums und der bilinearen Texturfilterung verfügen.

Zurück zu unserer Untersuchung der seltsamen Pixelausgabe, ist es sinnvoll, die Ausgabefarbe auf den Pixel-Shader zurückzuverfolgen: Der Pixel-Shader wird für jedes ausgewählte Pixel aufgerufen, um Teil der gerasterten Form zu sein. Das in einer früheren Abbildung dargestellte einfarbige blaue Quad könnte einen besonders einfachen Shader aufweisen:

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

Für das texturierte Quad muss der Pixel-Shader geringfügig geändert werden:

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 );
} 

Dieser Code setzt voraus, dass die 4 x 4-Textur in MyTexture gespeichert wird. Wie gezeigt, ist der MySampler-Textur-Sampler so festgelegt, dass er bilineare Filterung für MyTexture ausführt. Der Pixel-Shader wird für jedes gerasterte Pixel einmal aufgerufen, und jedes Mal, wenn die zurückgegebene Farbe die texturierte Texturfarbe bei vTexCoord ist. Jedes Mal, wenn der Pixel-Shader aufgerufen wird, wird das vTexCoord-Argument auf die Texturkoordinaten dieses Pixels festgelegt. Das bedeutet, dass der Shader den Textur-Sampler nach der gefilterten Texturfarbe an der genauen Position des Pixels fragt, wie in der folgenden Abbildung beschrieben.

Abbildung von Stichprobenstandorten für Texturkoordinaten

Die Textur (überlagert) wird direkt an Pixelstandorten (als schwarze Punkte dargestellt) abgetastet. Texturkoordinaten werden von der Rasterung nicht beeinflusst (sie verbleiben im projizierten Bildschirmraum des ursprünglichen Quads). Die schwarzen Punkte zeigen an, wo sich die Rasterungspixel befinden. Die Texturkoordinaten an jedem Pixel lassen sich leicht durch Interpolieren der an jedem Scheitelpunkt gespeicherten Koordinaten bestimmen: Das Pixel bei (0,0) fällt mit dem Scheitelpunkt bei (0, 0) zusammen; Daher sind die Texturkoordinaten an diesem Pixel einfach die Texturkoordinaten, die an diesem Scheitelpunkt,UV (0,0, 0,0) gespeichert sind. Für das Pixel bei (3, 1) sind die interpolierten Koordinaten UV (0,75, 0,25), da sich dieses Pixel bei drei Vierteln der Breite der Textur und einem Viertel der Höhe befindet. Diese interpolierten Koordinaten werden an den Pixel-Shader übergeben.

Die Texel sind nicht mit den Pixeln in diesem Beispiel aneinander ausgerichtet. Jedes Pixel (und damit jeder Stichprobenpunkt) wird an der Ecke von vier Texels positioniert. Da der Filtermodus auf Linear festgelegt ist, durchschnittlich der Sampler die Farben der vier Texels, die sich diese Ecke teilen. Dies erklärt, warum das Pixel, das als rot erwartet wird, tatsächlich drei Viertel Grau plus ein Viertel rot ist, das Pixel, das erwartet wird, grün ist ein halbes Grau plus ein Viertel Rot plus ein Viertel Grün usw.

Um dieses Problem zu beheben, müssen Sie nur das Quad den Pixeln, denen es gerastert wird, ordnungsgemäß zuordnen und dadurch die Texels korrekt zu Pixeln zuordnen. Die folgende Abbildung zeigt die Ergebnisse des Zeichnens desselben Quads zwischen (-0,5, -0,5) und (3,5, 3,5), dem von Anfang an vorgesehenen Quad.

Abbildung eines strukturierten Quads, das dem gerasterten Quad entspricht

Die obige Abbildung zeigt, dass das Quad (dargestellt von (-0,5, -0,5) bis (3,5, 3,5)) genau mit dem gerasterten Bereich übereinstimmt.

Zusammenfassung

Zusammenfassend lässt sich sagen, dass Pixel und Texel tatsächlich Punkte sind, keine vollfarbigen Blöcke. Der Bildschirmraum entsteht am oberen linken Pixel, die Texturkoordinaten jedoch in der oberen linken Ecke des Rasters der Textur. Denken Sie am wichtigsten daran, 0,5 Einheiten von den x- und y-Komponenten Ihrer Vertexpositionen zu subtrahieren, wenn Sie im transformierten Bildschirmbereich arbeiten, um Texels richtig an Pixeln auszurichten.

Der folgende Code ist ein Beispiel für das Zurücksetzen der Scheitelpunkte eines Quadrats von 256 x 256, um eine Textur von 256 x 256 im transformierten Bildschirmraum ordnungsgemäß anzuzeigen.

//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;
}

Texturkoordinaten