Příprava na pohovor z počítačové grafiky

Dotazy k pohovoru pro vývojáře počítačové grafiky

15 vybraných dotazů k pohovoru z počítačové grafiky, seskupených podle úrovně seniority. Použijte je k zopakování základů, praktických kompromisů a uvažování na seniorské úrovni pro produkční prostředí.

Spustit AI pohovor z počítačové grafikyNení vyžadována platební karta. K dispozici je 1 bezplatná relace.
Příprava na technické pohovory v angličtiněRežim pro lidi, pro které angličtina není rodným jazykem, určený k procvičování technických pohovorů.

Otázky pro juniory

1Popište lineární blend skinning a jak se matice kostí aplikují na vertex s vícenásobnými vlivy.

Lineární Blend Skinning (LBS) je technika geometrické deformace používaná k animaci 3D sítí na základě podkladové kosterní hierarchie. V kosterní animaci se každá animovaná kost pohybuje relativně k její referenční konfiguraci (výchozí póza, bind pose). Pro transformaci vertexu ovlivněného více kostmi: 1. Původní pozice vertexu v prostoru sítě je transformována do lokálního prostoru každé kosti vynásobením maticí inverzní výchozí pózy kosti ($B_i^{-1}$). 2. Vertex je poté transformován z lokálního prostoru kosti do aktuálního prostoru animované pózy pomocí matice animované pózy kosti ($M_i$). Složená transformace $S_i = M_i \cdot B_i^{-1}$ je matice palety pro skinning. 3. Konečná pozice skinovaného vertexu je vypočítána jako lineární vážený součet přes všechny ovlivňující kosti: $$v' = \sum_{i=1}^{k} w_i \cdot (M_i \cdot B_i^{-1} \cdot v)$$ kde skalární váhy kostí $w_i$ musí být normalizovány (tj. $\sum w_i = 1.0$). Na GPU (Graphics Processing Unit) se to typicky provádí ve vertex shaderu (nebo ve výpočetním pre-passu pro skinning) načítáním předpočítané palety kostí z uniform/strukturovaného bufferu pomocí atributů indexů kostí vertexů a lineárním blendováním pozic. Normály a tangenty vertexů jsou transformovány pomocí rotační části blendované skinning matice a renormalizovány.

#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);
}
Zkusit odpovědět na tuto otázku s trenérem AI

2Popište GPU instancing a jaká data pro instanci a rozložení dat činí vykreslování mnoha podobných objektů efektivním.

GPU instancing je technika vykreslování, která vykresluje více kopií stejné základní geometrie (sdílející vrcholové a indexové buffery) v jediném volání vykreslení (draw call) (např. `DrawIndexedInstanced` v Direct3D nebo `glDrawElementsInstanced` v OpenGL), čímž drasticky snižuje režii ovladače CPU-GPU a počet volání vykreslení (draw call) **API (Application Programming Interface)**. **Data pro instanci:** Aby se zajistilo, že instance vypadají a chovají se odlišně, jsou poskytována data pro instanci, která běžně zahrnují: - Transformační data: Světová matice nebo komprimovaná pozice/rotace/škálování. - Vlastnosti materiálu: Barevné odstíny, posuny/škálování UV souřadnic nebo indexy ID materiálu. - Dynamické parametry: Fáze animace, posuny lightmapy nebo příznaky viditelnosti. **Rozložení dat a metody přístupu:** 1. **Instancované vrcholové buffery:** Vyhrazený vrcholový buffer navázaný s krokovou rychlostí na instanci (např. `D3D11_INPUT_PER_INSTANCE_DATA`). GPU automaticky posouvá buffer pro každou instanci. 2. **StructuredBuffer / Uniform Buffer (SSBO / Constant Buffer):** Data instance jsou nahrána do pole bufferu a vertex shader do něj indexuje pomocí vestavěného systémového identifikátoru instance (`SV_InstanceID` v HLSL, `gl_InstanceID` v GLSL). Udržování kompaktních dat pro instanci (např. afinní matice 3x4 nebo pozice + kvaternion místo plných matic 4x4 a komprimované barvy FP16/uint32) minimalizuje propustnost paměti GPU a optimalizuje využití cache.

struct InstanceData {
    float4x4 worldMatrix;
    float4   colorTint;
};

