Подготовка к собеседованию по компьютерной графике

Вопросы для собеседования разработчика компьютерной графики

15 отобранных вопросов для собеседования по компьютерной графике, сгруппированных по уровням квалификации. Используйте их для повторения основ, понимания практических компромиссов и рассуждений на уровне Senior-разработчика о производственных системах.

Начать ИИ-собеседование по компьютерной графикеКредитная карта не требуется. Доступна 1 бесплатная сессия.
Практика технических собеседований на английскомРежим, в котором не носители языка могут потренироваться проходить интервью.

Вопросы для Junior-разработчиков

1Опишите линейный скиннинг со смешиванием (LBS) и применение матриц костей к вершине, на которую влияют несколько костей.

Линейный скиннинг со смешиванием (LBS) — это метод геометрической деформации, используемый для анимации трехмерных сеток на основе базовой иерархии скелета. В скелетной анимации каждая анимированная кость перемещается относительно своей исходной конфигурации (bind pose). Чтобы преобразовать вершину, на которую влияют несколько костей: 1. Исходное положение вершины в пространстве сетки преобразуется в локальное пространство каждой кости путем умножения на обратную матрицу исходной позы кости ($B_i^{-1}$). 2. Затем вершина преобразуется из локального пространства кости в пространство текущей анимированной позы с использованием анимированной матрицы кости ($M_i$). Составное преобразование $S_i = M_i \cdot B_i^{-1}$ является матрицей палитры скиннинга. 3. Итоговое положение вершины после скиннинга вычисляется как линейная взвешенная сумма по всем влияющим костям: $$v' = \sum_{i=1}^{k} w_i \cdot (M_i \cdot B_i^{-1} \cdot v)$$ где скалярные веса костей $w_i$ должны быть нормализованы (т. е. $\sum w_i = 1.0$). На GPU (графическом процессоре) это обычно выполняется в вершинном шейдере (или на предварительном проходе скиннинга в вычислительном шейдере) путем выборки предварительно вычисленной палитры костей из uniform/структурированного буфера с использованием атрибутов индексов костей вершины и линейного смешивания позиций. Сохраняйте преобразования исходной позы, анимированных суставов и меша в согласованных пространствах, включая любые необходимые обратные преобразования меша. Для конвейеров с жесткими костями смешивание преобразованных нормалей и их повторная нормализация является распространенным приближением. При неравномерном масштабировании или сдвиговой деформации (скосе) используйте подходящее обратное транспонированное преобразование нормалей для выбранного приближения деформации; преобразуйте касательные как направления и повторно ортогонализируйте касательный базис. Смешанная матрица, как правило, не является чистым вращением.

#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);
}
Попробовать ответить на вопрос AI-тренеру

2Что такое инстансинг на GPU и как организовать данные, чтобы эффективно рисовать много похожих объектов?

Инстансинг рисует несколько экземпляров общей геометрии одной командой, например DrawIndexedInstanced или glDrawElementsInstanced. Для экземпляров передают преобразование, цвет, индекс материала или фазу анимации. Их можно хранить в отдельном вершинном потоке с шагом на экземпляр или в массиве, который шейдер индексирует по идентификатору экземпляра. Учитывайте смещение первого экземпляра по правилам API. Подбирайте компактное выровненное представление, например аффинную матрицу 3 × 4, проверяя точность и пропускную способность.

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;
}
Попробовать ответить на вопрос AI-тренеру

3Чем различаются отсечение по объёму видимости, отсечение перекрытых объектов и отсечение обратных граней? Где их выполняют?

Проверка объёма видимости отбрасывает объекты целиком вне области обзора, обычно по сфере или ограничивающему параллелепипеду на CPU либо GPU. Отсечение по перекрытию ищет объекты, скрытые непрозрачной геометрией, например через иерархический буфер глубины или запросы видимости. Отсечение обратных граней обычно выполняет растеризатор по порядку вершин треугольника в оконных координатах и настройке лицевой стороны. На уровне групп треугольников возможна приближённая проверка конуса нормалей.

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
}
Попробовать ответить на вопрос AI-тренеру

