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:
234
data/crtpi_240.glsl
Normal file
234
data/crtpi_240.glsl
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
/*
|
||||||
|
crt-pi - A Raspberry Pi friendly CRT shader.
|
||||||
|
|
||||||
|
Copyright (C) 2015-2016 davej
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify it
|
||||||
|
under the terms of the GNU General Public License as published by the Free
|
||||||
|
Software Foundation; either version 2 of the License, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
This shader is designed to work well on Raspberry Pi GPUs (i.e. 1080P @ 60Hz on a game with a 4:3 aspect ratio). It pushes the Pi's GPU hard and enabling some features will slow it down so that it is no longer able to match 1080P @ 60Hz. You will need to overclock your Pi to the fastest setting in raspi-config to get the best results from this shader: 'Pi2' for Pi2 and 'Turbo' for original Pi and Pi Zero. Note: Pi2s are slower at running the shader than other Pis, this seems to be down to Pi2s lower maximum memory speed. Pi2s don't quite manage 1080P @ 60Hz - they drop about 1 in 1000 frames. You probably won't notice this, but if you do, try enabling FAKE_GAMMA.
|
||||||
|
|
||||||
|
SCANLINES enables scanlines. You'll almost certainly want to use it with MULTISAMPLE to reduce moire effects. SCANLINE_WEIGHT defines how wide scanlines are (it is an inverse value so a higher number = thinner lines). SCANLINE_GAP_BRIGHTNESS defines how dark the gaps between the scan lines are. Darker gaps between scan lines make moire effects more likely.
|
||||||
|
|
||||||
|
GAMMA enables gamma correction using the values in INPUT_GAMMA and OUTPUT_GAMMA. FAKE_GAMMA causes it to ignore the values in INPUT_GAMMA and OUTPUT_GAMMA and approximate gamma correction in a way which is faster than true gamma whilst still looking better than having none. You must have GAMMA defined to enable FAKE_GAMMA.
|
||||||
|
|
||||||
|
CURVATURE distorts the screen by CURVATURE_X and CURVATURE_Y. Curvature slows things down a lot.
|
||||||
|
|
||||||
|
By default the shader uses linear blending horizontally. If you find this too blury, enable SHARPER.
|
||||||
|
|
||||||
|
BLOOM_FACTOR controls the increase in width for bright scanlines.
|
||||||
|
|
||||||
|
MASK_TYPE defines what, if any, shadow mask to use. MASK_BRIGHTNESS defines how much the mask type darkens the screen.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma parameter CURVATURE_X "Screen curvature - horizontal" 0.10 0.0 1.0 0.01
|
||||||
|
#pragma parameter CURVATURE_Y "Screen curvature - vertical" 0.15 0.0 1.0 0.01
|
||||||
|
#pragma parameter MASK_BRIGHTNESS "Mask brightness" 0.70 0.0 1.0 0.01
|
||||||
|
#pragma parameter SCANLINE_WEIGHT "Scanline weight" 6.0 0.0 15.0 0.1
|
||||||
|
#pragma parameter SCANLINE_GAP_BRIGHTNESS "Scanline gap brightness" 0.12 0.0 1.0 0.01
|
||||||
|
#pragma parameter BLOOM_FACTOR "Bloom factor" 1.5 0.0 5.0 0.01
|
||||||
|
#pragma parameter INPUT_GAMMA "Input gamma" 2.4 0.0 5.0 0.01
|
||||||
|
#pragma parameter OUTPUT_GAMMA "Output gamma" 2.2 0.0 5.0 0.01
|
||||||
|
|
||||||
|
// Haven't put these as parameters as it would slow the code down.
|
||||||
|
#define SCANLINES
|
||||||
|
#define MULTISAMPLE
|
||||||
|
#define GAMMA
|
||||||
|
//#define FAKE_GAMMA
|
||||||
|
//#define CURVATURE
|
||||||
|
//#define SHARPER
|
||||||
|
// MASK_TYPE: 0 = none, 1 = green/magenta, 2 = trinitron(ish)
|
||||||
|
#define MASK_TYPE 2
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef GL_ES
|
||||||
|
#define COMPAT_PRECISION mediump
|
||||||
|
precision mediump float;
|
||||||
|
#else
|
||||||
|
#define COMPAT_PRECISION
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef PARAMETER_UNIFORM
|
||||||
|
uniform COMPAT_PRECISION float CURVATURE_X;
|
||||||
|
uniform COMPAT_PRECISION float CURVATURE_Y;
|
||||||
|
uniform COMPAT_PRECISION float MASK_BRIGHTNESS;
|
||||||
|
uniform COMPAT_PRECISION float SCANLINE_WEIGHT;
|
||||||
|
uniform COMPAT_PRECISION float SCANLINE_GAP_BRIGHTNESS;
|
||||||
|
uniform COMPAT_PRECISION float BLOOM_FACTOR;
|
||||||
|
uniform COMPAT_PRECISION float INPUT_GAMMA;
|
||||||
|
uniform COMPAT_PRECISION float OUTPUT_GAMMA;
|
||||||
|
#else
|
||||||
|
#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
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* COMPATIBILITY
|
||||||
|
- GLSL compilers
|
||||||
|
*/
|
||||||
|
|
||||||
|
//uniform vec2 TextureSize;
|
||||||
|
#if defined(CURVATURE)
|
||||||
|
varying vec2 screenScale;
|
||||||
|
#endif
|
||||||
|
varying vec2 TEX0;
|
||||||
|
varying float filterWidth;
|
||||||
|
|
||||||
|
#if defined(VERTEX)
|
||||||
|
//uniform mat4 MVPMatrix;
|
||||||
|
//attribute vec4 VertexCoord;
|
||||||
|
//attribute vec2 TexCoord;
|
||||||
|
//uniform vec2 InputSize;
|
||||||
|
//uniform vec2 OutputSize;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
#if defined(CURVATURE)
|
||||||
|
screenScale = vec2(1.0, 1.0); //TextureSize / InputSize;
|
||||||
|
#endif
|
||||||
|
filterWidth = (768.0 / 240.0) / 3.0;
|
||||||
|
TEX0 = vec2(gl_MultiTexCoord0.x, 1.0-gl_MultiTexCoord0.y)*1.0001;
|
||||||
|
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
|
||||||
|
}
|
||||||
|
#elif defined(FRAGMENT)
|
||||||
|
|
||||||
|
uniform sampler2D Texture;
|
||||||
|
|
||||||
|
#if defined(CURVATURE)
|
||||||
|
vec2 Distort(vec2 coord)
|
||||||
|
{
|
||||||
|
vec2 CURVATURE_DISTORTION = vec2(CURVATURE_X, CURVATURE_Y);
|
||||||
|
// Barrel distortion shrinks the display area a bit, this will allow us to counteract that.
|
||||||
|
vec2 barrelScale = 1.0 - (0.23 * CURVATURE_DISTORTION);
|
||||||
|
coord *= screenScale;
|
||||||
|
coord -= vec2(0.5);
|
||||||
|
float rsq = coord.x * coord.x + coord.y * coord.y;
|
||||||
|
coord += coord * (CURVATURE_DISTORTION * rsq);
|
||||||
|
coord *= barrelScale;
|
||||||
|
if (abs(coord.x) >= 0.5 || abs(coord.y) >= 0.5)
|
||||||
|
coord = vec2(-1.0); // If out of bounds, return an invalid value.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
coord += vec2(0.5);
|
||||||
|
coord /= screenScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
return coord;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
float CalcScanLineWeight(float dist)
|
||||||
|
{
|
||||||
|
return max(1.0-dist*dist*SCANLINE_WEIGHT, SCANLINE_GAP_BRIGHTNESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
float CalcScanLine(float dy)
|
||||||
|
{
|
||||||
|
float scanLineWeight = CalcScanLineWeight(dy);
|
||||||
|
#if defined(MULTISAMPLE)
|
||||||
|
scanLineWeight += CalcScanLineWeight(dy-filterWidth);
|
||||||
|
scanLineWeight += CalcScanLineWeight(dy+filterWidth);
|
||||||
|
scanLineWeight *= 0.3333333;
|
||||||
|
#endif
|
||||||
|
return scanLineWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec2 TextureSize = vec2(320.0, 240.0);
|
||||||
|
#if defined(CURVATURE)
|
||||||
|
vec2 texcoord = Distort(TEX0);
|
||||||
|
if (texcoord.x < 0.0)
|
||||||
|
gl_FragColor = vec4(0.0);
|
||||||
|
else
|
||||||
|
#else
|
||||||
|
vec2 texcoord = TEX0;
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
vec2 texcoordInPixels = texcoord * TextureSize;
|
||||||
|
#if defined(SHARPER)
|
||||||
|
vec2 tempCoord = floor(texcoordInPixels) + 0.5;
|
||||||
|
vec2 coord = tempCoord / TextureSize;
|
||||||
|
vec2 deltas = texcoordInPixels - tempCoord;
|
||||||
|
float scanLineWeight = CalcScanLine(deltas.y);
|
||||||
|
vec2 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;
|
||||||
|
vec2 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);
|
||||||
|
float signY = sign(dy);
|
||||||
|
dy = dy * dy;
|
||||||
|
dy = dy * dy;
|
||||||
|
dy *= 8.0;
|
||||||
|
dy /= TextureSize.y;
|
||||||
|
dy *= signY;
|
||||||
|
vec2 tc = vec2(texcoord.x, yCoord + dy);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
vec3 colour = texture2D(Texture, tc).rgb;
|
||||||
|
|
||||||
|
#if defined(SCANLINES)
|
||||||
|
#if defined(GAMMA)
|
||||||
|
#if defined(FAKE_GAMMA)
|
||||||
|
colour = colour * colour;
|
||||||
|
#else
|
||||||
|
colour = pow(colour, vec3(INPUT_GAMMA));
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
scanLineWeight *= BLOOM_FACTOR;
|
||||||
|
colour *= scanLineWeight;
|
||||||
|
|
||||||
|
#if defined(GAMMA)
|
||||||
|
#if defined(FAKE_GAMMA)
|
||||||
|
colour = sqrt(colour);
|
||||||
|
#else
|
||||||
|
colour = pow(colour, vec3(1.0/OUTPUT_GAMMA));
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#if MASK_TYPE == 0
|
||||||
|
gl_FragColor = vec4(colour, 1.0);
|
||||||
|
#else
|
||||||
|
#if MASK_TYPE == 1
|
||||||
|
float whichMask = fract((gl_FragCoord.x*1.0001) * 0.5);
|
||||||
|
vec3 mask;
|
||||||
|
if (whichMask < 0.5)
|
||||||
|
mask = vec3(MASK_BRIGHTNESS, 1.0, MASK_BRIGHTNESS);
|
||||||
|
else
|
||||||
|
mask = vec3(1.0, MASK_BRIGHTNESS, 1.0);
|
||||||
|
#elif MASK_TYPE == 2
|
||||||
|
float whichMask = fract((gl_FragCoord.x*1.0001) * 0.3333333);
|
||||||
|
vec3 mask = vec3(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
|
||||||
|
|
||||||
|
gl_FragColor = vec4(colour * mask, 1.0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
379
source/main.cpp
379
source/main.cpp
@@ -38,6 +38,7 @@
|
|||||||
// Estructura para vértices de sprites
|
// Estructura para vértices de sprites
|
||||||
struct SpriteVertex {
|
struct SpriteVertex {
|
||||||
float position[2]; // x, y coordinates
|
float position[2]; // x, y coordinates
|
||||||
|
float color[4]; // r, g, b, a color components
|
||||||
float texCoord[2]; // u, v texture coordinates
|
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"(
|
NSString* spriteVertexShaderSource = @R"(
|
||||||
#include <metal_stdlib>
|
#include <metal_stdlib>
|
||||||
using namespace metal;
|
using namespace metal;
|
||||||
|
|
||||||
struct SpriteVertexIn {
|
struct SpriteVertexIn {
|
||||||
float2 position [[attribute(0)]];
|
float2 position [[attribute(0)]];
|
||||||
float2 texCoord [[attribute(1)]];
|
float4 color [[attribute(1)]];
|
||||||
|
float2 texCoord [[attribute(2)]];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SpriteVertexOut {
|
struct SpriteVertexOut {
|
||||||
float4 position [[position]];
|
float4 position [[position]];
|
||||||
|
float4 color;
|
||||||
float2 texCoord;
|
float2 texCoord;
|
||||||
};
|
};
|
||||||
|
|
||||||
vertex SpriteVertexOut sprite_vertex_main(SpriteVertexIn in [[stage_in]]) {
|
vertex SpriteVertexOut sprite_vertex_main(SpriteVertexIn in [[stage_in]]) {
|
||||||
SpriteVertexOut out;
|
SpriteVertexOut out;
|
||||||
out.position = float4(in.position, 0.0, 1.0);
|
out.position = float4(in.position, 0.0, 1.0);
|
||||||
|
out.color = in.color;
|
||||||
out.texCoord = in.texCoord;
|
out.texCoord = in.texCoord;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@@ -233,6 +237,7 @@ using namespace metal;
|
|||||||
|
|
||||||
struct SpriteVertexOut {
|
struct SpriteVertexOut {
|
||||||
float4 position [[position]];
|
float4 position [[position]];
|
||||||
|
float4 color;
|
||||||
float2 texCoord;
|
float2 texCoord;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -240,12 +245,205 @@ fragment float4 sprite_fragment_main(SpriteVertexOut in [[stage_in]],
|
|||||||
texture2d<float> spriteTexture [[texture(0)]],
|
texture2d<float> spriteTexture [[texture(0)]],
|
||||||
sampler textureSampler [[sampler(0)]]) {
|
sampler textureSampler [[sampler(0)]]) {
|
||||||
float4 textureColor = spriteTexture.sample(textureSampler, in.texCoord);
|
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;
|
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];
|
id<MTLLibrary> backgroundVertexLibrary = [device newLibraryWithSource:backgroundVertexShaderSource options:nil error:&error];
|
||||||
if (!backgroundVertexLibrary || error) {
|
if (!backgroundVertexLibrary || error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -348,14 +546,17 @@ fragment float4 sprite_fragment_main(SpriteVertexOut in [[stage_in]],
|
|||||||
spritePipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
|
spritePipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
|
||||||
spritePipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
|
spritePipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
|
||||||
|
|
||||||
// Configurar vertex descriptor para sprites
|
// Configurar vertex descriptor para sprites (position + color + texCoord)
|
||||||
MTLVertexDescriptor* spriteVertexDescriptor = [[MTLVertexDescriptor alloc] init];
|
MTLVertexDescriptor* spriteVertexDescriptor = [[MTLVertexDescriptor alloc] init];
|
||||||
spriteVertexDescriptor.attributes[0].format = MTLVertexFormatFloat2; // position
|
spriteVertexDescriptor.attributes[0].format = MTLVertexFormatFloat2; // position
|
||||||
spriteVertexDescriptor.attributes[0].offset = 0;
|
spriteVertexDescriptor.attributes[0].offset = 0;
|
||||||
spriteVertexDescriptor.attributes[0].bufferIndex = 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].offset = 8;
|
||||||
spriteVertexDescriptor.attributes[1].bufferIndex = 0;
|
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].stride = sizeof(SpriteVertex);
|
||||||
spriteVertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
|
spriteVertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
|
||||||
|
|
||||||
@@ -417,15 +618,57 @@ fragment float4 sprite_fragment_main(SpriteVertexOut in [[stage_in]],
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crear vertex buffer para sprites
|
// Crear vertex buffer para sprites (para 5 sprites = 30 vértices)
|
||||||
id<MTLBuffer> spriteVertexBuffer = [device newBufferWithLength:1024 options:MTLResourceCPUCacheModeDefaultCache];
|
id<MTLBuffer> spriteVertexBuffer = [device newBufferWithLength:4096 options:MTLResourceCPUCacheModeDefaultCache];
|
||||||
if (!spriteVertexBuffer) {
|
if (!spriteVertexBuffer) {
|
||||||
std::cout << "Failed to create sprite vertex buffer" << std::endl;
|
std::cout << "Failed to create sprite vertex buffer" << std::endl;
|
||||||
return -1;
|
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 << "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
|
// Main loop
|
||||||
bool quit = false;
|
bool quit = false;
|
||||||
@@ -444,12 +687,12 @@ fragment float4 sprite_fragment_main(SpriteVertexOut in [[stage_in]],
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crear render pass descriptor
|
// PASO 1: Renderizar escena a offscreen texture
|
||||||
MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
|
MTLRenderPassDescriptor* offscreenRenderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
|
||||||
renderPassDescriptor.colorAttachments[0].texture = drawable.texture;
|
offscreenRenderPassDescriptor.colorAttachments[0].texture = offscreenTexture;
|
||||||
renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
|
offscreenRenderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
|
||||||
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0);
|
offscreenRenderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0);
|
||||||
renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
|
offscreenRenderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
|
||||||
|
|
||||||
// Crear command buffer
|
// Crear command buffer
|
||||||
id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
|
id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
|
||||||
@@ -457,58 +700,104 @@ fragment float4 sprite_fragment_main(SpriteVertexOut in [[stage_in]],
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crear render command encoder
|
// Crear render command encoder para offscreen
|
||||||
id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
|
id<MTLRenderCommandEncoder> offscreenEncoder = [commandBuffer renderCommandEncoderWithDescriptor:offscreenRenderPassDescriptor];
|
||||||
if (!renderEncoder) {
|
if (!offscreenEncoder) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Dibujar fondo degradado primero
|
// 1. Dibujar fondo degradado primero
|
||||||
[renderEncoder setRenderPipelineState:backgroundPipelineState];
|
[offscreenEncoder setRenderPipelineState:backgroundPipelineState];
|
||||||
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6];
|
[offscreenEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6];
|
||||||
|
|
||||||
// 2. Dibujar triángulo encima
|
// 2. Dibujar triángulo encima
|
||||||
[renderEncoder setRenderPipelineState:trianglePipelineState];
|
[offscreenEncoder setRenderPipelineState:trianglePipelineState];
|
||||||
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
|
[offscreenEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
|
||||||
|
|
||||||
// 3. Dibujar sprite con alpha blending
|
// 3. Dibujar 5 sprites con diferentes colores
|
||||||
[renderEncoder setRenderPipelineState:spritePipelineState];
|
[offscreenEncoder setRenderPipelineState:spritePipelineState];
|
||||||
|
|
||||||
// Configurar textura y sampler
|
// Configurar textura y sampler para sprites
|
||||||
[renderEncoder setFragmentTexture:spriteTexture atIndex:0];
|
[offscreenEncoder setFragmentTexture:spriteTexture atIndex:0];
|
||||||
[renderEncoder setFragmentSamplerState:textureSampler atIndex:0];
|
[offscreenEncoder setFragmentSamplerState:textureSampler atIndex:0];
|
||||||
|
|
||||||
// Crear sprite centrado con un tamaño pequeño
|
// Crear 5 sprites en diferentes posiciones con diferentes colores
|
||||||
SpriteVertex spriteVertices[6]; // 2 triángulos para formar un quad
|
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 spriteSize = 30.0f;
|
||||||
float windowWidth = 960.0f;
|
float windowWidth = 960.0f;
|
||||||
float windowHeight = 720.0f;
|
float windowHeight = 720.0f;
|
||||||
|
float halfWidth = (spriteSize / windowWidth);
|
||||||
|
float halfHeight = (spriteSize / windowHeight);
|
||||||
|
|
||||||
// Convertir a coordenadas NDC
|
// Posiciones de los 5 sprites (evitando el centro donde está el triángulo)
|
||||||
float halfWidth = (spriteSize / windowWidth); // 0.1 en NDC
|
float positions[5][2] = {
|
||||||
float halfHeight = (spriteSize / windowHeight); // 0.133 en NDC
|
{-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)
|
// Colores para cada sprite (RGBA)
|
||||||
spriteVertices[0] = {{-halfWidth, -halfHeight}, {0.0f, 1.0f}}; // bottom-left
|
float colors[5][4] = {
|
||||||
spriteVertices[1] = {{ halfWidth, -halfHeight}, {1.0f, 1.0f}}; // bottom-right
|
{1.0f, 0.0f, 0.0f, 1.0f}, // Rojo
|
||||||
spriteVertices[2] = {{-halfWidth, halfHeight}, {0.0f, 0.0f}}; // top-left
|
{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)
|
// Generar vértices para los 5 sprites
|
||||||
spriteVertices[3] = {{ halfWidth, -halfHeight}, {1.0f, 1.0f}}; // bottom-right
|
for (int i = 0; i < 5; i++) {
|
||||||
spriteVertices[4] = {{ halfWidth, halfHeight}, {1.0f, 0.0f}}; // top-right
|
float centerX = positions[i][0];
|
||||||
spriteVertices[5] = {{-halfWidth, halfHeight}, {0.0f, 0.0f}}; // top-left
|
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
|
// Copiar vértices al buffer
|
||||||
void* spriteData = [spriteVertexBuffer contents];
|
void* spriteData = [spriteVertexBuffer contents];
|
||||||
memcpy(spriteData, spriteVertices, sizeof(spriteVertices));
|
memcpy(spriteData, spriteVertices, sizeof(spriteVertices));
|
||||||
|
|
||||||
// Configurar vertex buffer y dibujar
|
// Configurar vertex buffer y dibujar todos los sprites en offscreen
|
||||||
[renderEncoder setVertexBuffer:spriteVertexBuffer offset:0 atIndex:0];
|
[offscreenEncoder setVertexBuffer:spriteVertexBuffer offset:0 atIndex:0];
|
||||||
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6];
|
[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
|
// Presentar drawable
|
||||||
[commandBuffer presentDrawable:drawable];
|
[commandBuffer presentDrawable:drawable];
|
||||||
|
|||||||
Reference in New Issue
Block a user