Движок визуализации 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. Он демонстрирует использование трехмерной текстуры в тестовом приложении для визуализации распределения теплового излучения в пространстве от нагрева деталей материнской платы, находящейся внутри замкнутого контура. Рассмотрим этот пример подробнее.
В первую очередь, необходимо открыть и построить в сцене модель, поверх которой будет отрисовываться текстура.
Далее необходимо создать строковый поток 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, разрабатывая новые методы и классы для повышения удобства и скорости его работы. Все это становится возможным не только благодаря анализу существующих потребностей автоматизированных систем, но и благодаря активным пользователям, так же, как и мы, стремящимся к повышению качества модуля визуализации.
Автор:
Артем Максименко
Продакт-менеджер C3D Labs