Freigeben über


Vergleichen von OpenGL ES 2.0-Puffern, Uniforms und Vertexattributen mit Direct3D

Wichtige APIs

Während des Portierens von OpenGL ES 2.0 zu Direct3D 11 müssen Sie die Syntax und das API-Verhalten ändern, um Daten zwischen der App und den Shaderprogrammen zu übergeben.

In OpenGL ES 2.0 werden Daten auf vier Arten an Und aus Shaderprogrammen übergeben: als Uniforms für Konstantendaten, als Attribute für Vertexdaten als Pufferobjekte für andere Ressourcendaten (z. B. Texturen). In Direct3D 11 werden diese in etwa Konstantenpuffern, Vertexpuffern und Unterressourcen zugeordnet. Trotz der oberflächlichen Gemeinsamkeit werden sie in der Verwendung ganz anders behandelt.

Hier ist die grundlegende Zuordnung.

OpenGL ES 2.0 Direct3D 11
uniform Konstantenpufferfeld (cbuffer).
Attribut Vertexpufferelementfeld, das durch ein Eingabelayout festgelegt und mit einer bestimmten HLSL-Semantik gekennzeichnet ist.
Buffer-Objekt Puffer; Eine allgemeine Pufferdefinition finden Sie unter D3D11_SUBRESOURCE_DATA und D3D11_BUFFER_DESC.
Framepufferobjekt (FBO) Renderziel(n); Siehe ID3D11RenderTargetView mit ID3D11Texture2D.
Hintergrundpuffer Swapchain mit "Hintergrundpuffer"-Oberfläche; Siehe IDXGISwapChain1 mit angefügtem IDXGISurface1.

 

Portpuffer

In OpenGL ES 2.0 folgt der Prozess zum Erstellen und Binden beliebiger Pufferarten im Allgemeinen diesem Muster.

  • Rufen Sie glGenBuffers auf, um einen oder mehrere Puffer zu generieren und die Handles an sie zurückzugeben.
  • Rufen Sie glBindBuffer auf, um das Layout eines Puffers zu definieren, z. B. GL_ELEMENT_ARRAY_BUFFER.
  • Rufen Sie glBufferData auf, um den Puffer mit bestimmten Daten (z. B. Vertexstrukturen, Indexdaten oder Farbdaten) in einem bestimmten Layout aufzufüllen.

Der am häufigsten verwendete Puffertyp ist der Vertexpuffer, der die Positionen der Scheitelpunkte in einem Koordinatensystem minimal enthält. In der Regel wird ein Scheitelpunkt durch eine Struktur dargestellt, die die Positionskoordinaten, einen normalen Vektor für die Vertexposition, einen Tangensvektor an die Vertexposition und textursuchkoordinaten (uv) enthält. Der Puffer enthält eine zusammenhängende Liste dieser Scheitelpunkte in einer bestimmten Reihenfolge (z. B. dreieckige Liste, Streifen oder Fächer), die zusammen die sichtbaren Polygone in Ihrer Szene darstellen. (In Direct3D 11 und OpenGL ES 2.0 ist es ineffizient, mehrere Vertexpuffer pro Draw-Aufruf zu verwenden.)

Hier sehen Sie ein Beispiel für einen Vertexpuffer und einen Indexpuffer, der mit OpenGL ES 2.0 erstellt wurde:

OpenGL ES 2.0: Erstellen und Auffüllen eines Vertexpuffers und eines Indexpuffers.

glGenBuffers(1, &renderer->vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, renderer->vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * CUBE_VERTICES, renderer->vertices, GL_STATIC_DRAW);

glGenBuffers(1, &renderer->indexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, renderer->indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int) * CUBE_INDICES, renderer->vertexIndices, GL_STATIC_DRAW);

Andere Puffer umfassen Pixelpuffer und Zuordnungen, z. B. Texturen. Die Shaderpipeline kann in Texturpuffern (pixmaps) oder Pufferobjekte rendern und diese Puffer in zukünftigen Shaderdurchläufen verwenden. Im einfachsten Fall lautet der Anruffluss:

  • Rufen Sie glGenFramebuffers auf, um ein Framepufferobjekt zu generieren.
  • Rufen Sie glBindFramebuffer auf, um das Framepufferobjekt zum Schreiben zu binden.
  • Rufen Sie glFramebufferTexture2D auf, um in eine angegebene Texturzuordnung zu zeichnen.

