02.05.2023 • C3D Vision

Движок C3D Vision управляет отображением 3D-текстур «на лету»

Движок визуализации C3D Vision пополнился новым функционалом. Теперь у пользователей есть возможность создания объемных текстур и отображения их в сцене. В этой заметке мы расскажем об объектах API Vision, непосредственно работающих с текстурами, а также продемонстрируем на уровне кода, как с этими объектами может взаимодействовать пользователь.

Что нового в API Vision?

Новый функционал позволяет пользователю не только использовать 3D текстуры, но и управлять их отрисовкой. Для этих целей в API Vision разработаны два класса – Texture3D и RenderPostProcess. Рассмотрим эти классы подробнее.

Texture3D представляет стандартный C++ класс и служит для обработки 3D-текстур. Чтобы работать с ними внутри, Vision необходимо создать объект этого класса и инициализировать его с помощью данных текстуры. В качестве таких данных могут выступать битовая карта текстуры или файл в формате изображения. Среди прочих возможностей класса есть функции получения ширины, высоты, глубины загруженной текстуры, проверки альфа-канала, а также функции для задания фильтров растеризации и режима повтора текстуры.

class Texture3D
{
public:
    /// \ru Конструктор по умолчанию. \en Default constructor. \~
    Texture3D();
    /// \ru Конструктор копирования. \en Copy constructor. \~
    Texture3D(const Texture3D& other) = delete;
    /// \ru Деструктор. \en Destructor. \~
    virtual ~Texture3D();

    <...>
}

RenderPostProcess также является стандартным классом и позволяет выполнять финальную постобработку сцены. В качестве входного параметра для создания объекта класса используется строковый поток, содержащий код программы для исполнения процессорами видеокарты — шейдер. RenderPostProcess вызывает его после рендеринга основной сцены и вносит пользовательские корректировки при отрисовке текстуры с ее изображением на экран. Такой подход позволяет пользователю самостоятельно управлять параметрами отображения текстуры прямо «на лету». Кроме того, RenderPostProcess содержит методы для добавления объектов матрицы и текстуры со строковыми параметрами name. Они необходимы шейдеру для доступа к этим объектам во время рендеринга текстуры.

class RenderPostProcess
{
public:
    /// \ru Конструктор. \en Constructor. \~
    explicit RenderPostProcess(std::istream& pixelShader);
    /// \ru Конструктор. \en Constructor. \~
    explicit RenderPostProcess(std::istream& vertexShader, std::istream& pixelShader);
    /// \ru Деструктор. \en Destructor. \~
    ~RenderPostProcess();

    <...>
}

Пример использования новых классов

Среди примеров, поставляющихся с дистрибутивом C3D Vision, существует проект 10_Texture3D. Он демонстрирует использование трехмерной текстуры в тестовом приложении для визуализации распределения теплового излучения в пространстве от нагрева деталей материнской платы, находящейся внутри замкнутого контура. Рассмотрим этот пример подробнее.

В первую очередь, необходимо открыть и построить в сцене модель, поверх которой будет отрисовываться текстура.

Движок C3D Vision управляет отображением 3D-текстур «на лету», фото 1
Открытая модель

Далее необходимо создать строковый поток std::stringstream pixelShader и передать в него описание шейдера на языке GLSL в виде глобальной переменной shaderSrc.

std::stringstream pixelShader(shaderSrc);
RenderPostProcess* pPostProcess = new RenderPostProcess(pixelShader);

В подобной переменной может храниться любой шейдер.

static const char shaderSrc[] =
"#version 110\n"

"varying vec2 VSN_TEXCOORD;"

"uniform sampler2D VSN_SCENE_COLOR_TEXTURE;"
"uniform sampler2D VSN_SCENE_DEPTH_TEXTURE;"
"uniform mat4 VSN_INV_VIEW_PROJECTION;"

"uniform sampler3D ValueTexture;"
"uniform sampler1D CoverTexture;"

"uniform mat4 WorldToTexture;"

"vec3 ToWorld(vec2 texCoord, float depth)"
"{"
"    vec3 screenPos = vec3(texCoord.xy, depth);"
"    <...>"

"    return position.xyz / position.w;"
"}"

"void main(void)"
"{"
"    float depth = texture2D(VSN_SCENE_DEPTH_TEXTURE, VSN_TEXCOORD).a;"
"    <...>"

"    vec4 colorTex = texture2D(VSN_SCENE_COLOR_TEXTURE, VSN_TEXCOORD);"
"    gl_FragColor = vec4(mix(colorTex.rgb, accum.rgb, accum.a  / (colorTex.a + accum.a)), max(colorTex.a, accum.a));"
"}";

После создается указатель на класс Texture3D и инициализируется внутри функции loadTexture3D.

Matrix3DF transform;
Texture3D* tex3d = loadTexture3D(transform);

В этой функции из файла текстуры fv_texture3d.dat считываются данные с помощью объекта потока. Полученные данные используются для инициализации вектора данных текстуры std::vector<const unsigned char*> texData.

static Texture3D* loadTexture3D(Matrix3DF& transform)
{
    QFile file(":/fv_texture3d.dat");
    <...>
        QDataStream dataStream(fileData);
    <...>

        // размер x-y-z сетки
        uint32_t sizeX, sizeY, sizeZ;
    dataStream >> sizeX;
    dataStream >> sizeY;
    dataStream >> sizeZ;

    // положение бокса
    Vector3DF origin;
    dataStream >> origin.x;
    dataStream >> origin.y;
    dataStream >> origin.z;

    // оси бокса
    Vector3DF axis[3];
    for (int i = 0; i < 3; i++)
    {
        dataStream >> axis[i].x;
        dataStream >> axis[i].y;
        dataStream >> axis[i].z;
    }

    transform = {
        axis[0].x, axis[0].y, axis[0].z, 0.0f,
        axis[1].x, axis[1].y, axis[1].z, 0.0f,
        axis[2].x, axis[2].y, axis[2].z, 0.0f,
        origin.x,  origin.y,  origin.z,  1.0f
    };

    // линейный массив значений в порядке обхода z-y-x
    auto size = sizeX * sizeY * sizeZ;
    std::vector<float> values(size);
    dataStream.readRawData(reinterpret_cast<char*>(values.data()),
        static_cast<int>(values.size() * sizeof(float)));

    const size_t bgra = 4;
    auto count = sizeX * sizeY * bgra;
    std::vector<const unsigned char*> texData;
    for (uint32_t z = 0; z < sizeZ; z++)
    {
        unsigned char* slice = new unsigned char[count];
        for (uint32_t y = 0; y < sizeY; y++)
        {
            for (uint32_t x = 0; x < sizeX; x++)
            {
                int value_index = z + sizeZ * y + sizeY * sizeZ * x;
                float value = values[value_index];
                auto sliceNum = (y * sizeX + x) * bgra;
                slice[sliceNum + 0] = 255;
                slice[sliceNum + 1] = 255;
                slice[sliceNum + 2] = 255;
                slice[sliceNum + 3] = 0;
                // 0 - отсутствие данных
                if (value != 0.0f)
                    slice[sliceNum + 3] = value * 255;
            }
        }
        texData.push_back(slice);
    }

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

    auto texture3D = new Texture3D();
    texture3D->Init(TextureFormat::BGRA, SizeI(sizeX, sizeY), texData);
    texture3D->SetFilters(TextureFilter::Linear, TextureFilter::Linear);
    texture3D->SetWrap(TextureWrap::NoRepeate, TextureWrap::NoRepeate, TextureWrap::NoRepeate);

    <...>
    return texture3D;
}

Добавляем полученный объект и его имя name в RenderPostProcess* pPostProcess:

pPostProcess->AddTexture("ValueTexture", tex3d);
pPostProcess->AddMatrix("WorldToTexture", transform.Div());

Добавляем палитру цветов для окраски распределения:

ColorsArray* paletteColors = new ColorsArray{
        {0, 0, 255, 0}, {0, 100, 255, 13}, {0, 185, 255, 13}, {0, 255, 255, 25},
        {0, 255, 159, 38}, {0, 255, 0, 51}, {159, 255, 0, 77}, {255, 255, 0, 102},
        {255, 185, 0, 128}, {255, 100, 0, 153}, {255, 0, 0, 205}
};
pPostProcess->AddTexture("CoverTexture", paletteColors);

После устанавливаем постобработку в сцену:

graphicsScene()->SetPostProcess(pPostProcess);

Ниже демонстрируется визуализация сцены после выполнения всех инструкций.

Движок C3D Vision управляет отображением 3D-текстур «на лету», фото 2

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

Артем Максименко, продакт-менеджер C3D Labs
Автор:
Артем Максименко
Продакт-менеджер C3D Labs
Поделиться материалом
Вверх