KasugaLib PMX → PBR Conversion¶
Interfaces, configuration, and call reference. For the mechanics see pbr.md.
This page covers KasugaLib's current PMX stylized PBR conversion pipeline, the implementation changes in this round, observability and troubleshooting, and how to customize it with the player configuration and the Java API.
1. How It Works¶
PMX source materials mainly provide a base texture, a diffuse color, a specular color, and a specular strength; they do not directly contain complete PBR textures. KasugaLib performs one pre-bake during the client resource load phase:
- Read the numeric properties of the PMX material and generate automatic conversion parameters.
- Apply the Java registration rules and the player JSON rules in order.
- Generate a normal texture from the luminance gradient of the base texture.
- Generate a specular texture that matches the current LabPBR/Iris path.
- Pack the base color, normal, and specular into three corresponding atlases.
- Write the result to the disk cache; later resource reloads reuse it first.
Automatic parameters¶
smoothness: mapped from the PMXshininess.f0_code: generated from the average of the PMX specular color; the automatic value is limited to 4–90, and no metal is guessed automatically.subsurface: default 0.normal_strength: default 0.08.emission: default 0.
Output channels¶
Normal texture:
| Channel | Content |
|---|---|
| R | Encoded tangent-space X |
| G | Encoded tangent-space Y |
| B | AO, currently 255 |
| A | Height, currently 255, that is, parallax height is not enabled |
Specular texture:
| Channel | Content |
|---|---|
| R | Perceptual smoothness |
| G | Dielectric F0 or the LabPBR metal code |
| B | Porosity/SSS; non-zero SSS maps to 65–255 |
| A | Emission |
Normal sampling uses repeat/wrap at texture edges, matching the repeatable-UV behavior of PMX.
2. Changes in This Round¶
Independent material variants¶
The atlas identity is decided jointly by "source texture + final PBR parameters":
- Same source texture, same parameters: reuse the same variant.
- Same source texture, different parameters: generate an independent variant and independent normal/specular sprites.
- If the same atlas identity is bound to a different source image or different parameters again: throw a conflict error immediately, instead of averaging silently.
GPU, CPU, and caching¶
- Prefer generating both the normal and specular outputs in a single pass on the render thread through an offscreen framebuffer and MRT.
- When the GPU shader is unavailable, this session switches automatically to the deterministic CPU reference baker.
- The GLSL of the GPU baker lives in
assets/kasuga_lib/shaders/pbr/stylized_bake.vshandstylized_bake.fsh. - The GPU baker explicitly saves, clears, and restores
GL_PIXEL_PACK_BUFFER,GL_PIXEL_UNPACK_BUFFER, and the pack/unpack row-length/skip state.
Load performance¶
- Fixed the linear
indexOflookup in PMX vertex deduplication, replacing the near-O(n²) path of large models with a hash index. - Unused copies of base textures are no longer put into the PBR atlas repeatedly.
- The PBR disk cache is prefetched in parallel by the resource load threads; only misses go back to the render thread for GPU baking.
- The content hash of the same source image and profile is reused within a single load.
BufferedImageis converted toNativeImagedirectly, instead of being encoded to an in-memory PNG and decoded again.- The CPU fallback runs at most two bakes at a time, so it does not occupy all resource load threads.
- In-world resource reload uses generation-based publishing: new models are built in the background first, and are atomically swapped on the game thread only after the atlas and the sprite mappings are all ready; Kasuga model drawing is paused during the reload, and the old backend instances of the replaced models are cleaned up.
3. Observability¶
Startup indicator¶
Minecraft's resource loading screen additionally shows KasugaLib's current stage and progress, including:
- scanning model resources;
- the current model resource;
- the current PMX name and the PMX count inside the ZIP;
- reading PBR cache entries;
- the number of GPU bakes for cache misses;
- PBR atlas packing.
Batch log¶
After each PBR preparation finishes, one summary log line is emitted, for example:
Prepared 91 stylized PBR variants from 74 source textures (59.4 MP, 17 shared-source variants): GPU 0, CPU 0, disk hits 91, failures 0, elapsed 880.3 ms
Field meanings:
| Field | Meaning |
|---|---|
| variants | Number of configured variants in the final atlas |
| source textures | Number of distinct source images |
| MP | Total processed pixels, counted per variant |
| shared-source variants | Number of variants added because the same source image uses different parameters |
| GPU | Number of items newly generated on the GPU in this batch |
| CPU | Number of items newly generated by the CPU fallback in this batch |
| disk hits | Number of disk cache hits in this batch |
| failures | Number of items that failed in this batch and used the default PBR map |
| elapsed | Total time of cache prefetch and baking of misses |
A first load usually shows a GPU count; a warm-cache load usually shows GPU 0, CPU 0 and a high disk hits.
Client commands¶
/kasuga_pbr status
/kasuga_pbr reload_config
/kasuga_pbr rebake
status prints:
ready,failed,total: status counts of cache keys within this session;GPU,CPU: number of items actually newly baked;disk-cache: number of disk cache hits;memory-cache: number of in-memory result hits within the same resource load;requests: total number of requests the coordinator received, including cache prefetch and atlas consumption;key: cumulative time of generating content cache keys;read: cumulative time of decoding disk PNGs;GPU-time,CPU-time: cumulative time of the corresponding baker;write: cumulative time of writing to the disk cache.
reload_config reloads the player configuration and triggers a resource reload. rebake deletes the whole Kasuga PBR disk cache, clears the session statistics, and triggers a full resource reload.
When either command runs inside a world, Kasuga models are temporarily hidden during the resource reload. New models are not published before the atlas is ready, so the render thread never observes a missing sprite; after the reload finishes, backend instances are recreated with the new configuration. On every platform, macOS included, cache misses uniformly prefer the GPU baker; only when the GPU shader or a GL call fails does it fall back to the CPU.
Troubleshooting¶
- No change after editing parameters: run
/kasuga_pbr reload_config. Parameters take part in the cache key, so clearing the cache by hand is usually unnecessary. - Suspected corrupt old cache: run
/kasuga_pbr rebake. GPU=0: ifdisk-cacheis high, this is a normal cache hit; if a GPU unavailable log appears, check for OpenGL/shader errors, as the system will use the CPU fallback.failures > 0: search the log forFailed to bake stylized PBR texture; the log entry contains the cache key and the exception.- Atlas too large: compare
variantswithsource textures. A large difference usually means many materials use different configurations. - Startup still slow: use the indicator to tell whether it is stuck on PMX, cache reading, or atlas, and compare the staged timings in the batch log and
/kasuga_pbr status.
4. Player JSON Customization¶
The configuration file is located at:
<game directory>/config/kasuga-pbr.json
The file is generated automatically when it does not exist. Base structure:
{
"enabled": true,
"defaults": {},
"rules": []
}
enabled: false: disables the player configuration layer; PMX automatic conversion and other Java rules are still kept.defaults: conversion parameters that override all PMX materials.rules: applied top to bottom in array order; a later matching rule can override the earlier result.- A rule only needs the fields it wants to match and override.
Match fields¶
| Field | Type | Description |
|---|---|---|
enabled |
boolean | Set to false to temporarily disable a single rule |
model |
string glob | Full model ResourceLocation |
texture |
string glob | Original texture ResourceLocation, not the hashed variant ID |
local_name |
string glob | PMX local material name |
english_name |
string glob | PMX English material name |
material_index |
integer | PMX material index, starting from 0 |
min_shininess |
number | Lower bound of PMX shininess, inclusive |
max_shininess |
number | Upper bound of PMX shininess, inclusive |
String matching is case-insensitive and supports * and ?. Omitting a match field means that field is unconstrained.
Material names and texture names can serve as auxiliary filter conditions, but using them as the only basis for classifying materials is not recommended. For a specific model, prefer model + material_index; for cross-model rules, prefer PMX numeric ranges or the structured context in the Java API.
Output fields¶
| Field | Range | Description |
|---|---|---|
smoothness |
0–1 | Perceptual smoothness |
f0_code |
0–255 | LabPBR specular G channel code |
subsurface |
0–1 | Subsurface scattering strength |
normal_strength |
0–1 | Strength of the normal derived from the base color luminance |
emission |
0–1 | Emission strength |
Out-of-range values are clamped automatically by PbrConversionSettings.
LabPBR predefined metal codes:
| Metal | f0_code |
|---|---|
| Iron | 230 |
| Gold | 231 |
| Aluminum | 232 |
| Chrome | 233 |
| Copper | 234 |
| Lead | 235 |
| Platinum | 236 |
| Silver | 237 |
| Use albedo as the metal color | 255 |
0–229 is used for dielectric F0. Automatic conversion does not produce metal codes above 230.
Configuration example¶
The example below applies stylization to the material indices of one specific model:
{
"enabled": true,
"defaults": {
"normal_strength": 0.06
},
"rules": [
{
"model": "kasuga_lib:models/pmx/test2.mmd.zip/unfading_flowers_miku_black.pmx",
"material_index": 12,
"smoothness": 0.72,
"f0_code": 230,
"normal_strength": 0.035
},
{
"model": "kasuga_lib:models/pmx/test2.mmd.zip/unfading_flowers_miku_black.pmx",
"material_index": 18,
"smoothness": 0.38,
"subsurface": 0.45,
"normal_strength": 0.025
},
{
"model": "kasuga_lib:models/pmx/*",
"min_shininess": 96,
"smoothness": 0.85
}
]
}
After saving, run:
/kasuga_pbr reload_config
5. Java API Customization¶
The public API lives in:
lib.kasuga.rendering.models.mc.api.pbr
Main types:
PbrConversionSettings: immutable final conversion parameters, clamped automatically at construction, withwith...methods.PbrMaterialContext: read-only snapshot of the current model, texture, material index, PMX names, colors, shininess, flags, and texture dimensions.PbrConversionRule: conversion function.PbrConversionRegistry: rule registration, unregistration, and ordering.PbrMetalPreset: LabPBR metal code enum.
Registering rules¶
import lib.kasuga.rendering.models.mc.api.pbr.PbrConversionRegistry;
import lib.kasuga.rendering.models.mc.api.pbr.PbrMetalPreset;
import net.minecraft.resources.ResourceLocation;
PbrConversionRegistry.Registration registration = PbrConversionRegistry.register(
ResourceLocation.fromNamespaceAndPath("example_mod", "character_materials"),
500,
(context, current) -> {
if (!context.modelId().getNamespace().equals("example_mod")) {
return current;
}
if (context.materialIndex() == 4) {
return current
.withMetal(PbrMetalPreset.SILVER)
.withSmoothness(0.82f)
.withNormalStrength(0.04f);
}
if (context.shininess() < 8.0f && context.diffuseAlpha() < 1.0f) {
return current
.withSmoothness(0.3f)
.withSubsurface(0.2f);
}
return current;
}
);
When the rule is no longer needed, call:
registration.close();
You can also register again with the same ID; the old rule is removed first.
Priority and error behavior¶
- Rules run from the smallest
priorityto the largest. - Rules with the same priority run in string order of the rule ID.
- The player JSON configuration is registered at priority
1000. - To provide a base value before the player configuration, use a priority smaller than 1000.
- To force an override of the player configuration, use a priority greater than 1000; this reduces the player's ability to customize and should be used with care.
- When a rule returns
nullor throws a runtime exception, KasugaLib logs a warning and keeps the result of the previous rule, then continues.
PbrMaterialContext fields¶
Code rules can access:
modelId,textureId,materialIndex;localName,englishName,metadata;- diffuse RGBA;
- specular RGB;
- ambient RGB;
shininess;noCull,receivesShadow;textureWidth,textureHeight.
Prefer rules that use the stable combination of model ID, material index, and PMX numeric values. If names must be used, constrain the model ID as well and allow for fallback behavior when the model version changes.
6. Compatibility and Caveats¶
- Baking currently happens during the client resource load/reload phase and does not run every frame.
- The PBR atlas is matched to the Iris shader path; without Iris or a shader pack the base-color model is still usable, but whether the PBR channels produce a visual effect depends on the actual rendering path.
- Using many different configurations on the same base texture increases the atlas area and VRAM usage. Reuse identical parameter combinations as much as possible.
- When changing the channel semantics or the algorithm of a baker,
PbrBakeProfile.VERSIONmust be bumped so that old disk caches are invalidated. rebakedeletes the whole Kasuga PBR cache, which makes the next load expensive; use it only for troubleshooting or an algorithm upgrade.