Add synthetic OpenPBR MaterialX test scenes with texturing

Created comprehensive test suite for OpenPBR materials demonstrating various
material properties and texture mapping workflows. All scenes use proper USD
shader node IDs (OpenPBRSurface, UsdUVTexture, UsdPrimvarReader_float2) rather
than MaterialX node definition IDs.

New test files:
- openpbr-brick-sphere.usda: Diffuse material with brick texture
- openpbr-metallic-cube.usda: Metallic material with checkerboard
- openpbr-emissive-plane.usda: Emissive with cat texture (Mesh - MaterialX exportable)
- openpbr-glass-sphere.usda: Glass/transmission material
- openpbr-subsurface-sphere.usda: Subsurface scattering with texture
- openpbr-coated-cube.usda: Clear coat over brick texture
- openpbr-multi-object.usda: Multi-material scene (4 objects, 4 materials)

Texture asset:
- textures/brick.bmp: 64x64 red brick pattern with gray mortar
- create_brick_texture.py: Python script to generate brick texture

Documentation:
- OPENPBR_TESTS_README.md: Comprehensive guide covering material structure,
  shader node IDs, Tydra conversion limitations, and testing procedures

Material features demonstrated:
- Base layer (color, metalness, roughness)
- Specular (weight, roughness, IOR)
- Transmission (weight, color, thin_walled)
- Emission (color, luminance)
- Subsurface scattering (color, weight, radius, radius_scale)
- Coat (weight, roughness, IOR, color)
- Texture mapping with UV coordinates
- Multiple texture formats (BMP, PNG, JPG)

Note: Materials bound to parametric primitives (Sphere, Cube) are not
currently converted by Tydra RenderScene. Use explicit Mesh primitives
for MaterialX export compatibility.

Tested: All files parse correctly, emissive-plane exports to MaterialX XML/JSON

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Syoyo Fujita
2025-11-05 01:50:24 +09:00
parent f5cf93464c
commit 567ee25f1d
10 changed files with 727 additions and 0 deletions

81
create_brick_texture.py Normal file
View File

@@ -0,0 +1,81 @@
#!/usr/bin/env python3
"""Generate a simple 64x64 brick texture BMP"""
import struct
def create_brick_bmp(filename, width=64, height=64):
# BMP header
file_size = 54 + (width * height * 3)
pixel_data_offset = 54
dib_header_size = 40
# BMP file header (14 bytes)
bmp_header = struct.pack('<2sIHHI',
b'BM', # Signature
file_size, # File size
0, # Reserved
0, # Reserved
pixel_data_offset # Pixel data offset
)
# DIB header (BITMAPINFOHEADER - 40 bytes)
dib_header = struct.pack('<IiiHHIIiiII',
dib_header_size, # Header size
width, # Width
height, # Height
1, # Planes
24, # Bits per pixel
0, # Compression (none)
0, # Image size (can be 0 for uncompressed)
2835, # X pixels per meter
2835, # Y pixels per meter
0, # Colors in palette
0 # Important colors
)
# Create brick pattern
pixel_data = bytearray()
# Brick colors (BGR format for BMP)
brick_red = (40, 60, 180) # Red brick
mortar_gray = (200, 200, 200) # Gray mortar
brick_height = 8
brick_width = 16
mortar_width = 2
for y in range(height):
row_data = bytearray()
# Determine if this is a mortar row
is_mortar_row = (y % (brick_height + mortar_width)) >= brick_height
# Offset every other row of bricks
offset = 0
if ((y // (brick_height + mortar_width)) % 2) == 1:
offset = brick_width // 2
for x in range(width):
x_offset = (x + offset) % width
# Determine if this is a mortar column
is_mortar_col = (x_offset % (brick_width + mortar_width)) >= brick_width
if is_mortar_row or is_mortar_col:
# Mortar
row_data.extend(mortar_gray)
else:
# Brick
row_data.extend(brick_red)
# BMP rows are stored bottom-to-top
pixel_data = row_data + pixel_data
# Write BMP file
with open(filename, 'wb') as f:
f.write(bmp_header)
f.write(dib_header)
f.write(pixel_data)
if __name__ == '__main__':
create_brick_bmp('models/textures/brick.bmp')
print("Created brick.bmp (64x64)")

View File

@@ -0,0 +1,152 @@
# OpenPBR MaterialX Test Files
This directory contains synthetic USDA test files demonstrating OpenPBR materials with textures.
## Test Files
### Basic Material Tests
1. **openpbr-brick-sphere.usda** - Simple diffuse material with brick texture
- OpenPBRSurface with base_color from texture
- Low metalness (0.0), medium roughness (0.7)
- Uses: textures/brick.bmp
2. **openpbr-metallic-cube.usda** - Metallic material with checkerboard texture
- High metalness (0.8), low roughness (0.2)
- Specular IOR: 1.5
- Uses: textures/checkerboard.png
3. **openpbr-emissive-plane.usda** - Emissive material with cat texture
- Emission from texture (emission_luminance: 10.0)
- Low base weight (0.5)
- Uses: textures/texture-cat.jpg
- **Note**: Uses explicit Mesh - material detectable by Tydra
4. **openpbr-glass-sphere.usda** - Glass/transmission material
- Zero base weight, full transmission (0.95)
- Zero roughness for clear glass
- Transmission color: slight blue tint
5. **openpbr-subsurface-sphere.usda** - Subsurface scattering material
- Base color and subsurface color from texture
- Subsurface weight: 0.5, radius: 0.1
- Radius scale: (1.0, 0.5, 0.3) for realistic SSS
- Uses: textures/01.jpg
6. **openpbr-coated-cube.usda** - Clear coat material
- Brick texture base with clear coat layer
- Coat weight: 0.8, coat roughness: 0.1
- Coat IOR: 1.5
- Uses: textures/brick.bmp
### Multi-Object Scene
7. **openpbr-multi-object.usda** - Scene with multiple objects and materials
- BrickSphere: Rough brick material
- MetalCube: Smooth gold-colored metal (no texture)
- GlassSphere: Clear glass with transmission
- GroundPlane: Checkerboard pattern (5x tiled)
- **Note**: Only GroundPlane material (CheckerMaterial) is detectable by Tydra as it's an explicit Mesh
## Texture Files
All test files reference textures in the `textures/` subdirectory:
- **brick.bmp** - 64x64 red brick pattern with gray mortar (generated)
- **checkerboard.png** - Black and white checkerboard pattern
- **texture-cat.jpg** - Cat photo texture
- **01.jpg** - Color texture for subsurface scattering
## Material Structure
All materials follow this structure:
```
def Scope "_materials"
{
def Material "MaterialName"
{
token outputs:surface.connect = <path/to/OpenPBRSurface.outputs:surface>
def Shader "OpenPBRSurface"
{
uniform token info:id = "OpenPBRSurface"
// OpenPBR parameters (inputs:base_color, etc.)
token outputs:surface
}
def Shader "TextureName" (optional)
{
uniform token info:id = "UsdUVTexture"
asset inputs:file = @./textures/filename@
float2 inputs:texcoord.connect = <path/to/Primvar.outputs:result>
color3f outputs:rgb
}
def Shader "Primvar" (optional)
{
uniform token info:id = "UsdPrimvarReader_float2"
int inputs:index = 0
float2 outputs:result
}
}
}
```
## Shader Node IDs
- **OpenPBRSurface**: OpenPBR surface shader (info:id = "OpenPBRSurface")
- **UsdUVTexture**: Texture sampler (info:id = "UsdUVTexture")
- **UsdPrimvarReader_float2**: UV coordinate reader (info:id = "UsdPrimvarReader_float2")
**Note**: MaterialX node definition IDs (e.g., "ND_open_pbr_surface_surfaceshader") are not currently supported. Use the simplified USD shader IDs above.
## Tydra RenderScene Conversion Notes
The Tydra render converter currently only detects materials bound to **explicit Mesh primitives**. Parametric primitives (Sphere, Cube) are not converted, so their materials won't appear in RenderScene.
**Files with detectable materials:**
- openpbr-emissive-plane.usda (uses Mesh)
- openpbr-multi-object.usda (only GroundPlane/CheckerMaterial detected)
**Files with non-detectable materials (parametric primitives):**
- openpbr-brick-sphere.usda
- openpbr-metallic-cube.usda
- openpbr-glass-sphere.usda
- openpbr-subsurface-sphere.usda
- openpbr-coated-cube.usda
These files still parse correctly and can be used for testing MaterialX parsing, but won't export materials via the WASM MaterialX dumper CLI.
## Testing
### Native Build
```bash
# Parse test
./build/tusdcat models/openpbr-brick-sphere.usda
# Note: OpenPBRSurface may show as "[???] Invalid ShaderNode" in output
# This is a pprinter limitation, not a parsing error
```
### WASM MaterialX Export
```bash
cd web/js
npm run dump-materialx -- ../../models/openpbr-emissive-plane.usda -v
npm run dump-materialx -- ../../models/openpbr-multi-object.usda -f json
```
## OpenPBR Parameters Demonstrated
- **Base Layer**: base_weight, base_color, base_metalness, base_roughness
- **Specular**: specular_weight, specular_roughness, specular_ior
- **Transmission**: transmission_color, transmission_weight
- **Emission**: emission_color, emission_luminance
- **Subsurface**: subsurface_color, subsurface_weight, subsurface_radius, subsurface_radius_scale
- **Coat**: coat_weight, coat_roughness, coat_ior, coat_color
- **Geometry**: geometry_thin_walled (for glass)
## Related Files
- `create_brick_texture.py` - Python script to generate brick.bmp texture
- `polysphere-materialx-001.usda` - More complex MaterialX scene from Blender

View File

@@ -0,0 +1,55 @@
#usda 1.0
(
doc = "Simple OpenPBR material with brick texture"
metersPerUnit = 1
upAxis = "Y"
defaultPrim = "World"
)
def Xform "World"
{
def Sphere "BrickSphere"
{
double radius = 1.0
rel material:binding = </World/_materials/BrickMaterial>
texCoord2f[] primvars:st = [(0, 0), (1, 0), (1, 1), (0, 1)] (
interpolation = "vertex"
)
}
def Scope "_materials"
{
def Material "BrickMaterial"
{
token outputs:surface.connect = </World/_materials/BrickMaterial/OpenPBRSurface.outputs:surface>
def Shader "OpenPBRSurface"
{
uniform token info:id = "OpenPBRSurface"
color3f inputs:base_color.connect = </World/_materials/BrickMaterial/BrickTexture.outputs:rgb>
float inputs:base_metalness = 0.0
float inputs:base_weight = 1.0
float inputs:specular_roughness = 0.7
float inputs:specular_weight = 0.5
token outputs:surface
}
def Shader "BrickTexture"
{
uniform token info:id = "UsdUVTexture"
asset inputs:file = @./textures/brick.bmp@
string inputs:filtertype = "linear"
float2 inputs:texcoord.connect = </World/_materials/BrickMaterial/Primvar.outputs:result>
color3f outputs:rgb
}
def Shader "Primvar"
{
uniform token info:id = "UsdPrimvarReader_float2"
int inputs:index = 0
float2 outputs:result
}
}
}
}

View File

@@ -0,0 +1,66 @@
#usda 1.0
(
doc = "OpenPBR coated material with roughness texture"
metersPerUnit = 1
upAxis = "Y"
defaultPrim = "World"
)
def Xform "World"
{
def Cube "CoatedCube"
{
double size = 2.0
rel material:binding = </World/_materials/CoatedMaterial>
texCoord2f[] primvars:st = [
(0, 0), (1, 0), (1, 1), (0, 1),
(0, 0), (1, 0), (1, 1), (0, 1),
(0, 0), (1, 0), (1, 1), (0, 1),
(0, 0), (1, 0), (1, 1), (0, 1),
(0, 0), (1, 0), (1, 1), (0, 1),
(0, 0), (1, 0), (1, 1), (0, 1)
] (
interpolation = "faceVarying"
)
}
def Scope "_materials"
{
def Material "CoatedMaterial"
{
token outputs:surface.connect = </World/_materials/CoatedMaterial/OpenPBRSurface.outputs:surface>
def Shader "OpenPBRSurface"
{
uniform token info:id = "OpenPBRSurface"
color3f inputs:base_color.connect = </World/_materials/CoatedMaterial/BrickTexture.outputs:rgb>
float inputs:base_weight = 1.0
float inputs:base_metalness = 0.0
float inputs:specular_roughness = 0.5
float inputs:specular_weight = 0.5
float inputs:coat_weight = 0.8
float inputs:coat_roughness = 0.1
float inputs:coat_ior = 1.5
color3f inputs:coat_color = (1.0, 1.0, 1.0)
token outputs:surface
}
def Shader "BrickTexture"
{
uniform token info:id = "UsdUVTexture"
asset inputs:file = @./textures/brick.bmp@
string inputs:filtertype = "linear"
float2 inputs:texcoord.connect = </World/_materials/CoatedMaterial/Primvar.outputs:result>
color3f outputs:rgb
}
def Shader "Primvar"
{
uniform token info:id = "UsdPrimvarReader_float2"
int inputs:index = 0
float2 outputs:result
}
}
}
}

View File

@@ -0,0 +1,55 @@
#usda 1.0
(
doc = "OpenPBR emissive material with cat texture"
metersPerUnit = 1
upAxis = "Y"
defaultPrim = "World"
)
def Xform "World"
{
def Mesh "EmissivePlane"
{
int[] faceVertexCounts = [4]
int[] faceVertexIndices = [0, 1, 2, 3]
point3f[] points = [(-2, 0, -2), (2, 0, -2), (2, 0, 2), (-2, 0, 2)]
texCoord2f[] primvars:st = [(0, 0), (1, 0), (1, 1), (0, 1)] (
interpolation = "vertex"
)
rel material:binding = </World/_materials/EmissiveMaterial>
}
def Scope "_materials"
{
def Material "EmissiveMaterial"
{
token outputs:surface.connect = </World/_materials/EmissiveMaterial/OpenPBRSurface.outputs:surface>
def Shader "OpenPBRSurface"
{
uniform token info:id = "OpenPBRSurface"
color3f inputs:base_color = (0.1, 0.1, 0.1)
float inputs:base_weight = 0.5
color3f inputs:emission_color.connect = </World/_materials/EmissiveMaterial/CatTexture.outputs:rgb>
float inputs:emission_luminance = 10.0
token outputs:surface
}
def Shader "CatTexture"
{
uniform token info:id = "UsdUVTexture"
asset inputs:file = @./textures/texture-cat.jpg@
string inputs:filtertype = "linear"
float2 inputs:texcoord.connect = </World/_materials/EmissiveMaterial/Primvar.outputs:result>
color3f outputs:rgb
}
def Shader "Primvar"
{
uniform token info:id = "UsdPrimvarReader_float2"
int inputs:index = 0
float2 outputs:result
}
}
}
}

View File

@@ -0,0 +1,38 @@
#usda 1.0
(
doc = "OpenPBR glass material with transmission"
metersPerUnit = 1
upAxis = "Y"
defaultPrim = "World"
)
def Xform "World"
{
def Sphere "GlassSphere"
{
double radius = 1.5
rel material:binding = </World/_materials/GlassMaterial>
}
def Scope "_materials"
{
def Material "GlassMaterial"
{
token outputs:surface.connect = </World/_materials/GlassMaterial/OpenPBRSurface.outputs:surface>
def Shader "OpenPBRSurface"
{
uniform token info:id = "OpenPBRSurface"
color3f inputs:base_color = (1.0, 1.0, 1.0)
float inputs:base_weight = 0.0
float inputs:specular_roughness = 0.0
float inputs:specular_weight = 1.0
float inputs:specular_ior = 1.5
color3f inputs:transmission_color = (0.95, 0.98, 1.0)
float inputs:transmission_weight = 1.0
bool inputs:geometry_thin_walled = 0
token outputs:surface
}
}
}
}

View File

@@ -0,0 +1,63 @@
#usda 1.0
(
doc = "OpenPBR metallic material with checkerboard texture"
metersPerUnit = 1
upAxis = "Y"
defaultPrim = "World"
)
def Xform "World"
{
def Cube "MetallicCube"
{
double size = 2.0
rel material:binding = </World/_materials/MetallicMaterial>
texCoord2f[] primvars:st = [
(0, 0), (1, 0), (1, 1), (0, 1),
(0, 0), (1, 0), (1, 1), (0, 1),
(0, 0), (1, 0), (1, 1), (0, 1),
(0, 0), (1, 0), (1, 1), (0, 1),
(0, 0), (1, 0), (1, 1), (0, 1),
(0, 0), (1, 0), (1, 1), (0, 1)
] (
interpolation = "faceVarying"
)
}
def Scope "_materials"
{
def Material "MetallicMaterial"
{
token outputs:surface.connect = </World/_materials/MetallicMaterial/OpenPBRSurface.outputs:surface>
def Shader "OpenPBRSurface"
{
uniform token info:id = "OpenPBRSurface"
color3f inputs:base_color.connect = </World/_materials/MetallicMaterial/CheckerTexture.outputs:rgb>
float inputs:base_metalness = 0.8
float inputs:base_weight = 1.0
float inputs:specular_roughness = 0.2
float inputs:specular_weight = 1.0
float inputs:specular_ior = 1.5
token outputs:surface
}
def Shader "CheckerTexture"
{
uniform token info:id = "UsdUVTexture"
asset inputs:file = @./textures/checkerboard.png@
string inputs:filtertype = "linear"
float2 inputs:texcoord.connect = </World/_materials/MetallicMaterial/Primvar.outputs:result>
color3f outputs:rgb
}
def Shader "Primvar"
{
uniform token info:id = "UsdPrimvarReader_float2"
int inputs:index = 0
float2 outputs:result
}
}
}
}

View File

@@ -0,0 +1,159 @@
#usda 1.0
(
doc = "Multiple objects with different OpenPBR materials and textures"
metersPerUnit = 1
upAxis = "Y"
defaultPrim = "World"
)
def Xform "World"
{
def Sphere "BrickSphere" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
double radius = 0.8
double3 xformOp:translate = (-3, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate"]
rel material:binding = </World/_materials/BrickMaterial>
}
def Cube "MetalCube" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
double size = 1.5
double3 xformOp:translate = (0, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate"]
rel material:binding = </World/_materials/MetalMaterial>
texCoord2f[] primvars:st = [
(0, 0), (1, 0), (1, 1), (0, 1),
(0, 0), (1, 0), (1, 1), (0, 1),
(0, 0), (1, 0), (1, 1), (0, 1),
(0, 0), (1, 0), (1, 1), (0, 1),
(0, 0), (1, 0), (1, 1), (0, 1),
(0, 0), (1, 0), (1, 1), (0, 1)
] (
interpolation = "faceVarying"
)
}
def Sphere "GlassSphere" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
double radius = 0.9
double3 xformOp:translate = (3, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate"]
rel material:binding = </World/_materials/GlassMaterial>
}
def Mesh "GroundPlane" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
int[] faceVertexCounts = [4]
int[] faceVertexIndices = [0, 1, 2, 3]
point3f[] points = [(-10, -2, -10), (10, -2, -10), (10, -2, 10), (-10, -2, 10)]
texCoord2f[] primvars:st = [(0, 0), (5, 0), (5, 5), (0, 5)] (
interpolation = "vertex"
)
rel material:binding = </World/_materials/CheckerMaterial>
}
def Scope "_materials"
{
def Material "BrickMaterial"
{
token outputs:surface.connect = </World/_materials/BrickMaterial/OpenPBRSurface.outputs:surface>
def Shader "OpenPBRSurface"
{
uniform token info:id = "OpenPBRSurface"
color3f inputs:base_color.connect = </World/_materials/BrickMaterial/BrickTexture.outputs:rgb>
float inputs:base_metalness = 0.0
float inputs:base_weight = 1.0
float inputs:specular_roughness = 0.8
token outputs:surface
}
def Shader "BrickTexture"
{
uniform token info:id = "UsdUVTexture"
asset inputs:file = @./textures/brick.bmp@
float2 inputs:texcoord.connect = </World/_materials/BrickMaterial/Primvar.outputs:result>
color3f outputs:rgb
}
def Shader "Primvar"
{
uniform token info:id = "UsdPrimvarReader_float2"
int inputs:index = 0
float2 outputs:result
}
}
def Material "MetalMaterial"
{
token outputs:surface.connect = </World/_materials/MetalMaterial/OpenPBRSurface.outputs:surface>
def Shader "OpenPBRSurface"
{
uniform token info:id = "OpenPBRSurface"
color3f inputs:base_color = (0.9, 0.85, 0.7)
float inputs:base_metalness = 0.95
float inputs:base_weight = 1.0
float inputs:specular_roughness = 0.15
token outputs:surface
}
}
def Material "GlassMaterial"
{
token outputs:surface.connect = </World/_materials/GlassMaterial/OpenPBRSurface.outputs:surface>
def Shader "OpenPBRSurface"
{
uniform token info:id = "OpenPBRSurface"
color3f inputs:base_color = (1.0, 1.0, 1.0)
float inputs:base_weight = 0.0
float inputs:specular_roughness = 0.0
float inputs:specular_weight = 1.0
color3f inputs:transmission_color = (0.95, 0.98, 1.0)
float inputs:transmission_weight = 0.95
token outputs:surface
}
}
def Material "CheckerMaterial"
{
token outputs:surface.connect = </World/_materials/CheckerMaterial/OpenPBRSurface.outputs:surface>
def Shader "OpenPBRSurface"
{
uniform token info:id = "OpenPBRSurface"
color3f inputs:base_color.connect = </World/_materials/CheckerMaterial/CheckerTexture.outputs:rgb>
float inputs:base_metalness = 0.0
float inputs:base_weight = 1.0
float inputs:specular_roughness = 0.6
token outputs:surface
}
def Shader "CheckerTexture"
{
uniform token info:id = "UsdUVTexture"
asset inputs:file = @./textures/checkerboard.png@
float2 inputs:texcoord.connect = </World/_materials/CheckerMaterial/Primvar.outputs:result>
color3f outputs:rgb
}
def Shader "Primvar"
{
uniform token info:id = "UsdPrimvarReader_float2"
int inputs:index = 0
float2 outputs:result
}
}
}
}

View File

@@ -0,0 +1,58 @@
#usda 1.0
(
doc = "OpenPBR subsurface scattering material with texture"
metersPerUnit = 1
upAxis = "Y"
defaultPrim = "World"
)
def Xform "World"
{
def Sphere "SubsurfaceSphere"
{
double radius = 1.0
rel material:binding = </World/_materials/SubsurfaceMaterial>
texCoord2f[] primvars:st = [(0, 0), (1, 0), (1, 1), (0, 1)] (
interpolation = "vertex"
)
}
def Scope "_materials"
{
def Material "SubsurfaceMaterial"
{
token outputs:surface.connect = </World/_materials/SubsurfaceMaterial/OpenPBRSurface.outputs:surface>
def Shader "OpenPBRSurface"
{
uniform token info:id = "OpenPBRSurface"
color3f inputs:base_color.connect = </World/_materials/SubsurfaceMaterial/ColorTexture.outputs:rgb>
float inputs:base_weight = 0.8
float inputs:specular_roughness = 0.3
float inputs:specular_weight = 0.5
color3f inputs:subsurface_color.connect = </World/_materials/SubsurfaceMaterial/ColorTexture.outputs:rgb>
float inputs:subsurface_weight = 0.5
float inputs:subsurface_radius = 0.1
color3f inputs:subsurface_radius_scale = (1.0, 0.5, 0.3)
token outputs:surface
}
def Shader "ColorTexture"
{
uniform token info:id = "UsdUVTexture"
asset inputs:file = @./textures/01.jpg@
string inputs:filtertype = "linear"
float2 inputs:texcoord.connect = </World/_materials/SubsurfaceMaterial/Primvar.outputs:result>
color3f outputs:rgb
}
def Shader "Primvar"
{
uniform token info:id = "UsdPrimvarReader_float2"
int inputs:index = 0
float2 outputs:result
}
}
}
}

BIN
models/textures/brick.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB