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
- Drop a TGorillaRenderPassPostFX component from component palette onto your form
- Select your TGorillaRenderPassPostFX instance in structure view
- Link the GorillaViewport instance to the “Viewport” property of your TGorillaRenderPassPostFX instance
- Open “SurfaceShader” property editor
- 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; }
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 |
---|---|---|
_RenderedTexture | sampler2D | The main render pass result texture. |
_MaskTexture | sampler2D | Optional texture for post effects. You can use this texture for additional shader computation. |
_TimeInfo.x | float | Starting timestamp of the post effect shader |
_TimeInfo.y | float | Current timestamp of the post effect shader |
_TimeInfo.z | float | Previous timestamp of the post effect shader |
_TimeInfo.w | float | Delta timestamp of the post effect shader |
DATA.TexCoord0 | vec2 | Texture coordinate of the post effect plane. |
DATA.WorldViewProjVertPos | vec4 | World-View-Projected vertex position of the post effect plane. |
DATA.Normal | vec3 | Normal 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; }
Links
You can find many free shaders in the web with less need of change.
- …