ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
MaterialConverter.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 <CVLog.h>
11 #include <CVTools.h>
12 #include <Utils/sm2cc.h>
13 #include <ecvMaterial.h>
14 #include <ecvMaterialSet.h>
15 #include <pcl/io/obj_io.h> // For pcl::TexMaterial
16 
17 namespace PclUtils {
18 namespace renders {
19 
23 
24  if (!ccMat) {
26  "[MaterialConverter::FromCCMaterial] ccMaterial is null");
27  return pbr;
28  }
29 
30  pbr.name = ccMat->getName().toStdString();
31 
32  // Extract PBR texture paths
33  using TexType = ccMaterial::TextureMapType;
34 
35  // Check for multiple map_Kd textures
36  auto diffuse_textures = ccMat->getTextureFilenames(TexType::DIFFUSE);
37  if (diffuse_textures.size() > 1) {
38  pbr.hasMultipleMapKd = true;
40  "[MaterialConverter::FromCCMaterial] Detected %zu map_Kd "
41  "textures for material '%s', will use traditional "
42  "multi-texture "
43  "rendering",
44  diffuse_textures.size(), pbr.name.c_str());
45  }
46 
47  QString diffuse_path = ccMat->getTextureFilename(TexType::DIFFUSE);
48  if (!diffuse_path.isEmpty()) {
49  pbr.baseColorTexture = diffuse_path.toStdString();
50  }
51 
52  QString ao_path = ccMat->getTextureFilename(TexType::AMBIENT);
53  if (!ao_path.isEmpty()) {
54  pbr.aoTexture = ao_path.toStdString();
55  }
56 
57  QString normal_path = ccMat->getTextureFilename(TexType::NORMAL);
58  if (!normal_path.isEmpty()) {
59  pbr.normalTexture = normal_path.toStdString();
60  }
61 
62  QString metallic_path = ccMat->getTextureFilename(TexType::METALLIC);
63  if (!metallic_path.isEmpty()) {
64  pbr.metallicTexture = metallic_path.toStdString();
65  }
66 
67  QString roughness_path = ccMat->getTextureFilename(TexType::ROUGHNESS);
68  if (!roughness_path.isEmpty()) {
69  pbr.roughnessTexture = roughness_path.toStdString();
70  }
71 
72  QString emissive_path = ccMat->getTextureFilename(TexType::EMISSIVE);
73  if (!emissive_path.isEmpty()) {
74  pbr.emissiveTexture = emissive_path.toStdString();
75  }
76 
77  // Extract advanced PBR texture paths (VTK 9.2+)
78  QString sheen_path = ccMat->getTextureFilename(TexType::SHEEN);
79  if (!sheen_path.isEmpty()) {
80  pbr.sheenTexture = sheen_path.toStdString();
81  }
82 
83  QString clearcoat_path = ccMat->getTextureFilename(TexType::CLEARCOAT);
84  if (!clearcoat_path.isEmpty()) {
85  pbr.clearcoatTexture = clearcoat_path.toStdString();
86  }
87 
88  QString clearcoat_roughness_path =
89  ccMat->getTextureFilename(TexType::CLEARCOAT_ROUGHNESS);
90  if (!clearcoat_roughness_path.isEmpty()) {
91  pbr.clearcoatRoughnessTexture = clearcoat_roughness_path.toStdString();
92  }
93 
94  QString anisotropy_path = ccMat->getTextureFilename(TexType::ANISOTROPY);
95  if (!anisotropy_path.isEmpty()) {
96  pbr.anisotropyTexture = anisotropy_path.toStdString();
97  }
98 
99  // Extract material properties
100  const ecvColor::Rgbaf& ambient = ccMat->getAmbient();
101  const ecvColor::Rgbaf& diffuse = ccMat->getDiffuseFront();
102  const ecvColor::Rgbaf& spec = ccMat->getSpecular();
103  const ecvColor::Rgbaf& emission = ccMat->getEmission();
104 
105  // Clamp color values to [0,1] range
106  pbr.baseColor[0] = std::max(0.0f, std::min(1.0f, diffuse.r));
107  pbr.baseColor[1] = std::max(0.0f, std::min(1.0f, diffuse.g));
108  pbr.baseColor[2] = std::max(0.0f, std::min(1.0f, diffuse.b));
109 
110  pbr.ambientColor[0] = std::max(0.0f, std::min(1.0f, ambient.r));
111  pbr.ambientColor[1] = std::max(0.0f, std::min(1.0f, ambient.g));
112  pbr.ambientColor[2] = std::max(0.0f, std::min(1.0f, ambient.b));
113 
114  pbr.diffuseColor[0] = std::max(0.0f, std::min(1.0f, diffuse.r));
115  pbr.diffuseColor[1] = std::max(0.0f, std::min(1.0f, diffuse.g));
116  pbr.diffuseColor[2] = std::max(0.0f, std::min(1.0f, diffuse.b));
117 
118  pbr.specularColor[0] = std::max(0.0f, std::min(1.0f, spec.r));
119  pbr.specularColor[1] = std::max(0.0f, std::min(1.0f, spec.g));
120  pbr.specularColor[2] = std::max(0.0f, std::min(1.0f, spec.b));
121 
122  // PBR scalar parameters with proper clamping
123  pbr.metallic = std::max(0.0f, std::min(1.0f, ccMat->getMetallic()));
124  pbr.roughness = std::max(0.0f, std::min(1.0f, ccMat->getRoughness()));
125  pbr.ao = std::max(0.0f, std::min(2.0f, ccMat->getAmbientOcclusion()));
126 
127  // Shininess: prefer explicit Ns value, but if PBR parameters (roughness,
128  // sheen) are present, calculate shininess from them to ensure consistency
129  // Roughness and shininess are inversely related:
130  // - roughness = 0.0 (smooth) -> high shininess
131  // - roughness = 1.0 (rough) -> low shininess
132  // Formula: shininess = base_shininess * (1 - roughness) * (1 +
133  // sheen_factor)
134  float baseShininess = ccMat->getShininessFront();
135  bool hasExplicitShininess =
136  (baseShininess !=
137  50.0f); // 50.0 is default, check if explicitly set
138  bool hasPBRParams = (pbr.roughness != 0.5f || ccMat->getSheen() > 0.0f ||
139  ccMat->getClearcoat() > 0.0f);
140 
141  if (hasExplicitShininess) {
142  // Use explicit Ns value from MTL file
143  pbr.shininess = std::max(0.0f, std::min(128.0f, baseShininess));
144  } else if (hasPBRParams) {
145  // Calculate shininess from PBR parameters
146  // Roughness: 0.0 = smooth (high shininess), 1.0 = rough (low shininess)
147  float roughnessFactor = 1.0f - pbr.roughness; // Invert roughness
148 
149  // Sheen: 0.0 = no sheen, 1.0 = high sheen (increases shininess)
150  float sheenFactor = 1.0f + ccMat->getSheen() * 0.5f; // Moderate boost
151 
152  // Clearcoat: 0.0 = no clearcoat, 1.0 = high clearcoat (increases
153  // shininess)
154  float clearcoatFactor =
155  1.0f + ccMat->getClearcoat() * 0.3f; // Moderate boost
156 
157  // Calculate effective shininess
158  // Base shininess of 50.0 is reasonable default, adjust based on PBR
159  // params
160  float effectiveShininess =
161  50.0f * roughnessFactor * sheenFactor * clearcoatFactor;
162  pbr.shininess = std::max(0.0f, std::min(128.0f, effectiveShininess));
163 
165  "[MaterialConverter::FromCCMaterial] Calculated shininess from "
166  "PBR params: "
167  "base=50.00, roughness=%.2f (factor=%.2f), sheen=%.2f "
168  "(factor=%.2f), "
169  "clearcoat=%.2f (factor=%.2f) -> shininess=%.2f",
170  pbr.roughness, roughnessFactor, ccMat->getSheen(), sheenFactor,
171  ccMat->getClearcoat(), clearcoatFactor, pbr.shininess);
172  } else {
173  // Use default shininess (50.0)
174  pbr.shininess = std::max(0.0f, std::min(128.0f, baseShininess));
175  }
176 
177  // Opacity: prefer from opacity texture, then ambient alpha, then diffuse
178  // alpha
179  QString opacity_path = ccMat->getTextureFilename(TexType::OPACITY);
180  if (!opacity_path.isEmpty()) {
181  // If opacity texture exists, use default opacity value
182  // The texture will modulate it
183  pbr.opacity = std::max(0.0f, std::min(1.0f, ambient.a));
184  } else {
185  // Use alpha channel from ambient or diffuse
186  pbr.opacity = std::max(0.0f, std::min(1.0f, ambient.a));
187  if (pbr.opacity <= 0.0f) {
188  pbr.opacity = std::max(0.0f, std::min(1.0f, diffuse.a));
189  }
190  }
191 
192  // Advanced PBR parameters
193  pbr.sheen = std::max(0.0f, std::min(1.0f, ccMat->getSheen()));
194  pbr.clearcoat = std::max(0.0f, std::min(1.0f, ccMat->getClearcoat()));
195  pbr.clearcoatRoughness =
196  std::max(0.0f, std::min(1.0f, ccMat->getClearcoatRoughness()));
197  pbr.anisotropy = std::max(0.0f, std::min(1.0f, ccMat->getAnisotropy()));
198 
199  // Emissive color
200  pbr.emissive[0] = std::max(0.0f, std::min(1.0f, emission.r));
201  pbr.emissive[1] = std::max(0.0f, std::min(1.0f, emission.g));
202  pbr.emissive[2] = std::max(0.0f, std::min(1.0f, emission.b));
203 
205  "[MaterialConverter::FromCCMaterial] Converted material '%s': "
206  "ambient=(%.2f,%.2f,%.2f), diffuse=(%.2f,%.2f,%.2f), "
207  "specular=(%.2f,%.2f,%.2f), shininess=%.2f, opacity=%.2f",
208  pbr.name.c_str(), pbr.ambientColor[0], pbr.ambientColor[1],
209  pbr.ambientColor[2], pbr.diffuseColor[0], pbr.diffuseColor[1],
210  pbr.diffuseColor[2], pbr.specularColor[0], pbr.specularColor[1],
211  pbr.specularColor[2], pbr.shininess, pbr.opacity);
212 
213  return pbr;
214 }
215 
217 MaterialConverter::FromPCLMaterial(const pcl::TexMaterial& pclMat) {
218  CVLog::Print(
219  "[MaterialConverter::FromPCLMaterial] ENTRY: tex_name=%s, "
220  "tex_file=%s",
221  pclMat.tex_name.c_str(), pclMat.tex_file.c_str());
222 
224 
225  pbr.name = pclMat.tex_name;
226 
227  // pcl::TexMaterial only has one texture path field (tex_file), so multiple
228  // textures are encoded into tex_name. Parse the encoding if present.
229  // Format: materialName_PBR_MULTITEX|type0:path0|type1:path1|...
230  // NOTE: This encoding is only used for pcl::TexMaterial compatibility.
231  // For direct ccMaterial usage, use addTextureMeshFromCCMesh() which doesn't
232  // need encoding.
233  std::string tex_name = pclMat.tex_name;
234  size_t pbr_pos = tex_name.find("_PBR_MULTITEX");
235 
236  if (pbr_pos != std::string::npos) {
237  // Parse encoded texture paths
238  size_t start = pbr_pos + 13; // Length of "_PBR_MULTITEX"
239  int diffuse_count = 0;
240  while (start < tex_name.length() && tex_name[start] == '|') {
241  size_t colon = tex_name.find(':', start + 1);
242  size_t next_pipe = tex_name.find('|', colon);
243  if (colon == std::string::npos) break;
244 
245  std::string type_str =
246  tex_name.substr(start + 1, colon - start - 1);
247  std::string path =
248  (next_pipe != std::string::npos)
249  ? tex_name.substr(colon + 1, next_pipe - colon - 1)
250  : tex_name.substr(colon + 1);
251 
252  int type = std::stoi(type_str);
253 
254  switch (type) {
255  case 0: // DIFFUSE
256  pbr.baseColorTexture = path;
257  diffuse_count++;
258  break;
259  case 1: // AMBIENT (AO)
260  pbr.aoTexture = path;
261  break;
262  case 3: // NORMAL
263  pbr.normalTexture = path;
264  break;
265  case 4: // METALLIC
266  pbr.metallicTexture = path;
267  break;
268  case 5: // ROUGHNESS
269  pbr.roughnessTexture = path;
270  break;
271  default:
272  break;
273  }
274 
275  start = (next_pipe != std::string::npos) ? next_pipe
276  : tex_name.length();
277  }
278 
279  if (diffuse_count > 1) {
280  pbr.hasMultipleMapKd = true;
282  "[MaterialConverter::FromPCLMaterial] Detected %d map_Kd "
283  "textures for material '%s', will use traditional "
284  "multi-texture rendering",
285  diffuse_count, pbr.name.c_str());
286  }
287 
288  // Extract real material name (remove encoded part)
289  pbr.name = tex_name.substr(0, pbr_pos);
290  } else {
291  // Single texture mode - use tex_file directly
292  if (!pclMat.tex_file.empty()) {
293  pbr.baseColorTexture = pclMat.tex_file;
294  }
295  }
296 
297  // Extract material properties
298  pbr.baseColor[0] = pclMat.tex_Kd.r;
299  pbr.baseColor[1] = pclMat.tex_Kd.g;
300  pbr.baseColor[2] = pclMat.tex_Kd.b;
301 
302  pbr.ambientColor[0] = pclMat.tex_Ka.r;
303  pbr.ambientColor[1] = pclMat.tex_Ka.g;
304  pbr.ambientColor[2] = pclMat.tex_Ka.b;
305 
306  pbr.diffuseColor[0] = pclMat.tex_Kd.r;
307  pbr.diffuseColor[1] = pclMat.tex_Kd.g;
308  pbr.diffuseColor[2] = pclMat.tex_Kd.b;
309 
310  pbr.specularColor[0] = pclMat.tex_Ks.r;
311  pbr.specularColor[1] = pclMat.tex_Ks.g;
312  pbr.specularColor[2] = pclMat.tex_Ks.b;
313 
314  pbr.shininess = std::max(0.0f, std::min(128.0f, pclMat.tex_Ns));
315  pbr.opacity = std::max(0.0f, std::min(1.0f, pclMat.tex_d));
316 
317  // PBR scalar parameters (defaults for PCL materials)
318  pbr.metallic = 0.0f;
319  pbr.roughness = 0.5f;
320  pbr.ao = 1.0f;
321 
322  // Emissive color (default to black)
323  pbr.emissive[0] = 0.0f;
324  pbr.emissive[1] = 0.0f;
325  pbr.emissive[2] = 0.0f;
326 
328  "[MaterialConverter::FromPCLMaterial] Converted material '%s': "
329  "ambient=(%.2f,%.2f,%.2f), diffuse=(%.2f,%.2f,%.2f), "
330  "specular=(%.2f,%.2f,%.2f), shininess=%.2f, opacity=%.2f",
331  pbr.name.c_str(), pbr.ambientColor[0], pbr.ambientColor[1],
332  pbr.ambientColor[2], pbr.diffuseColor[0], pbr.diffuseColor[1],
333  pbr.diffuseColor[2], pbr.specularColor[0], pbr.specularColor[1],
334  pbr.specularColor[2], pbr.shininess, pbr.opacity);
335 
336  return pbr;
337 }
338 
341  if (!materials || materials->empty()) {
342  CVLog::Error(
343  "[MaterialConverter::FromMaterialSet] No materials provided");
345  return pbr;
346  }
347 
348  auto firstMaterial = materials->at(0);
349  if (!firstMaterial) {
350  CVLog::Error(
351  "[MaterialConverter::FromMaterialSet] First material is null!");
353  return pbr;
354  }
355 
356  // Convert first material (PBRRenderer only uses
357  // first)
359  FromCCMaterial(firstMaterial.data());
360 
361  // Check all materials for PBR textures to ensure accurate detection
362  // This is important for TextureRenderManager::AnalyzeMaterials which uses
363  // hasPBRTextures() to determine rendering mode
364  using TexType = ccMaterial::TextureMapType;
365  bool found_pbr_in_other_materials = false;
366 
367  // Check all materials (not just first) for PBR textures
368  for (size_t i = 0; i < materials->size(); ++i) {
369  auto mat = materials->at(i);
370  if (!mat || mat == firstMaterial) continue;
371 
372  // Check if this material has any PBR texture types
373  if (mat->hasTextureMap(TexType::NORMAL) ||
374  mat->hasTextureMap(TexType::METALLIC) ||
375  mat->hasTextureMap(TexType::ROUGHNESS) ||
376  mat->hasTextureMap(TexType::AMBIENT)) {
377  found_pbr_in_other_materials = true;
378  break;
379  }
380 
381  // Also check for base color texture (DIFFUSE) - it's also a PBR texture
382  auto diffuse_textures = mat->getTextureFilenames(TexType::DIFFUSE);
383  if (!diffuse_textures.empty()) {
384  found_pbr_in_other_materials = true;
385  break;
386  }
387  }
388 
389  // If we found PBR textures in other materials but first material's
390  // conversion didn't detect them, ensure first material's baseColorTexture
391  // is set (if it has a diffuse texture) so hasPBRTextures() returns true
392  if (found_pbr_in_other_materials && !pbr.hasPBRTextures()) {
393  auto firstDiffuse =
394  firstMaterial->getTextureFilenames(TexType::DIFFUSE);
395  if (!firstDiffuse.empty() && pbr.baseColorTexture.empty()) {
396  pbr.baseColorTexture = firstDiffuse[0].toStdString();
398  "[MaterialConverter::FromMaterialSet] Found PBR textures "
399  "in "
400  "other materials, ensuring first material's "
401  "baseColorTexture "
402  "is set for accurate detection");
403  }
404  }
405 
406  return pbr;
407 }
408 
410  if (!material) return false;
411 
412  // Check if material name contains _PBR_MULTITEX encoding
413  // This indicates the material came from pcl::TexMaterial (pcl::TextureMesh)
414  // and uses encoding due to pcl::TexMaterial structure limitations
415  std::string mat_name = CVTools::FromQString(material->getName());
416  if (mat_name.find("_PBR_MULTITEX") != std::string::npos) {
417  return true;
418  }
419 
420  // Also check if material has actual PBR texture types (for direct
421  // ccMaterial usage) PBR textures: Normal, Metallic, Roughness, AO, etc.
422  using TexType = ccMaterial::TextureMapType;
423 
424  if (material->hasTextureMap(TexType::NORMAL) ||
425  material->hasTextureMap(TexType::METALLIC) ||
426  material->hasTextureMap(TexType::ROUGHNESS) ||
427  material->hasTextureMap(TexType::AMBIENT)) {
428  return true;
429  }
430 
431  return false;
432 }
433 
435  if (!materials || materials->empty()) return false;
436 
437  int count = 0;
438  using TexType = ccMaterial::TextureMapType;
439  for (size_t i = 0; i < materials->size(); ++i) {
440  auto mat = materials->at(i);
441  if (mat) {
442  auto diffuse_textures = mat->getTextureFilenames(TexType::DIFFUSE);
443  count += diffuse_textures.size();
444  }
445  }
446 
447  return count > 1;
448 }
449 
451  if (!materials || materials->empty()) return 0;
452 
453  int count = 0;
454  using TexType = ccMaterial::TextureMapType;
455  for (size_t i = 0; i < materials->size(); ++i) {
456  auto mat = materials->at(i);
457  if (mat) {
458  auto diffuse_textures = mat->getTextureFilenames(TexType::DIFFUSE);
459  count += static_cast<int>(diffuse_textures.size());
460  }
461  }
462 
464  "[MaterialConverter::CountMapKd] Counted %d map_Kd textures",
465  count);
466 
467  return count;
468 }
469 
470 } // namespace renders
471 } // namespace PclUtils
int count
char type
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 Error(const char *format,...)
Display an error dialog with formatted message.
Definition: CVLog.cpp:143
static std::string FromQString(const QString &qs)
Definition: CVTools.cpp:100
static VtkUtils::VtkMultiTextureRenderer::PBRMaterial FromCCMaterial(const ccMaterial *ccMat)
Convert ccMaterial to PBRMaterial.
static bool HasPBREncoding(const ccMaterial *material)
Detect if material has PBR encoding.
static int CountMapKd(const ccMaterialSet *materials)
Count map_Kd textures in material set.
static VtkUtils::VtkMultiTextureRenderer::PBRMaterial FromPCLMaterial(const ::pcl::TexMaterial &pclMat)
Convert pcl::TexMaterial to PBRMaterial (deprecated)
static bool HasMultipleMapKd(const ccMaterialSet *materials)
Detect if material set has multiple map_Kd textures.
static VtkUtils::VtkMultiTextureRenderer::PBRMaterial FromMaterialSet(const ccMaterialSet *materials)
Convert first material from ccMaterialSet to PBRMaterial.
Mesh (triangle) material.
Mesh (triangle) material.
Definition: ecvMaterial.h:28
float getClearcoatRoughness() const
Returns clearcoat roughness.
Definition: ecvMaterial.h:130
TextureMapType
Texture map types for PBR materials.
Definition: ecvMaterial.h:176
float getRoughness() const
Returns roughness factor.
Definition: ecvMaterial.h:115
float getSheen() const
Returns sheen factor.
Definition: ecvMaterial.h:120
float getAmbientOcclusion() const
Returns ambient occlusion factor.
Definition: ecvMaterial.h:140
float getMetallic() const
Returns metallic factor.
Definition: ecvMaterial.h:110
float getShininessFront() const
Returns front shininess.
Definition: ecvMaterial.h:100
const ecvColor::Rgbaf & getEmission() const
Returns emission color.
Definition: ecvMaterial.h:91
const ecvColor::Rgbaf & getSpecular() const
Returns specular color.
Definition: ecvMaterial.h:84
std::vector< QString > getTextureFilenames(TextureMapType type) const
Get all texture filenames for a specific map type.
const ecvColor::Rgbaf & getDiffuseFront() const
Returns front diffuse color.
Definition: ecvMaterial.h:61
bool hasTextureMap(TextureMapType type) const
Check if a specific texture map type exists.
const QString & getTextureFilename() const
Returns the texture filename (if any)
Definition: ecvMaterial.h:44
float getClearcoat() const
Returns clearcoat factor.
Definition: ecvMaterial.h:125
const ecvColor::Rgbaf & getAmbient() const
Returns ambient color.
Definition: ecvMaterial.h:72
float getAnisotropy() const
Returns anisotropy factor.
Definition: ecvMaterial.h:135
const QString & getName() const
Returns the material name.
Definition: ecvMaterial.h:42
RGBA color structure.
static const std::string path
Definition: PointCloud.cpp:59
constexpr ColorCompType OPACITY
Definition: ecvColorTypes.h:35
Generic PBR material structure (supports multi-texture)