bo3b / 3Dmigoto

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

Incorrect Vertex data in GF2 Elixium #187

Closed HBigger closed 5 months ago

HBigger commented 5 months ago

I tried to dump vertex buffers from Girl's Frontline2 Elixium. The value of texcoords, blendindices and blendweights are all the same. 000007-vb0=474ec2d2-vs=f58b6b788ceaba1a-ps=04d13489e228a5ef.txt

It's obvious that blendindices should be integers. I try to import the vb&ib files into blender, and the model is displayed normally, so i guess the position data is correct.

DarkStarSword commented 5 months ago

You forgot to attach the index buffer, or for that matter the rest of the frame analysis dump. The only valid data in the vertex buffer you did attach is POSITION and NORMAL. TEXCOORD is in vb1, which you forgot to attach, and it's AlignedByteOffset is not 0, which is unusual given the header declares that nothing else is in vb1. Everything else reuses InputSlot:0 and AlignedByteOffset: 0 making them invalid.

Curiously the vertex buffer stride (40 bytes) is larger than the data inside it (sizeof(R32G32B32_FLOAT) + sizeof(R32G32B32_FLOAT) = 24 bytes), leaving room for an additional 16 bytes per vertex. Since you forgot to attach the buf files I cannot examine them to determine if there is any actual data here, or if it was just padding (which would be unusual).

Is this buffer straight out of 3DMigoto or have you used another script to pre-process it?

Google tells me this game engine is Unity, so it is likely that the missing blendindices data is in an earlier draw call - the vanilla version of my blender script does not fully support combining these together for import yet, though there is some work in progress towards this goal - refer to this bug: https://github.com/DarkStarSword/3d-fixes/issues/19

There are forks of the script created for specific games (e.g. Genshin Impact) that do support the pre-skinning pass that Unity uses, but my understanding is that they used hard coded offsets so may require some modification to work in other games.

HBigger commented 5 months ago

Thanks for your reply!

Curiously the vertex buffer stride (40 bytes) is larger than the data inside it (sizeof(R32G32B32_FLOAT) + sizeof(R32G32B32_FLOAT) = 24 bytes), leaving room for an additional 16 bytes per vertex. Since you forgot to attach the buf files I cannot examine them to determine if there is any actual data here, or if it was just padding (which would be unusual).

In a drawcall which shares the same vb0_vertexcount(of course also the same vb0 hash) and ib_first index, I found a vb0 file that solve this problem. 000019-vb0=474ec2d2-vs=b1701006f73554da-ps=7fb77c7166d44fb1.txt I put part of the vb0 content here for convenience

stride: 40

element[2]: SemanticName: TANGENT SemanticIndex: 0 Format: R32G32B32A32_FLOAT InputSlot: 0 AlignedByteOffset: 24 InputSlotClass: per-vertex InstanceDataStepRate: 0 vertex-data: vb0[0]+000 POSITION: -0.0827184916, -0.0571886711, 0.0920202881 vb0[0]+012 NORMAL: 0.88827616, 0.00512076262, -0.450060397 vb0[0]+024 TANGENT: 0.103927225, -0.975161254, 0.195394143, -1 vb0[0]+000 TEXCOORD1: 0.313725501, 0.407843143, 0.662745118, 0.741176486 vb0[0]+000 TEXCOORD2: 0.313725501, 0.407843143, 0.662745118, 0.741176486 vb0[0]+000 TEXCOORD3: 0.313725501, 0.407843143, 0.662745118, 0.741176486 vb0[0]+000 TEXCOORD5: 0.313725501, 0.407843143, 0.662745118, 0.741176486 vb0[0]+000 TEXCOORD6: 0.313725501, 0.407843143, 0.662745118, 0.741176486 vb0[0]+000 TEXCOORD7: 0.313725501, 0.407843143, 0.662745118, 0.741176486 vb0[0]+000 BLENDWEIGHTS: 0.313725501, 0.407843143, 0.662745118, 0.741176486 vb0[0]+000 BLENDINDICES: 0.313725501, 0.407843143, 0.662745118, 0.741176486

I guess the TANGENT data is also valid in this vb0 file. And its size is 16(R32G32B32A32_FLOAT). The size add up to 40(12+12+16), which matches the stride.

Is this buffer straight out of 3DMigoto or have you used another script to pre-process it?

It's straight out of 3DMigoto.

so it is likely that the missing blendindices data is in an earlier draw call

I write a script to search all vb files in the same folder that contain the same vertex count. vbfile-vb0=474ec2d2-vertexcount=3813-id=7_19_34_txt.zip And I check them manually. All of them contain no data that looks like the actual BLENDINDICIES

I also check all the files that share the same vertex buffer hash with it (but with different first index & vertex count), but to no avail. vbfile-vb0=474ec2d2-vertexcount=5211-id=14_23_38_txt.zip vbfile-vb0=474ec2d2-vertexcount=5357-id=1_55_56_txt.zip

StarBobis commented 5 months ago

Emmm, GF2 already fix the problem for can use 3dmigoto to make model modify mod, this game sell skins and change their GPU bone animation calculation into CPU, so now you can't dump any topology = pointlist 's file to get it's BLENDINDICES and BLENDWEIGHTS.

And the value you dumped from trianglelist file is all same because it's a fake value generated by 3dmigoto if 3dmigoto find it's slot is empty but element desc is there, actually you can see the file's stride is 40 which means there is only POSITION 12, NORMAL 12, TANGENT 16, add them together is 40, so other value is fake and useless.

HBigger commented 5 months ago

Base on the the script for Genshin Impact https://github.com/SilentNightSound/GI-Model-Importer/blob/main/Tools/genshin_3dmigoto_collect.py I guess the blend indices data is in a certain root draw call's vb file.

So I try to search BLENDINDICES data that's over 1 through the whole dump folder. And the result is there's no BLENDINDICES data that's over 1.

    invalidcount = 0
    validcount = 0
    for filename in frame_dump_files:
        if "vb" in filename and ".txt" in filename:
            with open(os.path.join(frame_dump_folder, filename), "r") as f:
                for x in f.readlines():
                    if "BLENDINDICES:" in x:
                        indices = x.split(":")[1]
                        for y in indices.split(","):
                            indi = float(y)
                            if indi>1:
                                validcount += 1
                            else: 
                                invalidcount += 1

            print(validcount,invalidcount)                       
    return
DarkStarSword commented 5 months ago

So I try to search BLENDINDICES data that's over 1 through the whole dump folder. And the result is there's no BLENDINDICES data that's over 1.

It could be that frame analysis wasn't active when the pre-skinning pass ran - how have you set up FA in this game?

Another possibility is that the game might be applying pre-skinning on the CPU, which 3DMigoto can't do much about (Edit: StarBobis post above confirms this is the case). Apparently Unity does this if the developer didn't have a pro license (which is evident when launching the game as the Unity splash screen will display - a pro license is required to remove that).

If you attach the frame analysis log file I can take a look to find out which shaders were responsible for the pre-skinning pass or if it came from the CPU, or if you would like to learn how to do this yourself, the process is:

  1. Identify the correct log file. If the game is not using deferred rendering it will just be "log.txt", otherwise it might be one of the log-*.txt files, with the ID matching the ctx- frame analysis folder.
  2. Locate the IASetVertexBuffers line for the buffer of interest. In your case the draw call is 7 (from the 1st part of the filename), so search for a line in the log "000007 IASetVertexBuffers". There may be 1, more than 1, or 0 lines like this, depending on how efficiently the game handles passing these buffers to DirectX. You are looking for the last one of these that sets the buffer in slot 0 before draw call 7 ends, e.g. you might find something like this:
    000007 IASetVertexBuffers(StartSlot:0, NumBuffers:1, ppVertexBuffers:0x0000001DEC0DFB08, pStrides:0x0000001DEC0DFAD0, pOffsets:0x0000001DEC0DFAD4)
       0: resource=0x0000018AD5DA2230 hash=474ec2d2
  3. In the above line, 0: is the slot (vertex buffer 0), hash should match what was in your filename, and resource=0x.... is the memory address - search backwards in the file for that memory address. You are particularly looking for an SOSetTargets call, but take note if you do see it used in anything else as well in case the game is doing something weird. Continuing the above example you might find something like:
    000001 SOSetTargets(NumBuffers:1, ppSOTargets:0x0000001DEC0DFC60, pOffsets:0x0000001DEC0DFCB0)
       0: resource=0x0000018AD5DA2230 hash=65fd0d73
  4. That means the pre-skinning pass for that buffer occurred in draw call 1. Then you can look for other interesting info from this draw call, for example the input vertex buffers that are likely to contain the T-posed positions, blendindices + blendweights:
    000001 IASetVertexBuffers(StartSlot:0, NumBuffers:1, ppVertexBuffers:0x0000001DEC0DFB48, pStrides:0x0000001DEC0DFB10, pOffsets:0x0000001DEC0DFB14)
       0: resource=0x0000018ACAFC84B0 hash=8f4bc08a
    000001 IASetVertexBuffers(StartSlot:1, NumBuffers:1, ppVertexBuffers:0x0000001DEC0DFB48, pStrides:0x0000001DEC0DFB10, pOffsets:0x0000001DEC0DFB14)
       1: resource=0x0000018ACAFC87B0 hash=0bb8989a

    The shaders used for pre-skinning (note no pixel shader is assigned, this pass operates with vertex + geometry shaders only, and the current 3DMigoto won't hash the geometry shader for these stream out passes (which is on my TODO list to fix), so only the vertex shader hash can be used to match this in a ShaderOverride):

    000001 VSSetShader(pVertexShader:0x00000189E39149C8, ppClassInstances:0x0000000000000000, NumClassInstances:0) hash=e58e7a5d769437dd
    000001 PSSetShader(pPixelShader:0x0000000000000000, ppClassInstances:0x0000000000000000, NumClassInstances:0)
    000001 GSSetShader(pShader:0x00000189E3914108, ppClassInstances:0x0000000000000000, NumClassInstances:0)
DarkStarSword commented 5 months ago

Emmm, GF2 already fix the problem for can use 3dmigoto to make model modify mod, this game sell skins and change their GPU bone animation calculation into CPU, so now you can't dump any topology = pointlist 's file to get it's BLENDINDICES and BLENDWEIGHTS.

Yeah, that will be a show stopper for 3DMigoto, since it can't intervene with anything the game does on the CPU. I don't know anything about this game - does it use any anti-tamper/anti-mod/anti-cheat detection?

And the value you dumped from trianglelist file is all same because it's a fake value generated by 3dmigoto if 3dmigoto find it's slot is empty but element desc is there, actually you can see the file's stride is 40 which means there is only POSITION 12, NORMAL 12, TANGENT 16, add them together is 40, so other value is fake and useless.

They're not generated by 3DMigoto - it simply follows whatever the game specified in the input layout that it passed to DirectX, but since they all reuse the same offset as POSITION it will try to reinterpret the POSITION data as a different type (e.g. here POSITION is a R32G32B32_FLOAT, but the invalid semantics are declared as e.g. R8G8B8A8_UNORM, so it will attempt to interpret the data as such - and you can verify that if the shader were to access those semantics it would get the same values that 3DMigoto wrote in the .txt file). 3DMigoto doesn't try to be smart here - it just logs whatever the game told it. It's the Blender addon that recognises they are invalid and ignores them.

HBigger commented 5 months ago

If you attach the frame analysis log file I can take a look to find out which shaders were responsible for the pre-skinning pass or if it came from the CPU, or if you would like to learn how to do this yourself, the process is:

Thanks for your elucidation. I followed the process and examine the log file. log.txt Unfortunately, there's no SOSetTargets call. So no geometry shaders is utilized here. Guess the game use cpu for pre-skinning.