Skip to main content

Overview

The draw_model_execute hook is called when the game renders 3D models, allowing you to override materials, create chams (colored overlays), and apply custom visual effects to player models and weapons.
This hook is called at virtual table index 21 of the i_model_render interface.

Hook Signature

void __fastcall draw_model_execute_detour(
    void* ecx,
    void* edx,
    void* context,
    void* state,
    sdk::model_render_info& info,
    math::matrix_3x4* custom_bone_to_world
)

Parameters

ecx
void*
ECX register (fastcall convention) - pointer to the interface
edx
void*
EDX register (fastcall convention) - not used
context
void*
Rendering context
state
void*
Rendering state
info
sdk::model_render_info&
Information about the model being rendered, including model pointer and entity index
custom_bone_to_world
math::matrix_3x4*
Custom bone transformation matrices, or nullptr to use default

When It’s Called

This hook is invoked every time a 3D model is rendered, including:
  • Player models
  • Weapon models
  • World objects
  • Particle effects

Common Use Cases

Identifying Model Types

Filter models by checking the model name:
bool model_is_player = strstr(info.model->name, _("models/player"));
bool model_is_weapon = strstr(info.model->name, _("weapons/v_"));

Creating Custom Materials

Create and cache custom materials for visual effects:
static sdk::i_material* animated_wireframe{};
static sdk::i_material* material_regular{};
static sdk::i_material* material_flat{};

if (!animated_wireframe) {
    // Create VMT file
    std::ofstream(_("csgo/materials/animated_wireframe.vmt")) << _(R"#("UnlitGeneric" {
        "$basetexture" "models/inventory_items/dreamhack_trophies/dreamhack_star_blur"
        "$wireframe" "1"
        "$alpha" "0.6"
        "$additive" "1"
        "$ignorez" "1"
        "proxies" {
            "texturescroll" {
                "texturescrollvar" "$basetexturetransform"
                "texturescrollrate" "0.2"
                "texturescrollangle" "90"
            }
        }
    })#");
}

animated_wireframe = g_interfaces.material_system->find_material(_("animated_wireframe"));
material_regular = g_interfaces.material_system->find_material(_("debug/debugambientcube"));
material_flat = g_interfaces.material_system->find_material(_("debug/debugdrawflat"));

Player Model Chams

Apply colored overlays to player models:
if (model_is_player && info.entity_index >= 0 && info.entity_index <= 64) {
    material_regular->color_modulate(99 / 255.f, 0 / 255.f, 114 / 255.f);
    material_regular->set_material_var_flag(sdk::MATERIAL_VAR_IGNOREZ, true);
    
    g_interfaces.model_render->forced_material_override(material_regular);
    
    draw_model_execute_hook.call_original<void>(
        ecx, edx, context, state, info, custom_bone_to_world
    );
    
    g_interfaces.model_render->forced_material_override(nullptr);
}

Lag Compensation Visualization

Render backtrack positions for lag compensation:
if (model_is_player && info.entity_index >= 0 && info.entity_index <= 64) {
    static auto unlag_pointer = g_convars[_("sv_maxunlag")];
    auto sv_maxunlag_ticks = sdk::time_to_ticks(unlag_pointer->get_float());
    
    for (int i = 0; i < sv_maxunlag_ticks; i++) {
        auto current_record = &g_lagcomp.heap_records[info.entity_index][i];
        
        // Render with gradient color based on age
        const int green = static_cast<int>((i + 1) * 3 * 2.55f);
        
        material_regular->color_modulate(
            (255 - green) / 255.f, 
            green / 255.f, 
            0.7058f
        );
        material_regular->alpha_modulate(0.7f);
        
        // Draw using backtrack bone matrix
        draw_model_execute_hook.call_original<void>(
            ecx, edx, context, state, info, current_record->bone_matrix
        );
    }
}

Weapon Chams

Apply animated materials to viewmodel weapons:
if (model_is_weapon) {
    animated_wireframe->color_modulate(0 / 255.f, 255 / 255.f, 255 / 255.f);
    animated_wireframe->set_material_var_flag(sdk::MATERIAL_VAR_IGNOREZ, false);
    
    g_interfaces.model_render->forced_material_override(animated_wireframe);
    
    draw_model_execute_hook.call_original<void>(
        ecx, edx, context, state, info, custom_bone_to_world
    );
    
    g_interfaces.model_render->forced_material_override(nullptr);
}
Always reset material overrides after rendering:
g_interfaces.model_render->forced_material_override(nullptr);
Failing to do so will cause all subsequent models to use your custom material.

Material Flags

Common material flags you can modify:
  • MATERIAL_VAR_IGNOREZ - Render through walls (ignores depth buffer)
  • MATERIAL_VAR_WIREFRAME - Render as wireframe
  • MATERIAL_VAR_FLAT - Flat shading
  • MATERIAL_VAR_ADDITIVE - Additive blending

Implementation Example

CREATE_HOOK_HELPER(
    draw_model_execute_hook,
    void(__fastcall)(void*, void*, void*, void*, 
                     sdk::model_render_info&, math::matrix_3x4*)
);

static void __fastcall draw_model_execute_detour(
    void* ecx,
    void* edx,
    void* context,
    void* state,
    sdk::model_render_info& info,
    math::matrix_3x4* custom_bone_to_world
);

Source Files

  • Header: globals/hooks/draw_model_execute/draw_model_execute.h:16-30
  • Implementation: globals/hooks/draw_model_execute/draw_model_execute.cpp:6-99

Initialization

void init()
{
    hooks::draw_model_execute_hook.create(
        virtual_func::get(g_interfaces.model_render, 21),
        draw_model_execute_detour,
        _("draw_model_execute_detour")
    );
}

See Also

Build docs developers (and LLMs) love