The power of GPU shaders

Discussions related to graphics (2D and 3D), animation and games programming
David Williams

The power of GPU shaders

Post by David Williams »

Possibly off-topic for this forum (at least for the time being), but here's an amazing demo of what can be achieved with a GPU shader (in roughly 184 lines of code):

https://www.shadertoy.com/view/Ms2SD1

I couldn't believe my eyes at first. I thought I was watching a pre-rendered video, but no. All real-time, no textures, purely procedural and somewhat mathematical. The website (shadertoy.com) showcases many excellent GPU shaders.

Exercise for the interested (and clever) BBC BASIC user: port this 'Seascape' shader/demo to BBCSDL :)


David.
--
guest

Re: The power of GPU shaders

Post by guest »

David Williams wrote: Thu 23 Aug 2018, 16:54Exercise for the interested (and clever) BBC BASIC user: port this 'Seascape' shader/demo to BBCSDL
Here you go! It needs a relatively powerful GPU for full frame rate (and runs extremely slowly on a Raspberry Pi). A complete code listing is below.

http://www.youtube.com/watch?v=5GPfWg0nAY4

Code: Select all

      REM 'Procedural Seascape' by Alexander Alekseev, tdmaav@gmail.com, 2014
      REM BBCSDL version by Richard Russell, www.rtrussell.co.uk, 04-Jan-2019

      REM Fix window size (if the version of SDL allows):
      IF @platform% >= &2000500 THEN
        SYS "SDL_SetWindowResizable", @hwnd%, 0, @memhdc%
      ENDIF

      REM Initialise window:
      ScrW% = 640
      ScrH% = 480
      VDU 23,22,ScrW%;ScrH%;8,16,16,128

      REM Install libraries:
      INSTALL @lib$ + "ogllib"

      REM Create arrays:
      DIM Object%(0), nVert%(0), vFormat%(0), vSize%(0), Material%(0), Texture%(0)
      DIM Pan(0), Tilt(0), Roll(0), Xpos(0), Ypos(0), Zpos(0), Camera(2), LookAt(2)
      DIM Light%(0), Vertex$(10), Fragment$(1000)

      REM Fill vertex and fragment shader arrays from DATA statements:
      PROCreadshader(Vertex$())
      PROCreadshader(Fragment$())

      REM Create simple rectangular object:
      F% = OPENOUT(@tmp$+"rectangle.fvf")
      BPUT#F%,6 MOD 256   : BPUT#F%,6 DIV 256 : BPUT#F%,0 : BPUT#F%,0 : REM vertex count
      BPUT#F%,2 : BPUT#F%,0 : BPUT#F%,12 : BPUT#F%,0 : REM vertex format and size
      PROC4(F%,+ScrW%/ScrH%) : PROC4(F%,+1) : PROC4(F%,0)
      PROC4(F%,-ScrW%/ScrH%) : PROC4(F%,+1) : PROC4(F%,0)
      PROC4(F%,-ScrW%/ScrH%) : PROC4(F%,-1) : PROC4(F%,0)
      PROC4(F%,-ScrW%/ScrH%) : PROC4(F%,-1) : PROC4(F%,0)
      PROC4(F%,+ScrW%/ScrH%) : PROC4(F%,-1) : PROC4(F%,0)
      PROC4(F%,+ScrW%/ScrH%) : PROC4(F%,+1) : PROC4(F%,0)
      CLOSE #F%

      REM Ensure cleanup on exit:
      ON CLOSE PROCcleanup : QUIT
      ON ERROR PROCcleanup : IF ERR=17 CHAIN @lib$+"../examples/tools/touchide" ELSE MODE 3 : PRINT REPORT$ : END

      REM Initialise OpenGL:
      pDevice% = FN_initgl(@hwnd%, 1, 0)
      IF pDevice% = 0 ERROR 100, "Couldn't initialise OpenGL"

      REM Get addresses of OpenGLfunctions:
      `glCreateShader`      = FNgpa("glCreateShader")
      IF `glCreateShader` = 0 ERROR 100, "OpenGL shaders are not available on this platform"
      `glAttachShader`      = FNgpa("glAttachShader")
      `glDeleteShader`      = FNgpa("glDeleteShader")
      `glShaderSource`      = FNgpa("glShaderSource")
      `glCompileShader`     = FNgpa("glCompileShader")
      `glGetShaderiv`       = FNgpa("glGetShaderiv")
      `glCreateProgram`     = FNgpa("glCreateProgram")
      `glLinkProgram`       = FNgpa("glLinkProgram")
      `glGetProgramiv`      = FNgpa("glGetProgramiv")
      `glGetShaderInfoLog`  = FNgpa("glGetShaderInfoLog")
      `glGetProgramInfoLog` = FNgpa("glGetProgramInfoLog")
      `glUseProgram`        = FNgpa("glUseProgram")
      `glGetUniformLocation`= FNgpa("glGetUniformLocation")
      `glUniform1fv`        = FNgpa("glUniform1fv")
      `glUniform2i`         = FNgpa("glUniform2i")

      REM Set OpenGL constants:
      GL_FRAGMENT_SHADER = &8B30
      GL_VERTEX_SHADER = &8B31
      GL_COMPILE_STATUS = &8B81
      GL_LINK_STATUS = &8B82
      GL_INFO_LOG_LENGTH = &8B84

      REM Create shader objects:
      SYS `glCreateShader`, GL_VERTEX_SHADER, @memhdc% TO oVertex%
      SYS `glCreateShader`, GL_FRAGMENT_SHADER, @memhdc% TO oFragment%

      REM Compile shaders:
      PROCcompileshader(oVertex%, Vertex$())
      PROCcompileshader(oFragment%, Fragment$())

      REM Create program object and link:
      SYS `glCreateProgram`, @memhdc% TO ProgramObject%
      SYS `glAttachShader`, ProgramObject%, oVertex%, @memhdc%
      SYS `glAttachShader`, ProgramObject%, oFragment%, @memhdc%
      SYS `glLinkProgram`, ProgramObject%, @memhdc%
      SYS `glGetProgramiv`, ProgramObject%, GL_LINK_STATUS, ^linked%, @memhdc%
      IF linked% = 0 THEN
        SYS `glGetProgramiv`, ProgramObject%, GL_INFO_LOG_LENGTH, ^blen%, @memhdc%
        DIM plog%% blen%
        SYS `glGetProgramInfoLog`, ProgramObject%, blen%, ^slen%, plog%%, @memhdc%
        ERROR 100, "Program object failed to link:" + CHR$&D + CHR$&A + LEFT$($$plog%%,220)
      ENDIF

      REM Load dummy 3D object:
      Object%(0) = FN_load3d(pDevice%, @tmp$+"rectangle.fvf", nVert%(0), vFormat%(0), vSize%(0))
      IF Object%(0) = 0 ERROR 100, "Couldn't load rectangle.fvf"

      REM Use shaders:
      SYS `glUseProgram`, ProgramObject%, @memhdc%
      SYS `glGetUniformLocation`, ProgramObject%, "iTime",       @memhdc% TO pTime%
      SYS `glGetUniformLocation`, ProgramObject%, "iMouse",      @memhdc% TO pMouse%
      SYS `glGetUniformLocation`, ProgramObject%, "iResolution", @memhdc% TO pResolution%

      REM Render:
      fov = 0.4
      mindist = 0.1
      maxdist = 10
      camroll = 0
      Camera() = 0, 0, -5.0
      LookAt() = 0, 0, 0
      aspect = ScrW% / ScrH%
      REPEAT
        MOUSE X%,Y%,B%
        ftime% = FN_f4(TIME/100)
        SYS `glUniform1fv`, pTime%, 1, ^ftime%, @memhdc%
        SYS `glUniform2i`, pMouse%, X%, Y%, @memhdc%
        SYS `glUniform2i`, pResolution%, ScrW%, INT(ScrH%*0.8), @memhdc%
        PROC_render(pDevice%, &00385080, 0, Light%(), 1, Material%(), Texture%(), Object%(), \
        \           nVert%(), vFormat%(), vSize%(), Pan(), Tilt(), Roll(), Xpos(), Ypos(), Zpos(), \
        \           Camera(), LookAt(), fov, aspect, mindist, maxdist, camroll)
        WAIT 2
      UNTIL FALSE
      END

      DEF PROCcleanup
      ON ERROR OFF
      *REFRESH ON
      IF !^Object%()  : IF Object%(0) PROC_release(Object%(0))
      oVertex% += 0   : IF oVertex%   SYS `glDeleteShader`, oVertex%, @memhdc%
      oFragment% += 0 : IF oFragment% SYS `glDeleteShader`, oFragment%, @memhdc%
      pDevice% += 0   : IF pDevice%   PROC_release(pDevice%)
      ENDPROC

      DEF PROCreadshader(shader$())
      LOCAL I%, a$
      REPEAT
        READ a$
        shader$(I%) = a$ + CHR$&A : REM LF-terminate
        I% += 1
      UNTIL a$ = ""
      ENDPROC

      DEF PROCcompileshader(object%, shader$())
      LOCAL compiled%, blen%, slen%, code%%, plog%%, code$
      code$ = SUM(shader$()) + CHR$0
      code%% = PTR(code$)
      SYS `glShaderSource`, object%, 1, ^code%%, FALSE, @memhdc%
      SYS `glCompileShader`, object%, @memhdc%
      SYS `glGetShaderiv`, object%, GL_COMPILE_STATUS, ^compiled%, @memhdc%
      IF compiled% = 0 THEN
        SYS `glGetShaderiv`, object%, GL_INFO_LOG_LENGTH, ^blen%, @memhdc%
        DIM plog%% blen%
        SYS `glGetShaderInfoLog`, object%, blen%, ^slen%, plog%%, @memhdc%
        ERROR 100, "Shader failed to compile:" + CHR$&D + CHR$&A + LEFT$($$plog%%,220)
      ENDIF
      ENDPROC

      DEF FNgpa(function$)
      LOCAL function%%
      SYS "SDL_GL_GetProcAddress", function$, @memhdc% TO function%%
      IF @platform% AND &40 ELSE function%% = !^function%%
      = function%%

      DEF PROC4(F%,a) : LOCAL A% : A%=FN_f4(a)
      BPUT #F%,A% : BPUT #F%,A%>>8 : BPUT#F%,A%>>16 : BPUT#F%,A%>>24
      ENDPROC

      REM Minimal 'default' vertex shader:
      DATA "#version 120"
      DATA "void main()"
      DATA "{"
      DATA "gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;"
      DATA "}"
      DATA ""

      REM Fragment Shader code from https://www.shadertoy.com/view/Ms2SD1
      DATA "#version 120"
      DATA "uniform float iTime;"
      DATA "uniform ivec2 iMouse;"
      DATA "uniform ivec2 iResolution;"

      DATA "/*"
      DATA "* 'Seascape' by Alexander Alekseev aka TDM - 2014"
      DATA "* License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License."
      DATA "* Contact: tdmaav@gmail.com"
      DATA "*/"

      DATA "const int NUM_STEPS = 8;"
      DATA "const float PI = 3.141592;"
      DATA "const float EPSILON= 1e-3;"
      DATA "#define EPSILON_NRM (0.1 / iResolution.x)"

      DATA "// sea"
      DATA "const int ITER_GEOMETRY = 3;"
      DATA "const int ITER_FRAGMENT = 5;"
      DATA "const float SEA_HEIGHT = 0.6;"
      DATA "const float SEA_CHOPPY = 4.0;"
      DATA "const float SEA_SPEED = 0.8;"
      DATA "const float SEA_FREQ = 0.16;"
      DATA "const vec3 SEA_BASE = vec3(0.1,0.19,0.22);"
      DATA "const vec3 SEA_WATER_COLOR = vec3(0.8,0.9,0.6);"
      DATA "#define SEA_TIME (1.0 + iTime * SEA_SPEED)"
      DATA "const mat2 octave_m = mat2(1.6,1.2,-1.2,1.6);"

      DATA "// math"
      DATA "mat3 fromEuler(vec3 ang) {"
      DATA "vec2 a1 = vec2(sin(ang.x),cos(ang.x));"
      DATA "vec2 a2 = vec2(sin(ang.y),cos(ang.y));"
      DATA "vec2 a3 = vec2(sin(ang.z),cos(ang.z));"
      DATA "mat3 m;"
      DATA "m[0] = vec3(a1.y*a3.y+a1.x*a2.x*a3.x,a1.y*a2.x*a3.x+a3.y*a1.x,-a2.y*a3.x);"
      DATA "m[1] = vec3(-a2.y*a1.x,a1.y*a2.y,a2.x);"
      DATA "m[2] = vec3(a3.y*a1.x*a2.x+a1.y*a3.x,a1.x*a3.x-a1.y*a3.y*a2.x,a2.y*a3.y);"
      DATA "return m;"
      DATA "}"
      DATA "float hash( vec2 p ) {"
      DATA "float h = dot(p,vec2(127.1,311.7));"
      DATA "return fract(sin(h)*43758.5453123);"
      DATA "}"
      DATA "float noise( in vec2 p ) {"
      DATA "vec2 i = floor( p );"
      DATA "vec2 f = fract( p );"
      DATA "vec2 u = f*f*(3.0-2.0*f);"
      DATA "return -1.0+2.0*mix( mix( hash( i + vec2(0.0,0.0) ),"
      DATA "hash( i + vec2(1.0,0.0) ), u.x),"
      DATA "mix( hash( i + vec2(0.0,1.0) ),"
      DATA "hash( i + vec2(1.0,1.0) ), u.x), u.y);"
      DATA "}"

      DATA "// lighting"
      DATA "float diffuse(vec3 n,vec3 l,float p) {"
      DATA "return pow(dot(n,l) * 0.4 + 0.6,p);"
      DATA "}"
      DATA "float specular(vec3 n,vec3 l,vec3 e,float s) {"
      DATA "float nrm = (s + 8.0) / (PI * 8.0);"
      DATA "return pow(max(dot(reflect(e,n),l),0.0),s) * nrm;"
      DATA "}"

      DATA "// sky"
      DATA "vec3 getSkyColor(vec3 e) {"
      DATA "e.y = max(e.y,0.0);"
      DATA "return vec3(pow(1.0-e.y,2.0), 1.0-e.y, 0.6+(1.0-e.y)*0.4);"
      DATA "}"

      DATA "// sea"
      DATA "float sea_octave(vec2 uv, float choppy) {"
      DATA "uv += noise(uv);"
      DATA "vec2 wv = 1.0-abs(sin(uv));"
      DATA "vec2 swv = abs(cos(uv));"
      DATA "wv = mix(wv,swv,wv);"
      DATA "return pow(1.0-pow(wv.x * wv.y,0.65),choppy);"
      DATA "}"

      DATA "float map(vec3 p) {"
      DATA "float freq = SEA_FREQ;"
      DATA "float amp = SEA_HEIGHT;"
      DATA "float choppy = SEA_CHOPPY;"
      DATA "vec2 uv = p.xz; uv.x *= 0.75;"

      DATA "float d, h = 0.0;"
      DATA "for(int i = 0; i < ITER_GEOMETRY; i++) {"
      DATA "d = sea_octave((uv+SEA_TIME)*freq,choppy);"
      DATA "d += sea_octave((uv-SEA_TIME)*freq,choppy);"
      DATA "h += d * amp;"
      DATA "uv *= octave_m; freq *= 1.9; amp *= 0.22;"
      DATA "choppy = mix(choppy,1.0,0.2);"
      DATA "}"
      DATA "return p.y - h;"
      DATA "}"

      DATA "float map_detailed(vec3 p) {"
      DATA "float freq = SEA_FREQ;"
      DATA "float amp = SEA_HEIGHT;"
      DATA "float choppy = SEA_CHOPPY;"
      DATA "vec2 uv = p.xz; uv.x *= 0.75;"

      DATA "float d, h = 0.0;"
      DATA "for(int i = 0; i < ITER_FRAGMENT; i++) {"
      DATA "d = sea_octave((uv+SEA_TIME)*freq,choppy);"
      DATA "d += sea_octave((uv-SEA_TIME)*freq,choppy);"
      DATA "h += d * amp;"
      DATA "uv *= octave_m; freq *= 1.9; amp *= 0.22;"
      DATA "choppy = mix(choppy,1.0,0.2);"
      DATA "}"
      DATA "return p.y - h;"
      DATA "}"

      DATA "vec3 getSeaColor(vec3 p, vec3 n, vec3 l, vec3 eye, vec3 dist) {"
      DATA "float fresnel = clamp(1.0 - dot(n,-eye), 0.0, 1.0);"
      DATA "fresnel = pow(fresnel,3.0) * 0.65;"

      DATA "vec3 reflected = getSkyColor(reflect(eye,n));"
      DATA "vec3 refracted = SEA_BASE + diffuse(n,l,80.0) * SEA_WATER_COLOR * 0.12;"

      DATA "vec3 color = mix(refracted,reflected,fresnel);"

      DATA "float atten = max(1.0 - dot(dist,dist) * 0.001, 0.0);"
      DATA "color += SEA_WATER_COLOR * (p.y - SEA_HEIGHT) * 0.18 * atten;"

      DATA "color += vec3(specular(n,l,eye,60.0));"

      DATA "return color;"
      DATA "}"

      DATA "// tracing"
      DATA "vec3 getNormal(vec3 p, float eps) {"
      DATA "vec3 n;"
      DATA "n.y = map_detailed(p);"
      DATA "n.x = map_detailed(vec3(p.x+eps,p.y,p.z)) - n.y;"
      DATA "n.z = map_detailed(vec3(p.x,p.y,p.z+eps)) - n.y;"
      DATA "n.y = eps;"
      DATA "return normalize(n);"
      DATA "}"

      DATA "float heightMapTracing(vec3 ori, vec3 dir, out vec3 p) {"
      DATA "float tm = 0.0;"
      DATA "float tx = 1000.0;"
      DATA "float hx = map(ori + dir * tx);"
      DATA "if(hx > 0.0) return tx;"
      DATA "float hm = map(ori + dir * tm);"
      DATA "float tmid = 0.0;"
      DATA "for(int i = 0; i < NUM_STEPS; i++) {"
      DATA "tmid = mix(tm,tx, hm/(hm-hx));"
      DATA "p = ori + dir * tmid;"
      DATA "float hmid = map(p);"
      DATA "if(hmid < 0.0) {"
      DATA "tx = tmid;"
      DATA "hx = hmid;"
      DATA "} else {"
      DATA "tm = tmid;"
      DATA "hm = hmid;"
      DATA "}"
      DATA "}"
      DATA "return tmid;"
      DATA "}"

      DATA "// main"
      DATA "void main() {"
      DATA "vec2 uv = gl_FragCoord.xy / iResolution.xy;"
      DATA "uv = uv * 2.0 - 1.0;"
      DATA "uv.x *= iResolution.x / iResolution.y;"
      DATA "float time = iTime * 0.3 + iMouse.x*0.01;"

      DATA "// ray"
      DATA "vec3 ang = vec3(sin(time*3.0)*0.1,sin(time)*0.2+0.3,time);"
      DATA "vec3 ori = vec3(0.0,3.5,time*5.0);"
      DATA "vec3 dir = normalize(vec3(uv.xy,-2.0)); dir.z += length(uv) * 0.15;"
      DATA "dir = normalize(dir) * fromEuler(ang);"

      DATA "// tracing"
      DATA "vec3 p;"
      DATA "heightMapTracing(ori,dir,p);"
      DATA "vec3 dist = p - ori;"
      DATA "vec3 n = getNormal(p, dot(dist,dist) * EPSILON_NRM);"
      DATA "vec3 light = normalize(vec3(0.0,1.0,0.8));"

      DATA "// color"
      DATA "vec3 color = mix("
      DATA "getSkyColor(dir),"
      DATA "getSeaColor(p,n,light,dir,dist),"
      DATA "pow(smoothstep(0.0,-0.05,dir.y),0.3));"

      DATA "// post"
      DATA "gl_FragColor = vec4(pow(color,vec3(0.75)), 1.0);"
      DATA "}"
      DATA ""
David Williams

Re: The power of GPU shaders

Post by David Williams »

guest wrote: Fri 04 Jan 2019, 11:08
David Williams wrote: Thu 23 Aug 2018, 16:54Exercise for the interested (and clever) BBC BASIC user: port this 'Seascape' shader/demo to BBCSDL
Here you go! It needs a relatively powerful GPU for full frame rate (and runs extremely slowly on a Raspberry Pi). A complete code listing is below.
Brilliant! :D

Whilst I'm not getting consistent results across my 3 PCs (which of course is beyond your control), it runs great on my desktop PC. I haven't yet tried it on my Raspberry Pi 3B+.

Thanks for completing the 'exercise' !


David.
--
guest

Re: The power of GPU shaders

Post by guest »

David Williams wrote: Fri 04 Jan 2019, 18:21Whilst I'm not getting consistent results across my 3 PCs (which of course is beyond your control), it runs great on my desktop PC.
It's a tough ask for the GPU to perform that calculation on every single pixel, which is what is being demanded of it! I've deliberately set the window quite small (640 x 480) because obviously the computational load scales with the total pixel count, and will likely exceed the capabilities of even the fastest graphics card if you enlarge it too much.
I haven't yet tried it on my Raspberry Pi 3B+.
It doesn't even manage 1 fps on my 3B, but it's evidently using what limited shader resources the RPi GPU has because the CPU load remains very low throughout.

Are there any other examples at ShaderToy that you think would be worth porting to BBCSDL? Having done one it might not be too difficult to do others, but that remains to be seen.
David Williams

Re: The power of GPU shaders

Post by David Williams »

guest wrote: Fri 04 Jan 2019, 18:53 there any other examples at ShaderToy that you think would be worth porting to BBCSDL? Having done one it might not be too difficult to do others, but that remains to be seen.
This one looks nice:

"Clouds"
https://www.shadertoy.com/view/XslGRr
guest

Re: The power of GPU shaders

Post by guest »

David Williams wrote: Fri 04 Jan 2019, 20:14This one looks nice: "Clouds"
That's not entirely procedural, it uses an input texture ('iChannel0'). Do you know how to obtain a copy? Does one have to be signed in to download it? Can you get it for me?

Edit: Never mind, I found that there was a download link in the comments.
guest

Re: The power of GPU shaders

Post by guest »

Once I'd found the texture the Clouds demo posed no particular problems, except that it seems to be even more demanding on the GPU than the Seascape! Unusually, I found that trying to record the output - for uploading to YouTube - reduced the frame rate and I ended up having to do it on a faster PC. It's likely that the MPEG-4 video encoding uses the GPU and might therefore be competing for resources with the shader code.

http://www.youtube.com/watch?v=gqe5PGzROos

