# HDRGen - Synthetic HDR/EXR Environment Map Generator A pure Node.js tool for generating synthetic HDR environment maps for testing, debugging, and prototyping PBR materials and IBL (Image-Based Lighting) setups. ## Features - **Pure JavaScript/Node.js** - No external dependencies, no native bindings - **Multiple Presets** - White furnace, sun & sky, studio lighting - **Flexible Output** - HDR (Radiance RGBE) and EXR (OpenEXR) formats - **Dual Projections** - Equirectangular (lat-long) and cubemap - **Physically-Based** - Linear color space, HDR values, proper intensity scaling - **Customizable** - Extensive options for each preset - **CLI & API** - Use from command line or import as library ## Installation ```bash cd tools/hdrgen npm install ``` Make CLI globally available: ```bash npm link ``` ## Quick Start ### Generate White Furnace (Energy Conservation Test) ```bash node src/cli.js --preset white-furnace -o output/furnace.hdr ``` ### Generate Sun & Sky Environment ```bash node src/cli.js --preset sun-sky --sun-elevation 45 -o output/sky.hdr ``` ### Generate Studio Lighting ```bash node src/cli.js --preset studio -o output/studio.hdr ``` ### Generate Cubemap ```bash node src/cli.js --preset studio --projection cubemap --width 512 -o output/studio_cube ``` ### Generate All Examples ```bash npm run example ``` ## CLI Usage ```bash hdrgen [OPTIONS] ``` ### General Options | Option | Description | Default | |--------|-------------|---------| | `-h, --help` | Show help message | - | | `-p, --preset ` | Preset name (white-furnace, sun-sky, studio) | `white-furnace` | | `-w, --width ` | Width in pixels | `2048` | | `--height ` | Height in pixels | `1024` | | `--projection ` | Projection type (latlong, cubemap) | `latlong` | | `-f, --format ` | Output format (hdr, exr) | `hdr` | | `-o, --output ` | Output file path | `output/_.` | ### White Furnace Options | Option | Description | Default | |--------|-------------|---------| | `--intensity ` | Furnace intensity | `1.0` | **Purpose:** Uniform white environment for testing energy conservation. A perfect diffuse BRDF should integrate to exactly the furnace intensity. ### Sun & Sky Options | Option | Description | Default | |--------|-------------|---------| | `--sun-elevation ` | Sun elevation angle (0=horizon, 90=zenith) | `45` | | `--sun-azimuth ` | Sun azimuth angle (0=north, 90=east) | `135` | | `--sun-intensity ` | Sun disk intensity | `100.0` | | `--sky-intensity ` | Base sky intensity | `0.5` | **Purpose:** Procedural outdoor environment with directional sun and atmospheric sky gradient. ### Studio Lighting Options | Option | Description | Default | |--------|-------------|---------| | `--key-intensity ` | Key light intensity (main light) | `50.0` | | `--fill-intensity ` | Fill light intensity (shadow fill) | `10.0` | | `--rim-intensity ` | Rim light intensity (back highlight) | `20.0` | | `--ambient-intensity ` | Ambient light intensity | `0.5` | **Purpose:** Professional 3-point lighting setup for product visualization and character lighting. ## Presets ### 1. White Furnace Uniform white environment at specified intensity. Essential for validating PBR material energy conservation. **Use Cases:** - Energy conservation testing - BRDF validation - Exposure calibration - Albedo verification **Example:** ```bash # Standard furnace test hdrgen -p white-furnace --intensity 1.0 -o output/furnace.hdr # High intensity test hdrgen -p white-furnace --intensity 10.0 -o output/furnace_10x.hdr ``` **Expected Results:** - Perfectly diffuse white material should appear with albedo = intensity - Metals should appear dark (no diffuse reflection) - Specular reflections should be uniform in all directions ### 2. Sun & Sky Procedural sky with sun disk and atmospheric gradient. Simplified Hosek-Wilkie sky model approximation. **Use Cases:** - Outdoor scene lighting - Architecture visualization - Product photography (outdoor) - Time-of-day studies **Parameters:** - **Sun Elevation:** Height of sun above horizon (0°-90°) - `0°-10°`: Sunrise/sunset (warm, long shadows) - `30°-60°`: Mid-day (balanced, natural) - `80°-90°`: Noon (overhead, harsh) - **Sun Azimuth:** Compass direction of sun (0°-360°) - `0°`: North - `90°`: East - `180°`: South - `270°`: West **Examples:** ```bash # Afternoon sun (front-right) hdrgen -p sun-sky --sun-elevation 45 --sun-azimuth 135 -o output/afternoon.hdr # Sunset (low sun, west) hdrgen -p sun-sky --sun-elevation 5 --sun-azimuth 270 --sun-intensity 150 -o output/sunset.hdr # Noon (overhead) hdrgen -p sun-sky --sun-elevation 85 --sun-azimuth 0 --sun-intensity 200 -o output/noon.hdr # Sunrise (low sun, east) hdrgen -p sun-sky --sun-elevation 10 --sun-azimuth 90 --sun-intensity 120 -o output/sunrise.hdr ``` ### 3. Studio Lighting 3-point lighting setup with key, fill, and rim lights. Classic photography and cinematography lighting pattern. **Use Cases:** - Product rendering - Character lighting - Controlled lighting studies - Material comparison **Light Configuration:** - **Key Light:** Main light source (front-right, elevated) - Position: 45° right, 30° up - Color: Warm (slightly yellow) - **Fill Light:** Shadow fill (front-left, lower) - Position: 30° left, 20° up - Color: Cool (slightly blue) - **Rim Light:** Edge/separation light (back, elevated) - Position: Behind subject, 25° up - Color: Neutral white **Examples:** ```bash # Standard studio setup hdrgen -p studio -o output/studio.hdr # High-key lighting (bright, low contrast) hdrgen -p studio --key-intensity 80 --fill-intensity 30 --ambient-intensity 1.0 -o output/highkey.hdr # Low-key lighting (dramatic, high contrast) hdrgen -p studio --key-intensity 30 --fill-intensity 5 --rim-intensity 40 --ambient-intensity 0.1 -o output/lowkey.hdr # Product lighting (strong rim for edge definition) hdrgen -p studio --key-intensity 60 --fill-intensity 15 --rim-intensity 50 -o output/product.hdr ``` ## Projection Types ### Equirectangular (Lat-Long) Single image with 2:1 aspect ratio (e.g., 2048x1024). Standard format for environment maps. **Characteristics:** - Full 360° horizontal, 180° vertical coverage - Distortion at poles (top and bottom stretched) - Most compact format (single file) - Widely supported by renderers and DCCs **Output:** Single `.hdr` or `.exr` file ```bash hdrgen -p sun-sky --projection latlong -w 2048 --height 1024 -o output/sky.hdr ``` ### Cubemap Six square images representing faces of a cube. No distortion, but requires 6 separate files. **Face Order (OpenGL Convention):** - `+X`: Right - `-X`: Left - `+Y`: Top (up) - `-Y`: Bottom (down) - `+Z`: Front - `-Z`: Back **Characteristics:** - No polar distortion - Uniform sampling across all directions - Larger total file size (6 files) - Preferred for real-time rendering and importance sampling **Output:** Six files with face suffixes: `_+X.hdr`, `_-X.hdr`, etc. ```bash hdrgen -p studio --projection cubemap --width 512 -o output/studio_cube # Creates: studio_cube_+X.hdr, studio_cube_-X.hdr, ..., studio_cube_-Z.hdr ``` ## File Formats ### HDR (Radiance RGBE) Standard format for HDR images. Widely supported, compact, 8-bit per channel with shared exponent. **Specifications:** - Format: RGBE (RGB + 8-bit shared exponent) - Precision: ~7 decimal digits - Dynamic range: 10^-38 to 10^38 - File size: 4 bytes per pixel - Extension: `.hdr` **Advantages:** - Small file size - Fast to write - Universal support - Good for most IBL use cases **Limitations:** - Limited precision (8-bit mantissa) - No alpha channel - No arbitrary metadata ### EXR (OpenEXR) High-precision format from ILM. Better precision, alpha channel support, extensive metadata. **Specifications:** - Format: Half-float (16-bit) or float (32-bit) per channel - Precision: Half = 11-bit mantissa, Float = 24-bit mantissa - Dynamic range: Half = 10^-5 to 65504 - File size: 6 bytes (half) or 12 bytes (float) per pixel - Extension: `.exr` **Note:** Current implementation converts to HDR. For production EXR writing, use `@openexr/node` or similar library. ## API Usage ### As ES6 Module ```javascript import { HDRGenerator } from './src/hdrgen.js'; // Generate lat-long sun & sky const result = HDRGenerator.generate({ preset: 'sun-sky', width: 2048, height: 1024, projection: 'latlong', format: 'hdr', output: 'output/sky.hdr', presetOptions: { sunElevation: 45, sunAzimuth: 135, sunIntensity: 100.0 } }); // Access raw image data const { latLongImage } = result; console.log(`Generated ${latLongImage.width}x${latLongImage.height} HDR image`); ``` ### Programmatic Generation ```javascript import { HDRImage, EnvMapPresets, HDRWriter } from './src/hdrgen.js'; // Create custom environment const image = new HDRImage(2048, 1024); // Apply preset EnvMapPresets.sunSky(image, { sunElevation: 30, sunIntensity: 150.0 }); // Write to file HDRWriter.writeRGBE(image, 'output/custom.hdr'); ``` ### Custom Procedural Environments ```javascript import { HDRImage, Vec3 } from './src/hdrgen.js'; const image = new HDRImage(1024, 512); for (let y = 0; y < image.height; y++) { for (let x = 0; x < image.width; x++) { const u = x / image.width; const v = y / image.height; // Custom procedural function const r = Math.sin(u * Math.PI * 4) * 0.5 + 0.5; const g = Math.sin(v * Math.PI * 4) * 0.5 + 0.5; const b = 0.5; image.setPixel(x, y, r * 10.0, g * 10.0, b * 10.0); } } HDRWriter.writeRGBE(image, 'output/procedural.hdr'); ``` ## Resolution Guidelines ### Lat-Long Environments | Use Case | Resolution | Aspect | File Size (HDR) | |----------|-----------|--------|-----------------| | Quick preview | 512x256 | 2:1 | ~0.5 MB | | Development | 1024x512 | 2:1 | ~2 MB | | Production (low) | 2048x1024 | 2:1 | ~8 MB | | Production (standard) | 4096x2048 | 2:1 | ~32 MB | | Production (high) | 8192x4096 | 2:1 | ~128 MB | ### Cubemap Environments | Use Case | Face Size | Total Resolution | File Size (HDR x6) | |----------|----------|------------------|-------------------| | Preview | 128x128 | 128 | ~0.4 MB | | Low | 256x256 | 256 | ~1.5 MB | | Medium | 512x512 | 512 | ~6 MB | | High | 1024x1024 | 1K | ~24 MB | | Ultra | 2048x2048 | 2K | ~96 MB | **Recommendation:** Start with 1024x512 lat-long for development, use 2048x1024 or higher for final renders. ## Testing & Validation ### Energy Conservation Test White furnace with diffuse white material should reflect exactly the furnace intensity: ```bash # Generate test environment hdrgen -p white-furnace --intensity 1.0 -o output/furnace_test.hdr # In your renderer: # 1. Load furnace_test.hdr # 2. Create material: diffuse white (albedo = 1.0) # 3. Render sphere # 4. Expected result: sphere appears with color = (1.0, 1.0, 1.0) ``` ### Sun Direction Validation ```bash # Generate known sun position (45° elevation, 90° east) hdrgen -p sun-sky --sun-elevation 45 --sun-azimuth 90 -o output/sun_test.hdr # Visual check: Bright spot should be in upper-right quadrant of lat-long image # Azimuth 90° = right side of image (east) # Elevation 45° = mid-height of image ``` ### Studio Lighting Validation ```bash # Generate studio environment hdrgen -p studio -o output/studio_test.hdr # Expected light positions: # - Key light: Front-right (bright warm spot) # - Fill light: Front-left (dimmer cool glow) # - Rim light: Back (sharp highlight) ``` ## Importing in DCCs ### Blender 1. Switch to **Shading** workspace 2. Select **World** shader 3. Add **Environment Texture** node 4. Open generated `.hdr` file 5. Connect to **Background** shader ### Houdini 1. Create **Environment Light** 2. Set **Environment Map** to generated `.hdr` file 3. Adjust **Intensity** if needed ### Maya 1. Create **Skydome Light** 2. Load generated `.hdr` in **Color** attribute 3. Set **Exposure** if needed ### Unreal Engine 1. Import `.hdr` as **Texture Cube** (for cubemaps) or **Texture 2D** (for lat-long) 2. Create **Skylight** actor 3. Set **Source Type** to **SLS Specified Cubemap** 4. Assign texture ### Unity 1. Import `.hdr` file 2. Set **Texture Shape** to **Cube** (for cubemap) or **2D** (for lat-long) 3. In **Lighting** window, assign to **Environment Skybox** ## Technical Details ### Color Space All generated environments are in **linear color space** (no gamma encoding). This is correct for PBR rendering and HDR textures. - Input values: Linear RGB - Output values: Linear RGB (HDR, values can exceed 1.0) - No sRGB transfer function applied ### HDR Range Values are unbounded and can significantly exceed 1.0: - Sun disk: 50-200+ (direct sunlight simulation) - Sky: 0.1-2.0 (indirect ambient) - Studio lights: 10-100 (controlled lighting) - White furnace: 0.1-10.0 (testing range) ### Coordinate System **Lat-Long (Equirectangular):** - U (horizontal): 0 = -180° (west), 0.5 = 0° (north), 1.0 = 180° (east) - V (vertical): 0 = -90° (down), 0.5 = 0° (horizon), 1.0 = 90° (up) - Direction: +X right, +Y up, +Z forward **Cubemap (OpenGL Convention):** - Faces: +X, -X, +Y, -Y, +Z, -Z - +Y is up (top face) - Right-handed coordinate system ## Troubleshooting ### Generated files appear too dark/bright Adjust intensity parameters: ```bash # Increase sun intensity hdrgen -p sun-sky --sun-intensity 200 -o output/bright_sky.hdr # Decrease studio key light hdrgen -p studio --key-intensity 25 -o output/dim_studio.hdr ``` ### Sun position is incorrect Check azimuth convention: - 0° = North (top of lat-long image, middle) - 90° = East (right side of lat-long image) - 180° = South (bottom, middle) - 270° = West (left side) ### Cubemap faces don't align properly Ensure DCC is using OpenGL convention (+Y up). Some DCCs (DirectX convention) may require face reordering. ### EXR files aren't working Current implementation writes HDR format. For true EXR support, integrate `@openexr/node` or similar library. ## Limitations - **EXR Writing:** Currently writes HDR format instead (requires external library for true EXR) - **Sky Model:** Simplified procedural sky (not full Hosek-Wilkie or Nishita) - **Compression:** HDR files are uncompressed (no RLE compression) - **Metadata:** Minimal file metadata (no custom tags) ## Roadmap - [ ] Proper EXR writing with compression - [ ] More presets (indoor, sunset, overcast, etc.) - [ ] Importance sampling map generation - [ ] Diffuse/specular pre-filtering - [ ] HDRI panorama manipulation (rotate, exposure) - [ ] Animation (time-of-day sequence) - [ ] Web-based visualizer ## License Apache License 2.0 Copyright 2024 - Present, Light Transport Entertainment Inc. ## References - [HDR Image Formats](https://www.pauldebevec.com/Research/HDR/) - [Radiance RGBE Format](https://floyd.lbl.gov/radiance/refer/Notes/picture_format.html) - [OpenEXR Specification](https://www.openexr.com/) - [PBR Theory](https://www.pbrt.org/) - [Hosek-Wilkie Sky Model](https://cgg.mff.cuni.cz/projects/SkylightModelling/) ## Contributing Contributions welcome! Please ensure: - Code follows existing style - Tests pass: `npm test` - Examples work: `npm run example` - Documentation is updated ## Support For issues or questions, see: https://github.com/syoyo/tinyusdz