The shadows in white knitwear, reflections on coated shoulder armor, skin transitions on the hands, bright sleeve edges, and broken highlights in the hair arise from different material and compositing mechanisms. Explaining the complete character requires separating surface properties, directional responses, geometric outlines, and later overlays before assessing each difference from the reference.
The previous article, Reconstructing Stylized Character Rendering — Part 1: A Hybrid Deferred–Forward Pipeline, established the execution environment: the scene remains deferred, characters retain their geometry data, and a dedicated CharacterForward pass supplies opaque color. This article follows that entry point into the materials, examining how cloth, visor, skin, eyes, and hair consume their inputs and which experiments help locate discrepancies.
The study establishes surface correspondence, checks actual sampling and coordinate spaces, then compares lighting components and composited results. Resource and draw records are needed to establish whether normal BA channels were preserved and which draw produced a highlight; color adjustments cannot answer either question.
This article describes a static character reconstruction study in UE 5.8. Numerical results and historical A/B images come from existing research records; the engine and RDC captures were not rerun for this article. Code illustrates responsibilities: public engine API names are retained, while project-specific parameters use neutral names. Excerpts marked as abbreviated omit context and are not complete patches.


Match the Surface Before Comparing Its Color
This article records source evidence, UE implementation, and visual compensation separately. All three descriptions can apply at once.
| Dimension | Usage in this article |
|---|---|
| Source evidence | RDC exact denotes bytes, constants, or instructions directly available in a capture; RDC inferred denotes responsibilities inferred from data dependencies. Unsupported details remain unconfirmed. |
| UE implementation | Distinguishes direct ports, necessary space or encoding conversions (UE transform), and current simplified implementations. A local conversion does not establish equivalence of the entire algorithm. |
| Visual compensation | Deliberate departures from source inputs or formulas are marked Visual compensation. Until all active branches are traced, finding no compensation does not establish its absence globally. |
Validation status is separate: asset configuration, live GPU binding, draw consumption, corresponding-pixel validation, and validation under dynamic conditions each require their own evidence. Asset settings do not establish live bindings, and single-frame pixels do not validate animation or dynamic lighting. Formula provenance below applies to the specified subchain, not to the material as a whole.
RDC denotes a RenderDoc capture file; RT, a render target; IBL, image-based lighting; and MID, a material instance dynamic. APV refers here to Adaptive Probe Volumes in the source pipeline, not an integrated UE system of that name. HN, RS, and ST are resource-semantic labels retained in this article for dual normals, the specular lookup, and stripe-control inputs. No English expansions are invented for them.
Identical screen coordinates in the source and UE do not guarantee the same surface. Hair cards overlap heavily, and a triangle projected over a pixel may fail the depth test. PixelHistory is therefore used first to identify the visible layer and subsequent overwrites, before matching geometry.
Establishing Geometric Correspondence with UV Triplets
The study exports PostVS vertices and indices and matches triangles by their three UV coordinates to locate the corresponding UE surface. A single UV point is insufficient: mirrored or overlapping UVs and degenerate triangles can all introduce ambiguity.
In one historically validated dataset, the source hair has 9,205 triangles and UE has 9,204. With the required V conversion and UV keys rounded to five decimal places, 9,204 source triangles match, including 7,598 unique matches. Without the V conversion, none match. This result is specific to the imported mesh’s UV relationship; other assets require separate checks.
After identifying a triangle, perspective-correct interpolation transfers the sample into UE, followed by a visibility check at the new pixel. The explanatory formula is:
PerspectiveCorrectAttribute = Σ(ScreenBarycentricWeight_i × Attribute_i / clipW_i)
/ Σ(ScreenBarycentricWeight_i / clipW_i)
Even with an exact triangle match, snapping to the nearest pixel center can produce a UV difference of roughly two texels at 2048². The records therefore distinguish nearby samples on the same surface from counterfactual calculations using identical UVs and sampling footprints. The former establishes image attribution; the latter isolates formula or resource differences.
Cross-checking the Debugger, Exported Resources, and Actual RTs
| Method | Question | Main limitation |
|---|---|---|
| Action / Pipeline enumeration | Which stage, shader, and RT are active? | Names may be absent; correlate pipeline state. |
| PixelHistory | Which draw passed depth and which overwrote existing color? | Some backend samples need cross-checks. |
| PostVS / Input export | Do skinned geometry, UVs, and tangents correspond? | Check stride, semantics, and instances. |
| DebugPixel | Which operands produce a color component? | Simulated sampling may differ from the GPU. |
| DebugThread | How does shadow compute produce occlusion counts and mappings? | Inputs are groups/threads, not bare screen coordinates. |
| DDS / RT export | What resources and outputs actually exist on the GPU? | Preserve format, quantization, mips, and slices. |
The following illustrates the existing tools’ call pattern; it is not a request to rerun old events:
controller->SetFrameEvent(eventId, true);
auto history = controller->PixelHistory(target, x, y, Subresource(), CompType::Typeless);
auto mesh = controller->GetPostVSData(0, 0, MeshDataStage::VSOut);
auto* trace = controller->DebugPixel(x, y, inputs);
// Call FreeTrace after reading the trace; finally close the controller, capture, and global replay.
The actual tools are described in replay_probe.cpp and the background capture/replay documentation. They are console programs and create no ReplayOutput, swap-chain viewer, or qrenderdoc window.
High-resolution screenshots also require checking the actual viewport. Historical captures contain both low-resolution and 1920×1080 draws; a large render target does not establish that a draw used the high-resolution viewport. Re-enumerate events and resources for each capture instead of reusing identifiers from another run.
Separate Resource Names from Their Actual Semantics

The table summarizes current evidence coverage by part. Completion is limited to this static study; the relevant sections discuss details and remaining errors.
| Part | Strongest current evidence | Still incomplete |
|---|---|---|
| Cloth | Packed conversion, Ramp endpoints, and screen-space shadow consumers | Overall environment response and controlled appearance experiments |
| Visor | Source Cube, complete mip chain, source normals, and hardware sampling | Final adoption of Base/Packed and overall reflection convergence |
| Face / Skin | SDF channels, LUT coordinate algorithm, and the two-color-endpoint dataflow | Active Skin LUT SRV and color-space checks, body bindings, and generalization of static compensation |
| Eye | MatCap / parallax paths in source documentation | Current Eye2 simplification and unobstructed multi-view validation |
| Hair | HN dual normals, RS, ST, object frame, and N1 counterfactual | Central highlight, shadows, cross-run geometry, animation, and dynamic lighting |
Compatibility Binding Names Can Carry Different Part Data
The current C++ resource resolver preserves historical aliases. For example, CharacterSkinShadowLUT can carry the HN dual-normal resource in a hair permutation, while CharacterSkinSDFMap can carry hair Packed rather than the facial SDF.
The code prioritizes more explicit new names:
const UTexture* LUTTexture = ResolveCharacterTexture({
TEXT("CharacterSourceSkinShadowLUT"),
TEXT("CharacterSourceHairSplitNormalMap"),
TEXT("CharacterSkinShadowLUT") });
The Packed resolver includes CharacterSourceFaceControlMap, CharacterSourceClothPackedMap, CharacterSourceHairPackedMap, and legacy aliases. The actual facial SDF has its own CharacterSourceFaceSDFLightmap / CharacterFaceSDFMap path.
This article uses semantic names such as HN, Packed, and RS to explain the algorithms and lists the current compatibility slots. A slot containing Skin in its name does not establish an incorrect Hair binding; the bound GPU resource determines that.
Tracing Material Slots to the Inputs of This Draw
The early design proposed one master material with per-part static switches. The current delivery uses multiple part-specific parents sharing one engine shading model. This still consumes one shading-model identity while allowing textures, sampler types, and Opaque/Masked requirements to be maintained separately at the asset level. The table comes from the latest recorded live_inputs.json, not filenames.
| Instance or group (neutral example names) | Latest recorded parent | Shader family and responsibility |
|---|---|---|
MI_Example_body_01 |
M_CharacterNPR_Skin2 |
Body skin; body in the name does not denote the Body shader variant |
MI_Example_face_01 |
M_CharacterNPR_Skin2 |
Facial Skin with a face control map and SDF |
MI_Example_cloth_01/02/03 |
M_CharacterNPR_Cloth1122 |
Capture-1122 resources and computation in the Body branch |
MI_Example_cloth_04 |
M_CharacterNPR_Body2 |
General Body parent; not every cloth slot can be assumed identical |
MI_Example_brow_01, MI_Example_iris_01 |
M_CharacterNPR_Eye2 |
Eye family; brow and iris parameters may differ |
| Runtime hair MID | MI_Example_hair_01 |
Final hair parameters are overridden by a dynamic instance; reading only the disk MI is insufficient |
A visor hiding the eyes does not mean their materials are absent. Sharing a parent between body and facial skin likewise does not imply identical SDF switches or color coefficients. Trace the component, section/material slot, final Material Interface, parent, parameter overrides, and compiled variant in that order. Guessing from names at any step can apply an otherwise plausible fix to the wrong part.
Format, Mips, and Samplers Jointly Define a Resource
| Resource use | Information to preserve | Symptoms of a mismatch |
|---|---|---|
| Albedo | sRGB setting, Alpha consumption, Tint order | Gray skin, whitened yellow armor, altered cutout contours |
| Packed material map | Linear sampling, all RGBA channels, actual channel semantics | Stronger specular, filled shadows, lost surface distinctions |
| Conventional normals | BC5/source format, XY decoding, Y conversion, mips, sampling bias | Shifted visor highlights, reversed cloth reflection directions |
| Hair HN | RGBA preservation, RG/BA direction pairs, Linear sampling | Incorrect highlight-ring curvature and variation |
| Diffuse/Spec Ramp | Original dimensions, Linear sampling, separate RGB and Alpha consumers | Simultaneous changes to hue and light/shadow gating |
| Skin color LUT | Flattened 32³ layout, coordinate color domain, slice interpolation | Pink facial color or tinted chin shadows |
| SDF direction field | Independent binding, local-light left/right sign, resource-specific V conversion | Reversed facial shadows or misplaced boundaries |
| HDR Cube | Face order, compressed blocks, all mips, direction mapping, sampler | Local visor response that roughness adjustments cannot correct |
For Cloth1122, the build records set the captured Ramps to sRGB=False, preserving linear channels and disabling automatic mip generation. These settings apply to the two lookup textures, not every character texture. Conversely, the visor normals and HDR Cube require their source mip chains.
Old export paths in scripts document historical provenance and should not be executed unchanged on another machine. Build scripts are cited to explain asset connections, not to request rebuilding or deleting validated materials.
Color domains must also agree with actual sampling settings:
| Data | Resource / sampling domain | Meaning in computation | Current evidence |
|---|---|---|---|
| Base Albedo | Color textures decoded under an sRGB convention | Linear base color multiplied by Tint | The path is documented; this does not establish SRV validation for every instance |
| Packed / HN / Ramp | Linear reads preserving required channels | Control data, directions, or lookup colors; control data is not a photometric quantity | Check each resource’s import and active-binding records |
| BC6H Cube | HDR linear resource | Environment radiance | Existing byte, mip, and hardware-sampling experiments |
| Skin LUT | Active SRV attributes unconfirmed | The excerpt manually calls sRGBToLinear |
Sampling contract remains unresolved |
| SceneColor | Current character output writes pre-exposed linear color | Handle PreExposure before scene-linear comparisons | Historical luminance reports state this processing; per-sample stages remain undocumented |
| Final PNG | Display-referred image | Appearance observation | Not used to recompute HDR terms; this article also includes data visualizations that must be read according to their captions |
Exporting GPU Resources Again to Verify Live Bindings
Correct import settings establish configuration intent. Further evidence must show that compilation, material instances, and upload still lead the GPU to sample the intended data. The study verifies source RS, environment Cube, and visor normals by exporting them from new captures: an asset name cannot guarantee contents, mips, or format.
Temporary material instances, stale references, and default fallbacks can prevent a file change from reaching an actual draw. Each reconstruction run therefore requires a check of live resources.
Cloth: Color Endpoints Matter More Than One Shading Factor
White knitwear needs both texture detail and broad stylized shading. Yellow shoulder armor, dark outerwear, and the metal visor have different material weights. The Body path separates surface properties, Ramp endpoint blending, direct specular, and environment reflection, consuming the relevant data per part and pixel.

Visible appearance categories do not map one-to-one to shader families. Several cloth instances share a Cloth parent, while another uses a Body parent; all enter the ordinary surface part. Exposed body skin enters Skin. White knitwear and dark regions may share an atlas, and one slot may serve multiple sections. Records establish slots, instances, and parents, but do not identify a visible region’s section from color or slot number alone.
Dark garment regions can come from textures, directional Ramps, geometric occlusion, or environment contributions. Tracing their consumers separately helps explain both the shading of fabric folds and bright edges on dark outerwear.

Appearance Correspondence for Knitwear and Dark Outerwear
Matching parts in the reference and UE first establishes the observation region. Base color, directional Ramp, occlusion, and display processing can all affect the knitwear’s highlights, folds, and shadows. The comparisons below frame the questions; Packed and Ramp consumers then explain the implementation.

View all original and location images in this group.
Dark outerwear and white knitwear may share the ordinary-surface family, but a single overall brightness ratio cannot assess both. The outerwear crop remains a separate region of interest. Its bright edges combine direct light, environment terms, and local lights and are not classified as outlines in advance.

View all original and location images in this group.
Direction and Space Conventions in the Formulas
These conventions apply to the excerpts in this article. Mathematical usage established by an expression is separated from variables whose upstream assignments are missing, rather than imposing ordinary PBR conventions on every part.
| Symbol | Expression and evidence scope |
|---|---|
N |
The Body reflection expression requires a normalized normal in the same space as V; the current chain is interpreted in world space. Tangent decoding and resource orientation require per-part checks. |
V |
In R = 2*dot(V,N)*N-V, interpreted as surface-to-camera, so R corresponds to reflect(-V,N). Not every upstream assignment was provided; this does not generalize to all identically named variables. |
L / HairLightDirection |
No article-wide sign convention is imposed: Face uses projected local-light components, while Hair uses an input before object-frame correction. Trace upstream signs per branch. |
H |
Body combines the environment axis, projected view, and V with weights; Hair sums the modified light direction and HairHeadView. Neither is directly replaced by ordinary normalize(L+V). |
Nramp, environmentAxis, projectedCamera |
Used in directional lookups or half-vector construction; both dot-product operands must share a space. Their full upstream construction is absent from the excerpts, so names alone cannot establish their spaces. |
HairHeadView |
Participates in projections against captured object axes and in half-vector construction. Upstream normalization, sign, and transform order require the complete shader; it cannot automatically be treated as a universal V. |
T/B |
Tangent/bitangent in the current hair import chain. Source and UE T/N are close, while B is reversed; the Y conversion in HN decoding addresses this difference. |
TangentToWorld |
mul(nTS, TangentToWorld) mathematically multiplies a vector on the left to transform tangent-space directions into world space. This does not establish row-major or column-major memory storage. |
HairUnityObjectCol0/1/2 |
Captured object-frame axes expressed in UE coordinates, used in current world-direction calculations; not taken directly from UE Actor Rotation. Their source and static limitations are discussed in the hair section. |
Convert Packed Channels into the Correct Surface Properties
The actual Cloth1122 connections illustrate the Body material contract. This family covers ordinary character surfaces such as garments and armor. Metallic behavior is determined by per-pixel material weights rather than one classification for the whole part.
| Current material input | Actual connection / consumer | Meaning |
|---|---|---|
BaseAlbedo.RGB × BaseTint |
Base Color | Linear base color after sRGB texture decoding |
BaseNormal |
Normal | UE normal-sampling output; still check whether coordinate conversion has already occurred |
ClothPackedMap.R |
Metallic | Authored continuous metallic weight, without arbitrary sharpening |
ClothPackedMap.G × 0.5 |
Specular | Converts the source dielectric F0 scale to the UE pin definition |
1 − ClothPackedMap.A |
Roughness | A stores smoothness, not roughness |
ClothPackedMap.B |
Ramp/shadow gating in the custom PS | Connecting it to AO alone does not implement its role |
CharacterRampAtlas |
Multiple lookups with separate RGB and A consumers | Color variation and blend weights |
CharacterHairSpecRamp compatibility slot |
Body specular lookup | Hair in the name does not establish a hair binding |
Source dielectric F0 is 0.04 × P.G; UE’s standard Specular maps to 0.08 × Specular, requiring Specular=0.5×P.G. Connecting G directly to Specular would double dielectric reflectance. The factor converts between the reflectance scales of the two pin definitions.
Excerpt from the existing material-build script:
mel.connect_material_property(packed, "R", unreal.MaterialProperty.MP_METALLIC)
roughness = mel.create_material_expression(
master, unreal.MaterialExpressionOneMinus, -900, 400)
mel.connect_material_expressions(packed, "A", roughness, "")
mel.connect_material_property(roughness, "", unreal.MaterialProperty.MP_ROUGHNESS)
half = mel.create_material_expression(
master, unreal.MaterialExpressionConstant, -900, 520)
half.set_editor_property("r", 0.5)
specular = mel.create_material_expression(
master, unreal.MaterialExpressionMultiply, -680, 500)
mel.connect_material_expressions(packed, "G", specular, "A")
mel.connect_material_expressions(half, "", specular, "B")
mel.connect_material_property(specular, "", unreal.MaterialProperty.MP_SPECULAR)
The script builds the material graph and supplies surface properties. The USF then computes lighting under the source pipeline’s rules. Both are needed for the complete character result.
Ramp Alpha, Control Maps, and Shadows Select Color Endpoints
Standard DefaultLit generally computes diffuse response from BaseColor, Metallic, and lighting. The cloth path also blends authored color endpoints: a full material-color endpoint, a darker endpoint, and an endpoint with expanded chroma, selected through Ramp Alpha and screen-space shadows. Fine texture and broad shading can thus be controlled separately, rather than turning every normal variation into a separate binary shadow.
Provenance: endpoint and gating relationships follow existing source-capture analysis; the code below is the current UE expression. 1.92 and 0.432 include conversion for upstream numerical scaling (half-value means roughly 0.5 times the value, not FP16 precision), so they cannot be labeled verbatim source constants. Ramp roles follow consumer tracing; independent recomputation has not covered every cloth pixel.
Luma() is the linear-RGB luminance helper in the shader excerpt and directly affects color computation. Its full definition was not included in the handoff; no weights are invented here. Reproducing the numbers requires the current helper source. This is separate evidence from the luminance definition in the historical measurement scripts below.
The following is an equivalent abbreviated form of the current CharacterForwardPass.usf, with shortened variable names; it is not a standalone compilable function. E is a Ramp lookup along the environment direction, Q is a lookup along another direction, P is Packed, and Sg is character-specific screen-space shadow visibility.
// Two distinct directional indices; do not replace both with the current main-light NdotL.
uE = saturate((dot(Nramp, axisE) - 0.1) * 0.5 + 0.5);
uQ = saturate(dot(Nramp, axisQ) * 0.5 + 0.5);
E = Ramp(float2(uE, row));
Q = Ramp(float2(uQ, row));
gate = min(E.a, min(P.b, Sg));
blend = saturate(Q.a * P.b * Sg + E.a);
fullColor = DiffuseColor * 1.92;
halfColor = DiffuseColor * 0.432;
c = halfColor * 0.65;
expanded = Luma(c).xxx + 1.20 * (c - Luma(c).xxx);
base = lerp(expanded, halfColor, blend);
base = lerp(base, fullColor, gate);
range = max(E.r, max(E.g, E.b)) - min(E.r, min(E.g, E.b));
rampChroma = lerp(1.0.xxx, E.rgb, range);
colored = base * rampChroma;
ratio = clamp(Luma(base) / max(Luma(colored), 0.001), 0.0, 1.5);
result = colored * ratio; // The current capture selects this endpoint at the end.
1.92 and 0.432 must be interpreted with the upstream color scale. Half-value in the records means DiffuseColor has been scaled by approximately 0.5 before this block, unrelated to half, min16float, or FP16 storage precision. The current coefficients convert source endpoints into that scaled domain; the old implementation applied the scaling again, halving the branch twice. Coefficients and upstream scale must be checked together.
The Ramp RGB range controls interpolation from white to the Ramp color, rather than multiplying every pixel by its roughly 0.9 plateau value. Alpha participates in two gates. Treating RGB as brightness and discarding A changes both the sweater’s dark side and local seam structure.
Sg participates in color-endpoint selection. Without it, an earlier lerp may replace a dark seam endpoint with a bright one. Darkening the entire RGB afterward also reduces specular and local edge highlights.
Current cloth-instance records show CharacterExactDiffuseBlend=1, CharacterClothRampPoseYaw=0, CharacterClothRampGateFloor=0, CharacterClothRampNormalDetail=1, and CharacterClothLowTailLift=0. That run uses the restored full diffuse path without those older pose and low-brightness compensations. Retained compensation code alone does not establish that the final image depends on it.
Mapping a Fixed Captured Environment Term to UE Light Intensity
This active branch in capture 26 does not sample the full APV per point. It selects a fixed environment color basis, modulated by upward orientation and the preceding gate. The restored structure is:
up = saturate(N.z + 0.15) * 1.5 + 0.5;
basis = float3(0.849077106, 0.895768583, 1.150923014);
carrier = up * lerp(basis, 1.0.xxx, gate);
factor = 1.05 * (1.624386787.xxx + 0.287722468 * carrier)
/ 1.624386787;
N.z is the Up component after source-to-UE coordinate conversion. Existing records trace the fixed color basis and coefficients to active capture inputs; the expression above is the UE integration. Interpreting the final division as cancellation of the outer main-light scale is a responsibility inference about the current implementation, not proof of source-shader design intent. These fixed inputs cover only this capture condition.
Increasing Skylight intensity changes another lighting path. Skylight affects the scene under UE’s rules, while this color basis enters a specific material chain under a specific gate. Check the paths separately rather than adjusting them by average image brightness.


How the Direct-Specular Correction Appears on the Sleeve
This historical run changed both the captured half-vector construction and the directional-gate interpolation, replacing the incorrect lerp(0.45, gate, 0.55) with lerp(0.45, 1, gate). Because two mathematical corrections were applied together, the images cannot be called a direct-specular toggle or attributed to only one change.

View all original and location images in this group.
The exact historical commit and complete A-side camera and skeletal inputs were not archived. The AA requested by the B-side configuration also does not establish which method actually executed. This retains historical-observation status; no reconstruction percentage is computed without supporting control conditions.
Visor and Coatings: Restore Direct Specular and Environment Reflection Separately
View-dependent reflection contributes strongly to the visor’s form, but direct specular and IBL are separate chains. The Body path combines the captured environment axis, projected view, and current view direction into H according to the source branch. Its distribution function is rational; absence of pow does not imply absence of specular.
Provenance: half-vector construction, distribution, and gating follow existing source-instruction analysis. The code also contains coordinate conversion, numerical guards, and UE lighting-interface adaptation. Its correctness is assessed separately from the later single-input Cube and normal experiments, which do not validate the entire direct-specular block.
// Abbreviated current Body direct-specular core; input-validity handling omitted.
H = normalize(environmentAxis + 2 * projectedCamera + 3 * V);
NoH = saturate(dot(N, H));
NoV = saturate(dot(N, V));
a = max(roughness * roughness, 1.0 / 128.0);
a2 = a * a;
d = NoH * NoH * (a2 - 1) + 1;
D = a2 / max(d * d, 0.0001);
visibility = 0.5 / max(2 * NoV + a + 0.0001, 0.0001);
specCore = min(max(D * visibility - 0.00006103515625, 0), 20);
rampUV = float2(saturate(D * (a2 + 0.0001)),
saturate(roughness * (1 - metallic)));
specTint = SpecRamp(rampUV);
specCarrier = lerp(0.45, 1, environmentGate) * (environmentGate * 0.5 + 0.5);
The denominator uses NoV, not the NoL left in an old comment. Spec Ramp U comes from the normalized distribution value; V comes from roughness and nonmetallic weight, not NoH plus a fixed row. The final result is multiplied by main-light color, material F0, directional environment coefficients, specCarrier, and 1/PI. Interpreting the current UE 1/PI as cancellation of the main light’s diffuse-intensity convention is an integration-layer inference. Available article material cannot establish it as proven source-shader design intent. Skin shares this rational core, but differs in H and final gating; the complete Body block cannot simply be applied to the face.
IBL follows: normal decoding, world reflection direction, roughness-to-mip mapping, source Cube sampling, environment BRDF, material environment gating, and addition to linear color. A final blue-purple multiplier cannot correct arbitrary changes earlier in this chain.
Provenance: later experiments support Cube and normal bytes, mips, and hardware sampling. Axis reordering is a UE transform; environment weights follow the archived active branch. The material retains the visual compensations listed at the end, so resource experiments do not establish global equivalence.
Excerpt of the current Body reflection chain:
float3 R = 2.0 * dot(V, N) * N - V;
float mip = 1.2 * log2(max(roughness, 0.001)) + 5.0;
float3 radiance = SourceCube.SampleLevel(
SourceSampler, float3(-R.x, R.z, R.y), mip).rgb;
float3 reflected = radiance * CharacterCapturedEnvironmentBRDF(F0, roughness, NoV);
float weight = 0.45 * lerp(0.45, 1.0, environmentGate);
IBL = reflected * weight
* float3(0.849077106, 0.895768583, 1.150923014);
This is shortened for readability; the actual source also includes validity checks, fallbacks, and numerical guards. environmentGate continues the cloth material chain’s gating. Main-light specular, local-light specular, and environment reflection can all appear on the visor, but use different directions, occlusion, and sampling inputs and should be output separately for comparison.

Yellow shoulder armor and white knitwear require separate checks of base color, metallic weight, and reflected energy. Saturated-coating classification remains for diagnostics, but the early uniform 1.85 energy boost is disabled. The 0.68 darkening of white dielectrics was also removed after correcting the post-process input domain. These changes show why local color adjustments depend on correct global exposure and display inputs.
Even after diffuse endpoints and specular calculations are correct, reflections depend on resource inputs. Any mismatch in Cube values, mips, sampler, or world reflection direction means changing Roughness alone changes both highlight width and sampled content, making attribution harder.


Shoulder Appearance and Historical Corrections
The shoulder region combines coating color and reflection. Differences in highlights or saturation do not alone identify roughness, Cube, normal, or display-transform errors. Establish the observation region first, then distinguish the inputs changed in the two historical runs below.

View all original and location images in this group.
This run restored the rational environment BRDF and environment gate and removed studio-lighting intensity influence from the reflection path. It did not replace the original full-mip Cube. The change therefore cannot be presented as a Cube-replacement result or labeled IBL-only: other contributions remain in the composite.

View all original and location images in this group.
Another run changed reflection calculations to consume the cloth normal after the captured handedness conversion. This corrects the reflection-direction input; it is neither a roughness adjustment nor another source-Cube experiment. The local change is subtle and should be viewed at original pixels, without sharpening, brightness matching, or exaggerated differences to manufacture a strong improvement.

