render: port the water and ocean shaders to exact native branches
This commit is contained in:
parent
fc9c9fdbcb
commit
75c8487ee6
7 changed files with 6622 additions and 4414 deletions
|
|
@ -70,6 +70,30 @@ cbuffer S : register(b1) {
|
||||||
float4 dyn_wsx;
|
float4 dyn_wsx;
|
||||||
float4 dyn_wsy;
|
float4 dyn_wsy;
|
||||||
float4 dyn_wsz;
|
float4 dyn_wsz;
|
||||||
|
// flowingwateralpha m_params rows (captured per frame from the water PS
|
||||||
|
// bank; consumed by the exact water branch, cam_pos.w = -30):
|
||||||
|
float4 wat_p0; // m_params[0]: tangent-normal scale xzw, material mult y
|
||||||
|
float4 wat_p1; // m_params[1]: animated-UV scroll speeds (tap1 xy, tap2 zw)
|
||||||
|
float4 wat_p2; // m_params[2]: tap UV scales (tap1 xy, tap2 zw)
|
||||||
|
float4 wat_p3; // x = g_fAnimationTime, y = mask threshold,
|
||||||
|
// z = spec power, w = alpha floor
|
||||||
|
// ocean_defaultPS rows (exact ocean branch, cam_pos.w = -31): the PCA
|
||||||
|
// mean (pre-swizzled to R,G,B channel order) + the six weight rows
|
||||||
|
// (component-0/1 pairs for R, G, B) + m_params[0..2].
|
||||||
|
float4 oc_mean;
|
||||||
|
float4 oc_w0; // R, component 0
|
||||||
|
float4 oc_w1; // R, component 1
|
||||||
|
float4 oc_w2; // G, component 0
|
||||||
|
float4 oc_w3; // G, component 1
|
||||||
|
float4 oc_w4; // B, component 0
|
||||||
|
float4 oc_w5; // B, component 1
|
||||||
|
float4 oc_p0; // m_params[0]: ward spec color
|
||||||
|
float4 oc_p1; // m_params[1]: y = multiplier, z = tonedown scale,
|
||||||
|
// w = PCA-tap uv scale
|
||||||
|
float4 oc_p2; // m_params[2]: ward spread u/v (xy), roughness (z)
|
||||||
|
// oceanreflection_defaultPS row (fam 32): x = multiplier, y/z = the
|
||||||
|
// world-height alpha fade bounds.
|
||||||
|
float4 orf;
|
||||||
};
|
};
|
||||||
// Character-family lighting (defaultcharacter.fx and friends): canonical
|
// Character-family lighting (defaultcharacter.fx and friends): canonical
|
||||||
// per-draw rows captured from the guest PIXEL constant bank at palette
|
// per-draw rows captured from the guest PIXEL constant bank at palette
|
||||||
|
|
@ -365,7 +389,8 @@ float4 ps_main(VSOut i) : SV_Target {
|
||||||
// GetTangentLight kd replaces the flat fold, and the phong spec returns.
|
// GetTangentLight kd replaces the flat fold, and the phong spec returns.
|
||||||
// With no maps bound the flat-map fold 0.39 * 2.39562 = 0.93429 and the
|
// With no maps bound the flat-map fold 0.39 * 2.39562 = 0.93429 and the
|
||||||
// vertex normal apply (the v1 look).
|
// vertex normal apply (the v1 look).
|
||||||
if (cam_pos.w < -20.5) {
|
if (cam_pos.w < -20.5 && cam_pos.w > -25.5) { // dynobj variants only:
|
||||||
|
// -21/-22; the exact water family (-30) and sky (-40) pass through.
|
||||||
if (cam_pos.w < -21.5) {
|
if (cam_pos.w < -21.5) {
|
||||||
clip(albedo.a - 0.1176); // dynamicobject.alphatest: ALPHAREF 30
|
clip(albedo.a - 0.1176); // dynamicobject.alphatest: ALPHAREF 30
|
||||||
}
|
}
|
||||||
|
|
@ -644,6 +669,143 @@ float4 ps_main(VSOut i) : SV_Target {
|
||||||
float3 lin;
|
float3 lin;
|
||||||
float out_a = 1.0;
|
float out_a = 1.0;
|
||||||
bool reduced_tone = false;
|
bool reduced_tone = false;
|
||||||
|
// flowingwateralpha (fam 30): the canal/waterfall water shader,
|
||||||
|
// hand-ported from flowingwateralpha_defaultPS and verified per-pixel
|
||||||
|
// against the ucode (dual time-scrolled ripple-normal taps -> TBN ->
|
||||||
|
// lightmap-lit body + phong sun spec + cube reflection, then the
|
||||||
|
// standard fog/exposure/tonemap chain; alpha = the spec term's green
|
||||||
|
// channel, floored; reflection strength drives opacity over the bed).
|
||||||
|
if (fam > 29.5 && fam < 30.5) {
|
||||||
|
float t = wat_p3.x;
|
||||||
|
float3 n1 = macro.Sample(smp, i.uv * wat_p2.xy + wat_p1.xy * t).rgb;
|
||||||
|
float3 n2 = macro.Sample(smp, i.uv * wat_p2.zw + wat_p1.zw * t).rgb;
|
||||||
|
float3 nsum = 2.0 * n1 + 2.0 * n2 - 2.0;
|
||||||
|
// vNormal = normalize(m_params[0].xzw * (n1 + n2)) in tangent space.
|
||||||
|
float3 vn = normalize(float3(nsum.x * wat_p0.x, nsum.y * wat_p0.z,
|
||||||
|
nsum.z * wat_p0.w));
|
||||||
|
float3 wn0 = dot(i.nrm, i.nrm) > 0.01
|
||||||
|
? normalize(i.nrm)
|
||||||
|
: normalize(cross(ddx(i.rpos), ddy(i.rpos)));
|
||||||
|
// Stored frame: tanb = the mesh's usage-6 TANGENT here (see the water
|
||||||
|
// pack in DecodeMesh), sentinel sign = the unwrap sign.x;
|
||||||
|
// B = sign.x * cross(N, T), exactly the game's VS.
|
||||||
|
float3 wt, wb;
|
||||||
|
if (i.tanb.w > 0.1) {
|
||||||
|
wt = normalize(i.tanb.xyz);
|
||||||
|
wb = cross(wn0, wt) * (i.tanb.w > 0.6 ? 1.0 : -1.0);
|
||||||
|
} else {
|
||||||
|
ScreenTangentFrame(wn0, i.rpos, i.uv, wt, wb);
|
||||||
|
}
|
||||||
|
float3 wn = normalize(vn.x * wt + vn.y * wb + vn.z * wn0);
|
||||||
|
float3 vd = -normalize(i.rpos);
|
||||||
|
float s = SampleCsmShadow(i.rpos + cam_pos.xyz, 0.0);
|
||||||
|
// Lightmap at the unwrap + 0.01 * vNormal.xz (the ripple refraction
|
||||||
|
// wobble), console fetch semantics, min-clamped like every world
|
||||||
|
// family. White-fallback pages skip the clamp (see the env fetch).
|
||||||
|
float2 wlm_uv = i.uv2 + 0.01 * vn.xz;
|
||||||
|
float3 wlm = lightmap.SampleLevel(smp_clamp, wlm_uv, 0.0).rgb;
|
||||||
|
float3 lml = tint.r > 0.0 ? min(wlm * wlm, s + sh_color.rgb)
|
||||||
|
: wlm * wlm;
|
||||||
|
// GetTangentLight on the WORLD normal: fixed axis (0.58, 0.62, 0.39)
|
||||||
|
// with xy signs following the sun direction, x 1/normalize(axis).z.
|
||||||
|
float kd = (wn.x * 0.58 * sign(sh_sun.x) +
|
||||||
|
wn.y * 0.62 * sign(sh_sun.y) + wn.z * 0.39) * 2.39562;
|
||||||
|
lin = lml * kd * dlin;
|
||||||
|
// Spec/reflection masks (t4): x = phong mask, z = reflection mask,
|
||||||
|
// thresholded by m_params[3].y. Zero until resolved: matte water.
|
||||||
|
float2 wmask = float2(0.0, 0.0);
|
||||||
|
if (overlay.x > 0.0) {
|
||||||
|
float4 wm4 = decal_art.Sample(smp, i.uv);
|
||||||
|
wmask = saturate(float2(wm4.x, wm4.z) - wat_p3.y);
|
||||||
|
}
|
||||||
|
// Phong vs the real sun, power m_params[3].z, tint (2.1, 1.8, 1.5),
|
||||||
|
// scaled by sat(clamped lightmap green - 0.1).
|
||||||
|
float3 wrl = 2.0 * wn * dot(wn, sh_sun.xyz) - sh_sun.xyz;
|
||||||
|
float ks = pow(max(saturate(dot(vd, wrl)), 1e-6), wat_p3.z);
|
||||||
|
float3 wspec =
|
||||||
|
ks * wmask.x * float3(2.1, 1.8, 1.5) * saturate(lml.g - 0.1);
|
||||||
|
// Cube reflection, same shape as fams 5/6: reflect(E, wN) with xy
|
||||||
|
// negated, luminosity lerped toward 1 by 0.3 * sat(4*mask - 2.6),
|
||||||
|
// x mask x 1.5. misc.y = the cube LOD bias (640p-gradient parity).
|
||||||
|
float3 wrv = vd - 2.0 * wn * dot(vd, wn);
|
||||||
|
float3 wcube =
|
||||||
|
overlay.y > 0.0
|
||||||
|
? env_cube.SampleBias(smp, float3(-wrv.x, -wrv.y, wrv.z),
|
||||||
|
misc.y).rgb
|
||||||
|
: float3(0.0, 0.0, 0.0);
|
||||||
|
float wrlum = 0.3 * saturate(4.0 * wmask.y - 2.6);
|
||||||
|
float3 wrefl = wcube * (lml.g + wrlum * (1.0 - lml.g)) * wmask.y * 1.5;
|
||||||
|
float3 wspec_term = wspec + wrefl;
|
||||||
|
lin += wspec_term;
|
||||||
|
float3 wxe = (lin * (fog_a * wat_p0.y) + fog_rgb) * expo;
|
||||||
|
return ToneOut(wxe, max(wspec_term.g, wat_p3.w), false);
|
||||||
|
}
|
||||||
|
// Exact ocean surface (fam 31, ocean_defaultPS; verified per-pixel
|
||||||
|
// against the ucode): PCA-compressed dual-component normal map, lerped
|
||||||
|
// toward flat by the overlay-scaled tonedown, cube reflection x squared
|
||||||
|
// lightmap x fresnel, ward anisotropic sun spec. OPAQUE (the game's PS
|
||||||
|
// outputs alpha 0 and draws without blending).
|
||||||
|
if (fam > 30.5 && fam < 31.5) {
|
||||||
|
float2 suv = i.uv * oc_p1.w;
|
||||||
|
float4 c0v = macro.Sample(smp, suv) * 2.0 - 1.0; // t3: comp 0
|
||||||
|
float4 c1v = detail_map.Sample(smp, suv) * 2.0 - 1.0; // t8: comp 1
|
||||||
|
// PCA normal in the shader's Z-UP basis; mean/weights are stored in
|
||||||
|
// 0..1 texture space and folded 2x-1 after the sum.
|
||||||
|
float3 pca = (float3(dot(c0v, oc_w0) + dot(c1v, oc_w1),
|
||||||
|
dot(c0v, oc_w2) + dot(c1v, oc_w3),
|
||||||
|
dot(c0v, oc_w4) + dot(c1v, oc_w5)) +
|
||||||
|
oc_mean.xyz) * 2.0 - 1.0;
|
||||||
|
// tonedown = 2 * m_params[1].z * sat(dot(c1v, wB1) + overlay.x):
|
||||||
|
// the ucode folds the second PCA blue dot into the overlay term.
|
||||||
|
float ovx = overlay.z > 0.0
|
||||||
|
? spec2_map.Sample(smp, i.uv * overlay.w).x
|
||||||
|
: 1.0;
|
||||||
|
float otd = (2.0 * oc_p1.z) * saturate(dot(c1v, oc_w5) + ovx);
|
||||||
|
float3 nt = normalize(lerp(float3(0.0, 0.0, 1.0), pca, otd));
|
||||||
|
nt = normalize(max(abs(nt), 0.001));
|
||||||
|
float3 N = float3(nt.x, nt.z, nt.y); // shader Z-up -> world Y-up
|
||||||
|
float3 E = -normalize(i.rpos);
|
||||||
|
// Cube reflection: reflect(E, N) with xy negated, x the SQUARED
|
||||||
|
// single-tap lightmap (no chromaticity, no CSM clamp in this PS).
|
||||||
|
float3 orv = E - 2.0 * N * dot(E, N);
|
||||||
|
float3 ocube =
|
||||||
|
overlay.y > 0.0
|
||||||
|
? env_cube.SampleBias(smp, float3(-orv.x, -orv.y, orv.z),
|
||||||
|
misc.y).rgb
|
||||||
|
: float3(0.0, 0.0, 0.0);
|
||||||
|
float2 olm_uv = i.uv2 + 0.01 * nt.xy;
|
||||||
|
float3 olm = lightmap.SampleLevel(smp_clamp, olm_uv, 0.0).rgb;
|
||||||
|
float3 orefl = ocube * (olm * olm);
|
||||||
|
float NV = abs(dot(N, E));
|
||||||
|
float F = 0.25 + 0.75 * pow(1.0 - NV, 5.0);
|
||||||
|
// Ward anisotropic sun spec (spreads oc_p2.xy, roughness oc_p2.z).
|
||||||
|
float3 oud = normalize(float3(-N.z, 0.0, N.x));
|
||||||
|
float3 ovd = cross(N, oud);
|
||||||
|
float dNE = dot(N, E);
|
||||||
|
float dNL = dot(N, sh_sun.xyz);
|
||||||
|
float oax = oc_p2.z / oc_p2.x;
|
||||||
|
float oay = oc_p2.z / oc_p2.y;
|
||||||
|
float3 H = normalize(sh_sun.xyz + E);
|
||||||
|
float eu = dot(H, oud) / oax;
|
||||||
|
float ev = dot(H, ovd) / oay;
|
||||||
|
float oden = sqrt(abs(dNL * dNE)) * 12.566371 * oax * oay;
|
||||||
|
float ward = exp(-2.0 * (eu * eu + ev * ev) / (1.0 + dot(H, N))) /
|
||||||
|
max(oden, 1e-6);
|
||||||
|
float3 ocol = orefl * F + ward * oc_p0.rgb;
|
||||||
|
float3 oxe = (ocol * (fog_a * oc_p1.y) + fog_rgb) * expo;
|
||||||
|
return ToneOut(oxe, 1.0, false);
|
||||||
|
}
|
||||||
|
// Baked ocean horizon sheet (fam 32, oceanreflection_defaultPS;
|
||||||
|
// verified per-pixel): squared sea texture x multiplier, fogged and
|
||||||
|
// tonemapped; alpha ramps with world height between the orf.z/orf.y
|
||||||
|
// bounds (the bright haze band fading in toward the horizon).
|
||||||
|
if (fam > 31.5 && fam < 32.5) {
|
||||||
|
float3 rcol = albedo.rgb * albedo.rgb * orf.x;
|
||||||
|
float ra = saturate((i.rpos.y + cam_pos.y - orf.z) /
|
||||||
|
max(orf.y - orf.z, 1e-4));
|
||||||
|
float3 rxe = (rcol * fog_a + fog_rgb) * expo;
|
||||||
|
return ToneOut(rxe, ra, false);
|
||||||
|
}
|
||||||
if (fam > 8.5 && fam < 12.5) {
|
if (fam > 8.5 && fam < 12.5) {
|
||||||
// tree/treeanimate: D^2 * max(lm^2, floor) * scale [* tint mult];
|
// tree/treeanimate: D^2 * max(lm^2, floor) * scale [* tint mult];
|
||||||
// proxyworld/incandescent: D^2 * scale. No shadow receive, no kd, no
|
// proxyworld/incandescent: D^2 * scale. No shadow receive, no kd, no
|
||||||
|
|
@ -1126,6 +1288,37 @@ float4 ps_refl_gbuf(VSOut i) : SV_Target {
|
||||||
// Env families carry cam_pos.w = -fam; water rides the legacy branch with
|
// Env families carry cam_pos.w = -fam; water rides the legacy branch with
|
||||||
// cam_pos.w = 0 (misc.x is the packed normal-trim on fams 5/6 here, so it
|
// cam_pos.w = 0 (misc.x is the packed normal-trim on fams 5/6 here, so it
|
||||||
// cannot discriminate).
|
// cannot discriminate).
|
||||||
|
if (cam_pos.w < -30.5 && cam_pos.w > -32.5) {
|
||||||
|
// Exact ocean families: no SSR contribution (the ocean keeps its own
|
||||||
|
// cube/baked reflections): zero weight, in-front visibility.
|
||||||
|
return float4(OctEncode(float3(0.0, 1.0, 0.0)), i.pos.w, -0.0);
|
||||||
|
}
|
||||||
|
if (cam_pos.w < -29.5 && cam_pos.w > -30.5) {
|
||||||
|
// Exact water (fam 30): the branch's own ripple-perturbed normal;
|
||||||
|
// weight from the thresholded reflection mask (t4.z), boosted like the
|
||||||
|
// glass arm below. Never writes scene depth -> in-front visibility.
|
||||||
|
float t = wat_p3.x;
|
||||||
|
float3 n1 = macro.Sample(smp, i.uv * wat_p2.xy + wat_p1.xy * t).rgb;
|
||||||
|
float3 n2 = macro.Sample(smp, i.uv * wat_p2.zw + wat_p1.zw * t).rgb;
|
||||||
|
float3 nsum = 2.0 * n1 + 2.0 * n2 - 2.0;
|
||||||
|
float3 vn = normalize(float3(nsum.x * wat_p0.x, nsum.y * wat_p0.z,
|
||||||
|
nsum.z * wat_p0.w));
|
||||||
|
float3 wn0 = dot(i.nrm, i.nrm) > 0.01
|
||||||
|
? normalize(i.nrm)
|
||||||
|
: normalize(cross(ddx(i.rpos), ddy(i.rpos)));
|
||||||
|
float3 wt, wb;
|
||||||
|
if (i.tanb.w > 0.1) {
|
||||||
|
wt = normalize(i.tanb.xyz);
|
||||||
|
wb = cross(wn0, wt) * (i.tanb.w > 0.6 ? 1.0 : -1.0);
|
||||||
|
} else {
|
||||||
|
ScreenTangentFrame(wn0, i.rpos, i.uv, wt, wb);
|
||||||
|
}
|
||||||
|
float3 wwn = normalize(vn.x * wt + vn.y * wb + vn.z * wn0);
|
||||||
|
float wmask = overlay.x > 0.0
|
||||||
|
? saturate(decal_art.Sample(smp, i.uv).z - wat_p3.y)
|
||||||
|
: 0.0;
|
||||||
|
return float4(OctEncode(wwn), i.pos.w, -saturate(wmask * 2.5));
|
||||||
|
}
|
||||||
if (cam_pos.w < -0.5) {
|
if (cam_pos.w < -0.5) {
|
||||||
// Fams 5/6/13: the reflective branch's own normal + reflection mask.
|
// Fams 5/6/13: the reflective branch's own normal + reflection mask.
|
||||||
// No alpha test; masked reflective draws skip the clip in ps_main too
|
// No alpha test; masked reflective draws skip the clip in ps_main too
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1650,6 +1650,7 @@ bool BuildItemFromMesh(uint8_t* base, uint32_t mesh, DrawItem& item) {
|
||||||
item.tangent_offset = 0;
|
item.tangent_offset = 0;
|
||||||
item.binormal_offset = 0;
|
item.binormal_offset = 0;
|
||||||
item.tb_fmt = 0;
|
item.tb_fmt = 0;
|
||||||
|
item.tan_fmt = 0;
|
||||||
bool have_tan = false;
|
bool have_tan = false;
|
||||||
bool have_bin = false;
|
bool have_bin = false;
|
||||||
item.skinned = false;
|
item.skinned = false;
|
||||||
|
|
@ -1692,6 +1693,9 @@ bool BuildItemFromMesh(uint8_t* base, uint32_t mesh, DrawItem& item) {
|
||||||
if (have_tan && have_bin) {
|
if (have_tan && have_bin) {
|
||||||
item.tb_fmt = 16;
|
item.tb_fmt = 16;
|
||||||
}
|
}
|
||||||
|
if (have_tan) {
|
||||||
|
item.tan_fmt = 16; // tangent-only layouts (water meshes) use this
|
||||||
|
}
|
||||||
if (!have_pos) {
|
if (!have_pos) {
|
||||||
g_rej_geom.fetch_add(1, std::memory_order_relaxed);
|
g_rej_geom.fetch_add(1, std::memory_order_relaxed);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -1754,7 +1758,10 @@ bool BuildItemFromMesh(uint8_t* base, uint32_t mesh, DrawItem& item) {
|
||||||
item.decal_tileable = false;
|
item.decal_tileable = false;
|
||||||
item.transparent = false;
|
item.transparent = false;
|
||||||
item.water = false;
|
item.water = false;
|
||||||
|
item.water_flowing = false;
|
||||||
|
item.water_ocean = 0;
|
||||||
item.water_normal = 0;
|
item.water_normal = 0;
|
||||||
|
item.water_normal2 = 0;
|
||||||
item.water_env = 0;
|
item.water_env = 0;
|
||||||
item.unlit = false;
|
item.unlit = false;
|
||||||
item.env_family = 0;
|
item.env_family = 0;
|
||||||
|
|
@ -1795,9 +1802,13 @@ bool BuildItemFromMesh(uint8_t* base, uint32_t mesh, DrawItem& item) {
|
||||||
} else if (std::memcmp(text, "decal", 6) == 0) {
|
} else if (std::memcmp(text, "decal", 6) == 0) {
|
||||||
slot = &item.decal_art;
|
slot = &item.decal_art;
|
||||||
} else if (std::memcmp(text, "normal", 7) == 0) {
|
} else if (std::memcmp(text, "normal", 7) == 0) {
|
||||||
// Exact match only ("normal2" is the second flowing-water tap).
|
// Exact match only. Consumed by the water branches (the flowing
|
||||||
// Consumed by the water branch; other families ignore it.
|
// ripple map / the ocean's first PCA component); other families
|
||||||
|
// ignore it.
|
||||||
slot = &item.water_normal;
|
slot = &item.water_normal;
|
||||||
|
} else if (std::memcmp(text, "normal2", 8) == 0) {
|
||||||
|
// The ocean's second PCA normal component map.
|
||||||
|
slot = &item.water_normal2;
|
||||||
} else if (std::memcmp(text, "alpha", 6) == 0) {
|
} else if (std::memcmp(text, "alpha", 6) == 0) {
|
||||||
// Hair strand coverage (cac_hair/defaulthair tf5, sampled at the
|
// Hair strand coverage (cac_hair/defaulthair tf5, sampled at the
|
||||||
// second texcoord), the hair alpha-blend term.
|
// second texcoord), the hair alpha-blend term.
|
||||||
|
|
@ -1922,6 +1933,18 @@ bool BuildItemFromMesh(uint8_t* base, uint32_t mesh, DrawItem& item) {
|
||||||
// fallback diffuse x near-white ocean lightmap x2).
|
// fallback diffuse x near-white ocean lightmap x2).
|
||||||
item.water = std::memcmp(mat_name, "water.", 6) == 0 ||
|
item.water = std::memcmp(mat_name, "water.", 6) == 0 ||
|
||||||
std::memcmp(mat_name, "ocean.", 6) == 0;
|
std::memcmp(mat_name, "ocean.", 6) == 0;
|
||||||
|
// water.flowing* = the flowingwateralpha shader family (canal /
|
||||||
|
// waterfall). Takes the exact water branch when the frame's
|
||||||
|
// m_params rows are captured (see FrameScene::water_rows).
|
||||||
|
item.water_flowing =
|
||||||
|
std::memcmp(mat_name, "water.flowing", 13) == 0;
|
||||||
|
// ocean.default / ocean.reflection: the sea surface and its
|
||||||
|
// baked horizon reflection sheet (see DrawItem::water_ocean).
|
||||||
|
if (std::memcmp(mat_name, "ocean.default", 14) == 0) {
|
||||||
|
item.water_ocean = 1;
|
||||||
|
} else if (std::memcmp(mat_name, "ocean.reflection", 17) == 0) {
|
||||||
|
item.water_ocean = 2;
|
||||||
|
}
|
||||||
// Exact world-shading family (see DrawItem::env_family). The
|
// Exact world-shading family (see DrawItem::env_family). The
|
||||||
// attrib <-> pixel-shader-family mapping is 1:1; the shading
|
// attrib <-> pixel-shader-family mapping is 1:1; the shading
|
||||||
// models were verified per-pixel against the game's own shaders.
|
// models were verified per-pixel against the game's own shaders.
|
||||||
|
|
@ -3975,7 +3998,9 @@ void OnDrawDone(uint8_t* base, uint32_t func, uint32_t r4, uint32_t r5, uint32_t
|
||||||
// independent done-flag: the first main-pass draw can be a character/tree
|
// independent done-flag: the first main-pass draw can be a character/tree
|
||||||
// whose PS allocates differently (rejected by the sanity gate below).
|
// whose PS allocates differently (rejected by the sanity gate below).
|
||||||
if ((!g_fog_frame_done || !g_shadow_frame_done || !g_sky_frame_done ||
|
if ((!g_fog_frame_done || !g_shadow_frame_done || !g_sky_frame_done ||
|
||||||
!g_tree_frame_done || !g_proxy_frame_done || !g_dynobj_frame_done) &&
|
!g_tree_frame_done || !g_proxy_frame_done || !g_dynobj_frame_done ||
|
||||||
|
!g_water_frame_done || !g_ocean_frame_done ||
|
||||||
|
!g_oceanrefl_frame_done) &&
|
||||||
func == 0 && flags2d == 0 && SceneEnabled() &&
|
func == 0 && flags2d == 0 && SceneEnabled() &&
|
||||||
(g_fog_cam[0] != 0.0f || g_fog_cam[1] != 0.0f || g_fog_cam[2] != 0.0f)) {
|
(g_fog_cam[0] != 0.0f || g_fog_cam[1] != 0.0f || g_fog_cam[2] != 0.0f)) {
|
||||||
const float dx = LoadGuestF32(base, bank + 16 * 4) - g_fog_cam[0];
|
const float dx = LoadGuestF32(base, bank + 16 * 4) - g_fog_cam[0];
|
||||||
|
|
@ -4129,6 +4154,157 @@ void OnDrawDone(uint8_t* base, uint32_t func, uint32_t r4, uint32_t r5, uint32_t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// flowingwateralpha m_params + animation time (PS c11..c14, c15.x),
|
||||||
|
// POSITIVE debug-path gate like the receiver rows: the water bank
|
||||||
|
// keeps the whole baseenvironment receiver layout, so only the name
|
||||||
|
// discriminates. One material's rows serve the frame; every
|
||||||
|
// flowingwateralpha draw in a capture carried identical m_params, and
|
||||||
|
// the animation time is pass-global anyway.
|
||||||
|
if (!g_water_frame_done && ps_bank != 0) {
|
||||||
|
const auto water_ps = [&]() -> bool {
|
||||||
|
const auto check = [&](uint32_t obj) -> bool {
|
||||||
|
if (obj < 0x10000 || !GuestReadableApprox(base, obj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
char text[120] = {};
|
||||||
|
for (int k = 0; k < 119; ++k) {
|
||||||
|
text[k] = char(REX_LOAD_U8(obj + 0x54 + k));
|
||||||
|
if (text[k] == '\0') break;
|
||||||
|
}
|
||||||
|
return std::strstr(text, "\\flowingwateralpha") != nullptr;
|
||||||
|
};
|
||||||
|
return check(g_cur_ps_obj.load(std::memory_order_relaxed)) ||
|
||||||
|
check(g_cur_vs_obj.load(std::memory_order_relaxed));
|
||||||
|
};
|
||||||
|
if (water_ps()) {
|
||||||
|
float rows[17];
|
||||||
|
for (int i = 0; i < 16; ++i) {
|
||||||
|
rows[i] = LoadGuestF32(base, ps_bank + (44 + i) * 4); // c11..c14
|
||||||
|
}
|
||||||
|
rows[16] = LoadGuestF32(base, ps_bank + 60 * 4); // c15.x = time
|
||||||
|
// Range gate: normal scales O(1), material multiplier small
|
||||||
|
// positive, scroll speeds/UV scales bounded, spec power a real
|
||||||
|
// exponent, alpha floor a blend factor, time non-negative.
|
||||||
|
bool sane = rows[1] > 0.0f && rows[1] <= 8.0f && // c11.y mult
|
||||||
|
rows[13] >= 0.0f && rows[13] <= 1.0f && // c14.y thr
|
||||||
|
rows[14] >= 1.0f && rows[14] <= 1000.0f && // c14.z pow
|
||||||
|
rows[15] >= 0.0f && rows[15] <= 1.0f && // c14.w floor
|
||||||
|
rows[16] >= 0.0f && rows[16] < 1e7f; // time
|
||||||
|
for (int i = 0; i < 4 && sane; ++i) {
|
||||||
|
sane = std::fabs(rows[0 + i]) <= 8.0f && // c11
|
||||||
|
std::fabs(rows[4 + i]) <= 64.0f && // c12 speeds
|
||||||
|
std::fabs(rows[8 + i]) <= 256.0f; // c13 scales
|
||||||
|
}
|
||||||
|
if (sane) {
|
||||||
|
const bool first = !g_water_have;
|
||||||
|
std::memcpy(g_water_rows, rows, sizeof(rows));
|
||||||
|
g_water_have = true;
|
||||||
|
g_water_frame_done = true;
|
||||||
|
if (first) {
|
||||||
|
REXLOG_INFO(
|
||||||
|
"native-scene: water m_params captured: p0=({:.3g},{:.3g},"
|
||||||
|
"{:.3g},{:.3g}) speed=({:.3g},{:.3g},{:.3g},{:.3g}) "
|
||||||
|
"scale=({:.3g},{:.3g},{:.3g},{:.3g}) thr={:.3g} pow={:.3g} "
|
||||||
|
"floor={:.3g} t={:.2f}",
|
||||||
|
rows[0], rows[1], rows[2], rows[3], rows[4], rows[5],
|
||||||
|
rows[6], rows[7], rows[8], rows[9], rows[10], rows[11],
|
||||||
|
rows[13], rows[14], rows[15], rows[16]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
static std::atomic<int> s_water_rejects{0};
|
||||||
|
if (s_water_rejects.fetch_add(1, std::memory_order_relaxed) < 4) {
|
||||||
|
REXLOG_INFO(
|
||||||
|
"native-scene: water m_params REJECTED by range gate: "
|
||||||
|
"mult={:.3g} thr={:.3g} pow={:.3g} floor={:.3g} t={:.3g}",
|
||||||
|
rows[1], rows[13], rows[14], rows[15], rows[16]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ocean_defaultPS material rows (PCA mean/weights at c2..c8,
|
||||||
|
// m_params[0..2] at c12..c14) and the oceanreflection sheet row (c3).
|
||||||
|
// Same POSITIVE debug-path gating as the water rows.
|
||||||
|
if ((!g_ocean_frame_done || !g_oceanrefl_frame_done) && ps_bank != 0) {
|
||||||
|
const auto ps_name_has = [&](const char* needle) -> bool {
|
||||||
|
const auto check = [&](uint32_t obj) -> bool {
|
||||||
|
if (obj < 0x10000 || !GuestReadableApprox(base, obj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
char text[120] = {};
|
||||||
|
for (int k = 0; k < 119; ++k) {
|
||||||
|
text[k] = char(REX_LOAD_U8(obj + 0x54 + k));
|
||||||
|
if (text[k] == '\0') break;
|
||||||
|
}
|
||||||
|
return std::strstr(text, needle) != nullptr;
|
||||||
|
};
|
||||||
|
return check(g_cur_ps_obj.load(std::memory_order_relaxed)) ||
|
||||||
|
check(g_cur_vs_obj.load(std::memory_order_relaxed));
|
||||||
|
};
|
||||||
|
if (!g_ocean_frame_done && ps_name_has("\\ocean_default")) {
|
||||||
|
float c[64];
|
||||||
|
for (int i = 0; i < 64; ++i) {
|
||||||
|
c[i] = LoadGuestF32(base, ps_bank + (8 + i) * 4); // c2..c17
|
||||||
|
}
|
||||||
|
const float* m0 = c + (12 - 2) * 4; // c12
|
||||||
|
const float* m1 = c + (13 - 2) * 4; // c13
|
||||||
|
const float* m2 = c + (14 - 2) * 4; // c14
|
||||||
|
bool sane = m1[1] > 0.0f && m1[1] <= 8.0f && // multiplier
|
||||||
|
m1[2] >= 0.0f && m1[2] <= 8.0f && // tonedown scale
|
||||||
|
m1[3] > 0.0f && m1[3] <= 4096.0f && // uv scale
|
||||||
|
m2[0] > 0.01f && m2[0] <= 1000.0f && // spread u
|
||||||
|
m2[1] > 0.01f && m2[1] <= 1000.0f && // spread v
|
||||||
|
m2[2] > 0.001f && m2[2] <= 100.0f; // roughness
|
||||||
|
for (int i = 0; i < 4 && sane; ++i) {
|
||||||
|
sane = c[i] >= -2.0f && c[i] <= 2.0f; // PCA mean row
|
||||||
|
}
|
||||||
|
if (sane) {
|
||||||
|
float* o = g_ocean_rows;
|
||||||
|
// mean pre-swizzled for (R,G,B): PS c2.x, c2.z, c2.y
|
||||||
|
o[0] = c[0];
|
||||||
|
o[1] = c[2];
|
||||||
|
o[2] = c[1];
|
||||||
|
o[3] = 0.0f;
|
||||||
|
// weight rows in model order: c3, c4, c7, c8, c5, c6
|
||||||
|
const int worder[6] = {3, 4, 7, 8, 5, 6};
|
||||||
|
for (int w = 0; w < 6; ++w) {
|
||||||
|
std::memcpy(o + 4 + w * 4, c + (worder[w] - 2) * 4,
|
||||||
|
4 * sizeof(float));
|
||||||
|
}
|
||||||
|
std::memcpy(o + 28, m0, 4 * sizeof(float));
|
||||||
|
std::memcpy(o + 32, m1, 4 * sizeof(float));
|
||||||
|
std::memcpy(o + 36, m2, 4 * sizeof(float));
|
||||||
|
const bool first = !g_ocean_have;
|
||||||
|
g_ocean_have = true;
|
||||||
|
g_ocean_frame_done = true;
|
||||||
|
if (first) {
|
||||||
|
REXLOG_INFO(
|
||||||
|
"native-scene: ocean rows captured: mult={:.3g} "
|
||||||
|
"tonedown={:.3g} uvscale={:.3g} ward=({:.3g},{:.3g},{:.3g})",
|
||||||
|
m1[1], m1[2], m1[3], m2[0], m2[1], m2[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!g_oceanrefl_frame_done && ps_name_has("\\oceanreflection")) {
|
||||||
|
float row[4];
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
row[i] = LoadGuestF32(base, ps_bank + (12 + i) * 4); // c3
|
||||||
|
}
|
||||||
|
// multiplier small positive; the fade bounds must not coincide.
|
||||||
|
if (row[0] > 0.0f && row[0] <= 8.0f &&
|
||||||
|
std::fabs(row[1] - row[2]) > 1e-4f) {
|
||||||
|
std::memcpy(g_oceanrefl_rows, row, sizeof(row));
|
||||||
|
const bool first = !g_oceanrefl_have;
|
||||||
|
g_oceanrefl_have = true;
|
||||||
|
g_oceanrefl_frame_done = true;
|
||||||
|
if (first) {
|
||||||
|
REXLOG_INFO(
|
||||||
|
"native-scene: oceanreflection row captured: mult={:.3g} "
|
||||||
|
"fade=({:.3g},{:.3g})",
|
||||||
|
row[0], row[1], row[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Not gated on the shadows cvar: the captured rows also carry the
|
// Not gated on the shadows cvar: the captured rows also carry the
|
||||||
// scene exposure / material multiplier / sun direction consumed by the
|
// scene exposure / material multiplier / sun direction consumed by the
|
||||||
// exact world shading (rows 40/45/24..26); shading must not die when
|
// exact world shading (rows 40/45/24..26); shading must not die when
|
||||||
|
|
@ -8353,6 +8529,19 @@ void BuildFrameScene(uint8_t* base, const SubmitRecord* records, size_t count) {
|
||||||
std::memcpy(scene.fog_ramp, g_fog_rows, 4 * sizeof(float));
|
std::memcpy(scene.fog_ramp, g_fog_rows, 4 * sizeof(float));
|
||||||
std::memcpy(scene.fog_color, g_fog_rows + 4, 4 * sizeof(float));
|
std::memcpy(scene.fog_color, g_fog_rows + 4, 4 * sizeof(float));
|
||||||
}
|
}
|
||||||
|
if (g_water_have) {
|
||||||
|
std::memcpy(scene.water_rows, g_water_rows, sizeof(scene.water_rows));
|
||||||
|
scene.water_valid = true;
|
||||||
|
}
|
||||||
|
if (g_ocean_have) {
|
||||||
|
std::memcpy(scene.ocean_rows, g_ocean_rows, sizeof(scene.ocean_rows));
|
||||||
|
scene.ocean_valid = true;
|
||||||
|
}
|
||||||
|
if (g_oceanrefl_have) {
|
||||||
|
std::memcpy(scene.oceanrefl_rows, g_oceanrefl_rows,
|
||||||
|
sizeof(scene.oceanrefl_rows));
|
||||||
|
scene.oceanrefl_valid = true;
|
||||||
|
}
|
||||||
if (g_shadow_have) {
|
if (g_shadow_have) {
|
||||||
std::memcpy(scene.shadow_rows, g_shadow_rows, sizeof(g_shadow_rows));
|
std::memcpy(scene.shadow_rows, g_shadow_rows, sizeof(g_shadow_rows));
|
||||||
scene.shadow_valid = true;
|
scene.shadow_valid = true;
|
||||||
|
|
@ -8484,6 +8673,9 @@ void BuildFrameScene(uint8_t* base, const SubmitRecord* records, size_t count) {
|
||||||
g_tree_frame_done = false;
|
g_tree_frame_done = false;
|
||||||
g_proxy_frame_done = false;
|
g_proxy_frame_done = false;
|
||||||
g_dynobj_frame_done = false;
|
g_dynobj_frame_done = false;
|
||||||
|
g_water_frame_done = false;
|
||||||
|
g_ocean_frame_done = false;
|
||||||
|
g_oceanrefl_frame_done = false;
|
||||||
|
|
||||||
|
|
||||||
// Draw-time STRETCH VETO: the last line of defense, judging what the GPU
|
// Draw-time STRETCH VETO: the last line of defense, judging what the GPU
|
||||||
|
|
|
||||||
|
|
@ -139,8 +139,28 @@ struct DrawItem {
|
||||||
// `water_normal` is the material's `normal` channel texture, bound in the
|
// `water_normal` is the material's `normal` channel texture, bound in the
|
||||||
// macro slot (water never carries a macro overlay).
|
// macro slot (water never carries a macro overlay).
|
||||||
bool water;
|
bool water;
|
||||||
|
// AttribulatorMaterialName starts "water.flowing": the flowingwateralpha
|
||||||
|
// shader family (canal / waterfall surfaces). These take the exact water
|
||||||
|
// branch (hand-ported from flowingwateralpha_defaultPS and verified
|
||||||
|
// per-pixel against the ucode) when the frame's water m_params rows are
|
||||||
|
// captured; other water.* / ocean.* materials keep the legacy water
|
||||||
|
// shading until their own shader layouts are verified from a capture.
|
||||||
|
bool water_flowing = false;
|
||||||
|
// ocean materials: 1 = ocean.default (the sea surface, ocean_defaultPS:
|
||||||
|
// PCA dual-component normal + ward spec + cube reflection, drawn OPAQUE),
|
||||||
|
// 2 = ocean.reflection (the baked horizon reflection sheet, height-ramp
|
||||||
|
// alpha). Exact branches gated on the frame's ocean rows.
|
||||||
|
uint8_t water_ocean = 0;
|
||||||
uint32_t water_normal;
|
uint32_t water_normal;
|
||||||
|
// `normal2` channel: the ocean's second PCA normal component map (the
|
||||||
|
// flowingwater families ignore it).
|
||||||
|
uint32_t water_normal2 = 0;
|
||||||
uint32_t water_env; // `environment` channel cube map (reflection term)
|
uint32_t water_env; // `environment` channel cube map (reflection term)
|
||||||
|
// Usage-6 tangent present WITHOUT a usage-7 binormal (tan_fmt = 16, the
|
||||||
|
// water-mesh layout: pos + uv + lm_norm + tangent). The flowingwater VS
|
||||||
|
// reconstructs the frame as N from lm_norm.zw + sign bits, B =
|
||||||
|
// sign.x * cross(N, T); DecodeMesh mirrors that (see the water pack).
|
||||||
|
uint8_t tan_fmt = 0;
|
||||||
// character.cloth_ropa (Ropa cloth-simulated garments, e.g. player tees):
|
// character.cloth_ropa (Ropa cloth-simulated garments, e.g. player tees):
|
||||||
// the VS variant branches on a flag row kept in front of the bone palette
|
// the VS variant branches on a flag row kept in front of the bone palette
|
||||||
// - sim active means the dynamic VB already holds deformed root-local
|
// - sim active means the dynamic VB already holds deformed root-local
|
||||||
|
|
@ -295,6 +315,29 @@ struct FrameScene {
|
||||||
// [2] = tree PS c4.y (tint multiplier), [3] = proxyworld PS c3.y (0.45).
|
// [2] = tree PS c4.y (tint multiplier), [3] = proxyworld PS c3.y (0.45).
|
||||||
// Defaults from the day capture cover frames before the first hit.
|
// Defaults from the day capture cover frames before the first hit.
|
||||||
float family_rows[4] = {0.3435f, 0.02f, 1.0f, 0.45f};
|
float family_rows[4] = {0.3435f, 0.02f, 1.0f, 0.45f};
|
||||||
|
// flowingwateralpha.fx (the water.flowing* materials) m_params rows,
|
||||||
|
// captured once per frame from a flowingwateralpha PS bank (debug-path
|
||||||
|
// classified): [0..3] = m_params[0] (PS c11: normal scale xzw + the
|
||||||
|
// material multiplier in y), [4..7] = m_params[1] (c12: the two animated
|
||||||
|
// UV scroll speeds), [8..11] = m_params[2] (c13: the two tap UV scales),
|
||||||
|
// [12..15] = m_params[3] (c14: y = mask threshold, z = spec power,
|
||||||
|
// w = alpha floor), [16] = g_fAnimationTime (c15.x). Consumed by the
|
||||||
|
// exact water scene PS branch (cam_pos.w = -30).
|
||||||
|
bool water_valid = false;
|
||||||
|
float water_rows[17] = {};
|
||||||
|
// ocean_defaultPS material rows, captured once per frame from an ocean PS
|
||||||
|
// bank (debug-path classified): [0..3] = PCA mean pre-swizzled for the
|
||||||
|
// (R,G,B) channel order (PS c2.x, c2.z, c2.y), [4..27] = the six PCA
|
||||||
|
// weight rows in model order (c3, c4, c7, c8, c5, c6), [28..31] =
|
||||||
|
// m_params[0] (spec color, c12), [32..35] = m_params[1] (c13: y = mult,
|
||||||
|
// z = tonedown scale, w = uv scale), [36..39] = m_params[2] (c14: ward
|
||||||
|
// spread u/v + roughness).
|
||||||
|
bool ocean_valid = false;
|
||||||
|
float ocean_rows[40] = {};
|
||||||
|
// oceanreflection_defaultPS row (PS c3): x = multiplier, y/z = the
|
||||||
|
// height-ramp alpha fade bounds.
|
||||||
|
bool oceanrefl_valid = false;
|
||||||
|
float oceanrefl_rows[4] = {};
|
||||||
// dynamicobject.fx frame-global lighting rows, captured from a
|
// dynamicobject.fx frame-global lighting rows, captured from a
|
||||||
// dynamicobject/alphatestdynamicobject PS bank (debug-path classified):
|
// dynamicobject/alphatestdynamicobject PS bank (debug-path classified):
|
||||||
// [0..2] sun direction (PS c9), [3] scene exposure (c13.x),
|
// [0..2] sun direction (PS c9), [3] scene exposure (c13.x),
|
||||||
|
|
|
||||||
|
|
@ -526,8 +526,9 @@ bool DecodeMesh(nrhi::Device* device, uint8_t* base, const DrawItem& item,
|
||||||
// encodings (decalenvironment VS: o0.zw = |uv|; baseenvironment VS:
|
// encodings (decalenvironment VS: o0.zw = |uv|; baseenvironment VS:
|
||||||
// maxs r2.zw = |r4.xy|). Raw signed values sample mirrored atlas cells.
|
// maxs r2.zw = |r4.xy|). Raw signed values sample mirrored atlas cells.
|
||||||
// Exception: hair meshes, their second texcoord is the strand-alpha
|
// Exception: hair meshes, their second texcoord is the strand-alpha
|
||||||
// UV, passed RAW by the hair VS (o0.zw = uv, float2).
|
// UV, passed RAW by the hair VS (o0.zw = uv, float2), and the ocean,
|
||||||
if (item.char_family < 4) {
|
// whose VS passes the lightmap UV raw (Out.UV.zw = In.LM, no abs).
|
||||||
|
if (item.char_family < 4 && item.water_ocean == 0) {
|
||||||
dst[v * 14 + 5] = std::fabs(dst[v * 14 + 5]);
|
dst[v * 14 + 5] = std::fabs(dst[v * 14 + 5]);
|
||||||
dst[v * 14 + 6] = std::fabs(dst[v * 14 + 6]);
|
dst[v * 14 + 6] = std::fabs(dst[v * 14 + 6]);
|
||||||
}
|
}
|
||||||
|
|
@ -578,7 +579,35 @@ bool DecodeMesh(nrhi::Device* device, uint8_t* base, const DrawItem& item,
|
||||||
return uint32_t(std::lround((f * 0.5f + 0.5f) * 255.0f)) & 0xFFu;
|
return uint32_t(std::lround((f * 0.5f + 0.5f) * 255.0f)) & 0xFFu;
|
||||||
};
|
};
|
||||||
float n3[3] = {0.0f, 0.0f, 0.0f};
|
float n3[3] = {0.0f, 0.0f, 0.0f};
|
||||||
if (item.env_family != 0 &&
|
if (item.water_flowing && !item.skinned && item.uv2_fmt == 26 &&
|
||||||
|
item.tan_fmt == 16) {
|
||||||
|
// Water meshes (flowingwater VS layout): the fmt-26 element is
|
||||||
|
// lm_norm: xy = |unwrap| with sign bits, zw = normal.xy (snorm),
|
||||||
|
// normal.z = sign.y * sqrt(1 - xy^2), and the usage-6 k_10_11_11
|
||||||
|
// element is the TANGENT (no usage-3 normal, no usage-7 binormal).
|
||||||
|
// Pack the tangent into the free blend-weight bytes with the unwrap
|
||||||
|
// sign.x in the sentinel byte; the shader rebuilds the frame as
|
||||||
|
// B = sign.x * cross(N, T), exactly the game's VS
|
||||||
|
// (vBinormal = signs.x * cross(vNormal, vTangent)).
|
||||||
|
const uint8_t* q = src_vb + size_t(v) * item.stride + item.uv2_offset;
|
||||||
|
const int16_t sx = int16_t(SwapU16(*reinterpret_cast<const uint16_t*>(q)));
|
||||||
|
const int16_t sy = int16_t(SwapU16(*reinterpret_cast<const uint16_t*>(q + 2)));
|
||||||
|
const int16_t nx = int16_t(SwapU16(*reinterpret_cast<const uint16_t*>(q + 4)));
|
||||||
|
const int16_t ny = int16_t(SwapU16(*reinterpret_cast<const uint16_t*>(q + 6)));
|
||||||
|
n3[0] = nx / 32767.0f;
|
||||||
|
n3[1] = ny / 32767.0f;
|
||||||
|
// The game clamps xy_len at 0.999 (water normals ride |xy| ~ 1, so
|
||||||
|
// z floors at 0.0316 rather than 0, verified against the VS ucode).
|
||||||
|
const float xylen =
|
||||||
|
std::min(n3[0] * n3[0] + n3[1] * n3[1], 0.999f);
|
||||||
|
n3[2] = (sy > 0 ? 1.0f : -1.0f) * std::sqrt(1.0f - xylen);
|
||||||
|
float t3[3];
|
||||||
|
unpack_10_11_11(item.tangent_offset, t3);
|
||||||
|
const uint32_t packed = pk(t3[0]) | (pk(t3[1]) << 8) |
|
||||||
|
(pk(t3[2]) << 16) |
|
||||||
|
((sx > 0 ? 200u : 100u) << 24);
|
||||||
|
std::memcpy(&dst[v * 14 + 7], &packed, 4);
|
||||||
|
} else if (item.env_family != 0 &&
|
||||||
(item.env_family <= 6 || item.env_family == 13) &&
|
(item.env_family <= 6 || item.env_family == 13) &&
|
||||||
item.uv2_fmt == 26) {
|
item.uv2_fmt == 26) {
|
||||||
// Exact world families: the REAL vertex normal is packed in the
|
// Exact world families: the REAL vertex normal is packed in the
|
||||||
|
|
@ -1892,8 +1921,10 @@ bool EnsureGuestCubeTexture(const NativeGuestOutputRenderContext& context, uint8
|
||||||
// chain down to 1x1 below; other formats fall back to copying the guest
|
// chain down to 1x1 below; other formats fall back to copying the guest
|
||||||
// chain (down to 32px: smaller levels live packed inside a shared 32x32
|
// chain (down to 32px: smaller levels live packed inside a shared 32x32
|
||||||
// tile, see GetPackedTileOffset).
|
// tile, see GetPackedTileOffset).
|
||||||
|
const xenos::TextureFormat cube_base_fmt = rex::graphics::GetBaseFormat(info.format);
|
||||||
const bool rgba_chain =
|
const bool rgba_chain =
|
||||||
rex::graphics::GetBaseFormat(info.format) == xenos::TextureFormat::k_DXT1 &&
|
(cube_base_fmt == xenos::TextureFormat::k_DXT1 ||
|
||||||
|
cube_base_fmt == xenos::TextureFormat::k_8_8_8_8) &&
|
||||||
width >= 8 && (width & (width - 1)) == 0 && width == height;
|
width >= 8 && (width & (width - 1)) == 0 && width == height;
|
||||||
uint32_t mip_levels = 1;
|
uint32_t mip_levels = 1;
|
||||||
if (!rgba_chain && info.memory.mip_address != 0 && (width & (width - 1)) == 0 &&
|
if (!rgba_chain && info.memory.mip_address != 0 && (width & (width - 1)) == 0 &&
|
||||||
|
|
@ -1954,9 +1985,13 @@ bool EnsureGuestCubeTexture(const NativeGuestOutputRenderContext& context, uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rgba_chain) {
|
if (rgba_chain) {
|
||||||
// Decode DXT1 mip 0 -> RGBA8 per face, box-filter the full chain to
|
// Decode mip 0 -> RGBA8 per face (DXT1 block decode, or plain 8888
|
||||||
// 1x1, upload as an RGBA cube. CPU cost is one-time per cube (runs on
|
// texels: the water canal cubes), box-filter the full chain to 1x1,
|
||||||
// the decode workers).
|
// upload as an RGBA cube. The GUEST cube mip chain is never trusted:
|
||||||
|
// its packed sub-32px levels and per-face slice layout are exactly
|
||||||
|
// where garbage sneaks in, and the reflection fetch runs at a deep
|
||||||
|
// gradient LOD where a bad level dominates. CPU cost is one-time per
|
||||||
|
// cube (runs on the decode workers).
|
||||||
nrhi::Device* device = context.device;
|
nrhi::Device* device = context.device;
|
||||||
const uint32_t levels = 1u + uint32_t(std::countr_zero(width));
|
const uint32_t levels = 1u + uint32_t(std::countr_zero(width));
|
||||||
struct Level {
|
struct Level {
|
||||||
|
|
@ -2022,14 +2057,34 @@ bool EnsureGuestCubeTexture(const NativeGuestOutputRenderContext& context, uint8
|
||||||
bytes_per_block);
|
bytes_per_block);
|
||||||
}
|
}
|
||||||
SwapGuestEndian(bc_row.data(), uint32_t(bc_row.size()), info.endianness);
|
SwapGuestEndian(bc_row.data(), uint32_t(bc_row.size()), info.endianness);
|
||||||
|
if (cube_base_fmt == xenos::TextureFormat::k_DXT1) {
|
||||||
for (uint32_t bx = 0; bx < L0.cols; ++bx) {
|
for (uint32_t bx = 0; bx < L0.cols; ++bx) {
|
||||||
uint8_t px[16][4];
|
uint8_t px[16][4];
|
||||||
DecodeBc1Block(&bc_row[size_t(bx) * bytes_per_block], px);
|
DecodeBc1Block(&bc_row[size_t(bx) * bytes_per_block], px);
|
||||||
for (uint32_t r = 0; r < 4; ++r) {
|
for (uint32_t r = 0; r < 4; ++r) {
|
||||||
std::memcpy(&rgba[(size_t(by * 4 + r) * width + bx * 4) * 4], px[r * 4],
|
std::memcpy(&rgba[(size_t(by * 4 + r) * width + bx * 4) * 4],
|
||||||
16);
|
px[r * 4], 16);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// k_8_8_8_8: post-endian-swap bytes are already component order
|
||||||
|
// X,Y,Z,W (the SRV swizzle applies the fetch's channel remap).
|
||||||
|
std::memcpy(&rgba[size_t(by) * width * 4], bc_row.data(),
|
||||||
|
size_t(width) * 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Diagnostic dump (skate3_native_render_scene_lm_dump): the decoded
|
||||||
|
// face exactly as uploaded, for offline byte-diff against the
|
||||||
|
// reference decode of the same fetch words.
|
||||||
|
if (REXCVAR_GET(skate3_native_render_scene_lm_dump)) {
|
||||||
|
char path[260];
|
||||||
|
std::snprintf(path, sizeof(path),
|
||||||
|
"native_texture_dumps/cube_%08X_f%u_%ux%u.rgba",
|
||||||
|
info.memory.base_address, face, width, height);
|
||||||
|
if (FILE* f = std::fopen(path, "wb")) {
|
||||||
|
std::fwrite(rgba.data(), 1, size_t(width) * height * 4, f);
|
||||||
|
std::fclose(f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Upload level 0, then box-filter down the chain in place.
|
// Upload level 0, then box-filter down the chain in place.
|
||||||
const uint8_t* src = rgba.data();
|
const uint8_t* src = rgba.data();
|
||||||
|
|
@ -2062,8 +2117,8 @@ bool EnsureGuestCubeTexture(const NativeGuestOutputRenderContext& context, uint8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
device->Unmap(out.upload);
|
device->Unmap(out.upload);
|
||||||
REXLOG_INFO("native-scene: cube {:08X} {}x{} DXT1 -> RGBA full chain ({} levels)",
|
REXLOG_INFO("native-scene: cube {:08X} {}x{} fmt={} -> RGBA full chain ({} levels)",
|
||||||
tex_ptr, width, height, levels);
|
tex_ptr, width, height, uint32_t(info.format), levels);
|
||||||
nrhi::Swizzle swizzle_mapping[4];
|
nrhi::Swizzle swizzle_mapping[4];
|
||||||
ComposeSrvSwizzle(fetch.swizzle, xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA,
|
ComposeSrvSwizzle(fetch.swizzle, xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA,
|
||||||
swizzle_mapping);
|
swizzle_mapping);
|
||||||
|
|
@ -3756,6 +3811,9 @@ void WarmItemResources(const NativeGuestOutputRenderContext& context, uint8_t* b
|
||||||
// per-pixel too.
|
// per-pixel too.
|
||||||
warm_texture(item.water_normal);
|
warm_texture(item.water_normal);
|
||||||
}
|
}
|
||||||
|
if (item.water_ocean == 1) {
|
||||||
|
warm_texture(item.water_normal2); // second PCA component (t8)
|
||||||
|
}
|
||||||
if ((item.env_family >= 1 && item.env_family <= 4 && item.env_family != 2) ||
|
if ((item.env_family >= 1 && item.env_family <= 4 && item.env_family != 2) ||
|
||||||
item.dynobj != 0) {
|
item.dynobj != 0) {
|
||||||
// v2 detail normal map (t8; fam 2 carries no base+detail pair).
|
// v2 detail normal map (t8; fam 2 carries no base+detail pair).
|
||||||
|
|
@ -3990,6 +4048,9 @@ void ProcessPrewarmEntry(uint8_t* base, const PrewarmEntry& e) {
|
||||||
// per-pixel too.
|
// per-pixel too.
|
||||||
stage_texture(item.water_normal);
|
stage_texture(item.water_normal);
|
||||||
}
|
}
|
||||||
|
if (item.water_ocean == 1) {
|
||||||
|
stage_texture(item.water_normal2); // second PCA component (t8)
|
||||||
|
}
|
||||||
if ((item.env_family >= 1 && item.env_family <= 4 && item.env_family != 2) ||
|
if ((item.env_family >= 1 && item.env_family <= 4 && item.env_family != 2) ||
|
||||||
item.dynobj != 0) {
|
item.dynobj != 0) {
|
||||||
// v2 detail normal map (t8; fam 2 carries no base+detail pair).
|
// v2 detail normal map (t8; fam 2 carries no base+detail pair).
|
||||||
|
|
@ -5968,7 +6029,7 @@ void LogFrameStats(const FrameScene& scene, uint64_t frames, uint32_t drawn,
|
||||||
"store[n={} routes={} evict={}] "
|
"store[n={} routes={} evict={}] "
|
||||||
"heal[vfail={} demote={}] serve[sticky={} skipnew={} adstale={} adnone={}] "
|
"heal[vfail={} demote={}] serve[sticky={} skipnew={} adstale={} adnone={}] "
|
||||||
"shadow[valid={} ready={} draws={}] char[attempt={} valid={} drawn={} "
|
"shadow[valid={} ready={} draws={}] char[attempt={} valid={} drawn={} "
|
||||||
"reused={}] dynobj[valid={} drawn={}] "
|
"reused={}] dynobj[valid={} drawn={}] water[valid={} drawn={}] "
|
||||||
"lw[ctxs={} ents={} stamp={} fade0={} resc={} fill={} pal={} rows={}] "
|
"lw[ctxs={} ents={} stamp={} fade0={} resc={} fill={} pal={} rows={}] "
|
||||||
"refl[pair={} flat={} gate={:#x}] flips={} alt[bones={} shadow={}] "
|
"refl[pair={} flat={} gate={:#x}] flips={} alt[bones={} shadow={}] "
|
||||||
"rows_inst={} pal_srv={}",
|
"rows_inst={} pal_srv={}",
|
||||||
|
|
@ -5997,7 +6058,8 @@ void LogFrameStats(const FrameScene& scene, uint64_t frames, uint32_t drawn,
|
||||||
shadow_ready, shadow_draws,
|
shadow_ready, shadow_draws,
|
||||||
g_char_attempts.load(), g_char_valid.load(), g_char_drawn.load(),
|
g_char_attempts.load(), g_char_valid.load(), g_char_drawn.load(),
|
||||||
g_char_rows_reused.load(), scene.dynobj_valid,
|
g_char_rows_reused.load(), scene.dynobj_valid,
|
||||||
g_dynobj_drawn.load(), lw_ctxs, lw_ents, g_lw_stamped.load(),
|
g_dynobj_drawn.load(), scene.water_valid, g_water_drawn.load(),
|
||||||
|
lw_ctxs, lw_ents, g_lw_stamped.load(),
|
||||||
g_lw_fade0.load(), g_lw_ctx_rescued.load(), g_lw_gap_filled.load(),
|
g_lw_fade0.load(), g_lw_ctx_rescued.load(), g_lw_gap_filled.load(),
|
||||||
g_lw_pal_sub.load(), g_lw_rows_served.load(),
|
g_lw_pal_sub.load(), g_lw_rows_served.load(),
|
||||||
g_refl_pair.load(), g_refl_flat.load(), g_refl_gate.load(),
|
g_refl_pair.load(), g_refl_flat.load(), g_refl_gate.load(),
|
||||||
|
|
@ -6459,6 +6521,27 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
std::memcpy(cb + 64, scene.dynobj_ws, sizeof(scene.dynobj_ws));
|
std::memcpy(cb + 64, scene.dynobj_ws, sizeof(scene.dynobj_ws));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// flowingwateralpha m_params rows (wat_p0..wat_p3 at cb[76..91]):
|
||||||
|
// m_params[0..2] verbatim, then (time, mask threshold, spec power,
|
||||||
|
// alpha floor). Consumed by the exact water branch (cam_pos.w = -30),
|
||||||
|
// which is only selected per draw when scene.water_valid.
|
||||||
|
if (scene.water_valid) {
|
||||||
|
std::memcpy(cb + 76, scene.water_rows, 12 * sizeof(float)); // c11..c13
|
||||||
|
cb[88] = scene.water_rows[16]; // g_fAnimationTime (c15.x)
|
||||||
|
cb[89] = scene.water_rows[13]; // mask threshold (c14.y)
|
||||||
|
cb[90] = scene.water_rows[14]; // spec power (c14.z)
|
||||||
|
cb[91] = scene.water_rows[15]; // alpha floor (c14.w)
|
||||||
|
}
|
||||||
|
// ocean_defaultPS rows (oc_mean/oc_w0..5/oc_p0..2 at cb[92..131]) and
|
||||||
|
// the oceanreflection fade row (orf at cb[132..135]); consumed by the
|
||||||
|
// exact ocean branches (cam_pos.w = -31 / -32).
|
||||||
|
if (scene.ocean_valid) {
|
||||||
|
std::memcpy(cb + 92, scene.ocean_rows, sizeof(scene.ocean_rows));
|
||||||
|
}
|
||||||
|
if (scene.oceanrefl_valid) {
|
||||||
|
std::memcpy(cb + 132, scene.oceanrefl_rows,
|
||||||
|
sizeof(scene.oceanrefl_rows));
|
||||||
|
}
|
||||||
cmd->SetConstantBuffer(6, g_r.shadow_cb, cb_offset);
|
cmd->SetConstantBuffer(6, g_r.shadow_cb, cb_offset);
|
||||||
// b2 (character lighting) default: point at the ring base so the root
|
// b2 (character lighting) default: point at the ring base so the root
|
||||||
// CBV is never left unset; character draws re-point it per item.
|
// CBV is never left unset; character draws re-point it per item.
|
||||||
|
|
@ -7337,7 +7420,8 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
// refmask map (or the animated.tree noise tint) in the free decal slot;
|
// refmask map (or the animated.tree noise tint) in the free decal slot;
|
||||||
// overlay.w == 3 tells the shader the masks are live.
|
// overlay.w == 3 tells the shader the masks are live.
|
||||||
bool spec_bound = false;
|
bool spec_bound = false;
|
||||||
if (item.env_family != 0 && !item.decal && item.env_family != 10 &&
|
if (((item.env_family != 0 && !item.decal && item.env_family != 10) ||
|
||||||
|
item.water_flowing) &&
|
||||||
item.spec_tex != 0) {
|
item.spec_tex != 0) {
|
||||||
const GuestTexture* spec = resolve_texture(item.spec_tex, 6);
|
const GuestTexture* spec = resolve_texture(item.spec_tex, 6);
|
||||||
if (spec != &g_r.white) {
|
if (spec != &g_r.white) {
|
||||||
|
|
@ -7475,6 +7559,29 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
v2_flags |= 8;
|
v2_flags |= 8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Exact ocean (fam 31): the second PCA normal component rides the free
|
||||||
|
// t8 pair slot and the 16x16 macro overlay the t9 slot (water items use
|
||||||
|
// neither). v2_flags stays 0; the ocean branch signals its binds
|
||||||
|
// through the overlay row.
|
||||||
|
bool ocean_n2 = false;
|
||||||
|
bool ocean_ov = false;
|
||||||
|
if (item.water_ocean == 1 && debug_mode == 0 && scene.shadow_valid &&
|
||||||
|
scene.ocean_valid) {
|
||||||
|
if (item.water_normal2 != 0) {
|
||||||
|
const GuestTexture* n2 = resolve_texture(item.water_normal2, 7);
|
||||||
|
if (n2 != &g_r.white && n2->valid && n2->srv_mips != 0) {
|
||||||
|
v2_detail = n2;
|
||||||
|
ocean_n2 = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.macro_tex != 0) {
|
||||||
|
const GuestTexture* mo = resolve_texture(item.macro_tex, 6);
|
||||||
|
if (mo != &g_r.white && mo->valid && mo->srv_mips != 0) {
|
||||||
|
v2_spec2 = mo;
|
||||||
|
ocean_ov = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
constants[44] = item.macro_scale;
|
constants[44] = item.macro_scale;
|
||||||
constants[45] = item.macro_opacity;
|
constants[45] = item.macro_opacity;
|
||||||
constants[46] = macro_tex != &g_r.white ? 1.0f : 0.0f;
|
constants[46] = macro_tex != &g_r.white ? 1.0f : 0.0f;
|
||||||
|
|
@ -7490,7 +7597,53 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
? 4.0f
|
? 4.0f
|
||||||
: 3.0f)
|
: 3.0f)
|
||||||
: 0.0f);
|
: 0.0f);
|
||||||
if (item.water) {
|
// Exact flowingwateralpha branch (cam_pos.w = -30): the canal/waterfall
|
||||||
|
// 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
|
||||||
|
// world frame rows (exposure/sun/fog at b1, scene.shadow_valid) and the
|
||||||
|
// ripple normal map resolved in the macro slot; the legacy water
|
||||||
|
// shading covers every gap (and every non-flowing water material).
|
||||||
|
const bool water_exact = item.water && item.water_flowing &&
|
||||||
|
debug_mode == 0 && scene.shadow_valid &&
|
||||||
|
scene.water_valid && macro_tex != &g_r.white;
|
||||||
|
// Exact ocean surface (fam 31): both PCA component maps must resolve
|
||||||
|
// (t3 = comp 0 via the water_normal macro-slot bind, t8 = comp 1); the
|
||||||
|
// legacy water shading covers every gap. The horizon sheet (fam 32)
|
||||||
|
// needs only its baked texture (t0) and the captured fade row.
|
||||||
|
const bool ocean_exact = item.water && item.water_ocean == 1 &&
|
||||||
|
debug_mode == 0 && scene.shadow_valid &&
|
||||||
|
scene.ocean_valid && macro_tex != &g_r.white &&
|
||||||
|
ocean_n2;
|
||||||
|
const bool oceanrefl_exact = item.water && item.water_ocean == 2 &&
|
||||||
|
debug_mode == 0 && scene.shadow_valid &&
|
||||||
|
scene.oceanrefl_valid;
|
||||||
|
if (water_exact) {
|
||||||
|
// overlay.x = spec/reflection masks bound at t4, overlay.y = real
|
||||||
|
// environment cube at t6, overlay.z = ripple resolved (gate above).
|
||||||
|
constants[39] = -30.0f;
|
||||||
|
constants[44] = spec_bound ? 1.0f : 0.0f;
|
||||||
|
constants[45] = cube_tex != &g_r.white_cube ? 1.0f : 0.0f;
|
||||||
|
constants[46] = 1.0f;
|
||||||
|
constants[47] = 0.0f;
|
||||||
|
g_water_drawn.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
} else if (ocean_exact) {
|
||||||
|
// overlay.x = second PCA component at t8 (gate above guarantees it),
|
||||||
|
// overlay.y = real environment cube at t6, overlay.z = macro overlay
|
||||||
|
// at t9, overlay.w = macroOverlayUVScale.
|
||||||
|
constants[39] = -31.0f;
|
||||||
|
constants[44] = 1.0f;
|
||||||
|
constants[45] = cube_tex != &g_r.white_cube ? 1.0f : 0.0f;
|
||||||
|
constants[46] = ocean_ov ? 1.0f : 0.0f;
|
||||||
|
constants[47] = item.macro_scale;
|
||||||
|
g_water_drawn.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
} else if (oceanrefl_exact) {
|
||||||
|
constants[39] = -32.0f;
|
||||||
|
constants[44] = 0.0f;
|
||||||
|
constants[45] = 0.0f;
|
||||||
|
constants[46] = 0.0f;
|
||||||
|
constants[47] = 0.0f;
|
||||||
|
g_water_drawn.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
} else if (item.water) {
|
||||||
// overlay.x = ripple scroll time, overlay.y = real environment cube
|
// overlay.x = ripple scroll time, overlay.y = real environment cube
|
||||||
// bound at t6, overlay.z = ripple normal map resolved (in the macro
|
// bound at t6, overlay.z = ripple normal map resolved (in the macro
|
||||||
// slot; the shader synthesizes procedural ripples otherwise).
|
// slot; the shader synthesizes procedural ripples otherwise).
|
||||||
|
|
@ -7514,7 +7667,15 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
// mat_tint row (color), unused by these items otherwise
|
// mat_tint row (color), unused by these items otherwise
|
||||||
// (root-signature DWORD budget).
|
// (root-signature DWORD budget).
|
||||||
constants[48] = item.water ? 2.0f : (item.transparent ? 1.0f : 0.0f);
|
constants[48] = item.water ? 2.0f : (item.transparent ? 1.0f : 0.0f);
|
||||||
if (item.transparent || item.water) {
|
if (water_exact || ocean_exact || oceanrefl_exact) {
|
||||||
|
// misc.y = cube LOD bias (same 640p-gradient rationale as fams 5/6);
|
||||||
|
// fog comes from the shared b1 rows; misc.zw stay free. misc.x keeps
|
||||||
|
// the water marker for the SSR G-buffer restage.
|
||||||
|
constants[49] =
|
||||||
|
log2f(std::max(1.0f, float(context.guest_output_height) / 640.0f));
|
||||||
|
constants[50] = 0.0f;
|
||||||
|
constants[51] = 0.0f;
|
||||||
|
} else if (item.transparent || item.water) {
|
||||||
constants[49] = scene.fog_ramp[0];
|
constants[49] = scene.fog_ramp[0];
|
||||||
constants[50] = scene.fog_ramp[1];
|
constants[50] = scene.fog_ramp[1];
|
||||||
constants[51] = scene.fog_ramp[2];
|
constants[51] = scene.fog_ramp[2];
|
||||||
|
|
@ -7656,8 +7817,9 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
// t6 (cube) shares its table with t7 (shadow atlas); re-pair both.
|
// t6 (cube) shares its table with t7 (shadow atlas); re-pair both.
|
||||||
cmd->SetTexturePair(7, cube_tex->srv,
|
cmd->SetTexturePair(7, cube_tex->srv,
|
||||||
shadow_ready ? g_r.shadow_srv_final : g_r.white.srv);
|
shadow_ready ? g_r.shadow_srv_final : g_r.white.srv);
|
||||||
// World-shading v2 material pair (t8 detail + t9 decal spec).
|
// World-shading v2 material pair (t8 detail + t9 decal spec); the exact
|
||||||
if (v2_flags != 0) {
|
// ocean rides the same pair (t8 = second PCA component, t9 = overlay).
|
||||||
|
if (v2_flags != 0 || ocean_n2 || ocean_ov) {
|
||||||
cmd->SetTexturePair(8, (v2_detail != nullptr ? v2_detail : &g_r.white)->srv,
|
cmd->SetTexturePair(8, (v2_detail != nullptr ? v2_detail : &g_r.white)->srv,
|
||||||
(v2_spec2 != nullptr ? v2_spec2 : &g_r.white)->srv);
|
(v2_spec2 != nullptr ? v2_spec2 : &g_r.white)->srv);
|
||||||
}
|
}
|
||||||
|
|
@ -8008,9 +8170,16 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// The exact ocean surface (fam 31) is an OPAQUE draw in the game (its
|
||||||
|
// PS outputs alpha 0 and it writes z-prepass depth); routing it through
|
||||||
|
// the sorted alpha sub-pass would composite the blended horizon sheet
|
||||||
|
// (ocean.reflection) UNDER it. The legacy fallback also renders opaque,
|
||||||
|
// so the route holds while textures/rows are still resolving.
|
||||||
|
const bool ocean_opaque = item.water && item.water_ocean == 1 &&
|
||||||
|
scene.shadow_valid && scene.ocean_valid;
|
||||||
if ((item.transparent || item.water || hair_blend || glass_blend ||
|
if ((item.transparent || item.water || hair_blend || glass_blend ||
|
||||||
cac_alpha_blend || refl_trans_blend || char_fade_blend) &&
|
cac_alpha_blend || refl_trans_blend || char_fade_blend) &&
|
||||||
debug_mode == 0) {
|
!ocean_opaque && debug_mode == 0) {
|
||||||
if (REXCVAR_GET(skate3_native_render_scene_transparents)) {
|
if (REXCVAR_GET(skate3_native_render_scene_transparents)) {
|
||||||
transparent_items.push_back(&item);
|
transparent_items.push_back(&item);
|
||||||
stamp_route(2);
|
stamp_route(2);
|
||||||
|
|
|
||||||
|
|
@ -751,11 +751,13 @@ struct RendererState {
|
||||||
uint32_t shadow_dump_enqueued = 0;
|
uint32_t shadow_dump_enqueued = 0;
|
||||||
uint32_t shadow_dump_written = 0;
|
uint32_t shadow_dump_written = 0;
|
||||||
bool shadow_dump_done = false;
|
bool shadow_dump_done = false;
|
||||||
// Per-frame shadow constant buffer ring (CBV b1). 512-byte slices: the
|
// Per-frame shadow constant buffer ring (CBV b1). 768-byte slices: the
|
||||||
// first 256 bytes are the original 16-row block, rows 16-18 carry the
|
// first 256 bytes are the original 16-row block, rows 16-18 carry the
|
||||||
// dynamicobject world-shadow transform (dyn_ws* in the scene shader).
|
// dynamicobject world-shadow transform (dyn_ws*), rows 19-22 the
|
||||||
|
// flowingwateralpha m_params (wat_p*), rows 23-33 the ocean PCA/material
|
||||||
|
// rows and the oceanreflection fade row (oc_* / orf).
|
||||||
static constexpr uint32_t kShadowCbRegions = 8;
|
static constexpr uint32_t kShadowCbRegions = 8;
|
||||||
static constexpr uint32_t kShadowCbSlice = 512;
|
static constexpr uint32_t kShadowCbSlice = 768;
|
||||||
nrhi::Buffer* shadow_cb = nullptr;
|
nrhi::Buffer* shadow_cb = nullptr;
|
||||||
uint8_t* shadow_cb_cpu = nullptr;
|
uint8_t* shadow_cb_cpu = nullptr;
|
||||||
// dynamicobject static world-shadow map (512x512, same convention as the
|
// dynamicobject static world-shadow map (512x512, same convention as the
|
||||||
|
|
|
||||||
|
|
@ -292,6 +292,20 @@ inline bool g_shadow_frame_done = false;
|
||||||
inline float g_family_rows[4] = {0.3435f, 0.02f, 1.0f, 0.45f};
|
inline float g_family_rows[4] = {0.3435f, 0.02f, 1.0f, 0.45f};
|
||||||
inline bool g_tree_frame_done = false;
|
inline bool g_tree_frame_done = false;
|
||||||
inline bool g_proxy_frame_done = false;
|
inline bool g_proxy_frame_done = false;
|
||||||
|
// flowingwateralpha m_params + animation-time rows (see
|
||||||
|
// FrameScene::water_rows), captured from a flowingwateralpha PS bank
|
||||||
|
// (debug-path classified). Guest render thread only.
|
||||||
|
inline float g_water_rows[17] = {};
|
||||||
|
inline bool g_water_have = false;
|
||||||
|
inline bool g_water_frame_done = false;
|
||||||
|
// ocean_defaultPS / oceanreflection_defaultPS rows (see
|
||||||
|
// FrameScene::ocean_rows / oceanrefl_rows). Guest render thread only.
|
||||||
|
inline float g_ocean_rows[40] = {};
|
||||||
|
inline bool g_ocean_have = false;
|
||||||
|
inline bool g_ocean_frame_done = false;
|
||||||
|
inline float g_oceanrefl_rows[4] = {};
|
||||||
|
inline bool g_oceanrefl_have = false;
|
||||||
|
inline bool g_oceanrefl_frame_done = false;
|
||||||
// dynamicobject.fx frame-global lighting rows (see FrameScene::dynobj_rows),
|
// dynamicobject.fx frame-global lighting rows (see FrameScene::dynobj_rows),
|
||||||
// captured from a dynamicobject/alphatestdynamicobject PS bank (debug-path
|
// captured from a dynamicobject/alphatestdynamicobject PS bank (debug-path
|
||||||
// classified). Guest render thread only.
|
// classified). Guest render thread only.
|
||||||
|
|
@ -719,6 +733,7 @@ inline std::atomic<uint64_t> g_char_attempts{0};
|
||||||
inline std::atomic<uint64_t> g_char_valid{0};
|
inline std::atomic<uint64_t> g_char_valid{0};
|
||||||
inline std::atomic<uint64_t> g_char_drawn{0};
|
inline std::atomic<uint64_t> g_char_drawn{0};
|
||||||
inline std::atomic<uint64_t> g_dynobj_drawn{0};
|
inline std::atomic<uint64_t> g_dynobj_drawn{0};
|
||||||
|
inline std::atomic<uint64_t> g_water_drawn{0}; // exact-branch water draws
|
||||||
// character.cloth_ropa items captured in the sim-active RIGID mode (world
|
// character.cloth_ropa items captured in the sim-active RIGID mode (world
|
||||||
// from c188/c191 instead of a bone palette, see CaptureSkinnedState).
|
// from c188/c191 instead of a bone palette, see CaptureSkinnedState).
|
||||||
inline std::atomic<uint64_t> g_ropa_rigid{0};
|
inline std::atomic<uint64_t> g_ropa_rigid{0};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue