render: world shading v2 - per-pixel normal/detail/spec maps on env families
This commit is contained in:
parent
64fedeb7f0
commit
a64c9afb9a
4 changed files with 2317 additions and 1927 deletions
|
|
@ -58,6 +58,10 @@ cbuffer S : register(b1) {
|
|||
// (cacstamp_skin_nisPS port; see SampleCsmShadow). sh_misc.zw carry the
|
||||
// atlas dimensions for the point-snapped taps.
|
||||
float4 sh_char;
|
||||
// World-shading v2: x = stored-tangent polarity (the sign relating
|
||||
// cross(binormal, normal) x handedness to the game's tangent; +-1,
|
||||
// cvar-tunable against the emulated reference), yzw spare.
|
||||
float4 sh_v2;
|
||||
};
|
||||
// Character-family lighting (defaultcharacter.fx and friends): canonical
|
||||
// per-draw rows captured from the guest PIXEL constant bank at palette
|
||||
|
|
@ -84,6 +88,12 @@ Texture2D<float4> decal_art : register(t4);
|
|||
Texture2D<float4> normal_map : register(t5);
|
||||
Texture2D<float2> shadow_atlas : register(t7);
|
||||
TextureCube<float4> env_cube : register(t6);
|
||||
// World-shading v2 material maps (the t8/t9 pair table): the environment
|
||||
// families' detail normal map (t8, sampled at uv * misc.w) and the decal
|
||||
// families' spec/ecc masks (t9). Sampled only when the misc.z bind flags
|
||||
// say so (fams 1-4; see the env branch).
|
||||
Texture2D<float4> detail_map : register(t8);
|
||||
Texture2D<float4> spec2_map : register(t9);
|
||||
// Raw bone palette: 3 float4 rows per bone, column-vector affine [R | t],
|
||||
// applied with explicit dots (StructuredBuffer<float4x4> default packing is
|
||||
// column-major and would silently transpose the matrices).
|
||||
|
|
@ -101,6 +111,11 @@ struct VSOut {
|
|||
float2 uv2 : TEXCOORD2;
|
||||
float3 nrm : TEXCOORD3;
|
||||
float2 uv3 : TEXCOORD4;
|
||||
// Stored tangent frame (world-shading v2): xyz = the mesh's authored
|
||||
// binormal (world-rotated; zero when the mesh carries none), w = the
|
||||
// tangent handedness sign. Rides the static meshes' otherwise-unused
|
||||
// blend-weight bytes (see DecodeMesh).
|
||||
float4 tanb : TEXCOORD5;
|
||||
};
|
||||
VSOut vs_main(float3 p : POSITION, float2 uv : TEXCOORD0, float2 uv2 : TEXCOORD1,
|
||||
float4 bw : BLENDWEIGHT0, uint4 bi : BLENDINDICES0,
|
||||
|
|
@ -134,6 +149,13 @@ VSOut vs_main(float3 p : POSITION, float2 uv : TEXCOORD0, float2 uv2 : TEXCOORD1
|
|||
o.uv2 = uv2;
|
||||
o.nrm = n;
|
||||
o.uv3 = uv3;
|
||||
// Stored binormal + handedness from the blend-weight bytes (statics
|
||||
// only; skinned items use those bytes for actual weights). w passes
|
||||
// RAW: ~0 = no stored frame, ~0.39 = negative handedness, ~0.78 =
|
||||
// positive (the DecodeMesh sentinel bytes 0/100/200).
|
||||
float3 sb = bw.xyz * 2.0 - 1.0;
|
||||
o.tanb = tint.g > 0.0 ? float4(0.0, 0.0, 0.0, 0.0)
|
||||
: float4(mul(sb, (float3x3)world), bw.w);
|
||||
return o;
|
||||
}
|
||||
// Native CSM atlas sample at a world position: finest covering cascade,
|
||||
|
|
@ -542,16 +564,17 @@ float4 ps_main(VSOut i) : SV_Target {
|
|||
float3 lmg = lightmap.SampleLevel(smp_clamp, i.uv2, 0.0).rgb;
|
||||
// F12 isolation mode 7: visualize the RAW lightmap sample; mode 8:
|
||||
// visualize the lightmap unwrap coordinate (frac(uv2*16) in rg).
|
||||
// Debug-only taps on fams 5/6/13 (misc.z is 0 on the other fams).
|
||||
if (abs(misc.z - 7.0) < 0.5) {
|
||||
// Debug taps live on fams 5/6/13 only; on fams 1-4 misc.z carries
|
||||
// the v2 material bind flags instead (family-gated below).
|
||||
if (fam > 4.5 && abs(misc.z - 7.0) < 0.5) {
|
||||
return float4(lmg, 1.0);
|
||||
}
|
||||
if (abs(misc.z - 8.0) < 0.5) {
|
||||
if (fam > 4.5 && abs(misc.z - 8.0) < 0.5) {
|
||||
return float4(frac(i.uv2 * 16.0), 0.0, 1.0);
|
||||
}
|
||||
// Mode 9: lightmap-resolve status; RED = a real lightmap is bound
|
||||
// (tint.r set by the C++ resolve), BLUE = white fallback in t1.
|
||||
if (abs(misc.z - 9.0) < 0.5) {
|
||||
if (fam > 4.5 && abs(misc.z - 9.0) < 0.5) {
|
||||
return float4(tint.r, 0.0, 1.0 - tint.r, 1.0);
|
||||
}
|
||||
// tint.r == 0 = the real lightmap has not resolved yet (first-sight
|
||||
|
|
@ -560,21 +583,94 @@ float4 ps_main(VSOut i) : SV_Target {
|
|||
// patches for the decode window (the ramp-stencil "black square
|
||||
// flash"); serve unshadowed brightness until the real page lands.
|
||||
float3 lml = tint.r > 0.0 ? min(lmg * lmg, s + sh_color.rgb) : lmg * lmg;
|
||||
// GetTangentLight with the neutral (flat) normal map:
|
||||
// 0.39 * 2.39562 exactly. Fam 13 has no kd term at all; its body is
|
||||
// D^2 * lml * ALPHA, premultiplied once in the shader on top of the
|
||||
// a^2 blend factor (verified 0.0-error vs the ucode).
|
||||
lin = fam > 12.5 ? lml * dcol * albedo.a : lml * 0.93429 * dcol;
|
||||
if (overlay.w > 2.5) {
|
||||
// spec/ecc/refmask at t4: phong vs the FIXED literal light
|
||||
// (-0.14, 0.5, 0.9), power 10 + 290*ecc, tint (2.1, 1.8, 1.5),
|
||||
// scaled by the clamped lightmap green and the spec mask.
|
||||
float4 masks = decal_art.Sample(smp, i.uv);
|
||||
float3 wn = dot(i.nrm, i.nrm) > 0.01
|
||||
? normalize(i.nrm)
|
||||
: normalize(cross(ddx(i.rpos), ddy(i.rpos)));
|
||||
// misc.z = 3 (F12 reflection isolation): force the flat normal.
|
||||
if (overlay.w > 3.5 && abs(misc.z - 3.0) > 0.5) {
|
||||
// GetTangentLight (world-shading v2). vnd is the tangent-space mapped
|
||||
// normal from the material's base normal map (t5) + detail map (t8 at
|
||||
// uv * misc.w): xy = 2*base + 2*detail - 2, z = 2*base.z - 1.
|
||||
// baseenvironment/decal normalize vnd for the kd dot; default-
|
||||
// environment (plain normal map, no detail pair) uses it raw. Fams
|
||||
// 1-4 carry bind flags in misc.z (1 = base normal at t5, 2 = detail
|
||||
// at t8, 4 = spec2ch at t9); fams 5/6 derive vnd from the reflective
|
||||
// path's nt below. Without a normal map the exact flat-map fold
|
||||
// 0.39 * 2.39562 = 0.93429 applies (the v1 constant).
|
||||
uint v2f = fam < 4.5 ? (uint)(misc.z + 0.5) : 0u;
|
||||
float kd = 0.93429;
|
||||
float3 wn = dot(i.nrm, i.nrm) > 0.01
|
||||
? normalize(i.nrm)
|
||||
: normalize(cross(ddx(i.rpos), ddy(i.rpos)));
|
||||
float3 vnd_raw = float3(0.0, 0.0, 1.0); // wn mapping (max-z clamped)
|
||||
float3 vnd = float3(0.0, 0.0, 1.0); // kd dot (family-normalized)
|
||||
bool have_vnd = false;
|
||||
if ((v2f & 1u) != 0u) {
|
||||
float3 nmv = normal_map.Sample(smp, i.uv).rgb;
|
||||
float2 dxy = (v2f & 2u) != 0u
|
||||
? detail_map.Sample(smp, i.uv * misc.w).rg
|
||||
: float2(0.5, 0.5);
|
||||
vnd_raw = float3(nmv.xy * 2.0 + dxy * 2.0 - 2.0, nmv.z * 2.0 - 1.0);
|
||||
vnd = (fam < 1.5 || fam > 2.5) ? normalize(vnd_raw) : vnd_raw;
|
||||
have_vnd = true;
|
||||
}
|
||||
// Tangent frames shared by kd and the reflective normal mapping.
|
||||
// Two variants of the same construction:
|
||||
// - tt_s/bb_s = the RAW screen-space UV-gradient frame. Exact for
|
||||
// the kd SIGN terms under ANY UV orientation; bowl/ramp walls
|
||||
// map their texture rotated relative to world-up, and mirrored
|
||||
// islands flip dP/dU exactly like the stored tangent does.
|
||||
// - tt/bb = analytic world-up axes carrying the screen frame's
|
||||
// signs; degree-accurate where the mapping IS world-up aligned
|
||||
// (building facades, calibrated for the glass reflections).
|
||||
// Transferring signs onto misaligned axes is METASTABLE when the
|
||||
// UV grid sits ~90 deg off world-up: the transfer dots cross zero
|
||||
// along a curved wall and kd banded light/dark around the bowl
|
||||
// transition (seen in a matched F11 capture pair), which is why
|
||||
// kd and the fam 1-4 normal mapping use the screen frame instead.
|
||||
float3 tt = float3(1.0, 0.0, 0.0), bb = float3(0.0, 1.0, 0.0);
|
||||
float3 tt_s = tt, bb_s = bb;
|
||||
if (have_vnd || (overlay.w > 3.5 && abs(misc.z - 3.0) > 0.5)) {
|
||||
float3 dp1 = ddx(i.rpos), dp2 = ddy(i.rpos);
|
||||
float2 du1 = ddx(i.uv), du2 = ddy(i.uv);
|
||||
float3 dp2p = cross(dp2, wn), dp1p = cross(wn, dp1);
|
||||
tt = dp2p * du1.x + dp1p * du2.x;
|
||||
bb = dp2p * du1.y + dp1p * du2.y;
|
||||
tt *= -rsqrt(max(dot(tt, tt), 1e-12));
|
||||
bb *= rsqrt(max(dot(bb, bb), 1e-12));
|
||||
tt_s = tt;
|
||||
bb_s = bb;
|
||||
float3 bb2 = float3(0.0, 1.0, 0.0) - wn * wn.y;
|
||||
float lb2 = length(bb2);
|
||||
if (lb2 > 0.05) {
|
||||
bb2 /= lb2;
|
||||
float3 tt2 = cross(bb2, wn);
|
||||
tt = tt2 * (dot(tt2, tt) >= 0.0 ? 1.0 : -1.0);
|
||||
bb = bb2 * (dot(bb2, bb) >= 0.0 ? 1.0 : -1.0);
|
||||
}
|
||||
}
|
||||
// kd axes: prefer the mesh's STORED frame (binormal + handedness in
|
||||
// i.tanb, T = cross(B, N) x handedness x calibration polarity);
|
||||
// authoring decides per UV island whether the frame follows a
|
||||
// mirror, which no derivative frame can know (per-island brightness
|
||||
// steps on the wooden ramp panels / faint bowl striping). Screen
|
||||
// frame is the fallback for meshes without one.
|
||||
float3 kt = tt_s, kb = bb_s;
|
||||
if (i.tanb.w > 0.1) {
|
||||
kb = normalize(i.tanb.xyz);
|
||||
kt = cross(kb, wn) *
|
||||
((i.tanb.w > 0.6 ? 1.0 : -1.0) * sh_v2.x);
|
||||
}
|
||||
if (have_vnd) {
|
||||
// The mapped world normal feeds the spec mirror below; matte spec
|
||||
// is broad-lobed, so exact axis precision matters less than the
|
||||
// per-island signs (the analytic substitution exists for
|
||||
// mirror-sharp glass).
|
||||
wn = normalize(vnd_raw.x * kt + vnd_raw.y * kb +
|
||||
wn * max(vnd_raw.z, 0.05));
|
||||
kd = (vnd.x * 0.58 * sign(dot(kt, sh_sun.xyz)) +
|
||||
vnd.y * 0.62 * sign(dot(kb, sh_sun.xyz)) +
|
||||
vnd.z * 0.39) * 2.39562;
|
||||
}
|
||||
// misc.z = 3 (F12 reflection isolation): force the flat normal.
|
||||
// Only the reflective families (5/6/13) ever carry overlay.w == 4;
|
||||
// fams 1-4 signal their normal map through the misc.z flags above.
|
||||
if (overlay.w > 3.5 && abs(misc.z - 3.0) > 0.5) {
|
||||
// Per-pixel normal map (t5, paired descriptor): the real PS
|
||||
// (baseenvironmentreflective_defaultPS) reflects off the
|
||||
// normal-mapped normal: tangent normal = 2*(n + detail - 1) on
|
||||
|
|
@ -608,46 +704,29 @@ float4 ps_main(VSOut i) : SV_Target {
|
|||
float2 trim = float2(misc.x - trim_yi * 1000.0 - 500.0, trim_yi - 500.0) *
|
||||
0.001;
|
||||
float3 nt = float3(nmv.xy * 2.0 - 1.0 + trim, nmv.z * 2.0 - 1.0);
|
||||
float3 dp1 = ddx(i.rpos), dp2 = ddy(i.rpos);
|
||||
float2 du1 = ddx(i.uv), du2 = ddy(i.uv);
|
||||
float3 dp2p = cross(dp2, wn), dp1p = cross(wn, dp1);
|
||||
float3 tt = dp2p * du1.x + dp1p * du2.x;
|
||||
float3 bb = dp2p * du1.y + dp1p * du2.y;
|
||||
// Signs/lengths calibrated against the mesh's REAL stored TBN
|
||||
// (10_11_11 tangent @ +24, s16 normal xy @ +20, handedness in the
|
||||
// unwrap sign bits, decoded mesh-wide):
|
||||
// stored T == +dP/dU on 100% of tris (this formula's tt comes out
|
||||
// as -dP/dU -> negate), stored B == this bb direction (dot 0.999),
|
||||
// and the game's rows are UNIT so each axis normalizes SEPARATELY
|
||||
// - the shared max-normalizer both halved and (with the flip)
|
||||
// mirrored the horizontal tilt, which pushed the reflection
|
||||
// DEEPER into the dark cube region instead of off it (first
|
||||
// deploy looked unchanged). With these signs the reconstruction
|
||||
// matches the ucode's mapped normal to 4 decimals at the traced
|
||||
// pixel. An inward geometric wn flips all three terms together;
|
||||
// reflect() is invariant to that.
|
||||
tt *= -rsqrt(max(dot(tt, tt), 1e-12));
|
||||
bb *= rsqrt(max(dot(bb, bb), 1e-12));
|
||||
// The mesh's STORED TBN (what the game's VS interpolates) is the
|
||||
// world-up frame, not the exact UV-gradient frame: B = up
|
||||
// projected into the surface, T = cross(B, N), verified on the
|
||||
// decoded window vertices (B = (0, 0.999, 0), T = horizontal).
|
||||
// The screen-space frame lands ~3 deg off it (authored TBN vs UV
|
||||
// gradients), which rotates reflections by degrees, visible as
|
||||
// parallax against the emulated frame. Use the analytic axes and
|
||||
// keep only the screen-space frame's SIGNS (mirrored UV islands
|
||||
// flip the tangent; derivatives track that, the analytic frame
|
||||
// cannot). Near-horizontal surfaces keep the screen frame.
|
||||
float3 bb2 = float3(0.0, 1.0, 0.0) - wn * wn.y;
|
||||
float lb2 = length(bb2);
|
||||
if (lb2 > 0.05) {
|
||||
bb2 /= lb2;
|
||||
float3 tt2 = cross(bb2, wn);
|
||||
tt = tt2 * (dot(tt2, tt) >= 0.0 ? 1.0 : -1.0);
|
||||
bb = bb2 * (dot(bb2, bb) >= 0.0 ? 1.0 : -1.0);
|
||||
}
|
||||
// (Frame construction hoisted above; the calibration notes ride
|
||||
// with it; tt/bb here are the shared axes.)
|
||||
wn = normalize(nt.x * tt + nt.y * bb + wn * max(nt.z, 0.05));
|
||||
}
|
||||
// v2: the reflective families share the kd term; fam 5
|
||||
// (reflective) is a base-family material and normalizes vnd for
|
||||
// the dot like baseenvironment; fam 6 (reflective_simple) uses
|
||||
// it raw like the other _simple family. kt/kb = the shared kd
|
||||
// axes computed above (stored frame when the mesh carries one),
|
||||
// built from the pre-mapping geometric normal.
|
||||
float3 v56 = fam < 5.5 ? normalize(nt) : nt;
|
||||
kd = (v56.x * 0.58 * sign(dot(kt, sh_sun.xyz)) +
|
||||
v56.y * 0.62 * sign(dot(kb, sh_sun.xyz)) +
|
||||
v56.z * 0.39) * 2.39562;
|
||||
}
|
||||
// Fam 13 has no kd term at all; its body is D^2 * lml * ALPHA,
|
||||
// premultiplied once in the shader on top of the a^2 blend factor
|
||||
// (verified 0.0-error vs the ucode).
|
||||
lin = fam > 12.5 ? lml * dcol * albedo.a : lml * kd * dcol;
|
||||
if (overlay.w > 2.5) {
|
||||
// spec/ecc/refmask at t4: phong vs the FIXED literal light
|
||||
// (-0.14, 0.5, 0.9), power 10 + 290*ecc, tint (2.1, 1.8, 1.5),
|
||||
// scaled by the clamped lightmap green and the spec mask.
|
||||
float4 masks = decal_art.Sample(smp, i.uv);
|
||||
float3 vd = -normalize(i.rpos);
|
||||
float3 Ls = float3(-0.14, 0.5, 0.9);
|
||||
float3 refl = Ls - 2.0 * wn * dot(wn, Ls);
|
||||
|
|
@ -686,6 +765,18 @@ float4 ps_main(VSOut i) : SV_Target {
|
|||
}
|
||||
}
|
||||
}
|
||||
// v2 decal-family spec (spec/ecc at t9): the same phong-vs-fixed-
|
||||
// light term as the base families; the decal art occupies t4 on
|
||||
// fams 3/4, so their masks ride the second pair table.
|
||||
if ((v2f & 4u) != 0u) {
|
||||
float2 m2 = spec2_map.Sample(smp, i.uv).rg;
|
||||
float3 vd2 = -normalize(i.rpos);
|
||||
float3 Ls2 = float3(-0.14, 0.5, 0.9);
|
||||
float3 refl2 = Ls2 - 2.0 * wn * dot(wn, Ls2);
|
||||
float ks2 =
|
||||
pow(max(saturate(dot(vd2, -refl2)), 1e-6), 10.0 + 290.0 * m2.y);
|
||||
lin += ks2 * float3(2.1, 1.8, 1.5) * lml.g * m2.x;
|
||||
}
|
||||
if (fam > 12.5) {
|
||||
// Fam 13 blend factor: the PS outputs a^2 (straight-alpha blend on
|
||||
// top of the in-shader premultiplied body; wisps thin as ~a^3,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -82,6 +82,19 @@ REXCVAR_DEFINE_INT32(skate3_native_render_scene_msaa, 4, "Skate 3",
|
|||
"fix texture aliasing.")
|
||||
.range(1, 8)
|
||||
.lifecycle(rex::cvar::Lifecycle::kRequiresRestart);
|
||||
REXCVAR_DEFINE_DOUBLE(skate3_native_render_scene_world_v2_tan_sign, 1.0, "Skate 3",
|
||||
"World-shading v2: polarity relating the stored frame's "
|
||||
"cross(binormal, normal) x handedness to the game's tangent "
|
||||
"(+1/-1). Calibrated against the emulated reference; flips the "
|
||||
"kd sun-direction response if wrong.")
|
||||
.range(-1.0, 1.0)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_world_v2, true, "Skate 3",
|
||||
"World-shading v2: per-pixel normal/detail/spec maps on the exact "
|
||||
"environment families (the game's own GetTangentLight kd + phong "
|
||||
"terms; v1 folded them to the flat-map constants). Off = the v1 "
|
||||
"flat response, for A/B comparison.")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_ssao, true, "Skate 3",
|
||||
"Screen-space ambient occlusion (GTAO) in the native renderer: soft "
|
||||
"contact shading where surfaces meet (under ledges, rails, vehicles, "
|
||||
|
|
@ -2568,6 +2581,14 @@ bool BuildItemFromMesh(uint8_t* base, uint32_t mesh, DrawItem& item) {
|
|||
// the animated.tree "noise" tint map, bound at t4 on families
|
||||
// that carry no decal art (see DrawItem::spec_tex).
|
||||
slot = &item.spec_tex;
|
||||
} else if (std::memcmp(text, "detailNormalUVScale", 20) == 0) {
|
||||
// Shader-constant channel (the game's VS c8.x, typically 8): the
|
||||
// detail normal map's UV multiplier, consumed by the v2 kd term.
|
||||
const float f = std::bit_cast<float>(chan_u32(i, 0x10));
|
||||
if (f > 0.0f && f < 1e6f) {
|
||||
item.detail_scale = f;
|
||||
}
|
||||
continue;
|
||||
} else if (std::memcmp(text, "macroOverlayUVScale", 20) == 0 ||
|
||||
std::memcmp(text, "macroOverlayOpacity", 20) == 0) {
|
||||
// Shader-constant channel: the float lives in the first guid word.
|
||||
|
|
@ -10817,7 +10838,27 @@ bool DecodeMesh(nrhi::Device* device, uint8_t* base, const DrawItem& item,
|
|||
n3[1] = ny / 32767.0f;
|
||||
const float d = 1.0f - n3[0] * n3[0] - n3[1] * n3[1];
|
||||
n3[2] = (sy > 0 ? 1.0f : -1.0f) * std::sqrt(d > 0.0f ? d : 0.0f);
|
||||
(void)sx; // sign.x = tangent handedness, unused until normal maps
|
||||
// World-shading v2: the STORED tangent frame. These static meshes
|
||||
// never skin, so the blend-weight bytes are free: pack the mesh's
|
||||
// k_10_11_11 binormal (unorm-mapped) plus the unwrap sign.x tangent
|
||||
// handedness into them. The shader reconstructs T = cross(B, N) x
|
||||
// handedness; authoring decides per UV island whether the frame
|
||||
// follows a mirror, which derivative frames cannot know (the wooden
|
||||
// ramp panels banded light/dark per island without this).
|
||||
if (!item.skinned && item.normal_fmt == 16) {
|
||||
float b3[3];
|
||||
unpack_10_11_11(item.normal_offset, b3);
|
||||
const auto pk = [](float f) {
|
||||
return uint32_t(std::lround((f * 0.5f + 0.5f) * 255.0f)) & 0xFFu;
|
||||
};
|
||||
// w byte is a three-state presence sentinel: 0 = no stored frame
|
||||
// (meshes that skip this pack leave the word zero), 100 = negative
|
||||
// handedness, 200 = positive.
|
||||
const uint32_t packed = pk(b3[0]) | (pk(b3[1]) << 8) |
|
||||
(pk(b3[2]) << 16) |
|
||||
((sx > 0 ? 200u : 100u) << 24);
|
||||
std::memcpy(&dst[v * 14 + 7], &packed, 4);
|
||||
}
|
||||
} else if (item.normal_fmt == 16) {
|
||||
// (Vehicle meshes: the game's VS derives cross(tangent, binormal)
|
||||
// instead, but the stored usage-3 normal MATCHES it on every vertex
|
||||
|
|
@ -12476,14 +12517,18 @@ bool EnsureRootSignature(const NativeGuestOutputRenderContext& context) {
|
|||
// overlay.w == 4 (pair bound).
|
||||
ld.params[5] = {nrhi::BindingParamKind::kTextureTable, 4, 2,
|
||||
nrhi::Visibility::kPixel};
|
||||
// Dynamic-shadow additions: per-frame receiver constants (b1) + the
|
||||
// blurred shadow atlas (t5).
|
||||
// Dynamic-shadow additions: per-frame receiver constants (b1).
|
||||
ld.params[6] = {nrhi::BindingParamKind::kConstantBuffer, /*b*/ 1, 1,
|
||||
nrhi::Visibility::kPixel};
|
||||
ld.params[7] = {nrhi::BindingParamKind::kTextureTable, 7, 1,
|
||||
// Environment cube (t6) + blurred shadow atlas (t7) as ONE two-entry
|
||||
// table (bound together via SetTexturePair); merging them freed the
|
||||
// root-signature DWORD the v2 material table below needs.
|
||||
ld.params[7] = {nrhi::BindingParamKind::kTextureTable, 6, 2,
|
||||
nrhi::Visibility::kPixel};
|
||||
// Water environment cube (t6): the flowingwater/ocean reflection term.
|
||||
ld.params[8] = {nrhi::BindingParamKind::kTextureTable, 6, 1,
|
||||
// World-shading v2 material maps: the detail normal map (t8) + the
|
||||
// decal families' spec/ecc masks (t9), bound as a pair on draws whose
|
||||
// misc.z flags enable them.
|
||||
ld.params[8] = {nrhi::BindingParamKind::kTextureTable, 8, 2,
|
||||
nrhi::Visibility::kPixel};
|
||||
// Character lighting block (b2): the canonical per-draw rows captured
|
||||
// from the guest PS bank (CaptureCharLighting), sliced out of the bone
|
||||
|
|
@ -14294,9 +14339,15 @@ void WarmItemResources(const NativeGuestOutputRenderContext& context, uint8_t* b
|
|||
if (REXCVAR_GET(skate3_native_render_scene_macro)) {
|
||||
warm_texture(item.macro_tex);
|
||||
}
|
||||
if (item.water || item.char_family >= 6) {
|
||||
if (item.water || item.char_family >= 6 ||
|
||||
(item.env_family >= 1 && item.env_family <= 6)) {
|
||||
// v2: fams 1-4 sample their base normal map per-pixel too.
|
||||
warm_texture(item.water_normal);
|
||||
}
|
||||
if (item.env_family >= 1 && item.env_family <= 4 && item.env_family != 2) {
|
||||
// v2 detail normal map (t8; fam 2 carries no base+detail pair).
|
||||
warm_texture(item.detail_tex);
|
||||
}
|
||||
if (item.decal && REXCVAR_GET(skate3_native_render_scene_decals)) {
|
||||
warm_texture(item.decal_art);
|
||||
warm_fetch_words(item.decal_fetch);
|
||||
|
|
@ -14304,8 +14355,9 @@ void WarmItemResources(const NativeGuestOutputRenderContext& context, uint8_t* b
|
|||
if (item.char_family >= 4 && item.char_family <= 5) {
|
||||
warm_texture(item.hair_alpha_tex);
|
||||
}
|
||||
if ((item.env_family != 0 && !item.decal && item.env_family != 10) ||
|
||||
item.unlit) { // unlit = sky: spec_tex is the 1D sun gradient
|
||||
if ((item.env_family != 0 && item.env_family != 10) || item.unlit) {
|
||||
// unlit = sky: spec_tex is the 1D sun gradient. Decal fams (3/4) now
|
||||
// consume their spec masks too (v2, t9).
|
||||
warm_texture(item.spec_tex);
|
||||
}
|
||||
// Environment cube (negative-cached like the draw path).
|
||||
|
|
@ -14518,17 +14570,24 @@ void ProcessPrewarmEntry(uint8_t* base, const PrewarmEntry& e) {
|
|||
if (REXCVAR_GET(skate3_native_render_scene_macro)) {
|
||||
stage_texture(item.macro_tex);
|
||||
}
|
||||
if (item.water || item.char_family >= 6) {
|
||||
if (item.water || item.char_family >= 6 ||
|
||||
(item.env_family >= 1 && item.env_family <= 6)) {
|
||||
// v2: fams 1-4 sample their base normal map per-pixel too.
|
||||
stage_texture(item.water_normal);
|
||||
}
|
||||
if (item.env_family >= 1 && item.env_family <= 4 && item.env_family != 2) {
|
||||
// v2 detail normal map (t8; fam 2 carries no base+detail pair).
|
||||
stage_texture(item.detail_tex);
|
||||
}
|
||||
if (item.decal && REXCVAR_GET(skate3_native_render_scene_decals)) {
|
||||
stage_texture(item.decal_art);
|
||||
}
|
||||
if (item.char_family >= 4 && item.char_family <= 5) {
|
||||
stage_texture(item.hair_alpha_tex);
|
||||
}
|
||||
if ((item.env_family != 0 && !item.decal && item.env_family != 10) ||
|
||||
item.unlit) { // unlit = sky: spec_tex is the 1D sun gradient
|
||||
if ((item.env_family != 0 && item.env_family != 10) || item.unlit) {
|
||||
// unlit = sky: spec_tex is the 1D sun gradient. Decal fams (3/4) now
|
||||
// consume their spec masks too (v2, t9).
|
||||
stage_texture(item.spec_tex);
|
||||
}
|
||||
|
||||
|
|
@ -16837,6 +16896,10 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
|||
cb[39] = scene.family_rows[3]; // proxyworld scale (proxy PS c3.y)
|
||||
std::memcpy(cb + 40, scene.fog_color, 4 * sizeof(float));
|
||||
}
|
||||
// World-shading v2 row (sh_v2, cb[60..63]): x = stored-tangent
|
||||
// polarity.
|
||||
cb[60] =
|
||||
float(REXCVAR_GET(skate3_native_render_scene_world_v2_tan_sign));
|
||||
// dynamicobject.fx frame rows (dyn_sun/dyn_amb/dyn_misc at cb[44..55]).
|
||||
if (scene.dynobj_valid) {
|
||||
cb[44] = scene.dynobj_rows[0]; // sun dir (PS c9)
|
||||
|
|
@ -16854,7 +16917,10 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
|||
// b2 (character lighting) default: point at the ring base so the root
|
||||
// CBV is never left unset; character draws re-point it per item.
|
||||
cmd->SetConstantBuffer(9, g_r.bone_ring, bone_region);
|
||||
cmd->SetTexture(7, shadow_ready ? g_r.shadow_srv_final : g_r.white.srv);
|
||||
// t6/t7 ride one table: default = white cube + this frame's atlas
|
||||
// (draw_item re-pairs with the item's real cube when one resolves).
|
||||
cmd->SetTexturePair(7, g_r.white_cube.srv,
|
||||
shadow_ready ? g_r.shadow_srv_final : g_r.white.srv);
|
||||
}
|
||||
|
||||
uint32_t drawn = 0;
|
||||
|
|
@ -17794,16 +17860,60 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
|||
g_refl_gate.store(gate, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
// World-shading v2 (fams 1-4): the material's real per-pixel maps. The
|
||||
// base normal map rides the free t5 pair slot (t4 holds the spec masks
|
||||
// or the decal art there), the detail normal map + the decal families'
|
||||
// spec/ecc masks ride the t8/t9 pair. misc.z carries the bind flags
|
||||
// (1 = normal, 2 = detail, 4 = spec2), misc.w the detailNormalUVScale
|
||||
// channel constant; both slots are spare on opaque fams 1-4 (fog only
|
||||
// uses them on transparent/water items, F12 only on fams 5/6/13).
|
||||
const GuestTexture* v2_detail = nullptr;
|
||||
const GuestTexture* v2_spec2 = nullptr;
|
||||
uint32_t v2_flags = 0;
|
||||
if (item.env_family >= 1 && item.env_family <= 4 && !item.transparent &&
|
||||
!item.water && debug_mode == 0 && scene.shadow_valid &&
|
||||
REXCVAR_GET(skate3_native_render_scene_world_v2)) {
|
||||
if (item.water_normal != 0) {
|
||||
const GuestTexture* nrm = resolve_texture(item.water_normal, 3);
|
||||
if (nrm != &g_r.white && nrm->valid && nrm->srv_mips != 0) {
|
||||
pair_normal = nrm;
|
||||
normal_paired = true; // t5 pair bind; overlay.w stays 0..3 here
|
||||
v2_flags |= 1;
|
||||
}
|
||||
}
|
||||
// Fam 2 (environmentsimple) carries a plain normal map with no
|
||||
// base+detail pair; its vnd is the raw 2t-1 (shader-side).
|
||||
if ((v2_flags & 1u) != 0 && item.env_family != 2 &&
|
||||
item.detail_tex != 0 && item.detail_scale > 0.0f) {
|
||||
const GuestTexture* det = resolve_texture(item.detail_tex, 7);
|
||||
if (det != &g_r.white && det->valid && det->srv_mips != 0) {
|
||||
v2_detail = det;
|
||||
v2_flags |= 2;
|
||||
}
|
||||
}
|
||||
if (item.decal && item.spec_tex != 0) {
|
||||
const GuestTexture* sp = resolve_texture(item.spec_tex, 6);
|
||||
if (sp != &g_r.white && sp->valid && sp->srv_mips != 0) {
|
||||
v2_spec2 = sp;
|
||||
v2_flags |= 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
constants[44] = item.macro_scale;
|
||||
constants[45] = item.macro_opacity;
|
||||
constants[46] = macro_tex != &g_r.white ? 1.0f : 0.0f;
|
||||
// overlay.w: 1 = single-placement decal (art clamps), 2 = tileable
|
||||
// decal (art wraps; clamping a many-period uv range stretched the
|
||||
// border texels into the giant cliff-face streaks), 3 = spec masks
|
||||
// bound (exact env families).
|
||||
constants[47] = is_decal ? (item.decal_tileable ? 2.0f : 1.0f)
|
||||
: (spec_bound ? (normal_paired ? 4.0f : 3.0f)
|
||||
: 0.0f);
|
||||
// bound (exact env families). The 4-encode (t5 normal pair driving the
|
||||
// reflective trim path) is fams 5/6 only; fams 1-4 signal their
|
||||
// normal map through the misc.z flags instead.
|
||||
constants[47] =
|
||||
is_decal ? (item.decal_tileable ? 2.0f : 1.0f)
|
||||
: (spec_bound ? ((normal_paired && item.env_family >= 5)
|
||||
? 4.0f
|
||||
: 3.0f)
|
||||
: 0.0f);
|
||||
if (item.water) {
|
||||
// overlay.x = ripple scroll time, overlay.y = real environment cube
|
||||
// bound at t6, overlay.z = ripple normal map resolved (in the macro
|
||||
|
|
@ -17833,6 +17943,11 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
|||
constants[50] = scene.fog_ramp[1];
|
||||
constants[51] = scene.fog_ramp[2];
|
||||
std::memcpy(constants + 40, scene.fog_color, 4 * sizeof(float));
|
||||
} else if (v2_flags != 0) {
|
||||
// misc.z = the v2 material bind flags, misc.w = detailNormalUVScale
|
||||
// (fams 1-4 opaque; see the v2 block above).
|
||||
constants[50] = float(v2_flags);
|
||||
constants[51] = item.detail_scale;
|
||||
} else if ((item.env_family >= 5 && item.env_family <= 6) ||
|
||||
item.env_family == 13) {
|
||||
// misc.y = cube LOD bias for the reflective families: the guest's
|
||||
|
|
@ -17938,7 +18053,14 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
|||
} else {
|
||||
cmd->SetTexture(5, decal_tex->srv);
|
||||
}
|
||||
cmd->SetTexture(8, cube_tex->srv);
|
||||
// t6 (cube) shares its table with t7 (shadow atlas); re-pair both.
|
||||
cmd->SetTexturePair(7, cube_tex->srv,
|
||||
shadow_ready ? g_r.shadow_srv_final : g_r.white.srv);
|
||||
// World-shading v2 material pair (t8 detail + t9 decal spec).
|
||||
if (v2_flags != 0) {
|
||||
cmd->SetTexturePair(8, (v2_detail != nullptr ? v2_detail : &g_r.white)->srv,
|
||||
(v2_spec2 != nullptr ? v2_spec2 : &g_r.white)->srv);
|
||||
}
|
||||
// ROPA shape blend (see RendererState::ropa_shapes): combine the shape
|
||||
// generations with the kernel weights InterpolateDynamicItems computed
|
||||
// (the SAME 8-tap boxcar / pair-lerp the body bones and garment world
|
||||
|
|
|
|||
|
|
@ -204,8 +204,12 @@ struct DrawItem {
|
|||
uint32_t spec_tex = 0;
|
||||
// "detail" channel texture (env families 5/6): a constant 16x16 BC1 whose
|
||||
// hardware-decoded value folds into the normal composition as 2*d - 1,
|
||||
// derived per material at draw time (see the refl_bias cvars).
|
||||
// derived per material at draw time (see the refl_bias cvars). On fams
|
||||
// 1/3/4 (per-pixel world shading) it is a REAL tiling detail normal map,
|
||||
// sampled per-pixel at t8 with the detailNormalUVScale channel constant
|
||||
// below.
|
||||
uint32_t detail_tex = 0;
|
||||
float detail_scale = 0.0f;
|
||||
float tint[4]; // rgb + enable flag in w
|
||||
// The item's per-draw state (bone palette for skinned, world matrix for
|
||||
// rigid model-space props) was not available at capture time; deferred
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue