pulled out from my fork of upstream repo

This commit is contained in:
Jeff Carr 2024-02-19 07:16:10 -06:00
commit f4605dbbd1
9 changed files with 1086 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.swp
go.mod
go.sum

18
Makefile Normal file
View File

@ -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

133
README.md Normal file
View File

@ -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")

79
config.go Normal file
View File

@ -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"})
}

333
planetfall.glsl Normal file
View File

@ -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<dmin )
{
if( doColor )
{
color.w *= smoothstep(0.0, 5.0/s, dmin-d);
if( i==0 )
{
color.xyz = vec3(0.425,0.36,0.1)*1.1; // fall
//color.xyz = vec3(0.4,0.8,0.1); // summer
//color.xyz = vec3(0.4,0.4,0.8); // winter
}
color.zyx += 0.3*(1.0-sqrt(h))*sin(f.w*1111.0+vec3(0.0,1.0,2.0));
color.xyz = max(color.xyz,0.0);
}
dmin = d;
}
else
{
color.w *= 0.4*(0.1 + 0.9*smoothstep(0.0, 1.0/s, d-dmin));
}
}
return dmin;
}
// http://iquilezles.org/www/articles/normalsSDF/normalsSDF.htm
vec3 calcNormal( in vec3 pos, in float ep )
{
vec4 kk;
#if 0
vec2 e = vec2(1.0,-1.0)*0.5773;
return normalize( e.xyy*map( pos + e.xyy*ep, kk, false ) +
e.yyx*map( pos + e.yyx*ep, kk, false ) +
e.yxy*map( pos + e.yxy*ep, kk, false ) +
e.xxx*map( pos + e.xxx*ep, kk, false ) );
#else
// prevent the compiler from inlining map() 4 times
vec3 n = vec3(0.0);
for( int i=ZERO; i<4; i++ )
{
vec3 e = 0.5773*(2.0*vec3((((i+3)>>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; m<AA; m++ )
for( int n=ZERO; n<AA; n++ )
{
// pixel coordinates
vec2 o = vec2(float(m),float(n)) / float(AA) - 0.5;
vec2 p = (-uResolution.xy + 2.0*(gl_FragCoord.xy+o))/uResolution.y;
#else
vec2 p = (-uResolution.xy + 2.0*gl_FragCoord.xy)/uResolution.y;
#endif
// ray direction
vec3 rd = ca * normalize( vec3(p.xy,2.2) );
// background
vec3 col = vec3(0.1,0.14,0.18) + 0.1*rd.y;
// bounding volume
vec2 bound = sphIntersect( ro, rd, 2.1 );
if( bound.x>0.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 );
}

54
psutil.go Normal file
View File

@ -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
}

196
seascape.glsl Normal file
View File

@ -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);
}

86
seascape.go Normal file
View File

@ -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)
}

184
seascape_original.glsl Normal file
View File

@ -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);
}