bo3b / 3Dmigoto

Chiri's DX11 wrapper to enable fixing broken stereoscopic effects.
Other
688 stars 109 forks source link

Nioh 2, Shader Overwrite ambiguous #189

Closed Chimi-moryo closed 3 months ago

Chimi-moryo commented 4 months ago

Hi sir,

The past few weeks, i've been working on a Colorblind mod for nioh 2. So far i've been using the 3Dmigoto alteration available here : Nioh 2 ModEnabler

I've read and read about differents ways to alter shaders and textures through your videos and many tutorial. I kind of managed to do my mod but something seems of. So here is was i've done, and what seems broken :

I tried to change the color of the small dots that appears on the mini map like this :

image

Using the Hunter Mode, i found that the pixel shader in charge of the whole HUD is 4f03d3932b6d4324. But unfortunatly, all the icon resources shares the same hash : 31f8df9c. (32x32 image resources).

So what i tried after many trials was to edit the PixerShader and try to match by myself the corresponding texture and replacing it by another.

Here is my colorblind.ini file

[ShaderOverrideMiniMap]
hash = 4f03d3932b6d4324
x3 = ps-t0
x4 = $mods
ps-t26 = ResourceKodama
ps-t27 = ResourceKodamaColorblind
; Lots of ressources
ps-t38 = ResourceChest
ps-t39 = ResourceChestColorblind
; run = CustomShaderMiniMap

[TextureOverrideIcons]
hash = 31f8df9c 
filter_index = 1
match_priority = 1

[CustomShaderMiniMap]
ps =4f03d3932b6d4324-ps_replace.txt
handling = skip
drawindexed = auto

[ResourceKodama]
filename = kodama.dds 
[ResourceKodamaColorblind]
filename = kodama_colorblind.dds 

; All the resources files are here 

[ResourceChest]
filename = chest.dds 
[ResourceChestColorblind]
filename = chest_colorblind.dds

Sorry for the long file, but i wanted to be explicit with the fact that many textures are sharing the same Hash. i've only succeed to make my code compatible with the F2 key (enable/disable mod) by passing the value as a parameter as you can see.

And here how i altered the corresponding pixel Shader

cbuffer _Globals : register(b0)
{
  float SaturationScale : packoffset(c0);
  float Gamma : packoffset(c0.y);
  int ContrastValuesOfTexture[3] : packoffset(c1);
  float vATest : packoffset(c3.y);
}

SamplerState __smpsStage0_s : register(s0);
Texture2D<float4> currentTexture : register(t0);
Texture2D<float4> kodama : register(t26);
Texture2D<float4> kodamaColorBlind : register(t27);
Texture2D<float4> amrita : register(t28);
Texture2D<float4> amritaColorBlind : register(t29);
Texture2D<float4> enemy : register(t30);
Texture2D<float4> enemyColorblind : register(t31);
Texture2D<float4> boss : register(t32);
Texture2D<float4> bossColorblind : register(t33);
Texture2D<float4> people : register(t34);
Texture2D<float4> peopleColorblind : register(t35);
Texture2D<float4> miniboss : register(t36);
Texture2D<float4> minibossColorblind : register(t37);
Texture2D<float4> chest : register(t38);
Texture2D<float4> chestColorblind : register(t39);

// 3Dmigoto declarations
#define cmp -
Texture1D<float4> IniParams : register(t120);
Texture2D<float4> StereoParams : register(t125);

uint texEquals(Texture2D<float4> texture1, Texture2D<float4> texture2)
{
  float2 center = {0.5, 0.5};
  float4 texture1CenterPixel = texture1.Sample(__smpsStage0_s, center).xyzw;
  float4 texture2CenterPixel = texture2.Sample(__smpsStage0_s, center).xyzw;
  return texture1CenterPixel.x == texture2CenterPixel.x && texture1CenterPixel.y == texture2CenterPixel.y && texture1CenterPixel.z == texture2CenterPixel.z;
}

float4 getColor(Texture2D<float4> theTexture, float2 coord)
{
  return theTexture.Sample(__smpsStage0_s, coord).xyzw;
}

void main(
    float4 v0 : SV_Position0,
                float4 v1 : TEXCOORD0,
                            float2 v2 : TEXCOORD1,
                                        out float4 o0 : SV_Target0)
{
  float4 r0, r1, r2, r3;
  uint4 bitmask, uiDest;
  float4 fDest;

  float4 particle = IniParams.Load(int2(3, 0));
  float4 mods = IniParams.Load(int2(4, 0));

  float4 originalPixel = currentTexture.Sample(__smpsStage0_s, v2.xy).xyzw;
  r0.xyzw = originalPixel;

  if (particle.x == 1 && mods.x == 1) // only way to check the correct texture and mod compatible
  {
    if (texEquals(currentTexture, kodama))
    {
      r0.xyzw = getColor(kodamaColorBlind, v2.xy);
    }
    else if (texEquals(currentTexture, amrita))
    {
      r0.xyzw = getColor(amritaColorBlind, v2.xy);
    }
    else if (texEquals(currentTexture, enemy))
    {
      r0.xyzw = getColor(enemyColorblind, v2.xy);
    }
    else if (texEquals(currentTexture, boss))
    {
      r0.xyzw = getColor(bossColorblind, v2.xy);
    }
    else if (texEquals(currentTexture, people))
    {
      r0.xyzw = getColor(peopleColorblind, v2.xy);
    }
    else if (texEquals(currentTexture, miniboss))
    {
      r0.xyzw = getColor(minibossColorblind, v2.xy);
    }
    else if (texEquals(currentTexture, chest))
    {
      r0.xyzw = getColor(chestColorblind, v2.xy);
    }
  }

  // ... unaltered

  return;
}

But so far, as you can see in the ini file, if i use the CustomShader command in the ShaderOverride section, the whole HUD seems broken :

image

Whereas, when the pixelshader file is placed in the ShaderFixes folder (and commenting the Run CustomShader command), it works as intented :

image

and to be clear... the same happens when the shader is just the copy of the original shader place in my mod folder...

Do you have any idea regarding :

  1. The way i'm changing texture (by selecting the center pixel of each texture)
  2. How to include correctly the shader in my mod folder.

Here's my mod folder to fully understand what i'm trying to do :

image

DarkStarSword commented 4 months ago

Instead of drawindexed = auto, try draw = from_caller. The former is really only intended to be used when you have injected a custom index buffer and want the entire index buffer to be used, while the later is intended to be used with whatever buffers, offsets and draw call variant the game is already using.

And yes, I think the approach you are using to differentiate between textures makes sense. You might also see if enabling track_texture_updates=1 in the d3dx.ini allows you to differentiate between those textures by hash, but there are some significant performance impacts associated with that option so if you have a method that works well without it I'd probably stick to that.

I don't think it's quite relevant to your case, but you also might be interested in how I differentiated between different icons in Far Cry 4 - in that case it was using a texture atlas so all icons were on the same texture, and I needed to use the texture coordinates to differentiate between them, but there were too many to hard code the coordinates - so I dumped a copy of the atlas and painted over the icons in different colours that the shader could use to determine what to do with them: https://github.com/DarkStarSword/3d-fixes/blob/master/Far%20Cry%204/ShaderFixes/b5c2f686-icon-filter.png https://github.com/DarkStarSword/3d-fixes/blob/master/Far%20Cry%204/ShaderFixes/82efdcb5-icon-filter.png https://github.com/DarkStarSword/3d-fixes/blob/master/Far%20Cry%204/ShaderFixes/50344260ea3c9a9c-vs_replace.txt#L129 https://github.com/DarkStarSword/3d-fixes/blob/master/Far%20Cry%204/d3dx.ini#L796

Chimi-moryo commented 4 months ago

Thanks @DarkStarSword. I see what you did with the "art paiting" job on the initial map texture ^^. I can't interpolate this to my subject but i see the connection. The part with :

if (all(abs(colour - float4(0, 1, 0, 1)) <= 1/512)) return 1; if (all(abs(colour - float4(0, 0, 1, 1)) <= 1/512)) return 2; if (all(abs(colour - float4(1, 0, 1, 1)) <= 1/512)) return 5;

Could be usefull to add an epsilon to the calculation.

Just to be sure, can you confirm that the shader file "xxx.txt" must be placed in the ShaderFixes folder ? I'm gonna try your suggestion and see what can i do to optimize this.

Regards,

Chimi-moryo commented 3 months ago

@DarkStarSword hey sir, can you please just confirm that the shader replace txt file must be placed in the ShaderFixes folder and therefore, if I want to share my mod, I must explain to add both files in this folder and others files in the mod folder ?

DarkStarSword commented 3 months ago

If you put it in ShaderFixes 3DMigoto will automatically swap the original shader for it whenever it sees a shader with the matching hash, and if hunting is set to 0 this is done only once when the shader is initially loaded for best performance (if hunting is 1 or 2 it swaps it when the shader is used to allow reloading on the fly).

If you want to put it in a different folder to keep your mod installation instructions simple then you will need to match the shader hash in a [ShaderOverride] section and swap it yourself via a [CustomShader]. Performance won't be quite as good, but generally the impact is small enough to not matter.

Chimi-moryo commented 3 months ago

Hello, I've tried to do so as shown in the original post but it messes up everything as all the textures are broken in the HUD. Any clue regarding the code and how to properly use the custom shader ?

DarkStarSword commented 3 months ago

Did you try changing the draw command to draw = from_caller?

Chimi-moryo commented 3 months ago

Okay, my bad =D, it works with draw = from_caller. Can you explain a bit the difference please ? ^^'

DarkStarSword commented 3 months ago

draw = from_caller is intended to transfer an existing draw call from the game into a CustomShader. It will use the same type of draw call (Draw, DrawIndexed, DrawInstanced, etc) that the game used, and pass it the same arguments (vertex/index buffer offset and counts). You would typically use it when you are changing the shader or some other part of the render state, but have not altered the vertex/index buffers.

drawindexed = auto on the other hand is used for when you have altered the vertex and index buffers to replace the mesh being drawn. In these cases the original buffer offsets and counts that the game used are unlikely to match your replacement mesh (e.g. you may have added additional triangles to the mesh), so this will calculate these automatically assuming that the entire index buffer should be used (zero offset and the number of indices is determined based on the size of the currently bound index buffer), as that is how the 3DMigoto Blender addon sets up the exported buffers. Because it uses the entire buffer instead of just the subset that the game called for, trying to use it without replacing the vertex+index buffers may do the wrong thing.

Also worth noting that there are a couple of changes scheduled for the next release of 3DMigoto to add two extra similar draw commands: draw=auto for cases where there is no index buffer (uses the vertex buffer for this calculation instead) and drawindexedinstanced=auto for cases where GPU instancing is in use.

Chimi-moryo commented 3 months ago

Thanks for your explanation. I'll check the next updates as i think i'm gonna try to make more colorblind mods in the future.