Implementar shader CRT con post-processing: migración completa GLSL a MSL
- Migrar shader CRT de GLSL a Metal Shading Language (MSL) - Implementar renderizado de dos pasadas: * Paso 1: Renderizar escena a textura offscreen * Paso 2: Aplicar efecto CRT como post-procesamiento - Añadir shaders CRT con scanlines, shadow mask y gamma correction - Crear offscreen render target para renderizado intermedio - Implementar fullscreen quad para post-procesamiento - Configurar pipeline CRT con samplers linear y NEAREST - Mantener compatibilidad con sprites multi-color existentes - Resolución virtual CRT: 320x240 para apariencia retro auténtica 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
379
source/main.cpp
379
source/main.cpp
@@ -38,6 +38,7 @@
|
||||
// Estructura para vértices de sprites
|
||||
struct SpriteVertex {
|
||||
float position[2]; // x, y coordinates
|
||||
float color[4]; // r, g, b, a color components
|
||||
float texCoord[2]; // u, v texture coordinates
|
||||
};
|
||||
|
||||
@@ -204,24 +205,27 @@ fragment float4 triangle_fragment_main(VertexOut in [[stage_in]]) {
|
||||
}
|
||||
)";
|
||||
|
||||
// Shaders para sprites con textura
|
||||
// Shaders para sprites con textura y color
|
||||
NSString* spriteVertexShaderSource = @R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct SpriteVertexIn {
|
||||
float2 position [[attribute(0)]];
|
||||
float2 texCoord [[attribute(1)]];
|
||||
float4 color [[attribute(1)]];
|
||||
float2 texCoord [[attribute(2)]];
|
||||
};
|
||||
|
||||
struct SpriteVertexOut {
|
||||
float4 position [[position]];
|
||||
float4 color;
|
||||
float2 texCoord;
|
||||
};
|
||||
|
||||
vertex SpriteVertexOut sprite_vertex_main(SpriteVertexIn in [[stage_in]]) {
|
||||
SpriteVertexOut out;
|
||||
out.position = float4(in.position, 0.0, 1.0);
|
||||
out.color = in.color;
|
||||
out.texCoord = in.texCoord;
|
||||
return out;
|
||||
}
|
||||
@@ -233,6 +237,7 @@ using namespace metal;
|
||||
|
||||
struct SpriteVertexOut {
|
||||
float4 position [[position]];
|
||||
float4 color;
|
||||
float2 texCoord;
|
||||
};
|
||||
|
||||
@@ -240,12 +245,205 @@ fragment float4 sprite_fragment_main(SpriteVertexOut in [[stage_in]],
|
||||
texture2d<float> spriteTexture [[texture(0)]],
|
||||
sampler textureSampler [[sampler(0)]]) {
|
||||
float4 textureColor = spriteTexture.sample(textureSampler, in.texCoord);
|
||||
return textureColor;
|
||||
return textureColor * in.color; // Multiplicar textura por color de vértice para tinting
|
||||
}
|
||||
)";
|
||||
|
||||
// Compilar shaders de fondo
|
||||
// Shaders para CRT post-processing (fullscreen quad)
|
||||
NSString* crtVertexShaderSource = @R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct CRTVertexOut {
|
||||
float4 position [[position]];
|
||||
float2 texCoord;
|
||||
};
|
||||
|
||||
vertex CRTVertexOut crt_vertex_main(uint vertexID [[vertex_id]]) {
|
||||
CRTVertexOut out;
|
||||
|
||||
// Fullscreen quad con coordenadas de textura
|
||||
float2 positions[6] = {
|
||||
float2(-1.0, -1.0), // Bottom left
|
||||
float2( 1.0, -1.0), // Bottom right
|
||||
float2(-1.0, 1.0), // Top left
|
||||
float2( 1.0, -1.0), // Bottom right
|
||||
float2( 1.0, 1.0), // Top right
|
||||
float2(-1.0, 1.0) // Top left
|
||||
};
|
||||
|
||||
float2 texCoords[6] = {
|
||||
float2(0.0, 1.0), // Bottom left
|
||||
float2(1.0, 1.0), // Bottom right
|
||||
float2(0.0, 0.0), // Top left
|
||||
float2(1.0, 1.0), // Bottom right
|
||||
float2(1.0, 0.0), // Top right
|
||||
float2(0.0, 0.0) // Top left
|
||||
};
|
||||
|
||||
out.position = float4(positions[vertexID], 0.0, 1.0);
|
||||
out.texCoord = texCoords[vertexID];
|
||||
|
||||
return out;
|
||||
}
|
||||
)";
|
||||
|
||||
// CRT Fragment Shader - Migrado de GLSL a MSL
|
||||
NSString* crtFragmentShaderSource = @R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
// Parámetros del CRT shader
|
||||
#define CURVATURE_X 0.05
|
||||
#define CURVATURE_Y 0.1
|
||||
#define MASK_BRIGHTNESS 0.80
|
||||
#define SCANLINE_WEIGHT 6.0
|
||||
#define SCANLINE_GAP_BRIGHTNESS 0.12
|
||||
#define BLOOM_FACTOR 3.5
|
||||
#define INPUT_GAMMA 2.4
|
||||
#define OUTPUT_GAMMA 2.2
|
||||
|
||||
// Features habilitadas
|
||||
#define SCANLINES
|
||||
#define MULTISAMPLE
|
||||
#define GAMMA
|
||||
// #define FAKE_GAMMA
|
||||
// #define CURVATURE
|
||||
// #define SHARPER
|
||||
#define MASK_TYPE 2
|
||||
|
||||
struct CRTVertexOut {
|
||||
float4 position [[position]];
|
||||
float2 texCoord;
|
||||
};
|
||||
|
||||
float CalcScanLineWeight(float dist) {
|
||||
return max(1.0 - dist * dist * SCANLINE_WEIGHT, SCANLINE_GAP_BRIGHTNESS);
|
||||
}
|
||||
|
||||
float CalcScanLine(float dy, float filterWidth) {
|
||||
float scanLineWeight = CalcScanLineWeight(dy);
|
||||
#ifdef MULTISAMPLE
|
||||
scanLineWeight += CalcScanLineWeight(dy - filterWidth);
|
||||
scanLineWeight += CalcScanLineWeight(dy + filterWidth);
|
||||
scanLineWeight *= 0.3333333;
|
||||
#endif
|
||||
return scanLineWeight;
|
||||
}
|
||||
|
||||
fragment float4 crt_fragment_main(CRTVertexOut in [[stage_in]],
|
||||
texture2d<float> sceneTexture [[texture(0)]],
|
||||
sampler sceneSampler [[sampler(0)]]) {
|
||||
float2 TextureSize = float2(320.0, 240.0); // Resolución virtual del CRT
|
||||
float filterWidth = (768.0 / 240.0) / 3.0;
|
||||
|
||||
float2 texcoord = in.texCoord;
|
||||
|
||||
// Convertir a píxeles
|
||||
float2 texcoordInPixels = texcoord * TextureSize;
|
||||
|
||||
#ifdef SHARPER
|
||||
float2 tempCoord = floor(texcoordInPixels) + 0.5;
|
||||
float2 coord = tempCoord / TextureSize;
|
||||
float2 deltas = texcoordInPixels - tempCoord;
|
||||
float scanLineWeight = CalcScanLine(deltas.y, filterWidth);
|
||||
float2 signs = sign(deltas);
|
||||
deltas.x *= 2.0;
|
||||
deltas = deltas * deltas;
|
||||
deltas.y = deltas.y * deltas.y;
|
||||
deltas.x *= 0.5;
|
||||
deltas.y *= 8.0;
|
||||
deltas /= TextureSize;
|
||||
deltas *= signs;
|
||||
float2 tc = coord + deltas;
|
||||
#else
|
||||
float tempY = floor(texcoordInPixels.y) + 0.5;
|
||||
float yCoord = tempY / TextureSize.y;
|
||||
float dy = texcoordInPixels.y - tempY;
|
||||
float scanLineWeight = CalcScanLine(dy, filterWidth);
|
||||
float signY = sign(dy);
|
||||
dy = dy * dy;
|
||||
dy = dy * dy;
|
||||
dy *= 8.0;
|
||||
dy /= TextureSize.y;
|
||||
dy *= signY;
|
||||
float2 tc = float2(texcoord.x, yCoord + dy);
|
||||
#endif
|
||||
|
||||
// Samplear la textura de escena
|
||||
float3 colour = sceneTexture.sample(sceneSampler, tc).rgb;
|
||||
|
||||
#ifdef SCANLINES
|
||||
#ifdef GAMMA
|
||||
#ifdef FAKE_GAMMA
|
||||
colour = colour * colour;
|
||||
#else
|
||||
colour = pow(colour, float3(INPUT_GAMMA));
|
||||
#endif
|
||||
#endif
|
||||
scanLineWeight *= BLOOM_FACTOR;
|
||||
colour *= scanLineWeight;
|
||||
|
||||
#ifdef GAMMA
|
||||
#ifdef FAKE_GAMMA
|
||||
colour = sqrt(colour);
|
||||
#else
|
||||
colour = pow(colour, float3(1.0 / OUTPUT_GAMMA));
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Shadow mask
|
||||
#if MASK_TYPE == 0
|
||||
return float4(colour, 1.0);
|
||||
#else
|
||||
#if MASK_TYPE == 1
|
||||
float whichMask = fract((in.position.x * 1.0001) * 0.5);
|
||||
float3 mask;
|
||||
if (whichMask < 0.5) {
|
||||
mask = float3(MASK_BRIGHTNESS, 1.0, MASK_BRIGHTNESS);
|
||||
} else {
|
||||
mask = float3(1.0, MASK_BRIGHTNESS, 1.0);
|
||||
}
|
||||
#elif MASK_TYPE == 2
|
||||
float whichMask = fract((in.position.x * 1.0001) * 0.3333333);
|
||||
float3 mask = float3(MASK_BRIGHTNESS, MASK_BRIGHTNESS, MASK_BRIGHTNESS);
|
||||
if (whichMask < 0.3333333) {
|
||||
mask.x = 1.0;
|
||||
} else if (whichMask < 0.6666666) {
|
||||
mask.y = 1.0;
|
||||
} else {
|
||||
mask.z = 1.0;
|
||||
}
|
||||
#endif
|
||||
|
||||
return float4(colour * mask, 1.0);
|
||||
#endif
|
||||
}
|
||||
)";
|
||||
|
||||
// Compilar shaders CRT
|
||||
NSError* error = nil;
|
||||
id<MTLLibrary> crtVertexLibrary = [device newLibraryWithSource:crtVertexShaderSource options:nil error:&error];
|
||||
if (!crtVertexLibrary || error) {
|
||||
if (error) {
|
||||
std::cout << "Failed to compile CRT vertex shader: " << [[error localizedDescription] UTF8String] << std::endl;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
id<MTLLibrary> crtFragmentLibrary = [device newLibraryWithSource:crtFragmentShaderSource options:nil error:&error];
|
||||
if (!crtFragmentLibrary || error) {
|
||||
if (error) {
|
||||
std::cout << "Failed to compile CRT fragment shader: " << [[error localizedDescription] UTF8String] << std::endl;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
id<MTLFunction> crtVertexFunction = [crtVertexLibrary newFunctionWithName:@"crt_vertex_main"];
|
||||
id<MTLFunction> crtFragmentFunction = [crtFragmentLibrary newFunctionWithName:@"crt_fragment_main"];
|
||||
|
||||
// Compilar shaders de fondo
|
||||
id<MTLLibrary> backgroundVertexLibrary = [device newLibraryWithSource:backgroundVertexShaderSource options:nil error:&error];
|
||||
if (!backgroundVertexLibrary || error) {
|
||||
if (error) {
|
||||
@@ -348,14 +546,17 @@ fragment float4 sprite_fragment_main(SpriteVertexOut in [[stage_in]],
|
||||
spritePipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
|
||||
spritePipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
|
||||
|
||||
// Configurar vertex descriptor para sprites
|
||||
// Configurar vertex descriptor para sprites (position + color + texCoord)
|
||||
MTLVertexDescriptor* spriteVertexDescriptor = [[MTLVertexDescriptor alloc] init];
|
||||
spriteVertexDescriptor.attributes[0].format = MTLVertexFormatFloat2; // position
|
||||
spriteVertexDescriptor.attributes[0].offset = 0;
|
||||
spriteVertexDescriptor.attributes[0].bufferIndex = 0;
|
||||
spriteVertexDescriptor.attributes[1].format = MTLVertexFormatFloat2; // texCoord
|
||||
spriteVertexDescriptor.attributes[1].format = MTLVertexFormatFloat4; // color
|
||||
spriteVertexDescriptor.attributes[1].offset = 8;
|
||||
spriteVertexDescriptor.attributes[1].bufferIndex = 0;
|
||||
spriteVertexDescriptor.attributes[2].format = MTLVertexFormatFloat2; // texCoord
|
||||
spriteVertexDescriptor.attributes[2].offset = 24;
|
||||
spriteVertexDescriptor.attributes[2].bufferIndex = 0;
|
||||
spriteVertexDescriptor.layouts[0].stride = sizeof(SpriteVertex);
|
||||
spriteVertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
|
||||
|
||||
@@ -417,15 +618,57 @@ fragment float4 sprite_fragment_main(SpriteVertexOut in [[stage_in]],
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Crear vertex buffer para sprites
|
||||
id<MTLBuffer> spriteVertexBuffer = [device newBufferWithLength:1024 options:MTLResourceCPUCacheModeDefaultCache];
|
||||
// Crear vertex buffer para sprites (para 5 sprites = 30 vértices)
|
||||
id<MTLBuffer> spriteVertexBuffer = [device newBufferWithLength:4096 options:MTLResourceCPUCacheModeDefaultCache];
|
||||
if (!spriteVertexBuffer) {
|
||||
std::cout << "Failed to create sprite vertex buffer" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::cout << "Metal pipelines created successfully (background + triangle + sprites)" << std::endl;
|
||||
// Crear offscreen render target para CRT post-processing
|
||||
CGSize drawableSize = metalLayer.drawableSize;
|
||||
MTLTextureDescriptor* offscreenTextureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
|
||||
width:(NSUInteger)drawableSize.width
|
||||
height:(NSUInteger)drawableSize.height
|
||||
mipmapped:NO];
|
||||
offscreenTextureDescriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
|
||||
|
||||
id<MTLTexture> offscreenTexture = [device newTextureWithDescriptor:offscreenTextureDescriptor];
|
||||
if (!offscreenTexture) {
|
||||
std::cout << "Failed to create offscreen render target" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Crear pipeline CRT post-processing
|
||||
MTLRenderPipelineDescriptor* crtPipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
|
||||
crtPipelineDescriptor.vertexFunction = crtVertexFunction;
|
||||
crtPipelineDescriptor.fragmentFunction = crtFragmentFunction;
|
||||
crtPipelineDescriptor.colorAttachments[0].pixelFormat = metalLayer.pixelFormat;
|
||||
|
||||
id<MTLRenderPipelineState> crtPipelineState = [device newRenderPipelineStateWithDescriptor:crtPipelineDescriptor error:&error];
|
||||
if (!crtPipelineState || error) {
|
||||
if (error) {
|
||||
std::cout << "Failed to create CRT render pipeline: " << [[error localizedDescription] UTF8String] << std::endl;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Crear sampler para CRT (linear para suavizar la escalada)
|
||||
MTLSamplerDescriptor* crtSamplerDescriptor = [[MTLSamplerDescriptor alloc] init];
|
||||
crtSamplerDescriptor.minFilter = MTLSamplerMinMagFilterLinear;
|
||||
crtSamplerDescriptor.magFilter = MTLSamplerMinMagFilterLinear;
|
||||
crtSamplerDescriptor.sAddressMode = MTLSamplerAddressModeClampToEdge;
|
||||
crtSamplerDescriptor.tAddressMode = MTLSamplerAddressModeClampToEdge;
|
||||
|
||||
id<MTLSamplerState> crtSampler = [device newSamplerStateWithDescriptor:crtSamplerDescriptor];
|
||||
if (!crtSampler) {
|
||||
std::cout << "Failed to create CRT sampler" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::cout << "Metal pipelines created successfully (background + triangle + sprites + CRT)" << std::endl;
|
||||
std::cout << "Sprite texture loaded: " << [bitmap pixelsWide] << "x" << [bitmap pixelsHigh] << std::endl;
|
||||
std::cout << "Offscreen render target created: " << (int)drawableSize.width << "x" << (int)drawableSize.height << std::endl;
|
||||
|
||||
// Main loop
|
||||
bool quit = false;
|
||||
@@ -444,12 +687,12 @@ fragment float4 sprite_fragment_main(SpriteVertexOut in [[stage_in]],
|
||||
continue;
|
||||
}
|
||||
|
||||
// Crear render pass descriptor
|
||||
MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
|
||||
renderPassDescriptor.colorAttachments[0].texture = drawable.texture;
|
||||
renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
|
||||
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0);
|
||||
renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
|
||||
// PASO 1: Renderizar escena a offscreen texture
|
||||
MTLRenderPassDescriptor* offscreenRenderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
|
||||
offscreenRenderPassDescriptor.colorAttachments[0].texture = offscreenTexture;
|
||||
offscreenRenderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
|
||||
offscreenRenderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0);
|
||||
offscreenRenderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
|
||||
|
||||
// Crear command buffer
|
||||
id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
|
||||
@@ -457,58 +700,104 @@ fragment float4 sprite_fragment_main(SpriteVertexOut in [[stage_in]],
|
||||
continue;
|
||||
}
|
||||
|
||||
// Crear render command encoder
|
||||
id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
|
||||
if (!renderEncoder) {
|
||||
// Crear render command encoder para offscreen
|
||||
id<MTLRenderCommandEncoder> offscreenEncoder = [commandBuffer renderCommandEncoderWithDescriptor:offscreenRenderPassDescriptor];
|
||||
if (!offscreenEncoder) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 1. Dibujar fondo degradado primero
|
||||
[renderEncoder setRenderPipelineState:backgroundPipelineState];
|
||||
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6];
|
||||
[offscreenEncoder setRenderPipelineState:backgroundPipelineState];
|
||||
[offscreenEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6];
|
||||
|
||||
// 2. Dibujar triángulo encima
|
||||
[renderEncoder setRenderPipelineState:trianglePipelineState];
|
||||
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
|
||||
[offscreenEncoder setRenderPipelineState:trianglePipelineState];
|
||||
[offscreenEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
|
||||
|
||||
// 3. Dibujar sprite con alpha blending
|
||||
[renderEncoder setRenderPipelineState:spritePipelineState];
|
||||
// 3. Dibujar 5 sprites con diferentes colores
|
||||
[offscreenEncoder setRenderPipelineState:spritePipelineState];
|
||||
|
||||
// Configurar textura y sampler
|
||||
[renderEncoder setFragmentTexture:spriteTexture atIndex:0];
|
||||
[renderEncoder setFragmentSamplerState:textureSampler atIndex:0];
|
||||
// Configurar textura y sampler para sprites
|
||||
[offscreenEncoder setFragmentTexture:spriteTexture atIndex:0];
|
||||
[offscreenEncoder setFragmentSamplerState:textureSampler atIndex:0];
|
||||
|
||||
// Crear sprite centrado con un tamaño pequeño
|
||||
SpriteVertex spriteVertices[6]; // 2 triángulos para formar un quad
|
||||
// Crear 5 sprites en diferentes posiciones con diferentes colores
|
||||
SpriteVertex spriteVertices[30]; // 5 sprites × 6 vértices cada uno
|
||||
|
||||
// Sprite centrado de 30x30 pixels en pantalla de 960x720 (mantiene proporción de 10px en 320x240)
|
||||
// Configuración común
|
||||
float spriteSize = 30.0f;
|
||||
float windowWidth = 960.0f;
|
||||
float windowHeight = 720.0f;
|
||||
float halfWidth = (spriteSize / windowWidth);
|
||||
float halfHeight = (spriteSize / windowHeight);
|
||||
|
||||
// Convertir a coordenadas NDC
|
||||
float halfWidth = (spriteSize / windowWidth); // 0.1 en NDC
|
||||
float halfHeight = (spriteSize / windowHeight); // 0.133 en NDC
|
||||
// Posiciones de los 5 sprites (evitando el centro donde está el triángulo)
|
||||
float positions[5][2] = {
|
||||
{-0.6f, 0.6f}, // Esquina superior izquierda
|
||||
{ 0.6f, 0.6f}, // Esquina superior derecha
|
||||
{-0.6f, -0.6f}, // Esquina inferior izquierda
|
||||
{ 0.6f, -0.6f}, // Esquina inferior derecha
|
||||
{ 0.0f, -0.8f} // Centro inferior
|
||||
};
|
||||
|
||||
// Primer triángulo (bottom-left, bottom-right, top-left)
|
||||
spriteVertices[0] = {{-halfWidth, -halfHeight}, {0.0f, 1.0f}}; // bottom-left
|
||||
spriteVertices[1] = {{ halfWidth, -halfHeight}, {1.0f, 1.0f}}; // bottom-right
|
||||
spriteVertices[2] = {{-halfWidth, halfHeight}, {0.0f, 0.0f}}; // top-left
|
||||
// Colores para cada sprite (RGBA)
|
||||
float colors[5][4] = {
|
||||
{1.0f, 0.0f, 0.0f, 1.0f}, // Rojo
|
||||
{0.0f, 1.0f, 0.0f, 1.0f}, // Verde
|
||||
{0.0f, 0.0f, 1.0f, 1.0f}, // Azul
|
||||
{1.0f, 1.0f, 0.0f, 1.0f}, // Amarillo
|
||||
{1.0f, 0.0f, 1.0f, 1.0f} // Magenta
|
||||
};
|
||||
|
||||
// Segundo triángulo (bottom-right, top-right, top-left)
|
||||
spriteVertices[3] = {{ halfWidth, -halfHeight}, {1.0f, 1.0f}}; // bottom-right
|
||||
spriteVertices[4] = {{ halfWidth, halfHeight}, {1.0f, 0.0f}}; // top-right
|
||||
spriteVertices[5] = {{-halfWidth, halfHeight}, {0.0f, 0.0f}}; // top-left
|
||||
// Generar vértices para los 5 sprites
|
||||
for (int i = 0; i < 5; i++) {
|
||||
float centerX = positions[i][0];
|
||||
float centerY = positions[i][1];
|
||||
|
||||
// Índice base para este sprite
|
||||
int baseIndex = i * 6;
|
||||
|
||||
// Primer triángulo (bottom-left, bottom-right, top-left)
|
||||
spriteVertices[baseIndex + 0] = {{centerX - halfWidth, centerY - halfHeight}, {colors[i][0], colors[i][1], colors[i][2], colors[i][3]}, {0.0f, 1.0f}};
|
||||
spriteVertices[baseIndex + 1] = {{centerX + halfWidth, centerY - halfHeight}, {colors[i][0], colors[i][1], colors[i][2], colors[i][3]}, {1.0f, 1.0f}};
|
||||
spriteVertices[baseIndex + 2] = {{centerX - halfWidth, centerY + halfHeight}, {colors[i][0], colors[i][1], colors[i][2], colors[i][3]}, {0.0f, 0.0f}};
|
||||
|
||||
// Segundo triángulo (bottom-right, top-right, top-left)
|
||||
spriteVertices[baseIndex + 3] = {{centerX + halfWidth, centerY - halfHeight}, {colors[i][0], colors[i][1], colors[i][2], colors[i][3]}, {1.0f, 1.0f}};
|
||||
spriteVertices[baseIndex + 4] = {{centerX + halfWidth, centerY + halfHeight}, {colors[i][0], colors[i][1], colors[i][2], colors[i][3]}, {1.0f, 0.0f}};
|
||||
spriteVertices[baseIndex + 5] = {{centerX - halfWidth, centerY + halfHeight}, {colors[i][0], colors[i][1], colors[i][2], colors[i][3]}, {0.0f, 0.0f}};
|
||||
}
|
||||
|
||||
// Copiar vértices al buffer
|
||||
void* spriteData = [spriteVertexBuffer contents];
|
||||
memcpy(spriteData, spriteVertices, sizeof(spriteVertices));
|
||||
|
||||
// Configurar vertex buffer y dibujar
|
||||
[renderEncoder setVertexBuffer:spriteVertexBuffer offset:0 atIndex:0];
|
||||
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6];
|
||||
// Configurar vertex buffer y dibujar todos los sprites en offscreen
|
||||
[offscreenEncoder setVertexBuffer:spriteVertexBuffer offset:0 atIndex:0];
|
||||
[offscreenEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:30];
|
||||
|
||||
[renderEncoder endEncoding];
|
||||
[offscreenEncoder endEncoding];
|
||||
|
||||
// PASO 2: Aplicar CRT post-processing a la pantalla final
|
||||
MTLRenderPassDescriptor* finalRenderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
|
||||
finalRenderPassDescriptor.colorAttachments[0].texture = drawable.texture;
|
||||
finalRenderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
|
||||
finalRenderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0);
|
||||
finalRenderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
|
||||
|
||||
// Crear encoder para CRT post-processing
|
||||
id<MTLRenderCommandEncoder> crtEncoder = [commandBuffer renderCommandEncoderWithDescriptor:finalRenderPassDescriptor];
|
||||
if (!crtEncoder) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Aplicar CRT shader al resultado offscreen
|
||||
[crtEncoder setRenderPipelineState:crtPipelineState];
|
||||
[crtEncoder setFragmentTexture:offscreenTexture atIndex:0];
|
||||
[crtEncoder setFragmentSamplerState:crtSampler atIndex:0];
|
||||
[crtEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6];
|
||||
|
||||
[crtEncoder endEncoding];
|
||||
|
||||
// Presentar drawable
|
||||
[commandBuffer presentDrawable:drawable];
|
||||
|
||||
Reference in New Issue
Block a user