![]() |
Eigener Firemonkey TEffect Shader
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo,
ich versuche gerade für ein Open Source Projekt einen Effektfilter zu schreiben und Delphi schmeisst da ein paar kleinere Steine in den Weg. Das kleinere Problem ist, die Anzeige unter Windows zu Testzwecken auf DX9 zu stellen. Dafür musste ich in FMX.Context.DX11 die RegisterContext Funktion deaktivieren. Da es nur zum Testen des Shaders ist, ist das okay. Leider gibt es keinen anderen Weg das zu realisieren, denn GlobalUseDXInDX9Mode ist dafür nicht angedacht. Mit der Hilfe von Zudomon habe ich gestern einen Pixelshader für ps_2_a (DX9) erstellt.
Code:
Dieser basiert auf Lottes GLSL CRT Pixelfilter und die Idee ist dabei eine CRT Anzeige zu simulieren. Das ganze funktioniert zumindest mit DX9 recht gut. Ich kann die Werte für den Shader übergeben und er stellt das ganze auch (siehe Anhang) korrektet dar.
sampler s0 : register(s0);
float4 p0 : register(c0); float4 p1 : register(c1); #define screen_size float2(p0[0],p0[1]) #define hardScan float(p0[2]) #define hardPix float(p0[3]) #define warp float2(p1[0], p1[1]) // Amount of shadow mask. #define maskDark float (p1[2]) #define maskLight float (p1[3]) // sRGB to Linear. // Assuing using sRGB typed textures this should not be needed. float ToLinear1(float c){return(c<=0.04045)?c/12.92:pow(abs((c+0.055)/1.055),2.4);} float3 ToLinear(float3 c){return float3(ToLinear1(c.r),ToLinear1(c.g),ToLinear1(c.b));} // Linear to sRGB. // Assuing using sRGB typed textures this should not be needed. float ToSrgb1(float c){return(c<0.0031308?c*12.92:1.055*pow(abs(c),0.41666)-0.055);} float3 ToSrgb(float3 c){return float3(ToSrgb1(c.r),ToSrgb1(c.g),ToSrgb1(c.b));} // Nearest emulated sample given floating point position and texel offset. // Also zero's off screen. float3 Fetch(float2 pos,float2 off){ //pos=(floor(pos*screen_size+off)+float2(0.5,0.5))/screen_size; if (max(abs(pos.x-0.5), abs(pos.y-0.5)) > 0.5) { return float3(0.0, 0.0, 0.0);} return ToLinear(1.2 * tex2D(s0,pos).rgb);} // Distance in emulated pixels to nearest texel. float2 Dist(float2 pos){return -((pos-floor(pos))-float2(0.5, 0.5));} // 1D Gaussian. float Gaus(float pos,float scale){return exp2(scale*pos*pos);} // 3-tap Gaussian filter along horz line. // 3-tap Gaussian filter along horz line. float3 Horz3(float2 pos,float off){ float3 b=Fetch(pos,float2(-1.0,off)); float3 c=Fetch(pos,float2( 0.0,off)); float3 d=Fetch(pos,float2( 1.0,off)); float dst=Dist(pos).x; // Convert distance to weight. float scale=hardPix; float wb=Gaus(dst-1.0,scale); float wc=Gaus(dst+0.0,scale); float wd=Gaus(dst+1.0,scale); // Return filtered sample. return (b*wb+c*wc+d*wd)/(wb+wc+wd);} // 5-tap Gaussian filter along horz line. float3 Horz5(float2 pos,float off){ float3 a=Fetch(pos,float2(-2.0,off)); float3 b=Fetch(pos,float2(-1.0,off)); float3 c=Fetch(pos,float2( 0.0,off)); float3 d=Fetch(pos,float2( 1.0,off)); float3 e=Fetch(pos,float2( 2.0,off)); float dst=Dist(pos).x; // Convert distance to weight. float scale=hardPix; float wa=Gaus(dst-2.0,scale); float wb=Gaus(dst-1.0,scale); float wc=Gaus(dst+0.0,scale); float wd=Gaus(dst+1.0,scale); float we=Gaus(dst+2.0,scale); // Return filtered sample. return (a*wa+b*wb+c*wc+d*wd+e*we)/(wa+wb+wc+wd+we);} // Return scanline weight. float Scan(float2 pos,float off){ float dst=Dist(pos).y; return Gaus(dst+off,hardScan);} // Allow nearest three lines to effect pixel. float3 Tri(float2 pos){ float3 a=Horz3(pos,-1.0); float3 b=Horz5(pos, 0.0); float3 c=Horz3(pos, 1.0); float wa=Scan(pos,-1.0); float wb=Scan(pos, 0.0); float wc=Scan(pos, 1.0); return a*wa+b*wb+c*wc;} // Distortion of scanlines, and end of screen alpha. float2 Warp(float2 pos){ pos=pos*2.0-1.0; pos*=float2(1.0+(pos.y*pos.y)*warp.x,1.0+(pos.x*pos.x)*warp.y); return pos*0.5+0.5;} // Shadow mask. float3 Mask(float2 pos){ pos.x+=pos.y*3.0; float3 mask=float3(maskDark,maskDark,maskDark); pos.x=frac(pos.x/6.0); if(pos.x<0.333)mask.r=maskLight; else if(pos.x<0.666)mask.g=maskLight; else mask.b=maskLight; return mask; } float4 main(float2 tex : TEXCOORD0) : COLOR { float2 pos = tex*screen_size; tex = Warp(tex); float4 color; color.rgb = ToSrgb(Tri(tex)*Mask(pos)); return float4(color.r, color.g, color.b, 1); }
Delphi-Quellcode:
Das Problem welches ich jetzt habe ist den DX9 Shader auf DX11 zu portieren. Hat das jemand schon einmal gemacht?
procedure TCrtFilter.LoadShaders;
begin TFilterManager.FilterContext.SetShaders(FVertexShader, FShaders[FPass]); TFilterManager.FilterContext.SetShaderVariable('C0', [Vector3D(InputSize.Width, InputSize.Height, ValuesAsFloat['HardScan'], ValuesAsFloat['HardPix'])]); TFilterManager.FilterContext.SetShaderVariable('C1', [Vector3D(ValuesAsFloat['WarpX'], ValuesAsFloat['WarpY'], ValuesAsFloat['MaskDark'], ValuesAsFloat['MaskLight'])]); end; class function TCrtFilter.FilterAttr: TFilterRec; begin Result := TFilterRec.Create('CrtFilter', 'An effect that simulates a CRT Monitor.', [TFilterValueRec.Create('HardScan', 'Hardness of scanline.', TFilterValueType.Float, -12, -8, -16), TFilterValueRec.Create('HardPix', 'Hardness of pixels in scanline.', TFilterValueType.Float, -3, -2, -4), TFilterValueRec.Create('WarpX', 'Display Warp horizontal.', TFilterValueType.Float, 1.0 / 40.0, 0, -1 / 8), TFilterValueRec.Create('WarpY', 'Display Warp vertical.', TFilterValueType.Float, 1.0 / 44.0, 0, -1 / 8), TFilterValueRec.Create('MaskDark', 'Amount of minimum darkness shadow.', TFilterValueType.Float, 0.5, -10, 10), TFilterValueRec.Create('MaskLight', 'Amount of minimum lightness shadow.', TFilterValueType.Float, 1.5, -10, 10)]); end; |
AW: Eigener Firemonkey TEffect Shader
PDF
![]() |
AW: Eigener Firemonkey TEffect Shader
Danke,
das hatte ich in der Form auch schon:
Code:
Nachtrag: Der Shader oben scheint zu gehen, aber die Werte in dem Register sind alle 0.
Texture2D GTexture : register(t0);
SamplerState s0 : register(s0); float4 p0 : register(c0); float4 p1 : register(c1); #define screen_size float2(p0[0],p0[1]) #define hardScan float(p0[2]) #define hardPix float(p0[3]) #define warp float2(p1[0], p1[1]) // Amount of shadow mask. #define maskDark float (p1[2]) #define maskLight float (p1[3]) // sRGB to Linear. // Assuing using sRGB typed textures this should not be needed. float ToLinear1(float c){return(c<=0.04045)?c/12.92:pow(abs((c+0.055)/1.055),2.4);} float3 ToLinear(float3 c){return float3(ToLinear1(c.r),ToLinear1(c.g),ToLinear1(c.b));} // Linear to sRGB. // Assuing using sRGB typed textures this should not be needed. float ToSrgb1(float c){return(c<0.0031308?c*12.92:1.055*pow(abs(c),0.41666)-0.055);} float3 ToSrgb(float3 c){return float3(ToSrgb1(c.r),ToSrgb1(c.g),ToSrgb1(c.b));} // Nearest emulated sample given floating point position and texel offset. // Also zero's off screen. float3 Fetch(float2 pos,float2 off){ //pos=(floor(pos*screen_size+off)+float2(0.5,0.5))/screen_size; if (max(abs(pos.x-0.5), abs(pos.y-0.5)) > 0.5) { return float3(0.0, 0.0, 0.0);} return ToLinear(1.2 * GTexture.Sample(s0,pos).rgb);} // Distance in emulated pixels to nearest texel. float2 Dist(float2 pos){return -((pos-floor(pos))-float2(0.5, 0.5));} // 1D Gaussian. float Gaus(float pos,float scale){return exp2(scale*pos*pos);} // 3-tap Gaussian filter along horz line. // 3-tap Gaussian filter along horz line. float3 Horz3(float2 pos,float off){ float3 b=Fetch(pos,float2(-1.0,off)); float3 c=Fetch(pos,float2( 0.0,off)); float3 d=Fetch(pos,float2( 1.0,off)); float dst=Dist(pos).x; // Convert distance to weight. float scale=hardPix; float wb=Gaus(dst-1.0,scale); float wc=Gaus(dst+0.0,scale); float wd=Gaus(dst+1.0,scale); // Return filtered sample. return (b*wb+c*wc+d*wd)/(wb+wc+wd);} // 5-tap Gaussian filter along horz line. float3 Horz5(float2 pos,float off){ float3 a=Fetch(pos,float2(-2.0,off)); float3 b=Fetch(pos,float2(-1.0,off)); float3 c=Fetch(pos,float2( 0.0,off)); float3 d=Fetch(pos,float2( 1.0,off)); float3 e=Fetch(pos,float2( 2.0,off)); float dst=Dist(pos).x; // Convert distance to weight. float scale=hardPix; float wa=Gaus(dst-2.0,scale); float wb=Gaus(dst-1.0,scale); float wc=Gaus(dst+0.0,scale); float wd=Gaus(dst+1.0,scale); float we=Gaus(dst+2.0,scale); // Return filtered sample. return (a*wa+b*wb+c*wc+d*wd+e*we)/(wa+wb+wc+wd+we);} // Return scanline weight. float Scan(float2 pos,float off){ float dst=Dist(pos).y; return Gaus(dst+off,hardScan);} // Allow nearest three lines to effect pixel. float3 Tri(float2 pos){ float3 a=Horz3(pos,-1.0); float3 b=Horz5(pos, 0.0); float3 c=Horz3(pos, 1.0); float wa=Scan(pos,-1.0); float wb=Scan(pos, 0.0); float wc=Scan(pos, 1.0); return a*wa+b*wb+c*wc;} // Distortion of scanlines, and end of screen alpha. float2 Warp(float2 pos){ pos=pos*2.0-1.0; pos*=float2(1.0+(pos.y*pos.y)*warp.x,1.0+(pos.x*pos.x)*warp.y); return pos*0.5+0.5;} // Shadow mask. float3 Mask(float2 pos){ pos.x+=pos.y*3.0; float3 mask=float3(maskDark,maskDark,maskDark); pos.x=frac(pos.x/6.0); if(pos.x<0.333)mask.r=maskLight; else if(pos.x<0.666)mask.g=maskLight; else mask.b=maskLight; return mask; } float4 main(float2 tex: TEXCOORD) : SV_TARGET { float2 pos = tex*screen_size; tex = Warp(tex); float4 color; color.rgb = ToSrgb(Tri(tex)*Mask(pos)); return float4(color.r, color.g, color.b, 1); } Ich übergebe bei DX9 die Werte so: [TContextShaderVariable.Create('Input', TContextShaderVariableKind.Texture, 0, 0), TContextShaderVariable.Create('C0', TContextShaderVariableKind.Float, 0, 1), TContextShaderVariable.Create('C1', TContextShaderVariableKind.Float, 1, 1)] und genauso bei DX11. Das scheint aber nicht ganz korrekt, zumindest glaube ich das. Ist auch nicht korrekt: , [TContextShaderVariable.Create('Input', TContextShaderVariableKind.Texture, 0, 0), TContextShaderVariable.Create('C0', TContextShaderVariableKind.Float, 0, 16), TContextShaderVariable.Create('C1', TContextShaderVariableKind.Float, 16, 16)] ), Damit gehts :) |
AW: Eigener Firemonkey TEffect Shader
Liste der Anhänge anzeigen (Anzahl: 1)
Anbei mal der komplette Quellcode des Projekts. Die DX9 und DX11 Pixelshader funktionieren in Kombination mit dem TCRTFilter. Einfach den Effekt einem FMX Objekt zuweisen und schon wird die Anzeige "aufgehübscht".
Für die Konvertierung des DX9 Shaders nach GLSL habe ich den Konverter von Nvidia benutzt: ![]() Was aber nicht funktioniert ist, die Übergabe der Parameter an den GLSL Shader. Wenn ich die Werte in den GLSL Pixelshader manuell eintrage, erscheint auch ein Bild unter Mac, aber irgend etwas funktioniert bei der Übergabe der Variablen nicht richtig. Vielleicht hat ja jemand eine Idee? Wie gesagt unter Windows läuft der Filter prima. Wäre halt auch schon, wenn er unter OSX ebenso Gänge. Christian PS: Der beiliegende Quellcode ist public domain. |
AW: Eigener Firemonkey TEffect Shader
Sorry das ich mich so spät erst zurück melde und Super das Du es geschafft hast, das freut mich sehr! Ich schnupper da doch glatt mal rein und schau mir die Aufhübschung aus der Nähe an :thumb::stupid:
edit Wenn Dein Shader aus einem klaren Bild eine Art Arcade-Bild herstellen sollte, mit gebogenem Display und dunkle Scanlines, Hut ab, perfekt umgesetzt! |
AW: Eigener Firemonkey TEffect Shader
Liste der Anhänge anzeigen (Anzahl: 1)
Ja das geht mittlerweile sogar richtig gut. Ich habe noch eine TMaterialSource Klasse erstellt, damit man das auch auf einem 3d Objekt nutzen kann. Das Problem übrigens war, dass ich Float benutzt habe, richtig ist aber TContextShaderVariableKind.Matrix, dann klappt es auch unter GLSL.
Anbei mal der "finale" Code des Effektfilters. Das ganze sieht nach mehr aus, als es ist. Der Großteil ist dem Pixelshader -> Array of Byte Code geschuldet. |
AW: Eigener Firemonkey TEffect Shader
Falls Du etwas in Richtung Spiele machen möchtest, ich bin gerade dabei mir das
![]() |
AW: Eigener Firemonkey TEffect Shader
Liste der Anhänge anzeigen (Anzahl: 1)
Für Spiele ist der nicht gedacht. Vor knapp 20 Jahren (junge bin ich alt), habe ich einen ZX Spectrum Emulator für DOS geschrieben. Den gibt es im Quellcode noch irgendwo unter worldofspectrum.org oder zophar.net.
Vor einiger Zeit wollte ich den Code portieren und unter MacOS bzw. iOS und Android zum laufen kriegen. Nun schaut ein 320x256 Bild etwas komisch auf einem 4k Display aus und da lag es nahe die Anzeige aufzuhübschen. Ich habe jetzt einen eigenen Shader erstellt der auf allen Plattformen läuft und ziemlich genügsam ist. Auf dem Mac schaut da so aus, wie im Anhang. Christian PS: Aber du kannst da natürlich den Effekt auch für Arcade-Spiele nutzen. |
AW: Eigener Firemonkey TEffect Shader
Hey das ist doch ein tolles Gefühl wenn was funktioniert, oder?:thumb:
Hier noch ein paar nützliche Links zu diesem Thema ![]() ![]() Die DP bietet ![]() |
Alle Zeitangaben in WEZ +1. Es ist jetzt 08:08 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz