diff --git a/shader/.gitignore b/shader/.gitignore new file mode 100644 index 0000000..d4b20d9 --- /dev/null +++ b/shader/.gitignore @@ -0,0 +1,2 @@ +*.exe +.vscode \ No newline at end of file diff --git a/shader/LICENSE b/shader/LICENSE new file mode 100644 index 0000000..60cde56 --- /dev/null +++ b/shader/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 thegtproject + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/shader/README.md b/shader/README.md new file mode 100644 index 0000000..efbd5a1 --- /dev/null +++ b/shader/README.md @@ -0,0 +1,20 @@ +# Examples for Pixel's new custom fragment shader support + +A few examples on how to implement custom shaders in Pixel. Please note I am not good with graphics, so hopefully someone more talented than I come up with better demos soon :) + + +## fast radial blurring effect + +![](/fastblur.gif) + +## long exposure-ish effect + +![](/exposure.gif) + +## grayscale (from wiki tutorial) + +![](/grayscale.png) + +## wavy (from wiki tutorial) + +![](/wavy.gif) \ No newline at end of file diff --git a/shader/assets/images/thegopherproject.png b/shader/assets/images/thegopherproject.png new file mode 100644 index 0000000..5ffee86 Binary files /dev/null and b/shader/assets/images/thegopherproject.png differ diff --git a/shader/assets/shaders/exposure.frag.glsl b/shader/assets/shaders/exposure.frag.glsl new file mode 100644 index 0000000..4a7e02f --- /dev/null +++ b/shader/assets/shaders/exposure.frag.glsl @@ -0,0 +1,25 @@ +#version 330 core + +// Keep in mind, I have very little idea what I'm doing when it comes +// to these shaders, so take what you see here with a grain of salt. + +out vec4 fragColor; + +// Pixel default uniforms +uniform vec4 uTexBounds; +uniform sampler2D uTexture; +uniform sampler2D uBackBuffer; + +// Our custom uniforms +uniform float uAmount; + +void main() { + // It is often very useful to normalize the fragment coordinate. Usually + // represented as "uv" we do so here: + vec2 uv = gl_FragCoord.xy / uTexBounds.zw; + fragColor = texture(uTexture, uv); + + // uAmount is programmed to be adjustable with the left and right keys + // inside of Pixel + fragColor *= texture(uBackBuffer, uv).a * uAmount; +} diff --git a/shader/assets/shaders/fastblur.frag.glsl b/shader/assets/shaders/fastblur.frag.glsl new file mode 100644 index 0000000..9dbaaf6 --- /dev/null +++ b/shader/assets/shaders/fastblur.frag.glsl @@ -0,0 +1,95 @@ +#version 330 core + +// base shader code from https://www.shadertoy.com/view/XssSDs + +in vec2 vTexCoords; + +out vec4 fragColor; + +// Pixel default uniforms +uniform vec4 uTexBounds; +uniform sampler2D uTexture; + +// Our custom uniforms +uniform float uTime; +uniform vec4 uMouse; + +vec2 Circle(float Start, float Points, float Point) +{ + float Rad = (3.141592 * 2.0 * (1.0 / Points)) * (Point + Start); + return vec2(sin(Rad), cos(Rad)); +} + +void main() +{ + // It is often very useful to normalize the fragment coordinate. Usually + // represented as "uv" we do so here: + // + // Normalize the fragments's position, this is the location we use to sample + // our two textures/buffers. Note: Pixel passes resolution info through + // uTexBounds.zw (x, y) + vec2 uv = gl_FragCoord.xy / uTexBounds.zw; + + + vec2 PixelOffset = 1.0 / uTexBounds.zw; + float Start = 4.0 / 14.0; + vec2 Scale = 0.66 * 4.0 * 2.0 * PixelOffset.xy; + + vec3 N0 = texture(uTexture, uv + Circle(Start, 14.0, 0.0) * Scale).rgb; + vec3 N1 = texture(uTexture, uv + Circle(Start, 14.0, 1.0) * Scale).rgb; + vec3 N2 = texture(uTexture, uv + Circle(Start, 14.0, 2.0) * Scale).rgb; + vec3 N3 = texture(uTexture, uv + Circle(Start, 14.0, 3.0) * Scale).rgb; + vec3 N4 = texture(uTexture, uv + Circle(Start, 14.0, 4.0) * Scale).rgb; + vec3 N5 = texture(uTexture, uv + Circle(Start, 14.0, 5.0) * Scale).rgb; + vec3 N6 = texture(uTexture, uv + Circle(Start, 14.0, 6.0) * Scale).rgb; + vec3 N7 = texture(uTexture, uv + Circle(Start, 14.0, 7.0) * Scale).rgb; + vec3 N8 = texture(uTexture, uv + Circle(Start, 14.0, 8.0) * Scale).rgb; + vec3 N9 = texture(uTexture, uv + Circle(Start, 14.0, 9.0) * Scale).rgb; + vec3 N10 = texture(uTexture, uv + Circle(Start, 14.0, 10.0) * Scale).rgb; + vec3 N11 = texture(uTexture, uv + Circle(Start, 14.0, 11.0) * Scale).rgb; + vec3 N12 = texture(uTexture, uv + Circle(Start, 14.0, 12.0) * Scale).rgb; + vec3 N13 = texture(uTexture, uv + Circle(Start, 14.0, 13.0) * Scale).rgb; + vec3 N14 = texture(uTexture, uv).rgb; + + float W = 1.0 / 15.0; + + vec3 color = vec3(0,0,0); + + color.rgb = + (N0 * W) + + (N1 * W) + + (N2 * W) + + (N3 * W) + + (N4 * W) + + (N5 * W) + + (N6 * W) + + (N7 * W) + + (N8 * W) + + (N9 * W) + + (N10 * W) + + (N11 * W) + + (N12 * W) + + (N13 * W) + + (N14 * W); + + // curTexColor is the value of the current fragment color + // from Pixel's (the library) input texture. + vec4 curTexColor = texture(uTexture, uv); + + float xvalue = 0.0; + + // Left mouse button is currently pressed + if (uMouse[2] == 1.0) xvalue = uMouse[0] / uTexBounds.z; + + if(uv.x < xvalue) + { + color.rgb = curTexColor.rgb; + } + + // Draw a black verticle line between our two halves + // to distinguish unblurred and blurred + if(abs(uv.x - xvalue) < 0.0015) + color = vec3(0.0); + + fragColor = vec4(color.rgb, 1.0); +} diff --git a/shader/exposure.gif b/shader/exposure.gif new file mode 100644 index 0000000..384d1c0 Binary files /dev/null and b/shader/exposure.gif differ diff --git a/shader/exposure/main.go b/shader/exposure/main.go new file mode 100644 index 0000000..c928244 --- /dev/null +++ b/shader/exposure/main.go @@ -0,0 +1,170 @@ +package main + +// +// This example is my (thegtproject) own creation... I am not a designer- you've been warned. +// This is an attempt at performing a sort of "long-exposure" post effect. +// See ../assets/shaders/fastblur.frag.glsl for more details and comments +// + +import ( + "fmt" + "image/color" + "math" + "time" + + "github.com/faiface/pixel" + "github.com/faiface/pixel/imdraw" + "github.com/faiface/pixel/pixelgl" +) + +var ( + g = 0.1 + r1 = 180.0 + r2 = 90.0 + m1 = 32.0 + m2 = 8.0 + a1v = 0.0 + a2v = 0.0 + a1, a2 = a1a2DefaultValues() +) + +func run() { + win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ + Bounds: pixel.R(0, 0, 600, 310), + VSync: true, + }) + if err != nil { + panic(err) + } + CenterWindow(win) + win.SetSmooth(true) + modelMatrix := pixel.IM.ScaledXY(pixel.ZV, pixel.V(1, -1)).Moved(pixel.V(300, 300)) + viewMatrix := pixel.IM.Moved(win.Bounds().Center()) + + // I am putting all shader example initializing stuff here for + // easier reference to those learning to use this functionality + fragSource, err := LoadFileToString("../assets/shaders/exposure.frag.glsl") + if err != nil { + panic(err) + } + + // Here we setup our uniforms. Think of uniforms as global variables + // we can use inside of our fragment shader source code. + var uTimeVar float32 + + // We'll change this variable around with the arrow keys + var uAmountVar float32 = 0.2 + + // We will update these uniforms often, so use pointer + EasyBindUniforms(win.Canvas(), + "uTime", &uTimeVar, + "uAmount", &uAmountVar, + ) + + // Since we are making a post effect, we want to apply the shader + // to the entire final render. We will use an intermediate canvas + // to complete the draw frame and then draw the canvas to the window's + // canvas for shader processing. Otherwise, our shader would only be + // running on active vertex positions, in this case, only the line + // and circle draws generated by IMDraw. + intermediatecanvas := pixelgl.NewCanvas(win.Bounds()) + intermediatecanvas.SetMatrix(modelMatrix) + + wc := win.Canvas() + wc.SetFragmentShader(fragSource) + + sqrPos := win.Bounds().Moved(pixel.V(-300, -10)) + start := time.Now() + for !win.Closed() { + // Update our uniform variables + uTimeVar = float32(time.Since(start).Seconds()) + + switch { + case win.Pressed(pixelgl.KeyLeft): + uAmountVar -= 0.001 + win.SetTitle(fmt.Sprint(uAmountVar)) + case win.Pressed(pixelgl.KeyRight): + uAmountVar += 0.001 + win.SetTitle(fmt.Sprint(uAmountVar)) + } + + win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) + if win.JustPressed(pixelgl.KeySpace) { + a1, a2 = a1a2DefaultValues() + } + + a, b := update() + + imd := imdraw.New(nil) + + // Clearing the background color with a filled rectangle so that + // opengl will include the entire space in shader processing instead + // of just where the shape objects are drawn. + imd.Color = color.NRGBA{44, 44, 84, 255} + imd.Push(sqrPos.Min, sqrPos.Max) + imd.Rectangle(0) + + imd.Color = color.NRGBA{64, 64, 122, 255} + imd.Push(pixel.ZV, a, b) + imd.Line(3) + + imd.Color = color.NRGBA{51, 217, 178, 255} + imd.Push(a) + imd.Circle(m1/2, 0) + + imd.Color = color.NRGBA{255, 0, 0, 255} + imd.Push(b) + imd.Circle(m2/2, 0) + + imd.Draw(intermediatecanvas) + intermediatecanvas.Draw(win, viewMatrix) + win.Update() + } +} + +func update() (pixel.Vec, pixel.Vec) { + a1a := a1aCalculation() + a2a := a2aCalculation() + + a1v += a1a + a2v += a2a + + a1 += a1v + a2 += a2v + + a1v *= 0.9996 + a2v *= 0.9996 + + a := pixel.V(r1*math.Sin(a1), r1*math.Cos(a1)) + b := pixel.V(a.X+r2*math.Sin(a2), a.Y+r2*math.Cos(a2)) + + return a, b +} + +func main() { + pixelgl.Run(run) +} + +func a1a2DefaultValues() (float64, float64) { + return math.Pi / 2, math.Pi / 3 +} + +func a1aCalculation() float64 { + num1 := -g * (2*m1 + m2) * math.Sin(a1) + num2 := -m2 * g * math.Sin(a1-2*a2) + num3 := -2 * math.Sin(a1-a2) * m2 + num4 := a2v*a2v*r2 + a1v*a1v*r1*math.Cos(a1-a2) + den := r1 * (2*m1 + m2 - m2*math.Cos(2*a1-2*a2)) + + return (num1 + num2 + num3*num4) / den +} + +func a2aCalculation() float64 { + num1 := 2 * math.Sin(a1-a2) + num2 := (a1v * a1v * r1 * (m1 + m2)) + num3 := g * (m1 + m2) * math.Cos(a1) + num4 := a2v * a2v * r2 * m2 * math.Cos(a1-a2) + den := r2 * (2*m1 + m2 - m2*math.Cos(2*a2-2*a2)) + + return (num1 * (num2 + num3 + num4)) / den +} diff --git a/shader/exposure/psutil.go b/shader/exposure/psutil.go new file mode 100644 index 0000000..39fa6ba --- /dev/null +++ b/shader/exposure/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(), +// "uTime", &uTimeVar, +// "uMouse", &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/shader/fastblur.gif b/shader/fastblur.gif new file mode 100644 index 0000000..e3fa12a Binary files /dev/null and b/shader/fastblur.gif differ diff --git a/shader/fastblur/main.go b/shader/fastblur/main.go new file mode 100644 index 0000000..33f5a45 --- /dev/null +++ b/shader/fastblur/main.go @@ -0,0 +1,176 @@ +package main + +// +// This example will show you how to port a shader you find from shadertoy.com +// to Pixel. See ../assets/shaders/fastblur.frag.glsl for more details and comments +// + +import ( + "image/color" + "math" + "time" + + "github.com/faiface/pixel" + "github.com/faiface/pixel/imdraw" + "github.com/faiface/pixel/pixelgl" + "github.com/go-gl/mathgl/mgl32" +) + +var ( + g = 0.1 + r1 = 180.0 + r2 = 90.0 + m1 = 32.0 + m2 = 8.0 + a1v = 0.0 + a2v = 0.0 + a1, a2 = a1a2DefaultValues() +) + +func run() { + win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ + Bounds: pixel.R(0, 0, 600, 310), + VSync: true, + }) + if err != nil { + panic(err) + } + CenterWindow(win) + win.SetSmooth(true) + modelMatrix := pixel.IM.ScaledXY(pixel.ZV, pixel.V(1, -1)).Moved(pixel.V(300, 300)) + viewMatrix := pixel.IM.Moved(win.Bounds().Center()) + + // I am putting all shader example initializing stuff here for + // easier reference to those learning to use this functionality + fragSource, err := LoadFileToString("../assets/shaders/fastblur.frag.glsl") + if err != nil { + panic(err) + } + + // Here we setup our uniforms. Think of uniforms as global variables + // we can use inside of our fragment shader source code. + var uTimeVar float32 + + // It is common to provide a vec4 for a "mouse" uniform where + // uMouse[0] = X, uMouse[1] = Y, uMouse[2] = left mouse button, + // and uMouse[3] = right mouse button. + var uMouseVar mgl32.Vec4 + + // We will update these uniforms often, so use pointer + EasyBindUniforms(win.Canvas(), + "uTime", &uTimeVar, + "uMouse", &uMouseVar, + ) + + // Since we are making a post effect, we want to apply the shader + // to the entire final render. We will use an intermediate canvas + // to complete the draw frame and then draw the canvas to the window's + // canvas for shader processing. Otherwise, our shader would only be + // running on active vertex positions, in this case, only the line + // and circle draws generated by IMDraw. + intermediatecanvas := pixelgl.NewCanvas(win.Bounds()) + intermediatecanvas.SetMatrix(modelMatrix) + + wc := win.Canvas() + wc.SetFragmentShader(fragSource) + + sqrPos := win.Bounds().Moved(pixel.V(-300, -10)) + start := time.Now() + for !win.Closed() { + // Update our uniform variables + uTimeVar = float32(time.Since(start).Seconds()) + + uMouseVar[0] = float32(win.MousePosition().X) + uMouseVar[1] = float32(win.MousePosition().Y) + + if win.Pressed(pixelgl.MouseButton1) { + uMouseVar[2] = 1.0 + } else { + uMouseVar[2] = 0.0 + } + if win.Pressed(pixelgl.MouseButton2) { + uMouseVar[3] = 1.0 + } else { + uMouseVar[3] = 0.0 + } + + win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) + if win.JustPressed(pixelgl.KeySpace) { + a1, a2 = a1a2DefaultValues() + } + + a, b := update() + + imd := imdraw.New(nil) + + // Clearing the background color with a filled rectangle so that + // opengl will include the entire space in shader processing instead + // of just where the shape objects are drawn. + imd.Color = color.NRGBA{44, 44, 84, 255} + imd.Push(sqrPos.Min, sqrPos.Max) + imd.Rectangle(0) + + imd.Color = color.NRGBA{64, 64, 122, 255} + imd.Push(pixel.ZV, a, b) + imd.Line(3) + + imd.Color = color.NRGBA{51, 217, 178, 255} + imd.Push(a) + imd.Circle(m1/2, 0) + + imd.Color = color.NRGBA{52, 172, 224, 255} + imd.Push(b) + imd.Circle(m2/2, 0) + + imd.Draw(intermediatecanvas) + intermediatecanvas.Draw(win, viewMatrix) + win.Update() + } +} + +func update() (pixel.Vec, pixel.Vec) { + a1a := a1aCalculation() + a2a := a2aCalculation() + + a1v += a1a + a2v += a2a + + a1 += a1v + a2 += a2v + + a1v *= 0.9996 + a2v *= 0.9996 + + a := pixel.V(r1*math.Sin(a1), r1*math.Cos(a1)) + b := pixel.V(a.X+r2*math.Sin(a2), a.Y+r2*math.Cos(a2)) + + return a, b +} + +func main() { + pixelgl.Run(run) +} + +func a1a2DefaultValues() (float64, float64) { + return math.Pi / 2, math.Pi / 3 +} + +func a1aCalculation() float64 { + num1 := -g * (2*m1 + m2) * math.Sin(a1) + num2 := -m2 * g * math.Sin(a1-2*a2) + num3 := -2 * math.Sin(a1-a2) * m2 + num4 := a2v*a2v*r2 + a1v*a1v*r1*math.Cos(a1-a2) + den := r1 * (2*m1 + m2 - m2*math.Cos(2*a1-2*a2)) + + return (num1 + num2 + num3*num4) / den +} + +func a2aCalculation() float64 { + num1 := 2 * math.Sin(a1-a2) + num2 := (a1v * a1v * r1 * (m1 + m2)) + num3 := g * (m1 + m2) * math.Cos(a1) + num4 := a2v * a2v * r2 * m2 * math.Cos(a1-a2) + den := r2 * (2*m1 + m2 - m2*math.Cos(2*a2-2*a2)) + + return (num1 * (num2 + num3 + num4)) / den +} diff --git a/shader/fastblur/psutil.go b/shader/fastblur/psutil.go new file mode 100644 index 0000000..39fa6ba --- /dev/null +++ b/shader/fastblur/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(), +// "uTime", &uTimeVar, +// "uMouse", &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/shader/grayscale.png b/shader/grayscale.png new file mode 100644 index 0000000..23b6528 Binary files /dev/null and b/shader/grayscale.png differ diff --git a/shader/grayscale/main.go b/shader/grayscale/main.go new file mode 100644 index 0000000..fe990d6 --- /dev/null +++ b/shader/grayscale/main.go @@ -0,0 +1,74 @@ +package main + +import ( + "image/png" + "os" + + "github.com/faiface/pixel" + "github.com/faiface/pixel/pixelgl" +) + +var gopherimg *pixel.Sprite + +func gameloop(win *pixelgl.Window) { + win.Canvas().SetFragmentShader(fragmentShader) + + for !win.Closed() { + win.Clear(pixel.RGB(0, 0, 0)) + gopherimg.Draw(win, pixel.IM.Moved(win.Bounds().Center())) + win.Update() + } +} + +func run() { + cfg := pixelgl.WindowConfig{ + Title: "Pixel Rocks!", + Bounds: pixel.R(0, 0, 325, 348), + VSync: true, + } + win, err := pixelgl.NewWindow(cfg) + if err != nil { + panic(err) + } + f, err := os.Open("../assets/images/thegopherproject.png") + if err != nil { + panic(err) + } + img, err := png.Decode(f) + if err != nil { + panic(err) + } + pd := pixel.PictureDataFromImage(img) + gopherimg = pixel.NewSprite(pd, pd.Bounds()) + + gameloop(win) +} + +func main() { + pixelgl.Run(run) +} + +var fragmentShader = ` +#version 330 core + +in vec2 vTexCoords; + +out vec4 fragColor; + +uniform vec4 uTexBounds; +uniform sampler2D uTexture; + +void main() { + // Get our current screen coordinate + vec2 t = (vTexCoords - uTexBounds.xy) / uTexBounds.zw; + + // Sum our 3 color channels + float sum = texture(uTexture, t).r; + sum += texture(uTexture, t).g; + sum += texture(uTexture, t).b; + + // Divide by 3, and set the output to the result + vec4 color = vec4( sum/3, sum/3, sum/3, 1.0); + fragColor = color; +} +` diff --git a/shader/wavy.gif b/shader/wavy.gif new file mode 100644 index 0000000..134ddfd Binary files /dev/null and b/shader/wavy.gif differ diff --git a/shader/wavy/main.go b/shader/wavy/main.go new file mode 100644 index 0000000..3ae9c34 --- /dev/null +++ b/shader/wavy/main.go @@ -0,0 +1,92 @@ +package main + +import ( + "image/png" + "os" + "time" + + "github.com/thegtproject/pixel/imdraw" + + "github.com/faiface/pixel" + "github.com/faiface/pixel/pixelgl" +) + +var gopherimg *pixel.Sprite +var imd *imdraw.IMDraw + +var uTime, uSpeed float32 + +func gameloop(win *pixelgl.Window) { + win.Canvas().SetUniform("uTime", &uTime) + win.Canvas().SetUniform("uSpeed", &uSpeed) + uSpeed = 5.0 + win.Canvas().SetFragmentShader(fragmentShader) + + start := time.Now() + for !win.Closed() { + win.Clear(pixel.RGB(0, 0, 0)) + gopherimg.Draw(win, pixel.IM.Moved(win.Bounds().Center())) + uTime = float32(time.Since(start).Seconds()) + if win.Pressed(pixelgl.KeyRight) { + uSpeed += 0.1 + } + if win.Pressed(pixelgl.KeyLeft) { + uSpeed -= 0.1 + } + win.Update() + } +} + +func run() { + cfg := pixelgl.WindowConfig{ + Title: "Pixel Rocks!", + Bounds: pixel.R(0, 0, 325, 348), + VSync: true, + } + win, err := pixelgl.NewWindow(cfg) + if err != nil { + panic(err) + } + f, err := os.Open("../assets/images/thegopherproject.png") + if err != nil { + panic(err) + } + img, err := png.Decode(f) + if err != nil { + panic(err) + } + pd := pixel.PictureDataFromImage(img) + gopherimg = pixel.NewSprite(pd, pd.Bounds()) + + gameloop(win) +} + +func main() { + pixelgl.Run(run) +} + +var fragmentShader = ` +#version 330 core + +out vec4 fragColor; + +uniform sampler2D uTexture; +uniform vec4 uTexBounds; + +// custom uniforms +uniform float uSpeed; +uniform float uTime; + +void main() { + vec2 t = gl_FragCoord.xy / uTexBounds.zw; + vec3 influence = texture(uTexture, t).rgb; + + if (influence.r + influence.g + influence.b > 0.3) { + t.y += cos(t.x * 40.0 + (uTime * uSpeed))*0.005; + t.x += cos(t.y * 40.0 + (uTime * uSpeed))*0.01; + } + + vec3 col = texture(uTexture, t).rgb; + fragColor = vec4(col * vec3(0.6, 0.6, 1.2),1.0); +} +`