StructuredBuffer<InstanceData> gInstanceData : register(t0);

struct VSInput {
    float3 position : POSITION;
    float3 normal   : NORMAL;
};

struct VSOutput {
    float4 position : SV_POSITION;
    float4 color    : COLOR;
};

VSOutput main(VSInput input, uint instanceID : SV_InstanceID)
{
    VSOutput output;
    InstanceData inst = gInstanceData[instanceID];
    
    float4 worldPos = mul(inst.worldMatrix, float4(input.position, 1.0));
    output.position = mul(gViewProjMatrix, worldPos);
    output.color    = inst.colorTint;
    return output;
}
Zkusit odpovědět na tuto otázku s trenérem AI

3Vysvětlete Frustum culling, Occlusion culling a Back-face culling a kde se každý z nich typicky odehrává v rendereru.

Frustum culling, occlusion culling a back-face culling jsou tři doplňkové techniky viditelnosti, které odhazují neviditelné primitivy v různých fázích a granularitách renderovací pipeline: 1. **Frustum culling**: Odhazuje geometrii, která leží zcela mimo zorný jehlan kamery (camera's view frustum). Typicky se provádí na hrubých ohraničujících objemech (jako jsou AABB (Axis-Aligned Bounding Boxes) nebo ohraničující sféry) na CPU před odesláním draw callu, nebo na GPU pomocí compute shaderů v renderovacích pipeline řízených GPU. 2. **Occlusion culling**: Odhazuje objekty nebo primitivy, které jsou uvnitř zorného jehlanu, ale jsou skryty za jinou neprůhlednou geometrií. Může se odehrávat na CPU (pomocí softwarové rasterizace nebo předpočítané viditelnosti) nebo na GPU (pomocí hardwarových occlusion dotazů, testů Hi-Z depth bufferu pomocí GPU compute shaderů nebo meshlet culling) před plnou rasterizací. 3. **Back-face culling**: Odhazuje jednotlivé polygony, jejichž normály povrchu směřují od kamery. To je tradičně automaticky prováděno hardwarem s pevnou funkcí během nastavení/rasterizace trojúhelníků na GPU na základě pořadí vinutí v prostoru obrazovky (screen-space winding order), i když to lze také hrubě vyhodnotit na kuželech normál clusterů (např. v mesh shaderech).

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
}
Zkusit odpovědět na tuto otázku s trenérem AI

4Jak splajnové křivky jako Bézier, B-spline a Catmull-Rom generují plynulé cesty nebo geometrické pásy?

Splajnové křivky poskytují parametrické formulace $\mathbf{P}(t)$ pro definování plynulých 3D cest, drah kamery a extrudovaných geometrických pásů (jako jsou stuhy, cesty nebo trubky). 1. **Typy a vlastnosti křivek:** - **Bézierovy křivky:** Formulované Bernsteinovými polynomy. Interpolují pouze koncové body; mezilehlé kontrolní body definují tečné úchyty. Spojení segmentů se spojitostí $C^1$ vyžaduje kolineární tečné úchyty. - **B-splajny:** Konstruované pomocí bázových funkcí nad vektorovým uzlem (knot vector). Poskytují lokální kontrolu a vysokou parametrickou spojitost ($C^2$ pro kubické), ale obecně neprocházejí vnitřními kontrolními body. - **Catmull-Rom splajny:** Třída interpolujících splajnů, které procházejí přímo všemi vnitřními kontrolními body a automaticky zajišťují spojitost $C^1$, což je činí ideálními pro uživatelem vytvořené cesty. 2. **Generování cesty a geometrie:** - Vyhodnocením splajny v parametru $t$ se získá pozice $\mathbf{P}(t)$ a tečný vektor $\mathbf{T}(t) = \mathbf{P}'(t)$. - Pro extrudování 3D stuh nebo trubek je podél křivky zapotřebí ortogonální souřadnicový rámec (normála $\mathbf{N}(t)$ a binormála $\mathbf{B}(t)$). - Standardní Frenetovy-Serretovy rámce selhávají nebo se převracejí v inflexních bodech, kde je křivost $\kappa = 0$. Aby se zabránilo nepřirozenému kroucení stuhy, **Rámce paralelního transportu (Bishopovy rámce)** hladce šíří referenční orientaci podél křivky minimalizací rotační torze.

struct Vector3 { float x, y, z; };

Vector3 EvaluateCatmullRom(const Vector3& p0, const Vector3& p1, const Vector3& p2, const Vector3& p3, float t) {
    float t2 = t * t;
    float t3 = t2 * t;
    return 0.5f * ( (2.0f * p1) +
                    (-p0 + p2) * t +
                    (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * t2 +
                    (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3 );
}
Zkusit odpovědět na tuto otázku s trenérem AI

5Co je příkazový buffer a proč je vícevláknové zaznamenávání příkazů důležité ve špičkových enginech?

Příkazový buffer (nebo `command list` v Direct3D 12) je datová struktura v paměti, kde jsou na CPU zaznamenávány grafické, výpočetní a přenosové příkazy – jako je nastavení stavu pipeline, vázání deskriptorů, vydávání vykreslovacích volání (draw calls) a zaznamenávání bariér pipeline – pro následné odeslání a asynchronní provedení ve frontě GPU. Vícevláknové zaznamenávání příkazů je zásadní ve špičkových enginech, protože příprava vykreslovacích volání (draw calls) na straně CPU, vázání stavu a vyřazování (culling) byly tradičně primárními úzkými hrdly. Odstraněním omezení jednovláknového kontextu umožňují explicitní API engine rozdělit snímek na nezávislé renderovací úlohy napříč několika pracovními vlákny CPU. Například stínové průchody, části G-bufferu a post-processing mohou být zaznamenávány souběžně. Moderní API to usnadňují pomocí primárních a sekundárních příkazových bufferů (Vulkan) nebo `command lists` a `bundles` (D3D12). Sekundární příkazové buffery a `bundles` umožňují pracovním vláknům zaznamenávat podmnožiny vykreslovacích příkazů, které mohou být provedeny uvnitř primárního příkazového bufferu na odesílacím vláknu, čímž se maximalizuje využití vícejádrového CPU a minimalizuje zablokování fronty GPU.

// Worker Thread Job:
void RecordShadowPassChunk(VkCommandBuffer secondaryCmdBuf, const RenderJob& job) {
    VkCommandBufferInheritanceInfo inheritInfo{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO };
    inheritInfo.renderPass = job.shadowRenderPass;
    
    VkCommandBufferBeginInfo beginInfo{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
    beginInfo.flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT;
    beginInfo.pInheritanceInfo = &inheritInfo;
    
    vkBeginCommandBuffer(secondaryCmdBuf, &beginInfo);
    vkCmdBindPipeline(secondaryCmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, job.pipeline);
    vkCmdDrawIndexed(secondaryCmdBuf, job.indexCount, 1, 0, 0, 0);
    vkEndCommandBuffer(secondaryCmdBuf);
}

// Main Thread Submission:
// vkCmdExecuteCommands(primaryCmdBuf, secondaryCount, secondaryCmdBuffers.data());
// vkQueueSubmit(queue, 1, &submitInfo, fence);
Zkusit odpovědět na tuto otázku s trenérem AI

6Jak se push konstanty nebo root konstanty liší od uniformních/konstantních bufferů a kdy by se měly používat?

Push konstanty (ve Vulkanu) a root konstanty (v DirectX 12) poskytují mechanismus pro předávání malého množství uniformních dat přímo v příkazovém bufferu nebo root signatuře, čímž obcházejí režii alokace, aktualizace a navazování zdrojů GPU bufferu podložených deskriptory. Naproti tomu Uniformní buffery (UBOs - Uniform Buffer Objects) nebo Konstantní buffery (CBOs - Constant Buffer Objects) jsou podloženy vyhrazenými alokacemi paměti GPU, které jsou vázány na pipeline pomocí deskriptorů, deskriptorových tabulek nebo sad deskriptorů. Protože jsou push/root konstanty vloženy přímo do proudu příkazů, jsou ideální pro vysoce frekventovaná data pro každé vykreslení, která se často mění (jako jsou transformační matice objektů, indexy materiálů/meshů, časové hodnoty nebo dynamické posuny). Mají však přísná omezení velikosti (např. Vulkan garantuje minimální limit pouze 128 bajtů a prostor root signatury D3D12 je omezen na 64 DWORDů, sdílených s root deskriptory a tabulkami). Uniformní/konstantní buffery by se měly používat, když objem dat překračuje limity velikosti push konstant, když jsou data sdílena napříč více vykresleními (jako jsou matice kamery/pohledu pro každý snímek, globální osvětlení scény nebo nastavení prostředí), nebo když je potřeba perzistentní úložiště napříč průchody.

// Push Constant setup
struct PushData {
    glm::mat4 modelMatrix;
    uint32_t materialIndex;
};

// Command buffer recording: inline write directly into command stream
PushData data = { object.transform, object.matID };
vkCmdPushConstants(cmdBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushData), &data);
vkCmdDrawIndexed(cmdBuffer, indexCount, 1, 0, 0, 0);

// Compared to UBO: requires updating mapped GPU buffer, managing offsets/ring buffers, and binding descriptor sets
vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &perObjectDescriptorSet, 0, nullptr);
Zkusit odpovědět na tuto otázku s trenérem AI

7Projděte souřadnicové prostory, kterými prochází vrchol (vertex) z modelového prostoru do obrazovkového prostoru v renderovacím enginu v reálném čase.

V pipeline renderování v reálném čase prochází vrchol typicky několika souřadnicovými prostory: modelovým (lokálním) prostorem, světovým prostorem, prostorem pohledu (kamery), ořezávacím prostorem (Clip Space), normalizovanými souřadnicemi zařízení (NDC) a obrazovkovým prostorem (Viewport/Window Space). Vrchol začíná v modelovém prostoru vzhledem k lokálnímu počátku assetu. Vynásobením maticí modelu/světa (Model/World matrix) se umístí a zorientuje do sdíleného světového prostoru. Vynásobením maticí pohledu (View matrix) se transformuje do prostoru pohledu, kde je kamera v počátku a dívá se standardním směrem pohledu. Dále, vynásobením projekční maticí (Projection matrix) se souřadnice transformují do 4D ořezávacího prostoru Clip Space $(x_c, y_c, z_c, w_c)$, kde je geometrie oříznuta vůči objemu pohledu. Po oříznutí provede hardware s pevnou funkcí perspektivní dělení (dělení $x_c, y_c, z_c$ pomocí $w_c$), aby se vytvořily 3D normalizované souřadnice zařízení (NDC). Nakonec transformace Viewport mapuje souřadnice NDC na 2D pixelové souřadnice obrazovkového prostoru a hodnoty hloubkového bufferu.

// Vertex Shader Stage
float4 worldPos = mul(modelMatrix, float4(inPosition, 1.0));
float4 viewPos  = mul(viewMatrix, worldPos);
float4 clipPos  = mul(projMatrix, viewPos); // Output to rasterizer

// Hardware Fixed-Function Stages:
// 1. Clipping against [-w, w]
// 2. Perspective Divide: ndcPos = clipPos.xyz / clipPos.w;
// 3. Viewport Transform -> Screen pixel coordinates (x_px, y_px)
Zkusit odpovědět na tuto otázku s trenérem AI

Otázky pro středně pokročilé

8Porovnejte lineární prolnutí kůže (Linear Blend Skinning – LBS) se skinningem pomocí duálních kvaternionů (Dual-Quaternion Skinning – DQS) z hlediska kvality deformace, artefaktů a technické složitosti implementace.

Lineární prolnutí kůže (LBS) a skinning pomocí duálních kvaternionů (DQS) představují dva odlišné přístupy k deformaci skeletálních sítí: 1. **Kvalita deformace a artefakty:** * LBS vypočítává transformované vrcholy pomocí lineární interpolace matic kostních transformací. Přestože je rychlé, LBS trpí ztrátou objemu během silných rotací a kroucení, zejména artefaktem „candy-wrapper“, kdy se válcová geometrie hroutí podél osy kroucení. * DQS reprezentuje rigidní transformace kostí jako jednotkové duální kvaterniony (kombinující rotaci a translaci). Při prolnutí (např. pomocí duálního lineárního prolnutí – Dual Linear Blending) DQS přirozeně zachovává objem a eliminuje artefakty kroucení typu „candy-wrapper“. DQS však zavádí vlastní artefakty, jako je vyboulení nebo sevření (pinching) při extrémních ohybech kloubů. 2. **Technická a implementační složitost:** * LBS nativně podporuje plné afinní transformace (translaci, rotaci a nestejnoměrné škálování nebo zkosení) pomocí standardních pipeline s maticemi 4x4. * DQS nativně zpracovává pouze rigidní transformace. Zpracování škálování (zejména nestejnoměrného) vyžaduje vícenásobnou deformaci (multi-pass deformation), polární dekompozici nebo oddělení škálování a zkosení (scale-shear separation). Navíc DQS vyžaduje ošetření antipodality během prolnutí (kontrola skalárních součinů duálních kvaternionů, aby se zvolila nejkratší rotační cesta a zabránilo se převrácení/kolapsu sítě), což zvyšuje složitost matematiky shaderů a pipeline pro assety.

struct DualQuat {
    float4 rot;
    float4 trans;
};

DualQuat BlendDualQuaternions(uint4 indices, float4 weights, StructuredBuffer<DualQuat> boneDQs)
{
    DualQuat dq0 = boneDQs[indices.x];
    DualQuat blended = dq0;
    blended.rot *= weights.x;
    blended.trans *= weights.x;

    [unroll]
    for (int i = 1; i < 4; ++i)
    {
        DualQuat dqi = boneDQs[indices[i]];
        // Antipodality check: ensure shortest path
        float signVal = dot(dq0.rot, dqi.rot) < 0.0 ? -1.0 : 1.0;
        blended.rot += dqi.rot * (weights[i] * signVal);
        blended.trans += dqi.trans * (weights[i] * signVal);
    }
    float len = length(blended.rot);
    blended.rot /= len;
    blended.trans /= len;
    return blended;
}
Zkusit odpovědět na tuto otázku s trenérem AI

9Animované postavy se deformují nesprávně pouze na některých modelech (meshes). Jaká data assetu a shaderu byste zkontrolovali?

Když se animované postavy deformují nesprávně pouze na podmnožině modelů (meshes), problém obvykle pramení z nesouladu dat napříč pipeline assetů, rozložením vrcholů nebo konstantami shaderu. Systematická kontrola by měla pokrýt: 1. **Rozložení vrcholů a hranice indexů kostí:** Zajistěte, aby indexy kostí vrcholů nepřesahovaly počet kostí skeletu ani nepřetékaly jejich komprimovaný datový typ (např. použití `uint8`/`ubyte4`, když má skelet >256 kostí, což způsobuje přetečení indexu). 2. **Normalizace vah kostí:** Ověřte, že součet vah kostí na vrchol se rovná 1.0. Nenormalizované váhy způsobují, že se vrcholy smršťují k skeletu nebo se od něj vzdalují. 3. **Inverzní matice klidové pozice (Inverse Bind Pose Matrices – IBMs):** Potvrďte, že inverzní matice klidové pozice modelu odpovídají klidové pozici skeletu (rest pose) a souřadnicovému prostoru. Nesouhlasící klidové pozice způsobují, že se model rozpadá nebo je nesprávně posunut. 4. **Maximální počet vlivů na vrchol:** Zkontrolujte, zda DCC exportér exportoval více vlivů kostí na vrchol (např. 8 vlivů), než podporuje rozložení vrcholového bufferu nebo shader (např. 4 vlivy), čímž se váhy zahazují bez renormalizace. 5. **Hierarchie skeletu a indexování palety:** Ověřte, že mapování indexů kostí v modelu odpovídá paletě matic kostí nahrané do konstantních/strukturovaných 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!");
    }
}
Zkusit odpovědět na tuto otázku s trenérem AI