View all original and location images in this group.
These historical crops extend reflection observations beyond the visor but do not resolve missing A-side controls. The Cube hardware-sampling case below is separate evidence; its numerical conclusions cannot be transferred to these shoulder images.
Color Domains and Scope of the Quantitative Records
The visor and N1 percentages below retain historical results. The original handoff explicitly states that luminance ratios use scene-linear values with UE PreExposure removed. Final PNGs show display appearance, a different measurement domain.
| Measurement condition | What the available records establish |
|---|---|
| PreExposure | Luminance ratios account for pre-exposure. Read the corresponding View for each run rather than applying one frame’s value to all historical experiments. |
| Display transform | Ratios use scene-linear values, not display-PNG luminance. |
| Comparison stage / transparency composition | The provided summaries lack per-sample read events; samples cannot uniformly be labeled immediately after CharacterForward or after transparency composition. |
| Luma and mean | Reported as mean absolute luminance-ratio error, but weights, epsilon, full scripts, and per-point calculations are absent. Original values are retained without inventing an unchecked formula. |
| Correspondence | Check UVs, visible primitives, and coverage. Nearby pixel centers may have different sampling footprints; identical-input counterfactuals are reported separately. |
| Exclusions | N1 reports all eight points and seven after excluding a boundary-switching sample. The visor report retains worsening samples among its seven points. |
| Maximum RGB difference | 0.000121 compares hardware-sampling predictions with newly captured pixels. The summary omits the complete unpacking format, pre-exposure handling, and maximum-reduction formula. |
These are conditional historical local results, not independently reproducible measurements or full-image reconstruction percentages. N1 direction recomputation has separate input and angle records and is not conflated with luminance metrics.
Cube Bytes, Sampling, and Actual Output
The relevant source visor draw uses a 128×128, six-face BC6H Cube with eight mip levels. The old UE environment has the same face and mip counts, but import/sky filtering changes its values: source mip 0 peaks near 4296 versus about 2402 in old UE, while a later mip reverses that relationship.
The source sampler uses Min/Mag Linear and Mip Point; old UE uses trilinear mip blending. Even the same floating-point LOD produces different colors. A Cube input is defined by its six faces, full mip chain, format, lookup direction, and sampler, not its thumbnail.
At source visor sample (1020,786), debugger Cube and final-color values could not explain the exported R11G11B10 RT. The study exported the full Cube and reconstructed sampling at the specified direction and LOD before assessing the debugger/RT discrepancy.
The sample’s LOD is approximately 4.18422, selecting mip 4 under source Mip Point sampling. Recomputed radiance from the captured sampler and actual resource, substituted into otherwise unchanged terms, explains the RT to approximately one packed-float quantization unit.
A windowless D3D11 compute utility then performed actual hardware sampling for seven confirmed visible visor points. It is not another character renderer; it only performs controlled texture sampling, avoiding reliance on CPU or debugger approximations as hardware truth.
The conclusion is limited to sampling differences at these points and demonstrates the need for cross-checks. Register dependencies, actual RT values, exported resources, and hardware recomputation constrain one another.
The study uses a temporary, explicitly enabled original-DDS upload path that preserves BC6H blocks and all mips without UE sky convolution. Lookup directions use the validated UE-to-source mapping (-X,Z,Y), with Min/Mag Linear and Mip Point aligned to the source’s effective range.
Re-exporting the bound resource from the new RDC yields a complete DDS, including header, identical to the source bytes. This compares resource contents. Source MaxLOD=1000 and UE’s maximum float both leave the eight mips unrestricted, but the sampler descriptors are not byte-identical.

Mean absolute luminance-ratio error across seven points falls from 21.72% to 13.61%: four improve and three worsen. Some become too dark, indicating remaining errors in other lighting terms rather than a need for another global Cube gain.
The maximum RGB difference between seven newly captured pixels and prior hardware-sampling predictions is approximately 0.000121. The records support explaining local changes through input changes. With the measurement fields listed above incomplete, this article makes no broader precision or color-domain claim.
Visor Normals Also Require All Mips and Actual Sampling
The visor currently uses all 11 source BC5 normal mips, bilinear/Mip Point Wrap sampling, and SampleBias(-1), scoped to the target visor normal. Imported UV correspondence and the single green-component conversion must agree with the actual TBN; a fitted normal rotation is not a substitute.
The DDS re-exported from the new capture matches source bytes. For seven visible primitives, hardware predictions of normal RG and traces agree at the recorded precision; world-normal prediction error is below 6e-8. This establishes a resource-to-direction chain rather than a thumbnail-based judgment.

Mean seven-point luminance-ratio error changes from 13.61% to 11.32%, with six improvements and one regression. Separate source Base/Packed experiments remain excluded from the final candidate. The visor normal is restored, but other inputs are not yet fully matched to the source.
The rational environment BRDF is retained at the end for coefficient comparison. Replacing it with another approximation plus Cube gain is not equivalent: matching one angle can alter responses at other roughnesses and views.
Skin: Direction Fields, Color LUTs, and Shadow Overlays Have Separate Roles

Body and face share the Skin family but not necessarily active inputs. The facial direction field needs face-local lighting; hands and neck depend first on their own normals, directional response, and part gates. Records still show body Ramp/LUT lookup differences from the reference, and default return values do not establish valid GPU bindings. That correspondence is reserved for the third article; body skin is not described here as fully source-equivalent.
Local warmth, occlusion darkening, and contour colors must be explained through their consumers. Otherwise a dark finger edge may prompt an irrelevant facial-SDF edit, or a global skin-color change may improve the neck while disrupting a correct palm. The following sections establish each input’s role before discussing the scope of static adaptations.
Visible-Hand Correspondence and Its Limits
The hand provides a skin region beyond the visor, but each delivered side contains only 160×140 original pixels. It supports discussion of that visible patch, not validation of the complete arm, dynamic lighting, or isolated skin terms. Body Ramp/LUT binding corrections remain assigned to the third article.

View all original and location images in this group.
Control Map, SDF Direction Field, and Color LUT
Early designs can conflate the control map, direction field, and color LUT. Current code separates them as inputs; whether a particular instance enables them still requires binding checks.
| Map | Sampling | Known current consumers |
|---|---|---|
Facial Packed control map (alias CharacterSkinSDFMap) |
Main UV, Linear | G blends normal/directional branches; R/B participate in view tint; A selects color endpoints |
Actual CharacterFaceSDFMap |
U chosen by face-local light side; resource-specific V correction | B constructs an auxiliary normal; R+G constructs the moving light/shadow boundary; other edge chains have separate consumers |
CharacterSkinShadowLUT, 1024×32 |
Coordinates from input color, not mesh UVs | 32³ skin-color endpoint; not a one-dimensional NdotL scan |
For Face, the ordinary interpolated normal is not the lighting chain’s only normal. The implementation reconstructs projected and radial facial normals and blends them using control-map G. The radial center also transforms from source pose into the current Head frame in UE. This is coordinate migration, not a fixed world position obtained from the source material. The same code reconstructs a full three-dimensional auxiliary normal for the edge chain, avoiding use of the planar shading normal where a full spatial direction is required.
Facial reconstruction thus includes at least two spatial questions: which side of face-local space contains the main light, and which space contains the current surface and auxiliary normals. Reversing the sign of NdotL cannot correct both.
Face-local Light Direction Controls the SDF Boundary
Provenance: SDF channels and endpoint relationships follow source-shader dependency analysis. The code is an abbreviated current UE branch; face-local space and resource-specific V correction are adaptations. Current materials do not include this round’s SDF-toggle images or complete skin-pixel validation.
The active Face branch first projects the main light into face coordinates and takes normalized horizontal components. Let lx determine left/right and lz be the other component. The excerpt follows the actual source branch; its constants belong to this capture, not a universal face model.
uv.y = 1.0 - uv.y; // Only for the independent SDF resource whose reversal was verified.
uv.x = lx > 0 ? uv.x : 1.0 - uv.x;
float4 sdf = FaceSDF.SampleLevel(samp, uv, 0);
float nx = lx > 0 ? sdf.b * 2 - 1 : 1 - sdf.b * 2;
float3 nSDF = normalize(float3(nx, 1e-5, max(1 - abs(nx), 0)));
float center = clamp((1 - lz) * 0.5, 0.001, 0.999);
float lo = max(center * 2 - 1, 0);
float width = max(min(center * 2, 1) - lo, 1e-5);
float t = saturate(((sdf.r + sdf.g) * 0.5 - lo) / width);
float smoothT = t * t * (3 - 2 * t);
float sdfDirection = abs(-smoothT - max(lz, 0) * 0.5) * 2 - 1;
float faceInput = lerp(sdfDirection, clamp(NdotL - 0.1, -1, 1), control.g);
float rampU = faceInput * 0.5 + 0.5;
R+G is remapped within an interval that moves with light angle, moving the boundary. B supplies another normal direction, not the boundary scalar. Conflating these roles previously reversed a local edge response. The V correction follows imported-resource comparisons and applies only to that resource; the control map and other main-UV textures retain their orientation.
To diagnose this path, inspect FaceLightXZ, sampled FaceSDFRGB, the directional boundary, and Ramp U in sequence. Adjusting the Ramp curve can conceal incorrect local-light direction or SDF sampling. If the boundary is correct but skin color differs, inspect the color endpoints.
Preserve a Separate LUT Endpoint Instead of Replacing Skin Color Early
The 1024×32 texture contains 32 horizontally arranged 32×32 slices. Blue selects the slice, while red and green select the position inside it. The following follows current code with shortened resource names:
float3 enc = LinearToSrgb(baseColor);
float blue = saturate(enc.b) * 31;
float tile = floor(blue);
float f = blue - tile;
float v = 1 - (0.5 + saturate(enc.g) * 31) / 32;
float u0 = (tile * 32 + 0.5 + saturate(enc.r) * 31) / 1024;
float u1 = min(u0 + 32.0 / 1024, 1 - 0.5 / 1024);
float3 a = LUT.SampleLevel(samplerLUT, float2(u0, v), 0).rgb;
float3 b = LUT.SampleLevel(samplerLUT, float2(u1, v), 0).rgb;
float3 lutColor = sRGBToLinear(lerp(a, b, f));
Half-texel offsets avoid slice boundaries; interpolation between neighboring blue slices supplies the third dimension. This excerpt requires LUT sampling to return undecoded color values before the shader calls sRGBToLinear. The active Draw’s Skin LUT SRV attributes were not provided, so this requirement and possible double decoding remain unresolved from HLSL alone. If the SRV already performs sRGB decoding, another manual decode changes the color. Moving nonlinear conversion across sampling also changes filtering order; deleting one line does not establish source-sampling equivalence. Linear import settings in the supplement apply to the final display LUT and do not establish Skin LUT bindings. The current path keeps CharacterLUTBaseColor separate without replacing BaseColor early.
The two colors retain different downstream consumers. The source shader forms one endpoint from original base color with view tint and ShadowTint, and a darker endpoint from the LUT, then blends using Ramp Alpha, control G/A, and shadow visibility. Replacing BaseColor with LUT output at the start also feeds LUT color to every original-color branch, causing repeated grading. Pink faces and chins are characteristic symptoms of this semantic error.
The following dataflow explains this branch. Luma and chroma expansion remain linear, while LUT coordinates are computed separately under the rules above.
View tint and shadow color form one endpoint from the original skin color. Independent scaling and chroma processing form the other from LUT output. Ramp Alpha, material shadows, and control channels determine the main blend; another channel selects the final color. The two paths remain separate until their consumers rather than merging at entry.
This is color-grading-based skin shading, not UE Subsurface Profile screen-space scattering. Subsurface Profile in early records was a design suggestion; the current LUT path does not implement a complete subsurface-scattering pipeline.

View Tint and Screen-space Shadows Enter the Material Separately
Warm color near the facial contour is not a uniform white Fresnel addition. Current code restores this material-space tint from source instructions:
edge = saturate(0.85 * (1 - saturate(dot(N, V)))
* control.r * lerp(1, 0.7, control.b));
viewTint = lerp(1.0.xxx,
float3(0.539479613, 0.266355664, 0.174647391), edge);
It modifies the original skin-color endpoint. The SDF controls the main-light directional boundary. Dedicated screen-space shadows first pass through a facial mask to form material shadowing: 1 − carrier × (1 − screenVisibility), where carrier=max(control.b×viewGate, control.g). Neither is the later multiplicative OverlayShadow card.
Missing shadow beneath the fringe requires checking later occlusion layers. A red cast across the face after restoring the overlay requires checking color domains and material endpoints. Uniformly lowering facial exposure corrects neither cause and can damage already-correct lit regions.
Body Skin Retains Its Own Lighting and Local Adaptations
MI_Example_body_01 shares the Skin shader family with the face but lacks active facial-SDF gating. Body skin directly consumes its screen-space shadow and normal response, whereas Face uses the special shadow remapping above. Valid bindings and parameters distinguish them; Skin does not imply SDF. Even a retained SDF reference requires checking activation conditions before declaring the texture in use.
Body skin also restores an environment-color vector varying with normal Up. Omitting it and retaining only an approximate face scalar makes the palm a flat pale surface. Explicit UE adaptations remain downstream: a neck envelope based on Head height, wider low-Ramp hand shadows, reduced chroma, and local output scaling. These are static reconstruction compensations, not verbatim source-shader formulas.
Spatial isolation limits compensation. If hands and neck share a material, darkening the entire Skin output also applies the neck’s occlusion patch to the fingers. CharacterNonFaceSkinGate first excludes the face, and CharacterNeckEnvelope distinguishes neck and hand regions. This bounds static compensation without constituting a general character system.
Body-skin checks should include bright palm, palm-side midtones, dark finger gaps, and neck samples. Looking only at the brightest palm misses continuous darkness under fingers; examining only gaps can encourage overly narrow, deep corrections. Hair probes from the N1 case do not validate these regions.
Eyes: Distinguish Source Mechanisms from the Current Simplification
Scope of the Current Eye2 Simplification
Source-capture research records several eye variants, including _MATCAP_ON _EYE_HIGHLIGHT and color-LUT variants. MatCap sampling does not simply attach a fixed highlight to iris UVs; it uses view-related normals, parallax weights, and nonuniform scaling. The source-research explanatory expression is:
matcapUV = meshUV - viewN.xy * parallaxWeight;
matcapUV += viewN.xy * matcapScale * float2(1, 0.25);
eyeSpec = MatCap(matcapUV) * tint * intensity;
This summarizes a source-shader path, not implemented UE code. Current Eye2 build records create BaseAlbedo×BaseTint, a flat normal, and simplified EyeHighlightMap × EyeHighlightIntensity connected to Emissive. The latest iris intensity is 0.15, with the iris color texture still bound as the highlight map. This does not establish a complete parallax MatCap implementation.
# Key connections in the current build script; auxiliary-node creation omitted.
mel.connect_material_expressions(hi, "RGB", hmul, "A")
mel.connect_material_expressions(hii, "", hmul, "B")
# Add hmul to the retained branch with a zero-valued parameter, then connect to Emissive Color.
The shared Skin/Eye color block also has a conditional LUT branch whose execution depends on resource validity; an Eye compilation does not imply LUT sampling. Brow and iris use the Eye2 parent, sharing a shader family while retaining distinct instance colors and occlusion roles.
The visor heavily obscures this character’s eyes in the static image, so the simplified path remains and complete eye appearance is unvalidated. For an unobstructed version, first select the corresponding source Eye variant, export its MatCap and constants, restore view basis and UV offsets, and compare highlight trajectories across views rather than first adding a generic UE cornea model.
Outlines, Rim Lighting, and Local Shadow Overlays


Outline Width Must Be Interpreted Through Projection
Geometric outlines recover directions from skinned attributes, optionally decode smooth normals from UV1, transform through the tangent basis, and project into the clip plane. Width depends on viewport aspect, FOV, clip.w, near-distance attenuation, and masks; depth offset is separate. A material width value therefore is not directly a final pixel width, and normal extrusion distance does not describe the full implementation.
Current code includes projection-scale adaptation and minimum-displacement guards. These must be interpreted with the view rather than generalized as source constants for arbitrary characters. Depth modes separately determine shell-depth priming, Equal requirements for color, and stencil-based coverage selection. A two-sided body material alone does not establish the outline draw’s cull state.
When diagnosing screenshots, first determine whether an edge is new coverage outside the mesh. If it is brighter lighting inside a sleeve surface, inspect local lights and normals. The former requires shell geometry and depth analysis; the latter requires material-response analysis. Sharing one intensity control conceals the actual cause.
Rim Lighting Comes from Part-specific Directions and Light Responses
White hair edges, warm facial edges, and hard bright sleeve edges in the reference do not share one character-wide pow(1−N·V, exponent). Current code has captured hair, skin, and cloth local lights, plus an edge chain using auxiliary facial normals. They consume different normals, material weights, and light records.
For one captured cloth local light, attenuation starts with distance from the light position, converted to meters for a softened inverse-square term, then multiplied by range cutoff and cone attenuation. A separate view response supplies the material edge gate. Some source light types also project the light direction onto a plane perpendicular to a specified axis, adding directional support. Color and material response are multiplied afterward.
Light and surface positions determine distance, range, and cone attenuation; normal and view direction form the edge gate; light type may add projected directional support. These combine with part color and energy response at the end.
Without spatial attenuation, bright edges spread around the character. Wrong normals can put white edges on inner hair cards. Applying later shadows to all composited color can remove restored local-light edge energy. Some local-light occlusion still uses fixed visibility, a clear static limitation; a shadow parameter does not establish complete dynamic shadow projection.
Accordingly, rim lighting means a lighting contribution, outline means additional geometric shading, and shadow overlay means multiplicative coverage. They meet near contours but require different implementations and diagnostics.
Shadow Cards Output a Local Multiplication Factor
Full-frame source records include multiple OverlayShadow variants. Existing local experiments for this character support multiplicative modulation of existing color, using Zero, SrcColor blending in the corresponding material:
// SrcBlend = Zero, DstBlend = SrcColor, BlendOp = Add
after.rgb = before.rgb * shaderOutput.rgb;
The fixed-capture color factor comes from source constants. Event, ratio, and coverage records are in the appendix, separate from pixel attribution for the current UE final overlay, which remains assigned to the third article.
The UE asset uses BLEND_MODULATE, Unlit, and two-sided rendering, sends the factor to Emissive, and preserves geometric coverage. Emissive here is the modulation material’s factor-output interface, not an added glowing layer on the face.
m.set_editor_property("blend_mode", unreal.BlendMode.BLEND_MODULATE)
m.set_editor_property("shading_model", unreal.MaterialShadingModel.MSM_UNLIT)
m.set_editor_property("two_sided", True)
c.set_editor_property("constant",
unreal.LinearColor(0.673859537, 0.531917334, 0.567861497, 1.0))
mel.connect_material_property(c, "", unreal.MaterialProperty.MP_EMISSIVE_COLOR)
Hair shadow overlays and optional ground modulation are different objects. The supplementary event table confirms a target overlay draw in the final frame writing AfterDOFModulate. This is stronger than a build script alone but does not report how many pixels it changes in this run. Attachment, state, and coverage validation remain for the third article.
Geometric outlines use extra shell draws, rim lighting contributes illumination, and overlays locally modulate existing color. They should be validated separately despite meeting near image edges. Shell depth, stencil, and velocity integration are covered in the previous article; this section retains their material-consumer aspects.
Hair Inputs: Restore Dual Normals Before Checking Tangent Space
The hair sections follow three chains: HN preservation and N1/N2 decoding; object frame, dual lobes, and RS/ST coupling; pose control and identical-input N1 counterfactuals. The first two explain algorithms, the third tests spatial corrections. Complete capture identities are in the appendix, while conditions and results remain in the main text.
| Semantic | Current use | Validation focus |
|---|---|---|
| HN RG | N1 diffuse-normal decoding | RG range, import-time G reversal, actual TBN |
| HN BA | N2 specular-related normal field | BA preservation, linear sampling, tangent handedness |
| Packed R | Grooming-direction blend | Not ordinary color or arbitrary noise strength |
| Packed G | First-highlight mask and some diffuse modulation | Errors change both shape and energy |
| Packed B | Final consumer unconfirmed in traced paths | Do not invent semantics for an unconfirmed channel |
| Packed A | Second-highlight weight | Do not assume opacity |
| RS | Two-dimensional specular-color lookup | Computed coordinates, not direct mesh UVs |
| ST-derived input | Fine-stripe diffuse modulation | Current single-channel mapping must match the consumed source ST channel |
The table applies to traced hair branches, not every Packed texture in the game. Source shader slots also shift with variant pruning; t17 is not a permanent dual-normal contract.
A Four-channel Resource Cannot Be Imported as an Ordinary BC5 Normal
Source HN packs RGBA. The early generic TC_NORMALMAP / BC5 import could not preserve both required component pairs. Continuing to decode N2 from BA then produces a direction field different from the source.
Increasing normal strength cannot recover lost BA directions. Import was changed to preserve all four channels in a linear resource. Separate checks established variation in source BA, BA preservation in UE, live binding to the corrected resource, shader reads of BA, and consumption of the reconstructed direction by specular calculations.
BC5 import errors and tangent-basis errors require separate validation. After restoring channel preservation, spatial transforms still need checks against actual vertex and pixel inputs; remaining discrepancies cannot all be assigned to one cause.
Apply Decoding and Handedness Conversion Once
Signed interpretation of source samples and the 0–1 channels exported into PNG are different semantic layers. The HN path restores two components to -1–1 before reconstructing positive Z. For N1:
// Current source: DecodeCharacterHairN1XY
float2 DecodeCharacterHairN1XY(float2 PackedRG)
{
return (PackedRG * 2.0f - 1.0f) * float2(1.0f, -1.0f);
}
// Current form of subsequent Z reconstruction and TBN transformation
float3 nTS = float3(xy, sqrt(saturate(1.0f - dot(xy, xy))));
float3 nWorld = normalize(mul(nTS, MaterialParameters.TangentToWorld));
(1,-1) converts tangent handedness in this import chain rather than adding variation. Corresponding source vertices have tangent sign -1 versus UE +1; T/N directions are close and B is reversed. The HN asset itself does not flip G, so decoding applies the conversion once.
Cloth needs separate checks: some normals already flip G during import, and another flip introduces an error. flip_green_channel records establish this processing; a texture thumbnail cannot.
N1 and N2 Have Different Roles but Remain Visually Coupled
N1 mainly controls diffuse shading; N2 enters specular-direction construction. The visible hair highlight ring is not just one specular lobe. N2 also participates in ST diffuse modulation, while the diffuse base, transparency, local lighting, and display mapping jointly determine the perceived bright band.
A small first lobe at one pixel does not establish that the hair ignores the chain. Conversely, a bright visible band does not guarantee high HDR energy in the exponent-200 lobe. Experiments check support regions, component colors, and final contributions rather than treating amplified debug views as actual energy.
Mips and Sampling Footprints Affect Local Shape
The main N1, N2, and Packed paths restore source SampleBias(-1). It chooses LOD from derivatives and adds negative bias; it does not force mip 0 at every distance.
Hair data has high spatial frequency. Changes to mips, anisotropic filtering, or UV footprints can smooth or fragment direction fields and masks. If variation is insufficient, check sampling footprints before searching for unidentified shading terms.
Forming the Hair Highlight Ring from Direction Fields and Two Lobes
The Object Frame Participates in Lighting-vector Construction
Provenance: the object basis comes from captured resources; register roles follow last writers and consumers. UE axis reordering is spatial adaptation. Lobe shifts and exponents below have captured-constant support; ST’s FirstPeak dependency follows dataflow tracing. These local sources do not validate every hair pixel. The fixed object basis and other static compensation remain subject to the final limitations.
Source EID 1140 first reads the renderer’s actual object basis, then modifies the light direction. The key current excerpt is:
const float3 HairObjectProjection =
HairUnityObjectCol0 * dot(HairHeadView, HairUnityObjectCol0)
+ HairUnityObjectCol1 * HairLightDirection.z
+ HairUnityObjectCol2 * dot(HairHeadView, HairUnityObjectCol2);
const float3 HairModifiedLightDirection = normalize(
HairLightDirection + HairObjectProjection * 2.0f);
const float3 HairH = normalize(HairModifiedLightDirection + HairHeadView);
.z here is expressed in current UE world coordinates. Source mathematics must be interpreted against its Up axis, not mechanically changed to .y. Object axes come from the active captured structured-resource branch, not an inactive cbuffer fallback or UE Actor axes.
FBX import can bake transforms into the mesh, leaving the UE Primitive frame different from the source shader’s object frame. Using the wrong frame in highlight calculations affects direction and height across the hair and changes local response regions.
The current reconstruction fixes the object basis from capture 26. This explicitly limits it to static reconstruction. Animation support must connect the correct runtime frame source rather than treating three fixed vectors as a general character-asset configuration.
N2, Object Up, and Vertex Tangents Form the Grooming Direction
The core current calculation is:
const float CombBlend = saturate(CharacterHairPackedExact.r);
const float3 HairAnisoBitan = cross(
HairN2World, normalize(HairUnityObjectCol1));
const float3 HairBlendedBitan = lerp(
HairAnisoBitan, HairVertexTangentWorld, CombBlend);
const float HairTangentSignScale = lerp(
1.0f, MaterialParameters.UnMirrored, CombBlend);
const float3 StrandDir = HairTangentSignScale * cross(
HairN2World, HairBlendedBitan);
Packed.R determines the blend, while tangent sign contributes to direction scale. Replacing this with fixed world Up, or severely limiting the vertex-direction blend, suppresses local variation authored in the texture and geometry. The ring then appears too continuous or smooth, or drops at the wrong location on one side.
These variations come from authored data and geometric flow. Random noise at highlight boundaries would lose their correspondence with the surface.
Two Shifted Tangents Produce Lobes of Different Widths
Two shifted tangents are constructed from the grooming direction and N2:
const float3 T1 = normalize(StrandDir + HairN2World * (-0.10f));
const float3 T2 = normalize(StrandDir + HairN2World * (-0.14f));
const float T1dotH = dot(T1, HairH);
const float T2dotH = dot(T2, HairH);
Band = pow(sqrt(saturate(1.0f - T1dotH * T1dotH)), 200.0f);
CharacterHairSecondBand = pow(
sqrt(saturate(1.0f - T2dotH * T2dotH)), 80.0f);
The shifts come from decoded capture constants; the first exponent is 200 and the second 80. They determine angular response, but cannot define visible width independently of N2, StrandDir, and H.
High exponents make the response sensitive to directional error. A shift of a few degrees can remove the highlight from a sample previously near the lobe center. Lowering the exponent broadens the response elsewhere as well, so directional inputs should be checked first.
Separate Horizontal Azimuth Response from Geometric Shells
The azimuth term projects N2 and V onto UE’s horizontal plane, normalizes their dot product, and cubes it. Degenerate cases need guards so undefined azimuth does not produce arbitrary energy.
const float2 NAz = HairN2World.xy;
const float2 VAz = ViewV.xy;
const float NAzLen = length(NAz);
const float VAzLen = length(VAz);
const float AzCos = saturate(
dot(NAz, VAz) / max(NAzLen * VAzLen, 1e-5f));
const float WideHalo = (NAzLen > 1e-4f) ? pow(AzCos, 3.0f) : 0.0f;
Some old ShellN latitude clipping and artificial wide-ring code remains for diagnostics. However, the current exact SpecLine path has an extra latitude gate of 1, and the final configuration disables the artificial ring. Finding shell or wide-ring code does not establish active use; inspect feature gates and the final accumulation.
Two-dimensional RS and Control Maps Determine Highlight Color and Weight
The main first-lobe path is:
const float MaskedLobe = saturate(
PackedHair.g * Band * CharacterHairSpecLatitudeGate);
const float RampV = saturate(CharacterHairLobe2);
const float3 FirstRampColor = CharacterHairSpecRamp.SampleLevel(
CharacterHairSpecRampSampler,
float2(MaskedLobe, RampV), 0).rgb * MaskedLobe * CharacterHairWide;
const float FirstPeak = max(
FirstRampColor.r, max(FirstRampColor.g, FirstRampColor.b));
The first lobe additionally has an energy factor PackedHair.g * 0.20. The second combines a warm-color constant, Packed.A, the second angular lobe, azimuth response, ST weight, and suppression by the first lobe. The retained warm coefficient is (0.050876, 0.037972, 0.025843).
RS is a two-dimensional lookup indexed by computed values. Reversing mesh-UV V cannot compensate for a reversed RS V axis because RS is not sampled directly with mesh UVs. A single-input experiment established that the old UE RS was exactly the source flipped vertically. After correction, all 262,144 RGBA components exported from a new capture matched the source 256×256 resource.
This aligns source texture, import, material binding, and GPU contents, but the overall highlight still differs. The first-lobe peak remained different in that run, so investigation continued into the object frame and direction construction rather than recoloring RS again.
Detail Variation Also Modulates the Diffuse Base
A key error came from carrying a register’s earlier Boolean meaning past an overwrite. After the source first highlight lobe is computed, its maximum RGB value is written into the register consumed by the later ST branch. That consumer needs FirstPeak, not the earlier tangent-sign condition.
Register semantics change with each write. For reused registers such as r3.y, trace dependencies through the last writer and establish operand meaning at the target instruction.
ST coordinates include sixfold U tiling and an offset. Distinguish source channels from the current single-channel derivative: CharacterHairLineMap reads .r to carry the analyzed source ST control data. This does not make source RGBA R/G universally interchangeable.
const float2 HairSTUV = MaterialParameters.TexCoords[0]
* float2(6.0f, 1.0f) + float2(1.0f, 0.0f);
const float HairST = CharacterHairLineMap.Sample(
CharacterHairLineMapSampler, HairSTUV).r;
const float HairSTWeight = lerp(
1.0f, 1.0f - 0.55f * HairST, CharacterHairParams.w);
const float3 HairDiffuseT = normalize(
CharacterHairStrandForDiffuse + CharacterHairN2ForDiffuse * (-0.05f));
const float HairDiffuseTdotH = dot(HairDiffuseT, HairH);
const float HairDiffuseBand30 = pow(
sqrt(saturate(1.0f - HairDiffuseTdotH * HairDiffuseTdotH)), 30.0f);
const float HairDiffuseSTWeight = lerp(HairSTWeight, 1.0f, FirstPeak);
const float HairDiffuseModulation = 1.0f
+ PackedHair.g * (HairDiffuseSTWeight - 1.0f) * HairDiffuseBand30;
MainLightToon *= HairDiffuseModulation;
Specific constants in the source capture cancel another high-frequency square-wave term. Restoring that inactive term as visible noise would be incorrect. Actual energy depends on computation, constants, and feature gates together.
Final shading contains multiple components. ST diffuse modulation changes dark gaps and continuity between strands, while the first-lobe peak influences that modulation. Replacing only the specular formula without restoring this dependency can leave two apparently correct blocks producing an incorrect composite.
The current implementation removes this branch’s old 0.15 blend and 0.82 visual gain and uses the restored calculation directly. This local cleanup does not remove compensation from the entire shader; retained adjustments are listed at the end.
After the highlight formula is restored, the dependency between ST and the specular peak still affects ring shape. Output diffuse, specular, and modulation values together to identify which component produces an overly smooth band.
Pose and Normal Corrections Need Controlled Geometric Evidence
Record Captured Object Frames and UE Bone Mapping Separately
Temporary head/hair A/B experiments read captured object and bone-palette data, then map them onto UE bones after axis conversion.
Source vertices first obtain positions from the source bone palette and object frame, then world-axis mapping converts them into UE bone and component spaces. Preserve each step’s inputs, transform, and evidence level.
Source matrix bytes are RDC exact; palette-to-bone-name correspondence is RDC inferred; axis and inverse-component transforms are UE transform. Unrestored translation, neck blending, and historical fitting do not become exact data through this chain.
This run restores 27 head/hair-related world skinning rotations without changing camera or lighting or saving runtime poses into the original level.
Validate Geometric-normal Direction Error First
For 3,836 clearly corresponding geometric normals, mean directional error falls from 3.808745° to 0.168548°, with maximum error reduced to 1.148934°. Hair geometry orientation is substantially closer, but this does not establish matching texture normals, specular color, or all skinned positions.
This reduces ambiguity in later diagnostics. Remaining highlights can be investigated through N1/N2, sampling, or lighting without attributing a large head-rotation error to the material.
Shared Pose Removes Numerical Disagreement Between Hair Layers

Independent large-coordinate pose round trips in the two hair components introduced small numerical differences. Under strict depth comparison, the resulting loss of coplanarity produced coverage fragments. The correction belongs in geometric pose handling rather than N2 smoothing or highlight noise.
SetLeaderPoseComponent ultimately shares the main component’s bone pose, avoiding independent round trips. The historical report states matching world/clip outputs for 7,899 vertices in both layers, but the article handoff lacks the comparison script, raw float arrays, and tolerance definition. This report is retained without upgrading it to bitwise equality or inventing a maximum difference of zero.

Check Within-capture and Cross-run Consistency Separately
In the final N1 A/B, world position changes across runs by a mean 0.012528 cm and maximum 0.066864 cm; projection changes by a mean 0.0763 px and maximum 0.49116 px. Normal-component changes are small, but the positional drift can still move individual samples across pixel boundaries.
Within-capture agreement remains the historical report above, with its comparison criterion still missing. Cross-run data already shows measurable drift, so bitwise pose locking is not established. The validation JSON retains the failed strict cross-run consistency result.
N1 Correction: Assess Spatial Accuracy and Visual Convergence Separately
Change Only the Missing Handedness Conversion
Input inspection found the N1 diffuse normal missing the import-handedness Y conversion already applied to N2. The prediction was that one conversion would bring N1’s world direction closer to the source without changing N2’s exponent, width, intensity, or rim lighting.
Changes were limited to the N1 helper and five call sites, without fitted world rotation or camera, light, and highlight-parameter changes. The experiment preserves prior source, baseline image, expected formula, and geometry/pixel attribution requiring renewed checks.
Compile logs, actual shader bytecode, draws, and output resources confirm that the changed code entered the capture. Runs with compilation failure and default-shading fallback are excluded. The first capture failed and was discarded; the results below come from the second valid capture. Full identifiers are in Appendix B, Run Configuration and Capture Validity.
Report Identical-input Recomputation Separately from Visual Samples