In Direct3D 11 werden Pufferdatenelemente als "Unterressourcen" betrachtet und können von einzelnen Vertexdatenelementen bis hin zu MIP-Map-Texturen reichen.

Direct3D 11: Erstellen und Auffüllen eines Vertexpuffers und eines Indexpuffers.

D3D11_SUBRESOURCE_DATA vertexBufferData = {0};
vertexBufferData.pSysMem = cubeVertices;
vertexBufferData.SysMemPitch = 0;
vertexBufferData.SysMemSlicePitch = 0;
CD3D11_BUFFER_DESC vertexBufferDesc(sizeof(cubeVertices), D3D11_BIND_VERTEX_BUFFER);

m_d3dDevice->CreateBuffer(
  &vertexBufferDesc,
  &vertexBufferData,
  &m_vertexBuffer);

m_indexCount = ARRAYSIZE(cubeIndices);

D3D11_SUBRESOURCE_DATA indexBufferData = {0};
indexBufferData.pSysMem = cubeIndices;
indexBufferData.SysMemPitch = 0;
indexBufferData.SysMemSlicePitch = 0;
CD3D11_BUFFER_DESC indexBufferDesc(sizeof(cubeIndices), D3D11_BIND_INDEX_BUFFER);

m_d3dDevice->CreateBuffer(
  &indexBufferDesc,
  &indexBufferData,
  &m_indexBuffer);
    

Schreibbare Pixelpuffer oder Zuordnungen, z. B. ein Framepuffer, können als ID3D11Texture2D-Objekte erstellt werden. Diese können als Ressourcen an eine ID3D11RenderTargetView oder ID3D11ShaderResourceView gebunden werden, die nach dem Zeichnen mit der zugeordneten Swapchain angezeigt oder an einen Shader übergeben werden kann.

Direct3D 11: Erstellen eines Framepufferobjekts.

ComPtr<ID3D11RenderTargetView> m_d3dRenderTargetViewWin;
// ...
ComPtr<ID3D11Texture2D> frameBuffer;

m_swapChainCoreWindow->GetBuffer(0, IID_PPV_ARGS(&frameBuffer));
m_d3dDevice->CreateRenderTargetView(
  frameBuffer.Get(),
  nullptr,
  &m_d3dRenderTargetViewWin);

Ändern von Uniform- und Uniform-Pufferobjekten in Direct3D-Konstantenpuffer

In Open GL ES 2.0 sind Uniforms der Mechanismus zum Bereitstellen von Konstantendaten für einzelne Shaderprogramme. Diese Daten können von den Shadern nicht geändert werden.

Das Festlegen einer Uniform umfasst in der Regel die Bereitstellung einer der glUniform*-Methoden mit dem Uploadspeicherort in der GPU zusammen mit einem Zeiger auf die Daten im App-Speicher. Nachdem die glUniform*-Methode ausgeführt wurde, befinden sich die uniform-Daten im GPU-Speicher und können von den Shadern aufgerufen werden, die diese Uniform deklariert haben. Sie werden davon ausgegangen, dass die Daten so verpackt sind, dass der Shader sie basierend auf der uniform-Deklaration im Shader (mithilfe kompatibler Typen) interpretieren kann.

OpenGL ES 2.0 Erstellen einer einheitlichen Und Hochladen von Daten in die Datei

renderer->mvpLoc = glGetUniformLocation(renderer->programObject, "u_mvpMatrix");

// ...

glUniformMatrix4fv(renderer->mvpLoc, 1, GL_FALSE, (GLfloat*) &renderer->mvpMatrix.m[0][0]);

In der GLSL eines Shaders sieht die entsprechende einheitliche Deklaration wie folgt aus:

Open GL ES 2.0: EINHEITLICHE GLSL-Deklaration

uniform mat4 u_mvpMatrix;

Direct3D bezeichnet einheitliche Daten als "Konstantenpuffer", die wie Uniforms konstanten Daten enthalten, die einzelnen Shadern zur Verfügung gestellt werden. Wie bei uniform-Puffern ist es wichtig, die Konstantenpufferdaten im Arbeitsspeicher identisch mit der Art und Weise zu packen, wie der Shader sie interpretiert. Die Verwendung von DirectXMath-Typen (z . B. XMFLOAT4) anstelle von Plattformtypen (z . B. float* oder float[4]) garantiert eine ordnungsgemäße Ausrichtung des Datenelements.

Konstantenpuffer müssen über ein zugeordnetes GPU-Register verfügen, das verwendet wird, um auf diese Daten auf der GPU zu verweisen. Die Daten werden wie durch das Layout des Puffers angegeben in den Registerspeicherort gepackt.

Direct3D 11: Erstellen eines Konstantenpuffers und Hochladen von Daten darauf

struct ModelViewProjectionConstantBuffer
{
     DirectX::XMFLOAT4X4 mvp;
};

// ...

ModelViewProjectionConstantBuffer   m_constantBufferData;

// ...

XMStoreFloat4x4(&m_constantBufferData.mvp, mvpMatrix);

CD3D11_BUFFER_DESC constantBufferDesc(sizeof(ModelViewProjectionConstantBuffer), D3D11_BIND_CONSTANT_BUFFER);
m_d3dDevice->CreateBuffer(
  &constantBufferDesc,
  nullptr,
  &m_constantBuffer);

In der HLSL eines Shaders sieht die entsprechende Konstantenpufferdeklaration wie folgt aus:

Direct3D 11: HLSL-Deklaration des Konstantenpuffers

cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
  matrix mvp;
};

Beachten Sie, dass für jeden Konstantenpuffer ein Register deklariert werden muss. Unterschiedliche Direct3D-Featureebenen weisen unterschiedliche maximal verfügbare Register auf. Überschreiten Sie daher nicht die maximale Anzahl für die niedrigste Featureebene, auf die Sie abzielen.

Portieren von Vertexattributen zu direct3D-Eingabelayouts und HLSL-Semantik

Da Vertexdaten von der Shaderpipeline geändert werden können, erfordert OpenGL ES 2.0, dass Sie sie als "Attribute" anstelle von "uniforms" angeben. (Dies hat sich in späteren Versionen von OpenGL und GLSL geändert.) Vertexspezifische Daten wie die Vertexposition, Normalwerte, Tangenten und Farbwerte werden den Shadern als Attributwerte bereitgestellt. Diese Attributwerte entsprechen bestimmten Offsets für jedes Element in den Vertexdaten; Beispielsweise könnte das erste Attribut auf die Positionskomponente eines einzelnen Scheitelpunkts und die zweite auf die Normale verweisen usw.

Der grundlegende Prozess zum Verschieben der Vertexpufferdaten aus dem Hauptspeicher in die GPU sieht wie folgt aus:

  • Laden Sie die Vertexdaten mit glBindBuffer hoch.
  • Rufen Sie den Speicherort der Attribute auf der GPU mit glGetAttribLocation ab. Rufen Sie es für jedes Attribut im Vertexdatenelement auf.
  • Rufen Sie glVertexAttribPointer auf, um die richtige Attributgröße und den richtigen Offset innerhalb eines einzelnen Vertexdatenelements festzulegen. Gehen Sie dazu für jedes Attribut vor.
  • Aktivieren Sie die Vertexdatenlayoutinformationen mit glEnableVertexAttribArray.

OpenGL ES 2.0: Hochladen von Vertexpufferdaten in das Shader-Attribut

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, renderer->vertexBuffer);
loc = glGetAttribLocation(renderer->programObject, "a_position");

glVertexAttribPointer(loc, 3, GL_FLOAT, GL_FALSE, 
  sizeof(Vertex), 0);
loc = glGetAttribLocation(renderer->programObject, "a_color");
glEnableVertexAttribArray(loc);

glVertexAttribPointer(loc, 4, GL_FLOAT, GL_FALSE, 
  sizeof(Vertex), (GLvoid*) (sizeof(float) * 3));
glEnableVertexAttribArray(loc);

Jetzt deklarieren Sie in Ihrem Vertex-Shader Attribute mit denselben Namen, die Sie im Aufruf von glGetAttribLocation definiert haben.

OpenGL ES 2.0: Deklarieren eines Attributs in GLSL

attribute vec4 a_position;
attribute vec4 a_color;                     

In einigen Fällen gilt derselbe Prozess für Direct3D. Anstelle von Attributen werden Vertexdaten in Eingabepuffern bereitgestellt, die Vertexpuffer und die entsprechenden Indexpuffer enthalten. Da Direct3D jedoch nicht über die Deklaration "Attribut" verfügt, müssen Sie ein Eingabelayout angeben, das die einzelne Komponente der Datenelemente im Vertexpuffer und die HLSL-Semantik deklariert, die angibt, wo und wie diese Komponenten vom Vertex-Shader interpretiert werden sollen. Die HLSL-Semantik erfordert, dass Sie die Verwendung jeder Komponente mit einer bestimmten Zeichenfolge definieren, die das Shadermodul über seinen Zweck informiert. Vertexpositionsdaten werden z. B. als POSITION markiert, normale Daten werden als NORMAL markiert, und Vertexfarbdaten werden als COLOR markiert. (Andere Shaderphasen erfordern auch bestimmte Semantik, und diese Semantik weisen unterschiedliche Interpretationen auf der Grundlage der Shaderphase auf.) Weitere Informationen zur HLSL-Semantik finden Sie unter Portieren Der Shaderpipeline und der HLSL-Semantik.

Zusammen wird der Prozess des Festlegens der Vertex- und Indexpuffer und das Festlegen des Eingabelayouts als "Eingabeassembly" (IA)-Phase der Direct3D-Grafikpipeline bezeichnet.

Direct3D 11: Konfigurieren der Eingabeassemblyphase

// Set up the IA stage corresponding to the current draw operation.
UINT stride = sizeof(VertexPositionColor);
UINT offset = 0;
m_d3dContext->IASetVertexBuffers(
        0,
        1,
        m_vertexBuffer.GetAddressOf(),
        &stride,
        &offset);

m_d3dContext->IASetIndexBuffer(
        m_indexBuffer.Get(),
        DXGI_FORMAT_R16_UINT,
        0);

m_d3dContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_d3dContext->IASetInputLayout(m_inputLayout.Get());

Ein Eingabelayout wird deklariert und einem Vertex-Shader zugeordnet, indem das Format des Vertexdatenelements und die für jede Komponente verwendete Semantik deklariert wird. Das in der D3D11_INPUT_ELEMENT_DESC beschriebene Vertexelementdatenlayout muss dem Layout der entsprechenden Struktur entsprechen. Hier erstellen Sie ein Layout für Vertexdaten mit zwei Komponenten:

  • Eine Vertexpositionskoordinate, dargestellt im Hauptspeicher als XMFLOAT3, bei der es sich um ein ausgerichtetes Array von 32-Bit-Gleitkommawerten für die Koordinaten (x, y, z) handelt.
  • Ein Vertexfarbwert, dargestellt als XMFLOAT4, bei dem es sich um ein ausgerichtetes Array mit 4 32-Bit-Gleitkommawerten für die Farbe (RGBA) handelt.

Sie weisen jeweils eine Semantik sowie einen Formattyp zu. Anschließend übergeben Sie die Beschreibung an ID3D11Device1::CreateInputLayout. Das Eingabelayout wird verwendet, wenn wir ID3D11DeviceContext1::IASetInputLayout aufrufen, wenn Sie die Eingabeassembly während unserer Rendermethode einrichten.

Direct3D 11: Beschreiben eines Eingabelayouts mit spezifischer Semantik

ComPtr<ID3D11InputLayout> m_inputLayout;

// ...

const D3D11_INPUT_ELEMENT_DESC vertexDesc[] = 
{
  { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,  D3D11_INPUT_PER_VERTEX_DATA, 0 },
  { "COLOR",    0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};

m_d3dDevice->CreateInputLayout(
  vertexDesc,
  ARRAYSIZE(vertexDesc),
  fileData->Data,
  fileData->Length,
  &m_inputLayout);

// ...
// When we start the drawing process...

m_d3dContext->IASetInputLayout(m_inputLayout.Get());

Schließlich stellen Sie sicher, dass der Shader die Eingabedaten verstehen kann, indem Sie die Eingabe deklarieren. Die im Layout zugewiesene Semantik wird verwendet, um die richtigen Speicherorte im GPU-Speicher auszuwählen.

Direct3D 11: Deklarieren von Shadereingabedaten mit HLSL-Semantik

struct VertexShaderInput
{
  float3 pos : POSITION;
  float3 color : COLOR;
};