10Co jsou morph targets (tvarové klíče) nebo blend shapes (směsové tvary) a jak se kombinují se skeletálním skinningem pro animaci obličeje?

Morph targets (tvarové klíče) nebo blend shapes (směsové tvary) představují geometrické deformace uložené jako rozdílové posuny na vrchol (rozdílové pozice, rozdílové normály a volitelně rozdílové tangenty) vzhledem k základní síti v klidové pozici. Každý morph target je řízen skalární váhou (obvykle 0.0 až 1.0) a deformované atributy vrcholů se vypočítávají jako: `Morphed_Attribute = Base_Attribute + Sum(Weight_i * Delta_i)`. Při kombinování morph targets se skeletálním skinningem (např. pro animaci obličeje): 1. Pořadí vyhodnocení: Rozdílové hodnoty morph targetů musí být vyhodnoceny v prostoru modelu neutrální/výchozí pózy před aplikací skeletálního skinningu. 2. Krok skinningu: Morfované pozice a normály jsou následně transformovány maticemi kostí skeletálního skinningu. Aplikace morfování před skinningem zajišťuje, že se výrazy obličeje deformují přirozeně s otáčením hlavy a rotacemi čelistního kloubu. Z hlediska výkonu a propustnosti paměti způsobuje naivní ukládání a čtení kompletních kopií sítě pro desítky blend shapes vysoký tlak na propustnost paměti. Praktické implementace ukládají řídké rozdíly (pouze nenulové vrcholy), komprimují formáty rozdílů (např. FP16 (16bitová plovoucí čárka) nebo kvantovaná celá čísla) nebo používají předběžné průchody (pre-passy) GPU (Graphics Processing Unit) výpočetního shaderu pro jednorázový výpočet morfovaných vrcholů před několika průchody vykreslování.

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];
}
Zkusit odpovědět na tuto otázku s trenérem AI