Code: Select all

      REM 'Volumetric Clouds' by Inigo Quilez, www.iquilezles.org, 2013
      REM BBCSDL version by Richard Russell, www.rtrussell.co.uk, 04-Jan-2019

      REM Fix window size (if the version of SDL allows):
      IF @platform% >= &2000500 THEN
        SYS "SDL_SetWindowResizable", @hwnd%, 0, @memhdc%
      ENDIF

      REM Initialise window:
      ScrW% = 640
      ScrH% = 480
      VDU 23,22,ScrW%;ScrH%;8,16,16,128

      REM Install libraries:
      INSTALL @lib$ + "ogllib"

      REM Create arrays:
      DIM Object%(0), nVert%(0), vFormat%(0), vSize%(0), Material%(0), Texture%(0)
      DIM Pan(0), Tilt(0), Roll(0), Xpos(0), Ypos(0), Zpos(0), Camera(2), LookAt(2)
      DIM Light%(0), Vertex$(10), Fragment$(1000)

      REM Fill vertex and fragment shader arrays from DATA statements:
      PROCreadshader(Vertex$())
      PROCreadshader(Fragment$())

      REM Create simple rectangular object:
      F% = OPENOUT(@tmp$+"rectangle.fvf")
      BPUT#F%,6 MOD 256   : BPUT#F%,6 DIV 256 : BPUT#F%,0 : BPUT#F%,0 : REM vertex count
      BPUT#F%,2 : BPUT#F%,0 : BPUT#F%,12 : BPUT#F%,0 : REM vertex format and size
      PROC4(F%,+ScrW%/ScrH%) : PROC4(F%,+1) : PROC4(F%,0)
      PROC4(F%,-ScrW%/ScrH%) : PROC4(F%,+1) : PROC4(F%,0)
      PROC4(F%,-ScrW%/ScrH%) : PROC4(F%,-1) : PROC4(F%,0)
      PROC4(F%,-ScrW%/ScrH%) : PROC4(F%,-1) : PROC4(F%,0)
      PROC4(F%,+ScrW%/ScrH%) : PROC4(F%,-1) : PROC4(F%,0)
      PROC4(F%,+ScrW%/ScrH%) : PROC4(F%,+1) : PROC4(F%,0)
      CLOSE #F%

      REM Ensure cleanup on exit:
      ON CLOSE PROCcleanup : QUIT
      ON ERROR PROCcleanup : IF ERR=17 CHAIN @lib$+"../examples/tools/touchide" ELSE MODE 3 : PRINT REPORT$ : END

      REM Initialise OpenGL:
      pDevice% = FN_initgl(@hwnd%, 1, 0)
      IF pDevice% = 0 ERROR 100, "Couldn't initialise OpenGL"

      REM Get addresses of OpenGLfunctions:
      `glCreateShader`      = FNgpa("glCreateShader")
      IF `glCreateShader` = 0 ERROR 100, "OpenGL shaders are not available on this platform"
      `glAttachShader`      = FNgpa("glAttachShader")
      `glDeleteShader`      = FNgpa("glDeleteShader")
      `glShaderSource`      = FNgpa("glShaderSource")
      `glCompileShader`     = FNgpa("glCompileShader")
      `glGetShaderiv`       = FNgpa("glGetShaderiv")
      `glCreateProgram`     = FNgpa("glCreateProgram")
      `glLinkProgram`       = FNgpa("glLinkProgram")
      `glGetProgramiv`      = FNgpa("glGetProgramiv")
      `glGetShaderInfoLog`  = FNgpa("glGetShaderInfoLog")
      `glGetProgramInfoLog` = FNgpa("glGetProgramInfoLog")
      `glUseProgram`        = FNgpa("glUseProgram")
      `glGetUniformLocation`= FNgpa("glGetUniformLocation")
      `glActiveTexture`     = FNgpa("glActiveTexture")
      `glUniform1fv`        = FNgpa("glUniform1fv")
      `glUniform1i`         = FNgpa("glUniform1i")
      `glUniform2i`         = FNgpa("glUniform2i")

      REM Set OpenGL constants:
      GL_FRAGMENT_SHADER = &8B30
      GL_VERTEX_SHADER = &8B31
      GL_COMPILE_STATUS = &8B81
      GL_LINK_STATUS = &8B82
      GL_INFO_LOG_LENGTH = &8B84
      GL_TEXTURE0 = &84C0

      REM Create shader objects:
      SYS `glCreateShader`, GL_VERTEX_SHADER, @memhdc% TO oVertex%
      SYS `glCreateShader`, GL_FRAGMENT_SHADER, @memhdc% TO oFragment%

      REM Compile shaders:
      PROCcompileshader(oVertex%, Vertex$())
      PROCcompileshader(oFragment%, Fragment$())

      REM Create program object and link:
      SYS `glCreateProgram`, @memhdc% TO ProgramObject%
      SYS `glAttachShader`, ProgramObject%, oVertex%, @memhdc%
      SYS `glAttachShader`, ProgramObject%, oFragment%, @memhdc%
      SYS `glLinkProgram`, ProgramObject%, @memhdc%
      SYS `glGetProgramiv`, ProgramObject%, GL_LINK_STATUS, ^linked%, @memhdc%
      IF linked% = 0 THEN
        SYS `glGetProgramiv`, ProgramObject%, GL_INFO_LOG_LENGTH, ^blen%, @memhdc%
        DIM plog%% blen%
        SYS `glGetProgramInfoLog`, ProgramObject%, blen%, ^slen%, plog%%, @memhdc%
        ERROR 100, "Program object failed to link:" + CHR$&D + CHR$&A + LEFT$($$plog%%,220)
      ENDIF

      REM Load dummy 3D object:
      Object%(0) = FN_load3d(pDevice%, @tmp$+"rectangle.fvf", nVert%(0), vFormat%(0), vSize%(0))
      IF Object%(0) = 0 ERROR 100, "Couldn't load rectangle.fvf"

      REM Use shaders:
      SYS `glUseProgram`, ProgramObject%, @memhdc%
      SYS `glGetUniformLocation`, ProgramObject%, "iTime",       @memhdc% TO pTime%
      SYS `glGetUniformLocation`, ProgramObject%, "iMouse",      @memhdc% TO pMouse%
      SYS `glGetUniformLocation`, ProgramObject%, "iResolution", @memhdc% TO pResolution%
      SYS `glGetUniformLocation`, ProgramObject%, "iChannel0",   @memhdc% TO pChannel0%

      REM Load and bind texture to uniform:
      SYS `glUniform1i`, pChannel0%, 0, @memhdc%
      SYS `glActiveTexture`, GL_TEXTURE0, @memhdc%
      Texture%(0) = FN_loadtexture(pDevice%, @dir$ + "clouds.png")
      IF Texture%(0) = 0 ERROR 100, "Couldn't load clouds.png"

      REM Render:
      fov = 0.4
      mindist = 0.1
      maxdist = 10
      camroll = 0
      Camera() = 0, 0, -5.0
      LookAt() = 0, 0, 0
      aspect = ScrW% / ScrH%
      REPEAT
        MOUSE X%,Y%,B%
        ftime% = FN_f4(TIME/100)
        SYS `glUniform1fv`, pTime%, 1, ^ftime%, @memhdc%
        SYS `glUniform2i`, pMouse%, X%, Y%, @memhdc%
        SYS `glUniform2i`, pResolution%, ScrW%, ScrH%, @memhdc%
        PROC_render(pDevice%, &00385080, 0, Light%(), 1, Material%(), Texture%(), Object%(), \
        \           nVert%(), vFormat%(), vSize%(), Pan(), Tilt(), Roll(), Xpos(), Ypos(), Zpos(), \
        \           Camera(), LookAt(), fov, aspect, mindist, maxdist, camroll)
        WAIT 2
      UNTIL FALSE
      END

      DEF PROCcleanup
      ON ERROR OFF
      *REFRESH ON
      IF !^Texture%() : IF Texture%(0)PROC_release(Texture%(0))
      IF !^Object%()  : IF Object%(0) PROC_release(Object%(0))
      oVertex% += 0   : IF oVertex%   SYS `glDeleteShader`, oVertex%, @memhdc%
      oFragment% += 0 : IF oFragment% SYS `glDeleteShader`, oFragment%, @memhdc%
      pDevice% += 0   : IF pDevice%   PROC_release(pDevice%)
      ENDPROC

      DEF PROCreadshader(shader$())
      LOCAL I%, a$
      REPEAT
        READ a$
        shader$(I%) = a$ + CHR$&A : REM LF-terminate
        I% += 1
      UNTIL a$ = ""
      ENDPROC

      DEF PROCcompileshader(object%, shader$())
      LOCAL compiled%, blen%, slen%, code%%, plog%%, code$
      code$ = SUM(shader$()) + CHR$0
      code%% = PTR(code$)
      SYS `glShaderSource`, object%, 1, ^code%%, FALSE, @memhdc%
      SYS `glCompileShader`, object%, @memhdc%
      SYS `glGetShaderiv`, object%, GL_COMPILE_STATUS, ^compiled%, @memhdc%
      IF compiled% = 0 THEN
        SYS `glGetShaderiv`, object%, GL_INFO_LOG_LENGTH, ^blen%, @memhdc%
        DIM plog%% blen%
        SYS `glGetShaderInfoLog`, object%, blen%, ^slen%, plog%%, @memhdc%
        ERROR 100, "Shader failed to compile:" + CHR$&D + CHR$&A + LEFT$($$plog%%,220)
      ENDIF
      ENDPROC

      DEF FNgpa(function$)
      LOCAL function%%
      SYS "SDL_GL_GetProcAddress", function$, @memhdc% TO function%%
      IF @platform% AND &40 ELSE function%% = !^function%%
      = function%%

      DEF PROC4(F%,a) : LOCAL A% : A%=FN_f4(a)
      BPUT #F%,A% : BPUT #F%,A%>>8 : BPUT#F%,A%>>16 : BPUT#F%,A%>>24
      ENDPROC

      REM Minimal 'default' vertex shader:
      DATA "#version 120"
      DATA "void main()"
      DATA "{"
      DATA "gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;"
      DATA "}"
      DATA ""

      REM Fragment Shader code from https://www.shadertoy.com/view/XslGRr
      DATA "#version 120"
      DATA "uniform float iTime;"
      DATA "uniform ivec2 iMouse;"
      DATA "uniform ivec2 iResolution;"
      DATA "uniform sampler2D iChannel0;"

      DATA "// Created by inigo quilez - iq/2013"
      DATA "// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License."
      DATA "// Volumetric clouds. It performs level of detail (LOD) for faster rendering"

      DATA "float noise( in vec3 x )"
      DATA "{"
      DATA "vec3 p = floor(x);"
      DATA "vec3 f = fract(x);"
      DATA "f = f*f*(3.0-2.0*f);"

      DATA "vec2 uv = (p.xy+vec2(37.0,17.0)*p.z) + f.xy;"
      DATA "vec2 rg = texture2DLod( iChannel0, (uv+ 0.5)/256.0, 0. ).yx;"

      DATA "return -1.0+2.0*mix( rg.x, rg.y, f.z );"
      DATA "}"

      DATA "float map5( in vec3 p )"
      DATA "{"
      DATA "vec3 q = p - vec3(0.0,0.1,1.0)*iTime;"
      DATA "float f;"
      DATA "f  = 0.50000*noise( q ); q = q*2.02;"
      DATA "f += 0.25000*noise( q ); q = q*2.03;"
      DATA "f += 0.12500*noise( q ); q = q*2.01;"
      DATA "f += 0.06250*noise( q ); q = q*2.02;"
      DATA "f += 0.03125*noise( q );"
      DATA "return clamp( 1.5 - p.y - 2.0 + 1.75*f, 0.0, 1.0 );"
      DATA "}"

      DATA "float map4( in vec3 p )"
      DATA "{"
      DATA "vec3 q = p - vec3(0.0,0.1,1.0)*iTime;"
      DATA "float f;"
      DATA "f  = 0.50000*noise( q ); q = q*2.02;"
      DATA "f += 0.25000*noise( q ); q = q*2.03;"
      DATA "f += 0.12500*noise( q ); q = q*2.01;"
      DATA "f += 0.06250*noise( q );"
      DATA "return clamp( 1.5 - p.y - 2.0 + 1.75*f, 0.0, 1.0 );"
      DATA "}"
      DATA "float map3( in vec3 p )"
      DATA "{"
      DATA "vec3 q = p - vec3(0.0,0.1,1.0)*iTime;"
      DATA "float f;"
      DATA "f  = 0.50000*noise( q ); q = q*2.02;"
      DATA "f += 0.25000*noise( q ); q = q*2.03;"
      DATA "f += 0.12500*noise( q );"
      DATA "return clamp( 1.5 - p.y - 2.0 + 1.75*f, 0.0, 1.0 );"
      DATA "}"
      DATA "float map2( in vec3 p )"
      DATA "{"
      DATA "vec3 q = p - vec3(0.0,0.1,1.0)*iTime;"
      DATA "float f;"
      DATA "f  = 0.50000*noise( q ); q = q*2.02;"
      DATA "f += 0.25000*noise( q );;"
      DATA "return clamp( 1.5 - p.y - 2.0 + 1.75*f, 0.0, 1.0 );"
      DATA "}"

      DATA "vec3 sundir = normalize( vec3(-1.0,0.0,-1.0) );"

      DATA "vec4 integrate( in vec4 sum, in float dif, in float den, in vec3 bgcol, in float t )"
      DATA "{"
      DATA "// lighting"
      DATA "vec3 lin = vec3(0.65,0.7,0.75)*1.4 + vec3(1.0, 0.6, 0.3)*dif;"
      DATA "vec4 col = vec4( mix( vec3(1.0,0.95,0.8), vec3(0.25,0.3,0.35), den ), den );"
      DATA "col.xyz *= lin;"
      DATA "col.xyz = mix( col.xyz, bgcol, 1.0-exp(-0.003*t*t) );"
      DATA "// front to back blending"
      DATA "col.a *= 0.4;"
      DATA "col.rgb *= col.a;"
      DATA "return sum + col*(1.0-sum.a);"
      DATA "}"

      DATA "#define MARCH(S,M) for(int i=0;i<S;i++) {vec3 pos=ro+t*rd;if(pos.y<-3.0||pos.y>2.0||sum.a>0.99) break;float den=M(pos);if(den>0.01) {float dif=clamp((den-M(pos+0.3*sundir))/0.6,0.0,1.0);sum=integrate(sum,dif,den,bgcol,t);} t+=max(0.05,0.02*t);}"

      DATA "vec4 raymarch( in vec3 ro, in vec3 rd, in vec3 bgcol, in ivec2 px )"
      DATA "{"
      DATA "vec4 sum = vec4(0.0);"

      DATA "float t = 0.0;"

      DATA "MARCH(30,map5);"
      DATA "MARCH(30,map4);"
      DATA "MARCH(30,map3);"
      DATA "MARCH(30,map2);"

      DATA "return clamp( sum, 0.0, 1.0 );"
      DATA "}"

      DATA "mat3 setCamera( in vec3 ro, in vec3 ta, float cr )"
      DATA "{"
      DATA "vec3 cw = normalize(ta-ro);"
      DATA "vec3 cp = vec3(sin(cr), cos(cr),0.0);"
      DATA "vec3 cu = normalize( cross(cw,cp) );"
      DATA "vec3 cv = normalize( cross(cu,cw) );"
      DATA "return mat3( cu, cv, cw );"
      DATA "}"

      DATA "vec4 render( in vec3 ro, in vec3 rd, in ivec2 px )"
      DATA "{"
      DATA "// background sky"
      DATA "float sun = clamp( dot(sundir,rd), 0.0, 1.0 );"
      DATA "vec3 col = vec3(0.6,0.71,0.75) - rd.y*0.2*vec3(1.0,0.5,1.0) + 0.15*0.5;"
      DATA "col += 0.2*vec3(1.0,.6,0.1)*pow( sun, 8.0 );"

      DATA "// clouds"
      DATA "vec4 res = raymarch( ro, rd, col, px );"
      DATA "col = col*(1.0-res.w) + res.xyz;"

      DATA "// sun glare"
      DATA "col += 0.2*vec3(1.0,0.4,0.2)*pow( sun, 3.0 );"

      DATA "return vec4( col, 1.0 );"
      DATA "}"

      DATA "void main()"
      DATA "{"
      DATA "vec2 p = (-iResolution.xy + 2.0*gl_FragCoord.xy)/ iResolution.y;"

      DATA "vec2 m = iMouse.xy/iResolution.xy;"

      DATA "// camera"
      DATA "vec3 ro = 4.0*normalize(vec3(sin(3.0*m.x), 0.4*m.y, cos(3.0*m.x)));"
      DATA "vec3 ta = vec3(0.0, -1.0, 0.0);"
      DATA "mat3 ca = setCamera( ro, ta, 0.0 );"
      DATA "// ray"
      DATA "vec3 rd = ca * normalize( vec3(p.xy,1.5));"

      DATA "gl_FragColor = render( ro, rd, ivec2(gl_FragCoord-0.5) );"
      DATA "}"
      DATA ""
