ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
VtkMultiTextureRenderer.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
9 
10 #include <algorithm>
11 #include <fstream>
12 #include <mutex>
13 #include <sstream>
14 
15 // VTK
16 #include <vtkActor.h>
17 #include <vtkCellArray.h>
18 #include <vtkCellData.h>
19 #include <vtkFloatArray.h>
20 #include <vtkImageData.h>
21 #include <vtkImageResize.h>
22 #include <vtkLODActor.h>
23 #include <vtkLight.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>
34 
35 // Qt
36 #include <QFileInfo>
37 #include <QImage>
38 #include <QImageReader>
39 #include <QString>
40 
41 // CloudViewer
42 #include <CVLog.h>
43 #include <FileSystem.h>
44 #include <ecvMaterial.h>
45 
46 namespace VtkUtils {
47 
48 // Pimpl implementation
50  std::mutex cache_mutex; // Thread-safe cache access
51 
52  // Helper function: Create vtkImageData from QImage
54  if (qimage.isNull()) {
55  return nullptr;
56  }
57 
58  // Convert to RGBA format
59  QImage rgba_image = qimage.convertToFormat(QImage::Format_RGBA8888);
60 
63  image_data->SetDimensions(rgba_image.width(), rgba_image.height(), 1);
64  image_data->AllocateScalars(VTK_UNSIGNED_CHAR, 4);
65 
66  // Copy pixel data
67  unsigned char* vtk_ptr =
68  static_cast<unsigned char*>(image_data->GetScalarPointer());
69  const uchar* qt_ptr = rgba_image.constBits();
70 
71  int width = rgba_image.width();
72  int height = rgba_image.height();
73 
74  // VTK's Y-axis is bottom-up, Qt is top-down, need to flip
75  for (int y = 0; y < height; ++y) {
76  for (int x = 0; x < width; ++x) {
77  int vtk_idx = ((height - 1 - y) * width + x) * 4;
78  int qt_idx = (y * width + x) * 4;
79 
80  vtk_ptr[vtk_idx + 0] = qt_ptr[qt_idx + 0]; // R
81  vtk_ptr[vtk_idx + 1] = qt_ptr[qt_idx + 1]; // G
82  vtk_ptr[vtk_idx + 2] = qt_ptr[qt_idx + 2]; // B
83  vtk_ptr[vtk_idx + 3] = qt_ptr[qt_idx + 3]; // A
84  }
85  }
86 
87  return image_data;
88  }
89 };
90 
92  : impl_(std::make_unique<Impl>()) {
93  // Set default options
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;
98 }
99 
101  std::lock_guard<std::mutex> lock(impl_->cache_mutex);
102  texture_cache_.clear();
103  texture_memory_usage_.clear();
104 }
105 
106 // Removed unused CreateMultiTextureActor - not used in codebase
107 
108 // Removed unused UpdateTextures - not used in codebase
109 
110 vtkSmartPointer<vtkTexture> VtkMultiTextureRenderer::LoadTexture(
111  const std::string& texture_path, const RenderOptions& options) {
112  // Check cache first
113  {
114  std::lock_guard<std::mutex> lock(impl_->cache_mutex);
115  auto it = texture_cache_.find(texture_path);
116  if (it != texture_cache_.end()) {
117  return it->second;
118  }
119  }
120 
121  // Load image using Qt (via ccMaterial for consistency)
122  QImage qimage = ccMaterial::GetTexture(texture_path.c_str());
123  if (qimage.isNull()) {
125  "[VtkMultiTextureRenderer::LoadTexture] Failed to load "
126  "texture: %s",
127  texture_path.c_str());
128  return nullptr;
129  }
130 
131  // Convert to vtkImageData
132  vtkSmartPointer<vtkImageData> image_data = impl_->QImageToVtkImage(qimage);
133  if (!image_data) {
135  "[VtkMultiTextureRenderer::LoadTexture] Failed to convert "
136  "QImage to vtkImageData");
137  return nullptr;
138  }
139 
140  // Create texture
142  texture->SetInputData(image_data);
143  texture->SetInterpolate(1); // Linear interpolation
144  texture->MipmapOn(); // Enable mipmaps
145  texture->SetRepeat(1); // Allow texture repeat
146  texture->SetEdgeClamp(0);
147 
148  // Cache texture
149  {
150  std::lock_guard<std::mutex> lock(impl_->cache_mutex);
151  texture_cache_[texture_path] = texture;
152 
153  // Record memory usage
154  size_t memory_size =
155  image_data->GetActualMemorySize() * 1024; // KB to bytes
156  texture_memory_usage_[texture_path] = memory_size;
157  }
158 
160  "[VtkMultiTextureRenderer::LoadTexture] Loaded and cached texture: "
161  "%s (%dx%d)",
162  texture_path.c_str(), qimage.width(), qimage.height());
163 
164  return texture;
165 }
166 
167 // ============================================================================
168 // PBR Material Rendering Implementation (Filament-style)
169 // ============================================================================
170 VtkMultiTextureRenderer::RenderingMode
171 VtkMultiTextureRenderer::DetectRenderingMode(
172  const PBRMaterial& material) const {
173  // If there are multiple map_Kd textures, VTK PBR doesn't support it,
174  // so we must use traditional multi-texture rendering
175  if (material.hasMultipleMapKd) {
176  CVLog::Print(
177  "[VtkMultiTextureRenderer::DetectRenderingMode] Multiple "
178  "map_Kd detected, using traditional multi-texture rendering "
179  "instead of PBR");
180  return RenderingMode::TEXTURED;
181  }
182 
183  if (material.hasPBRTextures()) {
184  return RenderingMode::PBR;
185  } else if (material.hasAnyTexture()) {
186  return RenderingMode::TEXTURED;
187  } else {
188  return RenderingMode::MATERIAL_ONLY;
189  }
190 }
191 
192 void VtkMultiTextureRenderer::SetProperties(vtkProperty* property,
193  const PBRMaterial& material,
194  float opacity) {
195  property->SetInterpolationToPBR();
196  property->SetColor(material.baseColor[0], material.baseColor[1],
197  material.baseColor[2]);
198  // Set RGB colors for ambient, diffuse, specular
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]);
208  // Set intensity coefficients to 1.0 to preserve RGB color brightness
209  property->SetAmbient(1.0f);
210  property->SetDiffuse(1.0f);
211  property->SetSpecular(1.0f);
212  property->SetSpecularPower(material.shininess);
213  property->SetOpacity(opacity);
214 
215  property->BackfaceCullingOff();
216  property->FrontfaceCullingOff();
217 
219  "[VtkMultiTextureRenderer::SetProperties] Setting properties: "
220  "specularPower=%.2f, opacity=%.2f",
221  material.shininess, opacity);
222 }
223 
224 // ============================================================================
225 // Public method implementation - Unified material application interface
226 // ============================================================================
227 
230  const PBRMaterial& material,
232  vtkRenderer* renderer) {
233  // ========================================================================
234  // Input validation
235  // ========================================================================
236  if (!actor) {
237  CVLog::Error(
238  "[VtkMultiTextureRenderer::ApplyPBRMaterial] Actor is null");
239  return false;
240  }
241 
242  vtkProperty* property = actor->GetProperty();
243  if (!property) {
244  CVLog::Error(
245  "[VtkMultiTextureRenderer::ApplyPBRMaterial] Property is null");
246  return false;
247  }
248 
249  // Validate and clamp material parameter ranges
250  float opacity = std::max(0.0f, std::min(1.0f, material.opacity));
251 
252  // Enable transparency rendering support ONLY if opacity < 1.0
253  // This avoids unnecessary performance overhead for opaque materials
254  if (opacity < 1.0f) {
255  actor->ForceTranslucentOn();
256  actor->ForceOpaqueOff();
257 
258  if (renderer) {
259  vtkRenderWindow* renderWindow = renderer->GetRenderWindow();
260  if (renderWindow) {
261  // Enable alpha bit planes for transparency rendering
262  renderWindow->SetAlphaBitPlanes(1);
263  }
264 
265  // Enable depth peeling for proper transparency rendering
266  renderer->UseDepthPeelingOn();
267  renderer->SetMaximumNumberOfPeels(4); // Reasonable default
268  renderer->SetOcclusionRatio(0.0); // Full transparency support
269 
270  CVLog::Print(
271  "[VtkMultiTextureRenderer::ApplyPBRMaterial] Enabled "
272  "transparency rendering support: opacity=%.3f, depth "
273  "peeling enabled",
274  opacity);
275  }
276  } else {
277  // Fully opaque material - ensure transparent rendering is disabled
278  actor->ForceTranslucentOff();
279  actor->ForceOpaqueOn();
280 
282  "[VtkMultiTextureRenderer::ApplyPBRMaterial] Material is fully "
283  "opaque (opacity=%.3f), transparent rendering disabled",
284  opacity);
285  }
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));
288  float ao = std::max(
289  0.0f,
290  std::min(2.0f,
291  material.ao)); // AO can exceed 1.0 for enhanced effect
292 
293  if (material.opacity != opacity) {
295  "[VtkMultiTextureRenderer::ApplyPBRMaterial] Invalid opacity "
296  "%.2f, clamped to %.2f",
297  material.opacity, opacity);
298  }
299 
300  if (material.metallic != metallic) {
302  "[VtkMultiTextureRenderer::ApplyPBRMaterial] Invalid metallic "
303  "%.2f, clamped to %.2f",
304  material.metallic, metallic);
305  }
306 
307  if (material.roughness != roughness) {
309  "[VtkMultiTextureRenderer::ApplyPBRMaterial] Invalid roughness "
310  "%.2f, clamped to %.2f",
311  material.roughness, roughness);
312  }
313 
314  if (material.ao != ao) {
316  "[VtkMultiTextureRenderer::ApplyPBRMaterial] Invalid AO %.2f, "
317  "clamped to %.2f",
318  material.ao, ao);
319  }
320 
321  // ========================================================================
322  // Detect rendering mode and dispatch to corresponding handler
323  // ========================================================================
324  RenderingMode mode = DetectRenderingMode(material);
325 
327  "[VtkMultiTextureRenderer::ApplyPBRMaterial] Detected rendering "
328  "mode: %s",
329  mode == RenderingMode::PBR ? "PBR"
330  : mode == RenderingMode::TEXTURED ? "TEXTURED"
331  : "MATERIAL_ONLY");
332 
333  bool result = false;
334  switch (mode) {
335  case RenderingMode::PBR:
336  result = ApplyPBRRendering(actor, material, polydata, renderer,
337  metallic, roughness, ao, opacity);
338  break;
339  case RenderingMode::TEXTURED:
340  result = ApplyTexturedRendering(actor, material, opacity);
341  break;
342  case RenderingMode::MATERIAL_ONLY:
343  result = ApplyMaterialOnlyRendering(actor, material, opacity);
344  break;
345  }
346 
347  if (result) {
349  "[VtkMultiTextureRenderer::ApplyPBRMaterial] Material "
350  "application complete");
351  } else {
352  CVLog::Error(
353  "[VtkMultiTextureRenderer::ApplyPBRMaterial] Material "
354  "application failed");
355  }
356 
357  return result;
358 }
359 
360 bool VtkMultiTextureRenderer::ApplyPBRRendering(
362  const PBRMaterial& material,
364  vtkRenderer* renderer,
365  float metallic,
366  float roughness,
367  float ao,
368  float opacity) {
369  // ========================================================================
370  // Mode 1: PBR rendering (with PBR textures)
371  // ========================================================================
372 #if VTK_MAJOR_VERSION >= 9
374  "[VtkMultiTextureRenderer::ApplyPBRRendering] Using VTK 9+ PBR "
375  "rendering");
376 
377  vtkProperty* property = actor->GetProperty();
378  if (!property) {
379  CVLog::Error(
380  "[VtkMultiTextureRenderer::ApplyPBRRendering] Property is "
381  "null");
382  return false;
383  }
384 
385  // Clear all traditional texture settings to avoid conflicts
386  // with PBR API
387  property->RemoveAllTextures();
388 
389  // Ensure polydata and texture coordinates exist
390  if (!polydata) {
391  CVLog::Error(
392  "[VtkMultiTextureRenderer::ApplyPBRRendering] PolyData is null "
393  "for PBR rendering");
394  return false;
395  }
396 
397  // Check and set texture coordinates
398  vtkDataArray* tcoords = polydata->GetPointData()->GetTCoords();
399  if (!tcoords) {
400  // Try to find TCoords0
401  tcoords = polydata->GetPointData()->GetArray("TCoords0");
402  if (tcoords) {
403  polydata->GetPointData()->SetTCoords(tcoords);
405  "[VtkMultiTextureRenderer::ApplyPBRRendering] Set TCoords0 "
406  "as active texture coordinates");
407  } else {
409  "[VtkMultiTextureRenderer::ApplyPBRRendering] No texture "
410  "coordinates found, PBR textures may not display "
411  "correctly");
412  }
413  }
414 
415  if (tcoords) {
417  "[VtkMultiTextureRenderer::ApplyPBRRendering] Texture "
418  "coordinates: %lld points",
419  tcoords->GetNumberOfTuples());
420  }
421 
422  // 1. Enable PBR interpolation mode
423  property->SetInterpolationToPBR();
424 
425  // 2. Set base material properties (using clamped values)
426  property->SetColor(material.baseColor[0], material.baseColor[1],
427  material.baseColor[2]);
428  property->SetOpacity(opacity);
429 
430  // Check if we will use ORM texture
431  // If ORM texture is present, scalar values (metallic, roughness, ao) will
432  // be ignored/overridden by texture values. Setting them may cause
433  // multiplication/overlay effects leading to darker rendering.
434  bool has_orm_texture = (!material.aoTexture.empty() ||
435  !material.roughnessTexture.empty() ||
436  !material.metallicTexture.empty());
437 
438  // Only set scalar values if NO ORM texture will be used
439  // When ORM texture is present, VTK uses texture values directly
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);
448  } else {
450  "[VtkMultiTextureRenderer::ApplyPBRRendering] ORM texture will "
451  "be used, skipping scalar metallic/roughness/ao settings to "
452  "avoid conflicts");
453  }
454 
455 #if VTK_VERSION_NUMBER >= VTK_VERSION_CHECK(9, 2, 0)
456  // Advanced PBR parameters (VTK 9.2+)
457  property->SetAnisotropy(std::max(
458  0.0, std::min(1.0, static_cast<double>(material.anisotropy))));
459  property->SetAnisotropyRotation(std::max(
460  0.0,
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(
465  0.0,
466  std::min(1.0, static_cast<double>(material.clearcoatRoughness))));
467  // Note: VTK uses "Coat" terminology for clearcoat
468 
470  "[VtkMultiTextureRenderer::ApplyPBRRendering] Advanced PBR: "
471  "anisotropy=%.2f, clearcoat=%.2f, clearcoatRoughness=%.2f",
472  material.anisotropy, material.clearcoat,
473  material.clearcoatRoughness);
474 #endif
475 
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);
481 
482  // 3. Load BaseColor texture (Albedo)
483  if (!material.baseColorTexture.empty()) {
484  // Check if file exists
486  material.baseColorTexture)) {
488  "[VtkMultiTextureRenderer::ApplyPBRMaterial] BaseColor "
489  "texture file not found: %s",
490  material.baseColorTexture.c_str());
491  } else {
492  QImage albedo_img =
493  ccMaterial::GetTexture(material.baseColorTexture.c_str());
494  if (!albedo_img.isNull()) {
495  // Validate image dimensions
496  if (albedo_img.width() <= 0 || albedo_img.height() <= 0) {
497  CVLog::Error(
498  "[VtkMultiTextureRenderer::ApplyPBRMaterial] "
499  "Invalid BaseColor texture dimensions: %dx%d",
500  albedo_img.width(), albedo_img.height());
501  } else {
502  // Convert to RGB888 format (sRGB)
503  if (albedo_img.format() != QImage::Format_RGB888) {
504  albedo_img = albedo_img.convertToFormat(
505  QImage::Format_RGB888);
506  }
507 
508  vtkSmartPointer<vtkImageData> image_data =
509  impl_->QImageToVtkImage(albedo_img);
510  if (image_data) {
513  tex->SetInputData(image_data);
514  tex->SetInterpolate(1);
515  tex->MipmapOn();
516  tex->UseSRGBColorSpaceOn(); // Filament uses sRGB color
517  // space
518 
519  property->SetBaseColorTexture(tex);
521  "[VtkMultiTextureRenderer::ApplyPBRMaterial] "
522  "Applied BaseColor texture: %dx%d",
523  albedo_img.width(), albedo_img.height());
524  } else {
526  "[VtkMultiTextureRenderer::ApplyPBRMaterial] "
527  "Failed to convert BaseColor image to VTK "
528  "format");
529  }
530  }
531  } else {
533  "[VtkMultiTextureRenderer::ApplyPBRMaterial] Failed to "
534  "load BaseColor texture: %s",
535  material.baseColorTexture.c_str());
536  }
537  }
538  } else {
539  CVLog::Print(
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]);
544  }
545 
546  // 4. Create and apply ORM texture (Occlusion-Roughness-Metallic)
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());
554 
555  // Load each channel
556  QImage ao_img, roughness_img, metallic_img;
557  int width = 1024, height = 1024;
558 
559  if (!material.aoTexture.empty()) {
560  ao_img = ccMaterial::GetTexture(material.aoTexture.c_str());
561  if (!ao_img.isNull()) {
562  width = ao_img.width();
563  height = ao_img.height();
564  }
565  }
566  if (!material.roughnessTexture.empty()) {
567  roughness_img =
568  ccMaterial::GetTexture(material.roughnessTexture.c_str());
569  if (!roughness_img.isNull() && roughness_img.width() > width) {
570  width = roughness_img.width();
571  height = roughness_img.height();
572  }
573  }
574  if (!material.metallicTexture.empty()) {
575  metallic_img =
576  ccMaterial::GetTexture(material.metallicTexture.c_str());
577  if (!metallic_img.isNull() && metallic_img.width() > width) {
578  width = metallic_img.width();
579  height = metallic_img.height();
580  }
581  }
582 
583  // Validate size reasonableness
584  if (width <= 0 || height <= 0 || width > 8192 || height > 8192) {
586  "[VtkMultiTextureRenderer::ApplyPBRMaterial] Invalid ORM "
587  "texture dimensions: %dx%d, using default 1024x1024",
588  width, height);
589  width = 1024;
590  height = 1024;
591  }
592 
593  // Create ORM texture (R=Occlusion, G=Roughness, B=Metallic)
594  QImage orm(width, height, QImage::Format_RGB888);
595  if (orm.isNull()) {
596  CVLog::Error(
597  "[VtkMultiTextureRenderer::ApplyPBRMaterial] Failed to "
598  "create ORM texture");
599  } else {
600  // Preprocess each channel to grayscale
601  QImage o_gray =
602  (!material.aoTexture.empty() && !ao_img.isNull())
603  ? ao_img.scaled(width, height,
604  Qt::IgnoreAspectRatio,
605  Qt::SmoothTransformation)
606  .convertToFormat(
607  QImage::Format_Grayscale8)
608  : QImage(width, height, QImage::Format_Grayscale8);
609  if (material.aoTexture.empty() || ao_img.isNull())
610  o_gray.fill(Qt::white);
611 
612  QImage r_gray =
613  (!material.roughnessTexture.empty() &&
614  !roughness_img.isNull())
615  ? roughness_img
616  .scaled(width, height,
617  Qt::IgnoreAspectRatio,
618  Qt::SmoothTransformation)
619  .convertToFormat(
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));
624 
625  QImage m_gray =
626  (!material.metallicTexture.empty() &&
627  !metallic_img.isNull())
628  ? metallic_img
629  .scaled(width, height,
630  Qt::IgnoreAspectRatio,
631  Qt::SmoothTransformation)
632  .convertToFormat(
633  QImage::Format_Grayscale8)
634  : QImage(width, height, QImage::Format_Grayscale8);
635  if (material.metallicTexture.empty() || metallic_img.isNull())
636  m_gray.fill(Qt::black);
637 
638  // Merge into ORM texture
639  for (int y = 0; y < height; ++y) {
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);
644 
645  for (int x = 0; x < width; ++x) {
646  orm_line[x * 3 + 0] = o_line[x]; // R = Occlusion
647  orm_line[x * 3 + 1] = r_line[x]; // G = Roughness
648  orm_line[x * 3 + 2] = m_line[x]; // B = Metallic
649  }
650  }
651 
652  // Apply ORM texture
654  impl_->QImageToVtkImage(orm);
655  if (orm_data) {
658  tex->SetInputData(orm_data);
659  tex->SetInterpolate(1);
660  tex->MipmapOn();
661 
662  property->SetORMTexture(tex);
664  "[VtkMultiTextureRenderer::ApplyPBRMaterial] Applied "
665  "ORM texture (%dx%d)",
666  width, height);
667  }
668  } // end of orm.isNull() check
669  }
670 
671  // 5. Configure renderer lighting (enhanced illumination)
672  // Only set up lighting once per renderer to avoid repeated operations
673  if (renderer) {
674  vtkLightCollection* lights = renderer->GetLights();
675  int num_lights = lights ? lights->GetNumberOfItems() : 0;
676 
677  // Enable IBL
678  renderer->UseImageBasedLightingOn();
679  renderer->AutomaticLightCreationOn();
680 
681  // Significantly enhance ambient light to avoid being too dark
682  renderer->SetAmbient(1.0, 1.0, 1.0); // Maximum ambient light
683 
684  // Only configure lighting if not already set up
685  // Check if we have the expected number of lights (1 headlight + auto
686  // lights)
687  if (num_lights == 0) {
688  // Add additional directional light source
690  light->SetLightTypeToHeadlight(); // Light source that follows
691  // camera
692  light->SetIntensity(1.5); // Enhanced brightness
693  light->SetColor(1.0, 1.0, 1.0);
694  renderer->AddLight(light);
695 
697  "[VtkMultiTextureRenderer::ApplyPBRMaterial] Lighting "
698  "configured (first time): ambient=1.0, headlight "
699  "intensity=1.5");
700  } else {
701  // Enhance existing lights for brighter textured model rendering
703  "[VtkMultiTextureRenderer::ApplyPBRMaterial] Found %d "
704  "existing lights, enhancing for textured model",
705  num_lights);
706  }
707  } else {
709  "[VtkMultiTextureRenderer::ApplyPBRMaterial] No renderer "
710  "provided, lighting may be insufficient");
711  }
712 
713  // 6. Set rendering quality
714  property->BackfaceCullingOff(); // Allow seeing backface
715  property->FrontfaceCullingOff(); // Allow seeing frontface
716 
717  property->SetLighting(true);
718  property->SetShading(true);
719  property->SetAmbient(1.0f);
720  property->SetDiffuse(1.0f);
721  property->SetSpecular(1.0f);
722 #else
724  "[VtkMultiTextureRenderer::ApplyPBRMaterial] VTK < 9.0, falling "
725  "back to Phong");
726  property->SetInterpolationToPhong();
727  property->SetColor(material.baseColor[0], material.baseColor[1],
728  material.baseColor[2]);
729  // Set RGB colors for ambient, diffuse, specular
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]);
739  // Set intensity coefficients to 1.0 to preserve RGB color brightness
740  // Since we're using RGB colors, setting intensity to 1.0 preserves the
741  // original color brightness
742  property->SetAmbient(1.0f);
743  property->SetDiffuse(1.0f);
744  property->SetSpecular(1.0f);
745  property->SetSpecularPower(material.shininess);
746  property->SetOpacity(opacity); // Use clamped value
747 
748  // Only apply BaseColor texture
749  if (!material.baseColorTexture.empty()) {
750  QImage img = ccMaterial::GetTexture(material.baseColorTexture.c_str());
751  if (!img.isNull()) {
752  vtkSmartPointer<vtkImageData> image_data =
753  impl_->QImageToVtkImage(img);
754  if (image_data) {
757  tex->SetInputData(image_data);
758  tex->SetInterpolate(1);
759  tex->MipmapOn();
760  actor->SetTexture(tex);
761  }
762  }
763  }
764 #endif
765 
767  "[VtkMultiTextureRenderer::ApplyPBRRendering] PBR rendering setup "
768  "complete");
769  return true;
770 }
771 
772 bool VtkMultiTextureRenderer::ApplyTexturedRendering(
774  const PBRMaterial& material,
775  float opacity) {
776  // ========================================================================
777  // Mode 2: Single texture rendering (only baseColor texture)
778  // ========================================================================
780  "[VtkMultiTextureRenderer::ApplyTexturedRendering] Using single "
781  "texture mode");
782 
783  vtkProperty* property = actor->GetProperty();
784  if (!property) {
785  CVLog::Error(
786  "[VtkMultiTextureRenderer::ApplyTexturedRendering] Property is "
787  "null");
788  return false;
789  }
790 
791  // Set material properties
792  SetProperties(property, material, opacity);
793 
794  // Load texture
795  std::string tex_path = material.baseColorTexture;
796  if (tex_path.empty()) tex_path = material.emissiveTexture;
797 
798  if (!tex_path.empty()) {
799  // Check if file exists
802  "[VtkMultiTextureRenderer::ApplyTexturedRendering] Texture "
803  "file not found: %s",
804  tex_path.c_str());
805  return false;
806  }
807 
808  QImage img = ccMaterial::GetTexture(tex_path.c_str());
809  if (!img.isNull()) {
810  vtkSmartPointer<vtkImageData> image_data =
811  impl_->QImageToVtkImage(img);
812  if (image_data) {
815  tex->SetInputData(image_data);
816  tex->SetInterpolate(1);
817  tex->MipmapOn();
818  actor->SetTexture(tex);
820  "[VtkMultiTextureRenderer::ApplyTexturedRendering] "
821  "Applied single texture: %dx%d",
822  img.width(), img.height());
823  } else {
825  "[VtkMultiTextureRenderer::ApplyTexturedRendering] "
826  "Failed to convert QImage to vtkImageData");
827  return false;
828  }
829  } else {
831  "[VtkMultiTextureRenderer::ApplyTexturedRendering] Failed "
832  "to load texture: %s",
833  tex_path.c_str());
834  return false;
835  }
836  } else {
838  "[VtkMultiTextureRenderer::ApplyTexturedRendering] No texture "
839  "path provided");
840  return false;
841  }
842 
844  "[VtkMultiTextureRenderer::ApplyTexturedRendering] Textured "
845  "rendering setup complete");
846  return true;
847 }
848 
849 bool VtkMultiTextureRenderer::ApplyMaterialOnlyRendering(
851  const PBRMaterial& material,
852  float opacity) {
853  // ========================================================================
854  // Mode 3: Pure material rendering (no texture) - PBR mode
855  // ========================================================================
857  "[VtkMultiTextureRenderer::ApplyMaterialOnlyRendering] Using "
858  "material-only PBR mode");
859 
860  vtkProperty* property = actor->GetProperty();
861  if (!property) {
862  CVLog::Error(
863  "[VtkMultiTextureRenderer::ApplyMaterialOnlyRendering] "
864  "Property is null");
865  return false;
866  }
867 
868  // Enable PBR interpolation mode (same as ApplyPBRRendering)
869  property->SetInterpolationToPBR();
870 
871  // Set base material properties
872  property->SetColor(material.baseColor[0], material.baseColor[1],
873  material.baseColor[2]);
874  property->SetOpacity(opacity);
875 
876  // Set PBR scalar parameters (metallic, roughness, ao)
877  // These are essential for PBR rendering to show proper glossy/reflective
878  // surfaces
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));
882 
883  property->SetMetallic(metallic);
884  property->SetRoughness(roughness);
885  property->SetOcclusionStrength(ao);
886 
887 #if VTK_VERSION_NUMBER >= VTK_VERSION_CHECK(9, 2, 0)
888  // Advanced PBR parameters (VTK 9.2+)
889  property->SetAnisotropy(std::max(
890  0.0, std::min(1.0, static_cast<double>(material.anisotropy))));
891  property->SetAnisotropyRotation(std::max(
892  0.0,
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(
897  0.0,
898  std::min(1.0, static_cast<double>(material.clearcoatRoughness))));
899  // Note: VTK uses "Coat" terminology for clearcoat
900 
902  "[VtkMultiTextureRenderer::ApplyMaterialOnlyRendering] Advanced "
903  "PBR: "
904  "anisotropy=%.2f, clearcoat=%.2f, clearcoatRoughness=%.2f",
905  material.anisotropy, material.clearcoat,
906  material.clearcoatRoughness);
907 #endif
908 
909  // Set lighting properties for proper PBR rendering
910  property->SetLighting(true);
911  property->SetShading(true);
912  property->BackfaceCullingOff();
913  property->FrontfaceCullingOff();
914 
916  "[VtkMultiTextureRenderer::ApplyMaterialOnlyRendering] Material "
917  "properties: "
918  "color=(%.2f,%.2f,%.2f), metallic=%.2f, roughness=%.2f, ao=%.2f, "
919  "opacity=%.2f",
920  material.baseColor[0], material.baseColor[1], material.baseColor[2],
921  metallic, roughness, ao, opacity);
922  return true;
923 }
924 
925 } // namespace VtkUtils
int width
int height
core::Tensor result
Definition: VtkUtils.cpp:76
static bool PrintDebug(const char *format,...)
Same as Print, but works only in Debug mode.
Definition: CVLog.cpp:153
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool Print(const char *format,...)
Prints out a formatted message in console.
Definition: CVLog.cpp:113
static bool PrintVerbose(const char *format,...)
Prints out a verbose formatted message in console.
Definition: CVLog.cpp:103
static bool Error(const char *format,...)
Display an error dialog with formatted message.
Definition: CVLog.cpp:143
bool ApplyPBRMaterial(vtkSmartPointer< vtkActor > actor, const PBRMaterial &material, vtkSmartPointer< vtkPolyData > polydata, vtkRenderer *renderer=nullptr)
Apply PBR material to actor (Filament style)
static QImage GetTexture(const QString &absoluteFilename)
Returns the texture image associated to a given name.
normal_z y
normal_z x
bool FileExists(const std::string &filename)
Definition: FileSystem.cpp:524
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)
Definition: Eigen.h:85
unsigned char uchar
Definition: shpopen.c:319
vtkSmartPointer< vtkImageData > QImageToVtkImage(const QImage &qimage)
Generic PBR material structure (supports multi-texture)