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
- ¿Qué es un Vertex Shader?
- Configuración del Entorno
- Estructura Básica de un Vertex Shader
- Ejemplo Práctico: Transformación de Vértices
- Compilación y Uso del Vertex Shader
- Ejercicios Prácticos
- ¿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.
- 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.
- 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
yVS_OUTPUT
definen los datos que el Vertex Shader recibe y envía. - Matrices de Transformación:
World
,View
, yProjection
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.
- 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
, yProjection
para transformarla al espacio de la pantalla. - Color: Pasamos el color del vértice sin cambios.
- 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
- 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.
Curso de Programación DirectX
Módulo 1: Introducción a DirectX
- ¿Qué es DirectX?
- Configuración del Entorno de Desarrollo
- Entendiendo la API de DirectX
- Creando Tu Primera Aplicación DirectX
Módulo 2: Conceptos Básicos de Direct3D
- Introducción a Direct3D
- Inicializando Direct3D
- Renderizando un Triángulo
- Manejando el Bucle de Renderizado
Módulo 3: Trabajando con Shaders
- Introducción a los Shaders
- Escribiendo Vertex Shaders
- Escribiendo Pixel Shaders
- Compilando y Usando Shaders
Módulo 4: Técnicas Avanzadas de Renderizado
Módulo 5: Modelos 3D y Animación
Módulo 6: Optimización del Rendimiento
- Perfilado y Depuración
- Optimizando el Rendimiento de Renderizado
- Gestión de Memoria
- Multithreading en DirectX