Движок визуализации 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);
<...>
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