render: character normal maps and specular lighting
This commit is contained in:
parent
01e2b51810
commit
275fa431c3
7 changed files with 15189 additions and 14818 deletions
|
|
@ -49,7 +49,11 @@ cbuffer CH : register(b2) {
|
||||||
// vehicles keep spec color + power in row 0 instead
|
// vehicles keep spec color + power in row 0 instead
|
||||||
float4 ch_tintA; // CAC diffuse tint / livingworld red-mask tint (w = apply)
|
float4 ch_tintA; // CAC diffuse tint / livingworld red-mask tint (w = apply)
|
||||||
float4 ch_tintB; // livingworld blue-mask tint / hair fresnel tint (w = strand-alpha scale)
|
float4 ch_tintB; // livingworld blue-mask tint / hair fresnel tint (w = strand-alpha scale)
|
||||||
float4 ch_misc; // x = alpha out, y = family
|
float4 ch_misc; // x = alpha out, y = family, z = lens-alpha flag,
|
||||||
|
// w = rim fresnel power
|
||||||
|
float4 ch_ks; // rgb = key spec color, w = power (0 = spec rows absent)
|
||||||
|
float4 ch_rs; // rgb = rim spec color, w = power
|
||||||
|
float4 ch_rim; // rgb = rim light color, w = key-spec fresnel power
|
||||||
};
|
};
|
||||||
Texture2D<float4> diffuse : register(t0);
|
Texture2D<float4> diffuse : register(t0);
|
||||||
Texture2D<float4> lightmap : register(t1);
|
Texture2D<float4> lightmap : register(t1);
|
||||||
|
|
|
||||||
|
|
@ -107,18 +107,80 @@ float4 ShadeCharacter(VSOut i, float4 albedo) {
|
||||||
} else {
|
} else {
|
||||||
// defaultcharacter / CAC pieces: key light + SH irradiance ambient,
|
// defaultcharacter / CAC pieces: key light + SH irradiance ambient,
|
||||||
// key gated by the CSM shadow (see the livingworld comment above).
|
// key gated by the CSM shadow (see the livingworld comment above).
|
||||||
float csm = min(SampleCsmShadowSoft(i.rpos + cam_pos.xyz, 0.012, cn, i.pos.xy),
|
// The material's DXT5nm normal map (x in ALPHA, y in GREEN; the PSes
|
||||||
SampleStaticSun(i.rpos + cam_pos.xyz, cn, i.pos.xy));
|
// read tf4.wy) rides the macro slot; overlay.z > 0 = resolved. The
|
||||||
|
// skinned vertex layout has no free bytes for the authored tangent
|
||||||
|
// frame (blend weights/indices own them), so use the screen-space
|
||||||
|
// cotangent frame. ScreenTangentFrame's env calibration (negated U
|
||||||
|
// axis, raw V axis) holds for characters too: projecting the skinned
|
||||||
|
// meshes through the frame's real view_proj and taking the D3D y-down
|
||||||
|
// screen-basis derivatives on the CAMERA-FACING triangles dots the
|
||||||
|
// frame against the skinned usage-6/7 tangent frame at -0.95 (tt.T) /
|
||||||
|
// +0.95 (bb.B); an edge-basis check without the projection lands on
|
||||||
|
// the back-face orientation and reads inverted. Map x rides T, map y
|
||||||
|
// rides B (the VS skins the usage-6 tangent into the interpolator the
|
||||||
|
// PS pairs with tf4.w, the usage-7 binormal into the tf4.y one).
|
||||||
|
float3 vn = cn;
|
||||||
|
if (overlay.z > 0.5) {
|
||||||
|
// misc.y = LOD bias to the console's 640p-gradient mip (same
|
||||||
|
// rationale as the reflective families' cube bias): mip 0 at 4K
|
||||||
|
// keeps fine wrinkle noise the console filters away, which reads
|
||||||
|
// as weaker authored folds than the emulated reference.
|
||||||
|
float2 nm = macro.SampleBias(smp, i.uv, misc.y).ag * 2.0 - 1.0;
|
||||||
|
float3 tt, bb;
|
||||||
|
ScreenTangentFrame(cn, i.rpos, i.uv, tt, bb);
|
||||||
|
vn = normalize(nm.x * tt + nm.y * bb +
|
||||||
|
cn * sqrt(saturate(1.0 - dot(nm, nm))));
|
||||||
|
}
|
||||||
|
float vndl_u = dot(vn, ch_light.xyz);
|
||||||
|
float vndl = saturate(vndl_u);
|
||||||
|
float csm = min(SampleCsmShadowSoft(i.rpos + cam_pos.xyz, 0.012, vn, i.pos.xy),
|
||||||
|
SampleStaticSun(i.rpos + cam_pos.xyz, vn, i.pos.xy));
|
||||||
if (ch_tintA.w > 0.0) {
|
if (ch_tintA.w > 0.0) {
|
||||||
dlin *= ch_tintA.rgb;
|
dlin *= ch_tintA.rgb;
|
||||||
}
|
}
|
||||||
float3 irr = saturate(
|
float3 irr = saturate(
|
||||||
ch_sh[0].rgb + cn.x * ch_sh[1].rgb + cn.y * ch_sh[2].rgb +
|
ch_sh[0].rgb + vn.x * ch_sh[1].rgb + vn.y * ch_sh[2].rgb +
|
||||||
cn.z * ch_sh[3].rgb + (cn.x * cn.z) * ch_sh[4].rgb +
|
vn.z * ch_sh[3].rgb + (vn.x * vn.z) * ch_sh[4].rgb +
|
||||||
(cn.z * cn.y) * ch_sh[5].rgb + (cn.y * cn.x) * ch_sh[6].rgb +
|
(vn.z * vn.y) * ch_sh[5].rgb + (vn.y * vn.x) * ch_sh[6].rgb +
|
||||||
(cn.z * cn.z) * ch_sh[7].rgb +
|
(vn.z * vn.z) * ch_sh[7].rgb +
|
||||||
(cn.x * cn.x - cn.y * cn.y) * ch_sh[8].rgb);
|
(vn.x * vn.x - vn.y * vn.y) * ch_sh[8].rgb);
|
||||||
lin = dlin * (ch_key.rgb * ndl * csm + irr * ch_amb.w);
|
float3 lit = ch_key.rgb * vndl * csm + irr * ch_amb.w;
|
||||||
|
float3 spec = float3(0.0, 0.0, 0.0);
|
||||||
|
// Rim light + key/rim phong specular, exact from the character PSes
|
||||||
|
// (ch_ks.w == 0 = rows not captured -> the terms vanish). The key spec
|
||||||
|
// reflects the sun about the mapped normal, gated on N.L >= 0 and the
|
||||||
|
// shadow; the rim terms share the game's fixed rim direction built
|
||||||
|
// from the sun and view. Spec mask = the diffuse alpha SQUARED
|
||||||
|
// (linear-space decode, like the vehicle gloss); skin/face carry a
|
||||||
|
// dedicated mask map in the free decal slot instead (overlay.w = 3;
|
||||||
|
// 2 = map present but not yet decoded -> mask 0, because the DXT1
|
||||||
|
// skin diffuse's opaque alpha would read as a full-white mask).
|
||||||
|
if (ch_ks.w > 0.0) {
|
||||||
|
float fb = 1.0 - saturate(dot(vn, vd));
|
||||||
|
float kfres = pow(max(fb, 1e-6), ch_rim.w);
|
||||||
|
float rfres = pow(max(fb, 1e-6), ch_misc.w);
|
||||||
|
float3 rd = normalize(ch_light.xyz * float3(-1.0, 0.2, -1.0) - vd);
|
||||||
|
lit += saturate(rfres * saturate(dot(vn, rd))) * ch_rim.rgb;
|
||||||
|
float3 kr = ch_light.xyz - 2.0 * vndl_u * vn;
|
||||||
|
float ks = pow(saturate(dot(vd, -kr)), ch_ks.w) * csm *
|
||||||
|
(vndl_u >= 0.0 ? 1.0 : 0.0);
|
||||||
|
float3 rr = rd - 2.0 * dot(vn, rd) * vn;
|
||||||
|
float rs = pow(saturate(dot(vd, -rr)), ch_rs.w) * rfres;
|
||||||
|
float smask = overlay.w > 2.5 ? decal_art.SampleBias(smp, i.uv, misc.y).r
|
||||||
|
: (overlay.w > 1.5 ? 0.0 : albedo.a);
|
||||||
|
smask *= smask;
|
||||||
|
spec = saturate((ks * ch_ks.rgb * kfres + rs * ch_rs.rgb) * smask);
|
||||||
|
}
|
||||||
|
lin = dlin * lit + spec;
|
||||||
|
// The PS multiplies the lit color by the material multiplier
|
||||||
|
// m_params[0].y before the tone chain (1.2 on the gameplay banks;
|
||||||
|
// without it the jeans sit ~14% darker than the emulated reference).
|
||||||
|
// Captured into the otherwise-unused tintB.w on fams 1/2; 0 = an
|
||||||
|
// older capture without it.
|
||||||
|
if (ch_tintB.w > 0.25) {
|
||||||
|
lin *= ch_tintB.w;
|
||||||
|
}
|
||||||
// defaultcharacter/cacstamp PSes end `max oC0.w, c13.x / c22.x`:
|
// defaultcharacter/cacstamp PSes end `max oC0.w, c13.x / c22.x`:
|
||||||
// the entity's spawn fade (see the livingworld comment above).
|
// the entity's spawn fade (see the livingworld comment above).
|
||||||
out_a = ch_misc.x;
|
out_a = ch_misc.x;
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -3242,11 +3242,18 @@ void CaptureHairTint(uint8_t* base, DrawItem& item) {
|
||||||
// Ny^2)*r8); the scale and the -1 are folded into the stored rows so the
|
// Ny^2)*r8); the scale and the -1 are folded into the stored rows so the
|
||||||
// shader evaluates a plain 9-row basis.
|
// shader evaluates a plain 9-row basis.
|
||||||
//
|
//
|
||||||
// Canonical block (15 float4 rows): [0] = light dir + hair fresnel power,
|
// Canonical block (18 float4 rows): [0] = light dir + hair fresnel power,
|
||||||
// [1] = key color + exposure, [2] = flat ambient rgb + SH-ambient
|
// [1] = key color + exposure, [2] = flat ambient rgb + SH-ambient
|
||||||
// multiplier (hair ambient scalar in w), [3..11] = SH rows, [12] = tintA
|
// multiplier (hair ambient scalar in w), [3..11] = SH rows, [12] = tintA
|
||||||
// (w = apply), [13] = tintB + strand-alpha scale, [14].x = alpha out,
|
// (w = apply), [13] = tintB + strand-alpha scale (fams 1/2: w = the
|
||||||
// [14].y = family (0 = capture failed validation -> legacy shading).
|
// material multiplier m_params[0].y instead), [14].x = alpha out,
|
||||||
|
// [14].y = family (0 = capture failed validation -> legacy shading),
|
||||||
|
// [14].z = lens-alpha flag, [14].w = rim fresnel power, [15] = key spec
|
||||||
|
// color + power, [16] = rim spec color + power, [17] = rim light color +
|
||||||
|
// key-spec fresnel power. The spec/rim rows (defaultcharacter: spec c7,
|
||||||
|
// rim spec c8, rim c11, fresnel powers c6.w/c5.z; cacstamp: c16/c17/c20,
|
||||||
|
// c15.w/c14.z [+ the editor row shift]) exist on fams 1/2 only; [15].w
|
||||||
|
// stays 0 when they fail their range gates and the PS terms vanish.
|
||||||
void CaptureCharLighting(uint8_t* base, DrawItem& item) {
|
void CaptureCharLighting(uint8_t* base, DrawItem& item) {
|
||||||
if (item.char_family == 0) {
|
if (item.char_family == 0) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -3263,7 +3270,7 @@ void CaptureCharLighting(uint8_t* base, DrawItem& item) {
|
||||||
// later draw with the same buffers (the caster-pass bank is stale; its
|
// later draw with the same buffers (the caster-pass bank is stale; its
|
||||||
// shadowPS touches no PS constants), and a failed refresh must not wipe
|
// shadowPS touches no PS constants), and a failed refresh must not wipe
|
||||||
// rows a previous successful capture staged.
|
// rows a previous successful capture staged.
|
||||||
float local[60];
|
float local[72];
|
||||||
float* d = local;
|
float* d = local;
|
||||||
std::memset(local, 0, sizeof(local));
|
std::memset(local, 0, sizeof(local));
|
||||||
uint8_t fam = item.char_family;
|
uint8_t fam = item.char_family;
|
||||||
|
|
@ -3379,6 +3386,51 @@ void CaptureCharLighting(uint8_t* base, DrawItem& item) {
|
||||||
d[12 * 4 + 3] = 1.0f;
|
d[12 * 4 + 3] = 1.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Material multiplier m_params[0].y (defaultcharacter c5.y / cacstamp
|
||||||
|
// c14.y): the PS multiplies the lit color by it before the tone chain
|
||||||
|
// (1.2 on the gameplay banks). Rides the fam-1/2-unused tintB.w row;
|
||||||
|
// out-of-range keeps 0 and the PS applies 1.
|
||||||
|
{
|
||||||
|
const float m0y = row((fam == 1 ? 5u : 14u) + plus, 1);
|
||||||
|
if (m0y > 0.25f && m0y < 4.0f) {
|
||||||
|
d[13 * 4 + 3] = m0y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Rim light + key/rim phong spec rows (see the header comment; observed
|
||||||
|
// gameplay banks: key spec white pow 5, rim spec gray pow 67/255, rim
|
||||||
|
// 0.2, fresnel powers 0.2-2.0). All-or-nothing behind range gates;
|
||||||
|
// a bank that fails them keeps [15].w at 0 and the PS skips the terms.
|
||||||
|
{
|
||||||
|
const uint32_t ks_r = (fam == 1 ? 7u : 16u) + plus;
|
||||||
|
const uint32_t rs_r = (fam == 1 ? 8u : 17u) + plus;
|
||||||
|
const uint32_t rim_r = (fam == 1 ? 11u : 20u) + plus;
|
||||||
|
const float kfres = row((fam == 1 ? 6u : 15u) + plus, 3);
|
||||||
|
const float rfres = row((fam == 1 ? 5u : 14u) + plus, 2);
|
||||||
|
float sr[12];
|
||||||
|
bool spec_ok = kfres >= 0.0f && kfres < 512.0f &&
|
||||||
|
rfres >= 0.0f && rfres < 512.0f;
|
||||||
|
for (int r = 0; r < 3 && spec_ok; ++r) {
|
||||||
|
const uint32_t src = r == 0 ? ks_r : (r == 1 ? rs_r : rim_r);
|
||||||
|
for (int c = 0; c < 3; ++c) {
|
||||||
|
sr[r * 4 + c] = row(src, uint32_t(c));
|
||||||
|
spec_ok = spec_ok && sr[r * 4 + c] >= 0.0f && sr[r * 4 + c] < 64.0f;
|
||||||
|
}
|
||||||
|
sr[r * 4 + 3] = row(src, 3);
|
||||||
|
}
|
||||||
|
spec_ok = spec_ok && sr[3] >= 0.5f && sr[3] < 512.0f &&
|
||||||
|
sr[7] >= 0.5f && sr[7] < 512.0f;
|
||||||
|
if (spec_ok) {
|
||||||
|
for (int c = 0; c < 4; ++c) {
|
||||||
|
d[15 * 4 + c] = sr[c];
|
||||||
|
d[16 * 4 + c] = sr[4 + c];
|
||||||
|
}
|
||||||
|
d[17 * 4 + 0] = sr[8];
|
||||||
|
d[17 * 4 + 1] = sr[9];
|
||||||
|
d[17 * 4 + 2] = sr[10];
|
||||||
|
d[17 * 4 + 3] = kfres;
|
||||||
|
d[14 * 4 + 3] = rfres;
|
||||||
|
}
|
||||||
|
}
|
||||||
d[14 * 4 + 0] = std::clamp(row((fam == 1 ? 13u : 22u) + plus, 0), 0.0f, 1.0f);
|
d[14 * 4 + 0] = std::clamp(row((fam == 1 ? 13u : 22u) + plus, 0), 0.0f, 1.0f);
|
||||||
// character.alpha accessory (sunglass lens): tell the PS fam-1/2 branch
|
// character.alpha accessory (sunglass lens): tell the PS fam-1/2 branch
|
||||||
// to take alpha from the coverage texture at t4/uv2 (ch_misc.z); the
|
// to take alpha from the coverage texture at t4/uv2 (ch_misc.z); the
|
||||||
|
|
|
||||||
|
|
@ -109,11 +109,11 @@ struct DrawItem {
|
||||||
// windows). 0 = not a character.
|
// windows). 0 = not a character.
|
||||||
// char_rows = the canonical per-draw lighting block captured from the
|
// char_rows = the canonical per-draw lighting block captured from the
|
||||||
// PIXEL constant bank at palette-capture time (CaptureCharLighting):
|
// PIXEL constant bank at palette-capture time (CaptureCharLighting):
|
||||||
// 15 float4 rows consumed by the scene PS character branch (cbuffer CH,
|
// 18 float4 rows consumed by the scene PS character branch (cbuffer CH,
|
||||||
// b2). rows[14].y = family, > 0 only when the capture validated; items
|
// b2). rows[14].y = family, > 0 only when the capture validated; items
|
||||||
// without it fall back to the legacy empirical character shading.
|
// without it fall back to the legacy empirical character shading.
|
||||||
uint8_t char_family = 0;
|
uint8_t char_family = 0;
|
||||||
float char_rows[60] = {};
|
float char_rows[72] = {};
|
||||||
// Hair strand coverage: the hair mesh's "alpha" channel texture, sampled
|
// Hair strand coverage: the hair mesh's "alpha" channel texture, sampled
|
||||||
// at the SECOND texcoord (raw float2); hair renders alpha-blended in the
|
// at the SECOND texcoord (raw float2); hair renders alpha-blended in the
|
||||||
// sorted sub-pass (the opaque path is the blocky-helmet look).
|
// sorted sub-pass (the opaque path is the blocky-helmet look).
|
||||||
|
|
|
||||||
|
|
@ -7986,10 +7986,12 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
if (debug_mode == 0 && item.char_family != 0 &&
|
if (debug_mode == 0 && item.char_family != 0 &&
|
||||||
item.char_rows[14 * 4 + 1] > 0.0f) {
|
item.char_rows[14 * 4 + 1] > 0.0f) {
|
||||||
const uint32_t offset = (g_r.bone_ring_offset + 255u) & ~255u;
|
const uint32_t offset = (g_r.bone_ring_offset + 255u) & ~255u;
|
||||||
if (offset + 256u <= RendererState::kBoneRegionSize) {
|
// 18 float4 rows = 288 bytes -> a 512-byte slot keeps the next
|
||||||
|
// allocation 256-aligned (CBV offset requirement).
|
||||||
|
if (offset + 512u <= RendererState::kBoneRegionSize) {
|
||||||
std::memcpy(g_r.bone_ring_cpu + bone_region + offset, item.char_rows,
|
std::memcpy(g_r.bone_ring_cpu + bone_region + offset, item.char_rows,
|
||||||
sizeof(item.char_rows));
|
sizeof(item.char_rows));
|
||||||
g_r.bone_ring_offset = offset + 256u;
|
g_r.bone_ring_offset = offset + 512u;
|
||||||
cmd->SetConstantBuffer(9, g_r.bone_ring, bone_region + offset);
|
cmd->SetConstantBuffer(9, g_r.bone_ring, bone_region + offset);
|
||||||
constants[39] = item.char_rows[14 * 4 + 1];
|
constants[39] = item.char_rows[14 * 4 + 1];
|
||||||
g_char_drawn.fetch_add(1, std::memory_order_relaxed);
|
g_char_drawn.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
|
@ -8025,7 +8027,9 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
item.macro_tex != 0 && REXCVAR_GET(skate3_native_render_scene_macro)
|
item.macro_tex != 0 && REXCVAR_GET(skate3_native_render_scene_macro)
|
||||||
? resolve_texture(item.macro_tex, 2)
|
? resolve_texture(item.macro_tex, 2)
|
||||||
: &g_r.white;
|
: &g_r.white;
|
||||||
if ((item.water || item.char_family >= 6) && item.water_normal != 0) {
|
if ((item.water || item.char_family >= 6 ||
|
||||||
|
(item.char_family >= 1 && item.char_family <= 2)) &&
|
||||||
|
item.water_normal != 0) {
|
||||||
// Water rides its ripple normal map in the macro slot (water never
|
// Water rides its ripple normal map in the macro slot (water never
|
||||||
// carries a macro overlay; overlay.z stays 0 below so the macro
|
// carries a macro overlay; overlay.z stays 0 below so the macro
|
||||||
// composite path never runs). Vehicles do the same with their DXN
|
// composite path never runs). Vehicles do the same with their DXN
|
||||||
|
|
@ -8033,6 +8037,10 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
// face away from the sun and shade as a dark ambient-blue patch that
|
// face away from the sun and shade as a dark ambient-blue patch that
|
||||||
// stops at the door seams (the exact PS with a FLAT map reproduces
|
// stops at the door seams (the exact PS with a FLAT map reproduces
|
||||||
// that artifact; with the real map it matches the emulated car).
|
// that artifact; with the real map it matches the emulated car).
|
||||||
|
// Character fams 1/2 (defaultcharacter / CAC skin, face, cloth,
|
||||||
|
// lenses) do the same with their DXT5nm `normal` channel, the
|
||||||
|
// garment crease / skin pore detail the emulated render shows;
|
||||||
|
// overlay.z > 0 tells the char branch the map resolved.
|
||||||
macro_tex = resolve_texture(item.water_normal, 3);
|
macro_tex = resolve_texture(item.water_normal, 3);
|
||||||
}
|
}
|
||||||
// Water / vehicle environment cube (t6, root param 8): decoded once per
|
// Water / vehicle environment cube (t6, root param 8): decoded once per
|
||||||
|
|
@ -8154,6 +8162,24 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
sky_sun_bound = true;
|
sky_sun_bound = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Character skin/face spec-mask map (the cacstamp `specular` channel):
|
||||||
|
// rides the free decal slot like the env families. Written to
|
||||||
|
// overlay.w below: 3 = mask bound, 2 = channel present but not yet
|
||||||
|
// decoded (the PS masks the spec OFF; the DXT1 skin diffuse's opaque
|
||||||
|
// alpha would read as a full-white mask), 0 = no channel (the PS masks
|
||||||
|
// by diffuse alpha^2, cloth/jeans). char_alpha lenses keep the decal
|
||||||
|
// slot for their coverage texture.
|
||||||
|
float char_spec = 0.0f;
|
||||||
|
if ((item.char_family == 1 || item.char_family == 2) && !item.char_alpha &&
|
||||||
|
item.spec_tex != 0) {
|
||||||
|
const GuestTexture* spec = resolve_texture(item.spec_tex, 6);
|
||||||
|
if (spec != &g_r.white && spec->valid && spec->srv_mips != 0) {
|
||||||
|
decal_tex = spec;
|
||||||
|
char_spec = 3.0f;
|
||||||
|
} else {
|
||||||
|
char_spec = 2.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
const bool is_decal =
|
const bool is_decal =
|
||||||
item.char_family < 4 && item.decal && decal_tex != &g_r.white;
|
item.char_family < 4 && item.decal && decal_tex != &g_r.white;
|
||||||
// Fam 5/6 masks+normal pair (t4/t5): the reflective PS perturbs its
|
// Fam 5/6 masks+normal pair (t4/t5): the reflective PS perturbs its
|
||||||
|
|
@ -8310,6 +8336,21 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
? 4.0f
|
? 4.0f
|
||||||
: 3.0f)
|
: 3.0f)
|
||||||
: 0.0f);
|
: 0.0f);
|
||||||
|
if (char_spec != 0.0f) {
|
||||||
|
// Character fam 1/2 spec-mask state (see the bind above); chars
|
||||||
|
// never take the is_decal/spec_bound paths, so the slot is theirs.
|
||||||
|
constants[47] = char_spec;
|
||||||
|
}
|
||||||
|
if (item.char_family == 1 || item.char_family == 2) {
|
||||||
|
// misc.y = normal/spec-map LOD bias to the console's 640p-gradient
|
||||||
|
// mip (the fam 5/6 cube-bias rationale): at 4K the shader's UV
|
||||||
|
// gradients pick ~1.75 mips finer than the game's own render, and
|
||||||
|
// mip-0 sampling keeps fine wrinkle noise the console filters away
|
||||||
|
// - the authored garment folds read weaker than the emulated
|
||||||
|
// reference without this.
|
||||||
|
constants[49] =
|
||||||
|
log2f(std::max(1.0f, float(context.guest_output_height) / 640.0f));
|
||||||
|
}
|
||||||
// Exact flowingwateralpha branch (cam_pos.w = -30): the canal/waterfall
|
// Exact flowingwateralpha branch (cam_pos.w = -30): the canal/waterfall
|
||||||
// shader hand-ported from the game's own PS and verified per-pixel
|
// shader hand-ported from the game's own PS and verified per-pixel
|
||||||
// against the ucode. Requires the frame m_params rows (b1), the shared
|
// against the ucode. Requires the frame m_params rows (b1), the shared
|
||||||
|
|
|
||||||
|
|
@ -374,7 +374,7 @@ inline std::unordered_map<uint64_t, std::array<uint32_t, 12>> g_frame_draw_fetch
|
||||||
// teal-vest twins), so the fallback applies only to meshes with a single
|
// teal-vest twins), so the fallback applies only to meshes with a single
|
||||||
// instance in the frame. Guest-render-thread only (capture hooks +
|
// instance in the frame. Guest-render-thread only (capture hooks +
|
||||||
// BuildFrameScene), no lock needed.
|
// BuildFrameScene), no lock needed.
|
||||||
inline std::unordered_map<uint32_t, std::array<float, 60>> g_char_rows_cache;
|
inline std::unordered_map<uint32_t, std::array<float, 72>> g_char_rows_cache;
|
||||||
// Frame-global character CSM receive biases (see FrameScene::char_shadow_bias)
|
// Frame-global character CSM receive biases (see FrameScene::char_shadow_bias)
|
||||||
// + the guest frame they were captured on (served while <120 frames stale;
|
// + the guest frame they were captured on (served while <120 frames stale;
|
||||||
// they only change with the scene lighting setup). Guest render thread only.
|
// they only change with the scene lighting setup). Guest render thread only.
|
||||||
|
|
@ -413,7 +413,7 @@ inline std::atomic<uint64_t> g_lw_rows_served{0};
|
||||||
// legacy flat shading: the "vehicle loses its texture/color at the edge"
|
// legacy flat shading: the "vehicle loses its texture/color at the edge"
|
||||||
// sighting.
|
// sighting.
|
||||||
struct CharRowsCtx {
|
struct CharRowsCtx {
|
||||||
std::array<float, 60> rows;
|
std::array<float, 72> rows;
|
||||||
uint32_t entity = 0;
|
uint32_t entity = 0;
|
||||||
};
|
};
|
||||||
inline std::unordered_map<uint32_t, CharRowsCtx> g_char_rows_cache_ctx;
|
inline std::unordered_map<uint32_t, CharRowsCtx> g_char_rows_cache_ctx;
|
||||||
|
|
@ -429,7 +429,7 @@ inline std::unordered_map<uint32_t, CharRowsCtx> g_char_rows_cache_ctx;
|
||||||
// flicker). Entity-stamped like CharRowsCtx (0 for non-LW instances) so a
|
// flicker). Entity-stamped like CharRowsCtx (0 for non-LW instances) so a
|
||||||
// recycled ctx never inherits the previous occupant's rows.
|
// recycled ctx never inherits the previous occupant's rows.
|
||||||
struct CharRowsInst {
|
struct CharRowsInst {
|
||||||
std::array<float, 60> rows;
|
std::array<float, 72> rows;
|
||||||
uint32_t entity = 0;
|
uint32_t entity = 0;
|
||||||
};
|
};
|
||||||
inline std::unordered_map<uint64_t, CharRowsInst> g_char_rows_cache_inst;
|
inline std::unordered_map<uint64_t, CharRowsInst> g_char_rows_cache_inst;
|
||||||
|
|
@ -442,7 +442,7 @@ inline std::unordered_map<uint64_t, CharRowsInst> g_char_rows_cache_inst;
|
||||||
// rows across the pair is exact. Garment-to-garment only; body pieces keep
|
// rows across the pair is exact. Garment-to-garment only; body pieces keep
|
||||||
// their per-piece caches.
|
// their per-piece caches.
|
||||||
struct CharRowsEnt {
|
struct CharRowsEnt {
|
||||||
std::array<float, 60> rows;
|
std::array<float, 72> rows;
|
||||||
uint8_t fam = 0; // donor family: the row layout is family-specific
|
uint8_t fam = 0; // donor family: the row layout is family-specific
|
||||||
};
|
};
|
||||||
inline std::unordered_map<uint32_t, CharRowsEnt> g_char_rows_cache_ent;
|
inline std::unordered_map<uint32_t, CharRowsEnt> g_char_rows_cache_ent;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue