Direktes Zuordnen von Texeln zu Pixeln (Direct3D 9)

Beim Rendern der 2D-Ausgabe mithilfe vorab transformierten Scheitelpunkts muss sichergestellt werden, dass jeder Texelbereich ordnungsgemäß einem einzelnen Pixelbereich entspricht. Andernfalls kann texturbedingte Verzerrung auftreten. Indem Sie die Grundlagen des Prozesses verstehen, dem Direct3D beim Rastern und Texturieren von Dreiecken folgt, 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 Wirklichkeit sind Pixel jedoch Punkte, keine Quadrate. Jedes Quadrat im obigen Diagramm gibt den bereich an, der durch das Pixel dargestellt wird, aber ein Pixel ist immer nur ein Punkt in der Mitte eines Quadrats. Dieser Unterschied ist zwar scheinbar klein, aber wichtig. Eine bessere Darstellung der gleichen Anzeige ist im folgenden Diagramm dargestellt.

Abbildung einer Anzeige, die aus Pixeln besteht

Im obigen Diagramm wird jedes physische Pixel ordnungsgemäß als Punkt in der Mitte jeder Zelle angezeigt. Die Bildschirmraumkoordinate (0, 0) befindet sich direkt am pixel oben links und somit in der Mitte der linken oberen Zelle. Die obere linke Ecke der Anzeige befindet sich daher bei (-0,5, -0,5), da sie 0,5 Zellen links und 0,5 Zellen vom pixel oben links entfernt ist. Direct3D rendert ein Quad mit Ecken bei (0, 0) und (4, 4), wie in der folgenden Abbildung dargestellt.

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. Tatsächlich ist es unmöglich, dass eine Rasteranzeige das Quad genau wie gezeigt ausfüllt, da die Ränder des Quader 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 einzelnen Farbe gefüllt. Wenn die Anzeige das Quad genau wie gezeigt rendern würde, müssten die Pixelzellen am Rand des Quads zwei unterschiedliche Farben anzeigen: Blau, wobei vom Quader abgedeckt ist, und Weiß, wo nur der Hintergrund sichtbar ist.

Stattdessen wird die Grafikhardware damit beschäftigt, zu bestimmen, welche Pixel aufgefüllt werden sollen, um das Quader zu ungefähren. Dieser Prozess wird als Rasterung bezeichnet und unter Rasterregeln (Direct3D 9) ausführlich dargestellt. In diesem speziellen Fall wird das rasterierte Quad in der folgenden Abbildung dargestellt.

Abbildung eines nicht texturierten Quads, das von (0,0) bis (4,4) gezeichnet wird

Beachten Sie, dass das an Direct3D übergebene Quad ecken bei (0, 0) und (4, 4) hat, die rasterierte Ausgabe (die obige Abbildung) jedoch Ecken bei (-0,5,-0,5) und (3,5,3,5) hat. Vergleichen Sie die beiden obigen Abbildungen auf Renderingunterschiede. Sie können sehen, dass das, was die Anzeige tatsächlich rendert, die richtige Größe ist, aber um -0,5 Zellen in die x- und y-Richtung verschoben wurde. Mit Ausnahme von Verfahren mit mehrfacher Stichprobenentnahme ist dies jedoch die bestmögliche Näherung an das Quader. (Ausführliche Informationen zur Mehrfachstichprobe finden Sie im Antialias-Beispiel.) Beachten Sie, dass der resultierende Bereich die Dimension 5 x 5 anstelle der gewünschten 4 x 4 hat, wenn der Rasterizer jede Zelle gefüllt hat, die das Quader überschritten hat.

Wenn Sie davon ausgehen, dass Bildschirmkoordinaten aus der oberen linken Ecke des Anzeigerasters stammen und nicht aus dem pixel oben links, 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 Texel und das Quad 4 x 4 Pixel groß ist, können Sie davon ausgehen, dass das texturierte Quad unabhängig von der Position auf dem Bildschirm, an dem das Quad gezeichnet wird, genau wie die Textur angezeigt wird. Dies ist jedoch nicht der Fall. auch geringfügige Änderungen an der Position beeinflussen, wie die Textur angezeigt wird. Die folgende Abbildung zeigt, wie ein Quad zwischen (0, 0) und (4, 4) nach dem Rastern und Texturieren angezeigt wird.

Abbildung eines strukturierten Quads, das aus (0, 0) und (4, 4) gezeichnet wird

Das in der obigen Abbildung gezeichnete Quader zeigt die texturierte Ausgabe (mit einem linearen Filtermodus und einem Adressierungsmodus für die Klammer) mit der überlagerten rasterierten Kontur. Im restlichen Teil dieses Artikels wird genau erläutert, warum die Ausgabe so aussieht, als wie die Textur aussieht, aber für diejenigen, die die Lösung wünschen, ist dies hier: Die Ränder des Eingabe-Quads müssen auf den Begrenzungslinien zwischen Pixelzellen liegen. Durch einfaches Verschieben der x- und y-Quad-Koordinaten um -0,5 Einheiten decken Texelzellen Pixelzellen perfekt ab, und das Quader kann auf dem Bildschirm perfekt neu erstellt werden. (Die letzte Abbildung in diesem Thema zeigt das Quad an den korrigierten Koordinaten.)

Die Details, warum die rasterierte Ausgabe nur eine geringfügige Ähnlichkeit mit der Eingabetextur auf sich hat, stehen in direktem Zusammenhang mit der Art und Weise, wie Direct3D Texturen adressiert und abwertet. Im Folgenden wird davon ausgegangen, dass Sie über ein gutes Verständnis des Texturkoordinatenraums und der bilinearen Texturfilterung verfügen.

Wenn wir zur Untersuchung der ungewöhnlichen Pixelausgabe zurückkommen, ist es sinnvoll, die Ausgabefarbe zurück zum Pixel-Shader zu verfolgen: Der Pixel-Shader wird für jedes ausgewählte Pixel aufgerufen, das Teil der rasterisierten Form sein soll. Das in einer früheren Abbildung dargestellte vollblaue Quader könnte einen besonders einfachen Shader haben:

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

Für das texturierte Quader 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 );
} 

Bei diesem Code wird davon ausgegangen, dass die 4 x 4-Textur in MyTexture gespeichert ist. Wie gezeigt, ist der MySampler-Textursampler für die bilineare Filterung in MyTexture festgelegt. Der Pixel-Shader wird einmal für jedes rasterierte Pixel aufgerufen, und jedes Mal, wenn die zurückgegebene Farbe die stichprobenisierte Texturfarbe bei vTexCoord ist. Jedes Mal, wenn der Pixel-Shader aufgerufen wird, wird das vTexCoord-Argument auf die Texturkoordinaten an diesem Pixel festgelegt. Das bedeutet, dass der Shader den Texturs sampler nach der gefilterten Texturfarbe an der genauen Position des Pixels fragt, wie in der folgenden Abbildung dargestellt.

Abbildung der Samplingpositionen für Texturkoordinaten

Die Textur (überlagert dargestellt) wird direkt an Pixelpositionen entnommen (als schwarze Punkte dargestellt). Texturkoordinaten sind nicht von der Rasterung betroffen (sie verbleiben im projizierten Bildschirmbereich des ursprünglichen Quadrats). Die schwarzen Punkte zeigen, wo sich die Rasterungspixel befinden. Die Texturkoordinaten an jedem Pixel können leicht bestimmt werden, indem die an jedem Scheitelpunkt gespeicherten Koordinaten interpoliert werden: Das Pixel bei (0,0) stimmt mit dem Scheitelpunkt bei (0, 0) überein. 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 seiner Höhe befindet. Diese interpolierten Koordinaten werden an den Pixel-Shader übergeben.

Die Texel werden nicht mit den Pixeln in diesem Beispiel in Einklang stehen. jedes Pixel (und somit jeder Samplingpunkt) wird an der Ecke von vier Texeln positioniert. Da der Filtermodus auf Linear festgelegt ist, durchschnittliche der Sampler die Farben der vier Texel, die diese Ecke teilen. Dies erklärt, warum das pixel, das als rot erwartet wird, tatsächlich drei Viertel grau plus ein Viertes Rot ist, das Pixel, das grün sein soll, halbgrau plus ein Viertes Rot plus ein Viertes Grün ist und so weiter.

Um dieses Problem zu beheben, müssen Sie das Quad nur den Pixeln zuordnen, denen es rastert, und dadurch die Texel ordnungsgemäß Pixeln zuordnen. Die folgende Abbildung zeigt die Ergebnisse des Zeichnens des gleichen Quads zwischen (-0,5, -0,5) und (3,5, 3,5). Dies ist das Quad, das von Anfang an vorgesehen ist.

Abbildung eines strukturierten Quads, das dem rasterierten Quad entspricht

In der obigen Abbildung wird veranschaulicht, dass das Quad (dargestellt von (-0,5, -0,5) bis (3,5, 3,5) genau mit dem rasterisierten Bereich entspricht.

Zusammenfassung

Zusammenfassend sind Pixel und Texel tatsächlich Punkte, keine soliden Blöcke. Der Bildschirmbereich stammt aus dem Pixel oben links, texturkoordinaten stammen jedoch in der linken oberen Ecke des Rasters der Textur. Denken Sie vor allem daran, 0,5 Einheiten von den x- und y-Komponenten Ihrer Scheitelpunktpositionen zu subtrahieren, wenn Sie im transformierten Bildschirmbereich arbeiten, um Texel ordnungsgemäß an Pixeln auszurichten.

Der folgende Code ist ein Beispiel für das Deaktivieren der Scheitelungen eines Quadrats von 256 durch 256, um eine Textur von 256 durch 256 im transformierten Bildschirmbereich 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