Introduction:
GameBryo has a complex material system that can generate different shader codes based on the rendering object state and attributes, improving the adaptability of the rendering process, you can define a set of materials to adapt to multiple rendering objects. GameByro initializes and uses the shader plug-ins to facilitate integration with art tools and implement platform independence. To achieve these goals, GameByro uses a complex mechanism. This article mainly explains how GameByro generates, compiles, and uses the shader code.
Shader
The shader interface of GameBryo is encapsulated in NiShader. The vertex data stream declaration, constant table access, and rendering State settings all use this class (somewhat similar to D3Deffect ). The program running NiShader is managed by NiShaderFactory. NiShaderFactory creates shader from the file through NiShaderLibrary and manages it with a global map. NiShaderLibrary parses the shader text to create the NiShader object and calls the 3D graphic interface to compile the shader code. This class is encapsulated as a dll and can be used as a plug-in. The creation of the NiShader class can be performed by parsing the file, or by using the C ++ class. You only need to inherit from NiShader. GameByro provides a nid3dxjavastshaderlib library for the PC platform. This Library provides the ability to parse the shader file and initialize the shader object. Users only need to write the meaning and comments of the shader code in the format defined by GameByro. nid3dxjavastshaderlibrary creates the NiD3Dshader object based on the text, and the object can be accessed through the Techinqe name in the application. Through this mechanism, we place the shader text files in the directory specified by the relevant art tools, and these shader can be used in the tools, in addition, the UI can be generated for relevant parameters and variables through the semantics and comments of the shader to facilitate art debugging.
The entire process on the WIN platform is as follows:
1. The application will first initialize the entire shader system at startup, and then import the Shader Parsing Library and the loading Library (in the form of dll ).
2. next, the application delegates the NiD3DShader initialization work to NiShaderLibrary for processing. NiShaderLibrary first loads all shader text files through nid3dxjavastloader, and uses nid3dxjavastparser to parse the text to generate the nid3dxjavastfile object, at the same time, NiD3DXEffectLoader is responsible for compiling shader code into a binary form GPU program.
3. NiD3DXEffectTechnique is responsible for generating the NiD3Dshader object through the information on NiD3DXEffectFile.
4. After all the shader objects are created, the initialization of NiShaderLibrary is complete, and NiShaderFactory is responsible for unified management.
Material:
NiMaterial generates and defines Shader for the rendering object, and NiMaterialInstance assigns and Cach Shader for the rendering object. NiFragmentMaterial provides a Shader Tree framework, which can be used to build a shader tree in its inheritance class. This mechanism allows NiFragmentMaterial to generate different shader codes based on different rendering states of objects. Cach is in the memory and saved to disk files. GameByro descriptor is widely used. The Shader parsing process mentioned above also uses descriptors to transmit information. NiMaterialDescriptor and NiGPUProgramDescriptor are used as descriptors in the material system. The information stored in these two classes is compatible, it is used to describe the features of a GPU program corresponding to a certain rendering state of a rendering object. NiFragmentMaterial generates NiMaterialDescriptor Based on the rendering target's status and attributes, and finds the matched shader through NiMaterialDescriptor. If the shader cannot be found, the shader tree is used to generate the corresponding shader program and save it to the disk file. At the current application startup, you can use this file to directly create the NiShader object. It can be said that the shader code generated by NiFragmentMaterial is tailored to specific rendering objects under specific circumstances.
The detailed process is as follows:
1. before rendering an object, NiMaterialInstance first checks whether the shader program of the object needs to be updated. If the shader program of the object does not need to be updated, it returns the NiShader of the current Cach directly, niMaterialInstance first generates a NiMaterialDescriptor Based on the rendering state of the object, and then compares the NiMaterialDescriptor with the NiShader of the current Cach. If the matching still returns the NiShader of the current Cach, transfer the obtained shader to NiMaterial.
2. niMaterial first queries the NiShader that matches the NiMaterialDescriptor through this NiShaderFactory. If it cannot be found, NiMaterialDescriptor generates NiShader and a piece of Shader code, and save it to the shader file named by the signature in the shader descriptor.
3. After obtaining the corresponding NiShader object, NiMaterialInstance will call the SetupGeometry interface of NiShader and declare the vertex in this interface.
The following code selects shader for Geometry by NiMaterialInstance:
NiShader * NiMaterialInstance: GetCurrentShader (NiRenderObject * pkGeometry,
Const NiPropertyState * pkState,
Const nidynamic1_tstate * pkEffects)
{
If (m_spMaterial)
{
Bool bGetNewShader = m_eNeedsUpdate = DIRTY;
If (m_eNeedsUpdate = UNKNOWN)
BGetNewShader = pkGeometry-> GetMaterialNeedsUpdateDefault ();
// Check if shader is still current
If (bGetNewShader & m_spCachedShader)
{
BGetNewShader =! M_spMaterial-> IsShaderCurrent (m_spCachedShader,
PkGeometry, pkState, pkEffects, m_uiMaterialExtraData );
}
// Get a new shader
If (bGetNewShader)
{
NiShader * pkNewShader = m_spMaterial-> GetCurrentShader (
PkGeometry, pkState, pkEffects, m_uiMaterialExtraData );
If (pkNewShader)
{
NIASSERT (m_spCachedShader! = PkNewShader );
ClearCachedShader ();
M_spCachedShader = pkNewShader;
If (! PkNewShader-> SetupGeometry (pkGeometry, this ))
ClearCachedShader ();
}
Else
{
ClearCachedShader ();
}
}
M_eNeedsUpdate = UNKNOWN;
}
Return m_spCachedShader;
}
If you want to implement your own shader tree through NiFragmentMaterial, You need to implement the logic of self-assembled code in the interface provided by NiFragmentMaterial. The code block is encapsulated by NiMaterialLibraryNode, niMaterialLibraryNode can be defined by writing C ++ Code directly, or by writing an XML script, which is then converted into C ++ Code by a dedicated parsing tool.
Shows the shader code file generated by NiStandardMaterial:
The file name is the mask of NiMaterialDescriptor, used to identify the behavior of the shader code.
The behavior of the Shader code is described as follows:
Shader description:
APPLYMODE = 1
WORLDPOSITION = 0
WORLDNORMAL = 0
WORLDNBT = 0
WORLDVIEW = 0
NORMALMAPTYPE = 0
PARALLAXMAPCOUNT = 0
BASEMAPCOUNT = 1
NORMALMAPCOUNT = 0
DARKMAPCOUNT = 0
DETAILMAPCOUNT = 0
BUMPMAPCOUNT = 0
GLOSSMAPCOUNT = 0
GLOWMAPCOUNT = 0
CUSTOMMAP00COUNT = 0
CUSTOMMAP01COUNT = 0
CUSTOMMAP02COUNT = 0
CUSTOMMAP03COUNT = 0
CUSTOMMAP04COUNT = 0
DECALMAPCOUNT = 0
FOGENABLED = 0
ENVMAPTYPE = 0
PROJLIGHTMAPCOUNT = 0
PROJLIGHTMAPTYPES = 0
PROJLIGHTMAPCLIPPED = 0
PROJSHADOWMAPCOUNT = 0
PROJSHADOWMAPTYPES = 0
PROJSHADOWMAPCLIPPED = 0
PERVERTEXLIGHTING = 1
UVSETFORMAP00 = 0
UVSETFORMAP01 = 0
UVSETFORMAP02 = 0
UVSETFORMAP03 = 0
UVSETFORMAP04 = 0
UVSETFORMAP05 = 0
UVSETFORMAP06 = 0
UVSETFORMAP07 = 0
UVSETFORMAP08 = 0
UVSETFORMAP09 = 0
UVSETFORMAP10 = 0
UVSETFORMAP11 = 0
POINTLIGHTCOUNT = 0
SPOTLIGHTCOUNT = 0
DIRLIGHTCOUNT = 0
SHADOWMAPFORLIGHT = 0
SPECULAR = 1
AMBDIFFEMISSIVE = 0
LIGHTINGMODE = 1
APPLYAMBIENT = 0
BASEMAPALPHAONLY = 0
APPLYEMISSIVE = 0
SHADOWTECHNIQUE = 0
ALPHATEST = 0
NiStanderMaterial is used to generate the shader code based on the data of these masks. You can use the interfaces GenerateVertexShadeTree, GeneratePixelShadeTree, and CreateShader to define your own shader generation rules.
Add your own rendering effect:
We can see from the previous sections that we want to define our own materials by writing the shader code. During application initialization, the shader code will be initialized to the NiShader object, and the NiSingleShaderMaterial object will be initialized through the NiShader object and allocated to the rendering object. In the default rendering process of GameByro, these steps are automatically performed. In the art, you only need to specify the Shader program for the material of the ry in the 3DMAX plug-in and export it to the nif file, the application can be correctly loaded and rendered. The second is to define its own NiMaterialFragment class, define how to generate a shader in the class, and assign the instance of this class to the ry when the application is running, this class will automatically generate a shader for the ry. The main difference between the two methods is that the rendering data settings of the materials defined in the first method must strictly comply with the data required in the shader code, otherwise, an error is reported. (For example, the vertex data stream must strictly comply with the shader program definition and provide the correct format texture for each sampler in the shader). The material defined by the second method is used, there is a high degree of fault tolerance and adaptability, but this kind of fault tolerance and adaptability needs to be completed by writing code by yourself. The NiStanderMaterial provided by GameByro provides this complete mechanism. If you set the texture in each texture slot, a corresponding texture processing flow will be generated. If you do not set the texture, there will be no processing flow for this texture.
To verify this process, I tried to add my own shader special effect, SubSurfaceScattering, or 3 s. Its principle is to simulate the effect of light scattering in a translucent object. Since this effect does not require preprocessing, and all textures are from disk files, it is easier to integrate them into the GameByro workflow.
The author puts the FX files that have been debugged in fx COMPOSER into the SDK \ Win32 \ Shaders \ Data directory in the SDK, and selects GameByroShader on the material panel of 3DMAX, then, you can see the Techinqe defined in the file in the display shader combo box. Click the apply button to display the custom parameter adjustment interface.
By adjusting the parameters, the rendering effects of skin and Jade are as follows:
Skin
Jade
Summary
GameByro's development process is very convenient and intuitive, but art can only allocate static data sources for the shader program, such as lighting charts and CubeMap; some texture data generated in real time in the program cannot be integrated into the art tools, such as shadow, refraction, reflection, and so on. These need to be implemented by the program. Debugging is not convenient. In most cases, you only need to use the NiStanderMaterial provided by GameByro to complete most of the material requirements. For special effects, you can write your own shader or use the shader library provided by the engine, only when we need to perform a lot of different processing based on complicated situations, we need to reload the NiFragmentMaterial to build our own shader tree. However, the shader tree program is generally complicated and difficult to write. Although the engine allows you to write material nodes through XML files, it is still inconvenient to use. GameByro does not provide related development tools for post-processing. The special effects of post-processing cannot be seen or obtained. Therefore, it must be improved.
GameByro provides some flexibility to generate dedicated shader code for ry in a specific environment, but it also pays the following price:
L analyzes the attributes and current status of the ry and generates the shader code for it with performance loss.
L after the Shader code is generated, it will be saved to the disk file. If Asynchronous is not used in this process, blocking may occur.
L The generated NiShader object consumes memory. GameByro initializes all shader files into NiShader objects by default. Therefore, when the game has been running for a long time, a large number of shader files will be generated, at this time, the memory consumption may be considerable, and the loading time will also increase. However, you can control the loading process and optimize the performance here.
Author: ye Qianyi
Introduction:
GameBryo has a complex material system that can generate different shader codes based on the rendering object state and attributes, improving the adaptability of the rendering process, you can define a set of materials to adapt to multiple rendering objects. GameByro initializes and uses the shader plug-ins to facilitate integration with art tools and implement platform independence. To achieve these goals, GameByro uses a complex mechanism. This article mainly explains how GameByro generates, compiles, and uses the shader code.
Shader
The shader interface of GameBryo is encapsulated in NiShader. The vertex data stream declaration, constant table access, and rendering State settings all use this class (somewhat similar to D3Deffect ). The program running NiShader is managed by NiShaderFactory. NiShaderFactory creates shader from the file through NiShaderLibrary and manages it with a global map. NiShaderLibrary parses the shader text to create the NiShader object and calls the 3D graphic interface to compile the shader code. This class is encapsulated as a dll and can be used as a plug-in. The creation of the NiShader class can be performed by parsing the file, or by using the C ++ class. You only need to inherit from NiShader. GameByro provides a nid3dxjavastshaderlib library for the PC platform. This Library provides the ability to parse the shader file and initialize the shader object. Users only need to write the meaning and comments of the shader code in the format defined by GameByro. nid3dxjavastshaderlibrary creates the NiD3Dshader object based on the text, and the object can be accessed through the Techinqe name in the application. Through this mechanism, we place the shader text files in the directory specified by the relevant art tools, and these shader can be used in the tools, in addition, the UI can be generated for relevant parameters and variables through the semantics and comments of the shader to facilitate art debugging.
The entire process on the WIN platform is as follows:
1. The application will first initialize the entire shader system at startup, and then import the Shader Parsing Library and the loading Library (in the form of dll ).
2. next, the application delegates the NiD3DShader initialization work to NiShaderLibrary for processing. NiShaderLibrary first loads all shader text files through nid3dxjavastloader, and uses nid3dxjavastparser to parse the text to generate the nid3dxjavastfile object, at the same time, NiD3DXEffectLoader is responsible for compiling shader code into a binary form GPU program.
3. NiD3DXEffectTechnique is responsible for generating the NiD3Dshader object through the information on NiD3DXEffectFile.
4. After all the shader objects are created, the initialization of NiShaderLibrary is complete, and NiShaderFactory is responsible for unified management.
Material:
NiMaterial generates and defines Shader for the rendering object, and NiMaterialInstance assigns and Cach Shader for the rendering object. NiFragmentMaterial provides a Shader Tree framework, which can be used to build a shader tree in its inheritance class. This mechanism allows NiFragmentMaterial to generate different shader codes based on different rendering states of objects. Cach is in the memory and saved to disk files. GameByro descriptor is widely used. The Shader parsing process mentioned above also uses descriptors to transmit information. NiMaterialDescriptor and NiGPUProgramDescriptor are used as descriptors in the material system. The information stored in these two classes is compatible, it is used to describe the features of a GPU program corresponding to a certain rendering state of a rendering object. NiFragmentMaterial generates NiMaterialDescriptor Based on the rendering target's status and attributes, and finds the matched shader through NiMaterialDescriptor. If the shader cannot be found, the shader tree is used to generate the corresponding shader program and save it to the disk file. At the current application startup, you can use this file to directly create the NiShader object. It can be said that the shader code generated by NiFragmentMaterial is tailored to specific rendering objects under specific circumstances.
The detailed process is as follows:
1. before rendering an object, NiMaterialInstance first checks whether the shader program of the object needs to be updated. If the shader program of the object does not need to be updated, it returns the NiShader of the current Cach directly, niMaterialInstance first generates a NiMaterialDescriptor Based on the rendering state of the object, and then compares the NiMaterialDescriptor with the NiShader of the current Cach. If the matching still returns the NiShader of the current Cach, transfer the obtained shader to NiMaterial.
2. niMaterial first queries the NiShader that matches the NiMaterialDescriptor through this NiShaderFactory. If it cannot be found, NiMaterialDescriptor generates NiShader and a piece of Shader code, and save it to the shader file named by the signature in the shader descriptor.
3. After obtaining the corresponding NiShader object, NiMaterialInstance will call the SetupGeometry interface of NiShader and declare the vertex in this interface.
The following code selects shader for Geometry by NiMaterialInstance:
NiShader * NiMaterialInstance: GetCurrentShader (NiRenderObject * pkGeometry,
Const NiPropertyState * pkState,
Const nidynamic1_tstate * pkEffects)
{
If (m_spMaterial)
{
Bool bGetNewShader = m_eNeedsUpdate = DIRTY;
If (m_eNeedsUpdate = UNKNOWN)
BGetNewShader = pkGeometry-> GetMaterialNeedsUpdateDefault ();
// Check if shader is still current
If (bGetNewShader & m_spCachedShader)
{
BGetNewShader =! M_spMaterial-> IsShaderCurrent (m_spCachedShader,
PkGeometry, pkState, pkEffects, m_uiMaterialExtraData );
}
// Get a new shader
If (bGetNewShader)
{
NiShader * pkNewShader = m_spMaterial-> GetCurrentShader (
PkGeometry, pkState, pkEffects, m_uiMaterialExtraData );
If (pkNewShader)
{
NIASSERT (m_spCachedShader! = PkNewShader );
ClearCachedShader ();
M_spCachedShader = pkNewShader;
If (! PkNewShader-> SetupGeometry (pkGeometry, this ))
ClearCachedShader ();
}
Else
{
ClearCachedShader ();
}
}
M_eNeedsUpdate = UNKNOWN;
}
Return m_spCachedShader;
}
If you want to implement your own shader tree through NiFragmentMaterial, You need to implement the logic of self-assembled code in the interface provided by NiFragmentMaterial. The code block is encapsulated by NiMaterialLibraryNode, niMaterialLibraryNode can be defined by writing C ++ Code directly, or by writing an XML script, which is then converted into C ++ Code by a dedicated parsing tool.
Shows the shader code file generated by NiStandardMaterial:
The file name is the mask of NiMaterialDescriptor, used to identify the behavior of the shader code.
The behavior of the Shader code is described as follows:
Shader description:
APPLYMODE = 1
WORLDPOSITION = 0
WORLDNORMAL = 0
WORLDNBT = 0
WORLDVIEW = 0
NORMALMAPTYPE = 0
PARALLAXMAPCOUNT = 0
BASEMAPCOUNT = 1
NORMALMAPCOUNT = 0
DARKMAPCOUNT = 0
DETAILMAPCOUNT = 0
BUMPMAPCOUNT = 0
GLOSSMAPCOUNT = 0
GLOWMAPCOUNT = 0
CUSTOMMAP00COUNT = 0
CUSTOMMAP01COUNT = 0
CUSTOMMAP02COUNT = 0
CUSTOMMAP03COUNT = 0
CUSTOMMAP04COUNT = 0
DECALMAPCOUNT = 0
FOGENABLED = 0
ENVMAPTYPE = 0
PROJLIGHTMAPCOUNT = 0
PROJLIGHTMAPTYPES = 0
PROJLIGHTMAPCLIPPED = 0
PROJSHADOWMAPCOUNT = 0
PROJSHADOWMAPTYPES = 0
PROJSHADOWMAPCLIPPED = 0
PERVERTEXLIGHTING = 1
UVSETFORMAP00 = 0
UVSETFORMAP01 = 0
UVSETFORMAP02 = 0
UVSETFORMAP03 = 0
UVSETFORMAP04 = 0
UVSETFORMAP05 = 0
UVSETFORMAP06 = 0
UVSETFORMAP07 = 0
UVSETFORMAP08 = 0
UVSETFORMAP09 = 0
UVSETFORMAP10 = 0
UVSETFORMAP11 = 0
POINTLIGHTCOUNT = 0
SPOTLIGHTCOUNT = 0
DIRLIGHTCOUNT = 0
SHADOWMAPFORLIGHT = 0
SPECULAR = 1
AMBDIFFEMISSIVE = 0
LIGHTINGMODE = 1
APPLYAMBIENT = 0
BASEMAPALPHAONLY = 0
APPLYEMISSIVE = 0
SHADOWTECHNIQUE = 0
ALPHATEST = 0
NiStanderMaterial is used to generate the shader code based on the data of these masks. You can use the interfaces GenerateVertexShadeTree, GeneratePixelShadeTree, and CreateShader to define your own shader generation rules.
Add your own rendering effect:
We can see from the previous sections that we want to define our own materials by writing the shader code. During application initialization, the shader code will be initialized to the NiShader object, and the NiSingleShaderMaterial object will be initialized through the NiShader object and allocated to the rendering object. In the default rendering process of GameByro, these steps are automatically performed. In the art, you only need to specify the Shader program for the material of the ry in the 3DMAX plug-in and export it to the nif file, the application can be correctly loaded and rendered. The second is to define its own NiMaterialFragment class, define how to generate a shader in the class, and assign the instance of this class to the ry when the application is running, this class will automatically generate a shader for the ry. The main difference between the two methods is that the rendering data settings of the materials defined in the first method must strictly comply with the data required in the shader code, otherwise, an error is reported. (For example, the vertex data stream must strictly comply with the shader program definition and provide the correct format texture for each sampler in the shader). The material defined by the second method is used, there is a high degree of fault tolerance and adaptability, but this kind of fault tolerance and adaptability needs to be completed by writing code by yourself. The NiStanderMaterial provided by GameByro provides this complete mechanism. If you set the texture in each texture slot, a corresponding texture processing flow will be generated. If you do not set the texture, there will be no processing flow for this texture.
To verify this process, I tried to add my own shader special effect, SubSurfaceScattering, or 3 s. Its principle is to simulate the effect of light scattering in a translucent object. Since this effect does not require preprocessing, and all textures are from disk files, it is easier to integrate them into the GameByro workflow.
The author puts the FX files that have been debugged in fx COMPOSER into the SDK \ Win32 \ Shaders \ Data directory in the SDK, and selects GameByroShader on the material panel of 3DMAX, then, you can see the Techinqe defined in the file in the display shader combo box. Click the apply button to display the custom parameter adjustment interface.
By adjusting the parameters, the rendering effects of skin and Jade are as follows:
Skin
Jade
Summary
GameByro's development process is very convenient and intuitive, but art can only allocate static data sources for the shader program, such as lighting charts and CubeMap; some texture data generated in real time in the program cannot be integrated into the art tools, such as shadow, refraction, reflection, and so on. These need to be implemented by the program. Debugging is not convenient. In most cases, you only need to use the NiStanderMaterial provided by GameByro to complete most of the material requirements. For special effects, you can write your own shader or use the shader library provided by the engine, only when we need to perform a lot of different processing based on complicated situations, we need to reload the NiFragmentMaterial to build our own shader tree. However, the shader tree program is generally complicated and difficult to write. Although the engine allows you to write material nodes through XML files, it is still inconvenient to use. GameByro does not provide related development tools for post-processing. The special effects of post-processing cannot be seen or obtained. Therefore, it must be improved.
GameByro provides some flexibility to generate dedicated shader code for ry in a specific environment, but it also pays the following price:
L analyzes the attributes and current status of the ry and generates the shader code for it with performance loss.
L after the Shader code is generated, it will be saved to the disk file. If Asynchronous is not used in this process, blocking may occur.
L The generated NiShader object consumes memory. GameByro initializes all shader files into NiShader objects by default. Therefore, when the game has been running for a long time, a large number of shader files will be generated, at this time, the memory consumption may be considerable, and the loading time will also increase. However, you can control the loading process and optimize the performance here.
Author: ye Qianyi