17 #include <vtkCellArray.h>
18 #include <vtkCellData.h>
19 #include <vtkFloatArray.h>
20 #include <vtkImageData.h>
21 #include <vtkImageResize.h>
22 #include <vtkLODActor.h>
24 #include <vtkLightCollection.h>
25 #include <vtkOpenGLRenderWindow.h>
26 #include <vtkPointData.h>
27 #include <vtkPoints.h>
28 #include <vtkPolyData.h>
29 #include <vtkPolyDataMapper.h>
30 #include <vtkProperty.h>
31 #include <vtkRenderer.h>
32 #include <vtkTexture.h>
33 #include <vtkTextureUnitManager.h>
38 #include <QImageReader>
54 if (qimage.isNull()) {
59 QImage rgba_image = qimage.convertToFormat(QImage::Format_RGBA8888);
63 image_data->SetDimensions(rgba_image.width(), rgba_image.height(), 1);
64 image_data->AllocateScalars(VTK_UNSIGNED_CHAR, 4);
67 unsigned char* vtk_ptr =
68 static_cast<unsigned char*
>(image_data->GetScalarPointer());
69 const uchar* qt_ptr = rgba_image.constBits();
71 int width = rgba_image.width();
72 int height = rgba_image.height();
78 int qt_idx = (
y *
width +
x) * 4;
80 vtk_ptr[vtk_idx + 0] = qt_ptr[qt_idx + 0];
81 vtk_ptr[vtk_idx + 1] = qt_ptr[qt_idx + 1];
82 vtk_ptr[vtk_idx + 2] = qt_ptr[qt_idx + 2];
83 vtk_ptr[vtk_idx + 3] = qt_ptr[qt_idx + 3];
92 : impl_(
std::make_unique<
Impl>()) {
94 default_options_.quality = TextureQuality::HIGH;
95 default_options_.filter_mode = FilterMode::MIPMAP;
96 default_options_.enable_mipmaps =
true;
97 default_options_.enable_texture_cache =
true;
101 std::lock_guard<std::mutex> lock(impl_->cache_mutex);
102 texture_cache_.clear();
103 texture_memory_usage_.clear();
111 const std::string& texture_path,
const RenderOptions& options) {
114 std::lock_guard<std::mutex> lock(impl_->cache_mutex);
115 auto it = texture_cache_.find(texture_path);
116 if (it != texture_cache_.end()) {
123 if (qimage.isNull()) {
125 "[VtkMultiTextureRenderer::LoadTexture] Failed to load "
127 texture_path.c_str());
135 "[VtkMultiTextureRenderer::LoadTexture] Failed to convert "
136 "QImage to vtkImageData");
142 texture->SetInputData(image_data);
143 texture->SetInterpolate(1);
145 texture->SetRepeat(1);
146 texture->SetEdgeClamp(0);
150 std::lock_guard<std::mutex> lock(impl_->cache_mutex);
151 texture_cache_[texture_path] = texture;
155 image_data->GetActualMemorySize() * 1024;
156 texture_memory_usage_[texture_path] = memory_size;
160 "[VtkMultiTextureRenderer::LoadTexture] Loaded and cached texture: "
162 texture_path.c_str(), qimage.width(), qimage.height());
170 VtkMultiTextureRenderer::RenderingMode
171 VtkMultiTextureRenderer::DetectRenderingMode(
172 const PBRMaterial& material)
const {
175 if (material.hasMultipleMapKd) {
177 "[VtkMultiTextureRenderer::DetectRenderingMode] Multiple "
178 "map_Kd detected, using traditional multi-texture rendering "
180 return RenderingMode::TEXTURED;
183 if (material.hasPBRTextures()) {
184 return RenderingMode::PBR;
185 }
else if (material.hasAnyTexture()) {
186 return RenderingMode::TEXTURED;
188 return RenderingMode::MATERIAL_ONLY;
192 void VtkMultiTextureRenderer::SetProperties(vtkProperty* property,
193 const PBRMaterial& material,
195 property->SetInterpolationToPBR();
196 property->SetColor(material.baseColor[0], material.baseColor[1],
197 material.baseColor[2]);
199 property->SetAmbientColor(material.ambientColor[0],
200 material.ambientColor[1],
201 material.ambientColor[2]);
202 property->SetDiffuseColor(material.diffuseColor[0],
203 material.diffuseColor[1],
204 material.diffuseColor[2]);
205 property->SetSpecularColor(material.specularColor[0],
206 material.specularColor[1],
207 material.specularColor[2]);
209 property->SetAmbient(1.0f);
210 property->SetDiffuse(1.0f);
211 property->SetSpecular(1.0f);
212 property->SetSpecularPower(material.shininess);
213 property->SetOpacity(opacity);
215 property->BackfaceCullingOff();
216 property->FrontfaceCullingOff();
219 "[VtkMultiTextureRenderer::SetProperties] Setting properties: "
220 "specularPower=%.2f, opacity=%.2f",
221 material.shininess, opacity);
232 vtkRenderer* renderer) {
238 "[VtkMultiTextureRenderer::ApplyPBRMaterial] Actor is null");
242 vtkProperty*
property = actor->GetProperty();
245 "[VtkMultiTextureRenderer::ApplyPBRMaterial] Property is null");
250 float opacity = std::max(0.0f, std::min(1.0f, material.
opacity));
254 if (opacity < 1.0f) {
255 actor->ForceTranslucentOn();
256 actor->ForceOpaqueOff();
259 vtkRenderWindow* renderWindow = renderer->GetRenderWindow();
262 renderWindow->SetAlphaBitPlanes(1);
266 renderer->UseDepthPeelingOn();
267 renderer->SetMaximumNumberOfPeels(4);
268 renderer->SetOcclusionRatio(0.0);
271 "[VtkMultiTextureRenderer::ApplyPBRMaterial] Enabled "
272 "transparency rendering support: opacity=%.3f, depth "
278 actor->ForceTranslucentOff();
279 actor->ForceOpaqueOn();
282 "[VtkMultiTextureRenderer::ApplyPBRMaterial] Material is fully "
283 "opaque (opacity=%.3f), transparent rendering disabled",
286 float metallic = std::max(0.0f, std::min(1.0f, material.
metallic));
287 float roughness = std::max(0.0f, std::min(1.0f, material.
roughness));
293 if (material.
opacity != opacity) {
295 "[VtkMultiTextureRenderer::ApplyPBRMaterial] Invalid opacity "
296 "%.2f, clamped to %.2f",
300 if (material.
metallic != metallic) {
302 "[VtkMultiTextureRenderer::ApplyPBRMaterial] Invalid metallic "
303 "%.2f, clamped to %.2f",
309 "[VtkMultiTextureRenderer::ApplyPBRMaterial] Invalid roughness "
310 "%.2f, clamped to %.2f",
314 if (material.
ao != ao) {
316 "[VtkMultiTextureRenderer::ApplyPBRMaterial] Invalid AO %.2f, "
324 RenderingMode mode = DetectRenderingMode(material);
327 "[VtkMultiTextureRenderer::ApplyPBRMaterial] Detected rendering "
329 mode == RenderingMode::PBR ?
"PBR"
330 : mode == RenderingMode::TEXTURED ?
"TEXTURED"
335 case RenderingMode::PBR:
336 result = ApplyPBRRendering(actor, material, polydata, renderer,
337 metallic, roughness, ao, opacity);
339 case RenderingMode::TEXTURED:
340 result = ApplyTexturedRendering(actor, material, opacity);
342 case RenderingMode::MATERIAL_ONLY:
343 result = ApplyMaterialOnlyRendering(actor, material, opacity);
349 "[VtkMultiTextureRenderer::ApplyPBRMaterial] Material "
350 "application complete");
353 "[VtkMultiTextureRenderer::ApplyPBRMaterial] Material "
354 "application failed");
360 bool VtkMultiTextureRenderer::ApplyPBRRendering(
362 const PBRMaterial& material,
364 vtkRenderer* renderer,
372 #if VTK_MAJOR_VERSION >= 9
374 "[VtkMultiTextureRenderer::ApplyPBRRendering] Using VTK 9+ PBR "
377 vtkProperty*
property = actor->GetProperty();
380 "[VtkMultiTextureRenderer::ApplyPBRRendering] Property is "
387 property->RemoveAllTextures();
392 "[VtkMultiTextureRenderer::ApplyPBRRendering] PolyData is null "
393 "for PBR rendering");
398 vtkDataArray* tcoords = polydata->GetPointData()->GetTCoords();
401 tcoords = polydata->GetPointData()->GetArray(
"TCoords0");
403 polydata->GetPointData()->SetTCoords(tcoords);
405 "[VtkMultiTextureRenderer::ApplyPBRRendering] Set TCoords0 "
406 "as active texture coordinates");
409 "[VtkMultiTextureRenderer::ApplyPBRRendering] No texture "
410 "coordinates found, PBR textures may not display "
417 "[VtkMultiTextureRenderer::ApplyPBRRendering] Texture "
418 "coordinates: %lld points",
419 tcoords->GetNumberOfTuples());
423 property->SetInterpolationToPBR();
426 property->SetColor(material.baseColor[0], material.baseColor[1],
427 material.baseColor[2]);
428 property->SetOpacity(opacity);
434 bool has_orm_texture = (!material.aoTexture.empty() ||
435 !material.roughnessTexture.empty() ||
436 !material.metallicTexture.empty());
440 if (!has_orm_texture) {
441 property->SetMetallic(metallic);
442 property->SetRoughness(roughness);
443 property->SetOcclusionStrength(ao);
445 "[VtkMultiTextureRenderer::ApplyPBRRendering] Using scalar "
446 "values: metallic=%.2f, roughness=%.2f, ao=%.2f",
447 metallic, roughness, ao);
450 "[VtkMultiTextureRenderer::ApplyPBRRendering] ORM texture will "
451 "be used, skipping scalar metallic/roughness/ao settings to "
455 #if VTK_VERSION_NUMBER >= VTK_VERSION_CHECK(9, 2, 0)
457 property->SetAnisotropy(std::max(
458 0.0, std::min(1.0,
static_cast<double>(material.anisotropy))));
459 property->SetAnisotropyRotation(std::max(
461 std::min(1.0,
static_cast<double>(material.anisotropyRotation))));
462 property->SetCoatStrength(std::max(
463 0.0, std::min(1.0,
static_cast<double>(material.clearcoat))));
464 property->SetCoatRoughness(std::max(
466 std::min(1.0,
static_cast<double>(material.clearcoatRoughness))));
470 "[VtkMultiTextureRenderer::ApplyPBRRendering] Advanced PBR: "
471 "anisotropy=%.2f, clearcoat=%.2f, clearcoatRoughness=%.2f",
472 material.anisotropy, material.clearcoat,
473 material.clearcoatRoughness);
477 "[VtkMultiTextureRenderer::ApplyPBRRendering] Base properties: "
478 "color=(%.2f,%.2f,%.2f), metallic=%.2f, roughness=%.2f, ao=%.2f",
479 material.baseColor[0], material.baseColor[1], material.baseColor[2],
480 metallic, roughness, ao);
483 if (!material.baseColorTexture.empty()) {
486 material.baseColorTexture)) {
488 "[VtkMultiTextureRenderer::ApplyPBRMaterial] BaseColor "
489 "texture file not found: %s",
490 material.baseColorTexture.c_str());
494 if (!albedo_img.isNull()) {
496 if (albedo_img.width() <= 0 || albedo_img.height() <= 0) {
498 "[VtkMultiTextureRenderer::ApplyPBRMaterial] "
499 "Invalid BaseColor texture dimensions: %dx%d",
500 albedo_img.width(), albedo_img.height());
503 if (albedo_img.format() != QImage::Format_RGB888) {
504 albedo_img = albedo_img.convertToFormat(
505 QImage::Format_RGB888);
509 impl_->QImageToVtkImage(albedo_img);
513 tex->SetInputData(image_data);
514 tex->SetInterpolate(1);
516 tex->UseSRGBColorSpaceOn();
519 property->SetBaseColorTexture(tex);
521 "[VtkMultiTextureRenderer::ApplyPBRMaterial] "
522 "Applied BaseColor texture: %dx%d",
523 albedo_img.width(), albedo_img.height());
526 "[VtkMultiTextureRenderer::ApplyPBRMaterial] "
527 "Failed to convert BaseColor image to VTK "
533 "[VtkMultiTextureRenderer::ApplyPBRMaterial] Failed to "
534 "load BaseColor texture: %s",
535 material.baseColorTexture.c_str());
540 "[VtkMultiTextureRenderer::ApplyPBRMaterial] No BaseColor "
541 "texture specified, using base color: (%.2f,%.2f,%.2f)",
542 material.baseColor[0], material.baseColor[1],
543 material.baseColor[2]);
547 if (!material.aoTexture.empty() || !material.roughnessTexture.empty() ||
548 !material.metallicTexture.empty()) {
550 "[VtkMultiTextureRenderer::ApplyPBRMaterial] Creating ORM "
551 "texture (O=%d, R=%d, M=%d)",
552 !material.aoTexture.empty(), !material.roughnessTexture.empty(),
553 !material.metallicTexture.empty());
556 QImage ao_img, roughness_img, metallic_img;
559 if (!material.aoTexture.empty()) {
561 if (!ao_img.isNull()) {
562 width = ao_img.width();
566 if (!material.roughnessTexture.empty()) {
569 if (!roughness_img.isNull() && roughness_img.width() >
width) {
570 width = roughness_img.width();
571 height = roughness_img.height();
574 if (!material.metallicTexture.empty()) {
577 if (!metallic_img.isNull() && metallic_img.width() >
width) {
578 width = metallic_img.width();
579 height = metallic_img.height();
584 if (
width <= 0 || height <= 0 || width > 8192 ||
height > 8192) {
586 "[VtkMultiTextureRenderer::ApplyPBRMaterial] Invalid ORM "
587 "texture dimensions: %dx%d, using default 1024x1024",
597 "[VtkMultiTextureRenderer::ApplyPBRMaterial] Failed to "
598 "create ORM texture");
602 (!material.aoTexture.empty() && !ao_img.isNull())
604 Qt::IgnoreAspectRatio,
605 Qt::SmoothTransformation)
607 QImage::Format_Grayscale8)
608 : QImage(
width,
height, QImage::Format_Grayscale8);
609 if (material.aoTexture.empty() || ao_img.isNull())
613 (!material.roughnessTexture.empty() &&
614 !roughness_img.isNull())
617 Qt::IgnoreAspectRatio,
618 Qt::SmoothTransformation)
620 QImage::Format_Grayscale8)
621 : QImage(
width,
height, QImage::Format_Grayscale8);
622 if (material.roughnessTexture.empty() || roughness_img.isNull())
623 r_gray.fill(qRgb(128, 128, 128));
626 (!material.metallicTexture.empty() &&
627 !metallic_img.isNull())
630 Qt::IgnoreAspectRatio,
631 Qt::SmoothTransformation)
633 QImage::Format_Grayscale8)
634 : QImage(
width,
height, QImage::Format_Grayscale8);
635 if (material.metallicTexture.empty() || metallic_img.isNull())
640 uchar* o_line = o_gray.scanLine(
y);
641 uchar* r_line = r_gray.scanLine(
y);
642 uchar* m_line = m_gray.scanLine(
y);
643 uchar* orm_line = orm.scanLine(
y);
646 orm_line[
x * 3 + 0] = o_line[
x];
647 orm_line[
x * 3 + 1] = r_line[
x];
648 orm_line[
x * 3 + 2] = m_line[
x];
654 impl_->QImageToVtkImage(orm);
658 tex->SetInputData(orm_data);
659 tex->SetInterpolate(1);
662 property->SetORMTexture(tex);
664 "[VtkMultiTextureRenderer::ApplyPBRMaterial] Applied "
665 "ORM texture (%dx%d)",
674 vtkLightCollection* lights = renderer->GetLights();
675 int num_lights = lights ? lights->GetNumberOfItems() : 0;
678 renderer->UseImageBasedLightingOn();
679 renderer->AutomaticLightCreationOn();
682 renderer->SetAmbient(1.0, 1.0, 1.0);
687 if (num_lights == 0) {
690 light->SetLightTypeToHeadlight();
692 light->SetIntensity(1.5);
693 light->SetColor(1.0, 1.0, 1.0);
694 renderer->AddLight(
light);
697 "[VtkMultiTextureRenderer::ApplyPBRMaterial] Lighting "
698 "configured (first time): ambient=1.0, headlight "
703 "[VtkMultiTextureRenderer::ApplyPBRMaterial] Found %d "
704 "existing lights, enhancing for textured model",
709 "[VtkMultiTextureRenderer::ApplyPBRMaterial] No renderer "
710 "provided, lighting may be insufficient");
714 property->BackfaceCullingOff();
715 property->FrontfaceCullingOff();
717 property->SetLighting(
true);
718 property->SetShading(
true);
719 property->SetAmbient(1.0f);
720 property->SetDiffuse(1.0f);
721 property->SetSpecular(1.0f);
724 "[VtkMultiTextureRenderer::ApplyPBRMaterial] VTK < 9.0, falling "
726 property->SetInterpolationToPhong();
727 property->SetColor(material.baseColor[0], material.baseColor[1],
728 material.baseColor[2]);
730 property->SetAmbientColor(material.ambientColor[0],
731 material.ambientColor[1],
732 material.ambientColor[2]);
733 property->SetDiffuseColor(material.diffuseColor[0],
734 material.diffuseColor[1],
735 material.diffuseColor[2]);
736 property->SetSpecularColor(material.specularColor[0],
737 material.specularColor[1],
738 material.specularColor[2]);
742 property->SetAmbient(1.0f);
743 property->SetDiffuse(1.0f);
744 property->SetSpecular(1.0f);
745 property->SetSpecularPower(material.shininess);
746 property->SetOpacity(opacity);
749 if (!material.baseColorTexture.empty()) {
753 impl_->QImageToVtkImage(img);
757 tex->SetInputData(image_data);
758 tex->SetInterpolate(1);
760 actor->SetTexture(tex);
767 "[VtkMultiTextureRenderer::ApplyPBRRendering] PBR rendering setup "
772 bool VtkMultiTextureRenderer::ApplyTexturedRendering(
774 const PBRMaterial& material,
780 "[VtkMultiTextureRenderer::ApplyTexturedRendering] Using single "
783 vtkProperty*
property = actor->GetProperty();
786 "[VtkMultiTextureRenderer::ApplyTexturedRendering] Property is "
792 SetProperties(property, material, opacity);
795 std::string tex_path = material.baseColorTexture;
796 if (tex_path.empty()) tex_path = material.emissiveTexture;
798 if (!tex_path.empty()) {
802 "[VtkMultiTextureRenderer::ApplyTexturedRendering] Texture "
803 "file not found: %s",
811 impl_->QImageToVtkImage(img);
815 tex->SetInputData(image_data);
816 tex->SetInterpolate(1);
818 actor->SetTexture(tex);
820 "[VtkMultiTextureRenderer::ApplyTexturedRendering] "
821 "Applied single texture: %dx%d",
822 img.width(), img.height());
825 "[VtkMultiTextureRenderer::ApplyTexturedRendering] "
826 "Failed to convert QImage to vtkImageData");
831 "[VtkMultiTextureRenderer::ApplyTexturedRendering] Failed "
832 "to load texture: %s",
838 "[VtkMultiTextureRenderer::ApplyTexturedRendering] No texture "
844 "[VtkMultiTextureRenderer::ApplyTexturedRendering] Textured "
845 "rendering setup complete");
849 bool VtkMultiTextureRenderer::ApplyMaterialOnlyRendering(
851 const PBRMaterial& material,
857 "[VtkMultiTextureRenderer::ApplyMaterialOnlyRendering] Using "
858 "material-only PBR mode");
860 vtkProperty*
property = actor->GetProperty();
863 "[VtkMultiTextureRenderer::ApplyMaterialOnlyRendering] "
869 property->SetInterpolationToPBR();
872 property->SetColor(material.baseColor[0], material.baseColor[1],
873 material.baseColor[2]);
874 property->SetOpacity(opacity);
879 float metallic = std::max(0.0f, std::min(1.0f, material.metallic));
880 float roughness = std::max(0.0f, std::min(1.0f, material.roughness));
881 float ao = std::max(0.0f, std::min(2.0f, material.ao));
883 property->SetMetallic(metallic);
884 property->SetRoughness(roughness);
885 property->SetOcclusionStrength(ao);
887 #if VTK_VERSION_NUMBER >= VTK_VERSION_CHECK(9, 2, 0)
889 property->SetAnisotropy(std::max(
890 0.0, std::min(1.0,
static_cast<double>(material.anisotropy))));
891 property->SetAnisotropyRotation(std::max(
893 std::min(1.0,
static_cast<double>(material.anisotropyRotation))));
894 property->SetCoatStrength(std::max(
895 0.0, std::min(1.0,
static_cast<double>(material.clearcoat))));
896 property->SetCoatRoughness(std::max(
898 std::min(1.0,
static_cast<double>(material.clearcoatRoughness))));
902 "[VtkMultiTextureRenderer::ApplyMaterialOnlyRendering] Advanced "
904 "anisotropy=%.2f, clearcoat=%.2f, clearcoatRoughness=%.2f",
905 material.anisotropy, material.clearcoat,
906 material.clearcoatRoughness);
910 property->SetLighting(
true);
911 property->SetShading(
true);
912 property->BackfaceCullingOff();
913 property->FrontfaceCullingOff();
916 "[VtkMultiTextureRenderer::ApplyMaterialOnlyRendering] Material "
918 "color=(%.2f,%.2f,%.2f), metallic=%.2f, roughness=%.2f, ao=%.2f, "
920 material.baseColor[0], material.baseColor[1], material.baseColor[2],
921 metallic, roughness, ao, opacity);
static bool PrintDebug(const char *format,...)
Same as Print, but works only in Debug mode.
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
static bool Print(const char *format,...)
Prints out a formatted message in console.
static bool PrintVerbose(const char *format,...)
Prints out a verbose formatted message in console.
static bool Error(const char *format,...)
Display an error dialog with formatted message.
bool ApplyPBRMaterial(vtkSmartPointer< vtkActor > actor, const PBRMaterial &material, vtkSmartPointer< vtkPolyData > polydata, vtkRenderer *renderer=nullptr)
Apply PBR material to actor (Filament style)
~VtkMultiTextureRenderer()
VtkMultiTextureRenderer()
static QImage GetTexture(const QString &absoluteFilename)
Returns the texture image associated to a given name.
bool FileExists(const std::string &filename)
colmap::RenderOptions RenderOptions
constexpr Rgb black(0, 0, 0)
constexpr Rgb white(MAX, MAX, MAX)
constexpr Rgbaf light(0.66f, 0.66f, 0.66f, 1.00f)
vtkSmartPointer< vtkImageData > QImageToVtkImage(const QImage &qimage)
Generic PBR material structure (supports multi-texture)