arabago96 commited on
Commit
05edbfd
·
1 Parent(s): 6d4c38f

Fix gamma values: video 2.2, texture 2.1, add GLB Raw export

Browse files

- Video: Set to standard gamma 2.2 for proper brightness
- Texture: Set to gamma 2.1 (slightly darker to reduce over-brightness)
- Add GLB Raw export with minimal processing (50% simplification, no hole filling)
- Fixes: video now properly bright, textures slightly less bright than before

app.py CHANGED
@@ -227,6 +227,35 @@ def extract_glb(
227
  return glb_path, glb_path
228
 
229
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  @spaces.GPU
231
  def extract_gaussian(state: dict, req: gr.Request) -> Tuple[str, str]:
232
  """
@@ -319,6 +348,7 @@ with gr.Blocks(delete_cache=(600, 600)) as demo:
319
 
320
  with gr.Row():
321
  extract_glb_btn = gr.Button("Extract GLB", interactive=False)
 
322
  extract_gs_btn = gr.Button("Extract Gaussian", interactive=False)
323
  gr.Markdown("""
324
  *NOTE: Gaussian file can be very large (~50MB), it will take a while to display and download.*
@@ -391,13 +421,13 @@ with gr.Blocks(delete_cache=(600, 600)) as demo:
391
  inputs=[image_prompt, multiimage_prompt, is_multiimage, seed, ss_guidance_strength, ss_sampling_steps, slat_guidance_strength, slat_sampling_steps, multiimage_algo],
392
  outputs=[output_buf, video_output],
393
  ).then(
394
- lambda: tuple([gr.update(interactive=True), gr.update(interactive=True)]),
395
- outputs=[extract_glb_btn, extract_gs_btn],
396
  )
397
 
398
  video_output.clear(
399
- lambda: tuple([gr.update(interactive=False), gr.update(interactive=False)]),
400
- outputs=[extract_glb_btn, extract_gs_btn],
401
  )
402
 
403
  extract_glb_btn.click(
@@ -409,6 +439,15 @@ with gr.Blocks(delete_cache=(600, 600)) as demo:
409
  outputs=[download_glb],
410
  )
411
 
 
 
 
 
 
 
 
 
 
412
  extract_gs_btn.click(
413
  extract_gaussian,
414
  inputs=[output_buf],
 
227
  return glb_path, glb_path
228
 
229
 
230
+ @spaces.GPU(duration=90)
231
+ def extract_glb_raw(
232
+ state: dict,
233
+ texture_size: int,
234
+ req: gr.Request,
235
+ ) -> Tuple[str, str]:
236
+ """
237
+ Extract a RAW GLB file from the 3D model with minimal processing.
238
+ Skips hole filling and uses only light simplification (50% instead of 90%).
239
+ Preserves more original geometry from the neural network.
240
+
241
+ Args:
242
+ state (dict): The state of the generated 3D model.
243
+ texture_size (int): The texture resolution.
244
+ Returns:
245
+ str: The path to the extracted raw GLB file.
246
+ """
247
+ if state is None:
248
+ raise gr.Error("Please generate a 3D model first before extracting GLB.")
249
+
250
+ user_dir = os.path.join(TMP_DIR, str(req.session_hash))
251
+ gs, mesh = unpack_state(state)
252
+ glb = postprocessing_utils.to_glb_raw(gs, mesh, texture_size=texture_size, verbose=True)
253
+ glb_path = os.path.join(user_dir, 'sample_raw.glb')
254
+ glb.export(glb_path)
255
+ torch.cuda.empty_cache()
256
+ return glb_path, glb_path
257
+
258
+
259
  @spaces.GPU
260
  def extract_gaussian(state: dict, req: gr.Request) -> Tuple[str, str]:
261
  """
 
348
 
349
  with gr.Row():
350
  extract_glb_btn = gr.Button("Extract GLB", interactive=False)
351
+ extract_glb_raw_btn = gr.Button("Extract GLB Raw (Minimal Processing)", interactive=False, variant="secondary")
352
  extract_gs_btn = gr.Button("Extract Gaussian", interactive=False)
353
  gr.Markdown("""
354
  *NOTE: Gaussian file can be very large (~50MB), it will take a while to display and download.*
 
421
  inputs=[image_prompt, multiimage_prompt, is_multiimage, seed, ss_guidance_strength, ss_sampling_steps, slat_guidance_strength, slat_sampling_steps, multiimage_algo],
422
  outputs=[output_buf, video_output],
423
  ).then(
424
+ lambda: tuple([gr.update(interactive=True), gr.update(interactive=True), gr.update(interactive=True)]),
425
+ outputs=[extract_glb_btn, extract_glb_raw_btn, extract_gs_btn],
426
  )
427
 
428
  video_output.clear(
429
+ lambda: tuple([gr.update(interactive=False), gr.update(interactive=False), gr.update(interactive=False)]),
430
+ outputs=[extract_glb_btn, extract_glb_raw_btn, extract_gs_btn],
431
  )
432
 
433
  extract_glb_btn.click(
 
439
  outputs=[download_glb],
440
  )
441
 
442
+ extract_glb_raw_btn.click(
443
+ extract_glb_raw,
444
+ inputs=[output_buf, texture_size],
445
+ outputs=[model_output, download_glb],
446
+ ).then(
447
+ lambda: gr.update(interactive=True),
448
+ outputs=[download_glb],
449
+ )
450
+
451
  extract_gs_btn.click(
452
  extract_gaussian,
453
  inputs=[output_buf],
trellis/utils/postprocessing_utils.py CHANGED
@@ -340,7 +340,7 @@ def bake_texture(
340
  # 🔧 Apply gamma correction (Linear → sRGB)
341
  # Fixes "dark model" issue by converting from linear color space to sRGB
342
  texture_linear = texture.reshape(texture_size, texture_size, 3).cpu().numpy()
343
- texture_srgb = np.power(np.clip(texture_linear, 0, 1), 1.0/2.3) # Gamma 2.3 (slightly less bright than 2.2)
344
  texture = np.clip(texture_srgb * 255, 0, 255).astype(np.uint8)
345
 
346
  # inpaint
@@ -406,6 +406,76 @@ def bake_texture(
406
  return texture
407
 
408
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
  def to_glb(
410
  app_rep: Union[Strivec, Gaussian],
411
  mesh: MeshExtractResult,
 
340
  # 🔧 Apply gamma correction (Linear → sRGB)
341
  # Fixes "dark model" issue by converting from linear color space to sRGB
342
  texture_linear = texture.reshape(texture_size, texture_size, 3).cpu().numpy()
343
+ texture_srgb = np.power(np.clip(texture_linear, 0, 1), 1.0/2.1) # Gamma 2.1 (slightly darker than standard 2.2)
344
  texture = np.clip(texture_srgb * 255, 0, 255).astype(np.uint8)
345
 
346
  # inpaint
 
406
  return texture
407
 
408
 
409
+ def to_glb_raw(
410
+ app_rep: Union[Strivec, Gaussian],
411
+ mesh: MeshExtractResult,
412
+ texture_size: int = 1024,
413
+ verbose: bool = True,
414
+ ) -> trimesh.Trimesh:
415
+ """
416
+ Convert a generated asset to a GLB file with MINIMAL processing.
417
+ Skips hole filling and uses light simplification only.
418
+ This preserves more original geometry while keeping GPU usage manageable.
419
+
420
+ Args:
421
+ app_rep (Union[Strivec, Gaussian]): Appearance representation.
422
+ mesh (MeshExtractResult): Extracted mesh.
423
+ texture_size (int): Size of the texture.
424
+ verbose (bool): Whether to print progress.
425
+ """
426
+ vertices = mesh.vertices.cpu().numpy()
427
+ faces = mesh.faces.cpu().numpy()
428
+
429
+ if verbose:
430
+ print(f'🔧 RAW GLB: Starting with {vertices.shape[0]} vertices, {faces.shape[0]} faces')
431
+
432
+ # LIGHT simplification only (50% reduction instead of 90%)
433
+ # This keeps GPU usage manageable while preserving more detail
434
+ import pyvista as pv
435
+ mesh_pv = pv.PolyData(vertices, np.concatenate([np.full((faces.shape[0], 1), 3), faces], axis=1))
436
+ mesh_pv = mesh_pv.decimate(0.5, progress_bar=verbose)
437
+ vertices, faces = mesh_pv.points, mesh_pv.faces.reshape(-1, 4)[:, 1:]
438
+
439
+ if verbose:
440
+ print(f'🔧 RAW GLB: After light simplification: {vertices.shape[0]} vertices, {faces.shape[0]} faces')
441
+
442
+ # parametrize mesh (required for texture)
443
+ vertices, faces, uvs = parametrize_mesh(vertices, faces)
444
+
445
+ if verbose:
446
+ print(f'🔧 RAW GLB: After UV unwrap: {vertices.shape[0]} vertices, {faces.shape[0]} faces')
447
+
448
+ # bake texture (same as normal)
449
+ observations, extrinsics, intrinsics = render_multiview(app_rep, resolution=1024, nviews=100)
450
+ masks = [np.any(observation > 0, axis=-1) for observation in observations]
451
+ extrinsics = [extrinsics[i].cpu().numpy() for i in range(len(extrinsics))]
452
+ intrinsics = [intrinsics[i].cpu().numpy() for i in range(len(intrinsics))]
453
+ texture = bake_texture(
454
+ vertices, faces, uvs,
455
+ observations, masks, extrinsics, intrinsics,
456
+ texture_size=texture_size, mode='opt',
457
+ lambda_tv=0.01,
458
+ verbose=verbose
459
+ )
460
+ texture = Image.fromarray(texture)
461
+
462
+ # rotate mesh (from z-up to y-up)
463
+ vertices = vertices @ np.array([[1, 0, 0], [0, 0, -1], [0, 1, 0]])
464
+
465
+ # Create material
466
+ material = trimesh.visual.material.PBRMaterial(
467
+ baseColorTexture=texture
468
+ )
469
+
470
+ mesh = trimesh.Trimesh(vertices, faces, visual=trimesh.visual.TextureVisuals(uv=uvs, material=material))
471
+ mesh.vertex_normals
472
+
473
+ if verbose:
474
+ print(f"✅ RAW GLB: Generated {len(mesh.vertex_normals)} vertex normals")
475
+
476
+ return mesh
477
+
478
+
479
  def to_glb(
480
  app_rep: Union[Strivec, Gaussian],
481
  mesh: MeshExtractResult,
trellis/utils/render_utils.py CHANGED
@@ -75,7 +75,7 @@ def render_frames(sample, extrinsics, intrinsics, options={}, colors_overwrite=N
75
  if 'depth' not in rets: rets['depth'] = []
76
  # Apply gamma correction to video frames (Linear → sRGB)
77
  color_linear = res['color'].detach().cpu().numpy().transpose(1, 2, 0)
78
- color_srgb = np.power(np.clip(color_linear, 0, 1), 1.0/2.3) # Gamma 2.3 (slightly less bright)
79
  rets['color'].append(np.clip(color_srgb * 255, 0, 255).astype(np.uint8))
80
  if 'percent_depth' in res:
81
  rets['depth'].append(res['percent_depth'].detach().cpu().numpy())
 
75
  if 'depth' not in rets: rets['depth'] = []
76
  # Apply gamma correction to video frames (Linear → sRGB)
77
  color_linear = res['color'].detach().cpu().numpy().transpose(1, 2, 0)
78
+ color_srgb = np.power(np.clip(color_linear, 0, 1), 1.0/2.2) # Standard gamma 2.2
79
  rets['color'].append(np.clip(color_srgb * 255, 0, 255).astype(np.uint8))
80
  if 'percent_depth' in res:
81
  rets['depth'].append(res['percent_depth'].detach().cpu().numpy())