15 ausgewählte Computer-Grafik Interviewfragen, gruppiert nach Erfahrungsstufe. Nutzen Sie diese, um Grundlagen, praktische Kompromisse und Überlegungen für die Produktionsumgebung auf Senior-Niveau zu überprüfen.
1Beschreiben Sie Linear Blend Skinning und die Anwendung von Knochenmatrizen auf einen Vertex mit mehreren Knocheneinflüssen.
Die lineare gewichtete Mischung von Knochentransformationen (Linear Blend Skinning, LBS) ist ein geometrisches Deformationsverfahren, das 3D-Meshes anhand einer Skeletthierarchie animiert. Jeder animierte Knochen bewegt sich relativ zur Referenzkonfiguration, der Bind Pose. Bei einem Vertex mit mehreren Einflüssen: 1. Die ursprüngliche Position im Mesh-Raum wird mit der inversen Bind-Pose-Matrix B_i^{-1} in den lokalen Raum jedes Knochens transformiert. 2. Die animierte Knochenmatrix M_i transformiert sie in den Raum der aktuellen Pose. Die zusammengesetzte Skinning-Palettenmatrix lautet S_i = M_i · B_i^{-1}. 3. Die endgültige Position ist die gewichtete lineare Summe aller Einflüsse: v' = Σ_{i=1}^{k} w_i · (M_i · B_i^{-1} · v). Die skalaren Knochengewichte müssen normalisiert sein: Σ w_i = 1,0. Auf dem Grafikprozessor (GPU) geschieht dies meist im Vertex-Shader oder einem vorgeschalteten Compute-Skinning-Durchlauf. Über Knochenindexattribute des Vertex wird die vorberechnete Palette aus einem Uniform-/Structured-Buffer gelesen und die transformierten Positionen werden linear gemischt. Bind-, animierte Gelenk- und Mesh-Transformationen müssen konsistente Koordinatenräume verwenden, einschließlich einer gegebenenfalls erforderlichen inversen Mesh-Transformation. Bei starren Knochen ist das Mischen transformierter Normalen mit anschließender Renormalisierung eine gängige Näherung. Bei nicht gleichförmiger Skalierung oder Scherung verwenden Sie eine zur gewählten Deformationsnäherung passende inverse transponierte Normalentransformation. Tangenten werden als Richtungen transformiert; das Tangentenkoordinatensystem wird erneut orthogonalisiert. Die gemischte Matrix ist im Allgemeinen keine reine Rotation.
#version 450
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in uvec4 inBoneIndices; // Up to 4 bone influences
layout(location = 3) in vec4 inBoneWeights; // Normalized: sum to 1.0
layout(set = 0, binding = 0) uniform BonePalette {
mat4 boneMatrices[128]; // Pre-multiplied: M_i * B_i^-1
};
layout(location = 0) out vec3 outNormal;
void main() {
mat4 skinMatrix = inBoneWeights.x * boneMatrices[inBoneIndices.x] +
inBoneWeights.y * boneMatrices[inBoneIndices.y] +
inBoneWeights.z * boneMatrices[inBoneIndices.z] +
inBoneWeights.w * boneMatrices[inBoneIndices.w];
vec4 skinnedPosition = skinMatrix * vec4(inPosition, 1.0);
gl_Position = u_ViewProjection * skinnedPosition;
// Transform normal with rotational part of skinMatrix and normalize
outNormal = normalize(mat3(skinMatrix) * inNormal);
}
2Was ist GPU-Instancing, und mit welchen Daten und Speicherlayouts lassen sich viele ähnliche Objekte effizient rendern?
Beim GPU-Instancing zeichnet ein Draw Call mehrere Instanzen derselben Geometrie. Sie teilen sich Vertex- und Indexpuffer. Beispiele sind DrawIndexedInstanced in Direct3D und glDrawElementsInstanced in OpenGL. Das spart Draw Calls und reduziert den CPU-seitigen Treiberaufwand. Pro Instanz können eine Weltmatrix oder kompakte Positions-, Rotations- und Skalierungsdaten, Farbton, UV-Versatz und UV-Skalierung, Material-ID, Animationsphase, Lightmap-Versatz oder Sichtbarkeitsflags übergeben werden. Bei einem Instanz-Vertexpuffer mit einer Schrittrate pro Instanz, etwa D3D11_INPUT_PER_INSTANCE_DATA, liest die GPU pro Instanz den nächsten Datensatz. Alternativ speichert man die Datensätze in einem vom Shader adressierbaren Puffer, etwa einem StructuredBuffer oder SSBO, oder bei kleinen Datenmengen in einem Uniform- bzw. Constant Buffer. Der Vertexshader greift über SV_InstanceID in HLSL oder gl_InstanceID in GLSL auf den passenden Datensatz zu. Kompakte 3×4- statt voller 4×4-Transformationsmatrizen, gepackte Transformationen oder FP16- bzw. uint32-Farben sparen Speicherbandbreite und verbessern die Cache-Nutzung.
3Erläutern Sie Frustum Culling, Occlusion Culling und Back-Face Culling und wo jedes typischerweise in einem Renderer auftritt.
Frustum Culling, Occlusion Culling und Back-Face Culling sind drei komplementäre Sichtbarkeitstechniken, die nicht sichtbare Primitive in verschiedenen Stufen und Granularitäten einer Rendering-Pipeline verwerfen:
1. **Frustum Culling**: Verwirft Geometrie, die vollständig außerhalb des Kamera-View-Frustums liegt. Es wird typischerweise auf groben Bounding Volumes (wie AABBs oder Bounding Spheres) auf der CPU vor der Draw-Submission durchgeführt, oder auf der GPU über Compute Shader in GPU-gesteuerten Rendering-Pipelines.
2. **Occlusion Culling**: Verwirft Objekte oder Primitive, die sich innerhalb des Frustums befinden, aber hinter anderer opaker Geometrie verborgen sind. Dies kann auf der CPU (mittels Software-Rasterisierung oder vorberechneter Sichtbarkeit) oder auf der GPU (mittels Hardware-Occlusion-Abfragen, GPU-Compute-Hi-Z-Tiefenpuffer-Tests oder Meshlet Culling) vor der vollständigen Rasterisierung erfolgen.
3. **Back-Face Culling**: Verwirft einzelne Polygone, deren Oberflächennormalen von der Kamera weg zeigen. Dies wird traditionell automatisch von Fixed-Function-Hardware während des Dreieck-Setups/der Rasterisierung auf der GPU basierend auf der Screen-Space-Winding-Order durchgeführt, obwohl es auch grob auf Cluster-Normalenkegeln (z. B. in Mesh Shaders) bewertet werden kann.
struct Plane { glm::vec3 normal; float distance; };
struct Sphere { glm::vec3 center; float radius; };
bool isSphereInsideFrustum(const Sphere& sphere, const Plane frustumPlanes[6]) {
for (int i = 0; i < 6; ++i) {
// Signed distance from plane to sphere center
float dist = glm::dot(frustumPlanes[i].normal, sphere.center) + frustumPlanes[i].distance;
if (dist < -sphere.radius) {
return false; // Completely outside
}
}
return true; // Inside or intersecting
}
4Wie erzeugen Spline-Kurven wie Bézier-, B-Spline- und Catmull-Rom-Kurven glatte Pfade oder Geometriestreifen?
Spline-Kurven beschreiben parametrisch P(t) und definieren glatte 3D-Pfade, Kamerabahnen und extrudierte Geometriestreifen wie Bänder, Straßen oder Röhren. 1. Kurventypen: Bézier-Kurven verwenden Bernstein-Polynome. Sie interpolieren nur die Endpunkte; die dazwischenliegenden Kontrollpunkte bestimmen die Tangentengriffe. Kollineare, korrekt ausgerichtete Tangentengriffe liefern geometrische G1-Stetigkeit. Parametrische C1-Stetigkeit verlangt zusätzlich passende Ableitungsbeträge unter Berücksichtigung von Segmentgraden und Parameterintervallen. B-Splines werden aus Basisfunktionen über einem Knotenvektor aufgebaut. Sie bieten lokale Kontrolle und hohe parametrische Stetigkeit: kubische B-Splines sind an einfachen inneren Knoten C2-stetig, wiederholte Knoten verringern die Stetigkeit. Im Allgemeinen verlaufen sie nicht durch innere Kontrollpunkte. Catmull-Rom-Splines sind interpolierende Splines, die bei konsistenter, nicht degenerierter Parametrisierung mit C1-Stetigkeit durch alle inneren Kontrollpunkte verlaufen. Sie eignen sich daher für vom Nutzer festgelegte Pfade. 2. Pfade und Geometrie: Die Auswertung bei t liefert die Position P(t) und die Tangente T(t) = P'(t). Zum Extrudieren von 3D-Bändern oder Röhren braucht man entlang der Kurve ein orthogonales Koordinatensystem mit Normale N(t) und Binormale B(t). Klassische Frenet-Serret-Rahmen versagen oder kippen an Wendestellen mit Krümmung κ = 0. Parallel-Transport-Rahmen beziehungsweise Bishop-Rahmen führen eine Referenzorientierung unter Minimierung der Rotationstorsion glatt entlang der Kurve fort und verhindern unnatürliche Verdrehungen des Bands.
5Was ist ein Command Buffer, und warum ist das parallele Aufzeichnen von Befehlen in leistungsfähigen Grafik-Engines wichtig?
Ein Command Buffer, in Direct3D 12 Command List genannt, speichert auf der CPU aufgezeichnete Grafik-, Compute- und Transferbefehle. Dazu gehören Pipeline-Zustände, Deskriptorbindungen, Draw Calls und Barrieren. Nach der Übermittlung führt eine GPU-Queue die Befehle asynchron aus. Vorbereitung der Draw Calls, Zustandswechsel und Culling können die CPU auslasten. Explizite APIs erlauben es, unabhängige Aufzeichnungsaufgaben auf mehrere CPU-Threads zu verteilen, etwa Schattenpässe, Teile des G-Buffers und Nachbearbeitung. Vulkan kann auf Worker-Threads sekundäre Command Buffer aufzeichnen lassen und sie aus einem primären Command Buffer ausführen. Direct3D 12 bietet neben einzeln einreichbaren Command Lists Bundles, die aus einer Direct Command List aufgerufen werden. Die GPU-Ausführung folgt den nötigen Reihenfolgen und Abhängigkeiten. Parallel ist hier vor allem die CPU-Aufzeichnung, wodurch mehrere Kerne genutzt und GPU-Wartezeiten durch verspätete Einreichung reduziert werden können.
6Wie unterscheiden sich Push Constants und Root Constants von Uniform- und Constant Buffers, und wann setzt man sie ein?
Mit Vulkan Push Constants und Direct3D 12 Root Constants übergibt man kleine Datenmengen direkt über Befehle. Die Root Signature legt dabei fest, welche Root Constants verfügbar sind. Die Werte brauchen keinen eigenen GPU-Puffer und keine entsprechende Deskriptorbindung. Uniform- und Constant Buffers speichern ihre Daten dagegen in GPU-Puffern und werden etwa über Deskriptoren, Tabellen oder Sets an die Pipeline gebunden. Direkt übergebene Konstanten eignen sich für kleine Werte, die sich von Draw Call zu Draw Call ändern, etwa Objekttransformationen, Material- oder Mesh-Indizes, Zeitwerte oder Offsets. Der Platz ist begrenzt: Vulkan garantiert mindestens 128 Byte für Push Constants. In Direct3D 12 stehen für die gesamte Root Signature höchstens 64 DWORDs zur Verfügung, einschließlich der Kosten von Root Descriptors und Tabellen. Puffer eignen sich für größere Daten und für Werte, die mehrere Draw Calls oder Renderpässe gemeinsam verwenden, etwa Kamera- und View-Matrizen pro Frame, Beleuchtung oder Umgebungseinstellungen.
7Welche Koordinatensysteme durchläuft ein Vertex beim Echtzeit-Rendering vom Modell bis zum Bildschirm?
Zuerst liegt der Vertex in lokalen Modellkoordinaten relativ zum Ursprung des Modells vor. Die Modellmatrix überführt ihn in Weltkoordinaten und positioniert und orientiert ihn in der Szene. Die View-Matrix transformiert ihn in Kamerakoordinaten. Dort liegt die Kamera im Ursprung und blickt in die durch die Konvention festgelegte Richtung. Die Projektionsmatrix liefert homogene Clip-Koordinaten $(x_c,y_c,z_c,w_c)$, in denen Geometrie an den Grenzen des Sichtvolumens abgeschnitten wird. Danach teilt die Pipeline $x_c$, $y_c$ und $z_c$ durch $w_c$ und erhält normierte Gerätekoordinaten (NDC). Die Viewport-Transformation bildet diese auf Fenster- oder Bildschirmkoordinaten und einen Tiefenwert ab.
8Vergleichen Sie Linear Blend Skinning und Dual-Quaternion-Skinning hinsichtlich Verformung, Artefakten und Implementierungsaufwand.
Linear Blend Skinning (LBS) kombiniert die von den einzelnen Knochen transformierten Vertexpositionen mit ihren Skinning-Gewichten. Das ist mit Standardmatrizen schnell und unterstützt affine Transformationen einschließlich ungleichmäßiger Skalierung und Scherung. Bei starken Rotationen und Verdrehung kann Volumen verloren gehen. Beim Candy-Wrapper-Artefakt zieht sich ein zylindrischer Querschnitt in Richtung der Drehachse zusammen. Dual-Quaternion-Skinning (DQS) stellt starre Knochentransformationen aus Rotation und Translation als Einheitsdualquaternionen dar und blendet diese. Das vermeidet typisches Einschnüren bei Verdrehung und erhält Volumen meist besser. An stark gebeugten Gelenken können Ausbeulungen oder Einschnürungen auftreten. Nicht starre Anteile wie ungleichmäßige Skalierung brauchen eine zusätzliche Behandlung, etwa einen separaten Pass, Polarzerlegung oder getrennte Skalierungs- und Scheranteile. Beim Mischen muss außerdem das Vorzeichen äquivalenter Quaterniondarstellungen konsistent gewählt werden. Dazu kann das Skalarprodukt ihrer Rotationsanteile dienen. Das vermeidet eine Auslöschung beim Blending und macht Shader und Asset-Pipeline aufwendiger.
9Bei animierten Figuren verformen sich nur manche Meshes falsch. Welche Asset- und Shaderdaten prüfen Sie?
Ich prüfe das Vertexlayout und die Knochenindizes. Gültige Indizes liegen bei nullbasierter Indizierung unterhalb der Größe der jeweiligen Palette. Ein uint8-Index kann Werte von 0 bis 255 speichern und bei größeren Indizes überlaufen. Die Skinning-Gewichte müssen pro Vertex in Summe 1 ergeben, sonst können sich Vertices falsch zusammenziehen oder verschieben. Die inversen Bind-Pose-Matrizen müssen zur Bindepose und zu den Koordinatensystemen von Mesh und Skelett passen. Fehler dort erzeugen Versatz oder extreme Verformungen. Ich vergleiche die Zahl der exportierten Knocheneinflüsse mit dem Vertexformat und dem Shader. Wenn etwa acht Einflüsse exportiert und nur vier unterstützt werden, müssen die verbleibenden Gewichte neu normiert werden. Schließlich prüfe ich Skelettzuordnung und Indexabbildung auf die Knochenmatrixpalette im Constant oder Structured Buffer.
struct SkinVertex {
float position[3];
uint8_t boneIndices[4];
uint8_t boneWeights[4]; // UNORM8
};
void ValidateMeshSkinData(const std::vector<SkinVertex>& vertices, uint32_t maxBoneCount)
{
for (size_t i = 0; i < vertices.size(); ++i)
{
const auto& v = vertices[i];
int weightSum = 0;
for (int b = 0; b < 4; ++b)
{
assert(v.boneIndices[b] < maxBoneCount && "Bone index exceeds palette size!");
weightSum += v.boneWeights[b];
}
assert(std::abs(weightSum - 255) <= 1 && "Bone weights do not normalize to 1.0!");
}
}
10Was sind Morph Targets oder Blend Shapes, und wie kombiniert man sie bei der Gesichtsanimation mit Skeletal Skinning?
Ein Morph Target beschreibt eine Verformung als Differenzen pro Vertex gegenüber einem neutralen Basismesh. Es kann Positions-, Normalen- und bei Bedarf Tangentendifferenzen speichern. Ein skalares Gewicht, meist zwischen 0 und 1, steuert den Beitrag. Das Ergebnis ist Basisattribut plus die Summe der gewichteten Differenzen. Für im Bindeposenraum definierte Blend Shapes wertet man die Differenzen zuerst in diesem Raum aus. Danach transformiert das Skeletal Skinning die veränderten Positionen und Normalen mit den Knochentransformationen. So folgen Gesichtsausdrücke den Bewegungen von Kopf und Kiefer. Vollständige Meshkopien für viele Blend Shapes kosten viel Speicherbandbreite. Stattdessen kann man nur Vertices mit von null verschiedenen Differenzen speichern, Deltas als FP16 oder quantisierte Ganzzahlen packen oder die verformten Vertices in einem Compute-Pass einmal für mehrere Renderpässe berechnen.
struct VertexInput {
float3 position : POSITION;
float3 normal : NORMAL;
uint4 boneIndices : BLENDINDICES;
float4 boneWeights : BLENDWEIGHT;
};
// 1. Accumulate morph deltas in local rest space
float3 morphedPos = input.position;
float3 morphedNorm = input.normal;
for (int i = 0; i < activeMorphCount; ++i) {
morphedPos += morphDeltasPos[i] * morphWeights[i];
morphedNorm += morphDeltasNorm[i] * morphWeights[i];
}
morphedNorm = normalize(morphedNorm);
// 2. Skin morphed geometry to world space
float4 skinnedPos = 0;
float3 skinnedNorm = 0;
for (int b = 0; b < 4; ++b) {
float4x4 boneMat = BoneMatrices[input.boneIndices[b]];
skinnedPos += mul(boneMat, float4(morphedPos, 1.0)) * input.boneWeights[b];
skinnedNorm += mul((float3x3)boneMat, morphedNorm) * input.boneWeights[b];
}
11Erklären Sie die Auswahl geometrischer Detailstufen (LOD), Mesh-Vereinfachung und Übergangsstrategien, die visuelle Stabilität, Attributerhalt und Leistung ausgleichen.
Die Verwendung geometrischer Detailstufen (LOD) reduziert die Mesh-Komplexität bei größerer Kameraentfernung und gleicht Bildqualität und Bildrate aus. 1. Auswahl: Verwenden Sie Bildschirmmetriken wie projizierten Begrenzungskugeldurchmesser, Anteil an der Bildschirmhöhe oder projizierten Pixelfehler statt fester Weltentfernungen. So werden Sichtfeld und Auflösung berücksichtigt. Hysterese mit getrennten Schwellen für höhere und niedrigere Detailstufen verhindert schnelles Hin- und Herschalten an Grenzen (LOD Thrashing). 2. Vereinfachung: Offline-Verfahren nutzen häufig quadratische Fehlermetriken (QEM) und wiederholtes Zusammenklappen von Kanten. Sie müssen Randsilhouetten erhalten, geometrische Verzerrung bestrafen und Vertexattribute wie Nahtstellen der Texturkoordinaten (UV), getrennte Normalen, Vertexfarben und Skinning-Gewichte über Attributfehlerterme in der quadratischen Metrik berücksichtigen. 3. Übergänge: Rasterbasierte Überblendung, auch Dithered Crossfading oder Screen-Door Stippling genannt, verwirft Pixel im Pixel-Shader anhand ineinandergreifender Dither-Muster, etwa einer Bayer-Matrix. Damit werden LODs ohne gewöhnliches Alpha-Blending überblendet. Discard- und Coverage-Änderungen können jedoch je nach Programmierschnittstelle (API) und Hardware frühe Tiefenschreibvorgänge oder andere Early-Z-Optimierungen einschränken; messen Sie auch die Kosten des doppelten Renderns im Übergang. Geomorphing interpoliert auf dem Grafikprozessor (GPU) über ein kurzes Zeitfenster Vertexpositionen benachbarter LOD-Meshes anhand einer expliziten Zuordnung beziehungsweise Collapse-Abbildung. Bei beliebigen Meshes mit unterschiedlicher Topologie lassen sich Vertexindizes nicht einfach miteinander interpolieren.
12Was ist eine Optimierung des Indexbuffers oder Vertexcaches, und warum beeinflusst die Reihenfolge der Dreiecke die Wiederverwendung transformierter Vertices?
Die Reihenfolge der Dreiecke im Indexbuffer bestimmt, wann gemeinsame Vertices erneut gebraucht werden. Liegen die Verweise nah beieinander, kann die GPU Ergebnisse des Vertexshaders wiederverwenden, solange sie noch verfügbar sind. Das wird oft mit einem kleinen Post-Transform-Cache modelliert, etwa FIFO oder LRU. Die tatsächliche Implementierung hängt von der Hardware ab. Für den Zugriff auf rohe Vertexdaten kann man anschließend den Vertexbuffer in der Reihenfolge der ersten Verwendung umsortieren und die Indizes entsprechend anpassen. Das verbessert die räumliche Lokalität und senkt Speicherverkehr. Verfahren wie die Optimierung nach Tom Forsyth oder Tipsify nutzen Informationen zu anstehenden Referenzen und Cache-Wiederverwendung, um passende nächste Dreiecke zu wählen. Ein Ziel ist ein niedriger ACMR-Wert, also wenige Cache Misses bzw. Vertexshader-Aufrufe pro Dreieck.
float calculateVertexScore(int cachePosition, int remainingValence) {
if (remainingValence == 0) return -1.0f;
float score = 0.0f;
if (cachePosition >= 0) {
if (cachePosition < 3) {
score = 0.75f; // Recent vertex in cache (bonus for immediate reuse)
} else {
score = std::pow(1.0f - (cachePosition - 3) / 29.0f, 1.5f); // Gradual falloff
}
}
// Bonus for vertices with few remaining triangles (clearing valence faster)
score += 2.0f * std::pow(remainingValence, -0.5f);
return score;
}
13Welche Verfahren zur Verdeckungsprüfung vermeiden CPU-GPU-Wartezeiten und sichtbares Aufpoppen?
Wartet die CPU im selben Frame auf Hardware-Occlusion-Queries, kann sie blockieren. Verzögertes Rücklesen spart Wartezeit, kann aber gerade sichtbar gewordene Objekte einen Frame lang auslassen. Ein zweiphasiges GPU-Verfahren prüft Hüllvolumen zunächst anhand früherer Sichtbarkeits- oder Hi-Z-Daten. Es zeichnet die voraussichtlich sichtbaren Objekte und baut aus der aktuellen Tiefe eine neue Hi-Z-Pyramide. Die zunächst aussortierten Kandidaten werden mit dieser Tiefe erneut geprüft. Neu sichtbare Objekte können dadurch noch vor Beleuchtung und Nachbearbeitung im selben Frame erscheinen, ohne CPU-Rücklesen. Alternativ können CPU-Worker vereinfachte Verdeckungsgeometrie per SIMD in einen kleinen Software-Tiefenpuffer rasterisieren und dort Hüllvolumen testen. Konservativ vergrößerte Hüllvolumen oder ein verzögerter Wechsel zu unsichtbar verringern Fehlentscheidungen bei schneller Kamerabewegung.
// Phase 1: Render instances visible in the previous frame
[numthreads(64, 1, 1)]
void Phase1_CullCS(uint id : SV_DispatchThreadID) {
if (id >= totalInstances) return;
Instance inst = instances[id];
if (wasVisibleLastFrame[id] && TestHiZ(inst.bounds, prevFrameHiZ)) {
AppendDraw(phase1DrawBuffer, inst);
currentVisibility[id] = true;
}
}
// [Phase 1 draws -> depth buffer written -> Hi-Z updated for current frame]
// Phase 2: Test previously occluded objects against updated Hi-Z to avoid popping
[numthreads(64, 1, 1)]
void Phase2_CullCS(uint id : SV_DispatchThreadID) {
if (id >= totalInstances) return;
if (!currentVisibility[id] && TestHiZ(instances[id].bounds, currentFrameHiZ)) {
AppendDraw(phase2DrawBuffer, instances[id]);
currentVisibility[id] = true;
}
}
14Wie gelangt ein erstelltes Mesh über Validierung, Tangentenberechnung, Optimierung und Quantisierung in GPU-Puffer?
Beim Import aus Formaten wie FBX, glTF oder USD prüft man Geometrie und Attribute auf ungültige Zahlen, degenerierte Dreiecke und gegebenenfalls Topologiefehler. Ungenutzte Vertices werden entfernt. Vertices werden nur zusammengelegt, wenn relevante Attribute übereinstimmen. Materialien lassen sich in Submeshes trennen. Tangenten und Bitangenten berechnet man etwa mit MikkTSpace, passend zum Normal-Map-Bake. UV-Nähte und gespiegelte Inseln brauchen passende Tangentenbasen. Das Vorzeichen kann in tangent.w liegen. Danach optimiert man die Indexreihenfolge für Wiederverwendung transformierter Vertices und die Vertexreihenfolge für lokale Speicherzugriffe. Bei Bedarf erzeugt man LOD-Stufen oder Meshlets. Positionen und UVs können beispielsweise als FP16 oder UNORM16 gespeichert werden. Für Normalen und Tangenten eignen sich SNORM8 oder oktaedrische Formate wie Oct16 und Oct32. Attribute lassen sich pro Vertex verschachteln oder in Streams trennen, etwa einen Positionsstream für Tiefenpässe. Abschließend serialisiert man Puffer, Hüllvolumen und LOD-Tabellen in ein flaches Binärformat für einen schnellen Upload über Staging-Speicher.
// Compress a float3 normal into 2D octahedral coordinates (8-bit SNORM each)
vec2 OctEncode(vec3 n) {
n /= (abs(n.x) + abs(n.y) + abs(n.z));
vec2 oct = (n.z >= 0.0) ? n.xy : (1.0 - abs(n.yx)) * sign(n.xy);
return oct * 0.5 + 0.5;
}
// Stored as 2x 8-bit unorm/snorm (2 bytes vs 12 bytes float3)
15Erklären Sie Meshlets, Cluster-Culling, Mesh Shader und Pipelines für dichte Mikrogeometrie in großen statischen Szenen.
Meshlets sind kleine Geometriecluster mit begrenzter Zahl an Vertices und Primitiven, beispielsweise 32 bis 128 Vertices und bis zu 128 bis 256 Dreiecke je nach Pipeline. Sie speichern lokale Indizes, Attributdaten oder Verweise darauf und oft eine Hüllkugel und einen Normalenkegel. In einer Mesh-Shader-Pipeline können optionale Task- oder Amplification-Shader Gruppen von Clustern auf Frustum, Verdeckung und Rückseiten prüfen und nur für verbleibende Cluster Mesh-Workgroups starten. Eine Workgroup verarbeitet Vertices gemeinsam und gibt Vertices und Primitive direkt an den Rasterisierer aus. Sehr dichte Geometrie führt zu vielen kleinen Dreiecken. Bei herkömmlicher Ausführung in 2×2-Pixelquads können mehrere Helper-Lanes mitarbeiten, obwohl das Dreieck nur einen Pixel abdeckt. Systeme wie Nanite verwenden hierarchische Cluster-LODs, um die Größe projizierter Details zu steuern. Sie können Hardware-Rasterisierung für größere Dreiecke mit Compute-Rasterisierung für sehr kleine Primitive verbinden.
#define MAX_VERTS 64
#define MAX_PRIMS 128
struct MeshletPayload { uint meshletIndices[32]; };
[outputtopology("triangle")]
[numthreads(32, 1, 1)]
void MainMS(
in uint gtid : SV_GroupThreadID,
in uint gid : SV_GroupID,
in payload MeshletPayload payloadData,
out vertices VertexOutput outVerts[MAX_VERTS],
out indices uint3 outIndices[MAX_PRIMS]
) {
uint meshletId = payloadData.meshletIndices[gid];
Meshlet m = meshlets[meshletId];
SetMeshOutputCounts(m.vertexCount, m.primitiveCount);
// Cooperatively transform vertices
for (uint v = gtid; v < m.vertexCount; v += 32) {
outVerts[v] = TransformVertex(m.vertexOffset + v);
}
// Output local triangle indices
for (uint p = gtid; p < m.primitiveCount; p += 32) {
outIndices[p] = GetMeshletTriangle(m.triangleOffset + p);
}
}