-
Notifications
You must be signed in to change notification settings - Fork 1
Modifying materials (textures and shaders)
This is a deep dive on materials and how to change shaders. If you just want to change a texture, please refer to the basic modding tutorial. Included is a tutorial on working with the mask texture (that pink _p / _q texture), here. The second half of this tutorial on obtaining UV (texture coordinate) maps is here. Thank you to Arc for much of this information!
Materials are the combination of textures and a pixel shader (a small program used by the GPU to draw the object). The shader will apply the textures to the object, and then modify the appearance (e.g. applying shininess and transparency). We can change textures but shaders cannot be modified. We can exchange one shader for another, however. For a deep discussion on why shaders cannot be directly modified, please refer to this tutorial.
Every mesh in a model must have a material applied to it. This assignment is made in the mesh_info.json file, which is a plain text file in JSON format. If you edit this file in notepad, you will see its assigned material. For example, in Agnès' default outfit (chr5001.mdl), her jacket (2_chr5001_body_setup36_04.vb) is assigned to "cloth_wight".
Contents of polySurface4962_07.material:
{
"name": "chr5001_body_setup36",
...
"primitives": [
...
{
"id_referenceonly": 4,
"material": "cloth_wight",
...
Looking in metadata.json, we can see the material "cloth_wight". Note that build_collada.py will look in material_info.json for the actual material definition, as information in mesh_info.json is nothing more than an assignment.
{
"id_referenceonly": 6,
"material_name": "cloth_wight",
"shader_name": "chr_cloth",
"str3": "chr_cloth",
"shader_switches_hash_referenceonly": "518c1273a9b53411",
"textures": [
{
"texture_image_name": "chr5001_03",
"texture_slot": 0,
"wrapS": 1,
"wrapT": 0
},
{
"texture_image_name": "chr5001_03_q",
"texture_slot": 7,
"wrapS": 1,
"wrapT": 0
},
{
"texture_image_name": "chr5001_03_n",
"texture_slot": 3,
"wrapS": 1,
"wrapT": 0
},
{
"texture_image_name": "toon_cloth2",
"texture_slot": 9,
"wrapS": 2,
"wrapT": 2
}
],
"shaders": [
{
"shader_name": "frozen",
"type_int": 0,
"data": 0
},
...
"material_switches": [
{
"material_switch_name": "SPECULARMODE",
"int2": 1
},
...
"uv_map_indices": [
0,
0,
0,
0,
0,
0,
0,
0
],
"unknown1": [
0,
0
],
"unknown2": [
0,
2,
6,
0.0,
1
]
},
There are 5 sections to each material:
- The shader itself, described by name. "shader_name" and "str3" which seem to be identical. In the case above, "chr_cloth" refers to a compiled version of chr_cloth.fx, which is generally in a binary file e.g. chr_cloth#a1b2c3d5.fxo. We do not know which .fxo file corresponds to which shader configuration currently, but the game will hash the data in the .mdl and look up the correct .fxo file. The 64-bit hash below the name is unique to kuro_mdl_tool and is used for finding and comparing shaders.
- The texture assignments. Above you can see a base texture (chr5001_03.dds in texture slot 0), a normal map (chr5001_03_n.dds in texture slot 3), mask map (chr5001_03_q.dds in texture slot 7), and a gradient map (toon_cloth2.dds in texture_slot 9).
- Tuneable shader parameters. The parameters themselves can be tuned by changing the numbers; however the parameters themselves cannot be changed. For example, if "highLightIntensity_g" is not present, you cannot add it. You also cannot delete parameters that the shader expects to be present. Finally, some shader switches are here, we do not know why - assume you cannot change any parameters that start with "Switch_".
- Shader switches. These tell the compiler how to configure the shader (what features are present or absent). These cannnot be changed.
- UV Map Indices - These tell the shader which UV map to assign to which shader. There are always 8 numbers, even if there are fewer than 8 textures.
- Unknown values - We do not know what these are for.
To change textures, see the basic modding tutorial. Note that if you are not changing the material, then you can change the textures but NOT the texture slots. (e.g. If the material expects a normal map, you must provide one.) You cannot simply remove the slot and you cannot add new slots, unless you change the material.
Note that Kuro expects the textures to be vertically flipped, even BC7 textures. Also note that texture slot 7 is something called a mask map, which is 3 greyscale textures combined into a single texture - an ambient occlusion texture on the red channel, a specular map on the green channel, and a cartoon edge strength on the blue channel.
To change the tuneable parameters, simply modify the numbers. Send "glowIntensity_g" into the stratosphere if you like. We do not know what the valid ranges are for each parameter. Experiment, or look at other models / materials. Again, you cannot add or remove parameters without changing the material, and to my knowledge you cannot change values for "Switch_" parameters.
To change the texture sampler definitions, you can edit the values. Valid values for wrapS and wrapT include 0 (REPEAT), 1 (MIRROR) and 2 (CLAMP). Here is a tutorial on what these values mean - keep in mind the tutorial was written for WebGL, but the fundamentals are the same.
You cannot change the shaders or the shader switches without changing the material. Changing the shaders themselves is changing the materials, see the next section on how to do this. (Changing the switches list incorrectly will probably not result in crashes, but that particular mesh will not render.)
Materials can be swapped out with other valid materials. This is mainly if you need shader features that the current material does not provide.
For this tutorial, I am going to attempt to shorten Agnès' skirt (2_woman01_body79_05.vb of chr5001_c03.mdl) slightly, using texture transparency. Here you can see I have removed the bottom of her skirt.
Notice however that the transparent part of the skirt is black, instead of transparent:
We can see why when we look at the material shader. Above, I had identified Agnès' skirt as c03_wear_akarui_W
by peeking inside mesh_info.json. Going into material_switches
within c03_wear_akarui_W
in metadata.json, I can see that SWITCH_ALPHATEST
is not present. Under "shaders" (the parameters), Switch_AlphaTest
is set to 0.
We may or may not be able to just turn the switch on. That is because turning on the switch changes the configuration of chr_cloth.fx, and we are unable to compile a new configuration. We must use an existing shader configuration (one of the chr_cloth#________.fxo files that are already in the game). This means that both the specific combination of switches, and the order in which they are listed, must be a combination and order that already exists in compiled form in the game.
The safest thing to do is to completely transplant in a new material. Looking at the entry for c03_wear_akarui_W
in material_info.json:
{
"id_referenceonly": 7,
"material_name": "c03_wear_akarui_W",
"shader_name": "chr_cloth",
"str3": "chr_cloth",
"shader_switches_hash_referenceonly": "a8b9eb0baf9ad2dc",
"textures": [
{
"texture_image_name": "chr5001_c03_03",
"texture_slot": 0,
...
We can see that the shader is chr_cloth
and the computed shader hash is a8b9eb0baf9ad2dc
(together they are chr_cloth#a8b9eb0baf9ad2dc
). Here we will use kuro_find_similar_shaders.py, which is available in Kuro MDL Tool v1.5.9 or newer. The shaders database, kuro_shaders.csv, should be in the same folder as kuro_find_similar_shaders.py.
> python3 kuro_find_similar_shaders.py
Please enter name of shader to analyze: chr_cloth#a8b9eb0baf9ad2dc
Please enter game restriction [KURO1, KURO2, or blank for None]: KURO1
Restrict matches to chr_cloth? [Y/n]: Y
It will generate a report file, report_KURO1_chr_cloth#a8b9eb0baf9ad2dc.txt
. Scanning the results, we can find a shader that is otherwise identical, except for Alpha testing:
chr_cloth#33c952f4581366bf: (available in a1003d.mdl)
SWITCH_ALPHATEST: -1
Switch_AlphaTest: 1
According to the report, we can find chr_cloth#33c952f4581366bf
in a1003d.mdl
. Unpacking that model and searching material_info.json, we find that chr_cloth#33c952f4581366bf
is used by cloth_bright_npc
.
Create a duplicate backup copy of c03_wear_akarui_W
. We will need it in a moment.
Copy over cloth_bright_npc
to Agnès' material_info.json, replacing c03_wear_akarui_W
. Change the name to c03_wear_akarui_W
. Change all the textures to the correct textures for c03_wear_akarui_W
. In the shaders section, replace the values (except any values starting with "Switch_" including "Switch_AlphaTest" which must remain untouched). For example, for shadowColor1_g
, the value is 0.56/0.43/0.41 in cloth_bright_npc
but we need to change it to 0.44/0.42/0.49 since that the correct value from c03_wear_akarui_W
. Note: I rounded here for clarity, but you do not want to reduce the precision - copy over the exact value!
You will also want to copy over the uv_map_indices
and unknown2
section at the bottom of the material. Some of the unknown2
values also function as switches. For example, the first value is generally 0, but if set to 1 then the material will change from Alpha Opaque to Alpha Blend, independent of the SWITCH_ALPHATEST
. I have no idea why.
This time, after I compile the model, the transparency is working!
Trails into Daybreak uses a new type of texture which we are calling the mask map. It is actually composed of three monochrome textures in one texture. Each texture uses one color channel: the red channel contains the ambient occlusion map, the green channel contains the specular map, and the blue channel contains a cartoon edge strength map. Thank you to Vandread, Arc and Ryn for helping me work these out!
Here you can see the effect of each individual map on the model - the clothes only:
To decompose the mask map into three textures (as I did in the example above), use the 'Color Matrix' Adjustment Plugin by midora for Paint.NET. Use the Red to Gray (solid)
option to obtain the ambient occlusion map, the Green to Gray (solid)
option for the specular map, and the Blue to Gray (solid)
option for the cartoon edge strength map.
To create these maps from scratch, as well as the normal map, I use Normal Map Online by Christian Petry. It is very easy to use. Upload your texture in PNG format to the website. I adjust the normal settings to strength of 0.8 and level of 8.8. Here I used default settings for everything else, but I recommend increasing the ambient occlusion strength from 0.5 to 1 as well. (You will see there is not enough AO in the resultant screenshots, but I was too lazy to take them again.)
Note: You may want to make your own custom maps, that is fine. Just make sure they are monochrome, and the remaining steps will be the same.
Once you have all your settings, press Download All.
You should have four new textures.
Load AmbientOcclusionMap.png
, SpecularMap.png
and DisplacementMap.png
into Paint.NET as layers on a single image, by closing all images (empty workspace), then dragging all three images onto the workspace at once and selecting "Add Layers." (We are going to use the displacement map as the cartoon edge strength map - it's not perfect but it does the job.)
Note: When I originally wrote this tutorial, I used the displacement map for the cartoon edge strength map. Now, however, I do not bother - I just use a solid dark grey square, resulting in even outlines. Either method is valid.
Select the top layer (which is SpecularMap.png
in my example). Open the Curves tool (Adjustments menu -> Curves, or press Ctrl-Shift-M). 1. Change the Transfer map from Luminosity
to RGB
. 2. For SpecularMap
we will unselect Green. 3. Drag the upper right point down from the top to the bottom, which will remove the red and blue channels, leaving a green and black image.
Go into Layer Properties (Layer menu -> Layer Properties, or press F4). Change the Blend Mode to Additive.
Repeat with the DisplacementMap.png
(change to blue by removing red and green) and AmbientOcclusionMap.png
(change to red by removing blue and green).
If you have a bottom white layer, which usually happens as Paint.NET adds one by default, you should delete it. Select that layer, then delete the layer (Layers menu -> Delete Layer, or press Ctrl-Shift-Del). Your layers should look like this:
Save your mask map as a BC7 DDS. Do the same for your normal map.
Load the map into the game to see the result. Here I think I would probably choose to increase the ambient occlusion strength to 1.0 instead of 0.5, but otherwise it is working as we intended!
The UV maps in the Kuro engine are a little quirky. If you just pull a texture into Blender and apply it to the mesh, it will look like this:
To make the life easier of 3D Modelers, texture coordinates can be off the texture, and the texture will repeat over and over again so that the coordinates will once again fall on the map. This is advantageous so that, for example, the texture of the right hand and the left hand can be the same, and the texture map can be separate for each (instead of the two being right on top of each other. For example, here is the map for Agnès's dress, which is 2_woman01_body79_03.vb:
First, just looking on the right side, you can see that the texture is vertically flipped. This is a quirk of the Kuro engine - all textures are fed into the GPU from the bottom to the top - even BC7. If you want, you can flip the image in Blender to see how the UV map actually applies (Image menu -> Flip -> Vertically).
Once it has been flipped, you can see that the UV map lines up properly now.
The UV map is double the size of the texture itself. You can see that the texture coordinates on the left are meant to be projected onto a mirror image of the texture on the right. And looking at "c03_wear_akarui_W" which is mapped to 2_woman01_body79_03.vb, we can see the texture does indeed dictate mirroring on the X-direction (and wrapping on the Y-direction). (S/T/R is X/Y/Z)
{
"id_referenceonly": 7,
"material_name": "c03_wear_akarui_W",
"shader_name": "chr_cloth",
"str3": "chr_cloth",
"textures": [
{
"texture_image_name": "chr5001_c03_03",
"texture_slot": 0,
"wrapS": 1,
"wrapT": 0
},
Remember, 0 is REPEAT, 1 is MIRROR and 2 is CLAMP - as discussed above.
This means that the game will repeat the textures over and over like this when mapping the coordinates:
However, Blender wraps (repeats) by default, instead of mirror (ping-pong). It is repeating the textures like this instead:
Therefore, if you really wanted to see what the texture should look like in Blender, you will need to recreate this node tree. It implements reading the Y-coordinate from 1.0 to 0.0 instead of 0.0 to 1.0, sets X-coordinate to mirror (ping-pong) and the Y-coordinate to wrap (repeat).
Note: You do not need to re-create these node trees, unless for some reason you need to see the results in Blender. I have only put them together for illustration purposes. However, if you want to re-create the node tree, they can be found in the Add menu at the top of the Shader editor, right above the nodes. Also, this node tree does not implement MixRBG to add the secondary UV.
UV Map: Add -> Input -> UV Map
Separate / Combine XYZ: Add -> Convertor -> Separate / Combine XYZ
Ping-Pong / Wrap: Add -> Convertor -> Math
Map Range: Add -> Convertor -> Map Range
Again, Please try this interactive wrap tool to see REPEAT, CLAMP_TO_EDGE and MIRROR in action! This tool is from here.
When evaluating what parts of a texture need to change, it is often useful to grab a UV map as png.
-
Now that you have applied chr5001_c03_03.png to 2_woman01_body79_03.vb, make sure 2_woman01_body79_03.vb is selected, and go to the UV Editing workspace on the Topbar. Note that two large editors predominate this workspace, UV Editor on the left and 3D Viewport in the middle. Outliner and Properties continues to be on the right.
-
In the 3D Viewport (center right), be sure that 2_woman01_body79_03.vb is the active mesh, and the viewport is in Edit Mode. Everything should be selected (in bright orange); if this is not the case, Select Menu -> All (Press A) in the 3D Viewport. Only vertices selected on the right will show up in the UV editor on the left.
- Since the UV map is utilizing mirror, we will need to export half the map at a time. Select the entire map, Select Menu -> All (Press A) in the UV Editor. Export the map, UV Menu -> Export UV Layout. Save the PNG file. Despite having the entire map selected, only the part of the map over the texture will export.
- To get the other side of the map, flip the map. (UV menu -> Mirror -> X Axis) Then you can export the other side.
Note: Often the map will not actually be on the texture, and will rely on texture mirroring / wrapping. For instance, this is a very common scenario:
In order to export and preview this UV, you will need to bring it down to exactly where it would be if it were not wrapped/mirrored. In the Sidebar, open the Image tab, and click on Y. Here it is 1.47861, so subtract 1 and make it 0.47861. If it is negative, then remove the whole number component, then add to 1. For example, if it is -2.52139, then remove the whole number component (the 2, resulting in -0.52139), then add to 1 (1 + -0.52139 = 0.47861).
- Once you have the UV map exported as a .png file, you can import it as a new layer on top of the texture in Paint.NET. Do not forget to vertically flip the texture (and then flip it back when ready to insert into the game)!
If you want to edit the UV map, it is very straightforward to do so.
All the same things you learned in the basic tutorial for manipulating the 3D Viewport and selecting, you do here in the 2D Viewport. Use the circle select, grab the vertices you want to transform, and transform them (transport, rotate, scale) using the tools. Do not forget to go back into Object mode in the Layout workspace when you are done, and export the mesh.