Global Shaders und Compute Shader

Aus UnrealWiki

Hinweis: Stand: Unreal Engine 5.8. Dieser Artikel fasst die aktuelle offizielle Epic-Dokumentation zusammen.

Global Shaders sind Shader, die in Unreal Engine direkt über C++ registriert und verwendet werden. Sie sind nicht an ein Material oder ein bestimmtes Mesh gebunden und eignen sich deshalb für allgemeine Rendering- und Compute-Aufgaben wie Post-Processing, Bildoperationen, Buffer-Verarbeitung oder eigene Render-Passes.

Artikelstatus
Bezugsstand Unreal Engine 5.8
Letzte Aktualisierung 2. September 2026
Zentrale Klassen und Hilfen FGlobalShader, FComputeShaderUtils, FRDGBuilder
Shader-Dateityp .usf (Unreal Shader File)

Einsatzgebiete

Global Shaders kommen zum Einsatz, wenn eine Aufgabe nicht sinnvoll über den Material Editor abgebildet werden kann oder bewusst unabhängig von Materialien und Meshes bleiben soll. Typische Beispiele sind:

  • Compute-Berechnungen auf Buffern oder Texturen
  • eigene Post-Processing-Schritte
  • Fullscreen-Passes
  • Erzeugen, Kopieren oder Umwandeln von GPU-Daten
  • Debug- und Visualisierungs-Passes
  • experimentelle Rendering-Pipelines

Ein Compute Shader ist dabei ein möglicher Shader-Typ. Er verarbeitet Daten in frei definierbaren Thread-Gruppen und erzeugt nicht unmittelbar Rastergrafik.

Bausteine eines Global Shaders

Ein Global Shader besteht in der Regel aus mehreren Teilen:

  1. einer Shader-Datei mit HLSL-Code (.usf)
  2. einer von FGlobalShader abgeleiteten C++-Klasse
  3. einer Parameterstruktur für Eingaben und Ausgaben
  4. einem Registrierungs-Makro, das Klasse, Datei, Einstiegspunkt und Shader-Stufe verbindet
  5. Code, der den Shader auswählt, Parameter bindet und den Dispatch oder Draw-Aufruf ausführt

Speicherort der Shader-Dateien

Engine-eigene Shader liegen unter Engine/Shaders. Shader eines Plugins gehören in dessen Shaders-Verzeichnis. Die Trennung hält experimentellen Code von den Engine-Dateien fern und erleichtert Updates sowie Versionsverwaltung.

Minimaler Compute Shader

Das folgende vereinfachte Beispiel verdoppelt jeden Wert eines Eingabebuffers. Es dient der Orientierung und muss an die konkrete Modul- und Ressourcenstruktur angepasst werden.

StructuredBuffer<uint> InputValues;
RWStructuredBuffer<uint> OutputValues;
uint ElementCount;

[numthreads(64, 1, 1)]
void MainCS(uint3 DispatchThreadId : SV_DispatchThreadID)
{
    const uint Index = DispatchThreadId.x;
    if (Index >= ElementCount)
    {
        return;
    }

    OutputValues[Index] = InputValues[Index] * 2;
}

numthreads(64, 1, 1) legt fest, dass jede Thread-Gruppe 64 Threads in X-Richtung enthält. Die Gesamtzahl der Gruppen muss auf der CPU-Seite passend zur Elementzahl berechnet werden.

C++-Repräsentation

Die C++-Klasse beschreibt den Shader gegenüber Unreal und definiert seine Parameter. Ein typisches Grundmuster sieht so aus:

class FExampleComputeShader : public FGlobalShader
{
    DECLARE_GLOBAL_SHADER(FExampleComputeShader);
    SHADER_USE_PARAMETER_STRUCT(FExampleComputeShader, FGlobalShader);

    BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
        SHADER_PARAMETER(uint32, ElementCount)
        SHADER_PARAMETER_SRV(StructuredBuffer<uint32>, InputValues)
        SHADER_PARAMETER_UAV(RWStructuredBuffer<uint32>, OutputValues)
    END_SHADER_PARAMETER_STRUCT()
};

IMPLEMENT_GLOBAL_SHADER(
    FExampleComputeShader,
    "/Plugin/Example/Private/ExampleCompute.usf",
    "MainCS",
    SF_Compute
);

Wichtige Punkte:

  • DECLARE_GLOBAL_SHADER deklariert den Shader-Typ.
  • SHADER_USE_PARAMETER_STRUCT aktiviert die strukturierte Parameterbindung.
  • BEGIN_SHADER_PARAMETER_STRUCT beschreibt Konstanten, SRVs und UAVs.
  • IMPLEMENT_GLOBAL_SHADER verbindet C++-Klasse, virtuellen Shader-Pfad, Einstiegspunkt und Shader-Stufe.
  • SF_Compute kennzeichnet den Shader als Compute Shader.

Je nach Modulgrenze kann statt DECLARE_GLOBAL_SHADER eine exportierte Shader-Deklaration nötig sein.

Kompilierungsbedingungen

Nicht jeder Shader sollte für jede Plattform oder jedes Feature Level gebaut werden. Dafür kann die Klasse eine Kompilierungsbedingung definieren. So lässt sich beispielsweise verhindern, dass ein Compute Shader für eine Plattform ohne die benötigten Fähigkeiten kompiliert wird.

Auch Compile-Time-Definitionen können aus C++ an den Shader übergeben werden. Das ist nützlich für feste Gruppengrößen, optionale Codepfade oder plattformspezifische Varianten.

Das Modul, das einen neuen Shader-Typ registriert, muss früh genug geladen werden. Wird der Typ erst nach der Initialisierung der Engine bekannt, kann Unreal die Registrierung mit einem Assert ablehnen. Für Shader-Module ist deshalb eine frühe Ladephase wie PostConfigInit relevant.

Dispatch über das RHI

FComputeShaderUtils::Dispatch sendet einen Compute Shader mit Parametern und Gruppenzahl an eine FRHIComputeCommandList. Das ist die direkte Variante auf RHI-Ebene.

const FIntVector GroupCount =
    FComputeShaderUtils::GetGroupCount(ElementCount, 64);

FComputeShaderUtils::Dispatch(
    RHICmdList,
    ComputeShader,
    Parameters,
    GroupCount
);

Die Hilfsfunktion GetGroupCount rundet die benötigte Gruppenzahl passend auf. Der Bounds-Check im Shader bleibt trotzdem notwendig, weil die letzte Gruppe mehr Threads als verbleibende Elemente enthalten kann.

Dispatch über den Render Dependency Graph

Für regulären High-Level-Rendering-Code empfiehlt Unreal den Render Dependency Graph (RDG). Ein Compute-Pass kann dort über FComputeShaderUtils::AddPass registriert werden.

RDG kennt dadurch die beteiligten Ressourcen und kann unter anderem:

  • Abhängigkeiten zwischen Passes bestimmen
  • Ressourcenübergänge und Barriers verwalten
  • ungenutzte Passes entfernen
  • Lebenszeiten temporärer Ressourcen optimieren
  • geeignete Arbeit parallelisieren
  • Async-Compute-Passes synchronisieren

Die Parameter müssen alle verwendeten RDG-Ressourcen korrekt deklarieren. Innerhalb des Passes darf nur auf Ressourcen zugegriffen werden, die über die Parameterstruktur als Abhängigkeit bekannt sind.

Shader-Entwicklung und Fehlersuche

Für die Entwicklung sind folgende Maßnahmen hilfreich:

  • r.ShaderDevelopmentMode=1 in ConsoleVariables.ini aktivieren, um ausführlichere Shader-Compile-Logs zu erhalten.
  • Einstiegspunkt, virtuellen Shader-Pfad und Shader-Stufe kontrollieren.
  • Sicherstellen, dass das Plugin und alle benötigten Module als Abhängigkeiten eingetragen sind.
  • Modul-Ladephase prüfen, wenn Unreal meldet, dass ein Shader-Typ zu spät registriert wurde.
  • Parameterstruktur zwischen HLSL und C++ sorgfältig abgleichen.
  • Thread-Gruppengröße und Bounds-Checks prüfen.
  • Ressourcenstatus, SRV-/UAV-Bindung und Synchronisation kontrollieren.
  • Bei RDG-Passes aussagekräftige Event-Namen verwenden und bei Bedarf RDG Insights einsetzen.

Bezug zum Heterogeneous-Multi-GPU-Projekt

Für das Projekt UE5 Heterogeneous Multi-GPU ist der Global-Shader-Weg besonders interessant: Unreal kann den Shader wie gewohnt kompilieren und das resultierende Shader-Bytecode-Blob bereitstellen. Dieses Blob kann anschließend als Grundlage für eine Compute Pipeline State auf dem separaten Worker-Device dienen.

Der geplante erste Test lässt sich in folgende Schritte zerlegen:

  1. einfachen Global Compute Shader in einem früh geladenen Modul registrieren
  2. Shader über Unreals reguläre Shader-Pipeline kompilieren lassen
  3. kompilierten Bytecode für den gewählten Worker-Adapter prüfen
  4. passende Root Signature und Compute Pipeline State auf dem Worker-ID3D12Device erzeugen
  5. Input- und Output-Buffer auf dem Worker-Device bereitstellen
  6. Compute Dispatch auf der eigenen Worker-Command-Queue ausführen
  7. Ergebnis über Readback zurückholen und auf der CPU verifizieren

Dabei ist wichtig, zwischen zwei Ebenen zu unterscheiden: Unreal übernimmt zunächst Registrierung und Kompilierung des Shaders; die Ausführung auf dem unabhängigen Worker-Device folgt im experimentellen Projekt über den eigenen D3D12-Pfad und nicht automatisch über Unreals normales RHI oder RDG.

Quellen und weiterführende Dokumentation

Dieser Artikel ist eine eigenständige deutschsprachige Zusammenfassung mit Projektbezug und keine vollständige Übersetzung der Epic-Dokumentation.