11Vysvětlete výběr geometrické úrovně detailů (LOD), zjednodušení sítě (mesh simplification) a strategie přechodu, které vyvažují vizuální stabilitu, zachování atributů a výkon.

Geometrická úroveň detailů (LOD) optimalizuje výkon vykreslování snížením složitosti sítě, když se objekty vzdalují od kamery, čímž vyvažuje vizuální věrnost a snímkovou frekvenci. 1. **Výběr LOD**: LOD by měly být vybírány pomocí metrik v prostoru obrazovky (jako je průměr promítnuté ohraničující sféry, procento výšky obrazovky nebo promítnutá chyba pixelu) spíše než statické vzdálenosti ve světovém prostoru, aby se zohlednilo zorné pole kamery (FOV) a změny rozlišení. Aby se zabránilo rychlému kolísání mezi LOD na hranicích vzdáleností ('LOD thrashing'), používá se hystereze, která udržuje oddělené prahové hodnoty pro přepínání nahoru a dolů. 2. **Zjednodušení sítě**: Offline generování se běžně opírá o Metriky kvadrikových chyb (QEM) pomocí iterativních kolapsů hran. Pro udržení vizuální kvality musí algoritmy zjednodušení zachovávat siluety hranic a penalizovat geometrické zkreslení, stejně jako zachovávat atributy vrcholů (UV švy, rozdělení normál, barvy vrcholů a váhy pro skinning) začleněním chybových členů atributů do kvadrikové metriky. 3. **Strategie přechodu**: Aby se zabránilo náhlému vizuálnímu 'vyskakování', enginy používají: * **Ditheringové prolínání (Dithered Crossfading) / Sítování (Screen-Door Stippling)**: V pixel shaderu zahazuje pixely pomocí prokládaného ditheringového vzoru (např. Bayerovy matice), čímž plynule prolíná mezi LOD bez nutnosti alfa míchání nebo narušení early-Z testu. * **Geomorfing**: Interpoluje pozice vrcholů mezi sousedními LOD sítěmi na GPU v krátkém přechodovém okně.

