Bibliotecas Gráficas em Zig — OpenGL, Vulkan e Renderização 2D/3D

Bibliotecas Gráficas em Zig — OpenGL, Vulkan e Renderização 2D/3D

O Zig está se tornando uma escolha popular para desenvolvimento gráfico e de jogos. Com performance comparável a C, controle fino de memória e compilação cruzada nativa, o ecossistema oferece desde bindings para APIs gráficas tradicionais até frameworks de renderização completos. Este guia cobre as principais opções disponíveis.

Panorama das Bibliotecas Gráficas

O ecossistema gráfico do Zig pode ser dividido em camadas:

  1. APIs de baixo nível: OpenGL, Vulkan, DirectX via bindings
  2. Frameworks intermediários: Abstrações sobre APIs gráficas
  3. Engines completas: Mach Engine e similares
  4. Bibliotecas 2D: Renderização de sprites, texto e UI

OpenGL com Zig

zopengl — Bindings OpenGL

const gl = @import("zopengl");
const glfw = @import("zglfw");

pub fn main() !void {
    try glfw.init();
    defer glfw.terminate();

    const window = try glfw.Window.create(800, 600, "Zig OpenGL", null, null, .{
        .context_version_major = 3,
        .context_version_minor = 3,
        .opengl_profile = .core,
    });
    defer window.destroy();

    glfw.makeContextCurrent(window);
    try gl.load(glfw.getProcAddress);

    // Configurar viewport
    gl.viewport(0, 0, 800, 600);
    gl.clearColor(0.2, 0.3, 0.3, 1.0);

    // Definir vértices do triângulo
    const vertices = [_]f32{
        -0.5, -0.5, 0.0, // inferior esquerdo
         0.5, -0.5, 0.0, // inferior direito
         0.0,  0.5, 0.0, // superior
    };

    var vao: gl.GLuint = undefined;
    var vbo: gl.GLuint = undefined;
    gl.genVertexArrays(1, &vao);
    gl.genBuffers(1, &vbo);

    gl.bindVertexArray(vao);
    gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
    gl.bufferData(gl.ARRAY_BUFFER, &vertices, gl.STATIC_DRAW);
    gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 3 * @sizeOf(f32), null);
    gl.enableVertexAttribArray(0);

    // Loop de renderização
    while (!window.shouldClose()) {
        gl.clear(gl.COLOR_BUFFER_BIT);
        gl.bindVertexArray(vao);
        gl.drawArrays(gl.TRIANGLES, 0, 3);
        window.swapBuffers();
        glfw.pollEvents();
    }
}

Vulkan com Zig

vulkan-zig — Bindings Gerados

O vulkan-zig gera bindings Zig a partir do registro Vulkan oficial:

const vk = @import("vulkan");

pub fn main() !void {
    // Criar instância Vulkan
    const app_info = vk.ApplicationInfo{
        .p_application_name = "Zig Vulkan App",
        .application_version = vk.makeApiVersion(0, 1, 0, 0),
        .p_engine_name = "Zig Engine",
        .engine_version = vk.makeApiVersion(0, 1, 0, 0),
        .api_version = vk.API_VERSION_1_3,
    };

    const create_info = vk.InstanceCreateInfo{
        .p_application_info = &app_info,
    };

    const instance = try vk.createInstance(&create_info, null);
    defer vk.destroyInstance(instance, null);

    // Enumerar dispositivos físicos
    var device_count: u32 = 0;
    _ = try vk.enumeratePhysicalDevices(instance, &device_count, null);

    std.debug.print("Dispositivos Vulkan encontrados: {}\n", .{device_count});
}

Raylib via Zig

O raylib é popular por sua simplicidade e o binding Zig é muito bem mantido:

const rl = @import("raylib");

pub fn main() void {
    rl.initWindow(800, 600, "Zig Raylib Demo");
    defer rl.closeWindow();

    rl.setTargetFPS(60);

    var posicao_bola = rl.Vector2{ .x = 400, .y = 300 };
    const raio: f32 = 25;
    const velocidade: f32 = 5;

    while (!rl.windowShouldClose()) {
        // Atualizar
        if (rl.isKeyDown(.key_right)) posicao_bola.x += velocidade;
        if (rl.isKeyDown(.key_left)) posicao_bola.x -= velocidade;
        if (rl.isKeyDown(.key_down)) posicao_bola.y += velocidade;
        if (rl.isKeyDown(.key_up)) posicao_bola.y -= velocidade;

        // Renderizar
        rl.beginDrawing();
        defer rl.endDrawing();

        rl.clearBackground(rl.Color.ray_white);
        rl.drawCircleV(posicao_bola, raio, rl.Color.red);
        rl.drawText("Use as setas para mover", 10, 10, 20, rl.Color.dark_gray);
        rl.drawFPS(740, 10);
    }
}

SDL2 via Zig

const sdl = @cImport({
    @cInclude("SDL2/SDL.h");
});

pub fn main() !void {
    if (sdl.SDL_Init(sdl.SDL_INIT_VIDEO) != 0) return error.SdlInitFailed;
    defer sdl.SDL_Quit();

    const window = sdl.SDL_CreateWindow(
        "Zig SDL2",
        sdl.SDL_WINDOWPOS_CENTERED,
        sdl.SDL_WINDOWPOS_CENTERED,
        800,
        600,
        0,
    ) orelse return error.WindowCreationFailed;
    defer sdl.SDL_DestroyWindow(window);

    const renderer = sdl.SDL_CreateRenderer(window, -1, sdl.SDL_RENDERER_ACCELERATED) orelse return error.RendererFailed;
    defer sdl.SDL_DestroyRenderer(renderer);

    var rodando = true;
    while (rodando) {
        var event: sdl.SDL_Event = undefined;
        while (sdl.SDL_PollEvent(&event) != 0) {
            if (event.type == sdl.SDL_QUIT) rodando = false;
        }

        _ = sdl.SDL_SetRenderDrawColor(renderer, 30, 50, 80, 255);
        _ = sdl.SDL_RenderClear(renderer);

        // Desenhar retângulo
        _ = sdl.SDL_SetRenderDrawColor(renderer, 255, 100, 50, 255);
        var rect = sdl.SDL_Rect{ .x = 100, .y = 100, .w = 200, .h = 150 };
        _ = sdl.SDL_RenderFillRect(renderer, &rect);

        sdl.SDL_RenderPresent(renderer);
    }
}

Renderização 2D Nativa

zimg — Processamento de Imagens

const zimg = @import("zimg");

pub fn main() !void {
    // Carregar imagem
    var img = try zimg.Image.loadFromFile(allocator, "sprite.png");
    defer img.deinit();

    // Manipular pixels
    img.setPixel(10, 20, .{ .r = 255, .g = 0, .b = 0, .a = 255 });

    // Redimensionar
    var resized = try img.resize(allocator, 128, 128);
    defer resized.deinit();

    // Salvar
    try resized.saveToFile("sprite_small.png");
}

Matemática para Gráficos

zlm — Zig Linear Math

const zlm = @import("zlm");

const Vec3 = zlm.Vec3;
const Mat4 = zlm.Mat4;

pub fn main() void {
    // Vetores
    const posicao = Vec3.new(1.0, 2.0, 3.0);
    const direcao = Vec3.new(0.0, 0.0, -1.0);
    const resultado = posicao.add(direcao.scale(5.0));
    _ = resultado;

    // Matrizes de transformação
    const modelo = Mat4.identity();
    const visao = Mat4.lookAt(
        Vec3.new(0, 0, 5),  // Posição da câmera
        Vec3.new(0, 0, 0),  // Alvo
        Vec3.new(0, 1, 0),  // Up
    );
    const projecao = Mat4.perspective(
        std.math.degreesToRadians(45.0),
        800.0 / 600.0,
        0.1,
        100.0,
    );

    const mvp = projecao.mul(visao).mul(modelo);
    _ = mvp;
}

Comparação de Opções

BibliotecaNívelPlataformasComplexidadeUso Ideal
MachAltoTodasMédiaJogos completos
RaylibAltoTodasBaixaProtótipos, jogos 2D
SDL2MédioTodasMédiaJogos, multimídia
zopenglBaixoDesktopAltaRenderização customizada
vulkan-zigBaixoDesktopMuito altaPerformance máxima

Próximos Passos

Explore o Mach Engine para desenvolvimento de jogos completo, as bibliotecas de áudio para som em jogos, e as ferramentas WASM para gráficos no browser. Confira o case do Mach e nossos tutoriais para projetos práticos.

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.