En este tema, aprenderemos a escribir Vertex Shaders, que son programas que se ejecutan en la GPU y procesan cada vértice de un objeto 3D. Los Vertex Shaders son fundamentales para transformar y manipular la geometría de los modelos antes de que sean renderizados.

Contenido

  1. ¿Qué es un Vertex Shader?
  2. Configuración del Entorno
  3. Estructura Básica de un Vertex Shader
  4. Ejemplo Práctico: Transformación de Vértices
  5. Compilación y Uso del Vertex Shader
  6. Ejercicios Prácticos

  1. ¿Qué es un Vertex Shader?

Un Vertex Shader es un programa escrito en HLSL (High-Level Shading Language) que se ejecuta en la GPU. Su principal función es transformar las coordenadas de los vértices de un modelo 3D desde el espacio del modelo al espacio de la pantalla. Además, puede realizar otras operaciones como la iluminación y la manipulación de atributos de vértices.

Funciones Principales de un Vertex Shader

  • Transformación de Coordenadas: Convertir las coordenadas de los vértices desde el espacio del modelo al espacio de la pantalla.
  • Iluminación: Calcular la iluminación en cada vértice.
  • Manipulación de Atributos: Modificar atributos como el color, las coordenadas de textura, etc.

  1. Configuración del Entorno

Antes de escribir un Vertex Shader, necesitamos configurar nuestro entorno de desarrollo. Asegúrate de tener instalado:

  • Visual Studio: Con el soporte para C++ y DirectX.
  • DirectX SDK: Para acceder a las librerías y herramientas necesarias.

  1. Estructura Básica de un Vertex Shader

Un Vertex Shader en HLSL tiene una estructura básica que incluye la declaración de entradas y salidas, y la función principal que realiza las transformaciones.

Ejemplo de Estructura Básica

// Estructura de entrada
struct VS_INPUT {
    float4 Pos : POSITION; // Posición del vértice
    float4 Color : COLOR;  // Color del vértice
};

// Estructura de salida
struct VS_OUTPUT {
    float4 Pos : SV_POSITION; // Posición transformada
    float4 Color : COLOR;     // Color del vértice
};

// Matrices de transformación
cbuffer ConstantBuffer : register(b0) {
    matrix World;
    matrix View;
    matrix Projection;
};

// Función principal del Vertex Shader
VS_OUTPUT main(VS_INPUT input) {
    VS_OUTPUT output;

    // Transformar la posición del vértice
    float4 worldPos = mul(input.Pos, World);
    float4 viewPos = mul(worldPos, View);
    output.Pos = mul(viewPos, Projection);

    // Pasar el color del vértice
    output.Color = input.Color;

    return output;
}

Explicación del Código

  • Estructuras de Entrada y Salida: VS_INPUT y VS_OUTPUT definen los datos que el Vertex Shader recibe y envía.
  • Matrices de Transformación: World, View, y Projection son matrices utilizadas para transformar las coordenadas del vértice.
  • Función Principal: La función main realiza las transformaciones y pasa los datos al siguiente paso en la pipeline de renderizado.

  1. Ejemplo Práctico: Transformación de Vértices

Vamos a escribir un Vertex Shader que transforma los vértices de un triángulo y aplica un color.

Código del Vertex Shader

struct VS_INPUT {
    float4 Pos : POSITION;
    float4 Color : COLOR;
};

struct VS_OUTPUT {
    float4 Pos : SV_POSITION;
    float4 Color : COLOR;
};

cbuffer ConstantBuffer : register(b0) {
    matrix World;
    matrix View;
    matrix Projection;
};

VS_OUTPUT main(VS_INPUT input) {
    VS_OUTPUT output;

    float4 worldPos = mul(input.Pos, World);
    float4 viewPos = mul(worldPos, View);
    output.Pos = mul(viewPos, Projection);

    output.Color = input.Color;

    return output;
}

Explicación del Código

  • Transformación de Coordenadas: Multiplicamos la posición del vértice por las matrices World, View, y Projection para transformarla al espacio de la pantalla.
  • Color: Pasamos el color del vértice sin cambios.

  1. Compilación y Uso del Vertex Shader