float CalculateLODDither(float2 screenPos, float lodBlendFactor)
{
    const float bayer4x4[16] = {
         0.0/16.0,  8.0/16.0,  2.0/16.0, 10.0/16.0,
        12.0/16.0,  4.0/16.0, 14.0/16.0,  6.0/16.0,
         3.0/16.0, 11.0/16.0,  1.0/16.0,  9.0/16.0,
        15.0/16.0,  7.0/16.0, 13.0/16.0,  5.0/16.0
    };
    uint2 pixelCoord = (uint2)screenPos.xy % 4;
    float threshold = bayer4x4[pixelCoord.y * 4 + pixelCoord.x];
    return (lodBlendFactor - threshold);
}

// In pixel shader: if (CalculateLODDither(input.position.xy, lodTransitionAlpha) < 0.0) discard;
Zkusit odpovědět na tuto otázku s trenérem AI

12Co je optimalizace indexového bufferu nebo vertexové cache a proč pořadí trojúhelníků ovlivňuje efektivitu cache po transformaci?

Optimalizace vertexové cache (cache vrcholů) (nebo indexového bufferu) přeuspořádá indexy trojúhelníků a data vrcholů v síti tak, aby maximalizovala četnost zásahů (hit rates) v hardwarových cache vrcholů grafického procesoru (GPU). GPU mají dvě hlavní cache vrcholů: 1. **Cache po transformaci (Post-Transform Cache):** Malá FIFO/LRU cache ukládající transformované výstupy vertex shaderu (pozice, atributy). Když sousední trojúhelníky sdílejí vrcholy, odkazování na tyto vrcholy těsně v indexovém proudu umožňuje GPU znovu použít cachované výstupy shaderu namísto spouštění vertex shaderu vícekrát pro stejný vrchol. 2. **Cache před transformací (Pre-Transform Cache):** L1/L2 paměťová cache GPU pro nezpracovaná data vertex bufferu. Přeuspořádání dat vertex bufferu tak, aby odpovídala pořadí prvního přístupu optimalizovaných indexů, maximalizuje prostorovou lokalitu a efektivitu šířky pásma paměti. Pořadí trojúhelníků přímo určuje přístupovou sekvenci v cache po transformaci. Optimalizační algoritmy (jako algoritmus Toma Forsytha nebo Tipsify) přiřazují dynamické skóre znovupoužití vrcholům na základě valence a pozice v cache, přičemž prioritizují trojúhelníky, které dokončují zbývající reference na nedávno cachované vrcholy, aby minimalizovaly průměrný poměr zmeškaných zásahů do cache (Average Cache Miss Ratio – ACMR).

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;
}
Zkusit odpovědět na tuto otázku s trenérem AI

Otázky pro seniory

13Jaké přístupy k odstraňování skrytých ploch (occlusion culling) zabraňují zablokování CPU (Central Processing Unit)-GPU (Graphics Processing Unit) a nesprávnému vizuálnímu objevování/mizení objektů (popping)?

Tradiční dotazy na hardwarové odstraňování skrytých ploch způsobují synchronní zablokování CPU-GPU při zpětném čtení, pokud CPU čeká na výsledky viditelnosti v rámci stejného snímku. Zpoždění zpětného čtení o jeden snímek zabraňuje zablokování, ale zavádí časovou latenci, což způsobuje viditelné objevování/mizení objektů (popping), když nově viditelné objekty nejsou vykresleny okamžitě. Aby se zabránilo zablokování CPU-GPU i vizuálnímu objevování/mizení (popping), moderní produkční architektury používají: 1. Dvoufázové GPU-řízené odstraňování skrytých ploch s Hi-Z (Hierarchical-Z): GPU testuje ohraničující boxy proti Hi-Z pyramidě hloubky generované z předchozího snímku. Objekty, o nichž je známo, že jsou viditelné, jsou vykresleny ve Fázi 1 (generování počáteční hloubky aktuálního snímku). Dříve zakryté objekty jsou znovu testovány proti aktualizované Hi-Z vyrovnávací paměti aktuálního snímku ve Fázi 2; všechny nově odhalené objekty jsou vykresleny okamžitě před osvětlením a post-processingem, čímž se eliminuje popping s nulovými zpětnými čteními CPU. 2. Softwarová rasterizace CPU: Hloubkový buffer s nízkým rozlišením je rasterizován čistě na pracovních vláknech CPU (pomocí SIMD (Single Instruction, Multiple Data)) z zjednodušených okludérských sítí. CPU testuje ohraničující boxy synchronně, aniž by potřebovalo dotazy GPU nebo by mu vznikala latence přenosu z GPU na CPU. 3. Konzervativní ohraničení a časová hystereze: Rozšíření ohraničujících objemů nebo zpoždění snížení stavu viditelnosti zabraňuje předčasnému odstraňování během rychlého pohybu kamery.

// 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;
    }
}
Zkusit odpovědět na tuto otázku s trenérem AI

14Popište typickou pipeline pro zpracování assetů od vytvořené sítě (authored mesh) po běhové (runtime) GPU buffery, včetně generování tangent, kvantizace, validace a optimalizace.

Standardní pipeline pro zpracování assetů transformuje surové DCC sítě (Digital Content Creation) (FBX, glTF, USD) do vysoce výkonných binárních formátů připravených pro GPU prostřednictvím pěti hlavních fází: 1. Příjem a Validace: Zdrojová síť je očištěna odstraněním duplicitních nebo nepoužitých vrcholů, zahozením degenerovaných trojúhelníků / trojúhelníků s nulovou plochou, ověřením manifold geometrie, zpracováním NaN hodnot (Not a Number) a rozdělením sítí s více materiály na samostatné pod-sítě. 2. Generování tangentního prostoru: Tangenty a bitangenty jsou vypočítány pomocí standardizovaných algoritmů (primárně MikkTSpace), aby byla zaručena vizuální shoda s nástroji pro „normal baking“. Tímto se správně zohlední UV švy a zrcadlené UV mapy (orientace je uložena v tangent.w). 3. Optimalizace: Indexy jsou přeorganizovány pro efektivitu cache vrcholů po transformaci (např. Forsyth/Tipsify), vertex buffery jsou přeorganizovány pro lokalitu načítání vrcholů před transformací a jsou generovány úrovně detailů (LOD) nebo meshlety. 4. Kvantizace a balení atributů: Atributy vrcholů jsou kvantizovány za účelem snížení paměťové stopy a šířky pásma paměti: pozice na 16bitové poloviční/unorm nebo normalizované celočíselné hodnoty, normály a tangenty na 8bitové SNORM nebo oktaedrické kódování (Oct16/Oct32) a UV souřadnice na 16bitové floaty/unorm. Atributy mohou být prokládány (AoS – Array of Structures) nebo rozděleny do více proudů (SoA – Structure of Arrays, např. pouze pozice pro průchody s předběžným vykreslením hloubky). 5. Finalizace a serializace: Buffery, ohraničující objemy (AABB/koule) a tabulky LOD jsou serializovány do plochých binárních souborů, které nevyžadují žádné opravy ukazatelů za běhu (runtime pointer patching), což umožňuje rychlé nahrání přes DMA (Direct Memory Access) do GPU bufferů pomocí staging paměti (dočasné vyrovnávací paměti).

// 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)
Zkusit odpovědět na tuto otázku s trenérem AI

15Vysvětlete meshlety, cluster culling, mesh shadery a pipeline husté mikrogeometrie pro velké statické scény.

Pipeline meshletů a architektury husté mikrogeometrie (například Nanite od Unrealu) nahrazují velké volání vykreslování s indexovanými buffery malými, ohraničenými geometrickými klastry nazývanými 'meshlety'. 1. **Meshlety**: Meshlet je klastr geometrie, typicky omezený na 32–128 vrcholů (vertices) a až 128–256 trojúhelníků. Každý meshlet obsahuje lokální indexy vrcholů, streamy atributů a předpočítaná ohraničující data (bounding sphere a normal cone). 2. **Mesh a Amplification Shaders**: Nahrazují pipeline shaderů s pevnou funkcí pro vrcholy (vertex), sestavení primitiv (primitive assembly) a geometrii (geometry shader). Amplification (Task) Shaders vyhodnocují frustum na úrovni klastru, okluzi a culling zadních stěn (normal-cone back-face culling) napříč skupinami meshletů. Přeživší meshlety spouštějí Mesh Shaders, kde skupina vláken (threadgroup) kooperativně transformuje vrcholy v integrované sdílené paměti (on-chip Shared Memory – LDS) a přímo vydává indexy primitiv do rasterizéru. 3. **Pipelines husté mikrogeometrie**: Geometrie s vysokou hustotou produkuje podpixelové trojúhelníky, které trpí závažným quad-overdraw (překreslováním čtverců), kde standardní hardware rasterizuje pomocné čtverce o velikosti 2x2 pixely, spouštějíc plné pixel shadery pouze pro jeden pokrytý pixel. Moderní systémy husté mikrogeometrie používají hierarchické struktury LOD (level of detail) klastrů (DAGy) pro dynamický výběr LOD klastrů, zajišťující délky hran přibližně 1 pixel, a často kombinují hardwarovou rasterizaci pro velké polygony s vlastními výpočetními softwarovými rasterizéry pro podpixelové mikropolygony.

#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);
    }
}
Zkusit odpovědět na tuto otázku s trenérem AI