AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

Eigener Firemonkey TEffect Shader

Ein Thema von CHackbart · begonnen am 23. Mai 2018 · letzter Beitrag vom 26. Mai 2018
Antwort Antwort
CHackbart

Registriert seit: 22. Okt 2012
267 Beiträge
 
#1

Eigener Firemonkey TEffect Shader

  Alt 23. Mai 2018, 11:53
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:
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);
}
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.

Delphi-Quellcode:
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;
Das Problem welches ich jetzt habe ist den DX9 Shader auf DX11 zu portieren. Hat das jemand schon einmal gemacht?
Miniaturansicht angehängter Grafiken
screenshot-2018-05-23-um-11.45.32.jpg  
  Mit Zitat antworten Zitat
Benutzerbild von KodeZwerg
KodeZwerg

Registriert seit: 1. Feb 2018
3.691 Beiträge
 
Delphi 11 Alexandria
 
#2

AW: Eigener Firemonkey TEffect Shader

  Alt 23. Mai 2018, 12:13
PDF ShaderPorting erklärt einem die Unterschiede.
Gruß vom KodeZwerg
  Mit Zitat antworten Zitat
CHackbart

Registriert seit: 22. Okt 2012
267 Beiträge
 
#3

AW: Eigener Firemonkey TEffect Shader

  Alt 23. Mai 2018, 12:41
Danke,

das hatte ich in der Form auch schon:

Code:
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);
}
Nachtrag: Der Shader oben scheint zu gehen, aber die Werte in dem Register sind alle 0.

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

Geändert von CHackbart (23. Mai 2018 um 13:06 Uhr)
  Mit Zitat antworten Zitat
CHackbart

Registriert seit: 22. Okt 2012
267 Beiträge
 
#4

AW: Eigener Firemonkey TEffect Shader

  Alt 23. Mai 2018, 14:42
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: http://developer.nvidia.com/cg-toolkit

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.
Angehängte Dateien
Dateityp: zip source.zip (1,78 MB, 15x aufgerufen)
  Mit Zitat antworten Zitat
Benutzerbild von KodeZwerg
KodeZwerg

Registriert seit: 1. Feb 2018
3.691 Beiträge
 
Delphi 11 Alexandria
 
#5

AW: Eigener Firemonkey TEffect Shader

  Alt 23. Mai 2018, 19:29
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

edit
Wenn Dein Shader aus einem klaren Bild eine Art Arcade-Bild herstellen sollte, mit gebogenem Display und dunkle Scanlines, Hut ab, perfekt umgesetzt!
Gruß vom KodeZwerg

Geändert von KodeZwerg (23. Mai 2018 um 20:01 Uhr)
  Mit Zitat antworten Zitat
CHackbart

Registriert seit: 22. Okt 2012
267 Beiträge
 
#6

AW: Eigener Firemonkey TEffect Shader

  Alt 23. Mai 2018, 20:45
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.
Angehängte Dateien
Dateityp: zip UCrtEffect.pas.zip (16,2 KB, 9x aufgerufen)

Geändert von CHackbart (23. Mai 2018 um 20:49 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von KodeZwerg
KodeZwerg

Registriert seit: 1. Feb 2018
3.691 Beiträge
 
Delphi 11 Alexandria
 
#7

AW: Eigener Firemonkey TEffect Shader

  Alt 23. Mai 2018, 21:42
Falls Du etwas in Richtung Spiele machen möchtest, ich bin gerade dabei mir das DelphiArcadeGames Beispiel-Paket zu laden, soll wohl FMX für alle OS sein. Vielleicht ist ja was brauchbares dabei?! Ich schau es mir jedenfalls mal an.
Gruß vom KodeZwerg
  Mit Zitat antworten Zitat
CHackbart

Registriert seit: 22. Okt 2012
267 Beiträge
 
#8

AW: Eigener Firemonkey TEffect Shader

  Alt 26. Mai 2018, 14:51
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.
Miniaturansicht angehängter Grafiken
bildschirmfoto-2018-05-26-um-14.48.05.jpg  
  Mit Zitat antworten Zitat
Benutzerbild von KodeZwerg
KodeZwerg

Registriert seit: 1. Feb 2018
3.691 Beiträge
 
Delphi 11 Alexandria
 
#9

AW: Eigener Firemonkey TEffect Shader

  Alt 26. Mai 2018, 15:01
Hey das ist doch ein tolles Gefühl wenn was funktioniert, oder?
Hier noch ein paar nützliche Links zu diesem Thema
Shadersammlung erklärts einem ganz gut worum es geht.
Hundreds of graphical effects, and a library that provides GLSL functionality in pure Delphi code habe ich mir noch nicht weiter angeschaut.

Die DP bietet hier ein ShaderFile Tool an.
Gruß vom KodeZwerg
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 04:24 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz