From f4605dbbd165348362ea38b6313b0818196cc675 Mon Sep 17 00:00:00 2001 From: Jeff Carr Date: Mon, 19 Feb 2024 07:16:10 -0600 Subject: [PATCH] pulled out from my fork of upstream repo --- .gitignore | 3 + Makefile | 18 +++ README.md | 133 ++++++++++++++++ config.go | 79 ++++++++++ planetfall.glsl | 333 +++++++++++++++++++++++++++++++++++++++++ psutil.go | 54 +++++++ seascape.glsl | 196 ++++++++++++++++++++++++ seascape.go | 86 +++++++++++ seascape_original.glsl | 184 +++++++++++++++++++++++ 9 files changed, 1086 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 config.go create mode 100644 planetfall.glsl create mode 100644 psutil.go create mode 100644 seascape.glsl create mode 100644 seascape.go create mode 100644 seascape_original.glsl diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a630ed4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.swp +go.mod +go.sum diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e9f03b9 --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +# go get -u github.com/faiface/pixel-examples +# cd ~/go/src/github.com/faiface/pixel-examples/community/seascape-shader + +all: + GO111MODULE=off go build + ./seascape-shader + +push: + git pull + git add --all + -git commit -a -s + git push + +update: + git pull + +diff: + git diff diff --git a/README.md b/README.md new file mode 100644 index 0000000..7d1dea7 --- /dev/null +++ b/README.md @@ -0,0 +1,133 @@ +# Shadertoy to Pixel shader conversion + +This will show you an example how you can take an example from [shadertoy.com](shadertoy.com) and use it with the Pixel Shader support. + +["Seascape" by Alexander Alekseev aka TDM - 2014](https://www.shadertoy.com/view/Ms2SD1) is a nice one. It's very impressive and doesn't use textures so that simplifies things for us. Let's try that one. + +Looking at the seascape.glsl example you can see **iResolution**, **iTime** and **iMouse** in there. + +These are commonly needed to be exposed because these things are coming from outside and needs to be updated. Any other variable you need to have changed/updated from code can be exposed like those. + +## Command line arguments + +``` +./seascape-shader -h # will show you the command line options + +./seascape-shader -filename ./shaders/seascape.glsl # Seascape +./seascape-shader -filename ./shaders/planetfall.glsl # Planet Fall demo +``` + +## Exposing variables + +How to expose variables like this? + +Well, first we need to figure out what type of variables they are. Looking at the shader, you can see **iResolution.x** in there. This tells you that it's not a simple type. In this case it's a **vec2***, a **Vector containing 2 values, x and y**. This makes sense since resolution is described by x and y. That is, width and height. + +And thus, we create our variable in Go with like so: +``` +uResolution := mgl32.Vec2{float32(win.Bounds().W()), float32(win.Bounds().H())} +``` +That is, to be of the type **mgl32.Vec2**. Here we create it by taking the window's width and height. + +**iTime** is just a float, and thus that is just created like so: +``` +var uTime float32 +``` + +For the **iMouse**, it's a **mgl32.Vec4**, a **Vector containing 4 variables**. We only use x and y for the mouse position here though. +``` +var uMouse mgl32.Vec4 +``` + +And finally, to make our variables available in the shader itself we use: +``` +canvas.SetUniform(name string, value interface{}) +``` + +We create a handy function to do this: + +``` go +func EasyBindUniforms(c *pixelgl.Canvas, unifs ...interface{}) { + if len(unifs)%2 != 0 { + panic("needs to be divisable by 2") + } + for i := 0; i < len(unifs); i += 2 { + + c.SetUniform(unifs[i+0].(string), unifs[i+1]) + } +} +``` +and we call that function like so: + +``` go + EasyBindUniforms(canvas, + "uResolution", &uResolution, + "uTime", &uTime, + "uMouse", &uMouse, + ) +``` + +## Updating shader source file + +We also need to do some updates to the shader file itself to match these variables. First thing would be to add the variables we exposed in Go. + +``` +uniform vec2 uResolution; +uniform float uTime; +uniform vec4 uMouse; +``` +Then we just rename the variables to match. + +We also need to rename the main function itself, as the one used here is specific for use with shadertoy. For our shader, the entrypoint is main(). So we rename: +``` +void mainImage( out vec4 fragColor, in vec2 fragCoord ) +``` +to +``` +void main() { +``` + + +Also, rename: +``` +fragCoord +``` +to +``` +gl_FragCoord +``` +because this is available globaly in the OpenGL space for the shader. + +We also need to add: +``` +out vec4 fragColor; +``` + +to expose that. + +Lastly, we need to add: +``` +#version 330 core +``` +at the top to tell what version we require. + + +## Using shader +To use the shader in our canvas we do: +``` +canvas.SetFragmentShader(fragSource string) +``` +where fragSource is the fragment shader, not a path fo a file. + +# + +## Result converting shadertoy shader to use with Pixel + +Here is a diff of the changes: + +![code changes](shader_diffs.png "Code changes") + + +And that is it. Running the program we should see this: + +![seascape animation](seascape.gif "Seascape animation") diff --git a/config.go b/config.go new file mode 100644 index 0000000..da8313a --- /dev/null +++ b/config.go @@ -0,0 +1,79 @@ +package main + +/* + This simply parses the command line arguments using the default golang + package called 'flag'. This can be used as a simple template to parse + command line arguments in other programs. + + It puts everything in the 'config' package which I think is a good + wrapper around the 'flags' package and doesn't need a whole mess of + global variables +*/ + +import "log" +import "os" +import "flag" +import "fmt" +import "github.com/gookit/config" + +var customUsage = func() { + fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s:\n", os.Args[0]) + flag.PrintDefaults() + + fmt.Println("") + fmt.Println("EXAMPLES:") + fmt.Println("") + fmt.Println(os.Args[0] + " --width 1024 --height 768 --drift .1 --filename shaders/seascape.glsl") + fmt.Println(os.Args[0] + " --width 640 --height 480 --filename shaders/planetfall.glsl") + fmt.Println("") +} + +func parseFlags() { + var version string + var race bool + var filename string + var width int + var height int + var glDrift float64 + + flag.StringVar (&version, "version", "v0.1", "Set compiled in version string") + + flag.StringVar (&filename, "filename", "shaders/seascape.glsl", "path to GLSL file") + flag.IntVar (&width, "width", 1024, "Width of the OpenGL Window") + flag.IntVar (&height, "height", 768, "Height of the OpenGL Window") + + flag.Float64Var (&glDrift, "drift", 0.01, "Speed of the gradual camera drift") + flag.BoolVar (&race, "race", race, "Use race detector") + + // Set the output if something fails to stdout rather than stderr + flag.CommandLine.SetOutput(os.Stdout) + + flag.Usage = customUsage + flag.Parse() + + if flag.Parsed() { + log.Println("flag.Parse() worked") + } else { + log.Println("flag.Parse() failed") + } + + // keys := []string{"filename", "width", "height", "drift"} + // keys := []string{"width", "height", "drift"} + +// keys := []string{"height"} +// config.LoadFlags(keys) + + config.Set("width", width) + config.Set("height", height) + config.Set("glDrift", glDrift) + config.Set("filename", filename) +} + +func parseConfig() { + config.WithOptions(config.ParseEnv) + parseFlags() + + // config.LoadOSEnv([]string{"MAIL"}) + // config.LoadOSEnv([]string{"USER"}) + // config.LoadOSEnv([]string{"BUILDDEBUG"}) +} diff --git a/planetfall.glsl b/planetfall.glsl new file mode 100644 index 0000000..d1a74e6 --- /dev/null +++ b/planetfall.glsl @@ -0,0 +1,333 @@ +// Created by inigo quilez - iq/2018 +// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. + +// Pretty much a modification to Klems' shader (https://www.shadertoy.com/view/XlcfRs) +// Youtube version: https://www.youtube.com/watch?v=q1OBrqtl7Yo + +#version 330 core + +// Change AA to 1 if it renders too slow for you +#define AA 1 + +uniform vec2 uResolution; +uniform float uTime; // shader playback time (in seconds) +uniform vec4 uMouse; + +// there is tearing on my box. is this because this isn't working? -- jcarr +uniform int iFrame; // shader playback frame + +out vec4 fragColor; +// in vec2 fragCoord; + +mat3 makeBase( in vec3 w ) +{ + float k = inversesqrt(1.0-w.y*w.y); + return mat3( vec3(-w.z,0.0,w.x)*k, + vec3(-w.x*w.y,1.0-w.y*w.y,-w.y*w.z)*k, + w); +} + +#define ZERO (min(iFrame,0)) + +// http://iquilezles.org/www/articles/intersectors/intersectors.htm +vec2 sphIntersect( in vec3 ro, in vec3 rd, in float rad ) +{ + float b = dot( ro, rd ); + float c = dot( ro, ro ) - rad*rad; + float h = b*b - c; + if( h<0.0 ) return vec2(-1.0); + h = sqrt(h); + return vec2(-b-h,-b+h); +} + +// http://iquilezles.org/www/articles/distfunctions/distfunctions.htm +float sdCapsule( in vec3 p, in float b, in float r ) +{ + float h = clamp( p.z/b, 0.0, 1.0 ); + return length( p - vec3(0.0,0.0,b)*h ) - r;//*(0.2+1.6*h); +} + +// modified Keinert et al's inverse Spherical Fibonacci Mapping +vec4 inverseSF( in vec3 p, const in float n ) +{ + const float PI = 3.14159265359; + const float PHI = 1.61803398875; + + float phi = min(atan(p.y,p.x),PI); + float k = max(floor(log(n*PI*sqrt(5.0)*(1.-p.z*p.z))/log(PHI+1.)),2.0); + float Fk = pow(PHI,k)/sqrt(5.0); + vec2 F = vec2(round(Fk),round(Fk*PHI)); + vec2 G = PI*(fract((F+1.0)*PHI)-(PHI-1.0)); + + mat2 iB = mat2(F.y,-F.x,G.y,-G.x)/(F.y*G.x-F.x*G.y); + vec2 c = floor(iB*0.5*vec2(phi,n*p.z-n+1.0)); + + float ma = 0.0; + vec4 res = vec4(0); + for( int s=0; s<4; s++ ) + { + vec2 uv = vec2(s&1,s>>1); + float i = dot(F,uv+c); + float phi = 2.0*PI*fract(i*PHI); + float cT = 1.0 - (2.0*i+1.0)/n; + float sT = sqrt(1.0-cT*cT); + vec3 q = vec3(cos(phi)*sT, sin(phi)*sT,cT); + float a = dot(p,q); + if (a > ma) + { + ma = a; + res.xyz = q; + res.w = i; + } + } + return res; +} + +float map( in vec3 p, out vec4 color, const in bool doColor ) +{ + float lp = length(p); + float dmin = lp-1.0; + { + vec3 w = p/lp; + vec4 fibo = inverseSF(w, 700.0); + float hh = 1.0 - smoothstep(0.05,0.1,length(fibo.xyz-w)); + dmin -= 0.07*hh; + color = vec4(0.05,0.1,0.1,1.0)*hh * (1.0+0.5*sin(fibo.w*111.1)); + } + + + float s = 1.0; + + for( int i=0; i<3; i++ ) + { + float h = float(i)/float(3-1); + + vec4 f = inverseSF(normalize(p), 65.0 + h*75.0); + + // snap + p -= f.xyz; + + // orient to surface + p = p*makeBase(f.xyz); + + // scale + float scale = 6.6 + 2.0*sin(111.0*f.w); + p *= scale; + p.xy *= 1.2; + + //translate + p.z -= 3.0 - length(p.xy)*0.6*sin(f.w*212.1); + + // measure distance + s *= scale; + float d = sdCapsule( p, -6.0, 0.42 ); + d /= s; + + if( d>1)&1),((i>>1)&1),(i&1))-1.0); + n += e*map(pos+e*ep, kk, false); + } + return normalize(n); +#endif + +} + +// http://iquilezles.org/www/articles/rmshadows/rmshadows.htm +float calcSoftshadow( in vec3 ro, in vec3 rd, float tmin, float tmax, const float k ) +{ + vec2 bound = sphIntersect( ro, rd, 2.1 ); + tmin = max(tmin,bound.x); + tmax = min(tmax,bound.y); + + float res = 1.0; + float t = tmin; + for( int i=0; i<50; i++ ) + { + vec4 kk; + float h = map( ro + rd*t, kk, false ); + res = min( res, k*h/t ); + t += clamp( h, 0.02, 0.20 ); + if( res<0.005 || t>tmax ) break; + } + return clamp( res, 0.0, 1.0 ); +} + +float raycast(in vec3 ro, in vec3 rd, in float tmin, in float tmax ) +{ + vec4 kk; + float t = tmin; + for( int i=0; i<512; i++ ) + { + vec3 p = ro + t*rd; + float h = map(p,kk,false); + if( abs(h)<(0.15*t/uResolution.x) ) break; + t += h*0.5; + if( t>tmax ) return -1.0;; + } + //if( t>tmax ) t=-1.0; + + return t; +} + +// void mainImage( out vec4 fragColor, in vec2 fragCoord ) +// gl_FragCoord.xy +void main() +{ + float an = (uTime-10.0)*0.05; + + // camera + vec3 ro = vec3( 4.5*sin(an), 0.0, 4.5*cos(an) ); + vec3 ta = vec3( 0.0, 0.0, 0.0 ); + // camera-to-world rotation + mat3 ca = makeBase( normalize(ta-ro) ); + + // render + vec3 tot = vec3(0.0); + + #if AA>1 + for( int m=ZERO; m0.0 ) + { + // raycast + float t = raycast(ro, rd, bound.x, bound.y ); + if( t>0.0 ) + { + // local geometry + vec3 pos = ro + t*rd; + vec3 nor = calcNormal(pos, 0.01); + vec3 upp = normalize(pos); + + // color and occlusion + vec4 mate; map(pos, mate, true); + + // lighting + col = vec3(0.0); + + // key ligh + { + // dif + vec3 lig = normalize(vec3(1.0,0.0,0.7)); + float dif = clamp(0.5+0.5*dot(nor,lig),0.0,1.0); + float sha = calcSoftshadow( pos+0.0001*nor, lig, 0.0001, 2.0, 6.0 ); + col += mate.xyz*dif*vec3(1.8,0.6,0.5)*1.1*vec3(sha,sha*0.3+0.7*sha*sha,sha*sha); + // spec + vec3 hal = normalize(lig-rd); + float spe = clamp( dot(nor,hal), 0.0, 1.0 ); + float fre = clamp( dot(-rd,lig), 0.0, 1.0 ); + fre = 0.2 + 0.8*pow(fre,5.0); + spe *= spe; + spe *= spe; + spe *= spe; + col += 1.0*(0.25+0.75*mate.x)*spe*dif*sha*fre; + } + + // back light + { + vec3 lig = normalize(vec3(-1.0,0.0,0.0)); + float dif = clamp(0.5+0.5*dot(nor,lig),0.0,1.0); + col += mate.rgb*dif*vec3(1.2,0.9,0.6)*0.2*mate.w; + } + + // dome light + { + float dif = clamp(0.3+0.7*dot(nor,upp),0.0,1.0); + #if 0 + dif *= 0.05 + 0.95*calcSoftshadow( pos+0.0001*nor, upp, 0.0001, 1.0, 1.0 ); + col += mate.xyz*dif*5.0*vec3(0.1,0.1,0.3)*mate.w; + #else + col += mate.xyz*dif*3.0*vec3(0.1,0.1,0.3)*mate.w*(0.2+0.8*mate.w); + #endif + } + + // fake sss + { + float fre = clamp(1.0+dot(rd,nor),0.0,1.0); + col += 0.3*vec3(1.0,0.3,0.2)*mate.xyz*mate.xyz*fre*fre*mate.w; + } + + // grade/sss + { + col = 2.0*pow( col, vec3(0.7,0.85,1.0) ); + } + + // exposure control + col *= 0.7 + 0.3*smoothstep(0.0,25.0,abs(uTime-31.0)); + + // display fake occlusion + //col = mate.www; + } + } + + + // gamma + col = pow( col, vec3(0.4545) ); + + tot += col; + #if AA>1 + } + tot /= float(AA*AA); + #endif + + // vignetting + vec2 q = gl_FragCoord.xy/uResolution.xy; + tot *= pow( 16.0*q.x*q.y*(1.0-q.x)*(1.0-q.y), 0.2 ); + + fragColor = vec4( tot, 1.0 ); +} diff --git a/psutil.go b/psutil.go new file mode 100644 index 0000000..2fb370e --- /dev/null +++ b/psutil.go @@ -0,0 +1,54 @@ +package main + +import ( + "io/ioutil" + + "github.com/faiface/pixel" + "github.com/faiface/pixel/pixelgl" +) + +// Pixel Shader utility functions + +// EasyBindUniforms does all the work for you, just pass in a +// valid array adhering to format: String, Variable, ... +// +// example: +// +// var uTimeVar float32 +// var uMouseVar mgl32.Vec4 +// +// EasyBindUniforms(win.GetCanvas(), +// "u_time", &uTimeVar, +// "u_mouse", &uMouseVar, +// ) +// +func EasyBindUniforms(c *pixelgl.Canvas, unifs ...interface{}) { + if len(unifs)%2 != 0 { + panic("needs to be divisable by 2") + } + for i := 0; i < len(unifs); i += 2 { + + c.SetUniform(unifs[i+0].(string), unifs[i+1]) + } +} + +// CenterWindow will... center the window +func CenterWindow(win *pixelgl.Window) { + x, y := pixelgl.PrimaryMonitor().Size() + width, height := win.Bounds().Size().XY() + win.SetPos( + pixel.V( + x/2-width/2, + y/2-height/2, + ), + ) +} + +// LoadFileToString loads the contents of a file into a string +func LoadFileToString(filename string) (string, error) { + b, err := ioutil.ReadFile(filename) + if err != nil { + return "", err + } + return string(b), nil +} diff --git a/seascape.glsl b/seascape.glsl new file mode 100644 index 0000000..7a69f40 --- /dev/null +++ b/seascape.glsl @@ -0,0 +1,196 @@ +/* + * "Seascape" by Alexander Alekseev aka TDM - 2014 + * License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. + * Contact: tdmaav@gmail.com + */ + +#version 330 core + +uniform vec2 uResolution; +uniform float uTime; +uniform vec4 uMouse; + +// This is how much you are drifting around. Zero means it only moves when the mouse moves +uniform float uDrift; + +out vec4 fragColor; + +const int NUM_STEPS = 8; +const float PI = 3.141592; +const float EPSILON = 1e-3; +#define EPSILON_NRM (0.1 / uResolution.x) + +// sea +const int ITER_GEOMETRY = 3; +const int ITER_FRAGMENT = 5; +const float SEA_HEIGHT = 0.6; +const float SEA_CHOPPY = 4.0; +const float SEA_SPEED = 0.8; +const float SEA_FREQ = 0.16; +const vec3 SEA_BASE = vec3(0.1,0.19,0.22); +const vec3 SEA_WATER_COLOR = vec3(0.8,0.9,0.6); +#define SEA_TIME (1.0 + uTime * SEA_SPEED) +const mat2 octave_m = mat2(1.6,1.2,-1.2,1.6); + +// math +mat3 fromEuler(vec3 ang) { + vec2 a1 = vec2(sin(ang.x),cos(ang.x)); + vec2 a2 = vec2(sin(ang.y),cos(ang.y)); + vec2 a3 = vec2(sin(ang.z),cos(ang.z)); + mat3 m; + 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); + m[1] = vec3(-a2.y*a1.x,a1.y*a2.y,a2.x); + 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); + return m; +} +float hash( vec2 p ) { + float h = dot(p,vec2(127.1,311.7)); + return fract(sin(h)*43758.5453123); +} +float noise( in vec2 p ) { + vec2 i = floor( p ); + vec2 f = fract( p ); + vec2 u = f*f*(3.0-2.0*f); + return -1.0+2.0*mix( mix( hash( i + vec2(0.0,0.0) ), + hash( i + vec2(1.0,0.0) ), u.x), + mix( hash( i + vec2(0.0,1.0) ), + hash( i + vec2(1.0,1.0) ), u.x), u.y); +} + +// lighting +float diffuse(vec3 n,vec3 l,float p) { + return pow(dot(n,l) * 0.4 + 0.6,p); +} +float specular(vec3 n,vec3 l,vec3 e,float s) { + float nrm = (s + 8.0) / (PI * 8.0); + return pow(max(dot(reflect(e,n),l),0.0),s) * nrm; +} + +// sky +vec3 getSkyColor(vec3 e) { + e.y = max(e.y,0.0); + return vec3(pow(1.0-e.y,2.0), 1.0-e.y, 0.6+(1.0-e.y)*0.4); +} + +// sea +float sea_octave(vec2 uv, float choppy) { + uv += noise(uv); + vec2 wv = 1.0-abs(sin(uv)); + vec2 swv = abs(cos(uv)); + wv = mix(wv,swv,wv); + return pow(1.0-pow(wv.x * wv.y,0.65),choppy); +} + +float map(vec3 p) { + float freq = SEA_FREQ; + float amp = SEA_HEIGHT; + float choppy = SEA_CHOPPY; + vec2 uv = p.xz; uv.x *= 0.75; + + float d, h = 0.0; + for(int i = 0; i < ITER_GEOMETRY; i++) { + d = sea_octave((uv+SEA_TIME)*freq,choppy); + d += sea_octave((uv-SEA_TIME)*freq,choppy); + h += d * amp; + uv *= octave_m; freq *= 1.9; amp *= 0.22; + choppy = mix(choppy,1.0,0.2); + } + return p.y - h; +} + +float map_detailed(vec3 p) { + float freq = SEA_FREQ; + float amp = SEA_HEIGHT; + float choppy = SEA_CHOPPY; + vec2 uv = p.xz; uv.x *= 0.75; + + float d, h = 0.0; + for(int i = 0; i < ITER_FRAGMENT; i++) { + d = sea_octave((uv+SEA_TIME)*freq,choppy); + d += sea_octave((uv-SEA_TIME)*freq,choppy); + h += d * amp; + uv *= octave_m; freq *= 1.9; amp *= 0.22; + choppy = mix(choppy,1.0,0.2); + } + return p.y - h; +} + +vec3 getSeaColor(vec3 p, vec3 n, vec3 l, vec3 eye, vec3 dist) { + float fresnel = clamp(1.0 - dot(n,-eye), 0.0, 1.0); + fresnel = pow(fresnel,3.0) * 0.65; + + vec3 reflected = getSkyColor(reflect(eye,n)); + vec3 refracted = SEA_BASE + diffuse(n,l,80.0) * SEA_WATER_COLOR * 0.12; + + vec3 color = mix(refracted,reflected,fresnel); + + float atten = max(1.0 - dot(dist,dist) * 0.001, 0.0); + color += SEA_WATER_COLOR * (p.y - SEA_HEIGHT) * 0.18 * atten; + + color += vec3(specular(n,l,eye,60.0)); + + return color; +} + +// tracing +vec3 getNormal(vec3 p, float eps) { + vec3 n; + n.y = map_detailed(p); + n.x = map_detailed(vec3(p.x+eps,p.y,p.z)) - n.y; + n.z = map_detailed(vec3(p.x,p.y,p.z+eps)) - n.y; + n.y = eps; + return normalize(n); +} + +float heightMapTracing(vec3 ori, vec3 dir, out vec3 p) { + float tm = 0.0; + float tx = 1000.0; + float hx = map(ori + dir * tx); + if(hx > 0.0) return tx; + float hm = map(ori + dir * tm); + float tmid = 0.0; + for(int i = 0; i < NUM_STEPS; i++) { + tmid = mix(tm,tx, hm/(hm-hx)); + p = ori + dir * tmid; + float hmid = map(p); + if(hmid < 0.0) { + tx = tmid; + hx = hmid; + } else { + tm = tmid; + hm = hmid; + } + } + return tmid; +} + +// main +void main() +{ + vec2 uv = gl_FragCoord.xy / uResolution.xy; + uv = uv * 2.0 - 1.0; + uv.x *= uResolution.x / uResolution.y; + float time = uTime * uDrift * 5 + uMouse.x*0.01; + + // ray + vec3 ang = vec3(sin(time*3.0)*0.1,sin(time)*0.2+0.3,time); + vec3 ori = vec3(0.0,3.5,time*5.0); + vec3 dir = normalize(vec3(uv.xy,-2.0)); dir.z += length(uv) * 0.15; + dir = normalize(dir) * fromEuler(ang); + + // tracing + vec3 p; + heightMapTracing(ori,dir,p); + vec3 dist = p - ori; + vec3 n = getNormal(p, dot(dist,dist) * EPSILON_NRM); + vec3 light = normalize(vec3(0.0,1.0,0.8)); + + // color + vec3 color = mix( + getSkyColor(dir), + getSeaColor(p,n,light,dir,dist), + pow(smoothstep(0.0,-0.05,dir.y),0.3)); + + // post + fragColor = vec4(pow(color,vec3(0.75)), 1.0); +} diff --git a/seascape.go b/seascape.go new file mode 100644 index 0000000..abfee6e --- /dev/null +++ b/seascape.go @@ -0,0 +1,86 @@ +package main + +import ( + "time" + + "github.com/go-gl/mathgl/mgl32" + + "github.com/faiface/pixel" + "github.com/faiface/pixel/pixelgl" + "golang.org/x/image/colornames" +) + +import "log" +import "github.com/gookit/config" + +func run() { + // Set up window configs + log.Println("width = ", config.Int("width"), "height = ", config.String("height")) + cfg := pixelgl.WindowConfig{ // Default: 1024 x 768 + Title: "Golang GLSL", + Bounds: pixel.R(0, 0, config.Float("width"), config.Float("height")), + VSync: true, + } + + win, err := pixelgl.NewWindow(cfg) + if err != nil { + panic(err) + } + + camVector := win.Bounds().Center() + + bounds := win.Bounds() + bounds.Max = bounds.Max.ScaledXY(pixel.V(1.0, 1.0)) + + // I am putting all shader example initializing stuff here for + // easier reference to those learning to use this functionality + + log.Println("Load GSGL file = ", config.String("filename")) + fragSource, err := LoadFileToString(config.String("filename")) + + if err != nil { + panic(err) + } + + var uMouse mgl32.Vec4 + var uTime float32 + log.Println("glDrift = ", config.String("glDrift")) + var glDrift float32 = float32(config.Float("glDrift")) + + canvas := win.Canvas() + uResolution := mgl32.Vec2{float32(win.Bounds().W()), float32(win.Bounds().H())} + + EasyBindUniforms(canvas, + "uResolution", &uResolution, + "uTime", &uTime, + "uMouse", &uMouse, + "uDrift", &glDrift, + ) + + canvas.SetFragmentShader(fragSource) + + start := time.Now() + + // Game Loop + for !win.Closed() { + uTime = float32(time.Since(start).Seconds()) + mpos := win.MousePosition() + uMouse[0] = float32(mpos.X) + uMouse[1] = float32(mpos.Y) + + win.Clear(colornames.Black) + + // Drawing to the screen + canvas.Draw(win, pixel.IM.Moved(camVector)) + + win.Update() + } + +} + +func main() { + // This parses the command line arguments + parseConfig() + + pixelgl.Run(run) +} diff --git a/seascape_original.glsl b/seascape_original.glsl new file mode 100644 index 0000000..b70f887 --- /dev/null +++ b/seascape_original.glsl @@ -0,0 +1,184 @@ +/* + * "Seascape" by Alexander Alekseev aka TDM - 2014 + * License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. + * Contact: tdmaav@gmail.com + */ + +const int NUM_STEPS = 8; +const float PI = 3.141592; +const float EPSILON = 1e-3; +#define EPSILON_NRM (0.1 / iResolution.x) + +// sea +const int ITER_GEOMETRY = 3; +const int ITER_FRAGMENT = 5; +const float SEA_HEIGHT = 0.6; +const float SEA_CHOPPY = 4.0; +const float SEA_SPEED = 0.8; +const float SEA_FREQ = 0.16; +const vec3 SEA_BASE = vec3(0.1,0.19,0.22); +const vec3 SEA_WATER_COLOR = vec3(0.8,0.9,0.6); +#define SEA_TIME (1.0 + iTime * SEA_SPEED) +const mat2 octave_m = mat2(1.6,1.2,-1.2,1.6); + +// math +mat3 fromEuler(vec3 ang) { + vec2 a1 = vec2(sin(ang.x),cos(ang.x)); + vec2 a2 = vec2(sin(ang.y),cos(ang.y)); + vec2 a3 = vec2(sin(ang.z),cos(ang.z)); + mat3 m; + 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); + m[1] = vec3(-a2.y*a1.x,a1.y*a2.y,a2.x); + 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); + return m; +} +float hash( vec2 p ) { + float h = dot(p,vec2(127.1,311.7)); + return fract(sin(h)*43758.5453123); +} +float noise( in vec2 p ) { + vec2 i = floor( p ); + vec2 f = fract( p ); + vec2 u = f*f*(3.0-2.0*f); + return -1.0+2.0*mix( mix( hash( i + vec2(0.0,0.0) ), + hash( i + vec2(1.0,0.0) ), u.x), + mix( hash( i + vec2(0.0,1.0) ), + hash( i + vec2(1.0,1.0) ), u.x), u.y); +} + +// lighting +float diffuse(vec3 n,vec3 l,float p) { + return pow(dot(n,l) * 0.4 + 0.6,p); +} +float specular(vec3 n,vec3 l,vec3 e,float s) { + float nrm = (s + 8.0) / (PI * 8.0); + return pow(max(dot(reflect(e,n),l),0.0),s) * nrm; +} + +// sky +vec3 getSkyColor(vec3 e) { + e.y = max(e.y,0.0); + return vec3(pow(1.0-e.y,2.0), 1.0-e.y, 0.6+(1.0-e.y)*0.4); +} + +// sea +float sea_octave(vec2 uv, float choppy) { + uv += noise(uv); + vec2 wv = 1.0-abs(sin(uv)); + vec2 swv = abs(cos(uv)); + wv = mix(wv,swv,wv); + return pow(1.0-pow(wv.x * wv.y,0.65),choppy); +} + +float map(vec3 p) { + float freq = SEA_FREQ; + float amp = SEA_HEIGHT; + float choppy = SEA_CHOPPY; + vec2 uv = p.xz; uv.x *= 0.75; + + float d, h = 0.0; + for(int i = 0; i < ITER_GEOMETRY; i++) { + d = sea_octave((uv+SEA_TIME)*freq,choppy); + d += sea_octave((uv-SEA_TIME)*freq,choppy); + h += d * amp; + uv *= octave_m; freq *= 1.9; amp *= 0.22; + choppy = mix(choppy,1.0,0.2); + } + return p.y - h; +} + +float map_detailed(vec3 p) { + float freq = SEA_FREQ; + float amp = SEA_HEIGHT; + float choppy = SEA_CHOPPY; + vec2 uv = p.xz; uv.x *= 0.75; + + float d, h = 0.0; + for(int i = 0; i < ITER_FRAGMENT; i++) { + d = sea_octave((uv+SEA_TIME)*freq,choppy); + d += sea_octave((uv-SEA_TIME)*freq,choppy); + h += d * amp; + uv *= octave_m; freq *= 1.9; amp *= 0.22; + choppy = mix(choppy,1.0,0.2); + } + return p.y - h; +} + +vec3 getSeaColor(vec3 p, vec3 n, vec3 l, vec3 eye, vec3 dist) { + float fresnel = clamp(1.0 - dot(n,-eye), 0.0, 1.0); + fresnel = pow(fresnel,3.0) * 0.65; + + vec3 reflected = getSkyColor(reflect(eye,n)); + vec3 refracted = SEA_BASE + diffuse(n,l,80.0) * SEA_WATER_COLOR * 0.12; + + vec3 color = mix(refracted,reflected,fresnel); + + float atten = max(1.0 - dot(dist,dist) * 0.001, 0.0); + color += SEA_WATER_COLOR * (p.y - SEA_HEIGHT) * 0.18 * atten; + + color += vec3(specular(n,l,eye,60.0)); + + return color; +} + +// tracing +vec3 getNormal(vec3 p, float eps) { + vec3 n; + n.y = map_detailed(p); + n.x = map_detailed(vec3(p.x+eps,p.y,p.z)) - n.y; + n.z = map_detailed(vec3(p.x,p.y,p.z+eps)) - n.y; + n.y = eps; + return normalize(n); +} + +float heightMapTracing(vec3 ori, vec3 dir, out vec3 p) { + float tm = 0.0; + float tx = 1000.0; + float hx = map(ori + dir * tx); + if(hx > 0.0) return tx; + float hm = map(ori + dir * tm); + float tmid = 0.0; + for(int i = 0; i < NUM_STEPS; i++) { + tmid = mix(tm,tx, hm/(hm-hx)); + p = ori + dir * tmid; + float hmid = map(p); + if(hmid < 0.0) { + tx = tmid; + hx = hmid; + } else { + tm = tmid; + hm = hmid; + } + } + return tmid; +} + +// main +void mainImage( out vec4 fragColor, in vec2 fragCoord ) { + vec2 uv = fragCoord.xy / iResolution.xy; + uv = uv * 2.0 - 1.0; + uv.x *= iResolution.x / iResolution.y; + float time = iTime * 0.3 + iMouse.x*0.01; + + // ray + vec3 ang = vec3(sin(time*3.0)*0.1,sin(time)*0.2+0.3,time); + vec3 ori = vec3(0.0,3.5,time*5.0); + vec3 dir = normalize(vec3(uv.xy,-2.0)); dir.z += length(uv) * 0.15; + dir = normalize(dir) * fromEuler(ang); + + // tracing + vec3 p; + heightMapTracing(ori,dir,p); + vec3 dist = p - ori; + vec3 n = getNormal(p, dot(dist,dist) * EPSILON_NRM); + vec3 light = normalize(vec3(0.0,1.0,0.8)); + + // color + vec3 color = mix( + getSkyColor(dir), + getSeaColor(p,n,light,dir,dist), + pow(smoothstep(0.0,-0.05,dir.y),0.3)); + + // post + fragColor = vec4(pow(color,vec3(0.75)), 1.0); +} \ No newline at end of file