4Как сплайновые кривые — Безье, B-сплайны и сплайны Катмулла — Рома — задают плавные траектории или геометрические полосы?

Сплайновые кривые задают параметрические зависимости $\mathbf{P}(t)$ для определения плавных трехмерных путей, траекторий камер и экструдированных геометрических полос (таких как ленты, дороги или трубы). 1. **Типы кривых и их свойства:** - **Кривые Безье:** Задаются с помощью полиномов Бернштейна. Они интерполируют только конечные точки; промежуточные контрольные точки задают касательные направляющие. Коллинеарные и сонаправленные касательные направляющие обеспечивают геометрическую непрерывность G1. Параметрическая непрерывность C1 дополнительно требует совпадения величин производных с учетом степеней сегментов и интервалов параметров. - **B-сплайны:** Строятся с использованием базисных функций над вектором узлов. Они обеспечивают локальный контроль и высокую параметрическую непрерывность (C2 для кубических сплайнов на простых внутренних узлах; кратные узлы снижают непрерывность), но, как правило, не проходят через внутренние контрольные точки. - **Сплайны Катмулла — Рома:** Класс интерполирующих сплайнов, которые проходят непосредственно через все внутренние контрольные точки с непрерывностью C1 при согласованной невырожденной параметризации, что делает их идеальными для путей, создаваемых пользователем. 2. **Генерация путей и геометрии:** - Вычисление сплайна при значении параметра $t$ дает позицию $\mathbf{P}(t)$ и вектор касательной $\mathbf{T}(t) = \mathbf{P}'(t)$. - Для экструзии трехмерных лент или труб вдоль кривой необходима ортогональная система координат (нормаль $\mathbf{N}(t)$ и бинормаль $\mathbf{B}(t)$). - Стандартные трехгранники Френе — Серре становятся неопределёнными или переворачиваются в точках перегиба, где кривизна $\kappa = 0$. Чтобы предотвратить неестественное скручивание ленты, **базисы параллельного переноса (базисы Бишопа)** плавно распространяют опорную ориентацию вдоль кривой, минимизируя вращательное кручение.

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 );
}
Попробовать ответить на вопрос AI-тренеру

5Что такое буфер команд и зачем игровому движку многопоточная запись команд?

Буфер или список команд хранит записанные на CPU операции рисования, вычислений, копирования, привязки ресурсов и синхронизации. Затем его отправляют в очередь GPU. Потоки CPU могут параллельно записывать независимые списки, например для теней и частей основного прохода, уменьшая время подготовки кадра. Vulkan поддерживает первичные и вторичные буферы, а D3D12 — списки команд и bundles. Соблюдайте правила владения пулами и аллокаторами. Порядок отправки и зависимости ресурсов определяются отдельно от параллельности записи.

// 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);
Попробовать ответить на вопрос AI-тренеру

6Чем push constants в Vulkan и корневые константы D3D12 отличаются от uniform-буферов и буферов констант?

Малые значения передаются командами непосредственно в состояние конвейера. Корневая сигнатура D3D12 задаёт их раскладку, а значения устанавливает список команд. Vulkan гарантирует поддержку не менее 128 байт push constants. В D3D12 вся корневая сигнатура ограничена 64 DWORD, которые делят константы, дескрипторы и таблицы. Такой способ удобен для небольшого индекса или параметра, часто меняющегося между вызовами. Буфер с привязкой ресурса подходит для больших или общих данных, например параметров камеры на весь кадр.

// 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);
Попробовать ответить на вопрос AI-тренеру

7Через какие системы координат проходит вершина от модели до экрана при рендеринге в реальном времени?

Вершина задана в локальных координатах объекта. Матрица модели переводит её в мир, матрица вида — в координаты камеры, матрица проекции — в однородные координаты отсечения $(x_c,y_c,z_c,w_c)$. Примитивы обрезаются по границам видимого объёма. Деление первых трёх компонент на $w_c$ даёт нормализованные координаты устройства (NDC). Преобразование области вывода переводит их в оконные координаты и глубину в диапазоне, заданном API.

// 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)
Попробовать ответить на вопрос AI-тренеру

Вопросы для Middle-разработчиков

8Чем линейный скиннинг и скиннинг двойными кватернионами отличаются по деформации, артефактам и сложности?

Линейный скиннинг складывает положения после преобразований костей с весами. Он прост и поддерживает аффинные преобразования, но при сильном скручивании может сжать поперечное сечение. Двойные кватернионы смешивают жёсткие повороты и переносы, обычно лучше сохраняя объём при скручивании. При сильном сгибе возможны выпуклости. Нужно согласовывать знаки кватернионов и нормализовать результат. Неравномерный масштаб и сдвиг требуют отдельной обработки, что усложняет шейдеры и подготовку ресурсов.

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;
}
Попробовать ответить на вопрос AI-тренеру

9Анимация искажает только некоторые меши. Какие данные ресурсов и шейдера проверить?

Сверьте форматы и смещения атрибутов индексов и весов, их диапазон и соответствие палитре костей. Проверьте, сколько влияний экспортировано и сколько читает шейдер. После усечения веса должны нормироваться, если это требуется выбранной схемой. Проверьте обратные матрицы исходной позы, порядок костей, пространства и матричное соглашение. Сравните правильный и проблемный меш в исходной позе, затем включайте по одной кости и проверяйте данные, загруженные в буфер.

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!");
    }
}
Попробовать ответить на вопрос AI-тренеру

10Что такое морф-цели, или blend shapes, и как сочетать их со скелетной деформацией лица?

Морф-цель хранит изменения координат и иногда нормалей и касательных относительно базовой сетки. Взвешенные изменения прибавляют к исходным атрибутам. Для целей, заданных в пространстве исходной позы, сначала вычисляют морф, затем скиннинг переносит результат вместе с костями головы и челюсти. Корректирующие цели, заданные в другом пространстве, могут иметь другой порядок. Для экономии памяти храните ненулевые изменения, используйте сжатие или вычисляйте деформацию один раз для нескольких проходов.

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];
}
Попробовать ответить на вопрос AI-тренеру

11Объясните выбор геометрических уровней детализации (LOD), упрощение полигональных сеток и стратегии переходов, обеспечивающие баланс визуальной стабильности, сохранения атрибутов и производительности.

Геометрический уровень детализации (LOD) оптимизирует производительность рендеринга за счет уменьшения сложности полигональной сетки по мере удаления объектов от камеры, обеспечивая баланс между визуальным качеством и частотой кадров. 1. Выбор LOD: уровни LOD следует выбирать с использованием экранных метрик (таких как проецируемый диаметр ограничивающей сферы, процент высоты экрана или проецируемая ошибка в пикселях), а не статического расстояния в мировом пространстве, чтобы учитывать изменения угла обзора (FOV) камеры и разрешения. Чтобы предотвратить быстрое мерцание между LOD на границах расстояний («LOD thrashing»), применяется гистерезис путем поддержания раздельных порогов для переключения вверх и вниз. 2. Упрощение сеток: предварительная генерация обычно основана на метриках квадратичной ошибки (Quadric Error Metrics, QEM) через итеративное схлопывание ребер. Для сохранения визуального качества алгоритмы упрощения должны сохранять силуэты границ и штрафовать геометрические искажения, а также сохранять атрибуты вершин (швы UV, разрывы нормалей, цвета вершин и веса скиннинга), включая члены ошибок атрибутов в квадратичную метрику. 3. Стратегии переходов: чтобы предотвратить резкое визуальное «выскакивание» (popping), движки применяют: - Плавный переход с дизерингом / трафаретное прореживание (Screen-Door Stippling): отбрасывает пиксели в пиксельном шейдере с использованием чередующегося шаблона дизеринга (например, матрицы Байера), плавно переключая LOD без обычного альфа-смешивания, однако отбрасывание фрагментов или изменение покрытия может ограничивать раннюю запись глубины или другие оптимизации ранней проверки глубины (early-Z) в зависимости от API и оборудования; измеряйте затраты на двойную отрисовку во время перехода. - Геоморфинг (Geomorphing): интерполирует положения вершин между сетками соседних LOD на GPU (графическом процессоре) в течение короткого окна перехода с использованием явного сопоставления вершин/схлопываний; произвольные сетки с разной топологией не могут просто интерполировать индексы вершин.

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;
Попробовать ответить на вопрос AI-тренеру

