This document describes the repository as it exists now. It is implementation-oriented, not a forward-looking design draft.
| 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 |
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:
- Manage multiple PBR texture sets in a single project.
- Map each set to one vanilla diffuse path.
- Import required and optional textures into True PBR slots.
- Support RMAOS authoring either as a pre-packed texture or as split Roughness, Metallic, AO, and Specular sources.
- Edit feature flags and material parameters.
- Export textures as DDS into a mod folder and generate a PGPatcher JSON file.
Primary references:
- True PBR spec: https://github.com/doodlum/skyrim-community-shaders/wiki/True-PBR
- PGPatcher Mod Authors Guide: https://github.com/hakasapl/PGPatcher/wiki/Mod-Authors
- 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
.tpbrfile path as the first argument opens that project on launch (enables Windows shell "open" verb from file association) - Drag-and-drop open:
.tpbrfiles 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
- 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) viaGetMetadataFromDDSFile— 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(viaImageUtils::loadImage) for format coverage (TGA, PNG, BMP, JPG) with post-load downscale to thumbnail size TextureCachesingleton 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
- 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
- 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
- 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)
- DDS export for each assigned slot
- Split-channel packing into
_rmaos.ddsduring export - Per-slot export size override (power-of-two downscale or original)
- Per-slot path override for custom PGPatcher
slotNpaths - PGPatcher JSON export to
PBRNIFPatcher/<project>.json - Automatic export directory creation under
textures/pbr/... - Pre-export validation with error/warning reporting
- 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
_sis unified as Specular (channel), not Subsurface; use_sss/_sk/_subsurfacefor the Subsurface slot
- 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
_rmaosfile 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 |
- 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}, ...]}
- Flat array:
- 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(withtexture,color,weight) andglint(withscreen_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.
- Matching fields:
- 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
renameis present, tries both the renamed stem and the original match stem for texture lookup - When
renamecontains 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, andmatchTextureis set to the effective PBR path. - When
renameis 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
renametarget are merged into a single texture set with the additional vanilla paths stored as match aliases. The primary entry is the one whosematchTexturestem matches the texture set name. - Entries with
delete: trueare 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...
- 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
- Any texture set can optionally have one or more Landscape TXST EDIDs
- When EDIDs are present, the exporter generates
PBRTextureSets/<edid>.jsonper 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
- JSON-based translation system via
TranslationManagersingleton andJsonTranslator(subclass ofQTranslator) - Translation files stored in
translations/directory next to the executable (source-tree fallback for development builds) - Each JSON file declares
locale,name, and atranslationsmap 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_HK→zh_CN) - Runtime language switching from the menu bar; triggers
QEvent::LanguageChangeto retranslate all widgets - All translatable UI widgets implement
retranslateUi()andchangeEvent()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>.jsonfile into thetranslations/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.
AppSettings(singleton) persists project-independent preferences viaQSettingsin INI format- Settings file:
TruePBR-Manager.ininext 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 sizeMainWindow/state: toolbar and dock widget layoutPaths/lastProjectDir: last directory used for open/save project dialogsPaths/lastExportPath: last used export folderPaths/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
- 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
- No undo/redo
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) andspecularLevelis 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.
- ProgId:
TruePBR.Projectregistered underHKCU\Software\Classes - Registry scope: per-user (HKCU), no admin elevation required
- Open verb:
shell\open\commandset 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/fileAssocRegisteredin AppSettings), aQMessageBox::questionasks 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.tpbrfile path, it is opened automatically after the main window is shown
| 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.
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
Implemented enums in the model layer:
PBRTextureSlot(9 values)ChannelMap(4 values)RMAOSSourceModewithPackedTextureandSeparateChannelsTextureMatchModewithAuto,Diffuse, andNormalDDSCompressionModewith BC7, BC6H, BC5, BC4, BC3, BC1, and RGBA8 variants (10 total, both sRGB and Linear where applicable)TextureAlphaModewithUnknown,None,Opaque, andTransparent
TextureEntry stores:
sourcePathslotwidthheightchannelsalphaMode(detected during import: Unknown, None, Opaque, Transparent)format
ChannelMapEntry stores the same metadata except for slot and alphaMode.
The current project model stores these flags:
emissiveparallaxsubsurfacesubsurfaceFoliagemultilayercoatDiffusecoatParallaxcoatNormalfuzzglinthair
The current parameter model stores:
specularLevelroughnessScaledisplacementScalesubsurfaceOpacitysubsurfaceColoremissiveScalecoatStrengthcoatRoughnesscoatSpecularLevelfuzzColorfuzzWeightglintScreenSpaceScaleglintLogMicrofacetDensityglintMicrofacetRoughnessglintDensityRandomizationvertexColorsvertexColorLumMultvertexColorSatMult
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 amatchTextureandmatchMode. - 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
slotNexport paths) - Active RMAOS source mode
- Split channel map entries
- Feature flags
- Parameters
- Landscape TXST EDIDs (optional, one JSON per EDID)
- Tags and notes
Project currently contains:
nameoutputModFoldertextureSets
It also implements:
addTextureSetremoveTextureSetsaveload
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
featuresparamsrmaos_source_modetexturesexport_compressionchannel_maps
Compression overrides are stored using stable keys such as:
bc7_srgbbc7_linearbc3_srgbbc6h_uf16bc5_linearbc4_linearbc1_srgbbc1_linearrgba8_srgbrgba8_linear
RMAOS source mode uses:
packedsplit
Match texture mode uses:
autodiffusenormal
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_scalewhen emissive is enabledemissive_colorwhen emissive is enabled (RGBA array[R, G, B, A]per PGPatcher spec; alpha defaults to 1.0)match_normalwhen a set is configured to match vanilla normal instead of diffuserenamewhen 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
matchTextureas the match field and adds arenamepointing back to the primarymatchTexturepath, so PGPatcher maps multiple vanilla textures to the same PBR texture set - Explicit
slotNpaths 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_cnrwhen corresponding slots have no exported texture- Coat fields when multilayer or coat normal is enabled
fuzzobject when fuzz is enabledglintobject when glint is enabledhairwhen hair is enabled- Vertex-color override fields when they differ from defaults
- All float values are rounded to 3 decimal places
<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.
- 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
rmaosSourceModeisSeparateChannels, the assigned RMAOS slot texture is ignored and a new_rmaos.ddsis generated from channels. - If required source files are missing, export continues and reports failures through logging and return status.
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 |
MainWindow coordinates:
- File menu actions: new, open, recent projects, save, export
- Current project state
- Current texture set selection
- Refresh of editor and preview 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 checkboxesParameterPanel: 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) displayTexturePreviewWidget: basic image display with wheel zoom and drag panDropZoneLabel: 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.
The preview area is a QStackedWidget toggled via 2D/3D buttons.
2D Mode (default):
- Show the current set's diffuse texture if present.
- Otherwise show the normal map if present.
- Otherwise clear the preview.
- Channel isolation (R/G/B/A) available via buttons when a texture is shown.
- Click any slot's DropZone to preview that specific texture.
3D Mode:
MaterialPreviewWidgetwraps 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
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
DescriptorHeaphelper 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())
IBLPipeline orchestrates GPU-based image-based lighting:
- Load HDRI file (EXR/HDR/DDS) via
IBLPipeline::loadHDRI()with color space detection - Convert pixels to ACEScg working space
- 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)
- Equirect → Cubemap (
- Results: ZH3 irradiance coefficients, prefiltered cubemap, intermediate cubemap for skybox, BRDF LUT
CPU fallback (computeZH3CPU()) available when GPU processing is not possible.
| 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.
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 CRUDcore/PBRTextureSet.*: enums, slot metadata, compression metadatacore/TextureImporter.*: import metadata inspectioncore/ChannelPacker.*: split-channel RMAOS generationcore/JsonExporter.*: PGPatcher JSON generationcore/ModExporter.*: DDS and JSON export orchestrationcore/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 generationcore/TextureSetValidator.*: pre-export validation checkscore/TranslationManager.*: JSON-based i18n, locale detection, hot-reloadcore/AppSettings.*: persistent application settings (INI format via QSettings)renderer/D3D12Renderer.*: D3D12 GPU backend, double-buffered renderingrenderer/D3D12UploadQueue.*: async texture upload via copy queuerenderer/DescriptorHeap.*: descriptor heap allocation helperrenderer/IBLPipeline.*: GPU IBL compute orchestrationrenderer/MeshGenerator.*: procedural mesh generation (Sphere, Plane, Cube, RoundedCube)ui/MaterialPreviewWidget.*: D3D12-based 3D preview Qt widgetui/SlotEditorWidget.*: slot/channel authoring UIui/ParameterPanel.*: numeric material parameter UIui/VanillaConversionDialog.*: vanilla texture conversion dialog with integrated flow layout and real-time output previewsutils/TextureCache.*: singleton RGBA8 pixel cache, keyed by canonical path + write time; shared across thumbnails, 2D/3D preview, and import; thread-safeutils/FileAssociation.*: Windows shell integration for.tpbrfile extension registration (HKCU registry, no elevation required)
- Visual Studio 2022 or newer with Desktop C++ workload
- CMake 3.21 or newer
- vcpkg with
VCPKG_ROOTconfigured
build.bat:
- Accepts
debugorreleaseargument (defaults todebug) - Delegates to
builddebug.batorbuildrelease.bat - Validates
VCPKG_ROOT - Locates Visual Studio via
vswherewhen 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:
defaultconfigure preset for Releasedebugconfigure preset for Debugtestconfigure preset for Debug with unit tests (-DTRUEPBR_BUILD_TESTS=ON)release,debug, andtestbuild presets
- Optional, enabled via
-DTRUEPBR_BUILD_TESTS=ONor thetestCMake preset TruePBR-Corestatic library extracts testable core logic (Project, PBRTextureSet, TextureSetValidator, JsonExporter, LandscapeExporter) with no Qt or D3D12 dependencyTruePBR-Testsexecutable linksTruePBR-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
- HLSL shaders are precompiled to
.csofiles during build viadxc.exe - Compiled shader objects are copied to the output directory alongside the executable
- Shader source files live in
src/renderer/with shared includes insrc/renderer/Common/
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
windeployqtwhen available, otherwise copyqwindows.dll - Copy
LICENSE
| 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) |
Two workflows are configured:
ci.yml: runs on PRs and pushes tomain. Performs pre-commit checks (prek), then builds onwindows-latestwith MSVC and uploadsdist/TruePBR-Manageras an artifact.release.yml: triggers on tags matchingv*. Builds, zipsdist/TruePBR-Manager, and uploads to GitHub Releases with auto-generated release notes.
.clang-formatbased on LLVM style with IndentWidth 4, ColumnLimit 120, Allman braces..pre-commit-config.yamlenforces clang-format onsrc/plus standard text hygiene hooks.
- 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.
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. Remains0during 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.
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
.tpbrproject automatically (导入已有 PBR Mod:读取含 JSON 和贴图的 Mod 目录,自动重建项目) - File association & shell integration: register
.tpbras default open tool, drag-to-open, command-line open, project switch confirmation (文件关联和 Shell 集成:注册 .tpbr 为默认打开工具、拖拽打开、命令行打开、项目切换确认) - Undo/redo