Urho3D: Materials
- Tutorial
The graphics subsystem is probably the most complex and confusing part of the engine. And at the same time, this is precisely the part in which you need to be very well oriented. You can easily process input, play sounds and not even think about how it is arranged inside. But a rare game will do without its own beautiful effects, and here you can not do without a certain set of knowledge. In one article it is impossible to cover the entire amount of information on this topic, but I hope that I can provide you with a framework, based on which you will be much easier to master all the nuances and subtleties of the Urho3D render.
The result of the experiments is here: github.com/1vanK/Urho3DHabrahabr05 . In the Demo folder there is actually a demo (by moving the mouse you can rotate the model, the wheel turns the light source). All added resources are separated from the engine files and are located in the Demo / MyData folder.
At the moment, integration is underway in the engine PBR (Physically Based Rendering, physically correct rendering). It will not add any significant changes to the engine itself, basically it's just an additional set of shaders. In addition, it has not yet settled down and in the finale some things can change. Therefore, in order to narrow down the range of the information in question, I will rely on an older version of the engine (at the time of writing this article, it is outdated by ten days :)
For experiments, I decided to take the Abaddon model from the Dota 2 game. There are both texture maps that Urho3D supports out of the box and those specific to the Source 2 engine. We also will not disregard them. Which map is responsible for what can be found in this document . But before dealing with materials, we need to convert the model into a format understandable for Urho3D. You can do this in many ways.
AssetImporter utility is supplied with the engine, which allows you to import models from a variety of formats. It can be found in the bin / tool folder. The command "AssetImporter.exe model abaddon_econ.fbx abaddon.mdl -t" combines all the fragments of the model into a single whole (in the fbx-file the model is divided into parts). Be sure to enable the "-t" option if you intend to use normal maps (this option adds tangent information to the model vertices). The AssetImporter.exe node abaddon_econ.fbx abaddon.xml -t command will save the model as a prefab, in which parts of the model are organized as a hierarchy of nodes. Since no textures are assigned to the model in this fbx-file, materials will have to be created manually.
You can import the model directly from the editor (File> Import model ...). In this case, the same AssetImporter utility is used. The "-t" option is enabled by default. Other parameters can be specified in the View> Editor settings window.
For lovers of Blender there is a separate exporter. And he is magnificent. Download the addon , install it and open the already prepared file Abaddon.blend (I left only the horse from the model and created material for it). Indicate the path where you want to save the result, check the settings (do not forget about the tangents) and click the Export button.
The first thing that catches your eye when opening the model in the Urho3D editor is the white legs of the horse. The fact is that Blender interprets the glow map as intensity (black and white image), and the Urho3D engine as color. You can fix the glow map itself (for example, in GIMP, multiply it by a diffuse map), but we are not looking for easy ways and we will change the Urho3D shader to adapt it to the texture set of the Source 2 engine. But it will be later, but for now, to see the model in a more pleasant way, you can change the value of the MatEmissiveColor glow factor to “0 0 0” in the material parameters (file abaddon_mount.xml), or delete this line altogether (just remember to return everything later, we still need this parameter). By the way, when you modify and save a material file, the editor automatically picks up the new version and updates its viewport. Moreover, this works even for shaders. You can write a shader and watch the result of your work in real time.
The rendering process in Urho3D is described using a set of text files: rendezvous (CoreData / RenderPaths / *. Xml), technicians (CoreData / Techniques / *. Xml) and shaders (CoreData / Shaders / GLSL / *. Glsl or CoreData / Shaders / HLSL * .hlsl). Once you understand the relationship between them, the rest is easy to figure out.
So, each scene object in most cases is rendered several times using different shaders (the so-called render passes). Which set of passes (shaders) is required to draw each object is described in the technique. The order in which these passes (shaders) will be executed is described in the render pass. Once again I draw your attention: the order of the lines in the technique is not important.
There are also materials (Data / Materials / *. Xml). The material determines which technique will be used for the object, and also fills this technique with specific content, that is, it determines the data that will be transferred to the shaders: textures, colors, etc.
That is, the combination of renderpairs + technique + shaders can be represented as an algorithm , and the material is a dataset for this algorithm.
To understand the procedure for calling shaders, take a look at this comparison plate:
If there is any passage in the technique, but there is no such passage in the render, then it will not be performed. And vice versa, if there is some passage in the render pass, but it is absent in the technique, then it will not be executed either. However, there are passages prescribed in the engine itself that you will not find in the render pass, but they will still be executed if they are in the technique.
Urho3D supports both OpenGL and DirectX, therefore it contains two similar in functionality sets of shaders, which are located in the folders CoreData / Shaders / GLSL and CoreData / Shaders / HLSL. Which API and, accordingly, which set of shaders will be used, is determined when compiling the engine. I prefer OpenGL as more universal, so in this article I will focus on it.
There are a lot of files in the CoreData / Shaders / GLSL folder, but you should pay special attention to one of them - LitSolid.glsl. This shader is used for the vast majority of materials. Urho3D uses the uber shader method, that is, unnecessary pieces are thrown out of the huge universal shader LitSolid.glsl through defines and a small, fast specialized shader is obtained. Sets of defines are specified in the techniques, and some defines are added by the engine itself.
Urho3D currently supports 3 rendering methods: Forward (traditional), Light Pre-Pass and Deferred. The differences between them are a topic for a separate discussion, therefore, we simply restrict ourselves to the Forward method, which is used by default and is described in the CoreData / RenderPaths / Forward.xml renderer.
Since we will modify the technique, copy it to a separate Techniques / MyTechnique.xml file so as not to affect other materials that may also use this technique. In the material also change the name of the technique used.
The technique uses the standard LitSolid.glsl shader for materials. Since we will change this shader, copy it to Shaders / GLSL / MyLitSolid.glsl. Also change the name of the shader used in the technique. At the same time, this technique can be simplified by throwing away the excess. Since the “prepass”, “material”, “deferred” and “depth” passages are absent in the Forward render pass (they are defined in other render pass), they will never be used. However, leave the “shadow” passage. Although it is absent in the render, it is a built-in passage and is registered in the engine itself. Without it, the horse will not cast shadows.
Now let's work with our shader Shaders / GLSL / MyLitSolid.glsl and change the way we use the glow map to the one we need. The EMISSIVEMAP define (which is the signal that the material should determine the glow map) in the shader is present in several places, and since we can’t parse the shader line by line within the article, we will go another way. Defines DEFERRED, PREPASS and MATERIAL will never be defined in the shader, since the passages in which they are defined, we removed from the technique as unused. Therefore, we boldly delete the code framed by them. Shader size decreased by a third. Define EMISSIVEMAP remained in only one place.
Let's multiply the glow map by a diffuse color. Replace string
per line
Now the horse's legs glow in the right blue color.
For more beauty, let's add a bloom effect (a halo around the bright parts of the image). It is implemented by two lines in the script :
Note that the post-processing effect is added to the end of the render. You can add it directly to the file.
Highlighting the edges of the model is a fake technique that simulates the light incident on the model from behind. The Source 2 engine uses a separate mask to indicate the parts of the model on which you want to apply this effect.
In order to transfer the texture to the shader, select an unused texture unit. Our material does not use an environment map, so we will use its slot. Also in the shader, we need two parameters: RimColor and RimPower, so we’ll add them right away. The resulting material looks like this . This can sometimes be confusing, but the name of the texture unit does not have to correspond to its use. You are free to transfer anything through texture units and apply it in your shaders as you like.
The name of the uniform in the shader will differ from the name of the parameter in the material by the prefix “c” (const): the RimColor parameter will become the cRimColor uniform, and the RimPower parameter will become the cRimPower uniform.
And the effect implementation itself looks like this:
Pay attention to the RIMMAP define. You might not want to use a backlight card, but simply apply an effect to the entire model. In this case, you simply do not define a RIMMAP definition in a technique. By the way, do not forget to define the RIMMAP define in the technique :) The final technique looks like this , and the shader looks like this .
urho3d.github.io/documentation/1.5/_rendering.html
urho3d.github.io/documentation/1.5/_a_p_i_differences.html
urho3d.github.io/documentation/1.5/_shaders.html
urho3d.github.io/documentation_paths.5 .html
urho3d.github.io/documentation/1.5/_materials.html
urho3d.github.io/documentation/1.5/_rendering_modes.html
For the impatient
The result of the experiments is here: github.com/1vanK/Urho3DHabrahabr05 . In the Demo folder there is actually a demo (by moving the mouse you can rotate the model, the wheel turns the light source). All added resources are separated from the engine files and are located in the Demo / MyData folder.
The considered version of the engine
At the moment, integration is underway in the engine PBR (Physically Based Rendering, physically correct rendering). It will not add any significant changes to the engine itself, basically it's just an additional set of shaders. In addition, it has not yet settled down and in the finale some things can change. Therefore, in order to narrow down the range of the information in question, I will rely on an older version of the engine (at the time of writing this article, it is outdated by ten days :)
Version April 9, 2016.
:: Указываем путь к git.exe
set "PATH=c:\Program Files (x86)\Git\bin\"
:: Скачиваем репозиторий
git clone https://github.com/Urho3D/Urho3D.git
:: Переходим в папку со скачанными исходниками
cd Urho3D
:: Возвращаем состояние репозитория к определённой версии (9 апреля 2016)
git reset --hard 4c8bd3efddf442cd31b49ce2c9a2e249a1f1d082
:: Ждём нажатия ENTER для закрытия консоли
pause
Importing a Model
For experiments, I decided to take the Abaddon model from the Dota 2 game. There are both texture maps that Urho3D supports out of the box and those specific to the Source 2 engine. We also will not disregard them. Which map is responsible for what can be found in this document . But before dealing with materials, we need to convert the model into a format understandable for Urho3D. You can do this in many ways.
AssetImporter utility is supplied with the engine, which allows you to import models from a variety of formats. It can be found in the bin / tool folder. The command "AssetImporter.exe model abaddon_econ.fbx abaddon.mdl -t" combines all the fragments of the model into a single whole (in the fbx-file the model is divided into parts). Be sure to enable the "-t" option if you intend to use normal maps (this option adds tangent information to the model vertices). The AssetImporter.exe node abaddon_econ.fbx abaddon.xml -t command will save the model as a prefab, in which parts of the model are organized as a hierarchy of nodes. Since no textures are assigned to the model in this fbx-file, materials will have to be created manually.
You can import the model directly from the editor (File> Import model ...). In this case, the same AssetImporter utility is used. The "-t" option is enabled by default. Other parameters can be specified in the View> Editor settings window.
For lovers of Blender there is a separate exporter. And he is magnificent. Download the addon , install it and open the already prepared file Abaddon.blend (I left only the horse from the model and created material for it). Indicate the path where you want to save the result, check the settings (do not forget about the tangents) and click the Export button.
The first thing that catches your eye when opening the model in the Urho3D editor is the white legs of the horse. The fact is that Blender interprets the glow map as intensity (black and white image), and the Urho3D engine as color. You can fix the glow map itself (for example, in GIMP, multiply it by a diffuse map), but we are not looking for easy ways and we will change the Urho3D shader to adapt it to the texture set of the Source 2 engine. But it will be later, but for now, to see the model in a more pleasant way, you can change the value of the MatEmissiveColor glow factor to “0 0 0” in the material parameters (file abaddon_mount.xml), or delete this line altogether (just remember to return everything later, we still need this parameter). By the way, when you modify and save a material file, the editor automatically picks up the new version and updates its viewport. Moreover, this works even for shaders. You can write a shader and watch the result of your work in real time.
Rendering process
The rendering process in Urho3D is described using a set of text files: rendezvous (CoreData / RenderPaths / *. Xml), technicians (CoreData / Techniques / *. Xml) and shaders (CoreData / Shaders / GLSL / *. Glsl or CoreData / Shaders / HLSL * .hlsl). Once you understand the relationship between them, the rest is easy to figure out.
So, each scene object in most cases is rendered several times using different shaders (the so-called render passes). Which set of passes (shaders) is required to draw each object is described in the technique. The order in which these passes (shaders) will be executed is described in the render pass. Once again I draw your attention: the order of the lines in the technique is not important.
There are also materials (Data / Materials / *. Xml). The material determines which technique will be used for the object, and also fills this technique with specific content, that is, it determines the data that will be transferred to the shaders: textures, colors, etc.
That is, the combination of renderpairs + technique + shaders can be represented as an algorithm , and the material is a dataset for this algorithm.
To understand the procedure for calling shaders, take a look at this comparison plate:
// НЕВЕРНО // ВЕРНО
for (i = 0; i < числоОбъектов; i++) for (int i = 0; i < числоПроходов; i++)
{ {
for (j = 0; j < числоПроходов; j++) for (j = 0; j < числоОбъектов; j++)
Рендрить(объект[i], проход[j]); {
} if (объект[j] имеет проход[i])
Рендерить(объект[j], проход[i]);
}
}
If there is any passage in the technique, but there is no such passage in the render, then it will not be performed. And vice versa, if there is some passage in the render pass, but it is absent in the technique, then it will not be executed either. However, there are passages prescribed in the engine itself that you will not find in the render pass, but they will still be executed if they are in the technique.
Shaders
Urho3D supports both OpenGL and DirectX, therefore it contains two similar in functionality sets of shaders, which are located in the folders CoreData / Shaders / GLSL and CoreData / Shaders / HLSL. Which API and, accordingly, which set of shaders will be used, is determined when compiling the engine. I prefer OpenGL as more universal, so in this article I will focus on it.
There are a lot of files in the CoreData / Shaders / GLSL folder, but you should pay special attention to one of them - LitSolid.glsl. This shader is used for the vast majority of materials. Urho3D uses the uber shader method, that is, unnecessary pieces are thrown out of the huge universal shader LitSolid.glsl through defines and a small, fast specialized shader is obtained. Sets of defines are specified in the techniques, and some defines are added by the engine itself.
Back to our horse
Urho3D currently supports 3 rendering methods: Forward (traditional), Light Pre-Pass and Deferred. The differences between them are a topic for a separate discussion, therefore, we simply restrict ourselves to the Forward method, which is used by default and is described in the CoreData / RenderPaths / Forward.xml renderer.
After exporting from Blender, we got Material / abaddon_mount.xml.
This material uses the Techniques / DiffNormalSpecEmissive.xml technique.
Since we will modify the technique, copy it to a separate Techniques / MyTechnique.xml file so as not to affect other materials that may also use this technique. In the material also change the name of the technique used.
The technique uses the standard LitSolid.glsl shader for materials. Since we will change this shader, copy it to Shaders / GLSL / MyLitSolid.glsl. Also change the name of the shader used in the technique. At the same time, this technique can be simplified by throwing away the excess. Since the “prepass”, “material”, “deferred” and “depth” passages are absent in the Forward render pass (they are defined in other render pass), they will never be used. However, leave the “shadow” passage. Although it is absent in the render, it is a built-in passage and is registered in the engine itself. Without it, the horse will not cast shadows.
As a result, we have Techniques / MyTechnique.xml
Now let's work with our shader Shaders / GLSL / MyLitSolid.glsl and change the way we use the glow map to the one we need. The EMISSIVEMAP define (which is the signal that the material should determine the glow map) in the shader is present in several places, and since we can’t parse the shader line by line within the article, we will go another way. Defines DEFERRED, PREPASS and MATERIAL will never be defined in the shader, since the passages in which they are defined, we removed from the technique as unused. Therefore, we boldly delete the code framed by them. Shader size decreased by a third. Define EMISSIVEMAP remained in only one place.
Let's multiply the glow map by a diffuse color. Replace string
finalColor += cMatEmissiveColor * texture2D(sEmissiveMap, vTexCoord.xy).rgb;
per line
finalColor += cMatEmissiveColor * texture2D(sEmissiveMap, vTexCoord.xy).rgb * diffColor.rgb;
Now the horse's legs glow in the right blue color.
For more beauty, let's add a bloom effect (a halo around the bright parts of the image). It is implemented by two lines in the script :
// Включаем HDR-рендеринг.
renderer.hdrRendering = true;
// Добавляем эффект постобработки.
viewport.renderPath.Append(cache.GetResource("XMLFile", "PostProcess/BloomHDR.xml"));
Note that the post-processing effect is added to the end of the render. You can add it directly to the file.
Rim light
Highlighting the edges of the model is a fake technique that simulates the light incident on the model from behind. The Source 2 engine uses a separate mask to indicate the parts of the model on which you want to apply this effect.
In order to transfer the texture to the shader, select an unused texture unit. Our material does not use an environment map, so we will use its slot. Also in the shader, we need two parameters: RimColor and RimPower, so we’ll add them right away. The resulting material looks like this . This can sometimes be confusing, but the name of the texture unit does not have to correspond to its use. You are free to transfer anything through texture units and apply it in your shaders as you like.
The name of the uniform in the shader will differ from the name of the parameter in the material by the prefix “c” (const): the RimColor parameter will become the cRimColor uniform, and the RimPower parameter will become the cRimPower uniform.
uniform float cRimPower;
uniform vec3 cRimColor;
And the effect implementation itself looks like this:
// RIM LIGHT
// Направление = позиция камеры - позиция фрагмента.
vec3 viewDir = normalize(cCameraPosPS - vWorldPos.xyz);
// Скалярное произведение параллельных единичных векторов = 1.
// Скалярное произведение перпендикулярных векторов = 0.
// То есть, если представить шар, то его середина будет белой
// (так как нормаль параллельна линии взгляда), а к краям темнеть.
// Нам же наоборот нужны светлые края, поэтому скалярное произведение вычитается из единицы.
float rimFactor = 1.0 - clamp(dot(normal, viewDir), 0.0, 1.0);
// Если cRimPower > 1, то подсветка сжимается к краям.
// Если cRimPower < 1, то подсветка наоборот становится более равномерной.
// При cRimPower = 0 вся модель будет равномерно окрашена, так как любое число в степени ноль = 1.
rimFactor = pow(rimFactor, cRimPower);
// Проверяем, нужно ли использовать карту подсветки.
#ifdef RIMMAP
// Учитываем карту и цвет подсветки.
finalColor += texture2D(sEnvMap, vTexCoord.xy).rgb * cRimColor * rimFactor;
#else
finalColor += cRimColor * rimFactor;
#endif
Pay attention to the RIMMAP define. You might not want to use a backlight card, but simply apply an effect to the entire model. In this case, you simply do not define a RIMMAP definition in a technique. By the way, do not forget to define the RIMMAP define in the technique :) The final technique looks like this , and the shader looks like this .
Literature
urho3d.github.io/documentation/1.5/_rendering.html
urho3d.github.io/documentation/1.5/_a_p_i_differences.html
urho3d.github.io/documentation/1.5/_shaders.html
urho3d.github.io/documentation_paths.5 .html
urho3d.github.io/documentation/1.5/_materials.html
urho3d.github.io/documentation/1.5/_rendering_modes.html