At new pixel (896,220), primitive 2870, maximum N1 equation-recomputation error is 7.821226e-9. With identical UV and HN values, the old-decoder counterfactual angle is 4.773216°, versus 0.222640° for current decoding.
The old-decoder result is recomputed with the old formula under identical inputs, separating the Y conversion from screenshot-sampling differences. It validates the corresponding sample, not every pixel direction.
Historical report values: mean absolute scene-linear luminance-ratio error over eight visible corresponding points falls from 13.3367% to 10.8390%, with four improvements and four regressions. Excluding the lower-left point that switches across a pixel boundary, the other seven change from 13.9570% to 12.3207%.
| Local sample | Before / source luminance | After / source luminance | Interpretation |
|---|---|---|---|
| Lower right | 1.28961 | 1.17424 | Less overbright, but still bright |
| Middle right | 1.07009 | 1.03819 | Closer, not identical |
| Center | — | 1.30264 | Still noticeably overbright |
| Upper middle | — | 0.86203 | Still too dark |
The dash indicates a historical value not reproduced here, not zero. Four regressions and cross-run geometry drift are retained; an improved mean is not presented as improvement at every point.
A Validated Local Transform Still Leaves Whole-image Differences
The experiment supports the handedness correction in the active N1 path: shader output agrees with independent recomputation, and mean error decreases among the limited visible samples. It does not validate equivalence of N2, shadows, all materials, or final display, and the A/B runs do not achieve bitwise geometry locking.
The records retain this local transform correction while reporting remaining visual differences. Evidence is still insufficient to establish complete character reconstruction.
Variables That Drive Material Cost
| Part or mechanism | Main cost sources | Main scaling variables |
|---|---|---|
| Body / Cloth | Packed, Ramps, screen-space shadows, direct specular, Cube | Screen coverage, material sections, active shadow and environment branches |
| Face / Skin | Control map, SDF, two LUT samples, endpoint blending | Visible coverage, branches, texture sampling, bandwidth |
| Hair | HN, Packed, RS, ST, multiple direction constructions, high-exponent lobes | Hair-card coverage, active samples, repeated shading in actual transparent layers |
| Outline / Overlay | Extra geometry draws, depth tests, Modulate writes/composition | Material sections, shell coverage, overlapping layers |
| Shader variants | Body / Skin / Eye / Hair, diagnostic and restoration switches | Platforms, compiled combinations, PSO count; assessed separately from per-pixel shading cost |
The first hair lobe also feeds ST diffuse modulation, so diffuse and specular costs are not fully independent. This static single-character study provides no GPU timings. The table lists cost sources and scaling variables; sample counts and runtime cost depend on actual compiled branches and visible coverage.
Technical Conclusions and Further Validation
Material validation depends on consistent correspondence between surfaces, resources, and consumers. Matching screen coordinates still requires checking the visible surface; matching names still requires checking GPU bytes. Space, handedness, and color-domain conversions should occur once at their respective interfaces. Cloth, skin, hair, and transparent coverage have different input chains; local formula validation still leaves overall appearance and dynamic conditions to test separately.
Reusable Findings and Conditions Not Yet Covered
| Category | Supported by existing evidence | Incomplete or unvalidated |
|---|---|---|
| UE integration | Dedicated part passes, resource bindings, current static execution | All platforms/render permutations, dynamic velocity, packaging |
| Hair inputs | RGBA dual normals, handedness, source RS, key sampling | Complete agreement of every original mip/import step |
| Hair shading | Grooming, modified H, dual lobes, ST dependencies | Overbright central highlight, complete component/display equivalence |
| Transparent layer | Strict comparison and reported within-capture two-layer agreement | Source transparent depth writes, cull separation, complete NPR |
| Pose | 27 rotations and closer geometric normals | Old translations, neck blending, bitwise cross-run locking, animation |
| Shadows | Source-matched kernel/signs and some input conventions | Atlas/casters/filtering, shared R channel, and other differences |
| Visor | Source Cube, source normal, component validation | Final Base/Packed adoption and full reflection-appearance agreement |
| Cloth / skin | Independent paths and some input corrections | Overall material response and environment-compensation convergence |
| Final display | Fixed research configuration and exposure-domain inspection | Per-mechanism equivalence of source temporal/post-processing |
The records retain the following non-source adjustments. Retained describes the recorded state, not a replacement for per-draw active-parameter checks. Removal conditions are requirements for future validation.
| Adjustment | Scope and nature | Recorded state | Required before removal |
|---|---|---|---|
| BaseTint | Material base-color appearance adjustment | Retained; per-instance values are not listed here | Source base-color input and corresponding material components |
Local composition 0.90 |
Local energy/composition adjustment | Retained; full consumer scope was not provided | Identical-input comparisons of the term and final composition |
grid-9 0.85 |
Hair-local ratio adjustment | Retained; full multiplication location still needs documentation | Local-light/occlusion consumers and pixel attribution |
SceneScale 1.15 |
Scene/Bloom input scale after display-node reads; visual compensation | Current display configuration depends on it | Cross-material, exposure, and display-input consistency; not a direct PNG multiplier |
| Environment compensation | Some material environment responses | Retained; not assumed shared by all parts | Environment resources and component responses |
| Neck blend / envelope | Static spatial adaptation for non-face Skin | Current local patch | Body bindings, multiple poses, local occlusion |
| Fitted strand translation | Static hair-geometry fitting | Historically retained | Source geometry, bone mapping, animation |
| Artificial Hair Ring | Historical artificial-highlight compensation | Disabled in final configuration | Continue independent source-direction-chain validation; retained code does not imply activation |
Reduce Variables Before Further Convergence
First improve cross-run geometry consistency: projection drift approaching half a pixel interferes with fine-strand measurements. Then inspect identical-input components at the overbright center and dark upper middle, comparing N1/N2, H, ST, main light, local light, shadows, and final composition rather than reducing the entire highlight.
Next address remaining pipeline differences such as transparent NPR and shared shadows, then establish corresponding-point sets for visor, cloth, and skin. Enable source-texture experiments one resource class at a time, with separate rollback paths.
Only then generalize fixed capture data: object frames from live characters, light records from live scenes, and stable material-parameter contracts, followed by multi-view, multi-light, animation, TSR/Velocity, packaging, and performance tests. These stages lack complete validation, so no verified real-time performance budget can be given.
These appearance discrepancies require corrections at different stages: channel loss at import, directional shifts in space and pose handling, color overwrites in render state, and environment-energy differences in Cube data and hardware sampling. Final color scaling cannot correct all these inputs and states.
The static result meets its stage objective with local discrepancies retained in the records. Further work can follow the same evidence chain, but animation, varied lighting, and performance each require new experiments rather than validation borrowed from static screenshots.
Later Articles: Visual Refinement, Material Systems, and Scene Integration
These two articles are P1 and P2 of the series. P1 explains character-pipeline integration into UE; P2 covers part-specific material algorithms and existing pixel evidence. They document the current implementation and validation stage, with further work to follow.
Further visual refinement will follow visible differences and corresponding experiments. Material-system articles will cover parameter organization, resource binding, and reuse; scene articles will examine characters under different environments, lighting, and spatial conditions. P3 prioritizes the topics below, with other articles following implementation and validation progress.
The third article focuses on temporal and Bloom correspondence, body-skin Ramp/LUT bindings, and shadow-overlay pixel validation. The first two retain the boundaries of current implementation and static evidence rather than counting this future work as complete.
The seven non-hair groups comprise four reference/UE appearance comparisons and three historical corrections. They give cloth, shoulder armor, and hands concrete regions to inspect, but do not complete the ten controlled-effect experiments. Suitable source images or tests are still missing for skin/SDF, received and self-shadowing, global/local outlines, and material rim lighting. Historical comparisons do not replace them.
Appendix A: Complete Environment BRDF Coefficients
The existing environment BRDF is retained for comparison with the visor reflection chain. It computes environment response only, excluding main and local lights, material gates, and final exposure. Read the rational A/B expressions together with final energy combination.
float3 CharacterCapturedEnvironmentBRDF(float3 F0, float Roughness, float NoV)
{
const float N2 = NoV * NoV;
const float N3 = N2 * NoV;
const float R2 = Roughness * Roughness;
const float R6 = R2 * R2 * R2;
const float A = (3.32707f * NoV + 0.0365463f
+ (-9.04756f * NoV + 9.0632f) * R2)
/ (3.59685f * N2 - 1.36772f * N3 + 1.0f
+ (-16.3174f * N2 + 9.04401f + 9.22949f * N3) * R2
+ (5.56589f + 19.788601f * N2 - 20.212299f * N3) * R6);
const float B = (0.99044f - 1.28514f * NoV
+ (1.29678f - 0.755907f * NoV) * R2)
/ (2.92338f * NoV + 59.4188f * N3 + 1.0f
+ (20.3225f - 27.030199f * NoV + 222.591995f * N3) * R2
+ (626.130005f * NoV + 316.627014f * N3 + 121.563004f) * R6);
return (F0 * A + B) * (1.0f + F0 * ((1.0f - A - B) / (A + B)));
}
Appendix B: Run Configuration and Capture Validity
Inputs and Switches Explicitly Enabled in the Final Configuration
The existing final-run arguments illustrate the static configuration’s scope. The complete runtime environment is not included:
& (Join-Path $PSScriptRoot 'run_goal_headless.ps1') -Round $Round -DryRun:$DryRun `
-StrictHairDepth -NoArtificialHairRing -UnitHairLocalEnergy -ExactHairRS `
-ExactEnvironmentCube -ExactVisorNormal -SourceHeadPose
| Switch | Current meaning |
|---|---|
| StrictHairDepth | Strict comparison for the target transparent hair redraw |
| NoArtificialHairRing | Disables historical artificial-ring compensation |
| UnitHairLocalEnergy | Sets the corresponding local-energy control to 1; this does not mean every composition coefficient is 1 |
| ExactHairRS | Temporarily binds source RS with the correct orientation |
| ExactEnvironmentCube | Original compressed Cube, all mips, corresponding sampling |
| ExactVisorNormal | Original normal input for the target visor |
| SourceHeadPose | Source-supported head/hair rotations and shared pose |
Source visor Base/Packed experiments are not included. The presence of a specially fitted shadow writer in source also does not establish activation.
Results Combine Scene, Code, Inputs, and Run Configuration
The original static validation level and BuiltData remain unchanged. The restoration script loads the scene in the background and applies temporary materials, resources, and poses in memory without saving them into the original level.
The final result combines original assets, engine code, external source inputs, and a fixed run configuration. Opening the original level directly does not apply runtime overrides and can therefore produce a different image.
Moving to another machine requires original models, textures, scene assets, the corresponding engine and capture tools, and their manifests and checksums. Article excerpts alone cannot reconstruct the full environment. Explain changes in source line endings, plugin versions, or engine association before updating checksum expectations.
The study uses offscreen execution and background replay: check inputs and compilation, apply the run configuration, start capture, await image completion, then verify capture validity and clean shutdown. Compilation, capture, and input-control failures are distinct; only valid runs enter visual comparison.
Valid N1 Capture Identity
The valid run goal_close_n1_basis_v2_20260906 records:
worker exit 0
material compile failures 0
viewport 1920 × 1080
main hair event 27407
transparent hair event 27821
main color resource 63351
shader stamp 2026.09.06-04.17.39
DDC GUID 08DA420A04964858960D82503489E695
Logs, shader timestamps, actual DXIL, events, and output targets must agree before declaring the new code captured. A Custom Forward compilation failure with default-shading fallback invalidates an experiment even if the screenshot looks normal.
The first capture encountered an EndFrameCapture access exception and was excluded. Only the successful second capture became a valid candidate; the first run’s PNG cannot serve as evidence for it.
Historical Local Shadow-overlay Records
Source full-frame records include OverlayShadow variants using small shadow-shape maps and auxiliary preparation. For this character’s capture 26, actual pixel-changing draws still need tracing. Existing overlay records show no pixel changes for EIDs 1382/1395/1414 in that check; 1431 changes 262 HDR pixels behind the fringe.
Event 1431 uses Zero, SrcColor blend factors rather than ordinary alpha blending:
The multiplication formula appears in the main shadow-overlay section.
The source constant is (0.673859537, 0.531917334, 0.567861497), with measured median post/pre ratio approximately (0.671053, 0.529661, 0.562500). The active texture is white 4×4, with visibility near one in the effective region, supporting restoration of a constant factor for this fixed capture. The 32×32 shape maps in other captures do not apply to this input.
Original Image Index
Location images are reduced in the seven composites, while detail panels preserve original pixels; enlarge them for inspection. All independent PNGs and uncomposited originals remain in the non-hair effect image index. Compositing changes neither provenance nor experiment status.
AI Collaboration Retrospective
Existing records show human image review repeatedly identifying local issues: highlight-ring position and variation, central brightness, visor reflection, and fragmented hair cards after pose changes. AI helped trace shader inputs and register dependencies, organize sampling and geometry data, and assemble run comparisons. Valid findings came from agreement among screenshots, actual resources, code, and numbers.
Errors included retaining stale register semantics after overwrites, treating debugger sampling as hardware truth, and compensating for incorrect transparency coverage with extra energy. Corrections required rechecking last writers, actual RTs, and resource bytes, and splitting combined changes into separate experiments. Runs failing capture or geometry checks were excluded from successful validation.
This article organizes existing evidence without replaying RDCs or redoing the character rendering. Four worsening samples, cross-run geometry drift, Eye2 simplification, and other unvalidated conditions remain documented. AI assisted tracing and explanation; broader visual conclusions still require corresponding experiments and human review.
1 thought on “Reconstructing Stylized Character Rendering — Part 2: Part-specific Materials and Shading Algorithms”