M

Mantrax Engine

Guía rápida – Cubo 3D con DX11

1 ¿Qué hace este ejemplo?

Este código muestra cómo:

  • Inicializar una ventana y dispositivo DirectX 11 con MantraxGFX.
  • Crear un cubo 3D con vértices e índices.
  • Configurar matrices de mundo, vista y proyección.
  • Dibujar varios cubos reutilizando el mismo mesh.
  • Controlar la rotación y visibilidad con el teclado.

2 Requisitos

  • Windows + SDK de Windows instalado.
  • MantraxGFX (incluyendo MantraxGFX.h y libs).
  • Compilador C++ (Visual Studio recomendado).
  • Shaders HLSL cube_vs.hlsl y cube_ps.hlsl.

3 Estructuras: vértices, índices y constantes

3.1. Estructura del vértice

Cada vértice del cubo tiene posición 3D (x, y, z) y color RGB (r, g, b).


struct Vertex {
    float x, y, z;   // Posición
    float r, g, b;   // Color
};
                

3.2. Índices del cubo

El cubo se define con 8 vértices y 36 índices (6 caras × 2 triángulos × 3 vértices).


Vertex vertices[] = {
    {-1, -1, -1, 1, 0, 0}, // 0
    { 1, -1, -1, 0, 1, 0}, // 1
    { 1,  1, -1, 0, 0, 1}, // 2
    {-1,  1, -1, 1, 1, 0}, // 3
    {-1, -1,  1, 1, 0, 1}, // 4
    { 1, -1,  1, 0, 1, 1}, // 5
    { 1,  1,  1, 1, 1, 1}, // 6
    {-1,  1,  1, 0, 0, 0}  // 7
};

uint32_t indices[] = {
    // Frente (+Z)
    4, 5, 6, 4, 6, 7,
    // Atrás (-Z)
    1, 0, 3, 1, 3, 2,
    // Izquierda (-X)
    0, 4, 7, 0, 7, 3,
    // Derecha (+X)
    5, 1, 2, 5, 2, 6,
    // Arriba (+Y)
    7, 6, 2, 7, 2, 3,
    // Abajo (-Y)
    0, 1, 5, 0, 5, 4
};
                

3.3. Buffer de constantes: matrices

TransformData agrupa las matrices de mundo, vista y proyección que se enviarán al shader de vértices.


struct TransformData {
    float world[16];
    float view[16];
    float proj[16];
};
                

4 Inicializar el motor y la ventana

En WinMain se crea una consola para logs, la ventana y el dispositivo gráfico DirectX 11 a través de MantraxGFX.


int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
    AllocConsole();
    FILE* f;
    freopen_s(&f, "CONOUT$", "w", stdout);

    HWND hwnd = (HWND)MantraxGFX::CreateWindowSimple(
        "Mantrax Engine - CUBO 3D",
        1280, 720
    );

    auto gfx = MantraxGFX::CreateDeviceDX11(hwnd, 1280, 720);
    // ...
}
              
  • CreateWindowSimple crea la ventana de render.
  • CreateDeviceDX11 inicializa el device y el swap chain.

5 Crear buffers y shaders

5.1. Vertex buffer

Almacena la lista de vértices del cubo.


MantraxGFX::BufferDesc vbDesc;
vbDesc.size    = sizeof(vertices);
vbDesc.dynamic = false;
vbDesc.type    = MantraxGFX::BufferType::Vertex;
auto* vertexBuffer = gfx->CreateBuffer(vbDesc, vertices);
                

5.2. Index buffer

Indica el orden en que se usan los vértices para formar triángulos.


MantraxGFX::BufferDesc ibDesc;
ibDesc.size    = sizeof(indices);
ibDesc.dynamic = false;
ibDesc.type    = MantraxGFX::BufferType::Index;
auto* indexBuffer = gfx->CreateBuffer(ibDesc, indices);
                

5.3. Constant buffer (TransformData)

Se usa para enviar matrices actualizadas cada frame.


MantraxGFX::BufferDesc cbDesc;
cbDesc.size    = sizeof(TransformData);
cbDesc.dynamic = true;
cbDesc.type    = MantraxGFX::BufferType::Constant;
auto* constantBuffer = gfx->CreateBuffer(cbDesc, nullptr);
                

5.4. Shaders y pipeline

Se cargan los shaders HLSL y se configura el pipeline (blend/depth).


MantraxGFX::ShaderDesc shaderDesc;
shaderDesc.vsPath = "cube_vs.hlsl";
shaderDesc.psPath = "cube_ps.hlsl";
auto* shader = gfx->CreateShader(shaderDesc);

MantraxGFX::PipelineDesc pipeDesc;
pipeDesc.shader = shader;
pipeDesc.blend  = MantraxGFX::BlendMode::Opaque;
pipeDesc.depth  = MantraxGFX::DepthMode::Less;
auto* pipeline  = gfx->CreatePipeline(pipeDesc);
                

6 Bucle principal: actualizar matrices y dibujar

En cada frame:

  1. Se procesa la cola de mensajes de Windows.
  2. Se calcula dt (delta time).
  3. Se actualiza la rotación global.
  4. Se construyen las matrices world, view y proj.
  5. Se actualiza el constant buffer.
  6. Se dibujan los cubos.

// Dentro del while (msg.message != WM_QUIT)
float dt = timer.Tick();

if (g_autoRotate)
    g_rotation += dt;

TransformData tdata;

// Matriz de vista (cámara)
MatrixLookAt(
    tdata.view,
    0.0f, 2.0f, -6.0f,   // eye
    0.0f, 0.0f,  0.0f    // target
);

// Matriz de proyección
uint32_t w, h;
gfx->GetWindowSize(&w, &h);
float aspect = (float)w / (float)h;
MatrixPerspective(
    tdata.proj,
    3.14159f / 4.0f,     // FOV
    aspect,
    0.1f,                // near
    100.0f               // far
);

auto* cmd = gfx->BeginFrame();
              

Dibujar varios cubos

En este ejemplo se reutiliza el mismo mesh y se cambia la matriz World para cada cubo.


struct CubeInstance {
    float x, y, z;
};

CubeInstance cubes[] = {
    { 0, 0, 0},
    { 3, 0, 0},
    {-3, 0, 0}
};

int numCubes = 3; // (ajusta según el tamaño de tu arreglo)

if (g_showCube) {
    cmd->SetPipeline(pipeline);
    cmd->SetVertexBuffer(vertexBuffer, sizeof(Vertex));
    cmd->SetIndexBuffer(indexBuffer);

    for (int i = 0; i < numCubes; i++) {
        float rot[16], trans[16], world[16];

        MatrixRotationY(rot, g_rotation + i * 0.5f);
        MatrixTranslation(trans, cubes[i].x, cubes[i].y, cubes[i].z);

        // world = rot (copiar)
        for (int j = 0; j < 16; j++)
            world[j] = rot[j];

        // aplicar traslación
        world[12] += trans[12];
        world[13] += trans[13];
        world[14] += trans[14];

        memcpy(tdata.world, world, sizeof(world));

        gfx->UpdateBuffer(constantBuffer, &tdata, sizeof(tdata));
        cmd->SetConstantBuffer(0, constantBuffer);
        cmd->DrawIndexed(36);
    }
}

gfx->EndFrame();
              

7 Controles de teclado

Se configura un callback de teclado donde se manejan teclas para controlar la escena.


gfx->SetKeyCallback([](const MantraxGFX::KeyEvent& evt) {
    if (evt.pressed && !evt.isRepeat) {
        switch (evt.keyCode) {
            case VK_ESCAPE:
                printf("ESC - salir\n");
                PostQuitMessage(0);
                break;

            case VK_SPACE:
                g_showCube = !g_showCube;
                printf("SPACE - Cubo %s\n", g_showCube ? "VISIBLE" : "OCULTO");
                break;

            case VK_LEFT:
                g_rotation -= 0.1f;
                break;

            case VK_RIGHT:
                g_rotation += 0.1f;
                break;

            case 'T':
                g_autoRotate = !g_autoRotate;
                printf("T - Auto rotación %s\n", g_autoRotate ? "ON" : "OFF");
                break;

            case 'R':
                g_rotation = 0.0f;
                printf("R - Reset\n");
                break;
        }
    }
});
              

Teclas

  • ESC – Salir
  • SPACE – Mostrar/ocultar cubo(s)
  • ← → – Rotación manual
  • T – Auto-rotación ON/OFF
  • R – Reset rotación

8 Ejemplo de shaders (cube_vs.hlsl / cube_ps.hlsl)

Ejemplo básico de cómo podrían lucir los shaders que esperan el TransformData y el color del vértice:

8.1. Vertex shader (cube_vs.hlsl)


cbuffer TransformBuffer : register(b0)
{
    float4x4 world;
    float4x4 view;
    float4x4 proj;
};

struct VSInput
{
    float3 pos   : POSITION;
    float3 color : COLOR;
};

struct VSOutput
{
    float4 pos   : SV_POSITION;
    float3 color : COLOR;
};

VSOutput main(VSInput input)
{
    VSOutput o;
    float4 wpos = mul(float4(input.pos, 1.0f), world);
    float4 vpos = mul(wpos, view);
    float4 ppos = mul(vpos, proj);

    o.pos   = ppos;
    o.color = input.color;
    return o;
}

8.2. Pixel shader (cube_ps.hlsl)


struct PSInput
{
    float4 pos   : SV_POSITION;
    float3 color : COLOR;
};

float4 main(PSInput input) : SV_TARGET
{
    return float4(input.color, 1.0f);
}