Skip to content

Latest commit

 

History

History
874 lines (686 loc) · 42 KB

File metadata and controls

874 lines (686 loc) · 42 KB

TruePBR Manager - Current Specification

This document describes the repository as it exists now. It is implementation-oriented, not a forward-looking design draft.

1. Overview

Item Value
Project Name TruePBR Manager
Type Desktop GUI application
Language C++20
Build System CMake 3.21+
GUI Framework Qt 6 Widgets
Package Manager vcpkg
Target Platform Windows x64
License CC BY-NC 4.0

2. Purpose

TruePBR Manager is a desktop authoring tool for Skyrim modding workflows built around Community Shaders True PBR and PGPatcher.

The current implementation is designed to:

  1. Manage multiple PBR texture sets in a single project.
  2. Map each set to one vanilla diffuse path.
  3. Import required and optional textures into True PBR slots.
  4. Support RMAOS authoring either as a pre-packed texture or as split Roughness, Metallic, AO, and Specular sources.
  5. Edit feature flags and material parameters.
  6. Export textures as DDS into a mod folder and generate a PGPatcher JSON file.

Primary references:

3. Implemented Feature Surface

3.1 Project lifecycle

  • New project in memory
  • Save project to .tpbr
  • Load project from .tpbr
  • Add, duplicate, rename, and remove texture sets
  • Add texture set flow: the path dialog is shown first (pre-filled with the base texture path setting if configured), then the name dialog is shown pre-filled with the last segment of the entered path (everything after the last \ or /). The user can edit the suggested name freely.
  • Base texture path: configurable vanilla path prefix (e.g. architecture\whiterun\) persisted in app settings; accessible via File > Set Base Texture Path...
  • Command-line open: passing a .tpbr file path as the first argument opens that project on launch (enables Windows shell "open" verb from file association)
  • Drag-and-drop open: .tpbr files can be dragged onto the main window to open them as the current project
  • Project switch confirmation: when a project is already loaded (has texture sets or has been saved), opening or creating a new project shows a Save/Discard/Cancel dialog to protect against accidental data loss

3.2 Texture authoring

  • Import slot textures from DDS and raster formats (PNG, TGA, BMP, JPG/JPEG)
  • Import split RMAOS channels independently
  • Drag-and-drop import onto slot and channel controls
  • Separate Import and Clear buttons per slot and per channel row
  • Click a slot's DropZone to preview that texture (does not open import dialog)
  • Persist imported file metadata: source path, dimensions, channel count, format
  • Flip Normal G: per-slot toggle button on the Normal row that inverts the Green channel at preview and export time (for DirectX/OpenGL normal map convention conversion). The source file is never modified; the flag is persisted in the project file and applied non-destructively.
  • Performance optimizations:
    • getDDSInfo() reads only the DDS header (148 bytes) via GetMetadataFromDDSFile — no pixel data is loaded or decompressed
    • Alpha mode at import time is inferred from the file format without pixel scanning (conservative: assumes Opaque for formats with alpha capability)
    • Thumbnails use mip-aware DDS loading (loadDDSAtMaxSize) to decompress only a ~64×64 mip level (~16 KB) instead of the full 4K image (~67 MB)
    • Raster thumbnails use stb_image (via ImageUtils::loadImage) for format coverage (TGA, PNG, BMP, JPG) with post-load downscale to thumbnail size
    • TextureCache singleton caches decoded RGBA8 pixel data keyed by (canonicalPath, lastWriteTime), shared across 2D preview, 3D preview, and channel packing — eliminates redundant disk reads on repeated preview refreshes

3.3 Material authoring

  • Edit feature flags in the UI
  • Edit base, emissive, parallax, subsurface, coat, fuzz, and glint parameters in the UI
  • Color parameters support per-row display mode toggle between 0.0-1.0 (float) and 0-255 (integer) ranges; internal model always stores normalized float values
  • Store per-slot export compression overrides
  • Store RMAOS source mode per texture set

3.4 2D preview

  • Image preview with zoom, pan, and per-channel isolation (R/G/B/A)
  • Click any slot's texture to preview it; channel buttons appear for multi-channel textures
  • Default display: diffuse → normal → empty

3.5 3D material preview

  • D3D12-based real-time PBR renderer with Cook-Torrance BRDF
  • Loads Diffuse, Normal, RMAOS, and feature textures (Emissive, Coat/Fuzz, Subsurface/CoatColor) from the current texture set
  • RMAOS composed from split channels in SeparateChannels mode
  • Four mesh shapes: Sphere, Plane (double-sided), Cube, Rounded Cube
  • Orbit camera (left-drag to rotate, scroll to zoom)
  • Directional light rotation (right-drag)
  • HDRI environment rotation (middle-drag)
  • Image-Based Lighting (IBL) with GPU compute pipeline:
    • HDRI loading from EXR, HDR, and DDS files
    • HDRI color space detection (Rec709, ACEScg, ACES2065-1, Rec2020) with automatic conversion to ACEScg
    • 4 GPU compute passes: equirect-to-cubemap, ZH3 diffuse irradiance, GGX prefiltered specular, BRDF LUT
    • Configurable prefilter resolution and sample count
  • Skybox rendering from loaded HDRI
  • GT7 tone mapping with exposure compensation (EV)
  • All shader computation in ACEScg working color space
  • Temporal Anti-Aliasing (TAA) with per-frame jitter and velocity reprojection
  • HDR output support (scRGB) with paper-white and peak-brightness controls
  • VSync toggle (DXGI_PRESENT_ALLOW_TEARING when disabled)
  • Render flags: Horizon Occlusion, Multi-Bounce AO, Specular Occlusion
  • Full PBR feature support in preview: emissive, subsurface, coat, fuzz, glint, hair
  • Automatic GPU selection (discrete adapter preferred)
  • Device-lost detection and recovery
  • Full mip chain generation: CPU box filter downsampling with per-level upload to GPU
  • Configurable mip LOD bias for material texture sampling (default -1.0, adjustable -1.0 to 0.0)
  • Debug channel visualization: Normal, Roughness, Metallic, AO, Specular (bypasses tone mapping)

3.6 Export

  • DDS export for each assigned slot
  • Split-channel packing into _rmaos.dds during export
  • Per-slot export size override (power-of-two downscale or original)
  • Per-slot path override for custom PGPatcher slotN paths
  • PGPatcher JSON export to PBRNIFPatcher/<project>.json
  • Automatic export directory creation under textures/pbr/...
  • Pre-export validation with error/warning reporting

3.7 Batch import

  • Scan a folder and auto-assign textures to slots by suffix convention
  • Recognized slot suffixes: _n, _nrm, _normal, _nor, _g, _e, _emissive, _emission, _glow, _p, _h, _height, _parallax, _displacement, _disp, _rmaos, _cnr, _f, _fuzz, _sss, _sk, _subsurface
  • Recognized channel suffixes: _r, _roughness, _rough, _m, _metallic, _metal, _metalness, _o, _ao, _occlusion, _s, _specular, _spec
  • Files with no recognized suffix (or _d, _diffuse, _albedo, _basecolor) are assigned as Diffuse
  • If channel maps are found, RMAOS source mode is automatically set to SeparateChannels
  • Suffix _s is unified as Specular (channel), not Subsurface; use _sss/_sk/_subsurface for the Subsurface slot

3.7.1 Auto-Detect from Diffuse

  • Once a Diffuse/Albedo texture is imported, the "Auto-Detect" button (in the Diffuse slot row) scans the same directory for files sharing the same base name with recognized PBR suffixes
  • Base name extraction: strips known diffuse suffixes (_d, _diffuse, _albedo, _basecolor) from the filename stem; if no known suffix is present, the full stem is used as the base name
  • Case-insensitive suffix matching
  • Supported file types: .png, .dds, .tga, .bmp, .jpg, .jpeg
  • RMAOS priority rule: if a packed _rmaos file is found, individual channel files (_r, _m, _o, _s) are discarded; RMAOS mode is set to PackedTexture
  • If only individual channels are found (no _rmaos), mode is set to SeparateChannels
  • Conflict handling: if target slots already have textures, a dialog asks the user to "Overwrite All", "Keep Existing", or "Cancel"
  • The button is disabled until a Diffuse texture is imported
  • The full suffix reference table is displayed in the button's tooltip

Recognized suffix table:

Target Suffixes
Normal (slot) _n, _nrm, _normal, _nor
RMAOS (slot) _rmaos
Emissive (slot) _e, _g, _emissive, _glow, _emission
Displacement (slot) _p, _h, _height, _parallax, _displacement, _disp
Subsurface (slot) _sss, _sk, _subsurface
Fuzz (slot) _f, _fuzz
Coat Normal (slot) _cnr
Roughness (channel) _r, _roughness, _rough
Metallic (channel) _m, _metallic, _metal, _metalness
AO (channel) _o, _ao, _occlusion
Specular (channel) _s, _specular, _spec

3.8 Import existing PBR mod

  • Import an existing PBR mod directory containing PGPatcher JSON and DDS textures
  • Recursively scans <modDir>/PBRNIFPatcher/ (including all subdirectories) for JSON files and resolves textures from <modDir>/textures/pbr/
  • When multiple JSON files are found, presents a selection dialog for the user to choose one to import
  • Supports both PGPatcher JSON layouts:
    • Flat array: [{entry}, {entry}, ...]
    • Defaults with entries: {"default": {...}, "entries": [{entry}, ...]}
  • Parses all PGPatcher fields per the Mod Authors specification:
    • Matching fields: texture / match_diffuse, match_normal, rename
    • Feature flags: emissive, parallax, subsurface, subsurface_foliage, coat_normal, coat_diffuse, coat_parallax, hair
    • Nested feature objects: fuzz (with texture, color, weight) and glint (with screen_space_scale, log_microfacet_density, microfacet_roughness, density_randomization)
    • Parameters: specular_level, roughness_scale, displacement_scale, subsurface_opacity, subsurface_color, emissive_scale, emissive_color, coat parameters
    • Vertex color controls: vertex_colors, vertex_color_lum_mult, vertex_color_sat_mult
    • Explicit slot paths: slot1..slot8
    • Lock fields: lock_diffuse, lock_normal, etc.
  • Textures are resolved on disk by convention-based path (textures/pbr/<matchDir>/<stem><suffix>) or explicit slot paths
  • Case-insensitive file lookup for texture resolution
  • When rename is present, tries both the renamed stem and the original match stem for texture lookup
  • When rename contains a directory path (e.g., landscape\dirt02), PBR textures are resolved from the rename directory instead of the original match directory. The original vanilla match is stored as an alias, and matchTexture is set to the effective PBR path.
  • When rename is a stem-only value (e.g., custom_wood), PBR textures stay in the original match directory and no alias is created.
  • Multiple entries sharing the same rename target are merged into a single texture set with the additional vanilla paths stored as match aliases. The primary entry is the one whose matchTexture stem matches the texture set name.
  • Entries with delete: true are skipped
  • Default fields are merged into each entry (entry fields take precedence)
  • Replaces the current project; sets the mod directory as the export folder; project name defaults to the JSON filename
  • Reports import diagnostics (errors, warnings, info) to the user
  • Accessible from File > Import PBR Mod...

3.9 Input validation

  • Pre-export validation checks per texture set:
    • Missing required slots (Diffuse, Normal, RMAOS)
    • Resolution mismatches between slot textures
    • Non-power-of-two resolutions
    • Enabled features with missing corresponding textures
    • NIF slot conflicts (TX06: CoatNormalRoughness vs Fuzz; TX07: Subsurface vs CoatColor)
    • Empty vanilla match texture path
    • Empty or duplicate match alias paths
  • Errors block export; warnings allow continue with user confirmation

3.10 Landscape support

  • Any texture set can optionally have one or more Landscape TXST EDIDs
  • When EDIDs are present, the exporter generates PBRTextureSets/<edid>.json per EDID
  • The JSON contains only material parameters (roughnessScale, displacementScale, specularLevel, subsurfaceColor, subsurfaceOpacity)
  • Textures are shared with NIF export — no separate landscape texture output
  • This is an additive option, not a separate type: the same set can serve both NIF and Landscape

3.11 Localization

  • JSON-based translation system via TranslationManager singleton and JsonTranslator (subclass of QTranslator)
  • Translation files stored in translations/ directory next to the executable (source-tree fallback for development builds)
  • Each JSON file declares locale, name, and a translations map keyed by fully-qualified class context ("tpbr::ClassName")
  • Shipped languages: English (en.json), Simplified Chinese (zh_CN.json)
  • Automatic locale detection on startup using system language with Chinese-family fallback (zh_CN, zh_TW, zh_HKzh_CN)
  • Runtime language switching from the menu bar; triggers QEvent::LanguageChange to retranslate all widgets
  • All translatable UI widgets implement retranslateUi() and changeEvent() overrides
  • Hot-reload: QTimer-based 2-second polling detects file changes and reloads translations without restart
  • Adding a new language requires only dropping a new <locale>.json file into the translations/ directory

Development workflow: Any code change that adds, removes, or modifies tr() strings MUST be accompanied by corresponding updates to ALL translation files (translations/*.json). The key in each JSON file is the exact English source string passed to tr(), scoped under the fully-qualified class context (e.g. "tpbr::MainWindow"). Forgetting to update translation files will cause the new strings to display untranslated in non-English locales.

3.12 Application settings persistence

  • AppSettings (singleton) persists project-independent preferences via QSettings in INI format
  • Settings file: TruePBR-Manager.ini next to the executable
  • Currently persisted settings:
    • General/language: selected UI locale (restored on startup instead of re-detecting system language)
    • General/fileAssocRegistered: whether the first-launch file association prompt has been shown (bool)
    • MainWindow/geometry: window position and size
    • MainWindow/state: toolbar and dock widget layout
    • Paths/lastProjectDir: last directory used for open/save project dialogs
    • Paths/lastExportPath: last used export folder
    • Paths/baseTexturePath: vanilla path prefix for the "Add Texture Set" dialog (e.g. architecture\whiterun\)
    • RecentProjects/paths: ordered list of recently opened/saved project file paths (max 10)
    • Preview3D/*: 3D preview settings (light intensity, light color, exposure, HDRI selection, IBL intensity/resolution/samples, render flags, display options, shape)
  • Window geometry and state are saved on close and restored on launch
  • Language preference is saved whenever the user switches language via the menu
  • 3D preview settings are saved on close and restored on launch
  • Recent projects list is updated on every successful open or save

3.13 Export

  • Export runs on a background thread with a modal progress dialog
  • Progress reports per-texture-set and for JSON export steps
  • Users can cancel an in-progress export
  • Textures whose output DDS already exists, is newer than the source, and matches the target compression and dimensions are automatically skipped
  • RMAOS channel-packed textures are skipped if the output is newer than all channel source files

3.14 Known current limitations

  • No undo/redo

3.15 Vanilla texture set conversion

Converts vanilla Skyrim Blinn-Phong textures into True PBR texture sets. Accessible from File > Convert Vanilla Textures...

Input textures (one texture set per conversion):

Vanilla Slot Required Maps to PBR Output
Diffuse (TX00) Yes Albedo (with gamma/brightness and cubemap tint applied)
Normal (TX01) Yes Normal (pass-through; alpha optionally used as specular source)
Glow (TX02) No Emissive (with gamma/brightness applied)
Parallax (TX03) No Displacement (pass-through)
Specular (TX07 / Normal alpha) No RMAOS Specular channel (A)
BackLight (TX07) No Subsurface (with gamma/brightness applied)
EnvMask (TX02) No RMAOS Metallic channel (G); also used as blend mask for cubemap tint
Cubemap (TX05) No Averaged to a single color for metallic tint overlay on Albedo

Conversion parameters:

  • Shininess (float, default 50): Blinn-Phong exponent, converted to roughness via pow(2 / (2 + shininess), 0.25)
  • Specular Mode: Direct (use specular map values as-is) or Divide by PI (divide by pi for energy conservation)
  • Normal Alpha is Specular (bool): when enabled and no separate Specular map is provided, extracts specular from the Normal map's alpha channel
  • Derive Roughness from Specular (bool): when enabled, uses the specular value (from separate Specular map or Normal alpha if that option is enabled) to compute per-pixel roughness. Formula: Roughness = pow(1 - specular/255, roughnessPower) * 255. In this mode, RMAOS Specular channel (A) is set to white (255) and specularLevel is automatically set to 0.04 on the generated texture set. Works with any specular source.
  • Roughness Power (float, 0.1–10.0, default 1.0): exponent for the roughness derivation formula. Only active when Derive Roughness from Specular is enabled.
  • Metallic Roughness Override (optional float, 0.0-1.0): when enabled, overrides the computed roughness value for pixels where EnvMask > 0 (metallic regions)
  • Per-texture Gamma/Brightness: independent gamma (0.1-5.0) and brightness (-1.0 to 1.0) controls for each color texture (Diffuse, Glow, BackLight, Cubemap). Applied non-destructively in linear space: sRGB→linear → pow(linear, 1/gamma) → add brightness → linear→sRGB

Cubemap metallic tint overlay: when both EnvMask and Cubemap are provided, the Albedo output is blended with the cubemap's average color using the EnvMask as a blend mask: albedo = lerp(diffuse, cubemapAvgColor, envMask.R). The cubemap average color is computed in linear space after applying the cubemap's gamma/brightness adjustments. When EnvMask dimensions differ from Diffuse, nearest-neighbor sampling is used for coordinate mapping.

RMAOS channel synthesis:

Channel Source
R (Roughness) shininessToRoughness(shininess), or metallicRoughnessOverride where EnvMask > 0, or pow(1 - normalAlpha, roughnessPower) when Derive Roughness from Alpha is enabled
G (Metallic) EnvMask red channel (0 if absent); nearest-neighbor sampled when dimensions differ from Diffuse
B (AO) Solid white (255, no vanilla source)
A (Specular) Specular map (nearest-neighbor sampled), or Normal alpha (if enabled), or white (255, when Derive Roughness from Alpha mode), or default 20 (~0.08 baseline)

Dialog layout (single-page, integrated flow):

  • Left panel: scrollable list of 8 vanilla input texture rows, each with thumbnail preview, browse/clear buttons, and per-texture gamma/brightness controls for color textures
  • Right panel: 3x2 grid of PBR output previews (Albedo, Normal, RMAOS, Emissive, Displacement, Subsurface) shown simultaneously
  • Bottom: conversion parameters, output settings (directory, texture set name, vanilla match path), and Convert/Cancel buttons
  • All output previews update in real-time (150ms debounce) as inputs or parameters change
  • Preview operations run on 256x256 downscaled copies for performance; full-resolution data is loaded from disk during final conversion

Conversion output:

  • Albedo: <name>.dds (BC7 sRGB)
  • Normal: <name>_n.dds (BC7 linear)
  • RMAOS: <name>_rmaos.dds (BC7 linear)
  • Emissive: <name>_g.dds (BC7 sRGB, if Glow provided)
  • Displacement: <name>_p.dds (BC7 linear, if Parallax provided)
  • Subsurface: <name>_s.dds (BC7 sRGB, if BackLight provided)

Files are saved to a user-specified output directory. The generated PBRTextureSet is automatically added to the current project.

Execution: conversion runs on a background worker thread with a modal QProgressDialog (matching the export pattern). Progress reports per step (loading, processing, saving each DDS). Supports cancel via shared atomic flag.

Unit tests (test_VanillaConverter.cpp): sRGB/linear round-trip, shininess-to-roughness mapping, gamma/brightness pixel processing, alpha extraction, input validation, per-texture GammaBrightnessParams helper.

3.16 File association and shell integration

  • ProgId: TruePBR.Project registered under HKCU\Software\Classes
  • Registry scope: per-user (HKCU), no admin elevation required
  • Open verb: shell\open\command set to "<exe_path>" "%1", enabling double-click-to-open in Windows Explorer
  • Default icon: application executable icon (DefaultIcon = "<exe_path>",0)
  • First-launch prompt: on first startup (tracked via General/fileAssocRegistered in AppSettings), a QMessageBox::question asks the user whether to register the file association. The prompt is shown only once regardless of the answer.
  • Manual registration: accessible via File > Register .tpbr File Association menu action; shows success/failure feedback
  • Shell notification: SHChangeNotify(SHCNE_ASSOCCHANGED, ...) is called after registration to refresh Explorer icons and context menus immediately
  • Unregistration: FileAssociation::unregisterAssociation() is available but not exposed in the UI (reserved for future settings or uninstaller use)
  • Command-line integration: the application checks argv[1] on startup; if it is a valid .tpbr file path, it is opened automatically after the main window is shown

4. Texture Slot Model

4.1 Slot reference

Enum NIF Slot Suffix Content Status
Diffuse TX00 .dds Base Color RGB + Opacity A Implemented
Normal TX01 _n.dds Normal Map RGB Implemented
Emissive TX02 _g.dds Emissive / Glow RGB Implemented
Displacement TX03 _p.dds Height / Parallax Implemented
RMAOS TX05 _rmaos.dds Roughness R, Metallic G, AO B, Specular A Implemented
CoatNormalRoughness TX06 _cnr.dds Coat Normal RGB + Coat Roughness A Implemented
Fuzz TX06 _f.dds Fuzz RGB + Fuzz Mask A Implemented
Subsurface TX07 _s.dds Subsurface RGB + Opacity A Implemented
CoatColor TX07 _s.dds Coat Color RGB + Strength A Implemented

Notes:

  • TX06 is shared by coat normal roughness and fuzz.
  • TX07 is shared by subsurface and coat color.
  • The code allows both enums to exist in the data model; conflict handling remains a workflow responsibility.

4.2 RMAOS channels

The current channel packing model supports:

Channel Output Channel
Roughness R
Metallic G
AO B
Specular A

When a split channel is missing, exporter defaults are used:

  • Roughness: 255
  • Metallic: 0
  • AO: 255
  • Specular: 255

5. Core Data Model

5.1 Enumerations

Implemented enums in the model layer:

  • PBRTextureSlot (9 values)
  • ChannelMap (4 values)
  • RMAOSSourceMode with PackedTexture and SeparateChannels
  • TextureMatchMode with Auto, Diffuse, and Normal
  • DDSCompressionMode with BC7, BC6H, BC5, BC4, BC3, BC1, and RGBA8 variants (10 total, both sRGB and Linear where applicable)
  • TextureAlphaMode with Unknown, None, Opaque, and Transparent

5.2 Texture entry types

TextureEntry stores:

  • sourcePath
  • slot
  • width
  • height
  • channels
  • alphaMode (detected during import: Unknown, None, Opaque, Transparent)
  • format

ChannelMapEntry stores the same metadata except for slot and alphaMode.

5.3 Feature flags

The current project model stores these flags:

  • emissive
  • parallax
  • subsurface
  • subsurfaceFoliage
  • multilayer
  • coatDiffuse
  • coatParallax
  • coatNormal
  • fuzz
  • glint
  • hair

5.4 Parameters

The current parameter model stores:

  • specularLevel
  • roughnessScale
  • displacementScale
  • subsurfaceOpacity
  • subsurfaceColor
  • emissiveScale
  • coatStrength
  • coatRoughness
  • coatSpecularLevel
  • fuzzColor
  • fuzzWeight
  • glintScreenSpaceScale
  • glintLogMicrofacetDensity
  • glintMicrofacetRoughness
  • glintDensityRandomization
  • vertexColors
  • vertexColorLumMult
  • vertexColorSatMult

5.5 Texture set

Each PBRTextureSet currently contains:

  • Display name
  • Vanilla match texture path (primary)
  • Vanilla match mode: auto, diffuse, or normal
  • Match aliases: additional vanilla texture paths that share this PBR texture set (used with PGPatcher rename). Each alias stores a matchTexture and matchMode.
  • Imported textures map
  • Per-slot export compression map
  • Per-slot export size override map ({0,0} = original, otherwise power-of-two target)
  • Per-slot path override map (custom PGPatcher slotN export paths)
  • Active RMAOS source mode
  • Split channel map entries
  • Feature flags
  • Parameters
  • Landscape TXST EDIDs (optional, one JSON per EDID)
  • Tags and notes

5.6 Project

Project currently contains:

  • name
  • outputModFolder
  • textureSets

It also implements:

  • addTextureSet
  • removeTextureSet
  • save
  • load

6. Project File Format

Projects are saved as JSON using the .tpbr extension.

Top-level fields currently written by the app:

{
  "version": "1.0",
  "name": "ExampleProject",
  "output_mod_folder": "D:/Mods/Example",
  "texture_sets": []
}

Each texture set currently serializes:

  • Name and match texture
  • Match texture mode
  • Tags and notes
  • features
  • params
  • rmaos_source_mode
  • textures
  • export_compression
  • channel_maps

Compression overrides are stored using stable keys such as:

  • bc7_srgb
  • bc7_linear
  • bc3_srgb
  • bc6h_uf16
  • bc5_linear
  • bc4_linear
  • bc1_srgb
  • bc1_linear
  • rgba8_srgb
  • rgba8_linear

RMAOS source mode uses:

  • packed
  • split

Match texture mode uses:

  • auto
  • diffuse
  • normal

7. PGPatcher JSON Output

The exporter currently writes a JSON array, not a default plus entries object.

Output path:

<mod_folder>/PBRNIFPatcher/<project_name>.json

Representative entry shape:

[
  {
    "texture": "architecture\\whiterun\\wrwoodplank01",
    "emissive": false,
    "parallax": true,
    "subsurface_foliage": false,
    "subsurface": false,
    "specular_level": 0.04,
    "roughness_scale": 1.0,
    "subsurface_opacity": 1.0,
    "displacement_scale": 1.0,
    "subsurface_color": [1.0, 1.0, 1.0]
  }
]

Conditional fields currently emitted by implementation:

  • emissive_scale when emissive is enabled
  • emissive_color when emissive is enabled (RGBA array [R, G, B, A] per PGPatcher spec; alpha defaults to 1.0)
  • match_normal when a set is configured to match vanilla normal instead of diffuse
  • rename when exported PBR texture base name differs from the matched vanilla base name
  • Additional JSON entries for each match alias: each alias entry uses the alias's matchTexture as the match field and adds a rename pointing back to the primary matchTexture path, so PGPatcher maps multiple vanilla textures to the same PBR texture set
  • Explicit slotN paths when the generated path differs from what PGPatcher would infer by convention
  • lock_diffuse, lock_normal, lock_emissive, lock_parallax, lock_rmaos, lock_subsurface, lock_cnr when corresponding slots have no exported texture
  • Coat fields when multilayer or coat normal is enabled
  • fuzz object when fuzz is enabled
  • glint object when glint is enabled
  • hair when hair is enabled
  • Vertex-color override fields when they differ from defaults
  • All float values are rounded to 3 decimal places

8. Export Behavior

8.1 Output layout

<mod_folder>/
├── PBRNIFPatcher/
│   └── <project_name>.json
└── textures/
    └── pbr/
        └── <match_texture_parent>/
            ├── <stem>.dds
            ├── <stem>_n.dds
            ├── <stem>_rmaos.dds
            └── ...

Example:

If matchTexture is architecture\whiterun\wrwoodplank01, the diffuse export path is:

textures/pbr/architecture/whiterun/wrwoodplank01.dds

If the texture set name is changed, the exporter keeps the vanilla match directory but uses the texture set name as the PBR file base name. The PGPatcher JSON uses rename when possible, and falls back to explicit slotN paths when needed.

8.2 Export rules

  • Each assigned texture slot is exported to DDS.
  • Source DDS files whose format and mipmap count already match the target compression mode are copied as-is without re-encoding (copy-through optimization).
  • Other source DDS files are decoded and re-encoded using the selected export compression.
  • Raster sources are loaded and encoded to DDS.
  • Alpha mode is detected during import and influences compression availability: BC1 is only offered when alpha is None or Opaque. If a texture with real alpha data is assigned to a BC1-configured slot, the exporter falls back to BC7.
  • If rmaosSourceMode is SeparateChannels, the assigned RMAOS slot texture is ignored and a new _rmaos.dds is generated from channels.
  • If required source files are missing, export continues and reports failures through logging and return status.

8.3 Default compression policy

Current defaults from the code:

Slot Default Compression
Diffuse BC7 sRGB
Subsurface BC7 sRGB
Fuzz BC7 sRGB
CoatColor BC7 sRGB
Emissive BC6H UF16
Displacement BC4 Linear
Normal BC7 Linear
RMAOS BC7 Linear
CoatNormalRoughness BC7 Linear

9. UI Architecture

9.1 Main window

MainWindow coordinates:

  • File menu actions: new, open, recent projects, save, export
  • Current project state
  • Current texture set selection
  • Refresh of editor and preview panels

9.2 Panels

Current UI composition:

  • TextureSetPanel: list of texture sets with add/rename/duplicate/remove actions; shows [+Landscape] badge for sets with landscape EDIDs and [+N alias(es)] badge for sets with match aliases. Duplicate creates a full copy of the selected set with " (Copy)" appended to the name.
  • SlotEditorWidget: match path, match mode, slot imports, RMAOS mode, split-channel rows, compression selectors, landscape EDID editor, match alias editor (one vanilla path per line)
  • FeatureTogglePanel: feature checkboxes
  • ParameterPanel: parameter editors grouped by feature; color rows include a per-row toggle button to switch between float (0.0-1.0) and integer (0-255) display
  • TexturePreviewWidget: basic image display with wheel zoom and drag pan
  • DropZoneLabel: custom widget for drag-and-drop with thumbnail preview, click-to-browse, and DDS thumbnail loading

Note: ExportDialog exists in the codebase but is currently unused; MainWindow uses an inline export folder row instead. ImportDialog is a thin wrapper over QFileDialog::getOpenFileName.

9.3 Preview

The preview area is a QStackedWidget toggled via 2D/3D buttons.

2D Mode (default):

  1. Show the current set's diffuse texture if present.
  2. Otherwise show the normal map if present.
  3. Otherwise clear the preview.
  4. Channel isolation (R/G/B/A) available via buttons when a texture is shown.
  5. Click any slot's DropZone to preview that specific texture.

3D Mode:

  • MaterialPreviewWidget wraps the D3D12 renderer in a Qt widget.
  • Loads Diffuse, Normal, RMAOS, and feature textures (Emissive, Coat/Fuzz, Subsurface/CoatColor).
  • RMAOS composed from split channels when in SeparateChannels mode.
  • Input: left-drag orbit, right-drag rotate light, middle-drag rotate HDRI, scroll zoom.
  • Shape selector: Sphere, Plane, Cube, Rounded Cube.

3D Control Bar (visible only in 3D mode):

  • Light intensity slider (0–10) and color picker button
  • Exposure slider (EV compensation)
  • HDRI selector combo (scans a folder for .exr/.hdr/.dds files)
  • IBL intensity slider, prefilter resolution combo, sample count combo
  • Render flag checkboxes: Horizon Occlusion, Multi-Bounce AO, Specular Occlusion
  • Mip Bias slider (-1.0 to 0.0, default -1.0)
  • VSync, TAA, and HDR checkboxes
  • Paper-white and peak-brightness sliders (HDR mode only)
  • Debug visualization combo: Full Shading, Normal, Roughness, Metallic, AO, Specular
  • Shape selector: Sphere, Plane, Cube, Rounded Cube

9.4 D3D12 Renderer

D3D12Renderer provides the GPU backend:

  • Double-buffered frame management with per-frame command allocators and fence values
  • Dedicated async copy queue (D3D12UploadQueue) with 64 MB ring buffer for texture uploads
  • Full mip chain generation on CPU (box filter) with per-level upload via copy queue
  • DescriptorHeap helper for linear SRV/CBV/UAV and RTV/DSV allocation
  • Cook-Torrance PBR pipeline state with precompiled vertex and pixel shaders
  • Skybox pipeline for HDRI environment background
  • GT7 tone-mapping post-process pass (bypassed for debug visualization modes)
  • TAA resolve compute pass with velocity reprojection
  • HDR (scRGB) and SDR swap chain modes
  • GPU adapter selection preferring discrete GPUs
  • Device-lost detection (checkDeviceLost(), isDeviceLost())

9.5 IBL Pipeline

IBLPipeline orchestrates GPU-based image-based lighting:

  1. Load HDRI file (EXR/HDR/DDS) via IBLPipeline::loadHDRI() with color space detection
  2. Convert pixels to ACEScg working space
  3. Run 4 GPU compute passes:
    • Equirect → Cubemap (IBLEquirectToCube.hlsl)
    • ZH3 Diffuse Irradiance (IBLDiffuseIrradiance.hlsl)
    • GGX Specular Prefilter (IBLPrefilter.hlsl) with configurable mip chain
    • BRDF Integration LUT (IBLBrdfLut.hlsl)
  4. Results: ZH3 irradiance coefficients, prefiltered cubemap, intermediate cubemap for skybox, BRDF LUT

CPU fallback (computeZH3CPU()) available when GPU processing is not possible.

9.6 Shader files

File Type Purpose
PBRShader.hlsl VS+PS Cook-Torrance PBR with IBL, feature textures, TAA velocity
SkyboxShader.hlsl VS+PS Fullscreen HDRI background with Y-axis rotation
ToneMapShader.hlsl VS+PS GT7 tone mapping, exposure, SDR/HDR output
TAAResolve.hlsl CS Temporal resolve with history reprojection
IBLEquirectToCube.hlsl CS Equirectangular to 6-face cubemap
IBLCubemapMipGen.hlsl CS Wide 9-tap cubemap mip generation
IBLDiffuseIrradiance.hlsl CS ZH3 projection for diffuse irradiance
IBLPrefilter.hlsl CS GGX importance-sampled specular prefilter
IBLBrdfLut.hlsl CS Split-sum BRDF integration LUT

Shared HLSL includes under Common/: Math, BRDF, Shading, PBRMath, PBR, Random, ColorSpaces, GT7ToneMap, Glints2023.

10. Source Tree

TruePBR-Manager/
├── CMakeLists.txt
├── CMakePresets.json
├── build.bat
├── builddebug.bat
├── buildrelease.bat
├── README.md
├── SPEC.md
├── resources/
├── translations/   JSON translation files (en.json, zh_CN.json)
├── tests/          Unit tests (Google Test)
└── src/
    ├── app/
    ├── core/
    ├── renderer/
    ├── ui/
    ├── third_party/
    └── utils/

Important implementation modules:

  • core/Project.*: project serialization and CRUD
  • core/PBRTextureSet.*: enums, slot metadata, compression metadata
  • core/TextureImporter.*: import metadata inspection
  • core/ChannelPacker.*: split-channel RMAOS generation
  • core/JsonExporter.*: PGPatcher JSON generation
  • core/ModExporter.*: DDS and JSON export orchestration
  • core/ModImporter.*: import existing PBR mod directories (PGPatcher JSON + textures)
  • core/VanillaConverter.*: vanilla Blinn-Phong to True PBR conversion pipeline (math, RMAOS synthesis, DDS export)
  • core/LandscapeExporter.*: Landscape TXST JSON generation
  • core/TextureSetValidator.*: pre-export validation checks
  • core/TranslationManager.*: JSON-based i18n, locale detection, hot-reload
  • core/AppSettings.*: persistent application settings (INI format via QSettings)
  • renderer/D3D12Renderer.*: D3D12 GPU backend, double-buffered rendering
  • renderer/D3D12UploadQueue.*: async texture upload via copy queue
  • renderer/DescriptorHeap.*: descriptor heap allocation helper
  • renderer/IBLPipeline.*: GPU IBL compute orchestration
  • renderer/MeshGenerator.*: procedural mesh generation (Sphere, Plane, Cube, RoundedCube)
  • ui/MaterialPreviewWidget.*: D3D12-based 3D preview Qt widget
  • ui/SlotEditorWidget.*: slot/channel authoring UI
  • ui/ParameterPanel.*: numeric material parameter UI
  • ui/VanillaConversionDialog.*: vanilla texture conversion dialog with integrated flow layout and real-time output previews
  • utils/TextureCache.*: singleton RGBA8 pixel cache, keyed by canonical path + write time; shared across thumbnails, 2D/3D preview, and import; thread-safe
  • utils/FileAssociation.*: Windows shell integration for .tpbr file extension registration (HKCU registry, no elevation required)

11. Build and Packaging

11.1 Prerequisites

  • Visual Studio 2022 or newer with Desktop C++ workload
  • CMake 3.21 or newer
  • vcpkg with VCPKG_ROOT configured

11.2 Supported build entry points

build.bat:

  • Accepts debug or release argument (defaults to debug)
  • Delegates to builddebug.bat or buildrelease.bat
  • Validates VCPKG_ROOT
  • Locates Visual Studio via vswhere when needed
  • Uses Ninja when available, otherwise falls back to NMake Makefiles
  • Configures the build in build/
  • Auto-cleans CMake cache when platform triplet changes

CMake presets:

  • default configure preset for Release
  • debug configure preset for Debug
  • test configure preset for Debug with unit tests (-DTRUEPBR_BUILD_TESTS=ON)
  • release, debug, and test build presets

11.3 Unit tests

  • Optional, enabled via -DTRUEPBR_BUILD_TESTS=ON or the test CMake preset
  • TruePBR-Core static library extracts testable core logic (Project, PBRTextureSet, TextureSetValidator, JsonExporter, LandscapeExporter) with no Qt or D3D12 dependency
  • TruePBR-Tests executable links TruePBR-Core + Google Test
  • Test suites: PBRTextureSet free functions, TextureSetValidator rules, Project save/load round-trip, VanillaConverter math (sRGB conversion, shininess-to-roughness, gamma/brightness, per-texture params)
  • Run: cmake --preset test && cmake --build build --config Debug --target TruePBR-Tests && ctest --preset test

11.4 Shader compilation

  • HLSL shaders are precompiled to .cso files during build via dxc.exe
  • Compiled shader objects are copied to the output directory alongside the executable
  • Shader source files live in src/renderer/ with shared includes in src/renderer/Common/

11.5 Packaged output

The CMake target currently places the runtime package in:

dist/TruePBR-Manager/

Post-build steps currently do the following:

  • Copy runtime DLLs next to the executable
  • Run windeployqt when available, otherwise copy qwindows.dll
  • Copy LICENSE

12. Dependencies

Library Purpose Source
Qt 6 Widgets UI framework vcpkg (qtbase)
nlohmann/json JSON serialization vcpkg (nlohmann-json)
spdlog Logging vcpkg (spdlog)
DirectXTex DDS metadata, decode, encode, compression vcpkg (directxtex)
stb_image Raster image loading vcpkg (stb)
Google Test Unit testing framework vcpkg (gtest), optional
tinyexr EXR image loading for HDRI Vendored (v1.0.9, header-only)
D3D12 / DXGI GPU rendering (3D preview) Windows SDK (system)
d3dcompiler HLSL shader compilation fallback Windows SDK (system)

13. CI/CD

13.1 GitHub Actions

Two workflows are configured:

  • ci.yml: runs on PRs and pushes to main. Performs pre-commit checks (prek), then builds on windows-latest with MSVC and uploads dist/TruePBR-Manager as an artifact.
  • release.yml: triggers on tags matching v*. Builds, zips dist/TruePBR-Manager, and uploads to GitHub Releases with auto-generated release notes.

13.2 Code formatting

  • .clang-format based on LLVM style with IndentWidth 4, ColumnLimit 120, Allman braces.
  • .pre-commit-config.yaml enforces clang-format on src/ plus standard text hygiene hooks.

14. Conventions

  • Types use PascalCase.
  • Functions and variables use camelCase.
  • Public headers use #pragma once.
  • Public APIs are documented with Doxygen-style comments in headers.
  • Core logic is kept separate from Qt UI widgets.

15. Versioning

This project uses Semantic Versioning 2.0.0:

MAJOR.MINOR.PATCH
  • MAJOR: incompatible changes to the project file format (.tpbr) or export output that would break existing saved projects or exported mods. Remains 0 during initial development (pre-1.0), where any release may contain breaking changes.
  • MINOR: new user-facing features or significant enhancements (e.g. new preview modes, new export capabilities, new UI panels). Increment when a release adds functionality.
  • PATCH: bug fixes, performance improvements, documentation updates, and other changes that do not add new features.

The version is defined in CMakeLists.txt via project(TruePBR-Manager VERSION X.Y.Z) and embedded into the binary at build time through Version.h.in.

Git tags follow the format vX.Y.Z (e.g. v0.2.0). The release.yml GitHub Actions workflow triggers on v* tags to build and publish release artifacts.

16. Roadmap

Planned features not yet implemented:

  • Built-in vanilla texture set conversion (内置 vanilla texture set 转换)
  • Import existing PBR mod: read a mod directory containing PGPatcher JSON and textures, reconstruct a .tpbr project automatically (导入已有 PBR Mod:读取含 JSON 和贴图的 Mod 目录,自动重建项目)
  • File association & shell integration: register .tpbr as default open tool, drag-to-open, command-line open, project switch confirmation (文件关联和 Shell 集成:注册 .tpbr 为默认打开工具、拖拽打开、命令行打开、项目切换确认)
  • Undo/redo