Changing Materials (Textures and Shaders) - eArmada8/gust_stuff GitHub Wiki
Changing Materials (Textures and Shaders)
This page is intended to be a deep dive into materials, and how to think about changing them.
Quick definition of G1MG and the contents of mesh_metadata.json
The mesh metadata (the G1MG section of a G1M file) is in mesh_metadata.json, which is written by g1m_export_meshes.py to the meshes folder (named after the g1m file), and is read by g1m_import_meshes.py to rebuild the G1MG section at the time of import.
G1MG is split into 9 sections:
- GEOMETRY_SOCKETS - Unused in modding
- MATERIALS - Despite its name, this is the location of the texture assignments
- SHADER_PARAMS - The location of tuneable shader parameters
- VERTEX_BUFFERS - Location of the actual vertex buffers (the "VB" is here) - the data is not in the json, just a description
- VERTEX_ATTRIBUTES - Location of the vertex buffer structures (the "FMT" is derived from here)
- JOINT_PALETTES - Location of the bone maps (the "VGMAP" is derived from here)
- INDEX_BUFFER - Location of the index buffers (the "IB" is here) - the data is not in the json, just a description
- SUBMESH - The most important section, this is where all the primary assignments are. (which IB, VB go with which bone map, which textures, which shader params, etc)
- MESH_LOD - Describes a variety of things, such as the shader assignments, level of detail (LoD) assignments, cloth mesh data assignments. For some reason, there are 2 blocks - when making changes, update them both!
Structure of the materials
Materials are defined as the combination of textures and the pixel shaders that draw them. They are assigned in a variety of locations throughout the metadata. However, every material has the same basic structure:
The shader is the basic programming code that tells the GPU how to draw and color an object. It has two sets of options - the textures and the parameters.
Shaders
Shaders seem to be assigned in MESH_LOD of all places. Each LOD group has two entries (blocks), so I do not know all the rules of assigning shaders, but the hex code seems to tell the game which shader to use. Perhaps the two blocks are vertex shaders and pixel shaders? Or skinned and unskinned pixel shaders? I do not know.
{
"id_referenceonly": 0,
"name": "@E24D26E6",
"clothID": 0,
"unknown": 0,
"NUNID": 4294967295,
"indexCount": 2,
"indices": [
8,
9
]
},
In this example, in block 0, submesh 8 and 9 use @E24D26E6, apparently. Changing this hash code to another code (from the same G1M) seems to change the shader. The shader parameters must be compatible, more on that later. Also, I would presume that you cannot exchange cloth mesh (4D) shaders with non-cloth-mesh shaders, although I have not confirmed this.
Shader parameters
In the SHADER_PARAM section, you will find all the parameters. They do not seem to be fully understood or even properly decoded, as the values do not always make sense. Change individual values at your own risk. The parameter section assignments are in SUBMESH, more on this later.
Textures
Textures are in MATERIALS. Each entry is a list of textures, plus some variables. Many we do not know the meaning of, but "type" helps define which textures are used for color, normal mapping, highlights, shadows, etc.
For example, this section:
{
"id_referenceonly": 0,
"unknown1": 0,
"textureCount": 4,
"unknown2": 1,
"unknown3": 1,
"textures": [
{
"id": 0,
"layer": 0,
"type": 1,
"subtype": 1,
"tilemodex": 4,
"tilemodey": 4
},
{
"id": 3,
"layer": 0,
"type": 2,
"subtype": 25,
"tilemodex": 4,
"tilemodey": 4
},
{
"id": 2,
"layer": 0,
"type": 5,
"subtype": 5,
"tilemodex": 4,
"tilemodey": 4
},
{
"id": 1,
"layer": 0,
"type": 3,
"subtype": 0,
"tilemodex": 4,
"tilemodey": 4
}
]
},
corresponds to:
The id refers to the texture in the g1t file; textures inside g1t do not have names. Therefore, when unpacking and repacking g1t files, the g1t.json file will list the assignments of filenames to id numbers. I believe type 1 is color, type 2 is emissive, type 3 is normal and type 5 is occlusion, although I think it is more useful to just look at the assigned textures to understand the types (there are quite a few). The tilemode x and y seem to always be 4, which apparently is WRAP/REPEAT.
Changing materials
In this example, I want to change the Ryza's torso clothing to be shaded as skin instead.
Why might I want this? The most likely scenario is that I need to insert a new mesh with skin textures, using bones that the torso clothing mesh has access to, but the chest skin mesh does not, perhaps for a swimsuit mod or a halter top or something. This is also an important technique for mesh segmentation.
In this case, I want Mesh 10 to be shaded just like Mesh 15.
Looking in SUBMESH, I can compare their two blocks:
Mesh 10:
{
"id_referenceonly": 10,
"submeshFlags": 53,
"vertexBufferIndex": 9,
"bonePaletteIndex": 18,
"boneIndex": 0,
"unknown": 3,
"shaderParamIndex": 0,
"materialIndex": 0,
"indexBufferIndex": 9,
"unknown2": 1,
"indexBufferPrimType": 3,
"vertexBufferOffset": 0,
"vertexCount": 7634,
"indexBufferOffset": 0,
"indexCount": 36975
},
Mesh 15:
{
"id_referenceonly": 15,
"submeshFlags": 53,
"vertexBufferIndex": 9,
"bonePaletteIndex": 23,
"boneIndex": 2,
"unknown": 3,
"shaderParamIndex": 2,
"materialIndex": 0,
"indexBufferIndex": 9,
"unknown2": 1,
"indexBufferPrimType": 3,
"vertexBufferOffset": 11769,
"vertexCount": 1135,
"indexBufferOffset": 55995,
"indexCount": 5112
},
The goal is to have mesh 10 use mesh 15's shader with mesh 15's shader params. To successfully change assignments, the new assignment should have the expected types of textures and parameters. For example, if we want to use mesh 15's shader, and it has 4 textures (types 1,2,3,5) and 9 shader parameters (rampIndex, rampRimColorAdj, rampRimThres, rmIndex, wtBldPrms, wtDtFade, wtDtMkFade, wtWet, atColor), then we will need to make sure mesh 10 also has all of those.
Step 1 - Assign the shader
Luckily this is done. It turns out both meshes actually use the same shader. In the MESH_LOD section:
{
"id_referenceonly": 10,
"name": "@339F19F6",
"clothID": 0,
"unknown": 0,
"NUNID": 4294967295,
"indexCount": 8,
"indices": [
10,
11,
12,
13,
14,
15,
16,
17
]
},
We can see 10,11,12,13,14,15,16,17 all belong to the same group. If not, we can change the name of one group match the other (@339F19F6 in block 0, @00012B6D in block 1) - this would change the shader for the entire group and every submesh in that group would need compatible shader parameters and texture assignments. Or you can move the index over - but be very careful - clothID and NUNID must already match! Finally, you can create a whole new section. Just add it to the end - be careful with commas! I recommend a dedicated JSON editor like jsoneditoronline.org or a JSON validator like JSON Viewer plugin for Notepad++. Remember both block 0 and 1 must be updated!
*NOTE: While I have not personally confirmed this, it is reasonable to assume that the index and vertex buffer format is not flexible within a set of shaders. For example, if you want to use a certain shader, but the original mesh it is assigned to has 2 sets of UV coordinates (TEXCOORDs), then the mesh you are going to use it on should also have an identical format with 2 TEXCOORDs. This does not appear to be completely strict - I have changed meshes from Triangle Strip to Triangle List, for example, and the shaders rendered them correctly.
Step 2 - Set the texture assignments
Looking at the SUBMESH section, we can see that both mesh 10 and mesh 15 use "materialIndex": 0
. In this case, we do not need to change the textures, so we are actually done. However, if we wanted to change the textures for mesh 10 without changing the textures for mesh 15, for example, we would need the two meshes to point to two separate texture sections. In that case, we could, in MATERIALs, copy the entire section "id_referenceonly": 0
and paste it on to the end. Since there are currently 6 sections (0 through 5), we would be adding a seventh section (which is numbered 6, because JSON always starts at 0). We would then point mesh 10 to the seventh section by changing "materialIndex": 0
to "materialIndex": 6
.
The most important thing to keep in mind is that the new section likely requires the same set of textures to work properly, as the shader is expecting those values to be present (in our case, color, normal, emissive and occlusion texture assignments).
Step 3 - Set the shader parameters
Aha! This is where the two materials differ. They had the same shaders, same materials, but the parameters are different. Mesh 10 is using the parameters at "shaderParamIndex": 0
and mesh 15 is using the parameters at "shaderParamIndex": 2
. Since I just want mesh 10 to look like mesh 15, all I have to do is set "materialIndex": 2
in mesh 10's section of SUBMESH.
It turns out that "shaderParamIndex": 0
(on the left) and "shaderParamIndex": 2
(on the right) differ only by a single value, "rampIndex".
As an alternative to reassigning mesh 10 to "materialIndex": 2
, I could simply set "rampIndex" to 1 in material index 0. This would change the look of the shader for every mesh that is assigned to "materialIndex": 0
, however. Also, again, I could create a whole new section in SHADER_PARAMs, especially if I want to customize the values and I only want mesh 10 to adopt those values.
The most important thing to keep in mind is that the new section requires the same set of shader parameters to work properly, as the shader is expecting those values to be present (in our case, rampIndex, rampRimColorAdj, rampRimThres, rmIndex, wtBldPrms, wtDtFade, wtDtMkFade, wtWet, and atColor). Notice the trend?
The final effect
Here is what mesh 10 looks like with the original shader parameters on the left, and the new parameters on the right.
As you can see, the blouse, shorts, boots and upper coat are being shaded now with the skin settings, instead of the cloth settings. This may seem to be a very subtle change, and one that could be achieved with simple texture editing. So why would we go to this much trouble?
First, recognize that you cannot always achieve the look you want through texture editing alone, especially in different lighting conditions. No amount of editing can substitute for adjusting the level of shine that a pixel shader applies, or whether it is using specular vs diffuse light reflection, or if rim lighting is present or absent, for just a few possible examples.
Second, many times you will find yourself dealing with two meshes that appear to the player as a single mesh. Nothing ruins this illusion like non-matching materials. For example, Ryza's jacket appears to the player to be a single object, but is actually comprised of mesh 0, mesh 5 and mesh 10. Since we assigned mesh 10 to a new set of shader parameters, it now becomes obvious where mesh 10 connects to mesh 0:
No amount of texture editing can fix this. The difference in color between the two meshes will actually change with the different lighting conditions, so this must be fixed by making sure the two materials are shaded in the same way.