12Что такое оптимизация буфера индексов и вершинного кэша? Почему порядок треугольников влияет на повторное использование обработанных вершин?

Если соседние в буфере индексов треугольники используют общие вершины, GPU может повторно взять результаты вершинного шейдера. Перестановка треугольников сокращает расстояние между повторными ссылками. Отдельно можно переставить записи вершин по порядку использования, обновив индексы, чтобы улучшить локальность чтения. Модель повторного использования зависит от GPU, поэтому сравнивайте число запусков шейдера и время кадра. Сохраняйте ориентацию и учитывайте проходы, где порядок примитивов влияет на результат.

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;
}
Попробовать ответить на вопрос AI-тренеру

Вопросы для Senior-разработчиков

13Как отбрасывать закрытые объекты без ожидания GPU и заметного запаздывания их появления?

Выполняйте проверку и построение списка команд на GPU, не читая результат синхронно на CPU. В двухфазной схеме предыдущая глубина даёт предварительную оценку. Нарисуйте видимые объекты, построив актуальный буфер, и перепроверьте отклонённые до конца кадра. Другой вариант — консервативный буфер перекрытий на CPU по упрощённым препятствиям. Запас в границах и временная задержка отбрасывания снижают риск пропуска при движении. Проверяйте сценарии резкого изменения камеры.

// 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;
    }
}
Попробовать ответить на вопрос AI-тренеру

14Как подготовить сетку из редактора к загрузке в буферы GPU? Что проверять при генерации касательных и квантовании?

Проверьте индексы, конечность атрибутов, вырожденные треугольники и соглашения координат. Разделите вершины по несовместимым UV и нормалям и сгенерируйте касательные в том же базисе, что при запекании. Оптимизируйте порядок треугольников и чтения вершин, при необходимости постройте уровни детализации и мешлеты. Квантуйте атрибуты под допустимую ошибку, сохраняя масштаб и формат декодирования. Сериализуйте буферы, границы и раскладки. Проверьте экспорт и результат чтения шейдером на эталонных ресурсах.

// 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)
Попробовать ответить на вопрос AI-тренеру

15Что такое мешлеты и как отсечение кластеров и mesh-шейдеры помогают рисовать плотную геометрию?

Мешлет — небольшая группа треугольников с локальными индексами, ограничивающим объёмом и иногда конусом нормалей. По этим данным можно отбросить группу или выбрать детализацию. При поддержке mesh-шейдеров рабочая группа совместно преобразует вершины и выводит примитивы. Task-шейдер может запускать только нужные группы. Плотные сцены могут использовать иерархию кластеров и сочетать аппаратную и вычислительную растеризацию. Это отдельные проектные решения, а лимиты размеров и стоимость зависят от устройства.

#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);
    }
}
Попробовать ответить на вопрос AI-тренеру

Собеседования на телефоне: Computer Graphics

Отвечайте голосом и получайте обратную связь от ИИ в приложении для Android.

Скачать EngineerSpeak в Google Play
Сканируйте для скачивания