Para usar el Vertex Shader en tu aplicación DirectX, necesitas compilarlo y cargarlo en tu programa.

Compilación del Shader

ID3DBlob* vertexShaderBlob = nullptr;
ID3DBlob* errorBlob = nullptr;

HRESULT hr = D3DCompileFromFile(L"VertexShader.hlsl", nullptr, nullptr, "main", "vs_5_0", 0, 0, &vertexShaderBlob, &errorBlob);
if (FAILED(hr)) {
    if (errorBlob) {
        OutputDebugStringA((char*)errorBlob->GetBufferPointer());
        errorBlob->Release();
    }
    if (vertexShaderBlob) vertexShaderBlob->Release();
    return hr;
}

Creación del Vertex Shader

ID3D11VertexShader* vertexShader = nullptr;
hr = device->CreateVertexShader(vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), nullptr, &vertexShader);
if (FAILED(hr)) {
    vertexShaderBlob->Release();
    return hr;
}

Configuración del Input Layout

D3D11_INPUT_ELEMENT_DESC layout[] = {
    { "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 16, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};

ID3D11InputLayout* inputLayout = nullptr;
hr = device->CreateInputLayout(layout, ARRAYSIZE(layout), vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), &inputLayout);
vertexShaderBlob->Release();
if (FAILED(hr)) {
    return hr;
}

Uso del Vertex Shader

context->IASetInputLayout(inputLayout);
context->VSSetShader(vertexShader, nullptr, 0);

  1. Ejercicios Prácticos

Ejercicio 1: Transformación Básica

Escribe un Vertex Shader que transforme los vértices de un cuadrado y aplique un color degradado.

Ejercicio 2: Iluminación Básica

Modifica el Vertex Shader para calcular la iluminación básica utilizando la dirección de la luz y la normal del vértice.

Soluciones

Ejercicio 1: Transformación Básica

struct VS_INPUT {
    float4 Pos : POSITION;
    float4 Color : COLOR;
};

struct VS_OUTPUT {
    float4 Pos : SV_POSITION;
    float4 Color : COLOR;
};

cbuffer ConstantBuffer : register(b0) {
    matrix World;
    matrix View;
    matrix Projection;
};

VS_OUTPUT main(VS_INPUT input) {
    VS_OUTPUT output;

    float4 worldPos = mul(input.Pos, World);
    float4 viewPos = mul(worldPos, View);
    output.Pos = mul(viewPos, Projection);

    output.Color = input.Color;

    return output;
}

Ejercicio 2: Iluminación Básica

struct VS_INPUT {
    float4 Pos : POSITION;
    float4 Normal : NORMAL;
    float4 Color : COLOR;
};

struct VS_OUTPUT {
    float4 Pos : SV_POSITION;
    float4 Color : COLOR;
};

cbuffer ConstantBuffer : register(b0) {
    matrix World;
    matrix View;
    matrix Projection;
    float4 LightDirection;
    float4 LightColor;
};

VS_OUTPUT main(VS_INPUT input) {
    VS_OUTPUT output;

    float4 worldPos = mul(input.Pos, World);
    float4 viewPos = mul(worldPos, View);
    output.Pos = mul(viewPos, Projection);

    float3 normal = normalize(mul(input.Normal, (float3x3)World));
    float lightIntensity = max(dot(normal, LightDirection.xyz), 0.0);
    output.Color = input.Color * LightColor * lightIntensity;

    return output;
}

Conclusión

En esta sección, hemos aprendido qué es un Vertex Shader, cómo escribir uno básico en HLSL, y cómo integrarlo en una aplicación DirectX. Los Vertex Shaders son una parte esencial del pipeline de renderizado y permiten realizar transformaciones y manipulaciones avanzadas de los vértices. En el próximo tema, exploraremos los Pixel Shaders y cómo se utilizan para determinar el color final de los píxeles en la pantalla.

© Copyright 2024. Todos los derechos reservados