Skip to content

Implementing pass index

panzergame edited this page Aug 13, 2018 · 1 revision

BFBGE and UPBGE were not supporting all the information from the Object Info node :

object_info_node

This wiki is describing the modifications step by step of the sources to make this node works in game.

In this node Location is the object position, this one is properly updated. Object Index is the Pass Index in the object Relations panel. Material Index is the Pass Index property in material Render Pipeline Options (for node materials) or Options (for simple materials). For this last panel the option isn't exposed but exists. To show it in the UI the python script located in release/scripts/startup/bl_ui/properties_material.py must be modified. The class MATERIAL_PT_game_options is managing the properties in the Options panel when the game engine is selected.

class MATERIAL_PT_game_options(MaterialButtonsPanel, Panel):
    bl_label = "Options"
    COMPAT_ENGINES = {'BLENDER_GAME'}

    @classmethod
    def poll(cls, context):
        mat = context.material
        engine = context.scene.render.engine
        return check_material(mat) and (mat.type in {'SURFACE', 'WIRE'}) and (engine in cls.COMPAT_ENGINES)

    def draw(self, context):
        layout = self.layout

        base_mat = context.material
        mat = active_node_mat(base_mat)

        split = layout.split()

        col = split.column()
        if simple_material(base_mat):
            col.prop(mat, "invert_z")
            sub = col.row()
            sub.prop(mat, "offset_z")
            sub.active = mat.use_transparency and mat.transparency_method == 'Z_TRANSPARENCY'
        sub = col.column(align=True)
        sub.label(text="Light Group:")
        sub.prop(mat, "light_group", text="")
        row = sub.row(align=True)
        row.active = bool(mat.light_group)
        row.prop(mat, "use_light_group_exclusive", text="Exclusive")
        row.prop(mat, "use_light_group_local", text="Local")

        col = split.column()
        col.prop(mat, "use_mist")
        col.prop(mat, "use_vertex_color_paint")
        col.prop(mat, "use_vertex_color_light")
        col.prop(mat, "use_object_color")
        col.prop(mat, "use_instancing")

All the python script in bl_ui/ are using a common syntax to expose properties:

widget.prop(obj, "name")

The widget can be either a column, a row, a sub panel etc…, depending on the placement desired. objis the object used to take the property from and "name" the property name, in consideration obj.namemust exist.

Our property is Material.pass_index and we want it under Geometry Instancingproperty aligned with the same column. Then the line col.prop(mat, "pass_index")is added at the end of the draw function and the panel looks:

material_ui

Next we have to expose these variables to python game API, it's sometimes not needed or useful, but here the user can use the pass indices to dynamically change the property of a material. First of all we identify the classes concerned by these indices: KX_GameObject and KX_BlenderMaterial. These both class have a .h and .cpp file of the same name into the directory source/gameengine/Ketsji. In the two classes the variable m_passIndexis implemented:

class KX_BlenderMaterial/KX_GameObject : …
{
…
private:short m_passIndex;
…
};

Next the python API declaration for KX_BlenderMaterial.passIndexand KX_GameObject.passIndex follows the guide https://github.com/UPBGE/blender/wiki/Python-Proxy. The line:

	EXP_PYATTRIBUTE_SHORT_RW("passIndex", 0, SHRT_MAX, false, KX_GameObject/KX_BlenderMaterial, m_passIndex),

is added into PyAttributeDef KX_GameObject::Attributes[] and PyAttributeDef KX_BlenderMaterial::Attributes[] attributes arrays. This line register and attributes of type short using the variable passIndex in class KX_GameObject or KX_BlenderMaterial with a range of 0 to SHRT_MAX (maximum value for a short) without clamping but error throw.

These value should be initialized at game start based on blender data, for the materials the conversion is proceeded inside KX_BlenderMaterial constructor for object the function BL_GameObjectFromBlenderObject in BL_BLenderDataConversion.cpp is responsible.

As said the material constructor copy the value from blender data, this one is the struct Material and the variable index. The line

KX_BlenderMaterial::KX_BlenderMaterial(Material *mat, const std::string& name)
	:…,
       m_passIndex(mat->index)
{
…
}

is added into the men-initializer of KX_BlenderMaterial. Concerning KX_GameObject, the class supports trivial getter and setter for pass index:

class KX_GameObject : …
{
…
public:void SetPassIndex(short index);
	short GetPassIndex() const;
…
};

This setter is used in BL_GameObjectFromBlenderObject:

		gameobj->SetPassIndex(ob->index);

At this modification stage we have complete user interface to initialize the values from blender and modify them from python. The next step is passing these values to the rendering process.

Before typing anything, we have to understand how the rendering is basically working. The render is proceeded per materials, then per meshes part using this material, then per objects using this meshes part. So the material is bind at the first level, then all meshes part are bind and unbind and between, each object using this mesh part is updating material uniforms such as the object color and transform before a rendering of the mesh geometry. What we want is to update similarly to object color the object index. One class is used to store all the data relative to the object and shared by the meshes parts: RAS_MeshUser. This is holding the transformation, color, layer, front face setting etc… This is the correct place to add the object pass index. The short variable m_passIndex is added same for the trivial getter and setter:

class RAS_MeshUser : …
{
private:short m_passIndex;
…
public:short GetPassIndex() const;
…
	void SetPassIndex(short index);
…
};

After this we have to forward the object pass index from the mesh user to uniforms. The function GPU_material_bind_uniforms is updating the uniforms depending on objects of a material and is called into BL_BlenderShader::Update for every object mesh parts. In consideration this function has access to the mesh user and so object data.

Last argument of GPU_material_bind_uniforms is an optional array of three floats: [objects pass index, material pass index, random]. The first float is the value returned by RAS_MeshUser::GetPassIndex(). The second float is an argument passed to BL_BlenderShader::Update, as the caller is KX_BlenderMaterial::ActivateMeshSlot it's possible to pass m_passIndex to new argument short matPassIndex of BL_BlenderShader::Update.

Finally the random value is handled in way that mimic blender, blender is using the hash of the object name hash. As names are unique per object (most of the time) we can assume that a random unique per object can be used (but not equivalent at each game run as the only way to achieve it is by using the object name and replicated object can keep the same name, breaking the per object random) base on the hash of the client object pointer using the mesh user. This client object is get from RAS_MeshUser::GetClientObject() and warranted to be unique for each game object.

The function updating uniforms is:

void BL_BlenderShader::Update(RAS_MeshSlot *ms, short matPassIndex, RAS_Rasterizer *rasty)
{
…
	RAS_MeshUser *meshUser = ms->m_meshUser;
	const float (&obcol)[4] = meshUser->GetColor().Data();

	float objectInfo[3];
	if (GPU_get_material_builtins(m_gpuMat) & GPU_OBJECT_INFO) {
		objectInfo[0] = meshUser->GetPassIndex();
		objectInfo[1] = matPassIndex;
		objectInfo[2] = BLI_hash_int_2d((uintptr_t)meshUser->GetClientObject(), 0) / ((float)0xFFFFFFFF);
	}

	GPU_material_bind_uniforms(m_gpuMat, meshUser->GetMatrix().Data(), rasty->GetViewMatrix().Data(),
			obcol, meshUser->GetLayer(), 1.0f, nullptr, objectInfo);

…
}

To optimize the process, materials not using object info have bit GPU_OBJECT_INFO of GPU_get_material_builtins(m_gpuMat) to zero and they skip the object info update.

After doing all these modifications, we must check them by the accessing ways : UI and python API, leave a good commit message, open a pull request and have a nice day !

Clone this wiki locally