Porten der Shaderobjekte
Wichtige APIs
Beim Portieren des einfachen Renderers von OpenGL ES 2.0 besteht der erste Schritt darin, die entsprechenden Vertex- und Fragmentshaderobjekte in Direct3D 11 einzurichten und sicherzustellen, dass das Hauptprogramm nach der Kompilierung mit den Shaderobjekten kommunizieren kann.
Hinweis
Haben Sie ein neues Direct3D-Projekt erstellt? Führen Sie andernfalls die Anweisungen unter Erstellen eines neuen DirectX 11-Projekts für Universelle Windows-Plattform (UWP) aus. Bei dieser exemplarischen Vorgehensweise wird davon ausgegangen, dass Sie die DXGI- und Direct3D-Ressourcen zum Zeichnen auf dem Bildschirm erstellt haben und die in der Vorlage bereitgestellt werden.
Ähnlich wie OpenGL ES 2.0 müssen die kompilierten Shader in Direct3D einem Zeichnungskontext zugeordnet sein. Direct3D hat jedoch nicht das Konzept eines Shaderprogrammobjekts per se; Stattdessen müssen Sie die Shader direkt einem ID3D11DeviceContext zuweisen. Dieser Schritt folgt dem OpenGL ES 2.0-Prozess zum Erstellen und Binden von Shaderobjekten und bietet Ihnen die entsprechenden API-Verhaltensweisen in Direct3D.
Anweisungen
Schritt 1: Kompilieren der Shader
In diesem einfachen OpenGL ES 2.0-Beispiel werden die Shader als Textdateien gespeichert und als Zeichenfolgendaten für die Laufzeitkompilierung geladen.
OpenGL ES 2.0: Kompilieren eines Shaders
GLuint __cdecl CompileShader (GLenum shaderType, const char *shaderSrcStr)
// shaderType can be GL_VERTEX_SHADER or GL_FRAGMENT_SHADER. Returns 0 if compilation fails.
{
GLuint shaderHandle;
GLint compiledShaderHandle;
// Create an empty shader object.
shaderHandle = glCreateShader(shaderType);
if (shaderHandle == 0)
return 0;
// Load the GLSL shader source as a string value. You could obtain it from
// from reading a text file or hardcoded.
glShaderSource(shaderHandle, 1, &shaderSrcStr, NULL);
// Compile the shader.
glCompileShader(shaderHandle);
// Check the compile status
glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compiledShaderHandle);
if (!compiledShaderHandle) // error in compilation occurred
{
// Handle any errors here.
glDeleteShader(shaderHandle);
return 0;
}
return shaderHandle;
}
In Direct3D werden Shader während der Laufzeit nicht kompiliert. sie werden immer in CSO-Dateien kompiliert, wenn der Rest des Programms kompiliert wird. Wenn Sie Ihre App mit Microsoft Visual Studio kompilieren, werden die HLSL-Dateien in CSO-Dateien (.cso) kompiliert, die Ihre App laden muss. Stellen Sie sicher, dass Sie diese CSO-Dateien in Ihre App einschließen, wenn Sie sie verpacken!
Beachten Sie: Im folgenden Beispiel wird das Laden und Kompilieren des Shaders asynchron mithilfe des Autoschlüsselworts und der Lambda-Syntax ausgeführt. ReadDataAsync() ist eine Methode, die für die Vorlage implementiert wird, die in einer CSO-Datei als Array von Bytedaten (FileData) liest.
Direct3D 11: Kompilieren eines Shaders
auto loadVSTask = DX::ReadDataAsync(m_projectDir + "SimpleVertexShader.cso");
auto loadPSTask = DX::ReadDataAsync(m_projectDir + "SimplePixelShader.cso");
auto createVSTask = loadVSTask.then([this](Platform::Array<byte>^ fileData) {
m_d3dDevice->CreateVertexShader(
fileData->Data,
fileData->Length,
nullptr,
&m_vertexShader);
auto createPSTask = loadPSTask.then([this](Platform::Array<byte>^ fileData) {
m_d3dDevice->CreatePixelShader(
fileData->Data,
fileData->Length,
nullptr,
&m_pixelShader;
};
Schritt 2: Erstellen und Laden der Vertex- und Fragment-Shader (Pixel)-Shader
OpenGL ES 2.0 hat den Begriff eines Shaders "Program", das als Schnittstelle zwischen dem Hauptprogramm, das auf der CPU ausgeführt wird, und den Shadern dient, die auf der GPU ausgeführt werden. Shader werden kompiliert (oder aus kompilierten Quellen geladen) und einem Programm zugeordnet, das die Ausführung auf der GPU ermöglicht.
OpenGL ES 2.0: Laden der Vertex- und Fragment-Shader in ein Schattierungsprogramm
GLuint __cdecl LoadShaderProgram (const char *vertShaderSrcStr, const char *fragShaderSrcStr)
{
GLuint programObject, vertexShaderHandle, fragmentShaderHandle;
GLint linkStatusCode;
// Load the vertex shader and compile it to an internal executable format.
vertexShaderHandle = CompileShader(GL_VERTEX_SHADER, vertShaderSrcStr);
if (vertexShaderHandle == 0)
{
glDeleteShader(vertexShaderHandle);
return 0;
}
// Load the fragment/pixel shader and compile it to an internal executable format.
fragmentShaderHandle = CompileShader(GL_FRAGMENT_SHADER, fragShaderSrcStr);
if (fragmentShaderHandle == 0)
{
glDeleteShader(fragmentShaderHandle);
return 0;
}
// Create the program object proper.
programObject = glCreateProgram();
if (programObject == 0) return 0;
// Attach the compiled shaders
glAttachShader(programObject, vertexShaderHandle);
glAttachShader(programObject, fragmentShaderHandle);
// Compile the shaders into binary executables in memory and link them to the program object..
glLinkProgram(programObject);
// Check the project object link status and determine if the program is available.
glGetProgramiv(programObject, GL_LINK_STATUS, &linkStatusCode);
if (!linkStatusCode) // if link status <> 0
{
// Linking failed; delete the program object and return a failure code (0).
glDeleteProgram (programObject);
return 0;
}
// Deallocate the unused shader resources. The actual executables are part of the program object.
glDeleteShader(vertexShaderHandle);
glDeleteShader(fragmentShaderHandle);
return programObject;
}
// ...
glUseProgram(renderer->programObject);
Direct3D verfügt nicht über das Konzept eines Shaderprogrammobjekts. Stattdessen werden die Shader erstellt, wenn eine der Shadererstellungsmethoden auf der ID3D11Device-Schnittstelle (z. B. ID3D11Device::CreateVertexShader oder ID3D11Device::CreatePixelShader) aufgerufen wird. Um die Shader für den aktuellen Zeichnungskontext festzulegen, stellen wir diese den entsprechenden ID3D11DeviceContext mit einer set-Shadermethode bereit, z. B. ID3D11DeviceContext::VSSetShader für den Vertex-Shader oder ID3D11DeviceContext::P SSetShader für den Fragmentshader.
Direct3D 11: Legen Sie die Shader für den Zeichnungskontext des Grafikgeräts fest.
m_d3dContext->VSSetShader(
m_vertexShader.Get(),
nullptr,
0);
m_d3dContext->PSSetShader(
m_pixelShader.Get(),
nullptr,
0);
Schritt 3: Definieren der Daten, die den Shadern zur Verfügung stellen sollen
In unserem OpenGL ES 2.0-Beispiel gibt es eine einheitliche Deklaration für die Shaderpipeline:
- u_mvpMatrix: ein 4x4-Array von Floats, das die endgültige Modellansicht-Projektionstransformationsmatrix darstellt, die die Modellkoordinaten für den Würfel verwendet und sie in 2D-Projektionskoordinaten für die Scankonvertierung transformiert.
Und zwei Attributwerte für die Vertexdaten:
- a_position: ein 4-Float-Vektor für die Modellkoordinaten eines Scheitelpunkts.
- a_color: Ein 4-Float-Vektor für den RGBA-Farbwert, der dem Scheitelpunkt zugeordnet ist.
Open GL ES 2.0: GLSL-Definitionen für die Uniforms und Attribute
uniform mat4 u_mvpMatrix;
attribute vec4 a_position;
attribute vec4 a_color;
Die entsprechenden Hauptprogrammvariablen werden in diesem Fall als Felder für das Rendererobjekt definiert. (Verweisen Sie auf die Kopfzeile in Vorgehensweise: Portieren eines einfachen OpenGL ES 2.0-Renderers zu Direct3D 11.) Nachdem wir dies getan haben, müssen wir die Speicherorte im Arbeitsspeicher angeben, an denen das Hauptprogramm diese Werte für die Shaderpipeline liefert, die wir normalerweise direkt vor einem Draw-Aufruf ausführen:
OpenGL ES 2.0: Markieren der Position der Uniform- und Attributdaten
// Inform the shader of the attribute locations
loc = glGetAttribLocation(renderer->programObject, "a_position");
glVertexAttribPointer(loc, 3, GL_FLOAT, GL_FALSE,
sizeof(Vertex), 0);
glEnableVertexAttribArray(loc);
loc = glGetAttribLocation(renderer->programObject, "a_color");
glVertexAttribPointer(loc, 4, GL_FLOAT, GL_FALSE,
sizeof(Vertex), (GLvoid*) (sizeof(float) * 3));
glEnableVertexAttribArray(loc);
// Inform the shader program of the uniform location
renderer->mvpLoc = glGetUniformLocation(renderer->programObject, "u_mvpMatrix");
Direct3D hat nicht das Konzept eines "Attributs" oder eines "uniform" in demselben Sinne (oder zumindest teilt es diese Syntax nicht). Stattdessen enthält sie Konstantenpuffer, dargestellt als Direct3D-Unterressourcen – Ressourcen, die zwischen dem Hauptprogramm und den Shaderprogrammen gemeinsam verwendet werden. Einige dieser Unterressourcen, z. B. Vertexpositionen und -farben, werden als HLSL-Semantik beschrieben. Weitere Informationen zu Konstantenpuffern und HLSL-Semantik im Zusammenhang mit OpenGL ES 2.0-Konzepten finden Sie unter Portframepufferobjekte, Uniforms und Attribute.
Beim Verschieben dieses Prozesses in Direct3D konvertieren wir die Uniform in einen Direct3D-Konstantenpuffer (cbuffer) und weisen ihm ein Register für die Suche mit der HLSL-Registersemantik zu. Die beiden Vertexattribute werden als Eingabeelemente für die Shaderpipelinephasen behandelt und außerdem HLSL-Semantik (POSITION und COLOR0) zugewiesen, die die Shader informieren. Der Pixelshader akzeptiert eine SV_POSITION, wobei das präfix SV_ angibt, dass es sich um einen Systemwert handelt, der von der GPU generiert wird. (In diesem Fall handelt es sich um eine Pixelposition, die während der Scankonvertierung generiert wird.) VertexShaderInput und PixelShaderInput werden nicht als Konstantenpuffer deklariert, da der frühere zum Definieren des Vertexpuffers verwendet wird (siehe Portieren der Vertexpuffer und -daten), und die Daten für letztere werden als Ergebnis einer vorherigen Phase in der Pipeline generiert, was in diesem Fall der Vertex-Shader ist.
Direct3D: HLSL-Definitionen für die Konstantenpuffer und Vertexdaten
cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
matrix mvp;
};
// Per-vertex data used as input to the vertex shader.
struct VertexShaderInput
{
float4 pos : POSITION;
float4 color : COLOR0;
};
// Per-vertex color data passed through the pixel shader.
struct PixelShaderInput
{
float4 pos : SV_POSITION;
float3 color : COLOR0;
};
Weitere Informationen zum Portieren zu Konstantenpuffern und zur Anwendung der HLSL-Semantik, Lesen von Portframepufferobjekten, Uniforms und Attributen.
Hier sind die Strukturen für das Layout der Daten, die mit einem Konstanten- oder Vertexpuffer an die Shaderpipeline übergeben werden.
Direct3D 11: Deklarieren des Layouts für Konstanten- und Vertexpuffer
// Constant buffer used to send MVP matrices to the vertex shader.
struct ModelViewProjectionConstantBuffer
{
DirectX::XMFLOAT4X4 modelViewProjection;
};
// Used to send per-vertex data to the vertex shader.
struct VertexPositionColor
{
DirectX::XMFLOAT4 pos;
DirectX::XMFLOAT4 color;
};
Verwenden Sie die DirectXMath XM*-Typen für Die Konstantenpufferelemente, da sie beim Senden an die Shaderpipeline die richtige Verpackung und Ausrichtung für den Inhalt bereitstellen. Wenn Sie standardmäßige Float-Typen und Arrays der Windows-Plattform verwenden, müssen Sie die Verpackung und Ausrichtung selbst durchführen.
Um einen Konstantenpuffer zu binden, erstellen Sie eine Layoutbeschreibung als CD3D11_BUFFER_DESC Struktur, und übergeben Sie ihn an ID3DDevice::CreateBuffer. Übergeben Sie dann in der Rendermethode den Konstantenpuffer an ID3D11DeviceContext::UpdateSubresource vor dem Zeichnen.
Direct3D 11: Binden des Konstantenpuffers
CD3D11_BUFFER_DESC constantBufferDesc(sizeof(ModelViewProjectionConstantBuffer), D3D11_BIND_CONSTANT_BUFFER);
m_d3dDevice->CreateBuffer(
&constantBufferDesc,
nullptr,
&m_constantBuffer);
// ...
// Only update shader resources that have changed since the last frame.
m_d3dContext->UpdateSubresource(
m_constantBuffer.Get(),
0,
NULL,
&m_constantBufferData,
0,
0);
Der Vertexpuffer wird entsprechend erstellt und aktualisiert und wird im nächsten Schritt erläutert, portiert die Vertexpuffer und -daten.
Nächster Schritt
Porten der Vertexpuffer und -daten
Zugehörige Themen
Gewusst wie: Portieren eines einfachen OpenGL ES 2.0-Renderers zu Direct3D 11