David Williams

Re: The power of GPU shaders

Post by David Williams »

guest wrote: Sat 05 Jan 2019, 13:53 Once I'd found the texture the Clouds demo posed no particular problems, except that it seems to be even more demanding on the GPU than the Seascape! Unusually, I found that trying to record the output - for uploading to YouTube - reduced the frame rate and I ended up having to do it on a faster PC. It's likely that the MPEG-4 video encoding uses the GPU and might therefore be competing for resources with the shader code.
Excellent, and thanks for the effort, and for providing the source code. When time permits, I will have a go at 'porting' some other shaders over at ShaderToy, sharing the results here (if I'm successful).


David.
--
guest

Re: The power of GPU shaders

Post by guest »

David Williams wrote: Sat 05 Jan 2019, 16:01When time permits, I will have a go at 'porting' some other shaders over at ShaderToy, sharing the results here (if I'm successful).
With the framework in place I'm hoping it will be relatively easy to port others (although I don't know how to do the ones that use multiple stages with an intermediate buffer, such as Volcanic).

Of course the real challenge for you is to learn enough about shader programming to write your own code as part of a really spectacular game! A nice side-effect of doing it via BBC BASIC is the immediacy of seeing the result of any code changes (one reason why I decided to put the shader source code in DATA statements rather than a separate file). 8-)

I expect you realise this, but as things stand it's not possible to port shader examples to Android or iOS because there's a fundamental incompatibility between supporting logical plotting operations (GCOL), which work in OpenGLES 1.1 but not OpenGLES 2.0, and shaders, which work in OpenGLES 2.0 but not OpenGLES 1.1. Simplistically one would need two different versions of BBCSDL: one with GCOL but not shaders and the other with shaders but not GCOL!
KenDown
Posts: 327
Joined: Wed 04 Apr 2018, 06:36

Re: The power of GPU shaders

Post by KenDown »

Sorry, but that's not BBC BASIC. For example, it falls over on @platform% and there is no "ogllib" library.