PostFX

Even though Gorilla3D is compatible with Firemonkey post effects, it is difficult for many users to build their own post effect.

To solve this issue we provide the TGorillaRenderPassPostFX component.

It is now possible for you to easily create your own effect and to write a shader in the IDE at design time.

DesignTime

  1. Drop a TGorillaRenderPassPostFX component from component palette onto your form
  2. Select your TGorillaRenderPassPostFX instance in structure view
  3. Link the GorillaViewport instance to the “Viewport” property of your TGorillaRenderPassPostFX instance
  4. Open “SurfaceShader” property editor
  5. Write GLSL shader code using “vec3 SurfaceShader(inout TLocals DATA){…}” as entry function

Here is some nice television post-effect for the surface shader.

Just copy & paste into GorillaRenderPassPostFX1.SurfaceShader property editor:

vec3 mod289(vec3 x) {
  return x - floor(x * (1.0 / 289.0)) * 289.0;
}
 
vec2 mod289(vec2 x) {
  return x - floor(x * (1.0 / 289.0)) * 289.0;
}
 
vec3 permute(vec3 x) {
  return mod289(((x*34.0)+1.0)*x);
}
 
float snoise(vec2 v){
  const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439);
  vec2 i  = floor(v + dot(v, C.yy) );
  vec2 x0 = v -   i + dot(i, C.xx);
 
  vec2 i1;
  i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
  vec4 x12 = x0.xyxy + C.xxzz;
  x12.xy -= i1;
 
  i = mod289(i);
  vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) + i.x + vec3(0.0, i1.x, 1.0 ));
 
  vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
  m = m*m ;
  m = m*m ;
 
  vec3 x = 2.0 * fract(p * C.www) - 1.0;
  vec3 h = abs(x) - 0.5;
  vec3 ox = floor(x + 0.5);
  vec3 a0 = x - ox;
 
  m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
 
  vec3 g;
  g.x  = a0.x  * x0.x  + h.x  * x0.y;
  g.yz = a0.yz * x12.xz + h.yz * x12.yw;
  return 130.0 * dot(m, g);
}
 
float rand(vec2 co){
   return fract(sin(dot(co.xy,vec2(12.9898,78.233))) * 43758.5453);
}
 
vec3 SurfaceShader(inout TLocals DATA){
    vec2 uv = DATA.TexCoord0.xy;
    float iTime = mod(_TimeInfo.y, 360.0);
    float time = iTime * 2.0;
 
    float noise = max(0.0, snoise(vec2(time, uv.y * 0.3)) - 0.3) * (1.0 / 0.7);
 
    noise = noise + (snoise(vec2(time*10.0, uv.y * 2.4)) - 0.5) * 0.15;
 
    vec4 fragColor;
    float xpos = uv.x - noise * noise * 0.25;
	fragColor = tex2D(_RenderedTexture, vec2(xpos, uv.y));
 
    fragColor.rgb = mix(fragColor.rgb, vec3(rand(vec2(uv.y * time))), noise * 0.3).rgb;
 
    if (floor(mod(gl_FragCoord.y * 0.25, 2.0)) == 0.0){
        fragColor.rgb *= 1.0 - (0.15 * noise);
    }
 
    fragColor.g = mix(fragColor.r, texture(_RenderedTexture, vec2(xpos + noise * 0.05, uv.y)).g, 0.25);
    fragColor.b = mix(fragColor.r, texture(_RenderedTexture, vec2(xpos - noise * 0.05, uv.y)).b, 0.25);
 
    return fragColor.rgb;
}

Source: https://www.shadertoy.com/view/XtK3W3

Runtime

Of course you can also setup post effects at runtime.

Have a look at the following function simply extracting the red-color channel from main render pass.

var LPostFX := TGorillaRenderPassPostFX.Create(GorillaViewport1, 'RedChannelPostEffect');
LPostFX.Viewport := GorillaViewport1;
LPostFX.SurfaceShader.Text := 
  'vec3 SurfaceShader(inout TLocals DATA){'#13#10 +
  '  return vec3(tex2D(_RenderedTexture, DATA.TexCoord0.xy).r, 0.0, 0.0));'#13#10 +
  '}'#13#10;

Shaders

You can write shaders in GLSL syntax compatible with OpenGL 4.3 (on Windows) and OpenGLES 3.1+ (on Android).

WARNING: GLSL compiler does not allow comments in shader code. The framework currently does not filter comments.

The SurfaceShader property of the PostFX instance allows to put a string with compatible shader code into.

At least every shader needs a “SurfaceShader” function with the following header. Besides that you can add further functions to the shader.

vec3 SurfaceShader(inout TLocals DATA){
...
}

The shader itself provides some variables for usage:

Shader Variable Type Description
_RenderedTexturesampler2DThe main render pass result texture.
_MaskTexturesampler2DOptional texture for post effects. You can use this texture for additional shader computation.
_TimeInfo.xfloatStarting timestamp of the post effect shader
_TimeInfo.yfloatCurrent timestamp of the post effect shader
_TimeInfo.zfloatPrevious timestamp of the post effect shader
_TimeInfo.wfloatDelta timestamp of the post effect shader
DATA.TexCoord0vec2Texture coordinate of the post effect plane.
DATA.WorldViewProjVertPosvec4World-View-Projected vertex position of the post effect plane.
DATA.Normalvec3Normal vector of the post effect plane.

Examples

GameBoy Post Effect

We modified the Gameboy post effect from ShaderToy

Source: Nintendo GameBoy Classic (DMG) Post-Effect Written by jilski for the strangeness project (http://strangeness.jilski.com) June 2018

vec3 rgb(int r, int g, int b)
{
  return vec3(float(r) / 256.0, float(g) / 256.0, float(b) / 256.0);
}
 
void mainImage(out vec4 fragColor, in vec2 fragCoord, ivec2 iResolution, float iTime)
{
  float blend_factor = 1.0 - pow(0.5 + 0.5 * cos(iTime / 10.0), 2.0);
  float loudness = cos(iTime / 0.5);
  float pix_size = 8.0;
 
  vec3 palette[4] = vec3[](rgb(180,210,46), rgb(155,195,40), rgb(108,151,35), rgb(54,112,30));
  vec3 line = palette[0] * 1.6;
 
  vec2 coord = fragCoord / iResolution.xy;
 
  vec2 resolution = vec2(iResolution.x / pix_size, iResolution.y / pix_size);
 
  vec2 uv = mix(coord, (floor(coord * resolution) / resolution), blend_factor);
  vec2 pix = uv * resolution;
 
  vec3 orig = texture(_RenderedTexture, uv).rgb;
  vec3 col = line;
  vec2 line_width = vec2(1.0 / iResolution.x, 1.0 / iResolution.y);
 
  vec2 pix_thresh = mix(vec2(0.0), line_width, blend_factor);
 
  bool is_pixel = (coord.x - uv.x) > pix_thresh.x && (coord.y - uv.y) > pix_thresh.y;
  bool is_not_broken = int(pix.x) != int(resolution.x)-2
      && (int(pix.x) != int(resolution.x)-4 || fract(4.0*sin(iTime/10.))>0.95) && int(pix.x) != 1;
 
  if ( is_pixel && is_not_broken )
  {
    float val = (orig.r + orig.g+orig.b) / 3.0;
    int shade = min(3, int(4.0 * log(1.0 + val * 1.71)));
    col = palette[shade];
 
    vec2 offs = 3.0 * vec2(0.5 + 0.5 * sin(iTime), 0.5 + 0.5 * cos(iTime / 1.2));
    vec2 cen = vec2(0.5, 0.5) + offs;
    col += vec3(0.1, 0.14, 0.01) * (0.5 + 0.5 * sin(atan(cen.x, cen.y)));
    col -= vec3(0.8, 1.0, 0.5) * 5.0 * sin(loudness) * length(uv - coord);
  }
 
  col *= 1.1 - 0.3 * distance(coord, vec2(0.5, 0.5));
 
  col = mix(orig,col, blend_factor);
 
  fragColor = vec4(col, 1.0);
}
 
vec3 SurfaceShader(inout TLocals DATA){
  vec4 l_Color = vec4(0.0);
  ivec2 l_Resolution = textureSize(_RenderedTexture, 0);
  float iTime = mod(_TimeInfo.y, 360.0);
  mainImage(l_Color, gl_FragCoord.xy, l_Resolution, iTime);
  return l_Color.rgb;
}

TV Post Effect

A slighly modified version of the shader code at ShaderToy Source: Array and textureless GLSL 2D simplex noise function. Author : Ian McEwan, Ashima Arts.

vec3 mod289(vec3 x) {
  return x - floor(x * (1.0 / 289.0)) * 289.0;
}
 
vec2 mod289(vec2 x) {
  return x - floor(x * (1.0 / 289.0)) * 289.0;
}
 
vec3 permute(vec3 x) {
  return mod289(((x*34.0)+1.0)*x);
}
 
float snoise(vec2 v){
  const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439);
  vec2 i  = floor(v + dot(v, C.yy) );
  vec2 x0 = v -   i + dot(i, C.xx);
 
  vec2 i1;
  i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
  vec4 x12 = x0.xyxy + C.xxzz;
  x12.xy -= i1;
 
  i = mod289(i);
  vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) + i.x + vec3(0.0, i1.x, 1.0 ));
 
  vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
  m = m*m ;
  m = m*m ;
 
  vec3 x = 2.0 * fract(p * C.www) - 1.0;
  vec3 h = abs(x) - 0.5;
  vec3 ox = floor(x + 0.5);
  vec3 a0 = x - ox;
 
  m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
 
  vec3 g;
  g.x  = a0.x  * x0.x  + h.x  * x0.y;
  g.yz = a0.yz * x12.xz + h.yz * x12.yw;
  return 130.0 * dot(m, g);
}
 
float rand(vec2 co){
   return fract(sin(dot(co.xy,vec2(12.9898,78.233))) * 43758.5453);
}
 
vec3 SurfaceShader(inout TLocals DATA){
    vec2 uv = DATA.TexCoord0.xy;
    float iTime = mod(_TimeInfo.y, 360.0);
    float time = iTime * 2.0;
 
    float noise = max(0.0, snoise(vec2(time, uv.y * 0.3)) - 0.3) * (1.0 / 0.7);
 
    noise = noise + (snoise(vec2(time*10.0, uv.y * 2.4)) - 0.5) * 0.15;
 
    vec4 fragColor;
    float xpos = uv.x - noise * noise * 0.25;
	fragColor = tex2D(_RenderedTexture, vec2(xpos, uv.y));
 
    fragColor.rgb = mix(fragColor.rgb, vec3(rand(vec2(uv.y * time))), noise * 0.3).rgb;
 
    if (floor(mod(gl_FragCoord.y * 0.25, 2.0)) == 0.0){
        fragColor.rgb *= 1.0 - (0.15 * noise);
    }
 
    fragColor.g = mix(fragColor.r, texture(_RenderedTexture, vec2(xpos + noise * 0.05, uv.y)).g, 0.25);
    fragColor.b = mix(fragColor.r, texture(_RenderedTexture, vec2(xpos - noise * 0.05, uv.y)).b, 0.25);
 
    return fragColor.rgb;
}

Raindrop Post Effect

A modified version of the Heartfelt shader from ShaderToy.

Heartfelt - by Martijn Steinrucken aka BigWings - 2017 Email:countfrolic@gmail.com Twitter:@The_ArtOfCode License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

#define S(a, b, t) smoothstep(a, b, t)
//#define HAS_HEART
//#define HAS_ZOOM
#define USE_POST_PROCESSING
 
vec3 N13(float p) {
   vec3 p3 = fract(vec3(p) * vec3(.1031,.11369,.13787));
   p3 += dot(p3, p3.yzx + 19.19);
   return fract(vec3((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y, (p3.y+p3.z)*p3.x));
}
 
vec4 N14(float t) {
    return fract(sin(t*vec4(123., 1024., 1456., 264.))*vec4(6547., 345., 8799., 1564.));
}
 
float N(float t) {
    return fract(sin(t*12345.564)*7658.76);
}
 
float Saw(float b, float t) {
    return S(0., b, t)*S(1., b, t);
}
 
 
vec2 DropLayer2(vec2 uv, float t) {
    vec2 UV = uv;
 
    uv.y += t*0.75;
    vec2 a = vec2(6., 1.);
    vec2 grid = a*2.;
    vec2 id = floor(uv*grid);
 
    float colShift = N(id.x);
    uv.y += colShift;
 
    id = floor(uv*grid);
    vec3 n = N13(id.x*35.2+id.y*2376.1);
    vec2 st = fract(uv*grid)-vec2(.5, 0);
 
    float x = n.x-.5;
 
    float y = UV.y*20.;
    float wiggle = sin(y+sin(y));
    x += wiggle*(.5-abs(x))*(n.z-.5);
    x *= .7;
    float ti = fract(t+n.z);
    y = (Saw(.85, ti)-.5)*.9+.5;
    vec2 p = vec2(x, y);
 
    float d = length((st-p)*a.yx);
 
    float mainDrop = S(.4, .0, d);
 
    float r = sqrt(S(1., y, st.y));
    float cd = abs(st.x-x);
    float trail = S(.23*r, .15*r*r, cd);
    float trailFront = S(-.02, .02, st.y-y);
    trail *= trailFront*r*r;
 
    y = UV.y;
    float trail2 = S(.2*r, .0, cd);
    float droplets = max(0., (sin(y*(1.-y)*120.)-st.y))*trail2*trailFront*n.z;
    y = fract(y*10.)+(st.y-.5);
    float dd = length(st-vec2(x, y));
    droplets = S(.3, 0., dd);
    float m = mainDrop+droplets*r*trailFront;
 
    return vec2(m, trail);
}
 
float StaticDrops(vec2 uv, float t) {
    uv *= 40.;
 
    vec2 id = floor(uv);
    uv = fract(uv)-.5;
    vec3 n = N13(id.x*107.45+id.y*3543.654);
    vec2 p = (n.xy-.5)*.7;
    float d = length(uv-p);
 
    float fade = Saw(.025, fract(t+n.z));
    float c = S(.3, 0., d)*fract(n.z*10.)*fade;
    return c;
}
 
vec2 Drops(vec2 uv, float t, float l0, float l1, float l2) {
    float s = StaticDrops(uv, t)*l0;
    vec2 m1 = DropLayer2(uv, t)*l1;
    vec2 m2 = DropLayer2(uv*1.85, t)*l2;
 
    float c = s+m1.x+m2.x;
    c = S(.3, 1., c);
 
    return vec2(c, max(m1.y*l0, m2.y*l1));
}
 
void mainImage( out vec4 fragColor, in vec2 fragCoord, ivec3 iResolution, float iTime, float scrubTime, float amount, float zdist )
{
    // Mouse x = scrub time  y = rain amount (only without heart)
    vec4 iMouse = vec4(scrubTime, amount, zdist, 0);
 
    vec2 uv = (fragCoord.xy - .5 * iResolution.xy) / iResolution.y;
    vec2 UV = fragCoord.xy / iResolution.xy;
    vec3 M = iMouse.xyz / iResolution.xyz;
    float T = iTime + M.x * 2.;
 
  #ifdef HAS_HEART
    T = mod(iTime, 102.);
    T = mix(T, M.x * 102., M.z > 0.?1.:0.);
  #endif
 
    float t = T*.2;
 
    float rainAmount = iMouse.z > 0. ? M.y : sin(T*.05)*.3+.7;
 
    float maxBlur = mix(3., 6., rainAmount);
    float minBlur = 2.;
 
    float story = 0.;
    float heart = 0.;
 
    float zoom = 0.;
 
  #ifdef HAS_HEART
    story = S(0., 70., T);
 
    t = min(1., T/70.);
    t = 1.-t;
    t = (1.-t*t)*70.;
 
  #ifdef HAS_ZOOM
    zoom= mix(.3, 1.2, story);
    uv *=zoom;
  #endif
    minBlur = 4.+S(.5, 1., story)*3.;
    maxBlur = 6.+S(.5, 1., story)*1.5;
 
    vec2 hv = uv-vec2(.0, -.1);
    hv.x *= .5;
    float s = S(110., 70., T);
    hv.y-=sqrt(abs(hv.x))*.5*s;
    heart = length(hv);
    heart = S(.4*s, .2*s, heart)*s;
    rainAmount = heart;
 
    maxBlur-=heart;
    uv *= 1.5;
    t *= .25;
  #else
  #ifdef HAS_ZOOM
    zoom = -cos(T*.2);
    uv *= .7+zoom*.3;
  #endif
  #endif
    UV = (UV-.5)*(.9+zoom*.1)+.5;
 
    float staticDrops = S(-.5, 1., rainAmount)*2.;
    float layer1 = S(.25, .75, rainAmount);
    float layer2 = S(.0, .5, rainAmount);
 
    vec2 c = Drops(uv, t, staticDrops, layer1, layer2);
 
  #ifdef CHEAP_NORMALS
    vec2 n = vec2(dFdx(c.x), dFdy(c.x));
  #else
    vec2 e = vec2(.001, 0.);
    float cx = Drops(uv+e, t, staticDrops, layer1, layer2).x;
    float cy = Drops(uv+e.yx, t, staticDrops, layer1, layer2).x;
    vec2 n = vec2(cx-c.x, cy-c.x);
  #endif
 
  #ifdef HAS_HEART
    n *= 1.-S(60., 85., T);
    c.y *= 1.-S(80., 100., T)*.8;
  #endif
 
    float focus = mix(maxBlur-c.y, minBlur, S(.1, .2, c.x));
    vec3 col = textureLod(_RenderedTexture, UV+n, focus).rgb;
 
  #ifdef USE_POST_PROCESSING
    t = (T+3.)*.5;
    float colFade = sin(t*.2)*.5+.5+story;
    col *= mix(vec3(1.), vec3(.8, .9, 1.3), colFade);
    float fade = S(0., 10., T);
    float lightning = sin(t*sin(t*10.));
    lightning *= pow(max(0., sin(t+sin(t))), 10.);
    col *= 1.+lightning*fade*mix(1., .1, story*story);
    col *= 1.-dot(UV-=.5, UV);
 
  #ifdef HAS_HEART
    col = mix(pow(col, vec3(1.2)), col, heart);
    fade *= S(102., 97., T);
  #endif
 
    col *= fade;
  #endif
 
    fragColor = vec4(col, 1.);
}
 
vec3 SurfaceShader(inout TLocals DATA){
  vec4 l_Color = vec4(0.0);
  ivec3 l_Resolution = ivec3(textureSize(_RenderedTexture, 0), 1.0);
  float iTime = mod(_TimeInfo.y, 360.0);
  mainImage(l_Color, gl_FragCoord.xy, l_Resolution, iTime, 0.0, 20.0, 0.0);
  return l_Color.rgb;
}

You can find many free shaders in the web with less need of change.