├── LICENSE ├── README.md ├── basic-3d ├── .gitignore ├── basic-3d-boxes.gif ├── gfx │ ├── shader.go │ └── texture.go ├── main.go └── shaders │ ├── basic.frag │ └── basic.vert ├── basic-camera ├── .gitignore ├── cam │ └── camera.go ├── gfx │ ├── shader.go │ └── texture.go ├── main.go ├── shaders │ ├── basic.frag │ └── basic.vert └── win │ ├── input.go │ └── window.go ├── basic-light ├── .gitignore ├── cam │ └── camera.go ├── gfx │ ├── shader.go │ └── texture.go ├── main.go ├── shaders │ ├── basic.frag │ ├── basic.vert │ ├── light.frag │ ├── phong.frag │ └── phong.vert └── win │ ├── input.go │ └── window.go ├── basic-shaders ├── .gitignore ├── gfx │ └── shader.go ├── main.go └── shaders │ ├── basic.frag │ └── basic.vert ├── basic-textures ├── .gitignore ├── basic-textures.png ├── gfx │ ├── shader.go │ └── texture.go ├── main.go └── shaders │ ├── basic.frag │ └── basic.vert ├── colors ├── .gitignore ├── cam │ └── camera.go ├── gfx │ ├── shader.go │ └── texture.go ├── main.go ├── shaders │ ├── basic.frag │ ├── basic.vert │ └── light.frag └── win │ ├── input.go │ └── window.go ├── hello-triangle-advanced ├── .gitignore └── hello_triangle_advanced.go ├── hello-triangle ├── .gitignore └── hello_triangle.go ├── hello-window ├── .gitignore └── hello_window.go ├── images ├── RTS_Crate.png ├── container2.png ├── trollface-transparent.png └── trollface.png ├── light-maps ├── .gitignore ├── cam │ └── camera.go ├── gfx │ ├── shader.go │ └── texture.go ├── main.go ├── shaders │ ├── light.frag │ ├── phong.frag │ └── phong.vert └── win │ ├── input.go │ └── window.go └── materials ├── .gitignore ├── cam └── camera.go ├── gfx ├── shader.go └── texture.go ├── main.go ├── shaders ├── light.frag ├── phong.frag └── phong.vert └── win ├── input.go └── window.go /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Cory Stegelmeier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenGL Golang Samples 2 | 3 | This is a collection of sample files/projects that show off how to do certain things in OpenGL/Golang. 4 | 5 | I update this collection in my spare time as I learn more about OpenGL. 6 | It is likely that some examples may be incomplete or a work-in-progress. 7 | -------------------------------------------------------------------------------- /basic-3d/.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | -------------------------------------------------------------------------------- /basic-3d/basic-3d-boxes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstegel/opengl-samples-golang/1ed56cc6485acc36ebd30234dd7f405d2080b844/basic-3d/basic-3d-boxes.gif -------------------------------------------------------------------------------- /basic-3d/gfx/shader.go: -------------------------------------------------------------------------------- 1 | package gfx 2 | 3 | import ( 4 | "io/ioutil" 5 | "strings" 6 | "fmt" 7 | 8 | "github.com/go-gl/gl/v4.1-core/gl" 9 | ) 10 | 11 | type Shader struct { 12 | handle uint32 13 | } 14 | 15 | type Program struct { 16 | handle uint32 17 | shaders []*Shader 18 | } 19 | 20 | func (shader *Shader) Delete() { 21 | gl.DeleteShader(shader.handle) 22 | } 23 | 24 | func (prog *Program) Delete() { 25 | for _, shader := range prog.shaders { 26 | shader.Delete() 27 | } 28 | gl.DeleteProgram(prog.handle) 29 | } 30 | 31 | func (prog *Program) Attach(shaders ...*Shader) { 32 | for _, shader := range shaders { 33 | gl.AttachShader(prog.handle, shader.handle) 34 | prog.shaders = append(prog.shaders, shader) 35 | } 36 | } 37 | 38 | func (prog *Program) Use() { 39 | gl.UseProgram(prog.handle) 40 | } 41 | 42 | func (prog *Program) Link() error { 43 | gl.LinkProgram(prog.handle) 44 | return getGlError(prog.handle, gl.LINK_STATUS, gl.GetProgramiv, gl.GetProgramInfoLog, 45 | "PROGRAM::LINKING_FAILURE") 46 | } 47 | 48 | func (prog *Program) GetUniformLocation(name string) int32 { 49 | return gl.GetUniformLocation(prog.handle, gl.Str(name + "\x00")) 50 | } 51 | 52 | func NewProgram(shaders ...*Shader) (*Program, error) { 53 | prog := &Program{handle:gl.CreateProgram()} 54 | prog.Attach(shaders...) 55 | 56 | if err := prog.Link(); err != nil { 57 | return nil, err 58 | } 59 | 60 | return prog, nil 61 | } 62 | 63 | func NewShader(src string, sType uint32) (*Shader, error) { 64 | 65 | handle := gl.CreateShader(sType) 66 | glSrcs, freeFn := gl.Strs(src + "\x00") 67 | defer freeFn() 68 | gl.ShaderSource(handle, 1, glSrcs, nil) 69 | gl.CompileShader(handle) 70 | err := getGlError(handle, gl.COMPILE_STATUS, gl.GetShaderiv, gl.GetShaderInfoLog, 71 | "SHADER::COMPILE_FAILURE::") 72 | if err != nil { 73 | return nil, err 74 | } 75 | return &Shader{handle:handle}, nil 76 | } 77 | 78 | func NewShaderFromFile(file string, sType uint32) (*Shader, error) { 79 | src, err := ioutil.ReadFile(file) 80 | if err != nil { 81 | return nil, err 82 | } 83 | handle := gl.CreateShader(sType) 84 | glSrc, freeFn := gl.Strs(string(src) + "\x00") 85 | defer freeFn() 86 | gl.ShaderSource(handle, 1, glSrc, nil) 87 | gl.CompileShader(handle) 88 | err = getGlError(handle, gl.COMPILE_STATUS, gl.GetShaderiv, gl.GetShaderInfoLog, 89 | "SHADER::COMPILE_FAILURE::" + file) 90 | if err != nil { 91 | return nil, err 92 | } 93 | return &Shader{handle:handle}, nil 94 | } 95 | 96 | type getObjIv func(uint32, uint32, *int32) 97 | type getObjInfoLog func(uint32, int32, *int32, *uint8) 98 | 99 | func getGlError(glHandle uint32, checkTrueParam uint32, getObjIvFn getObjIv, 100 | getObjInfoLogFn getObjInfoLog, failMsg string) error { 101 | 102 | var success int32 103 | getObjIvFn(glHandle, checkTrueParam, &success) 104 | 105 | if success == gl.FALSE { 106 | var logLength int32 107 | getObjIvFn(glHandle, gl.INFO_LOG_LENGTH, &logLength) 108 | 109 | log := gl.Str(strings.Repeat("\x00", int(logLength))) 110 | getObjInfoLogFn(glHandle, logLength, nil, log) 111 | 112 | return fmt.Errorf("%s: %s", failMsg, gl.GoStr(log)) 113 | } 114 | 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /basic-3d/gfx/texture.go: -------------------------------------------------------------------------------- 1 | package gfx 2 | 3 | import ( 4 | "os" 5 | "errors" 6 | "image" 7 | "image/draw" 8 | _ "image/png" 9 | _ "image/jpeg" 10 | 11 | "github.com/go-gl/gl/v4.1-core/gl" 12 | ) 13 | 14 | type Texture struct { 15 | handle uint32 16 | target uint32 // same target as gl.BindTexture(, ...) 17 | texUnit uint32 // Texture unit that is currently bound to ex: gl.TEXTURE0 18 | } 19 | 20 | var errUnsupportedStride = errors.New("unsupported stride, only 32-bit colors supported") 21 | 22 | var errTextureNotBound = errors.New("texture not bound") 23 | 24 | func NewTextureFromFile(file string, wrapR, wrapS int32) (*Texture, error) { 25 | imgFile, err := os.Open(file) 26 | if err != nil { 27 | return nil, err 28 | } 29 | defer imgFile.Close() 30 | 31 | // Decode detexts the type of image as long as its image/ is imported 32 | img, _, err := image.Decode(imgFile) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return NewTexture(img, wrapR, wrapS) 37 | } 38 | 39 | func NewTexture(img image.Image, wrapR, wrapS int32) (*Texture, error) { 40 | rgba := image.NewRGBA(img.Bounds()) 41 | draw.Draw(rgba, rgba.Bounds(), img, image.Pt(0, 0), draw.Src) 42 | if rgba.Stride != rgba.Rect.Size().X*4 { // TODO-cs: why? 43 | return nil, errUnsupportedStride 44 | } 45 | 46 | var handle uint32 47 | gl.GenTextures(1, &handle) 48 | 49 | target := uint32(gl.TEXTURE_2D) 50 | internalFmt := int32(gl.SRGB_ALPHA) 51 | format := uint32(gl.RGBA) 52 | width := int32(rgba.Rect.Size().X) 53 | height := int32(rgba.Rect.Size().Y) 54 | pixType := uint32(gl.UNSIGNED_BYTE) 55 | dataPtr := gl.Ptr(rgba.Pix) 56 | 57 | texture := Texture{ 58 | handle:handle, 59 | target:target, 60 | } 61 | 62 | texture.Bind(gl.TEXTURE0) 63 | defer texture.UnBind() 64 | 65 | // set the texture wrapping/filtering options (applies to current bound texture obj) 66 | // TODO-cs 67 | gl.TexParameteri(texture.target, gl.TEXTURE_WRAP_R, wrapR) 68 | gl.TexParameteri(texture.target, gl.TEXTURE_WRAP_S, wrapS) 69 | gl.TexParameteri(texture.target, gl.TEXTURE_MIN_FILTER, gl.LINEAR) // minification filter 70 | gl.TexParameteri(texture.target, gl.TEXTURE_MAG_FILTER, gl.LINEAR) // magnification filter 71 | 72 | 73 | gl.TexImage2D(target, 0, internalFmt, width, height, 0, format, pixType, dataPtr) 74 | 75 | gl.GenerateMipmap(texture.handle) 76 | 77 | return &texture, nil 78 | } 79 | 80 | func (tex *Texture) Bind(texUnit uint32) { 81 | gl.ActiveTexture(texUnit) 82 | gl.BindTexture(tex.target, tex.handle) 83 | tex.texUnit = texUnit 84 | } 85 | 86 | func (tex *Texture) UnBind() { 87 | tex.texUnit = 0 88 | gl.BindTexture(tex.target, 0) 89 | } 90 | 91 | func (tex *Texture) SetUniform(uniformLoc int32) error { 92 | if tex.texUnit == 0 { 93 | return errTextureNotBound 94 | } 95 | gl.Uniform1i(uniformLoc, int32(tex.texUnit - gl.TEXTURE0)) 96 | return nil 97 | } 98 | 99 | func loadImageFile(file string) (image.Image, error) { 100 | infile, err := os.Open(file) 101 | if err != nil { 102 | return nil, err 103 | } 104 | defer infile.Close() 105 | 106 | // Decode automatically figures out the type of immage in the file 107 | // as long as its image/ is imported 108 | img, _, err := image.Decode(infile) 109 | return img, err 110 | } 111 | -------------------------------------------------------------------------------- /basic-3d/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | Adapted from this tutorial: http://www.learnopengl.com/#!Getting-started/Coordinate-Systems 5 | 6 | Shows how to use a basic coordinate system in OpenGL to create 3D objects 7 | */ 8 | 9 | import ( 10 | "log" 11 | "runtime" 12 | 13 | "github.com/go-gl/gl/v4.1-core/gl" 14 | "github.com/go-gl/glfw/v3.1/glfw" 15 | "github.com/go-gl/mathgl/mgl32" 16 | 17 | "github.com/cstegel/opengl-samples-golang/basic-3d/gfx" 18 | ) 19 | 20 | const windowWidth = 800 21 | const windowHeight = 600 22 | 23 | func init() { 24 | // GLFW event handling must be run on the main OS thread 25 | runtime.LockOSThread() 26 | } 27 | 28 | func main() { 29 | if err := glfw.Init(); err != nil { 30 | log.Fatalln("failed to inifitialize glfw:", err) 31 | } 32 | defer glfw.Terminate() 33 | 34 | glfw.WindowHint(glfw.Resizable, glfw.False) 35 | glfw.WindowHint(glfw.ContextVersionMajor, 4) 36 | glfw.WindowHint(glfw.ContextVersionMinor, 1) 37 | glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) 38 | glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) 39 | window, err := glfw.CreateWindow(windowWidth, windowHeight, "basic 3d", nil, nil) 40 | if err != nil { 41 | panic(err) 42 | } 43 | window.MakeContextCurrent() 44 | 45 | // Initialize Glow (go function bindings) 46 | if err := gl.Init(); err != nil { 47 | panic(err) 48 | } 49 | 50 | window.SetKeyCallback(keyCallback) 51 | 52 | err = programLoop(window) 53 | if err != nil { 54 | log.Fatal(err) 55 | } 56 | } 57 | 58 | /* 59 | * Creates the Vertex Array Object for a triangle. 60 | */ 61 | func createVAO(vertices []float32, indices []uint32) uint32 { 62 | 63 | var VAO uint32 64 | gl.GenVertexArrays(1, &VAO) 65 | 66 | var VBO uint32 67 | gl.GenBuffers(1, &VBO) 68 | 69 | var EBO uint32; 70 | gl.GenBuffers(1, &EBO) 71 | 72 | // Bind the Vertex Array Object first, then bind and set vertex buffer(s) and attribute pointers() 73 | gl.BindVertexArray(VAO) 74 | 75 | // copy vertices data into VBO (it needs to be bound first) 76 | gl.BindBuffer(gl.ARRAY_BUFFER, VBO) 77 | gl.BufferData(gl.ARRAY_BUFFER, len(vertices)*4, gl.Ptr(vertices), gl.STATIC_DRAW) 78 | 79 | // size of one whole vertex (sum of attrib sizes) 80 | var stride int32 = 3*4 + 2*4 81 | var offset int = 0 82 | 83 | // position 84 | gl.VertexAttribPointer(0, 3, gl.FLOAT, false, stride, gl.PtrOffset(offset)) 85 | gl.EnableVertexAttribArray(0) 86 | offset += 3*4 87 | 88 | // texture position 89 | gl.VertexAttribPointer(1, 2, gl.FLOAT, false, stride, gl.PtrOffset(offset)) 90 | gl.EnableVertexAttribArray(1) 91 | offset += 2*4 92 | 93 | // unbind the VAO (safe practice so we don't accidentally (mis)configure it later) 94 | gl.BindVertexArray(0) 95 | 96 | return VAO 97 | } 98 | 99 | func programLoop(window *glfw.Window) error { 100 | 101 | // the linked shader program determines how the data will be rendered 102 | vertShader, err := gfx.NewShaderFromFile("shaders/basic.vert", gl.VERTEX_SHADER) 103 | if err != nil { 104 | return err 105 | } 106 | 107 | fragShader, err := gfx.NewShaderFromFile("shaders/basic.frag", gl.FRAGMENT_SHADER) 108 | if err != nil { 109 | return err 110 | } 111 | 112 | program, err := gfx.NewProgram(vertShader, fragShader) 113 | if err != nil { 114 | return err 115 | } 116 | defer program.Delete() 117 | 118 | vertices := []float32{ 119 | // position // texture position 120 | -0.5, -0.5, -0.5, 0.0, 0.0, 121 | 0.5, -0.5, -0.5, 1.0, 0.0, 122 | 0.5, 0.5, -0.5, 1.0, 1.0, 123 | 0.5, 0.5, -0.5, 1.0, 1.0, 124 | -0.5, 0.5, -0.5, 0.0, 1.0, 125 | -0.5, -0.5, -0.5, 0.0, 0.0, 126 | 127 | -0.5, -0.5, 0.5, 0.0, 0.0, 128 | 0.5, -0.5, 0.5, 1.0, 0.0, 129 | 0.5, 0.5, 0.5, 1.0, 1.0, 130 | 0.5, 0.5, 0.5, 1.0, 1.0, 131 | -0.5, 0.5, 0.5, 0.0, 1.0, 132 | -0.5, -0.5, 0.5, 0.0, 0.0, 133 | 134 | -0.5, 0.5, 0.5, 1.0, 0.0, 135 | -0.5, 0.5, -0.5, 1.0, 1.0, 136 | -0.5, -0.5, -0.5, 0.0, 1.0, 137 | -0.5, -0.5, -0.5, 0.0, 1.0, 138 | -0.5, -0.5, 0.5, 0.0, 0.0, 139 | -0.5, 0.5, 0.5, 1.0, 0.0, 140 | 141 | 0.5, 0.5, 0.5, 1.0, 0.0, 142 | 0.5, 0.5, -0.5, 1.0, 1.0, 143 | 0.5, -0.5, -0.5, 0.0, 1.0, 144 | 0.5, -0.5, -0.5, 0.0, 1.0, 145 | 0.5, -0.5, 0.5, 0.0, 0.0, 146 | 0.5, 0.5, 0.5, 1.0, 0.0, 147 | 148 | -0.5, -0.5, -0.5, 0.0, 1.0, 149 | 0.5, -0.5, -0.5, 1.0, 1.0, 150 | 0.5, -0.5, 0.5, 1.0, 0.0, 151 | 0.5, -0.5, 0.5, 1.0, 0.0, 152 | -0.5, -0.5, 0.5, 0.0, 0.0, 153 | -0.5, -0.5, -0.5, 0.0, 1.0, 154 | 155 | -0.5, 0.5, -0.5, 0.0, 1.0, 156 | 0.5, 0.5, -0.5, 1.0, 1.0, 157 | 0.5, 0.5, 0.5, 1.0, 0.0, 158 | 0.5, 0.5, 0.5, 1.0, 0.0, 159 | -0.5, 0.5, 0.5, 0.0, 0.0, 160 | -0.5, 0.5, -0.5, 0.0, 1.0, 161 | } 162 | 163 | indices := []uint32{} 164 | 165 | VAO := createVAO(vertices, indices) 166 | texture0, err := gfx.NewTextureFromFile("../images/RTS_Crate.png", 167 | gl.CLAMP_TO_EDGE, gl.CLAMP_TO_EDGE) 168 | if err != nil { 169 | panic(err.Error()) 170 | } 171 | 172 | texture1, err := gfx.NewTextureFromFile("../images/trollface-transparent.png", 173 | gl.CLAMP_TO_EDGE, gl.CLAMP_TO_EDGE) 174 | if err != nil { 175 | panic(err.Error()) 176 | } 177 | 178 | cubePositions := [][]float32 { 179 | []float32{ 0.0, 0.0, -3.0 }, 180 | []float32{ 2.0, 5.0, -15.0}, 181 | []float32{-1.5, -2.2, -2.5 }, 182 | []float32{-3.8, -2.0, -12.3}, 183 | []float32{ 2.4, -0.4, -3.5 }, 184 | []float32{-1.7, 3.0, -7.5 }, 185 | []float32{ 1.3, -2.0, -2.5 }, 186 | []float32{ 1.5, 2.0, -2.5 }, 187 | []float32{ 1.5, 0.2, -1.5 }, 188 | []float32{-1.3, 1.0, -1.5 }, 189 | } 190 | 191 | gl.Enable(gl.DEPTH_TEST) 192 | 193 | for !window.ShouldClose() { 194 | // poll events and call their registered callbacks 195 | glfw.PollEvents() 196 | 197 | // background color 198 | gl.ClearColor(0.2, 0.5, 0.5, 1.0) 199 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) 200 | 201 | // draw vertices 202 | program.Use() 203 | 204 | // set texture0 to uniform0 in the fragment shader 205 | texture0.Bind(gl.TEXTURE0) 206 | texture0.SetUniform(program.GetUniformLocation("ourTexture0")) 207 | 208 | // set texture1 to uniform1 in the fragment shader 209 | texture1.Bind(gl.TEXTURE1) 210 | texture1.SetUniform(program.GetUniformLocation("ourTexture1")) 211 | 212 | // update shader transform matrices 213 | 214 | // Create transformation matrices 215 | rotateX := (mgl32.Rotate3DX(mgl32.DegToRad(-60 * float32(glfw.GetTime())))) 216 | rotateY := (mgl32.Rotate3DY(mgl32.DegToRad(-60 * float32(glfw.GetTime())))) 217 | rotateZ := (mgl32.Rotate3DZ(mgl32.DegToRad(-60 * float32(glfw.GetTime())))) 218 | 219 | viewTransform := mgl32.Translate3D(0, 0, -3) 220 | projectTransform := mgl32.Perspective(mgl32.DegToRad(60), windowWidth/windowHeight, 0.1, 100.0) 221 | 222 | 223 | gl.UniformMatrix4fv(program.GetUniformLocation("view"), 1, false, 224 | &viewTransform[0]) 225 | gl.UniformMatrix4fv(program.GetUniformLocation("project"), 1, false, 226 | &projectTransform[0]) 227 | 228 | gl.UniformMatrix4fv(program.GetUniformLocation("worldRotateX"), 1, false, 229 | &rotateX[0]) 230 | gl.UniformMatrix4fv(program.GetUniformLocation("worldRotateY"), 1, false, 231 | &rotateY[0]) 232 | gl.UniformMatrix4fv(program.GetUniformLocation("worldRotateZ"), 1, false, 233 | &rotateZ[0]) 234 | 235 | gl.BindVertexArray(VAO) 236 | 237 | for _, pos := range cubePositions { 238 | 239 | worldTranslate := mgl32.Translate3D(pos[0], pos[1], pos[2]) 240 | worldTransform := (worldTranslate.Mul4(rotateX.Mul3(rotateY).Mul3(rotateZ).Mat4())) 241 | 242 | gl.UniformMatrix4fv(program.GetUniformLocation("world"), 1, false, 243 | &worldTransform[0]) 244 | 245 | gl.DrawArrays(gl.TRIANGLES, 0, 36) 246 | } 247 | // gl.DrawElements(gl.TRIANGLES, 36, gl.UNSIGNED_INT, unsafe.Pointer(nil)) 248 | gl.BindVertexArray(0) 249 | 250 | texture0.UnBind() 251 | texture1.UnBind() 252 | 253 | // end of draw loop 254 | 255 | // swap in the rendered buffer 256 | window.SwapBuffers() 257 | } 258 | 259 | return nil 260 | } 261 | 262 | func keyCallback(window *glfw.Window, key glfw.Key, scancode int, action glfw.Action, 263 | mods glfw.ModifierKey) { 264 | 265 | // When a user presses the escape key, we set the WindowShouldClose property to true, 266 | // which closes the application 267 | if key == glfw.KeyEscape && action == glfw.Press { 268 | window.SetShouldClose(true) 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /basic-3d/shaders/basic.frag: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | in vec2 TexCoord; 4 | 5 | out vec4 color; 6 | 7 | uniform sampler2D ourTexture0; 8 | uniform sampler2D ourTexture1; 9 | 10 | void main() 11 | { 12 | // mix the two textures together 13 | color = mix(texture(ourTexture0, TexCoord), texture(ourTexture1, TexCoord), 0.5); 14 | } 15 | -------------------------------------------------------------------------------- /basic-3d/shaders/basic.vert: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | layout (location = 0) in vec3 position; 4 | layout (location = 1) in vec2 texCoord; 5 | 6 | out vec2 TexCoord; 7 | 8 | uniform mat4 world; 9 | uniform mat4 view; 10 | uniform mat4 project; 11 | 12 | void main() 13 | { 14 | gl_Position = project * view * world * vec4(position, 1.0); 15 | TexCoord = texCoord; // pass the texture coords on to the fragment shader 16 | } 17 | -------------------------------------------------------------------------------- /basic-camera/.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | -------------------------------------------------------------------------------- /basic-camera/cam/camera.go: -------------------------------------------------------------------------------- 1 | package cam 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/go-gl/mathgl/mgl32" 7 | "github.com/go-gl/mathgl/mgl64" 8 | 9 | "github.com/cstegel/opengl-samples-golang/basic-camera/win" 10 | ) 11 | 12 | type FpsCamera struct { 13 | // Camera options 14 | moveSpeed float64 15 | cursorSensitivity float64 16 | 17 | // Eular Angles 18 | pitch float64 19 | yaw float64 20 | 21 | // Camera attributes 22 | pos mgl32.Vec3 23 | front mgl32.Vec3 24 | up mgl32.Vec3 25 | right mgl32.Vec3 26 | worldUp mgl32.Vec3 27 | 28 | inputManager *win.InputManager 29 | } 30 | 31 | func NewFpsCamera(position, worldUp mgl32.Vec3, yaw, pitch float64, im *win.InputManager) (*FpsCamera) { 32 | cam := FpsCamera { 33 | moveSpeed: 5.00, 34 | cursorSensitivity: 0.05, 35 | pitch: pitch, 36 | yaw: yaw, 37 | pos: position, 38 | up: mgl32.Vec3{0, 1, 0}, 39 | worldUp: worldUp, 40 | inputManager: im, 41 | } 42 | 43 | return &cam 44 | } 45 | 46 | func (c *FpsCamera) Update(dTime float64) { 47 | c.updatePosition(dTime) 48 | c.updateDirection() 49 | } 50 | 51 | // UpdatePosition updates this camera's position by giving directions that 52 | // the camera is to travel in and for how long 53 | func (c *FpsCamera) updatePosition(dTime float64) { 54 | adjustedSpeed := float32(dTime * c.moveSpeed) 55 | 56 | if c.inputManager.IsActive(win.PLAYER_FORWARD) { 57 | c.pos = c.pos.Add(c.front.Mul(adjustedSpeed)) 58 | } 59 | if c.inputManager.IsActive(win.PLAYER_BACKWARD) { 60 | c.pos = c.pos.Sub(c.front.Mul(adjustedSpeed)) 61 | } 62 | if c.inputManager.IsActive(win.PLAYER_LEFT) { 63 | c.pos = c.pos.Sub(c.front.Cross(c.up).Normalize().Mul(adjustedSpeed)) 64 | } 65 | if c.inputManager.IsActive(win.PLAYER_RIGHT) { 66 | c.pos = c.pos.Add(c.front.Cross(c.up).Normalize().Mul(adjustedSpeed)) 67 | } 68 | } 69 | 70 | // UpdateCursor updates the direction of the camera by giving it delta x/y values 71 | // that came from a cursor input device 72 | func (c *FpsCamera) updateDirection() { 73 | dCursor := c.inputManager.CursorChange() 74 | 75 | dx := -c.cursorSensitivity * dCursor[0] 76 | dy := c.cursorSensitivity * dCursor[1] 77 | 78 | c.pitch += dy 79 | if c.pitch > 89.0 { 80 | c.pitch = 89.0 81 | } else if c.pitch < -89.0 { 82 | c.pitch = -89.0 83 | } 84 | 85 | c.yaw = math.Mod(c.yaw + dx, 360) 86 | c.updateVectors() 87 | } 88 | 89 | func (c *FpsCamera) updateVectors() { 90 | // x, y, z 91 | c.front[0] = float32(math.Cos(mgl64.DegToRad(c.pitch)) * math.Cos(mgl64.DegToRad(c.yaw))) 92 | c.front[1] = float32(math.Sin(mgl64.DegToRad(c.pitch))) 93 | c.front[2] = float32(math.Cos(mgl64.DegToRad(c.pitch)) * math.Sin(mgl64.DegToRad(c.yaw))) 94 | c.front = c.front.Normalize() 95 | 96 | // Gram-Schmidt process to figure out right and up vectors 97 | c.right = c.worldUp.Cross(c.front).Normalize() 98 | c.up = c.right.Cross(c.front).Normalize() 99 | } 100 | 101 | // GetCameraTransform gets the matrix to transform from world coordinates to 102 | // this camera's coordinates. 103 | func (camera *FpsCamera) GetTransform() mgl32.Mat4 { 104 | cameraTarget := camera.pos.Add(camera.front) 105 | 106 | return mgl32.LookAt( 107 | camera.pos.X(), camera.pos.Y(), camera.pos.Z(), 108 | cameraTarget.X(), cameraTarget.Y(), cameraTarget.Z(), 109 | camera.up.X(), camera.up.Y(), camera.up.Z(), 110 | ) 111 | } 112 | -------------------------------------------------------------------------------- /basic-camera/gfx/shader.go: -------------------------------------------------------------------------------- 1 | package gfx 2 | 3 | import ( 4 | "io/ioutil" 5 | "strings" 6 | "fmt" 7 | 8 | "github.com/go-gl/gl/v4.1-core/gl" 9 | ) 10 | 11 | type Shader struct { 12 | handle uint32 13 | } 14 | 15 | type Program struct { 16 | handle uint32 17 | shaders []*Shader 18 | } 19 | 20 | func (shader *Shader) Delete() { 21 | gl.DeleteShader(shader.handle) 22 | } 23 | 24 | func (prog *Program) Delete() { 25 | for _, shader := range prog.shaders { 26 | shader.Delete() 27 | } 28 | gl.DeleteProgram(prog.handle) 29 | } 30 | 31 | func (prog *Program) Attach(shaders ...*Shader) { 32 | for _, shader := range shaders { 33 | gl.AttachShader(prog.handle, shader.handle) 34 | prog.shaders = append(prog.shaders, shader) 35 | } 36 | } 37 | 38 | func (prog *Program) Use() { 39 | gl.UseProgram(prog.handle) 40 | } 41 | 42 | func (prog *Program) Link() error { 43 | gl.LinkProgram(prog.handle) 44 | return getGlError(prog.handle, gl.LINK_STATUS, gl.GetProgramiv, gl.GetProgramInfoLog, 45 | "PROGRAM::LINKING_FAILURE") 46 | } 47 | 48 | func (prog *Program) GetUniformLocation(name string) int32 { 49 | return gl.GetUniformLocation(prog.handle, gl.Str(name + "\x00")) 50 | } 51 | 52 | func NewProgram(shaders ...*Shader) (*Program, error) { 53 | prog := &Program{handle:gl.CreateProgram()} 54 | prog.Attach(shaders...) 55 | 56 | if err := prog.Link(); err != nil { 57 | return nil, err 58 | } 59 | 60 | return prog, nil 61 | } 62 | 63 | func NewShader(src string, sType uint32) (*Shader, error) { 64 | 65 | handle := gl.CreateShader(sType) 66 | glSrc, freeFn := gl.Strs(src + "\x00") 67 | defer freeFn() 68 | gl.ShaderSource(handle, 1, glSrc, nil) 69 | gl.CompileShader(handle) 70 | err := getGlError(handle, gl.COMPILE_STATUS, gl.GetShaderiv, gl.GetShaderInfoLog, 71 | "SHADER::COMPILE_FAILURE::") 72 | if err != nil { 73 | return nil, err 74 | } 75 | return &Shader{handle:handle}, nil 76 | } 77 | 78 | func NewShaderFromFile(file string, sType uint32) (*Shader, error) { 79 | src, err := ioutil.ReadFile(file) 80 | if err != nil { 81 | return nil, err 82 | } 83 | handle := gl.CreateShader(sType) 84 | glSrc, freeFn := gl.Strs(string(src) + "\x00") 85 | defer freeFn() 86 | gl.ShaderSource(handle, 1, glSrc, nil) 87 | gl.CompileShader(handle) 88 | err = getGlError(handle, gl.COMPILE_STATUS, gl.GetShaderiv, gl.GetShaderInfoLog, 89 | "SHADER::COMPILE_FAILURE::" + file) 90 | if err != nil { 91 | return nil, err 92 | } 93 | return &Shader{handle:handle}, nil 94 | } 95 | 96 | type getObjIv func(uint32, uint32, *int32) 97 | type getObjInfoLog func(uint32, int32, *int32, *uint8) 98 | 99 | func getGlError(glHandle uint32, checkTrueParam uint32, getObjIvFn getObjIv, 100 | getObjInfoLogFn getObjInfoLog, failMsg string) error { 101 | 102 | var success int32 103 | getObjIvFn(glHandle, checkTrueParam, &success) 104 | 105 | if success == gl.FALSE { 106 | var logLength int32 107 | getObjIvFn(glHandle, gl.INFO_LOG_LENGTH, &logLength) 108 | 109 | log := gl.Str(strings.Repeat("\x00", int(logLength))) 110 | getObjInfoLogFn(glHandle, logLength, nil, log) 111 | 112 | return fmt.Errorf("%s: %s", failMsg, gl.GoStr(log)) 113 | } 114 | 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /basic-camera/gfx/texture.go: -------------------------------------------------------------------------------- 1 | package gfx 2 | 3 | import ( 4 | "os" 5 | "errors" 6 | "image" 7 | "image/draw" 8 | _ "image/png" 9 | _ "image/jpeg" 10 | 11 | "github.com/go-gl/gl/v4.1-core/gl" 12 | ) 13 | 14 | type Texture struct { 15 | handle uint32 16 | target uint32 // same target as gl.BindTexture(, ...) 17 | texUnit uint32 // Texture unit that is currently bound to ex: gl.TEXTURE0 18 | } 19 | 20 | var errUnsupportedStride = errors.New("unsupported stride, only 32-bit colors supported") 21 | 22 | var errTextureNotBound = errors.New("texture not bound") 23 | 24 | func NewTextureFromFile(file string, wrapR, wrapS int32) (*Texture, error) { 25 | imgFile, err := os.Open(file) 26 | if err != nil { 27 | return nil, err 28 | } 29 | defer imgFile.Close() 30 | 31 | // Decode detexts the type of image as long as its image/ is imported 32 | img, _, err := image.Decode(imgFile) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return NewTexture(img, wrapR, wrapS) 37 | } 38 | 39 | func NewTexture(img image.Image, wrapR, wrapS int32) (*Texture, error) { 40 | rgba := image.NewRGBA(img.Bounds()) 41 | draw.Draw(rgba, rgba.Bounds(), img, image.Pt(0, 0), draw.Src) 42 | if rgba.Stride != rgba.Rect.Size().X*4 { // TODO-cs: why? 43 | return nil, errUnsupportedStride 44 | } 45 | 46 | var handle uint32 47 | gl.GenTextures(1, &handle) 48 | 49 | target := uint32(gl.TEXTURE_2D) 50 | internalFmt := int32(gl.SRGB_ALPHA) 51 | format := uint32(gl.RGBA) 52 | width := int32(rgba.Rect.Size().X) 53 | height := int32(rgba.Rect.Size().Y) 54 | pixType := uint32(gl.UNSIGNED_BYTE) 55 | dataPtr := gl.Ptr(rgba.Pix) 56 | 57 | texture := Texture{ 58 | handle:handle, 59 | target:target, 60 | } 61 | 62 | texture.Bind(gl.TEXTURE0) 63 | defer texture.UnBind() 64 | 65 | // set the texture wrapping/filtering options (applies to current bound texture obj) 66 | // TODO-cs 67 | gl.TexParameteri(texture.target, gl.TEXTURE_WRAP_R, wrapR) 68 | gl.TexParameteri(texture.target, gl.TEXTURE_WRAP_S, wrapS) 69 | gl.TexParameteri(texture.target, gl.TEXTURE_MIN_FILTER, gl.LINEAR) // minification filter 70 | gl.TexParameteri(texture.target, gl.TEXTURE_MAG_FILTER, gl.LINEAR) // magnification filter 71 | 72 | 73 | gl.TexImage2D(target, 0, internalFmt, width, height, 0, format, pixType, dataPtr) 74 | 75 | gl.GenerateMipmap(texture.handle) 76 | 77 | return &texture, nil 78 | } 79 | 80 | func (tex *Texture) Bind(texUnit uint32) { 81 | gl.ActiveTexture(texUnit) 82 | gl.BindTexture(tex.target, tex.handle) 83 | tex.texUnit = texUnit 84 | } 85 | 86 | func (tex *Texture) UnBind() { 87 | tex.texUnit = 0 88 | gl.BindTexture(tex.target, 0) 89 | } 90 | 91 | func (tex *Texture) SetUniform(uniformLoc int32) error { 92 | if tex.texUnit == 0 { 93 | return errTextureNotBound 94 | } 95 | gl.Uniform1i(uniformLoc, int32(tex.texUnit - gl.TEXTURE0)) 96 | return nil 97 | } 98 | 99 | func loadImageFile(file string) (image.Image, error) { 100 | infile, err := os.Open(file) 101 | if err != nil { 102 | return nil, err 103 | } 104 | defer infile.Close() 105 | 106 | // Decode automatically figures out the type of immage in the file 107 | // as long as its image/ is imported 108 | img, _, err := image.Decode(infile) 109 | return img, err 110 | } 111 | -------------------------------------------------------------------------------- /basic-camera/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | Adapted from this tutorial: http://www.learnopengl.com/#!Getting-started/Camera 5 | 6 | Shows how to create a basic controllable FPS camera. This has been refactored into 7 | classes to allow better reuse going forward. 8 | */ 9 | 10 | import ( 11 | "log" 12 | "runtime" 13 | 14 | "github.com/go-gl/gl/v4.1-core/gl" 15 | "github.com/go-gl/glfw/v3.1/glfw" 16 | "github.com/go-gl/mathgl/mgl32" 17 | 18 | "github.com/cstegel/opengl-samples-golang/basic-camera/gfx" 19 | "github.com/cstegel/opengl-samples-golang/basic-camera/win" 20 | "github.com/cstegel/opengl-samples-golang/basic-camera/cam" 21 | ) 22 | 23 | // vertices to draw 6 faces of a cube 24 | var cubeVertices = []float32{ 25 | // position // texture position 26 | -0.5, -0.5, -0.5, 0.0, 0.0, 27 | 0.5, -0.5, -0.5, 1.0, 0.0, 28 | 0.5, 0.5, -0.5, 1.0, 1.0, 29 | 0.5, 0.5, -0.5, 1.0, 1.0, 30 | -0.5, 0.5, -0.5, 0.0, 1.0, 31 | -0.5, -0.5, -0.5, 0.0, 0.0, 32 | 33 | -0.5, -0.5, 0.5, 0.0, 0.0, 34 | 0.5, -0.5, 0.5, 1.0, 0.0, 35 | 0.5, 0.5, 0.5, 1.0, 1.0, 36 | 0.5, 0.5, 0.5, 1.0, 1.0, 37 | -0.5, 0.5, 0.5, 0.0, 1.0, 38 | -0.5, -0.5, 0.5, 0.0, 0.0, 39 | 40 | -0.5, 0.5, 0.5, 1.0, 0.0, 41 | -0.5, 0.5, -0.5, 1.0, 1.0, 42 | -0.5, -0.5, -0.5, 0.0, 1.0, 43 | -0.5, -0.5, -0.5, 0.0, 1.0, 44 | -0.5, -0.5, 0.5, 0.0, 0.0, 45 | -0.5, 0.5, 0.5, 1.0, 0.0, 46 | 47 | 0.5, 0.5, 0.5, 1.0, 0.0, 48 | 0.5, 0.5, -0.5, 1.0, 1.0, 49 | 0.5, -0.5, -0.5, 0.0, 1.0, 50 | 0.5, -0.5, -0.5, 0.0, 1.0, 51 | 0.5, -0.5, 0.5, 0.0, 0.0, 52 | 0.5, 0.5, 0.5, 1.0, 0.0, 53 | 54 | -0.5, -0.5, -0.5, 0.0, 1.0, 55 | 0.5, -0.5, -0.5, 1.0, 1.0, 56 | 0.5, -0.5, 0.5, 1.0, 0.0, 57 | 0.5, -0.5, 0.5, 1.0, 0.0, 58 | -0.5, -0.5, 0.5, 0.0, 0.0, 59 | -0.5, -0.5, -0.5, 0.0, 1.0, 60 | 61 | -0.5, 0.5, -0.5, 0.0, 1.0, 62 | 0.5, 0.5, -0.5, 1.0, 1.0, 63 | 0.5, 0.5, 0.5, 1.0, 0.0, 64 | 0.5, 0.5, 0.5, 1.0, 0.0, 65 | -0.5, 0.5, 0.5, 0.0, 0.0, 66 | -0.5, 0.5, -0.5, 0.0, 1.0, 67 | } 68 | 69 | var cubePositions = [][]float32 { 70 | { 0.0, 0.0, -3.0}, 71 | { 2.0, 5.0, -15.0}, 72 | {-1.5, -2.2, -2.5 }, 73 | {-3.8, -2.0, -12.3}, 74 | { 2.4, -0.4, -3.5 }, 75 | {-1.7, 3.0, -7.5 }, 76 | { 1.3, -2.0, -2.5 }, 77 | { 1.5, 2.0, -2.5 }, 78 | { 1.5, 0.2, -1.5 }, 79 | {-1.3, 1.0, -1.5 }, 80 | } 81 | 82 | func init() { 83 | // GLFW event handling must be run on the main OS thread 84 | runtime.LockOSThread() 85 | } 86 | 87 | func main() { 88 | if err := glfw.Init(); err != nil { 89 | log.Fatalln("failed to inifitialize glfw:", err) 90 | } 91 | defer glfw.Terminate() 92 | 93 | glfw.WindowHint(glfw.Resizable, glfw.False) 94 | glfw.WindowHint(glfw.ContextVersionMajor, 4) 95 | glfw.WindowHint(glfw.ContextVersionMinor, 1) 96 | glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) 97 | glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) 98 | 99 | window := win.NewWindow(1280, 720, "basic camera") 100 | 101 | // Initialize Glow (go function bindings) 102 | if err := gl.Init(); err != nil { 103 | panic(err) 104 | } 105 | 106 | err := programLoop(window) 107 | if err != nil { 108 | log.Fatalln(err) 109 | } 110 | } 111 | 112 | /* 113 | * Creates the Vertex Array Object for a triangle. 114 | * indices is leftover from earlier samples and not used here. 115 | */ 116 | func createVAO(vertices []float32, indices []uint32) uint32 { 117 | 118 | var VAO uint32 119 | gl.GenVertexArrays(1, &VAO) 120 | 121 | var VBO uint32 122 | gl.GenBuffers(1, &VBO) 123 | 124 | var EBO uint32; 125 | gl.GenBuffers(1, &EBO) 126 | 127 | // Bind the Vertex Array Object first, then bind and set vertex buffer(s) and attribute pointers() 128 | gl.BindVertexArray(VAO) 129 | 130 | // copy vertices data into VBO (it needs to be bound first) 131 | gl.BindBuffer(gl.ARRAY_BUFFER, VBO) 132 | gl.BufferData(gl.ARRAY_BUFFER, len(vertices)*4, gl.Ptr(vertices), gl.STATIC_DRAW) 133 | 134 | // size of one whole vertex (sum of attrib sizes) 135 | var stride int32 = 3*4 + 2*4 136 | var offset int = 0 137 | 138 | // position 139 | gl.VertexAttribPointer(0, 3, gl.FLOAT, false, stride, gl.PtrOffset(offset)) 140 | gl.EnableVertexAttribArray(0) 141 | offset += 3*4 142 | 143 | // texture position 144 | gl.VertexAttribPointer(1, 2, gl.FLOAT, false, stride, gl.PtrOffset(offset)) 145 | gl.EnableVertexAttribArray(1) 146 | offset += 2*4 147 | 148 | // unbind the VAO (safe practice so we don't accidentally (mis)configure it later) 149 | gl.BindVertexArray(0) 150 | 151 | return VAO 152 | } 153 | 154 | func programLoop(window *win.Window) error { 155 | 156 | // the linked shader program determines how the data will be rendered 157 | vertShader, err := gfx.NewShaderFromFile("shaders/basic.vert", gl.VERTEX_SHADER) 158 | if err != nil { 159 | return err 160 | } 161 | 162 | fragShader, err := gfx.NewShaderFromFile("shaders/basic.frag", gl.FRAGMENT_SHADER) 163 | if err != nil { 164 | return err 165 | } 166 | 167 | program, err := gfx.NewProgram(vertShader, fragShader) 168 | if err != nil { 169 | return err 170 | } 171 | defer program.Delete() 172 | 173 | VAO := createVAO(cubeVertices, nil) 174 | texture0, err := gfx.NewTextureFromFile("../images/RTS_Crate.png", 175 | gl.CLAMP_TO_EDGE, gl.CLAMP_TO_EDGE) 176 | if err != nil { 177 | panic(err.Error()) 178 | } 179 | 180 | texture1, err := gfx.NewTextureFromFile("../images/trollface-transparent.png", 181 | gl.CLAMP_TO_EDGE, gl.CLAMP_TO_EDGE) 182 | if err != nil { 183 | panic(err.Error()) 184 | } 185 | 186 | // ensure that triangles that are "behind" others do not draw over top of them 187 | gl.Enable(gl.DEPTH_TEST) 188 | 189 | camera := cam.NewFpsCamera(mgl32.Vec3{0, 0, 3}, mgl32.Vec3{0, 1, 0}, -90, 0, window.InputManager()) 190 | 191 | for !window.ShouldClose() { 192 | 193 | // swaps in last buffer, polls for window events, and generally sets up for a new render frame 194 | window.StartFrame() 195 | 196 | // update camera position and direction from input evevnts 197 | camera.Update(window.SinceLastFrame()) 198 | 199 | // background color 200 | gl.ClearColor(0.2, 0.5, 0.5, 1.0) 201 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) // depth buffer needed for DEPTH_TEST 202 | 203 | program.Use() 204 | 205 | // bind textures 206 | texture0.Bind(gl.TEXTURE0) 207 | texture0.SetUniform(program.GetUniformLocation("ourTexture0")) 208 | 209 | texture1.Bind(gl.TEXTURE1) 210 | texture1.SetUniform(program.GetUniformLocation("ourTexture1")) 211 | 212 | // cube rotation matrices 213 | rotateX := (mgl32.Rotate3DX(mgl32.DegToRad(-60 * float32(glfw.GetTime())))) 214 | rotateY := (mgl32.Rotate3DY(mgl32.DegToRad(-60 * float32(glfw.GetTime())))) 215 | rotateZ := (mgl32.Rotate3DZ(mgl32.DegToRad(-60 * float32(glfw.GetTime())))) 216 | 217 | // creates perspective 218 | fov := float32(60.0) 219 | projectTransform := mgl32.Perspective(mgl32.DegToRad(fov), 220 | float32(window.Width())/float32(window.Height()), 221 | 0.1, 222 | 100.0) 223 | 224 | camTransform := camera.GetTransform() 225 | gl.UniformMatrix4fv(program.GetUniformLocation("camera"), 1, false, &camTransform[0]) 226 | gl.UniformMatrix4fv(program.GetUniformLocation("project"), 1, false, 227 | &projectTransform[0]) 228 | 229 | gl.BindVertexArray(VAO) 230 | 231 | // draw each cube after all coordinate system transforms are bound 232 | for _, pos := range cubePositions { 233 | worldTranslate := mgl32.Translate3D(pos[0], pos[1], pos[2]) 234 | worldTransform := (worldTranslate.Mul4(rotateX.Mul3(rotateY).Mul3(rotateZ).Mat4())) 235 | 236 | gl.UniformMatrix4fv(program.GetUniformLocation("world"), 1, false, 237 | &worldTransform[0]) 238 | 239 | gl.DrawArrays(gl.TRIANGLES, 0, 36) 240 | } 241 | 242 | gl.BindVertexArray(0) 243 | 244 | texture0.UnBind() 245 | texture1.UnBind() 246 | 247 | // end of draw loop 248 | } 249 | 250 | return nil 251 | } 252 | -------------------------------------------------------------------------------- /basic-camera/shaders/basic.frag: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | in vec2 TexCoord; 4 | 5 | out vec4 color; 6 | 7 | uniform sampler2D ourTexture0; 8 | uniform sampler2D ourTexture1; 9 | 10 | void main() 11 | { 12 | // mix the two textures together 13 | color = mix(texture(ourTexture0, TexCoord), texture(ourTexture1, TexCoord), 0.5); 14 | } 15 | -------------------------------------------------------------------------------- /basic-camera/shaders/basic.vert: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | layout (location = 0) in vec3 position; 4 | layout (location = 1) in vec2 texCoord; 5 | 6 | out vec2 TexCoord; 7 | 8 | uniform mat4 world; 9 | uniform mat4 camera; 10 | uniform mat4 project; 11 | 12 | void main() 13 | { 14 | gl_Position = project * camera * world * vec4(position, 1.0); 15 | TexCoord = texCoord; // pass the texture coords on to the fragment shader 16 | } 17 | -------------------------------------------------------------------------------- /basic-camera/win/input.go: -------------------------------------------------------------------------------- 1 | package win 2 | 3 | import ( 4 | "github.com/go-gl/glfw/v3.1/glfw" 5 | "github.com/go-gl/mathgl/mgl64" 6 | ) 7 | 8 | // Action is a configurable abstraction of a key press 9 | type Action int 10 | 11 | const ( 12 | PLAYER_FORWARD Action = iota 13 | PLAYER_BACKWARD Action = iota 14 | PLAYER_LEFT Action = iota 15 | PLAYER_RIGHT Action = iota 16 | PROGRAM_QUIT Action = iota 17 | ) 18 | 19 | type InputManager struct { 20 | actionToKeyMap map[Action]glfw.Key 21 | keysPressed [glfw.KeyLast]bool 22 | 23 | firstCursorAction bool 24 | cursor mgl64.Vec2 25 | cursorChange mgl64.Vec2 26 | cursorLast mgl64.Vec2 27 | bufferedCursorChange mgl64.Vec2 28 | } 29 | 30 | func NewInputManager() *InputManager { 31 | actionToKeyMap := map[Action]glfw.Key{ 32 | PLAYER_FORWARD: glfw.KeyW, 33 | PLAYER_BACKWARD: glfw.KeyS, 34 | PLAYER_LEFT: glfw.KeyA, 35 | PLAYER_RIGHT: glfw.KeyD, 36 | PROGRAM_QUIT: glfw.KeyEscape, 37 | } 38 | 39 | return &InputManager{ 40 | actionToKeyMap: actionToKeyMap, 41 | firstCursorAction: false, 42 | } 43 | } 44 | 45 | // IsActive returns whether the given Action is currently active 46 | func (im *InputManager) IsActive(a Action) bool { 47 | return im.keysPressed[im.actionToKeyMap[a]] 48 | } 49 | 50 | // Cursor returns the value of the cursor at the last time that CheckpointCursorChange() was called. 51 | func (im *InputManager) Cursor() mgl64.Vec2 { 52 | return im.cursor 53 | } 54 | 55 | // CursorChange returns the amount of change in the underlying cursor 56 | // since the last time CheckpointCursorChange was called 57 | func (im *InputManager) CursorChange() mgl64.Vec2 { 58 | return im.cursorChange 59 | } 60 | 61 | // CheckpointCursorChange updates the publicly available Cursor() and CursorChange() 62 | // methods to return the current Cursor and change since last time this method was called. 63 | func (im *InputManager) CheckpointCursorChange() { 64 | im.cursorChange[0] = im.bufferedCursorChange[0] 65 | im.cursorChange[1] = im.bufferedCursorChange[1] 66 | im.cursor[0] = im.cursorLast[0] 67 | im.cursor[1] = im.cursorLast[1] 68 | 69 | im.bufferedCursorChange[0] = 0 70 | im.bufferedCursorChange[1] = 0 71 | } 72 | 73 | func (im *InputManager) keyCallback(window *glfw.Window, key glfw.Key, scancode int, 74 | action glfw.Action, mods glfw.ModifierKey) { 75 | 76 | // timing for key events occurs differently from what the program loop requires 77 | // so just track what key actions occur and then access them in the program loop 78 | switch action { 79 | case glfw.Press: 80 | im.keysPressed[key] = true 81 | case glfw.Release: 82 | im.keysPressed[key] = false 83 | } 84 | } 85 | 86 | func (im *InputManager) mouseCallback(window *glfw.Window, xpos, ypos float64) { 87 | 88 | if im.firstCursorAction { 89 | im.cursorLast[0] = xpos 90 | im.cursorLast[1] = ypos 91 | im.firstCursorAction = false 92 | } 93 | 94 | im.bufferedCursorChange[0] += xpos - im.cursorLast[0] 95 | im.bufferedCursorChange[1] += ypos - im.cursorLast[1] 96 | 97 | im.cursorLast[0] = xpos 98 | im.cursorLast[1] = ypos 99 | } 100 | -------------------------------------------------------------------------------- /basic-camera/win/window.go: -------------------------------------------------------------------------------- 1 | package win 2 | 3 | import ( 4 | "log" 5 | "github.com/go-gl/glfw/v3.1/glfw" 6 | ) 7 | 8 | type Window struct { 9 | width int 10 | height int 11 | glfw *glfw.Window 12 | 13 | inputManager *InputManager 14 | firstFrame bool 15 | dTime float64 16 | lastFrameTime float64 17 | } 18 | 19 | func (w *Window) InputManager() *InputManager { 20 | return w.inputManager 21 | } 22 | 23 | func NewWindow(width, height int, title string) *Window { 24 | 25 | gWindow, err := glfw.CreateWindow(width, height, title, nil, nil) 26 | if err != nil { 27 | log.Fatalln(err) 28 | } 29 | 30 | gWindow.MakeContextCurrent() 31 | gWindow.SetInputMode(glfw.CursorMode, glfw.CursorDisabled) 32 | 33 | im := NewInputManager() 34 | 35 | gWindow.SetKeyCallback(im.keyCallback) 36 | gWindow.SetCursorPosCallback(im.mouseCallback) 37 | 38 | return &Window{ 39 | width: width, 40 | height: height, 41 | glfw: gWindow, 42 | inputManager: im, 43 | firstFrame: true, 44 | } 45 | } 46 | 47 | func (w *Window) Width() int { 48 | return w.width 49 | } 50 | 51 | func (w *Window) Height() int { 52 | return w.height 53 | } 54 | 55 | func (w *Window) ShouldClose() bool { 56 | return w.glfw.ShouldClose() 57 | } 58 | 59 | // StartFrame sets everything up to start rendering a new frame. 60 | // This includes swapping in last rendered buffer, polling for window events, 61 | // checkpointing cursor tracking, and updating the time since last frame. 62 | func (w *Window) StartFrame() { 63 | // swap in the previous rendered buffer 64 | w.glfw.SwapBuffers() 65 | 66 | // poll for UI window events 67 | glfw.PollEvents() 68 | 69 | if w.inputManager.IsActive(PROGRAM_QUIT) { 70 | w.glfw.SetShouldClose(true) 71 | } 72 | 73 | // base calculations of time since last frame (basic program loop idea) 74 | // For better advanced impl, read: http://gafferongames.com/game-physics/fix-your-timestep/ 75 | curFrameTime := glfw.GetTime() 76 | 77 | if w.firstFrame { 78 | w.lastFrameTime = curFrameTime 79 | w.firstFrame = false 80 | } 81 | 82 | w.dTime = curFrameTime - w.lastFrameTime 83 | w.lastFrameTime = curFrameTime 84 | 85 | w.inputManager.CheckpointCursorChange() 86 | } 87 | 88 | func (w *Window) SinceLastFrame() float64 { 89 | return w.dTime 90 | } 91 | -------------------------------------------------------------------------------- /basic-light/.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | -------------------------------------------------------------------------------- /basic-light/cam/camera.go: -------------------------------------------------------------------------------- 1 | package cam 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/go-gl/mathgl/mgl32" 7 | "github.com/go-gl/mathgl/mgl64" 8 | 9 | "github.com/cstegel/opengl-samples-golang/basic-light/win" 10 | ) 11 | 12 | type FpsCamera struct { 13 | // Camera options 14 | moveSpeed float64 15 | cursorSensitivity float64 16 | 17 | // Eular Angles 18 | pitch float64 19 | yaw float64 20 | 21 | // Camera attributes 22 | pos mgl32.Vec3 23 | front mgl32.Vec3 24 | up mgl32.Vec3 25 | right mgl32.Vec3 26 | worldUp mgl32.Vec3 27 | 28 | inputManager *win.InputManager 29 | } 30 | 31 | func NewFpsCamera(position, worldUp mgl32.Vec3, yaw, pitch float64, im *win.InputManager) (*FpsCamera) { 32 | cam := FpsCamera { 33 | moveSpeed: 5.00, 34 | cursorSensitivity: 0.05, 35 | pitch: pitch, 36 | yaw: yaw, 37 | pos: position, 38 | up: mgl32.Vec3{0, 1, 0}, 39 | worldUp: worldUp, 40 | inputManager: im, 41 | } 42 | 43 | return &cam 44 | } 45 | 46 | func (c *FpsCamera) Update(dTime float64) { 47 | c.updatePosition(dTime) 48 | c.updateDirection() 49 | } 50 | 51 | // UpdatePosition updates this camera's position by giving directions that 52 | // the camera is to travel in and for how long 53 | func (c *FpsCamera) updatePosition(dTime float64) { 54 | adjustedSpeed := float32(dTime * c.moveSpeed) 55 | 56 | if c.inputManager.IsActive(win.PLAYER_FORWARD) { 57 | c.pos = c.pos.Add(c.front.Mul(adjustedSpeed)) 58 | } 59 | if c.inputManager.IsActive(win.PLAYER_BACKWARD) { 60 | c.pos = c.pos.Sub(c.front.Mul(adjustedSpeed)) 61 | } 62 | if c.inputManager.IsActive(win.PLAYER_LEFT) { 63 | c.pos = c.pos.Sub(c.front.Cross(c.up).Normalize().Mul(adjustedSpeed)) 64 | } 65 | if c.inputManager.IsActive(win.PLAYER_RIGHT) { 66 | c.pos = c.pos.Add(c.front.Cross(c.up).Normalize().Mul(adjustedSpeed)) 67 | } 68 | } 69 | 70 | // UpdateCursor updates the direction of the camera by giving it delta x/y values 71 | // that came from a cursor input device 72 | func (c *FpsCamera) updateDirection() { 73 | dCursor := c.inputManager.CursorChange() 74 | 75 | dx := -c.cursorSensitivity * dCursor[0] 76 | dy := c.cursorSensitivity * dCursor[1] 77 | 78 | c.pitch += dy 79 | if c.pitch > 89.0 { 80 | c.pitch = 89.0 81 | } else if c.pitch < -89.0 { 82 | c.pitch = -89.0 83 | } 84 | 85 | c.yaw = math.Mod(c.yaw + dx, 360) 86 | c.updateVectors() 87 | } 88 | 89 | func (c *FpsCamera) updateVectors() { 90 | // x, y, z 91 | c.front[0] = float32(math.Cos(mgl64.DegToRad(c.pitch)) * math.Cos(mgl64.DegToRad(c.yaw))) 92 | c.front[1] = float32(math.Sin(mgl64.DegToRad(c.pitch))) 93 | c.front[2] = float32(math.Cos(mgl64.DegToRad(c.pitch)) * math.Sin(mgl64.DegToRad(c.yaw))) 94 | c.front = c.front.Normalize() 95 | 96 | // Gram-Schmidt process to figure out right and up vectors 97 | c.right = c.worldUp.Cross(c.front).Normalize() 98 | c.up = c.right.Cross(c.front).Normalize() 99 | } 100 | 101 | // GetCameraTransform gets the matrix to transform from world coordinates to 102 | // this camera's coordinates. 103 | func (camera *FpsCamera) GetTransform() mgl32.Mat4 { 104 | cameraTarget := camera.pos.Add(camera.front) 105 | 106 | return mgl32.LookAt( 107 | camera.pos.X(), camera.pos.Y(), camera.pos.Z(), 108 | cameraTarget.X(), cameraTarget.Y(), cameraTarget.Z(), 109 | camera.up.X(), camera.up.Y(), camera.up.Z(), 110 | ) 111 | } 112 | -------------------------------------------------------------------------------- /basic-light/gfx/shader.go: -------------------------------------------------------------------------------- 1 | package gfx 2 | 3 | import ( 4 | "io/ioutil" 5 | "strings" 6 | "fmt" 7 | 8 | "github.com/go-gl/gl/v4.1-core/gl" 9 | ) 10 | 11 | type Shader struct { 12 | handle uint32 13 | } 14 | 15 | type Program struct { 16 | handle uint32 17 | shaders []*Shader 18 | } 19 | 20 | func (shader *Shader) Delete() { 21 | gl.DeleteShader(shader.handle) 22 | } 23 | 24 | func (prog *Program) Delete() { 25 | for _, shader := range prog.shaders { 26 | shader.Delete() 27 | } 28 | gl.DeleteProgram(prog.handle) 29 | } 30 | 31 | func (prog *Program) Attach(shaders ...*Shader) { 32 | for _, shader := range shaders { 33 | gl.AttachShader(prog.handle, shader.handle) 34 | prog.shaders = append(prog.shaders, shader) 35 | } 36 | } 37 | 38 | func (prog *Program) Use() { 39 | gl.UseProgram(prog.handle) 40 | } 41 | 42 | func (prog *Program) Link() error { 43 | gl.LinkProgram(prog.handle) 44 | return getGlError(prog.handle, gl.LINK_STATUS, gl.GetProgramiv, gl.GetProgramInfoLog, 45 | "PROGRAM::LINKING_FAILURE") 46 | } 47 | 48 | func (prog *Program) GetUniformLocation(name string) int32 { 49 | return gl.GetUniformLocation(prog.handle, gl.Str(name + "\x00")) 50 | } 51 | 52 | func NewProgram(shaders ...*Shader) (*Program, error) { 53 | prog := &Program{handle:gl.CreateProgram()} 54 | prog.Attach(shaders...) 55 | 56 | if err := prog.Link(); err != nil { 57 | return nil, err 58 | } 59 | 60 | return prog, nil 61 | } 62 | 63 | func NewShader(src string, sType uint32) (*Shader, error) { 64 | 65 | handle := gl.CreateShader(sType) 66 | glSrc, freeFn := gl.Strs(src + "\x00") 67 | defer freeFn() 68 | gl.ShaderSource(handle, 1, glSrc, nil) 69 | gl.CompileShader(handle) 70 | err := getGlError(handle, gl.COMPILE_STATUS, gl.GetShaderiv, gl.GetShaderInfoLog, 71 | "SHADER::COMPILE_FAILURE::") 72 | if err != nil { 73 | return nil, err 74 | } 75 | return &Shader{handle:handle}, nil 76 | } 77 | 78 | func NewShaderFromFile(file string, sType uint32) (*Shader, error) { 79 | src, err := ioutil.ReadFile(file) 80 | if err != nil { 81 | return nil, err 82 | } 83 | handle := gl.CreateShader(sType) 84 | glSrc, freeFn := gl.Strs(string(src) + "\x00") 85 | defer freeFn() 86 | gl.ShaderSource(handle, 1, glSrc, nil) 87 | gl.CompileShader(handle) 88 | err = getGlError(handle, gl.COMPILE_STATUS, gl.GetShaderiv, gl.GetShaderInfoLog, 89 | "SHADER::COMPILE_FAILURE::" + file) 90 | if err != nil { 91 | return nil, err 92 | } 93 | return &Shader{handle:handle}, nil 94 | } 95 | 96 | type getObjIv func(uint32, uint32, *int32) 97 | type getObjInfoLog func(uint32, int32, *int32, *uint8) 98 | 99 | func getGlError(glHandle uint32, checkTrueParam uint32, getObjIvFn getObjIv, 100 | getObjInfoLogFn getObjInfoLog, failMsg string) error { 101 | 102 | var success int32 103 | getObjIvFn(glHandle, checkTrueParam, &success) 104 | 105 | if success == gl.FALSE { 106 | var logLength int32 107 | getObjIvFn(glHandle, gl.INFO_LOG_LENGTH, &logLength) 108 | 109 | log := gl.Str(strings.Repeat("\x00", int(logLength))) 110 | getObjInfoLogFn(glHandle, logLength, nil, log) 111 | 112 | return fmt.Errorf("%s: %s", failMsg, gl.GoStr(log)) 113 | } 114 | 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /basic-light/gfx/texture.go: -------------------------------------------------------------------------------- 1 | package gfx 2 | 3 | import ( 4 | "os" 5 | "errors" 6 | "image" 7 | "image/draw" 8 | _ "image/png" 9 | _ "image/jpeg" 10 | 11 | "github.com/go-gl/gl/v4.1-core/gl" 12 | ) 13 | 14 | type Texture struct { 15 | handle uint32 16 | target uint32 // same target as gl.BindTexture(, ...) 17 | texUnit uint32 // Texture unit that is currently bound to ex: gl.TEXTURE0 18 | } 19 | 20 | var errUnsupportedStride = errors.New("unsupported stride, only 32-bit colors supported") 21 | 22 | var errTextureNotBound = errors.New("texture not bound") 23 | 24 | func NewTextureFromFile(file string, wrapR, wrapS int32) (*Texture, error) { 25 | imgFile, err := os.Open(file) 26 | if err != nil { 27 | return nil, err 28 | } 29 | defer imgFile.Close() 30 | 31 | // Decode detexts the type of image as long as its image/ is imported 32 | img, _, err := image.Decode(imgFile) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return NewTexture(img, wrapR, wrapS) 37 | } 38 | 39 | func NewTexture(img image.Image, wrapR, wrapS int32) (*Texture, error) { 40 | rgba := image.NewRGBA(img.Bounds()) 41 | draw.Draw(rgba, rgba.Bounds(), img, image.Pt(0, 0), draw.Src) 42 | if rgba.Stride != rgba.Rect.Size().X*4 { // TODO-cs: why? 43 | return nil, errUnsupportedStride 44 | } 45 | 46 | var handle uint32 47 | gl.GenTextures(1, &handle) 48 | 49 | target := uint32(gl.TEXTURE_2D) 50 | internalFmt := int32(gl.SRGB_ALPHA) 51 | format := uint32(gl.RGBA) 52 | width := int32(rgba.Rect.Size().X) 53 | height := int32(rgba.Rect.Size().Y) 54 | pixType := uint32(gl.UNSIGNED_BYTE) 55 | dataPtr := gl.Ptr(rgba.Pix) 56 | 57 | texture := Texture{ 58 | handle:handle, 59 | target:target, 60 | } 61 | 62 | texture.Bind(gl.TEXTURE0) 63 | defer texture.UnBind() 64 | 65 | // set the texture wrapping/filtering options (applies to current bound texture obj) 66 | // TODO-cs 67 | gl.TexParameteri(texture.target, gl.TEXTURE_WRAP_R, wrapR) 68 | gl.TexParameteri(texture.target, gl.TEXTURE_WRAP_S, wrapS) 69 | gl.TexParameteri(texture.target, gl.TEXTURE_MIN_FILTER, gl.LINEAR) // minification filter 70 | gl.TexParameteri(texture.target, gl.TEXTURE_MAG_FILTER, gl.LINEAR) // magnification filter 71 | 72 | 73 | gl.TexImage2D(target, 0, internalFmt, width, height, 0, format, pixType, dataPtr) 74 | 75 | gl.GenerateMipmap(texture.handle) 76 | 77 | return &texture, nil 78 | } 79 | 80 | func (tex *Texture) Bind(texUnit uint32) { 81 | gl.ActiveTexture(texUnit) 82 | gl.BindTexture(tex.target, tex.handle) 83 | tex.texUnit = texUnit 84 | } 85 | 86 | func (tex *Texture) UnBind() { 87 | tex.texUnit = 0 88 | gl.BindTexture(tex.target, 0) 89 | } 90 | 91 | func (tex *Texture) SetUniform(uniformLoc int32) error { 92 | if tex.texUnit == 0 { 93 | return errTextureNotBound 94 | } 95 | gl.Uniform1i(uniformLoc, int32(tex.texUnit - gl.TEXTURE0)) 96 | return nil 97 | } 98 | 99 | func loadImageFile(file string) (image.Image, error) { 100 | infile, err := os.Open(file) 101 | if err != nil { 102 | return nil, err 103 | } 104 | defer infile.Close() 105 | 106 | // Decode automatically figures out the type of immage in the file 107 | // as long as its image/ is imported 108 | img, _, err := image.Decode(infile) 109 | return img, err 110 | } 111 | -------------------------------------------------------------------------------- /basic-light/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | http://www.learnopengl.com/#!Lighting/Basic-Lighting 5 | 6 | Shows basic Phong lighting 7 | */ 8 | 9 | import ( 10 | "log" 11 | "runtime" 12 | 13 | "github.com/go-gl/gl/v4.1-core/gl" 14 | "github.com/go-gl/glfw/v3.1/glfw" 15 | "github.com/go-gl/mathgl/mgl32" 16 | 17 | "github.com/cstegel/opengl-samples-golang/basic-light/gfx" 18 | "github.com/cstegel/opengl-samples-golang/basic-light/win" 19 | "github.com/cstegel/opengl-samples-golang/basic-light/cam" 20 | ) 21 | 22 | // vertices to draw 6 faces of a cube 23 | var cubeVertices = []float32{ 24 | // position // normal vector 25 | -0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 26 | 0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 27 | 0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 28 | 0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 29 | -0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 30 | -0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 31 | 32 | -0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 33 | 0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 34 | 0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 35 | 0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 36 | -0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 37 | -0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 38 | 39 | -0.5, 0.5, 0.5, -1.0, 0.0, 0.0, 40 | -0.5, 0.5, -0.5, -1.0, 0.0, 0.0, 41 | -0.5, -0.5, -0.5, -1.0, 0.0, 0.0, 42 | -0.5, -0.5, -0.5, -1.0, 0.0, 0.0, 43 | -0.5, -0.5, 0.5, -1.0, 0.0, 0.0, 44 | -0.5, 0.5, 0.5, -1.0, 0.0, 0.0, 45 | 46 | 0.5, 0.5, 0.5, 1.0, 0.0, 0.0, 47 | 0.5, 0.5, -0.5, 1.0, 0.0, 0.0, 48 | 0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 49 | 0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 50 | 0.5, -0.5, 0.5, 1.0, 0.0, 0.0, 51 | 0.5, 0.5, 0.5, 1.0, 0.0, 0.0, 52 | 53 | -0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 54 | 0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 55 | 0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 56 | 0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 57 | -0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 58 | -0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 59 | 60 | -0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 61 | 0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 62 | 0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 63 | 0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 64 | -0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 65 | -0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 66 | } 67 | 68 | var cubePositions = [][]float32 { 69 | { 0.0, 0.0, -3.0}, 70 | { 2.0, 5.0, -15.0}, 71 | {-1.5, -2.2, -2.5 }, 72 | {-3.8, -2.0, -12.3}, 73 | { 2.4, -0.4, -3.5 }, 74 | {-1.7, 3.0, -7.5 }, 75 | { 1.3, -2.0, -2.5 }, 76 | { 1.5, 2.0, -2.5 }, 77 | { 1.5, 0.2, -1.5 }, 78 | {-1.3, 1.0, -1.5 }, 79 | } 80 | 81 | func init() { 82 | // GLFW event handling must be run on the main OS thread 83 | runtime.LockOSThread() 84 | } 85 | 86 | func main() { 87 | if err := glfw.Init(); err != nil { 88 | log.Fatalln("failed to inifitialize glfw:", err) 89 | } 90 | defer glfw.Terminate() 91 | 92 | log.Println(glfw.GetVersionString()) 93 | 94 | glfw.WindowHint(glfw.Resizable, glfw.False) 95 | glfw.WindowHint(glfw.ContextVersionMajor, 4) 96 | glfw.WindowHint(glfw.ContextVersionMinor, 1) 97 | glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) 98 | glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) 99 | 100 | window := win.NewWindow(1280, 720, "basic light") 101 | 102 | // Initialize Glow (go function bindings) 103 | if err := gl.Init(); err != nil { 104 | panic(err) 105 | } 106 | 107 | err := programLoop(window) 108 | if err != nil { 109 | log.Fatalln(err) 110 | } 111 | } 112 | 113 | /* 114 | * Creates the Vertex Array Object for a triangle. 115 | * indices is leftover from earlier samples and not used here. 116 | */ 117 | func createVAO(vertices []float32, indices []uint32) uint32 { 118 | 119 | var VAO uint32 120 | gl.GenVertexArrays(1, &VAO) 121 | 122 | var VBO uint32 123 | gl.GenBuffers(1, &VBO) 124 | 125 | var EBO uint32; 126 | gl.GenBuffers(1, &EBO) 127 | 128 | // Bind the Vertex Array Object first, then bind and set vertex buffer(s) and attribute pointers() 129 | gl.BindVertexArray(VAO) 130 | 131 | // copy vertices data into VBO (it needs to be bound first) 132 | gl.BindBuffer(gl.ARRAY_BUFFER, VBO) 133 | gl.BufferData(gl.ARRAY_BUFFER, len(vertices)*4, gl.Ptr(vertices), gl.STATIC_DRAW) 134 | 135 | // size of one whole vertex (sum of attrib sizes) 136 | var stride int32 = 3*4 + 3*4 137 | var offset int = 0 138 | 139 | // position 140 | gl.VertexAttribPointer(0, 3, gl.FLOAT, false, stride, gl.PtrOffset(offset)) 141 | gl.EnableVertexAttribArray(0) 142 | offset += 3*4 143 | 144 | // normal 145 | gl.VertexAttribPointer(1, 3, gl.FLOAT, false, stride, gl.PtrOffset(offset)) 146 | gl.EnableVertexAttribArray(1) 147 | offset += 3*4 148 | 149 | // unbind the VAO (safe practice so we don't accidentally (mis)configure it later) 150 | gl.BindVertexArray(0) 151 | 152 | return VAO 153 | } 154 | 155 | func programLoop(window *win.Window) error { 156 | 157 | // the linked shader program determines how the data will be rendered 158 | vertShader, err := gfx.NewShaderFromFile("shaders/phong.vert", gl.VERTEX_SHADER) 159 | if err != nil { 160 | return err 161 | } 162 | 163 | fragShader, err := gfx.NewShaderFromFile("shaders/phong.frag", gl.FRAGMENT_SHADER) 164 | if err != nil { 165 | return err 166 | } 167 | 168 | program, err := gfx.NewProgram(vertShader, fragShader) 169 | if err != nil { 170 | return err 171 | } 172 | defer program.Delete() 173 | 174 | lightFragShader, err := gfx.NewShaderFromFile("shaders/light.frag", gl.FRAGMENT_SHADER) 175 | if err != nil { 176 | return err 177 | } 178 | 179 | // special shader program so that lights themselves are not affected by lighting 180 | lightProgram, err := gfx.NewProgram(vertShader, lightFragShader) 181 | if err != nil { 182 | return err 183 | } 184 | 185 | VAO := createVAO(cubeVertices, nil) 186 | lightVAO := createVAO(cubeVertices, nil) 187 | 188 | // ensure that triangles that are "behind" others do not draw over top of them 189 | gl.Enable(gl.DEPTH_TEST) 190 | 191 | camera := cam.NewFpsCamera(mgl32.Vec3{0, 0, 3}, mgl32.Vec3{0, 1, 0}, -90, 0, window.InputManager()) 192 | 193 | for !window.ShouldClose() { 194 | 195 | // swaps in last buffer, polls for window events, and generally sets up for a new render frame 196 | window.StartFrame() 197 | 198 | // update camera position and direction from input evevnts 199 | camera.Update(window.SinceLastFrame()) 200 | 201 | // background color 202 | gl.ClearColor(0, 0, 0, 1.0) 203 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) // depth buffer needed for DEPTH_TEST 204 | 205 | 206 | // cube rotation matrices 207 | rotateX := (mgl32.Rotate3DX(mgl32.DegToRad(-60 * float32(glfw.GetTime())))) 208 | rotateY := (mgl32.Rotate3DY(mgl32.DegToRad(-60 * float32(glfw.GetTime())))) 209 | rotateZ := (mgl32.Rotate3DZ(mgl32.DegToRad(-60 * float32(glfw.GetTime())))) 210 | 211 | // creates perspective 212 | fov := float32(60.0) 213 | projectTransform := mgl32.Perspective(mgl32.DegToRad(fov), 214 | float32(window.Width())/float32(window.Height()), 215 | 0.1, 216 | 100.0) 217 | 218 | camTransform := camera.GetTransform() 219 | lightPos := mgl32.Vec3{0.6, 1, 0.1} 220 | lightTransform := mgl32.Translate3D(lightPos.X(), lightPos.Y(), lightPos.Z()).Mul4( 221 | mgl32.Scale3D(0.2, 0.2, 0.2)) 222 | 223 | program.Use() 224 | gl.UniformMatrix4fv(program.GetUniformLocation("view"), 1, false, &camTransform[0]) 225 | gl.UniformMatrix4fv(program.GetUniformLocation("project"), 1, false, 226 | &projectTransform[0]) 227 | 228 | gl.BindVertexArray(VAO) 229 | 230 | // draw each cube after all coordinate system transforms are bound 231 | 232 | // obj is colored, light is white 233 | gl.Uniform3f(program.GetUniformLocation("objectColor"), 1.0, 0.5, 0.31) 234 | gl.Uniform3f(program.GetUniformLocation("lightColor"), 1.0, 1.0, 1.0) 235 | gl.Uniform3f(program.GetUniformLocation("lightPos"), lightPos.X(), lightPos.Y(), lightPos.Z()) 236 | 237 | for _, pos := range cubePositions { 238 | 239 | // turn the cubes into rectangular prisms for more fun 240 | worldTranslate := mgl32.Translate3D(pos[0], pos[1], pos[2]) 241 | worldTransform := worldTranslate.Mul4( 242 | rotateX.Mul3(rotateY).Mul3(rotateZ).Mat4().Mul4( 243 | mgl32.Scale3D(1.2, 1.2, 0.7), 244 | ), 245 | ) 246 | 247 | gl.UniformMatrix4fv(program.GetUniformLocation("model"), 1, false, 248 | &worldTransform[0]) 249 | 250 | gl.DrawArrays(gl.TRIANGLES, 0, 36) 251 | } 252 | gl.BindVertexArray(0) 253 | 254 | // Draw the light obj after the other boxes using its separate shader program 255 | // this means that we must re-bind any uniforms 256 | lightProgram.Use() 257 | gl.BindVertexArray(lightVAO) 258 | gl.UniformMatrix4fv(lightProgram.GetUniformLocation("model"), 1, false, &lightTransform[0]) 259 | gl.UniformMatrix4fv(lightProgram.GetUniformLocation("view"), 1, false, &camTransform[0]) 260 | gl.UniformMatrix4fv(lightProgram.GetUniformLocation("project"), 1, false, &projectTransform[0]) 261 | gl.DrawArrays(gl.TRIANGLES, 0, 36) 262 | 263 | gl.BindVertexArray(0) 264 | 265 | // end of draw loop 266 | } 267 | 268 | return nil 269 | } 270 | -------------------------------------------------------------------------------- /basic-light/shaders/basic.frag: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | out vec4 color; 4 | 5 | uniform vec3 objectColor; 6 | uniform vec3 lightColor; 7 | 8 | void main() 9 | { 10 | // the color of the light "reflects" off the object 11 | color = vec4(objectColor * lightColor, 1.0f); 12 | } 13 | -------------------------------------------------------------------------------- /basic-light/shaders/basic.vert: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | layout (location = 0) in vec3 position; 4 | 5 | out vec2 TexCoord; 6 | 7 | uniform mat4 world; 8 | uniform mat4 camera; 9 | uniform mat4 project; 10 | 11 | void main() 12 | { 13 | gl_Position = project * camera * world * vec4(position, 1.0); 14 | } 15 | -------------------------------------------------------------------------------- /basic-light/shaders/light.frag: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | // special fragment shader that is not affected by lighting 4 | // useful for debugging like showing locations of lights 5 | 6 | out vec4 color; 7 | 8 | void main() 9 | { 10 | color = vec4(1.0f); // color white 11 | } 12 | -------------------------------------------------------------------------------- /basic-light/shaders/phong.frag: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | in vec3 Normal; 4 | in vec3 FragPos; 5 | in vec3 LightPos; 6 | out vec4 color; 7 | 8 | uniform vec3 objectColor; 9 | uniform vec3 lightColor; 10 | 11 | void main() 12 | { 13 | // affects diffuse and specular lighting 14 | float lightPower = 2.0f; 15 | 16 | // diffuse and specular intensity are affected by the amount of light they get based on how 17 | // far they are from a light source (inverse square of distance) 18 | float distToLight = length(LightPos - FragPos); 19 | 20 | // this is not the correct equation for light decay but it is close 21 | // see light-casters sample for the proper way 22 | float distIntensityDecay = 1.0f / pow(distToLight, 2); 23 | 24 | float ambientStrength = 0.05f; 25 | vec3 ambientLight = ambientStrength * lightColor; 26 | 27 | vec3 norm = normalize(Normal); 28 | vec3 dirToLight = normalize(LightPos - FragPos); 29 | float lightNormalDiff = max(dot(norm, dirToLight), 0.0); 30 | 31 | // diffuse light is greatest when surface is perpendicular to light (dot product) 32 | vec3 diffuse = lightNormalDiff * lightColor; 33 | vec3 diffuseLight = lightPower * diffuse * distIntensityDecay * lightColor; 34 | 35 | float specularStrength = 1.0f; 36 | int shininess = 64; 37 | vec3 viewPos = vec3(0.0f, 0.0f, 0.0f); 38 | vec3 dirToView = normalize(viewPos - FragPos); 39 | vec3 reflectDir = reflect(-dirToLight, norm); 40 | float spec = pow(max(dot(dirToView, reflectDir), 0.0), shininess); 41 | vec3 specularLight = lightPower * specularStrength * spec * distIntensityDecay * lightColor; 42 | 43 | vec3 result = (diffuseLight + specularLight + ambientLight) * objectColor; 44 | color = vec4(result, 1.0f); 45 | } 46 | -------------------------------------------------------------------------------- /basic-light/shaders/phong.vert: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | layout (location = 0) in vec3 position; 4 | layout (location = 1) in vec3 normal; 5 | 6 | uniform mat4 model; 7 | uniform mat4 view; 8 | uniform mat4 project; 9 | 10 | uniform vec3 lightPos; // only need one light for a basic example 11 | 12 | out vec3 Normal; 13 | out vec3 FragPos; 14 | out vec3 LightPos; 15 | 16 | void main() 17 | { 18 | gl_Position = project * view * model * vec4(position, 1.0); 19 | 20 | // we transform positions and vectors to view space before performing lighting 21 | // calculations in the fragment shader so that we know that the viewer position is (0,0,0) 22 | // FragPos = vec3(model * vec4(position, 1.0)); 23 | FragPos = vec3(view * model * vec4(position, 1.0)); 24 | 25 | // LightPos = vec3(view * vec4(lightPos, 1.0)); 26 | LightPos = vec3(view * vec4(lightPos, 1.0)); 27 | 28 | // transform the normals to the view space 29 | // this is different from just multiplying by the model then view matrix since 30 | // normals can't translate and are changed by non-uniform scaling 31 | // instead we take the upper left 3x3 matrix of the transpose of the inverse of each transform 32 | // that we are transforming across 33 | // see here for more details: http://www.lighthouse3d.com/tutorials/glsl-tutorial/the-normal-matrix/ 34 | mat3 normMatrix = mat3(transpose(inverse(view))) * mat3(transpose(inverse(model))); 35 | Normal = normMatrix * normal; 36 | } 37 | -------------------------------------------------------------------------------- /basic-light/win/input.go: -------------------------------------------------------------------------------- 1 | package win 2 | 3 | import ( 4 | "github.com/go-gl/glfw/v3.1/glfw" 5 | "github.com/go-gl/mathgl/mgl64" 6 | ) 7 | 8 | // Action is a configurable abstraction of a key press 9 | type Action int 10 | 11 | const ( 12 | PLAYER_FORWARD Action = iota 13 | PLAYER_BACKWARD Action = iota 14 | PLAYER_LEFT Action = iota 15 | PLAYER_RIGHT Action = iota 16 | PROGRAM_QUIT Action = iota 17 | ) 18 | 19 | type InputManager struct { 20 | actionToKeyMap map[Action]glfw.Key 21 | keysPressed [glfw.KeyLast]bool 22 | 23 | firstCursorAction bool 24 | cursor mgl64.Vec2 25 | cursorChange mgl64.Vec2 26 | cursorLast mgl64.Vec2 27 | bufferedCursorChange mgl64.Vec2 28 | } 29 | 30 | func NewInputManager() *InputManager { 31 | actionToKeyMap := map[Action]glfw.Key{ 32 | PLAYER_FORWARD: glfw.KeyW, 33 | PLAYER_BACKWARD: glfw.KeyS, 34 | PLAYER_LEFT: glfw.KeyA, 35 | PLAYER_RIGHT: glfw.KeyD, 36 | PROGRAM_QUIT: glfw.KeyEscape, 37 | } 38 | 39 | return &InputManager{ 40 | actionToKeyMap: actionToKeyMap, 41 | firstCursorAction: false, 42 | } 43 | } 44 | 45 | // IsActive returns whether the given Action is currently active 46 | func (im *InputManager) IsActive(a Action) bool { 47 | return im.keysPressed[im.actionToKeyMap[a]] 48 | } 49 | 50 | // Cursor returns the value of the cursor at the last time that CheckpointCursorChange() was called. 51 | func (im *InputManager) Cursor() mgl64.Vec2 { 52 | return im.cursor 53 | } 54 | 55 | // CursorChange returns the amount of change in the underlying cursor 56 | // since the last time CheckpointCursorChange was called 57 | func (im *InputManager) CursorChange() mgl64.Vec2 { 58 | return im.cursorChange 59 | } 60 | 61 | // CheckpointCursorChange updates the publicly available Cursor() and CursorChange() 62 | // methods to return the current Cursor and change since last time this method was called. 63 | func (im *InputManager) CheckpointCursorChange() { 64 | im.cursorChange[0] = im.bufferedCursorChange[0] 65 | im.cursorChange[1] = im.bufferedCursorChange[1] 66 | im.cursor[0] = im.cursorLast[0] 67 | im.cursor[1] = im.cursorLast[1] 68 | 69 | im.bufferedCursorChange[0] = 0 70 | im.bufferedCursorChange[1] = 0 71 | } 72 | 73 | func (im *InputManager) keyCallback(window *glfw.Window, key glfw.Key, scancode int, 74 | action glfw.Action, mods glfw.ModifierKey) { 75 | 76 | // timing for key events occurs differently from what the program loop requires 77 | // so just track what key actions occur and then access them in the program loop 78 | switch action { 79 | case glfw.Press: 80 | im.keysPressed[key] = true 81 | case glfw.Release: 82 | im.keysPressed[key] = false 83 | } 84 | } 85 | 86 | func (im *InputManager) mouseCallback(window *glfw.Window, xpos, ypos float64) { 87 | 88 | if im.firstCursorAction { 89 | im.cursorLast[0] = xpos 90 | im.cursorLast[1] = ypos 91 | im.firstCursorAction = false 92 | } 93 | 94 | im.bufferedCursorChange[0] += xpos - im.cursorLast[0] 95 | im.bufferedCursorChange[1] += ypos - im.cursorLast[1] 96 | 97 | im.cursorLast[0] = xpos 98 | im.cursorLast[1] = ypos 99 | } 100 | -------------------------------------------------------------------------------- /basic-light/win/window.go: -------------------------------------------------------------------------------- 1 | package win 2 | 3 | import ( 4 | "log" 5 | "github.com/go-gl/glfw/v3.1/glfw" 6 | ) 7 | 8 | type Window struct { 9 | width int 10 | height int 11 | glfw *glfw.Window 12 | 13 | inputManager *InputManager 14 | firstFrame bool 15 | dTime float64 16 | lastFrameTime float64 17 | } 18 | 19 | func (w *Window) InputManager() *InputManager { 20 | return w.inputManager 21 | } 22 | 23 | func NewWindow(width, height int, title string) *Window { 24 | 25 | gWindow, err := glfw.CreateWindow(width, height, title, nil, nil) 26 | if err != nil { 27 | log.Fatalln(err) 28 | } 29 | 30 | gWindow.MakeContextCurrent() 31 | gWindow.SetInputMode(glfw.CursorMode, glfw.CursorDisabled) 32 | 33 | im := NewInputManager() 34 | 35 | gWindow.SetKeyCallback(im.keyCallback) 36 | gWindow.SetCursorPosCallback(im.mouseCallback) 37 | 38 | return &Window{ 39 | width: width, 40 | height: height, 41 | glfw: gWindow, 42 | inputManager: im, 43 | firstFrame: true, 44 | } 45 | } 46 | 47 | func (w *Window) Width() int { 48 | return w.width 49 | } 50 | 51 | func (w *Window) Height() int { 52 | return w.height 53 | } 54 | 55 | func (w *Window) ShouldClose() bool { 56 | return w.glfw.ShouldClose() 57 | } 58 | 59 | // StartFrame sets everything up to start rendering a new frame. 60 | // This includes swapping in last rendered buffer, polling for window events, 61 | // checkpointing cursor tracking, and updating the time since last frame. 62 | func (w *Window) StartFrame() { 63 | // swap in the previous rendered buffer 64 | w.glfw.SwapBuffers() 65 | 66 | // poll for UI window events 67 | glfw.PollEvents() 68 | 69 | if w.inputManager.IsActive(PROGRAM_QUIT) { 70 | w.glfw.SetShouldClose(true) 71 | } 72 | 73 | // base calculations of time since last frame (basic program loop idea) 74 | // For better advanced impl, read: http://gafferongames.com/game-physics/fix-your-timestep/ 75 | curFrameTime := glfw.GetTime() 76 | 77 | if w.firstFrame { 78 | w.lastFrameTime = curFrameTime 79 | w.firstFrame = false 80 | } 81 | 82 | w.dTime = curFrameTime - w.lastFrameTime 83 | w.lastFrameTime = curFrameTime 84 | 85 | w.inputManager.CheckpointCursorChange() 86 | } 87 | 88 | func (w *Window) SinceLastFrame() float64 { 89 | return w.dTime 90 | } 91 | -------------------------------------------------------------------------------- /basic-shaders/.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | -------------------------------------------------------------------------------- /basic-shaders/gfx/shader.go: -------------------------------------------------------------------------------- 1 | package gfx 2 | 3 | import ( 4 | "io/ioutil" 5 | "strings" 6 | "fmt" 7 | 8 | "github.com/go-gl/gl/v4.1-core/gl" 9 | ) 10 | 11 | type Shader struct { 12 | handle uint32 13 | } 14 | 15 | type Program struct { 16 | handle uint32 17 | shaders []*Shader 18 | } 19 | 20 | func (shader *Shader) Delete() { 21 | gl.DeleteShader(shader.handle) 22 | } 23 | 24 | func (prog *Program) Delete() { 25 | for _, shader := range prog.shaders { 26 | shader.Delete() 27 | } 28 | gl.DeleteProgram(prog.handle) 29 | } 30 | 31 | func (prog *Program) Attach(shaders ...*Shader) { 32 | for _, shader := range shaders { 33 | gl.AttachShader(prog.handle, shader.handle) 34 | prog.shaders = append(prog.shaders, shader) 35 | } 36 | } 37 | 38 | func (prog *Program) Use() { 39 | gl.UseProgram(prog.handle) 40 | } 41 | 42 | func (prog *Program) Link() error { 43 | gl.LinkProgram(prog.handle) 44 | return getGlError(prog.handle, gl.LINK_STATUS, gl.GetProgramiv, gl.GetProgramInfoLog, 45 | "PROGRAM::LINKING_FAILURE") 46 | } 47 | 48 | func NewProgram(shaders ...*Shader) (*Program, error) { 49 | prog := &Program{handle:gl.CreateProgram()} 50 | prog.Attach(shaders...) 51 | 52 | if err := prog.Link(); err != nil { 53 | return nil, err 54 | } 55 | 56 | return prog, nil 57 | } 58 | 59 | func NewShader(src string, sType uint32) (*Shader, error) { 60 | 61 | handle := gl.CreateShader(sType) 62 | glSrc, freeFn := gl.Strs(src + "\x00") 63 | defer freeFn() 64 | gl.ShaderSource(handle, 1, glSrc, nil) 65 | gl.CompileShader(handle) 66 | err := getGlError(handle, gl.COMPILE_STATUS, gl.GetShaderiv, gl.GetShaderInfoLog, 67 | "SHADER::COMPILE_FAILURE::") 68 | if err != nil { 69 | return nil, err 70 | } 71 | return &Shader{handle:handle}, nil 72 | } 73 | 74 | func NewShaderFromFile(file string, sType uint32) (*Shader, error) { 75 | src, err := ioutil.ReadFile(file) 76 | if err != nil { 77 | return nil, err 78 | } 79 | handle := gl.CreateShader(sType) 80 | glSrc, freeFn := gl.Strs(string(src) + "\x00") 81 | defer freeFn() 82 | gl.ShaderSource(handle, 1, glSrc, nil) 83 | gl.CompileShader(handle) 84 | err = getGlError(handle, gl.COMPILE_STATUS, gl.GetShaderiv, gl.GetShaderInfoLog, 85 | "SHADER::COMPILE_FAILURE::" + file) 86 | if err != nil { 87 | return nil, err 88 | } 89 | return &Shader{handle:handle}, nil 90 | } 91 | 92 | type getObjIv func(uint32, uint32, *int32) 93 | type getObjInfoLog func(uint32, int32, *int32, *uint8) 94 | 95 | func getGlError(glHandle uint32, checkTrueParam uint32, getObjIvFn getObjIv, 96 | getObjInfoLogFn getObjInfoLog, failMsg string) error { 97 | 98 | var success int32 99 | getObjIvFn(glHandle, checkTrueParam, &success) 100 | 101 | if success == gl.FALSE { 102 | var logLength int32 103 | getObjIvFn(glHandle, gl.INFO_LOG_LENGTH, &logLength) 104 | 105 | log := gl.Str(strings.Repeat("\x00", int(logLength))) 106 | getObjInfoLogFn(glHandle, logLength, nil, log) 107 | 108 | return fmt.Errorf("%s: %s", failMsg, gl.GoStr(log)) 109 | } 110 | 111 | return nil 112 | } 113 | -------------------------------------------------------------------------------- /basic-shaders/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | Adapted from this tutorial: http://www.learnopengl.com/#!Getting-started/Shaders 5 | 6 | Shows how to pass both position and color as inputs to a shader via a VBO 7 | */ 8 | 9 | import ( 10 | "log" 11 | "runtime" 12 | "unsafe" 13 | 14 | "github.com/go-gl/gl/v4.1-core/gl" 15 | "github.com/go-gl/glfw/v3.1/glfw" 16 | 17 | "github.com/cstegel/opengl-samples-golang/basic-shaders/gfx" 18 | ) 19 | 20 | const windowWidth = 800 21 | const windowHeight = 600 22 | 23 | func init() { 24 | // GLFW event handling must be run on the main OS thread 25 | runtime.LockOSThread() 26 | } 27 | 28 | func main() { 29 | if err := glfw.Init(); err != nil { 30 | log.Fatalln("failed to inifitialize glfw:", err) 31 | } 32 | defer glfw.Terminate() 33 | 34 | glfw.WindowHint(glfw.Resizable, glfw.False) 35 | glfw.WindowHint(glfw.ContextVersionMajor, 4) 36 | glfw.WindowHint(glfw.ContextVersionMinor, 1) 37 | glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) 38 | glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) 39 | window, err := glfw.CreateWindow(windowWidth, windowHeight, "basic shaders", nil, nil) 40 | if err != nil { 41 | panic(err) 42 | } 43 | window.MakeContextCurrent() 44 | 45 | // Initialize Glow (go function bindings) 46 | if err := gl.Init(); err != nil { 47 | panic(err) 48 | } 49 | 50 | window.SetKeyCallback(keyCallback) 51 | 52 | err = programLoop(window) 53 | if err != nil { 54 | log.Fatal(err) 55 | } 56 | } 57 | 58 | /* 59 | * Creates the Vertex Array Object for a triangle. 60 | */ 61 | func createTriangleVAO(vertices []float32, indices []uint32) uint32 { 62 | 63 | var VAO uint32 64 | gl.GenVertexArrays(1, &VAO) 65 | 66 | var VBO uint32 67 | gl.GenBuffers(1, &VBO) 68 | 69 | var EBO uint32; 70 | gl.GenBuffers(1, &EBO) 71 | 72 | // Bind the Vertex Array Object first, then bind and set vertex buffer(s) and attribute pointers() 73 | gl.BindVertexArray(VAO) 74 | 75 | // copy vertices data into VBO (it needs to be bound first) 76 | gl.BindBuffer(gl.ARRAY_BUFFER, VBO) 77 | gl.BufferData(gl.ARRAY_BUFFER, len(vertices)*4, gl.Ptr(vertices), gl.STATIC_DRAW) 78 | 79 | // copy indices into element buffer 80 | gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, EBO) 81 | gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(indices)*4, gl.Ptr(indices), gl.STATIC_DRAW) 82 | 83 | // position 84 | gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 6*4, gl.PtrOffset(0)) 85 | gl.EnableVertexAttribArray(0) 86 | 87 | // color 88 | gl.VertexAttribPointer(1, 3, gl.FLOAT, false, 6*4, gl.PtrOffset(3*4)) 89 | gl.EnableVertexAttribArray(1) 90 | 91 | // unbind the VAO (safe practice so we don't accidentally (mis)configure it later) 92 | gl.BindVertexArray(0) 93 | 94 | return VAO 95 | } 96 | 97 | func programLoop(window *glfw.Window) error { 98 | 99 | // the linked shader program determines how the data will be rendered 100 | vertShader, err := gfx.NewShaderFromFile("shaders/basic.vert", gl.VERTEX_SHADER) 101 | if err != nil { 102 | return err 103 | } 104 | 105 | fragShader, err := gfx.NewShaderFromFile("shaders/basic.frag", gl.FRAGMENT_SHADER) 106 | if err != nil { 107 | return err 108 | } 109 | 110 | shaderProgram, err := gfx.NewProgram(vertShader, fragShader) 111 | if err != nil { 112 | return err 113 | } 114 | defer shaderProgram.Delete() 115 | 116 | vertices := []float32{ 117 | // top 118 | 0.0, 0.5, 0.0, // position 119 | 1.0, 0.0, 0.0, // Color 120 | 121 | // bottom right 122 | 0.5, -0.5, 0.0, 123 | 0.0, 1.0, 0.0, 124 | 125 | // bottom left 126 | -0.5, -0.5, 0.0, 127 | 0.0, 0.0, 1.0, 128 | } 129 | 130 | indices := []uint32{ 131 | 0, 1, 2, // only triangle 132 | } 133 | 134 | VAO := createTriangleVAO(vertices, indices) 135 | 136 | for !window.ShouldClose() { 137 | // poll events and call their registered callbacks 138 | glfw.PollEvents() 139 | 140 | // perform rendering 141 | gl.ClearColor(0.2, 0.5, 0.5, 1.0) 142 | gl.Clear(gl.COLOR_BUFFER_BIT) 143 | 144 | // draw loop 145 | 146 | // draw triangle 147 | shaderProgram.Use() 148 | gl.BindVertexArray(VAO) 149 | gl.DrawElements(gl.TRIANGLES, 3, gl.UNSIGNED_INT, unsafe.Pointer(nil)) 150 | gl.BindVertexArray(0) 151 | 152 | // end of draw loop 153 | 154 | // swap in the rendered buffer 155 | window.SwapBuffers() 156 | } 157 | 158 | return nil 159 | } 160 | 161 | func keyCallback(window *glfw.Window, key glfw.Key, scancode int, action glfw.Action, 162 | mods glfw.ModifierKey) { 163 | 164 | // When a user presses the escape key, we set the WindowShouldClose property to true, 165 | // which closes the application 166 | if key == glfw.KeyEscape && action == glfw.Press { 167 | window.SetShouldClose(true) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /basic-shaders/shaders/basic.frag: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | in vec3 ourColor; 4 | out vec4 color; 5 | 6 | void main() 7 | { 8 | color = vec4(ourColor, 1.0f); 9 | } 10 | -------------------------------------------------------------------------------- /basic-shaders/shaders/basic.vert: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | layout (location = 0) in vec3 position; 4 | layout (location = 1) in vec3 color; 5 | 6 | out vec3 ourColor; // Output a color to the fragment shader 7 | 8 | void main() 9 | { 10 | gl_Position = vec4(position, 1.0); 11 | ourColor = color; // Set ourColor to the input color we got from the vertex data 12 | } 13 | -------------------------------------------------------------------------------- /basic-textures/.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | -------------------------------------------------------------------------------- /basic-textures/basic-textures.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstegel/opengl-samples-golang/1ed56cc6485acc36ebd30234dd7f405d2080b844/basic-textures/basic-textures.png -------------------------------------------------------------------------------- /basic-textures/gfx/shader.go: -------------------------------------------------------------------------------- 1 | package gfx 2 | 3 | import ( 4 | "io/ioutil" 5 | "strings" 6 | "fmt" 7 | 8 | "github.com/go-gl/gl/v4.1-core/gl" 9 | ) 10 | 11 | type Shader struct { 12 | handle uint32 13 | } 14 | 15 | type Program struct { 16 | handle uint32 17 | shaders []*Shader 18 | } 19 | 20 | func (shader *Shader) Delete() { 21 | gl.DeleteShader(shader.handle) 22 | } 23 | 24 | func (prog *Program) Delete() { 25 | for _, shader := range prog.shaders { 26 | shader.Delete() 27 | } 28 | gl.DeleteProgram(prog.handle) 29 | } 30 | 31 | func (prog *Program) Attach(shaders ...*Shader) { 32 | for _, shader := range shaders { 33 | gl.AttachShader(prog.handle, shader.handle) 34 | prog.shaders = append(prog.shaders, shader) 35 | } 36 | } 37 | 38 | func (prog *Program) Use() { 39 | gl.UseProgram(prog.handle) 40 | } 41 | 42 | func (prog *Program) Link() error { 43 | gl.LinkProgram(prog.handle) 44 | return getGlError(prog.handle, gl.LINK_STATUS, gl.GetProgramiv, gl.GetProgramInfoLog, 45 | "PROGRAM::LINKING_FAILURE") 46 | } 47 | 48 | func (prog *Program) GetUniformLocation(name string) int32 { 49 | return gl.GetUniformLocation(prog.handle, gl.Str(name + "\x00")) 50 | } 51 | 52 | func NewProgram(shaders ...*Shader) (*Program, error) { 53 | prog := &Program{handle:gl.CreateProgram()} 54 | prog.Attach(shaders...) 55 | 56 | if err := prog.Link(); err != nil { 57 | return nil, err 58 | } 59 | 60 | return prog, nil 61 | } 62 | 63 | func NewShader(src string, sType uint32) (*Shader, error) { 64 | 65 | handle := gl.CreateShader(sType) 66 | glSrc, freeFn := gl.Strs(src + "\x00") 67 | defer freeFn() 68 | gl.ShaderSource(handle, 1, glSrc, nil) 69 | gl.CompileShader(handle) 70 | err := getGlError(handle, gl.COMPILE_STATUS, gl.GetShaderiv, gl.GetShaderInfoLog, 71 | "SHADER::COMPILE_FAILURE::") 72 | if err != nil { 73 | return nil, err 74 | } 75 | return &Shader{handle:handle}, nil 76 | } 77 | 78 | func NewShaderFromFile(file string, sType uint32) (*Shader, error) { 79 | src, err := ioutil.ReadFile(file) 80 | if err != nil { 81 | return nil, err 82 | } 83 | handle := gl.CreateShader(sType) 84 | glSrc, freeFn := gl.Strs(string(src) + "\x00") 85 | defer freeFn() 86 | gl.ShaderSource(handle, 1, glSrc, nil) 87 | gl.CompileShader(handle) 88 | err = getGlError(handle, gl.COMPILE_STATUS, gl.GetShaderiv, gl.GetShaderInfoLog, 89 | "SHADER::COMPILE_FAILURE::" + file) 90 | if err != nil { 91 | return nil, err 92 | } 93 | return &Shader{handle:handle}, nil 94 | } 95 | 96 | type getObjIv func(uint32, uint32, *int32) 97 | type getObjInfoLog func(uint32, int32, *int32, *uint8) 98 | 99 | func getGlError(glHandle uint32, checkTrueParam uint32, getObjIvFn getObjIv, 100 | getObjInfoLogFn getObjInfoLog, failMsg string) error { 101 | 102 | var success int32 103 | getObjIvFn(glHandle, checkTrueParam, &success) 104 | 105 | if success == gl.FALSE { 106 | var logLength int32 107 | getObjIvFn(glHandle, gl.INFO_LOG_LENGTH, &logLength) 108 | 109 | log := gl.Str(strings.Repeat("\x00", int(logLength))) 110 | getObjInfoLogFn(glHandle, logLength, nil, log) 111 | 112 | return fmt.Errorf("%s: %s", failMsg, gl.GoStr(log)) 113 | } 114 | 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /basic-textures/gfx/texture.go: -------------------------------------------------------------------------------- 1 | package gfx 2 | 3 | import ( 4 | "os" 5 | "errors" 6 | "image" 7 | "image/draw" 8 | _ "image/png" 9 | _ "image/jpeg" 10 | 11 | "github.com/go-gl/gl/v4.1-core/gl" 12 | ) 13 | 14 | type Texture struct { 15 | handle uint32 16 | target uint32 // same target as gl.BindTexture(, ...) 17 | texUnit uint32 // Texture unit that is currently bound to ex: gl.TEXTURE0 18 | } 19 | 20 | var errUnsupportedStride = errors.New("unsupported stride, only 32-bit colors supported") 21 | 22 | var errTextureNotBound = errors.New("texture not bound") 23 | 24 | func NewTextureFromFile(file string, wrapR, wrapS int32) (*Texture, error) { 25 | imgFile, err := os.Open(file) 26 | if err != nil { 27 | return nil, err 28 | } 29 | defer imgFile.Close() 30 | 31 | // Decode detexts the type of image as long as its image/ is imported 32 | img, _, err := image.Decode(imgFile) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return NewTexture(img, wrapR, wrapS) 37 | } 38 | 39 | func NewTexture(img image.Image, wrapR, wrapS int32) (*Texture, error) { 40 | rgba := image.NewRGBA(img.Bounds()) 41 | draw.Draw(rgba, rgba.Bounds(), img, image.Pt(0, 0), draw.Src) 42 | if rgba.Stride != rgba.Rect.Size().X*4 { // TODO-cs: why? 43 | return nil, errUnsupportedStride 44 | } 45 | 46 | var handle uint32 47 | gl.GenTextures(1, &handle) 48 | 49 | target := uint32(gl.TEXTURE_2D) 50 | internalFmt := int32(gl.SRGB_ALPHA) 51 | format := uint32(gl.RGBA) 52 | width := int32(rgba.Rect.Size().X) 53 | height := int32(rgba.Rect.Size().Y) 54 | pixType := uint32(gl.UNSIGNED_BYTE) 55 | dataPtr := gl.Ptr(rgba.Pix) 56 | 57 | texture := Texture{ 58 | handle:handle, 59 | target:target, 60 | } 61 | 62 | texture.Bind(gl.TEXTURE0) 63 | defer texture.UnBind() 64 | 65 | // set the texture wrapping/filtering options (applies to current bound texture obj) 66 | // TODO-cs 67 | gl.TexParameteri(texture.target, gl.TEXTURE_WRAP_R, wrapR) 68 | gl.TexParameteri(texture.target, gl.TEXTURE_WRAP_S, wrapS) 69 | gl.TexParameteri(texture.target, gl.TEXTURE_MIN_FILTER, gl.LINEAR) // minification filter 70 | gl.TexParameteri(texture.target, gl.TEXTURE_MAG_FILTER, gl.LINEAR) // magnification filter 71 | 72 | 73 | gl.TexImage2D(target, 0, internalFmt, width, height, 0, format, pixType, dataPtr) 74 | 75 | gl.GenerateMipmap(texture.handle) 76 | 77 | return &texture, nil 78 | } 79 | 80 | func (tex *Texture) Bind(texUnit uint32) { 81 | gl.ActiveTexture(texUnit) 82 | gl.BindTexture(tex.target, tex.handle) 83 | tex.texUnit = texUnit 84 | } 85 | 86 | func (tex *Texture) UnBind() { 87 | tex.texUnit = 0 88 | gl.BindTexture(tex.target, 0) 89 | } 90 | 91 | func (tex *Texture) SetUniform(uniformLoc int32) error { 92 | if tex.texUnit == 0 { 93 | return errTextureNotBound 94 | } 95 | gl.Uniform1i(uniformLoc, int32(tex.texUnit - gl.TEXTURE0)) 96 | return nil 97 | } 98 | 99 | func loadImageFile(file string) (image.Image, error) { 100 | infile, err := os.Open(file) 101 | if err != nil { 102 | return nil, err 103 | } 104 | defer infile.Close() 105 | 106 | // Decode automatically figures out the type of immage in the file 107 | // as long as its image/ is imported 108 | img, _, err := image.Decode(infile) 109 | return img, err 110 | } 111 | -------------------------------------------------------------------------------- /basic-textures/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | Adapted from this tutorial: http://www.learnopengl.com/#!Getting-started/Textures 5 | 6 | Shows how to use basic textures in openGL 7 | */ 8 | 9 | import ( 10 | "log" 11 | "runtime" 12 | "unsafe" 13 | 14 | "github.com/go-gl/gl/v4.1-core/gl" 15 | "github.com/go-gl/glfw/v3.1/glfw" 16 | 17 | "github.com/cstegel/opengl-samples-golang/basic-textures/gfx" 18 | ) 19 | 20 | const windowWidth = 800 21 | const windowHeight = 600 22 | 23 | func init() { 24 | // GLFW event handling must be run on the main OS thread 25 | runtime.LockOSThread() 26 | } 27 | 28 | func main() { 29 | if err := glfw.Init(); err != nil { 30 | log.Fatalln("failed to inifitialize glfw:", err) 31 | } 32 | defer glfw.Terminate() 33 | 34 | glfw.WindowHint(glfw.Resizable, glfw.False) 35 | glfw.WindowHint(glfw.ContextVersionMajor, 4) 36 | glfw.WindowHint(glfw.ContextVersionMinor, 1) 37 | glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) 38 | glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) 39 | window, err := glfw.CreateWindow(windowWidth, windowHeight, "basic textures", nil, nil) 40 | if err != nil { 41 | panic(err) 42 | } 43 | window.MakeContextCurrent() 44 | 45 | // Initialize Glow (go function bindings) 46 | if err := gl.Init(); err != nil { 47 | panic(err) 48 | } 49 | 50 | window.SetKeyCallback(keyCallback) 51 | 52 | err = programLoop(window) 53 | if err != nil { 54 | log.Fatal(err) 55 | } 56 | } 57 | 58 | /* 59 | * Creates the Vertex Array Object for a triangle. 60 | */ 61 | func createVAO(vertices []float32, indices []uint32) uint32 { 62 | 63 | var VAO uint32 64 | gl.GenVertexArrays(1, &VAO) 65 | 66 | var VBO uint32 67 | gl.GenBuffers(1, &VBO) 68 | 69 | var EBO uint32; 70 | gl.GenBuffers(1, &EBO) 71 | 72 | // Bind the Vertex Array Object first, then bind and set vertex buffer(s) and attribute pointers() 73 | gl.BindVertexArray(VAO) 74 | 75 | // copy vertices data into VBO (it needs to be bound first) 76 | gl.BindBuffer(gl.ARRAY_BUFFER, VBO) 77 | gl.BufferData(gl.ARRAY_BUFFER, len(vertices)*4, gl.Ptr(vertices), gl.STATIC_DRAW) 78 | 79 | // copy indices into element buffer 80 | gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, EBO) 81 | gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(indices)*4, gl.Ptr(indices), gl.STATIC_DRAW) 82 | 83 | // size of one whole vertex (sum of attrib sizes) 84 | var stride int32 = 3*4 + 3*4 + 2*4 85 | var offset int = 0 86 | 87 | // position 88 | gl.VertexAttribPointer(0, 3, gl.FLOAT, false, stride, gl.PtrOffset(offset)) 89 | gl.EnableVertexAttribArray(0) 90 | offset += 3*4 91 | 92 | // color 93 | gl.VertexAttribPointer(1, 3, gl.FLOAT, false, stride, gl.PtrOffset(offset)) 94 | gl.EnableVertexAttribArray(1) 95 | offset += 3*4 96 | 97 | // texture position 98 | gl.VertexAttribPointer(2, 2, gl.FLOAT, false, stride, gl.PtrOffset(offset)) 99 | gl.EnableVertexAttribArray(2) 100 | offset += 2*4 101 | 102 | // unbind the VAO (safe practice so we don't accidentally (mis)configure it later) 103 | gl.BindVertexArray(0) 104 | 105 | return VAO 106 | } 107 | 108 | func programLoop(window *glfw.Window) error { 109 | 110 | // the linked shader program determines how the data will be rendered 111 | vertShader, err := gfx.NewShaderFromFile("shaders/basic.vert", gl.VERTEX_SHADER) 112 | if err != nil { 113 | return err 114 | } 115 | 116 | fragShader, err := gfx.NewShaderFromFile("shaders/basic.frag", gl.FRAGMENT_SHADER) 117 | if err != nil { 118 | return err 119 | } 120 | 121 | shaderProgram, err := gfx.NewProgram(vertShader, fragShader) 122 | if err != nil { 123 | return err 124 | } 125 | defer shaderProgram.Delete() 126 | 127 | vertices := []float32{ 128 | // top left 129 | -0.75, 0.75, 0.0, // position 130 | 1.0, 0.0, 0.0, // Color 131 | 1.0, 0.0, // texture coordinates 132 | 133 | // top right 134 | 0.75, 0.75, 0.0, 135 | 0.0, 1.0, 0.0, 136 | 0.0, 0.0, 137 | 138 | // bottom right 139 | 0.75, -0.75, 0.0, 140 | 0.0, 0.0, 1.0, 141 | 0.0, 1.0, 142 | 143 | // bottom left 144 | -0.75, -0.75, 0.0, 145 | 1.0, 1.0, 1.0, 146 | 1.0, 1.0, 147 | } 148 | 149 | indices := []uint32{ 150 | // rectangle 151 | 0, 1, 2, // top triangle 152 | 0, 2, 3, // bottom triangle 153 | } 154 | 155 | VAO := createVAO(vertices, indices) 156 | texture0, err := gfx.NewTextureFromFile("../images/RTS_Crate.png", 157 | gl.CLAMP_TO_EDGE, gl.CLAMP_TO_EDGE) 158 | if err != nil { 159 | panic(err.Error()) 160 | } 161 | 162 | texture1, err := gfx.NewTextureFromFile("../images/trollface.png", 163 | gl.CLAMP_TO_EDGE, gl.CLAMP_TO_EDGE) 164 | if err != nil { 165 | panic(err.Error()) 166 | } 167 | 168 | for !window.ShouldClose() { 169 | // poll events and call their registered callbacks 170 | glfw.PollEvents() 171 | 172 | // background color 173 | gl.ClearColor(0.2, 0.5, 0.5, 1.0) 174 | gl.Clear(gl.COLOR_BUFFER_BIT) 175 | 176 | // draw vertices 177 | shaderProgram.Use() 178 | 179 | // set texture0 to uniform0 in the fragment shader 180 | texture0.Bind(gl.TEXTURE0) 181 | texture0.SetUniform(shaderProgram.GetUniformLocation("ourTexture0")) 182 | 183 | // set texture1 to uniform1 in the fragment shader 184 | texture1.Bind(gl.TEXTURE1) 185 | texture1.SetUniform(shaderProgram.GetUniformLocation("ourTexture1")) 186 | 187 | gl.BindVertexArray(VAO) 188 | gl.DrawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, unsafe.Pointer(nil)) 189 | gl.BindVertexArray(0) 190 | 191 | texture0.UnBind() 192 | texture1.UnBind() 193 | 194 | // end of draw loop 195 | 196 | // swap in the rendered buffer 197 | window.SwapBuffers() 198 | } 199 | 200 | return nil 201 | } 202 | 203 | func keyCallback(window *glfw.Window, key glfw.Key, scancode int, action glfw.Action, 204 | mods glfw.ModifierKey) { 205 | 206 | // When a user presses the escape key, we set the WindowShouldClose property to true, 207 | // which closes the application 208 | if key == glfw.KeyEscape && action == glfw.Press { 209 | window.SetShouldClose(true) 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /basic-textures/shaders/basic.frag: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | in vec3 ourColor; 4 | in vec2 TexCoord; 5 | 6 | out vec4 color; 7 | 8 | uniform sampler2D ourTexture0; 9 | uniform sampler2D ourTexture1; 10 | 11 | void main() 12 | { 13 | // mix the two textures together (texture1 is colored with "ourColor") 14 | color = mix(texture(ourTexture0, TexCoord), texture(ourTexture1, TexCoord) * vec4(ourColor, 1.0f), 0.5); 15 | } 16 | -------------------------------------------------------------------------------- /basic-textures/shaders/basic.vert: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | layout (location = 0) in vec3 position; 4 | layout (location = 1) in vec3 color; 5 | layout (location = 2) in vec2 texCoord; 6 | 7 | out vec3 ourColor; 8 | out vec2 TexCoord; 9 | 10 | void main() 11 | { 12 | gl_Position = vec4(position, 1.0); 13 | ourColor = color; // pass the color on to the fragment shader 14 | TexCoord = texCoord; // pass the texture coords on to the fragment shader 15 | } 16 | -------------------------------------------------------------------------------- /colors/.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | -------------------------------------------------------------------------------- /colors/cam/camera.go: -------------------------------------------------------------------------------- 1 | package cam 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/go-gl/mathgl/mgl32" 7 | "github.com/go-gl/mathgl/mgl64" 8 | 9 | "github.com/cstegel/opengl-samples-golang/colors/win" 10 | ) 11 | 12 | type FpsCamera struct { 13 | // Camera options 14 | moveSpeed float64 15 | cursorSensitivity float64 16 | 17 | // Eular Angles 18 | pitch float64 19 | yaw float64 20 | 21 | // Camera attributes 22 | pos mgl32.Vec3 23 | front mgl32.Vec3 24 | up mgl32.Vec3 25 | right mgl32.Vec3 26 | worldUp mgl32.Vec3 27 | 28 | inputManager *win.InputManager 29 | } 30 | 31 | func NewFpsCamera(position, worldUp mgl32.Vec3, yaw, pitch float64, im *win.InputManager) (*FpsCamera) { 32 | cam := FpsCamera { 33 | moveSpeed: 5.00, 34 | cursorSensitivity: 0.05, 35 | pitch: pitch, 36 | yaw: yaw, 37 | pos: position, 38 | up: mgl32.Vec3{0, 1, 0}, 39 | worldUp: worldUp, 40 | inputManager: im, 41 | } 42 | 43 | return &cam 44 | } 45 | 46 | func (c *FpsCamera) Update(dTime float64) { 47 | c.updatePosition(dTime) 48 | c.updateDirection() 49 | } 50 | 51 | // UpdatePosition updates this camera's position by giving directions that 52 | // the camera is to travel in and for how long 53 | func (c *FpsCamera) updatePosition(dTime float64) { 54 | adjustedSpeed := float32(dTime * c.moveSpeed) 55 | 56 | if c.inputManager.IsActive(win.PLAYER_FORWARD) { 57 | c.pos = c.pos.Add(c.front.Mul(adjustedSpeed)) 58 | } 59 | if c.inputManager.IsActive(win.PLAYER_BACKWARD) { 60 | c.pos = c.pos.Sub(c.front.Mul(adjustedSpeed)) 61 | } 62 | if c.inputManager.IsActive(win.PLAYER_LEFT) { 63 | c.pos = c.pos.Sub(c.front.Cross(c.up).Normalize().Mul(adjustedSpeed)) 64 | } 65 | if c.inputManager.IsActive(win.PLAYER_RIGHT) { 66 | c.pos = c.pos.Add(c.front.Cross(c.up).Normalize().Mul(adjustedSpeed)) 67 | } 68 | } 69 | 70 | // UpdateCursor updates the direction of the camera by giving it delta x/y values 71 | // that came from a cursor input device 72 | func (c *FpsCamera) updateDirection() { 73 | dCursor := c.inputManager.CursorChange() 74 | 75 | dx := -c.cursorSensitivity * dCursor[0] 76 | dy := c.cursorSensitivity * dCursor[1] 77 | 78 | c.pitch += dy 79 | if c.pitch > 89.0 { 80 | c.pitch = 89.0 81 | } else if c.pitch < -89.0 { 82 | c.pitch = -89.0 83 | } 84 | 85 | c.yaw = math.Mod(c.yaw + dx, 360) 86 | c.updateVectors() 87 | } 88 | 89 | func (c *FpsCamera) updateVectors() { 90 | // x, y, z 91 | c.front[0] = float32(math.Cos(mgl64.DegToRad(c.pitch)) * math.Cos(mgl64.DegToRad(c.yaw))) 92 | c.front[1] = float32(math.Sin(mgl64.DegToRad(c.pitch))) 93 | c.front[2] = float32(math.Cos(mgl64.DegToRad(c.pitch)) * math.Sin(mgl64.DegToRad(c.yaw))) 94 | c.front = c.front.Normalize() 95 | 96 | // Gram-Schmidt process to figure out right and up vectors 97 | c.right = c.worldUp.Cross(c.front).Normalize() 98 | c.up = c.right.Cross(c.front).Normalize() 99 | } 100 | 101 | // GetCameraTransform gets the matrix to transform from world coordinates to 102 | // this camera's coordinates. 103 | func (camera *FpsCamera) GetTransform() mgl32.Mat4 { 104 | cameraTarget := camera.pos.Add(camera.front) 105 | 106 | return mgl32.LookAt( 107 | camera.pos.X(), camera.pos.Y(), camera.pos.Z(), 108 | cameraTarget.X(), cameraTarget.Y(), cameraTarget.Z(), 109 | camera.up.X(), camera.up.Y(), camera.up.Z(), 110 | ) 111 | } 112 | -------------------------------------------------------------------------------- /colors/gfx/shader.go: -------------------------------------------------------------------------------- 1 | package gfx 2 | 3 | import ( 4 | "io/ioutil" 5 | "strings" 6 | "fmt" 7 | 8 | "github.com/go-gl/gl/v4.1-core/gl" 9 | ) 10 | 11 | type Shader struct { 12 | handle uint32 13 | } 14 | 15 | type Program struct { 16 | handle uint32 17 | shaders []*Shader 18 | } 19 | 20 | func (shader *Shader) Delete() { 21 | gl.DeleteShader(shader.handle) 22 | } 23 | 24 | func (prog *Program) Delete() { 25 | for _, shader := range prog.shaders { 26 | shader.Delete() 27 | } 28 | gl.DeleteProgram(prog.handle) 29 | } 30 | 31 | func (prog *Program) Attach(shaders ...*Shader) { 32 | for _, shader := range shaders { 33 | gl.AttachShader(prog.handle, shader.handle) 34 | prog.shaders = append(prog.shaders, shader) 35 | } 36 | } 37 | 38 | func (prog *Program) Use() { 39 | gl.UseProgram(prog.handle) 40 | } 41 | 42 | func (prog *Program) Link() error { 43 | gl.LinkProgram(prog.handle) 44 | return getGlError(prog.handle, gl.LINK_STATUS, gl.GetProgramiv, gl.GetProgramInfoLog, 45 | "PROGRAM::LINKING_FAILURE") 46 | } 47 | 48 | func (prog *Program) GetUniformLocation(name string) int32 { 49 | return gl.GetUniformLocation(prog.handle, gl.Str(name + "\x00")) 50 | } 51 | 52 | func NewProgram(shaders ...*Shader) (*Program, error) { 53 | prog := &Program{handle:gl.CreateProgram()} 54 | prog.Attach(shaders...) 55 | 56 | if err := prog.Link(); err != nil { 57 | return nil, err 58 | } 59 | 60 | return prog, nil 61 | } 62 | 63 | func NewShader(src string, sType uint32) (*Shader, error) { 64 | 65 | handle := gl.CreateShader(sType) 66 | glSrc, freeFn := gl.Strs(src + "\x00") 67 | defer freeFn() 68 | gl.ShaderSource(handle, 1, glSrc, nil) 69 | gl.CompileShader(handle) 70 | err := getGlError(handle, gl.COMPILE_STATUS, gl.GetShaderiv, gl.GetShaderInfoLog, 71 | "SHADER::COMPILE_FAILURE::") 72 | if err != nil { 73 | return nil, err 74 | } 75 | return &Shader{handle:handle}, nil 76 | } 77 | 78 | func NewShaderFromFile(file string, sType uint32) (*Shader, error) { 79 | src, err := ioutil.ReadFile(file) 80 | if err != nil { 81 | return nil, err 82 | } 83 | handle := gl.CreateShader(sType) 84 | glSrc, freeFn := gl.Strs(string(src) + "\x00") 85 | defer freeFn() 86 | gl.ShaderSource(handle, 1, glSrc, nil) 87 | gl.CompileShader(handle) 88 | err = getGlError(handle, gl.COMPILE_STATUS, gl.GetShaderiv, gl.GetShaderInfoLog, 89 | "SHADER::COMPILE_FAILURE::" + file) 90 | if err != nil { 91 | return nil, err 92 | } 93 | return &Shader{handle:handle}, nil 94 | } 95 | 96 | type getObjIv func(uint32, uint32, *int32) 97 | type getObjInfoLog func(uint32, int32, *int32, *uint8) 98 | 99 | func getGlError(glHandle uint32, checkTrueParam uint32, getObjIvFn getObjIv, 100 | getObjInfoLogFn getObjInfoLog, failMsg string) error { 101 | 102 | var success int32 103 | getObjIvFn(glHandle, checkTrueParam, &success) 104 | 105 | if success == gl.FALSE { 106 | var logLength int32 107 | getObjIvFn(glHandle, gl.INFO_LOG_LENGTH, &logLength) 108 | 109 | log := gl.Str(strings.Repeat("\x00", int(logLength))) 110 | getObjInfoLogFn(glHandle, logLength, nil, log) 111 | 112 | return fmt.Errorf("%s: %s", failMsg, gl.GoStr(log)) 113 | } 114 | 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /colors/gfx/texture.go: -------------------------------------------------------------------------------- 1 | package gfx 2 | 3 | import ( 4 | "os" 5 | "errors" 6 | "image" 7 | "image/draw" 8 | _ "image/png" 9 | _ "image/jpeg" 10 | 11 | "github.com/go-gl/gl/v4.1-core/gl" 12 | ) 13 | 14 | type Texture struct { 15 | handle uint32 16 | target uint32 // same target as gl.BindTexture(, ...) 17 | texUnit uint32 // Texture unit that is currently bound to ex: gl.TEXTURE0 18 | } 19 | 20 | var errUnsupportedStride = errors.New("unsupported stride, only 32-bit colors supported") 21 | 22 | var errTextureNotBound = errors.New("texture not bound") 23 | 24 | func NewTextureFromFile(file string, wrapR, wrapS int32) (*Texture, error) { 25 | imgFile, err := os.Open(file) 26 | if err != nil { 27 | return nil, err 28 | } 29 | defer imgFile.Close() 30 | 31 | // Decode detexts the type of image as long as its image/ is imported 32 | img, _, err := image.Decode(imgFile) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return NewTexture(img, wrapR, wrapS) 37 | } 38 | 39 | func NewTexture(img image.Image, wrapR, wrapS int32) (*Texture, error) { 40 | rgba := image.NewRGBA(img.Bounds()) 41 | draw.Draw(rgba, rgba.Bounds(), img, image.Pt(0, 0), draw.Src) 42 | if rgba.Stride != rgba.Rect.Size().X*4 { // TODO-cs: why? 43 | return nil, errUnsupportedStride 44 | } 45 | 46 | var handle uint32 47 | gl.GenTextures(1, &handle) 48 | 49 | target := uint32(gl.TEXTURE_2D) 50 | internalFmt := int32(gl.SRGB_ALPHA) 51 | format := uint32(gl.RGBA) 52 | width := int32(rgba.Rect.Size().X) 53 | height := int32(rgba.Rect.Size().Y) 54 | pixType := uint32(gl.UNSIGNED_BYTE) 55 | dataPtr := gl.Ptr(rgba.Pix) 56 | 57 | texture := Texture{ 58 | handle:handle, 59 | target:target, 60 | } 61 | 62 | texture.Bind(gl.TEXTURE0) 63 | defer texture.UnBind() 64 | 65 | // set the texture wrapping/filtering options (applies to current bound texture obj) 66 | // TODO-cs 67 | gl.TexParameteri(texture.target, gl.TEXTURE_WRAP_R, wrapR) 68 | gl.TexParameteri(texture.target, gl.TEXTURE_WRAP_S, wrapS) 69 | gl.TexParameteri(texture.target, gl.TEXTURE_MIN_FILTER, gl.LINEAR) // minification filter 70 | gl.TexParameteri(texture.target, gl.TEXTURE_MAG_FILTER, gl.LINEAR) // magnification filter 71 | 72 | 73 | gl.TexImage2D(target, 0, internalFmt, width, height, 0, format, pixType, dataPtr) 74 | 75 | gl.GenerateMipmap(texture.handle) 76 | 77 | return &texture, nil 78 | } 79 | 80 | func (tex *Texture) Bind(texUnit uint32) { 81 | gl.ActiveTexture(texUnit) 82 | gl.BindTexture(tex.target, tex.handle) 83 | tex.texUnit = texUnit 84 | } 85 | 86 | func (tex *Texture) UnBind() { 87 | tex.texUnit = 0 88 | gl.BindTexture(tex.target, 0) 89 | } 90 | 91 | func (tex *Texture) SetUniform(uniformLoc int32) error { 92 | if tex.texUnit == 0 { 93 | return errTextureNotBound 94 | } 95 | gl.Uniform1i(uniformLoc, int32(tex.texUnit - gl.TEXTURE0)) 96 | return nil 97 | } 98 | 99 | func loadImageFile(file string) (image.Image, error) { 100 | infile, err := os.Open(file) 101 | if err != nil { 102 | return nil, err 103 | } 104 | defer infile.Close() 105 | 106 | // Decode automatically figures out the type of immage in the file 107 | // as long as its image/ is imported 108 | img, _, err := image.Decode(infile) 109 | return img, err 110 | } 111 | -------------------------------------------------------------------------------- /colors/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | Adapted from this tutorial: http://www.learnopengl.com/#!Lighting/Colors 5 | 6 | Shows how the basic usage of color for 3D objects 7 | */ 8 | 9 | import ( 10 | "log" 11 | "runtime" 12 | 13 | "github.com/go-gl/gl/v4.1-core/gl" 14 | "github.com/go-gl/glfw/v3.1/glfw" 15 | "github.com/go-gl/mathgl/mgl32" 16 | 17 | "github.com/cstegel/opengl-samples-golang/colors/gfx" 18 | "github.com/cstegel/opengl-samples-golang/colors/win" 19 | "github.com/cstegel/opengl-samples-golang/colors/cam" 20 | ) 21 | 22 | // vertices to draw 6 faces of a cube 23 | var cubeVertices = []float32{ 24 | // position // texture position 25 | -0.5, -0.5, -0.5, 0.0, 0.0, 26 | 0.5, -0.5, -0.5, 1.0, 0.0, 27 | 0.5, 0.5, -0.5, 1.0, 1.0, 28 | 0.5, 0.5, -0.5, 1.0, 1.0, 29 | -0.5, 0.5, -0.5, 0.0, 1.0, 30 | -0.5, -0.5, -0.5, 0.0, 0.0, 31 | 32 | -0.5, -0.5, 0.5, 0.0, 0.0, 33 | 0.5, -0.5, 0.5, 1.0, 0.0, 34 | 0.5, 0.5, 0.5, 1.0, 1.0, 35 | 0.5, 0.5, 0.5, 1.0, 1.0, 36 | -0.5, 0.5, 0.5, 0.0, 1.0, 37 | -0.5, -0.5, 0.5, 0.0, 0.0, 38 | 39 | -0.5, 0.5, 0.5, 1.0, 0.0, 40 | -0.5, 0.5, -0.5, 1.0, 1.0, 41 | -0.5, -0.5, -0.5, 0.0, 1.0, 42 | -0.5, -0.5, -0.5, 0.0, 1.0, 43 | -0.5, -0.5, 0.5, 0.0, 0.0, 44 | -0.5, 0.5, 0.5, 1.0, 0.0, 45 | 46 | 0.5, 0.5, 0.5, 1.0, 0.0, 47 | 0.5, 0.5, -0.5, 1.0, 1.0, 48 | 0.5, -0.5, -0.5, 0.0, 1.0, 49 | 0.5, -0.5, -0.5, 0.0, 1.0, 50 | 0.5, -0.5, 0.5, 0.0, 0.0, 51 | 0.5, 0.5, 0.5, 1.0, 0.0, 52 | 53 | -0.5, -0.5, -0.5, 0.0, 1.0, 54 | 0.5, -0.5, -0.5, 1.0, 1.0, 55 | 0.5, -0.5, 0.5, 1.0, 0.0, 56 | 0.5, -0.5, 0.5, 1.0, 0.0, 57 | -0.5, -0.5, 0.5, 0.0, 0.0, 58 | -0.5, -0.5, -0.5, 0.0, 1.0, 59 | 60 | -0.5, 0.5, -0.5, 0.0, 1.0, 61 | 0.5, 0.5, -0.5, 1.0, 1.0, 62 | 0.5, 0.5, 0.5, 1.0, 0.0, 63 | 0.5, 0.5, 0.5, 1.0, 0.0, 64 | -0.5, 0.5, 0.5, 0.0, 0.0, 65 | -0.5, 0.5, -0.5, 0.0, 1.0, 66 | } 67 | 68 | var cubePositions = [][]float32 { 69 | { 0.0, 0.0, -3.0}, 70 | { 2.0, 5.0, -15.0}, 71 | {-1.5, -2.2, -2.5 }, 72 | {-3.8, -2.0, -12.3}, 73 | { 2.4, -0.4, -3.5 }, 74 | {-1.7, 3.0, -7.5 }, 75 | { 1.3, -2.0, -2.5 }, 76 | { 1.5, 2.0, -2.5 }, 77 | { 1.5, 0.2, -1.5 }, 78 | {-1.3, 1.0, -1.5 }, 79 | } 80 | 81 | func init() { 82 | // GLFW event handling must be run on the main OS thread 83 | runtime.LockOSThread() 84 | } 85 | 86 | func main() { 87 | if err := glfw.Init(); err != nil { 88 | log.Fatalln("failed to inifitialize glfw:", err) 89 | } 90 | defer glfw.Terminate() 91 | 92 | glfw.WindowHint(glfw.Resizable, glfw.False) 93 | glfw.WindowHint(glfw.ContextVersionMajor, 4) 94 | glfw.WindowHint(glfw.ContextVersionMinor, 1) 95 | glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) 96 | glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) 97 | 98 | window := win.NewWindow(1280, 720, "colors") 99 | 100 | // Initialize Glow (go function bindings) 101 | if err := gl.Init(); err != nil { 102 | panic(err) 103 | } 104 | 105 | err := programLoop(window) 106 | if err != nil { 107 | log.Fatalln(err) 108 | } 109 | } 110 | 111 | /* 112 | * Creates the Vertex Array Object for a triangle. 113 | * indices is leftover from earlier samples and not used here. 114 | */ 115 | func createVAO(vertices []float32, indices []uint32) uint32 { 116 | 117 | var VAO uint32 118 | gl.GenVertexArrays(1, &VAO) 119 | 120 | var VBO uint32 121 | gl.GenBuffers(1, &VBO) 122 | 123 | var EBO uint32; 124 | gl.GenBuffers(1, &EBO) 125 | 126 | // Bind the Vertex Array Object first, then bind and set vertex buffer(s) and attribute pointers() 127 | gl.BindVertexArray(VAO) 128 | 129 | // copy vertices data into VBO (it needs to be bound first) 130 | gl.BindBuffer(gl.ARRAY_BUFFER, VBO) 131 | gl.BufferData(gl.ARRAY_BUFFER, len(vertices)*4, gl.Ptr(vertices), gl.STATIC_DRAW) 132 | 133 | // size of one whole vertex (sum of attrib sizes) 134 | var stride int32 = 3*4 + 2*4 135 | var offset int = 0 136 | 137 | // position 138 | gl.VertexAttribPointer(0, 3, gl.FLOAT, false, stride, gl.PtrOffset(offset)) 139 | gl.EnableVertexAttribArray(0) 140 | offset += 3*4 141 | 142 | // unbind the VAO (safe practice so we don't accidentally (mis)configure it later) 143 | gl.BindVertexArray(0) 144 | 145 | return VAO 146 | } 147 | 148 | func programLoop(window *win.Window) error { 149 | 150 | // the linked shader program determines how the data will be rendered 151 | vertShader, err := gfx.NewShaderFromFile("shaders/basic.vert", gl.VERTEX_SHADER) 152 | if err != nil { 153 | return err 154 | } 155 | 156 | fragShader, err := gfx.NewShaderFromFile("shaders/basic.frag", gl.FRAGMENT_SHADER) 157 | if err != nil { 158 | return err 159 | } 160 | 161 | program, err := gfx.NewProgram(vertShader, fragShader) 162 | if err != nil { 163 | return err 164 | } 165 | defer program.Delete() 166 | 167 | lightFragShader, err := gfx.NewShaderFromFile("shaders/light.frag", gl.FRAGMENT_SHADER) 168 | if err != nil { 169 | return err 170 | } 171 | 172 | // special shader program so that lights themselves are not affected by lighting 173 | lightProgram, err := gfx.NewProgram(vertShader, lightFragShader) 174 | if err != nil { 175 | return err 176 | } 177 | 178 | VAO := createVAO(cubeVertices, nil) 179 | lightVAO := createVAO(cubeVertices, nil) 180 | 181 | // ensure that triangles that are "behind" others do not draw over top of them 182 | gl.Enable(gl.DEPTH_TEST) 183 | 184 | camera := cam.NewFpsCamera(mgl32.Vec3{0, 0, 3}, mgl32.Vec3{0, 1, 0}, -90, 0, window.InputManager()) 185 | 186 | for !window.ShouldClose() { 187 | 188 | // swaps in last buffer, polls for window events, and generally sets up for a new render frame 189 | window.StartFrame() 190 | 191 | // update camera position and direction from input evevnts 192 | camera.Update(window.SinceLastFrame()) 193 | 194 | // background color 195 | gl.ClearColor(0, 0, 0, 1.0) 196 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) // depth buffer needed for DEPTH_TEST 197 | 198 | 199 | // cube rotation matrices 200 | rotateX := (mgl32.Rotate3DX(mgl32.DegToRad(-60 * float32(glfw.GetTime())))) 201 | rotateY := (mgl32.Rotate3DY(mgl32.DegToRad(-60 * float32(glfw.GetTime())))) 202 | rotateZ := (mgl32.Rotate3DZ(mgl32.DegToRad(-60 * float32(glfw.GetTime())))) 203 | 204 | // creates perspective 205 | fov := float32(60.0) 206 | projectTransform := mgl32.Perspective(mgl32.DegToRad(fov), 207 | float32(window.Width())/float32(window.Height()), 208 | 0.1, 209 | 100.0) 210 | 211 | camTransform := camera.GetTransform() 212 | lightPos := mgl32.Vec3{1.2, 1, 2} 213 | lightTransform := mgl32.Translate3D(lightPos.X(), lightPos.Y(), lightPos.Z()).Mul4( 214 | mgl32.Scale3D(0.2, 0.2, 0.2)) 215 | 216 | program.Use() 217 | gl.UniformMatrix4fv(program.GetUniformLocation("camera"), 1, false, &camTransform[0]) 218 | gl.UniformMatrix4fv(program.GetUniformLocation("project"), 1, false, 219 | &projectTransform[0]) 220 | 221 | gl.BindVertexArray(VAO) 222 | 223 | // draw each cube after all coordinate system transforms are bound 224 | 225 | // obj is colored, light is white 226 | gl.Uniform3f(program.GetUniformLocation("objectColor"), 1.0, 0.5, 0.31) 227 | gl.Uniform3f(program.GetUniformLocation("lightColor"), 1.0, 1.0, 1.0) 228 | 229 | for _, pos := range cubePositions { 230 | worldTranslate := mgl32.Translate3D(pos[0], pos[1], pos[2]) 231 | worldTransform := (worldTranslate.Mul4(rotateX.Mul3(rotateY).Mul3(rotateZ).Mat4())) 232 | 233 | gl.UniformMatrix4fv(program.GetUniformLocation("world"), 1, false, 234 | &worldTransform[0]) 235 | 236 | gl.DrawArrays(gl.TRIANGLES, 0, 36) 237 | } 238 | gl.BindVertexArray(0) 239 | 240 | // Draw the light obj after the other boxes using its separate shader program 241 | // this means that we must re-bind any uniforms 242 | lightProgram.Use() 243 | gl.BindVertexArray(lightVAO) 244 | gl.UniformMatrix4fv(lightProgram.GetUniformLocation("world"), 1, false, &lightTransform[0]) 245 | gl.UniformMatrix4fv(lightProgram.GetUniformLocation("camera"), 1, false, &camTransform[0]) 246 | gl.UniformMatrix4fv(lightProgram.GetUniformLocation("project"), 1, false, &projectTransform[0]) 247 | gl.DrawArrays(gl.TRIANGLES, 0, 36) 248 | 249 | gl.BindVertexArray(0) 250 | 251 | // end of draw loop 252 | } 253 | 254 | return nil 255 | } 256 | -------------------------------------------------------------------------------- /colors/shaders/basic.frag: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | out vec4 color; 4 | 5 | uniform vec3 objectColor; 6 | uniform vec3 lightColor; 7 | 8 | void main() 9 | { 10 | // the color of the light "reflects" off the object 11 | color = vec4(objectColor * lightColor, 1.0f); 12 | } 13 | -------------------------------------------------------------------------------- /colors/shaders/basic.vert: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | layout (location = 0) in vec3 position; 4 | 5 | out vec2 TexCoord; 6 | 7 | uniform mat4 world; 8 | uniform mat4 camera; 9 | uniform mat4 project; 10 | 11 | void main() 12 | { 13 | gl_Position = project * camera * world * vec4(position, 1.0); 14 | } 15 | -------------------------------------------------------------------------------- /colors/shaders/light.frag: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | // special fragment shader that is not affected by lighting 4 | // useful for debugging like showing locations of lights 5 | 6 | out vec4 color; 7 | 8 | void main() 9 | { 10 | color = vec4(1.0f); // color white 11 | } 12 | -------------------------------------------------------------------------------- /colors/win/input.go: -------------------------------------------------------------------------------- 1 | package win 2 | 3 | import ( 4 | "github.com/go-gl/glfw/v3.1/glfw" 5 | "github.com/go-gl/mathgl/mgl64" 6 | ) 7 | 8 | // Action is a configurable abstraction of a key press 9 | type Action int 10 | 11 | const ( 12 | PLAYER_FORWARD Action = iota 13 | PLAYER_BACKWARD Action = iota 14 | PLAYER_LEFT Action = iota 15 | PLAYER_RIGHT Action = iota 16 | PROGRAM_QUIT Action = iota 17 | ) 18 | 19 | type InputManager struct { 20 | actionToKeyMap map[Action]glfw.Key 21 | keysPressed [glfw.KeyLast]bool 22 | 23 | firstCursorAction bool 24 | cursor mgl64.Vec2 25 | cursorChange mgl64.Vec2 26 | cursorLast mgl64.Vec2 27 | bufferedCursorChange mgl64.Vec2 28 | } 29 | 30 | func NewInputManager() *InputManager { 31 | actionToKeyMap := map[Action]glfw.Key{ 32 | PLAYER_FORWARD: glfw.KeyW, 33 | PLAYER_BACKWARD: glfw.KeyS, 34 | PLAYER_LEFT: glfw.KeyA, 35 | PLAYER_RIGHT: glfw.KeyD, 36 | PROGRAM_QUIT: glfw.KeyEscape, 37 | } 38 | 39 | return &InputManager{ 40 | actionToKeyMap: actionToKeyMap, 41 | firstCursorAction: false, 42 | } 43 | } 44 | 45 | // IsActive returns whether the given Action is currently active 46 | func (im *InputManager) IsActive(a Action) bool { 47 | return im.keysPressed[im.actionToKeyMap[a]] 48 | } 49 | 50 | // Cursor returns the value of the cursor at the last time that CheckpointCursorChange() was called. 51 | func (im *InputManager) Cursor() mgl64.Vec2 { 52 | return im.cursor 53 | } 54 | 55 | // CursorChange returns the amount of change in the underlying cursor 56 | // since the last time CheckpointCursorChange was called 57 | func (im *InputManager) CursorChange() mgl64.Vec2 { 58 | return im.cursorChange 59 | } 60 | 61 | // CheckpointCursorChange updates the publicly available Cursor() and CursorChange() 62 | // methods to return the current Cursor and change since last time this method was called. 63 | func (im *InputManager) CheckpointCursorChange() { 64 | im.cursorChange[0] = im.bufferedCursorChange[0] 65 | im.cursorChange[1] = im.bufferedCursorChange[1] 66 | im.cursor[0] = im.cursorLast[0] 67 | im.cursor[1] = im.cursorLast[1] 68 | 69 | im.bufferedCursorChange[0] = 0 70 | im.bufferedCursorChange[1] = 0 71 | } 72 | 73 | func (im *InputManager) keyCallback(window *glfw.Window, key glfw.Key, scancode int, 74 | action glfw.Action, mods glfw.ModifierKey) { 75 | 76 | // timing for key events occurs differently from what the program loop requires 77 | // so just track what key actions occur and then access them in the program loop 78 | switch action { 79 | case glfw.Press: 80 | im.keysPressed[key] = true 81 | case glfw.Release: 82 | im.keysPressed[key] = false 83 | } 84 | } 85 | 86 | func (im *InputManager) mouseCallback(window *glfw.Window, xpos, ypos float64) { 87 | 88 | if im.firstCursorAction { 89 | im.cursorLast[0] = xpos 90 | im.cursorLast[1] = ypos 91 | im.firstCursorAction = false 92 | } 93 | 94 | im.bufferedCursorChange[0] += xpos - im.cursorLast[0] 95 | im.bufferedCursorChange[1] += ypos - im.cursorLast[1] 96 | 97 | im.cursorLast[0] = xpos 98 | im.cursorLast[1] = ypos 99 | } 100 | -------------------------------------------------------------------------------- /colors/win/window.go: -------------------------------------------------------------------------------- 1 | package win 2 | 3 | import ( 4 | "log" 5 | "github.com/go-gl/glfw/v3.1/glfw" 6 | ) 7 | 8 | type Window struct { 9 | width int 10 | height int 11 | glfw *glfw.Window 12 | 13 | inputManager *InputManager 14 | firstFrame bool 15 | dTime float64 16 | lastFrameTime float64 17 | } 18 | 19 | func (w *Window) InputManager() *InputManager { 20 | return w.inputManager 21 | } 22 | 23 | func NewWindow(width, height int, title string) *Window { 24 | 25 | gWindow, err := glfw.CreateWindow(width, height, title, nil, nil) 26 | if err != nil { 27 | log.Fatalln(err) 28 | } 29 | 30 | gWindow.MakeContextCurrent() 31 | gWindow.SetInputMode(glfw.CursorMode, glfw.CursorDisabled) 32 | 33 | im := NewInputManager() 34 | 35 | gWindow.SetKeyCallback(im.keyCallback) 36 | gWindow.SetCursorPosCallback(im.mouseCallback) 37 | 38 | return &Window{ 39 | width: width, 40 | height: height, 41 | glfw: gWindow, 42 | inputManager: im, 43 | firstFrame: true, 44 | } 45 | } 46 | 47 | func (w *Window) Width() int { 48 | return w.width 49 | } 50 | 51 | func (w *Window) Height() int { 52 | return w.height 53 | } 54 | 55 | func (w *Window) ShouldClose() bool { 56 | return w.glfw.ShouldClose() 57 | } 58 | 59 | // StartFrame sets everything up to start rendering a new frame. 60 | // This includes swapping in last rendered buffer, polling for window events, 61 | // checkpointing cursor tracking, and updating the time since last frame. 62 | func (w *Window) StartFrame() { 63 | // swap in the previous rendered buffer 64 | w.glfw.SwapBuffers() 65 | 66 | // poll for UI window events 67 | glfw.PollEvents() 68 | 69 | if w.inputManager.IsActive(PROGRAM_QUIT) { 70 | w.glfw.SetShouldClose(true) 71 | } 72 | 73 | // base calculations of time since last frame (basic program loop idea) 74 | // For better advanced impl, read: http://gafferongames.com/game-physics/fix-your-timestep/ 75 | curFrameTime := glfw.GetTime() 76 | 77 | if w.firstFrame { 78 | w.lastFrameTime = curFrameTime 79 | w.firstFrame = false 80 | } 81 | 82 | w.dTime = curFrameTime - w.lastFrameTime 83 | w.lastFrameTime = curFrameTime 84 | 85 | w.inputManager.CheckpointCursorChange() 86 | } 87 | 88 | func (w *Window) SinceLastFrame() float64 { 89 | return w.dTime 90 | } 91 | -------------------------------------------------------------------------------- /hello-triangle-advanced/.gitignore: -------------------------------------------------------------------------------- 1 | hello_triangle_advanced 2 | -------------------------------------------------------------------------------- /hello-triangle-advanced/hello_triangle_advanced.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | Adapted from this tutorial: http://www.learnopengl.com/#!Getting-started/Hello-Triangle 5 | */ 6 | 7 | import ( 8 | "log" 9 | "runtime" 10 | "unsafe" 11 | 12 | "github.com/go-gl/gl/v4.1-core/gl" 13 | "github.com/go-gl/glfw/v3.1/glfw" 14 | ) 15 | 16 | const windowWidth = 800 17 | const windowHeight = 600 18 | 19 | func init() { 20 | // GLFW event handling must be run on the main OS thread 21 | runtime.LockOSThread() 22 | } 23 | 24 | func main() { 25 | if err := glfw.Init(); err != nil { 26 | log.Fatalln("failed to inifitialize glfw:", err) 27 | } 28 | defer glfw.Terminate() 29 | 30 | glfw.WindowHint(glfw.Resizable, glfw.False) 31 | glfw.WindowHint(glfw.ContextVersionMajor, 4) 32 | glfw.WindowHint(glfw.ContextVersionMinor, 1) 33 | glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) 34 | glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) 35 | window, err := glfw.CreateWindow(windowWidth, windowHeight, "Hello advanced!", nil, nil) 36 | if err != nil { 37 | panic(err) 38 | } 39 | window.MakeContextCurrent() 40 | 41 | // Initialize Glow (go function bindings) 42 | if err := gl.Init(); err != nil { 43 | panic(err) 44 | } 45 | 46 | window.SetKeyCallback(keyCallback) 47 | 48 | programLoop(window) 49 | } 50 | 51 | var vertexShaderSource = ` 52 | #version 410 core 53 | 54 | layout (location = 0) in vec3 position; 55 | 56 | void main() 57 | { 58 | gl_Position = vec4(position.x, position.y, position.z, 1.0); 59 | } 60 | ` 61 | 62 | var fragmentShaderBlueSource = ` 63 | #version 410 core 64 | 65 | out vec4 color; 66 | 67 | void main() 68 | { 69 | color = vec4(0.0f, 0.0f, 1.0f, 1.0f); 70 | } 71 | ` 72 | 73 | var fragmentShaderRedSource = ` 74 | #version 410 core 75 | 76 | out vec4 color; 77 | 78 | void main() 79 | { 80 | color = vec4(1.0f, 0.0f, 0.0f, 1.0f); 81 | } 82 | ` 83 | 84 | type getGlParam func(uint32, uint32, *int32) 85 | type getInfoLog func(uint32, int32, *int32, *uint8) 86 | 87 | func checkGlError(glObject uint32, errorParam uint32, getParamFn getGlParam, 88 | getInfoLogFn getInfoLog, failMsg string) { 89 | 90 | var success int32 91 | getParamFn(glObject, errorParam, &success) 92 | if success != 1 { 93 | var infoLog [512]byte 94 | getInfoLogFn(glObject, 512, nil, (*uint8)(unsafe.Pointer(&infoLog))) 95 | log.Fatalln(failMsg, "\n", string(infoLog[:512])) 96 | } 97 | } 98 | 99 | func checkShaderCompileErrors(shader uint32) { 100 | checkGlError(shader, gl.COMPILE_STATUS, gl.GetShaderiv, gl.GetShaderInfoLog, 101 | "ERROR::SHADER::COMPILE_FAILURE") 102 | } 103 | 104 | func checkProgramLinkErrors(program uint32) { 105 | checkGlError(program, gl.LINK_STATUS, gl.GetProgramiv, gl.GetProgramInfoLog, 106 | "ERROR::PROGRAM::LINKING_FAILURE") 107 | } 108 | 109 | func compileShader(shaderCode string, shaderType uint32) uint32 { 110 | shader := gl.CreateShader(shaderType) 111 | shaderChars, freeFn := gl.Strs(shaderCode + "\x00") 112 | defer freeFn() 113 | gl.ShaderSource(shader, 1, shaderChars, nil) 114 | gl.CompileShader(shader) 115 | checkShaderCompileErrors(shader) 116 | return shader 117 | } 118 | 119 | /* 120 | * Link the provided shaders in the order they were given and return the linked program. 121 | */ 122 | func linkShaders(shaders []uint32) uint32 { 123 | program := gl.CreateProgram() 124 | for _, shader := range shaders { 125 | gl.AttachShader(program, shader) 126 | } 127 | gl.LinkProgram(program) 128 | checkProgramLinkErrors(program) 129 | 130 | return program 131 | } 132 | 133 | /* 134 | * Creates the Vertex Array Object for a triangle. 135 | */ 136 | func createTriangleVAO(vertices []float32, indices []uint32) uint32 { 137 | 138 | var VAO uint32 139 | gl.GenVertexArrays(1, &VAO) 140 | 141 | var VBO uint32 142 | gl.GenBuffers(1, &VBO) 143 | 144 | var EBO uint32; 145 | gl.GenBuffers(1, &EBO) 146 | 147 | // Bind the Vertex Array Object first, then bind and set vertex buffer(s) and attribute pointers() 148 | gl.BindVertexArray(VAO) 149 | 150 | // copy vertices data into VBO (it needs to be bound first) 151 | gl.BindBuffer(gl.ARRAY_BUFFER, VBO) 152 | gl.BufferData(gl.ARRAY_BUFFER, len(vertices)*4, gl.Ptr(vertices), gl.STATIC_DRAW) 153 | 154 | // copy indices into element buffer 155 | gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, EBO) 156 | gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(indices)*4, gl.Ptr(indices), gl.STATIC_DRAW) 157 | 158 | // specify the format of our vertex input 159 | // (shader) input 0 160 | // vertex has size 3 161 | // vertex items are of type FLOAT 162 | // do not normalize (already done) 163 | // stride of 3 * sizeof(float) (separation of vertices) 164 | // offset of where the position data starts (0 for the beginning) 165 | gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 3*4, gl.PtrOffset(0)) 166 | gl.EnableVertexAttribArray(0) 167 | 168 | // unbind the VAO (safe practice so we don't accidentally (mis)configure it later) 169 | gl.BindVertexArray(0) 170 | 171 | return VAO 172 | } 173 | 174 | func programLoop(window *glfw.Window) { 175 | 176 | // the linked shader program determines how the data will be rendered 177 | vertexShader := compileShader(vertexShaderSource, gl.VERTEX_SHADER) 178 | fragmentShaderBlue := compileShader(fragmentShaderBlueSource, gl.FRAGMENT_SHADER) 179 | shaderProgramBlue := linkShaders([]uint32{vertexShader, fragmentShaderBlue}) 180 | fragmentShaderRed := compileShader(fragmentShaderRedSource, gl.FRAGMENT_SHADER) 181 | shaderProgramRed := linkShaders([]uint32{vertexShader, fragmentShaderRed}) 182 | 183 | // shader objects are not needed after they are linked into a program object 184 | gl.DeleteShader(vertexShader) 185 | gl.DeleteShader(fragmentShaderBlue) 186 | gl.DeleteShader(fragmentShaderRed) 187 | 188 | vertices1 := []float32{ 189 | 0.2, 0.2, 0.0, // top right 190 | 0.2, -0.8, 0.0, // bottom right 191 | -0.8, -0.8, 0.0, // bottom left 192 | -0.8, 0.2, 0.0, // top left 193 | } 194 | 195 | indices1 := []uint32{ 196 | 0, 1, 3, // first triangle 197 | 1, 2, 3, // second triangle 198 | } 199 | 200 | VAO1 := createTriangleVAO(vertices1, indices1) 201 | 202 | vertices2 := []float32{ 203 | 0.2, 0.6, 0.0, // top 204 | 0.6, -0.2, 0.0, // bottom right 205 | -0.2, -0.2, 0.0, // bottom left 206 | } 207 | 208 | indices2 := []uint32{ 209 | 0, 1, 2, // only triangle 210 | } 211 | 212 | VAO2 := createTriangleVAO(vertices2, indices2) 213 | 214 | for !window.ShouldClose() { 215 | // poll events and call their registered callbacks 216 | glfw.PollEvents() 217 | 218 | // perform rendering 219 | gl.ClearColor(0.2, 0.5, 0.5, 1.0) 220 | gl.Clear(gl.COLOR_BUFFER_BIT) 221 | 222 | // draw loop 223 | 224 | // draw rectangle 225 | gl.PolygonMode(gl.FRONT_AND_BACK, gl.LINE) 226 | gl.UseProgram(shaderProgramRed) 227 | gl.BindVertexArray(VAO1) 228 | gl.DrawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, unsafe.Pointer(nil)) 229 | gl.BindVertexArray(0) 230 | 231 | // draw triangle 232 | gl.PolygonMode(gl.FRONT_AND_BACK, gl.FILL) 233 | gl.UseProgram(shaderProgramBlue) 234 | gl.BindVertexArray(VAO2) 235 | gl.DrawElements(gl.TRIANGLES, 3, gl.UNSIGNED_INT, unsafe.Pointer(nil)) 236 | gl.BindVertexArray(0) 237 | 238 | // end of draw loop 239 | 240 | // swap in the rendered buffer 241 | window.SwapBuffers() 242 | } 243 | } 244 | 245 | func keyCallback(window *glfw.Window, key glfw.Key, scancode int, action glfw.Action, 246 | mods glfw.ModifierKey) { 247 | 248 | // When a user presses the escape key, we set the WindowShouldClose property to true, 249 | // which closes the application 250 | if key == glfw.KeyEscape && action == glfw.Press { 251 | window.SetShouldClose(true) 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /hello-triangle/.gitignore: -------------------------------------------------------------------------------- 1 | hello_triangle 2 | -------------------------------------------------------------------------------- /hello-triangle/hello_triangle.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | Adapted from this tutorial: http://www.learnopengl.com/#!Getting-started/Hello-Triangle 5 | */ 6 | 7 | import ( 8 | "log" 9 | "runtime" 10 | "unsafe" 11 | 12 | "github.com/go-gl/gl/v4.1-core/gl" 13 | "github.com/go-gl/glfw/v3.1/glfw" 14 | ) 15 | 16 | const windowWidth = 800 17 | const windowHeight = 600 18 | 19 | func init() { 20 | // GLFW event handling must be run on the main OS thread 21 | runtime.LockOSThread() 22 | } 23 | 24 | func main() { 25 | if err := glfw.Init(); err != nil { 26 | log.Fatalln("failed to inifitialize glfw:", err) 27 | } 28 | defer glfw.Terminate() 29 | 30 | glfw.WindowHint(glfw.Resizable, glfw.False) 31 | glfw.WindowHint(glfw.ContextVersionMajor, 4) 32 | glfw.WindowHint(glfw.ContextVersionMinor, 1) 33 | glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) 34 | glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) 35 | window, err := glfw.CreateWindow(windowWidth, windowHeight, "Hello!", nil, nil) 36 | if err != nil { 37 | panic(err) 38 | } 39 | window.MakeContextCurrent() 40 | 41 | // Initialize Glow (go function bindings) 42 | if err := gl.Init(); err != nil { 43 | panic(err) 44 | } 45 | 46 | window.SetKeyCallback(keyCallback) 47 | 48 | programLoop(window) 49 | } 50 | 51 | var vertexShaderSource = ` 52 | #version 410 core 53 | 54 | layout (location = 0) in vec3 position; 55 | 56 | void main() 57 | { 58 | gl_Position = vec4(position.x, position.y, position.z, 1.0); 59 | } 60 | ` 61 | 62 | var fragmentShaderSource = ` 63 | #version 410 core 64 | 65 | out vec4 color; 66 | 67 | void main() 68 | { 69 | color = vec4(1.0f, 0.5f, 0.2f, 1.0f); 70 | } 71 | ` 72 | 73 | type getGlParam func(uint32, uint32, *int32) 74 | type getInfoLog func(uint32, int32, *int32, *uint8) 75 | 76 | func checkGlError(glObject uint32, errorParam uint32, getParamFn getGlParam, 77 | getInfoLogFn getInfoLog, failMsg string) { 78 | 79 | var success int32 80 | getParamFn(glObject, errorParam, &success) 81 | if success != 1 { 82 | var infoLog [512]byte 83 | getInfoLogFn(glObject, 512, nil, (*uint8)(unsafe.Pointer(&infoLog))) 84 | log.Fatalln(failMsg, "\n", string(infoLog[:512])) 85 | } 86 | } 87 | 88 | func checkShaderCompileErrors(shader uint32) { 89 | checkGlError(shader, gl.COMPILE_STATUS, gl.GetShaderiv, gl.GetShaderInfoLog, 90 | "ERROR::SHADER::COMPILE_FAILURE") 91 | } 92 | 93 | func checkProgramLinkErrors(program uint32) { 94 | checkGlError(program, gl.LINK_STATUS, gl.GetProgramiv, gl.GetProgramInfoLog, 95 | "ERROR::PROGRAM::LINKING_FAILURE") 96 | } 97 | 98 | func compileShaders() []uint32 { 99 | // create the vertex shader 100 | vertexShader := gl.CreateShader(gl.VERTEX_SHADER) 101 | shaderSourceChars, freeVertexShaderFunc := gl.Strs(vertexShaderSource) 102 | gl.ShaderSource(vertexShader, 1, shaderSourceChars, nil) 103 | gl.CompileShader(vertexShader) 104 | checkShaderCompileErrors(vertexShader) 105 | 106 | // create the fragment shader 107 | fragmentShader := gl.CreateShader(gl.FRAGMENT_SHADER) 108 | shaderSourceChars, freeFragmentShaderFunc := gl.Strs(fragmentShaderSource) 109 | gl.ShaderSource(fragmentShader, 1, shaderSourceChars, nil) 110 | gl.CompileShader(fragmentShader) 111 | checkShaderCompileErrors(fragmentShader) 112 | 113 | defer freeFragmentShaderFunc() 114 | defer freeVertexShaderFunc() 115 | 116 | return []uint32{vertexShader, fragmentShader} 117 | } 118 | 119 | /* 120 | * Link the provided shaders in the order they were given and return the linked program. 121 | */ 122 | func linkShaders(shaders []uint32) uint32 { 123 | program := gl.CreateProgram() 124 | for _, shader := range shaders { 125 | gl.AttachShader(program, shader) 126 | } 127 | gl.LinkProgram(program) 128 | checkProgramLinkErrors(program) 129 | 130 | // shader objects are not needed after they are linked into a program object 131 | for _, shader := range shaders { 132 | gl.DeleteShader(shader) 133 | } 134 | 135 | return program 136 | } 137 | 138 | /* 139 | * Creates the Vertex Array Object for a triangle. 140 | */ 141 | func createTriangleVAO() uint32 { 142 | vertices := []float32{ 143 | -0.5, -0.5, 0.0, 144 | 0.5, -0.5, 0.0, 145 | 0.0, 0.5, 0.0, 146 | } 147 | 148 | var VAO uint32 149 | gl.GenVertexArrays(1, &VAO) 150 | 151 | var VBO uint32 152 | gl.GenBuffers(1, &VBO) 153 | 154 | // Bind the Vertex Array Object first, then bind and set vertex buffer(s) and attribute pointers() 155 | gl.BindVertexArray(VAO) 156 | 157 | // copy vertices data into VBO (it needs to be bound first) 158 | gl.BindBuffer(gl.ARRAY_BUFFER, VBO) 159 | gl.BufferData(gl.ARRAY_BUFFER, len(vertices)*4, gl.Ptr(vertices), gl.STATIC_DRAW) 160 | 161 | // specify the format of our vertex input 162 | // (shader) input 0 163 | // vertex has size 3 164 | // vertex items are of type FLOAT 165 | // do not normalize (already done) 166 | // stride of 3 * sizeof(float) (separation of vertices) 167 | // offset of where the position data starts (0 for the beginning) 168 | gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 3*4, gl.PtrOffset(0)) 169 | gl.EnableVertexAttribArray(0) 170 | 171 | // unbind the VAO (safe practice so we don't accidentally (mis)configure it later) 172 | gl.BindVertexArray(0) 173 | 174 | return VAO 175 | } 176 | 177 | func programLoop(window *glfw.Window) { 178 | 179 | // the linked shader program determines how the data will be rendered 180 | shaders := compileShaders() 181 | shaderProgram := linkShaders(shaders) 182 | 183 | // VAO contains all the information about the data to be rendered 184 | VAO := createTriangleVAO() 185 | 186 | for !window.ShouldClose() { 187 | // poll events and call their registered callbacks 188 | glfw.PollEvents() 189 | 190 | // perform rendering 191 | gl.ClearColor(0.2, 0.5, 0.5, 1.0) 192 | gl.Clear(gl.COLOR_BUFFER_BIT) 193 | 194 | // draw loop 195 | gl.UseProgram(shaderProgram) // ensure the right shader program is being used 196 | gl.BindVertexArray(VAO) // bind data 197 | gl.DrawArrays(gl.TRIANGLES, 0, 3) // perform draw call 198 | gl.BindVertexArray(0) // unbind data (so we don't mistakenly use/modify it) 199 | // end of draw loop 200 | 201 | // swap in the rendered buffer 202 | window.SwapBuffers() 203 | } 204 | } 205 | 206 | func keyCallback(window *glfw.Window, key glfw.Key, scancode int, action glfw.Action, 207 | mods glfw.ModifierKey) { 208 | 209 | // When a user presses the escape key, we set the WindowShouldClose property to true, 210 | // which closes the application 211 | if key == glfw.KeyEscape && action == glfw.Press { 212 | window.SetShouldClose(true) 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /hello-window/.gitignore: -------------------------------------------------------------------------------- 1 | hello_window 2 | -------------------------------------------------------------------------------- /hello-window/hello_window.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | Adapted from this tutorial: http://www.learnopengl.com/#!Getting-started/Hello-Window 5 | */ 6 | 7 | import( 8 | "runtime" 9 | "log" 10 | 11 | "github.com/go-gl/gl/v4.1-core/gl" 12 | "github.com/go-gl/glfw/v3.1/glfw" 13 | ) 14 | 15 | const windowWidth = 800 16 | const windowHeight = 600 17 | 18 | func init() { 19 | // GLFW event handling must be run on the main OS thread 20 | runtime.LockOSThread() 21 | } 22 | 23 | func main() { 24 | if err := glfw.Init(); err != nil { 25 | log.Fatalln("failed to inifitialize glfw:", err) 26 | } 27 | defer glfw.Terminate() 28 | 29 | glfw.WindowHint(glfw.Resizable, glfw.False) 30 | glfw.WindowHint(glfw.ContextVersionMajor, 4) 31 | glfw.WindowHint(glfw.ContextVersionMinor, 1) 32 | glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) 33 | glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) 34 | window, err := glfw.CreateWindow(windowWidth, windowHeight, "Hello!", nil, nil) 35 | if err != nil { 36 | panic(err) 37 | } 38 | window.MakeContextCurrent() 39 | 40 | // Initialize Glow (go function bindings) 41 | if err := gl.Init(); err != nil { 42 | panic(err) 43 | } 44 | 45 | window.SetKeyCallback(keyCallback) 46 | 47 | // program loop 48 | for !window.ShouldClose() { 49 | // poll events and call their registered callbacks 50 | glfw.PollEvents() 51 | 52 | // perform rendering 53 | gl.ClearColor(0.2, 0.5, 0.5, 1.0) 54 | gl.Clear(gl.COLOR_BUFFER_BIT) 55 | 56 | // swap in the rendered buffer 57 | window.SwapBuffers() 58 | } 59 | } 60 | 61 | func keyCallback(window *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { 62 | // When a user presses the escape key, we set the WindowShouldClose property to true, 63 | // which closes the application 64 | if key == glfw.KeyEscape && action == glfw.Press { 65 | window.SetShouldClose(true) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /images/RTS_Crate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstegel/opengl-samples-golang/1ed56cc6485acc36ebd30234dd7f405d2080b844/images/RTS_Crate.png -------------------------------------------------------------------------------- /images/container2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstegel/opengl-samples-golang/1ed56cc6485acc36ebd30234dd7f405d2080b844/images/container2.png -------------------------------------------------------------------------------- /images/trollface-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstegel/opengl-samples-golang/1ed56cc6485acc36ebd30234dd7f405d2080b844/images/trollface-transparent.png -------------------------------------------------------------------------------- /images/trollface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstegel/opengl-samples-golang/1ed56cc6485acc36ebd30234dd7f405d2080b844/images/trollface.png -------------------------------------------------------------------------------- /light-maps/.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | -------------------------------------------------------------------------------- /light-maps/cam/camera.go: -------------------------------------------------------------------------------- 1 | package cam 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/go-gl/mathgl/mgl32" 7 | "github.com/go-gl/mathgl/mgl64" 8 | 9 | "github.com/cstegel/opengl-samples-golang/light-maps/win" 10 | ) 11 | 12 | type FpsCamera struct { 13 | // Camera options 14 | moveSpeed float64 15 | cursorSensitivity float64 16 | 17 | // Eular Angles 18 | pitch float64 19 | yaw float64 20 | 21 | // Camera attributes 22 | pos mgl32.Vec3 23 | front mgl32.Vec3 24 | up mgl32.Vec3 25 | right mgl32.Vec3 26 | worldUp mgl32.Vec3 27 | 28 | inputManager *win.InputManager 29 | } 30 | 31 | func NewFpsCamera(position, worldUp mgl32.Vec3, yaw, pitch float64, im *win.InputManager) (*FpsCamera) { 32 | cam := FpsCamera { 33 | moveSpeed: 5.00, 34 | cursorSensitivity: 0.05, 35 | pitch: pitch, 36 | yaw: yaw, 37 | pos: position, 38 | up: mgl32.Vec3{0, 1, 0}, 39 | worldUp: worldUp, 40 | inputManager: im, 41 | } 42 | 43 | return &cam 44 | } 45 | 46 | func (c *FpsCamera) Update(dTime float64) { 47 | c.updatePosition(dTime) 48 | c.updateDirection() 49 | } 50 | 51 | // UpdatePosition updates this camera's position by giving directions that 52 | // the camera is to travel in and for how long 53 | func (c *FpsCamera) updatePosition(dTime float64) { 54 | adjustedSpeed := float32(dTime * c.moveSpeed) 55 | 56 | if c.inputManager.IsActive(win.PLAYER_FORWARD) { 57 | c.pos = c.pos.Add(c.front.Mul(adjustedSpeed)) 58 | } 59 | if c.inputManager.IsActive(win.PLAYER_BACKWARD) { 60 | c.pos = c.pos.Sub(c.front.Mul(adjustedSpeed)) 61 | } 62 | if c.inputManager.IsActive(win.PLAYER_LEFT) { 63 | c.pos = c.pos.Sub(c.front.Cross(c.up).Normalize().Mul(adjustedSpeed)) 64 | } 65 | if c.inputManager.IsActive(win.PLAYER_RIGHT) { 66 | c.pos = c.pos.Add(c.front.Cross(c.up).Normalize().Mul(adjustedSpeed)) 67 | } 68 | } 69 | 70 | // UpdateCursor updates the direction of the camera by giving it delta x/y values 71 | // that came from a cursor input device 72 | func (c *FpsCamera) updateDirection() { 73 | dCursor := c.inputManager.CursorChange() 74 | 75 | dx := -c.cursorSensitivity * dCursor[0] 76 | dy := c.cursorSensitivity * dCursor[1] 77 | 78 | c.pitch += dy 79 | if c.pitch > 89.0 { 80 | c.pitch = 89.0 81 | } else if c.pitch < -89.0 { 82 | c.pitch = -89.0 83 | } 84 | 85 | c.yaw = math.Mod(c.yaw + dx, 360) 86 | c.updateVectors() 87 | } 88 | 89 | func (c *FpsCamera) updateVectors() { 90 | // x, y, z 91 | c.front[0] = float32(math.Cos(mgl64.DegToRad(c.pitch)) * math.Cos(mgl64.DegToRad(c.yaw))) 92 | c.front[1] = float32(math.Sin(mgl64.DegToRad(c.pitch))) 93 | c.front[2] = float32(math.Cos(mgl64.DegToRad(c.pitch)) * math.Sin(mgl64.DegToRad(c.yaw))) 94 | c.front = c.front.Normalize() 95 | 96 | // Gram-Schmidt process to figure out right and up vectors 97 | c.right = c.worldUp.Cross(c.front).Normalize() 98 | c.up = c.right.Cross(c.front).Normalize() 99 | } 100 | 101 | // GetCameraTransform gets the matrix to transform from world coordinates to 102 | // this camera's coordinates. 103 | func (camera *FpsCamera) GetTransform() mgl32.Mat4 { 104 | cameraTarget := camera.pos.Add(camera.front) 105 | 106 | return mgl32.LookAt( 107 | camera.pos.X(), camera.pos.Y(), camera.pos.Z(), 108 | cameraTarget.X(), cameraTarget.Y(), cameraTarget.Z(), 109 | camera.up.X(), camera.up.Y(), camera.up.Z(), 110 | ) 111 | } 112 | -------------------------------------------------------------------------------- /light-maps/gfx/shader.go: -------------------------------------------------------------------------------- 1 | package gfx 2 | 3 | import ( 4 | "io/ioutil" 5 | "strings" 6 | "fmt" 7 | 8 | "github.com/go-gl/gl/v4.1-core/gl" 9 | ) 10 | 11 | type Shader struct { 12 | handle uint32 13 | } 14 | 15 | type Program struct { 16 | handle uint32 17 | shaders []*Shader 18 | } 19 | 20 | func (shader *Shader) Delete() { 21 | gl.DeleteShader(shader.handle) 22 | } 23 | 24 | func (prog *Program) Delete() { 25 | for _, shader := range prog.shaders { 26 | shader.Delete() 27 | } 28 | gl.DeleteProgram(prog.handle) 29 | } 30 | 31 | func (prog *Program) Attach(shaders ...*Shader) { 32 | for _, shader := range shaders { 33 | gl.AttachShader(prog.handle, shader.handle) 34 | prog.shaders = append(prog.shaders, shader) 35 | } 36 | } 37 | 38 | func (prog *Program) Use() { 39 | gl.UseProgram(prog.handle) 40 | } 41 | 42 | func (prog *Program) Link() error { 43 | gl.LinkProgram(prog.handle) 44 | return getGlError(prog.handle, gl.LINK_STATUS, gl.GetProgramiv, gl.GetProgramInfoLog, 45 | "PROGRAM::LINKING_FAILURE") 46 | } 47 | 48 | func (prog *Program) GetUniformLocation(name string) int32 { 49 | return gl.GetUniformLocation(prog.handle, gl.Str(name + "\x00")) 50 | } 51 | 52 | func NewProgram(shaders ...*Shader) (*Program, error) { 53 | prog := &Program{handle:gl.CreateProgram()} 54 | prog.Attach(shaders...) 55 | 56 | if err := prog.Link(); err != nil { 57 | return nil, err 58 | } 59 | 60 | return prog, nil 61 | } 62 | 63 | func NewShader(src string, sType uint32) (*Shader, error) { 64 | 65 | handle := gl.CreateShader(sType) 66 | glSrc, freeFn := gl.Strs(src + "\x00") 67 | defer freeFn() 68 | gl.ShaderSource(handle, 1, glSrc, nil) 69 | gl.CompileShader(handle) 70 | err := getGlError(handle, gl.COMPILE_STATUS, gl.GetShaderiv, gl.GetShaderInfoLog, 71 | "SHADER::COMPILE_FAILURE::") 72 | if err != nil { 73 | return nil, err 74 | } 75 | return &Shader{handle:handle}, nil 76 | } 77 | 78 | func NewShaderFromFile(file string, sType uint32) (*Shader, error) { 79 | src, err := ioutil.ReadFile(file) 80 | if err != nil { 81 | return nil, err 82 | } 83 | handle := gl.CreateShader(sType) 84 | glSrc, freeFn := gl.Strs(string(src) + "\x00") 85 | defer freeFn() 86 | gl.ShaderSource(handle, 1, glSrc, nil) 87 | gl.CompileShader(handle) 88 | err = getGlError(handle, gl.COMPILE_STATUS, gl.GetShaderiv, gl.GetShaderInfoLog, 89 | "SHADER::COMPILE_FAILURE::" + file) 90 | if err != nil { 91 | return nil, err 92 | } 93 | return &Shader{handle:handle}, nil 94 | } 95 | 96 | type getObjIv func(uint32, uint32, *int32) 97 | type getObjInfoLog func(uint32, int32, *int32, *uint8) 98 | 99 | func getGlError(glHandle uint32, checkTrueParam uint32, getObjIvFn getObjIv, 100 | getObjInfoLogFn getObjInfoLog, failMsg string) error { 101 | 102 | var success int32 103 | getObjIvFn(glHandle, checkTrueParam, &success) 104 | 105 | if success == gl.FALSE { 106 | var logLength int32 107 | getObjIvFn(glHandle, gl.INFO_LOG_LENGTH, &logLength) 108 | 109 | log := gl.Str(strings.Repeat("\x00", int(logLength))) 110 | getObjInfoLogFn(glHandle, logLength, nil, log) 111 | 112 | return fmt.Errorf("%s: %s", failMsg, gl.GoStr(log)) 113 | } 114 | 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /light-maps/gfx/texture.go: -------------------------------------------------------------------------------- 1 | package gfx 2 | 3 | import ( 4 | "os" 5 | "errors" 6 | "image" 7 | "image/draw" 8 | _ "image/png" 9 | _ "image/jpeg" 10 | 11 | "github.com/go-gl/gl/v4.1-core/gl" 12 | ) 13 | 14 | type Texture struct { 15 | handle uint32 16 | target uint32 // same target as gl.BindTexture(, ...) 17 | texUnit uint32 // Texture unit that is currently bound to ex: gl.TEXTURE0 18 | } 19 | 20 | var errUnsupportedStride = errors.New("unsupported stride, only 32-bit colors supported") 21 | 22 | var errTextureNotBound = errors.New("texture not bound") 23 | 24 | func NewTextureFromFile(file string, wrapR, wrapS int32) (*Texture, error) { 25 | imgFile, err := os.Open(file) 26 | if err != nil { 27 | return nil, err 28 | } 29 | defer imgFile.Close() 30 | 31 | // Decode detexts the type of image as long as its image/ is imported 32 | img, _, err := image.Decode(imgFile) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return NewTexture(img, wrapR, wrapS) 37 | } 38 | 39 | func NewTexture(img image.Image, wrapR, wrapS int32) (*Texture, error) { 40 | rgba := image.NewRGBA(img.Bounds()) 41 | draw.Draw(rgba, rgba.Bounds(), img, image.Pt(0, 0), draw.Src) 42 | if rgba.Stride != rgba.Rect.Size().X*4 { // TODO-cs: why? 43 | return nil, errUnsupportedStride 44 | } 45 | 46 | var handle uint32 47 | gl.GenTextures(1, &handle) 48 | 49 | target := uint32(gl.TEXTURE_2D) 50 | internalFmt := int32(gl.SRGB_ALPHA) 51 | format := uint32(gl.RGBA) 52 | width := int32(rgba.Rect.Size().X) 53 | height := int32(rgba.Rect.Size().Y) 54 | pixType := uint32(gl.UNSIGNED_BYTE) 55 | dataPtr := gl.Ptr(rgba.Pix) 56 | 57 | texture := Texture{ 58 | handle:handle, 59 | target:target, 60 | } 61 | 62 | texture.Bind(gl.TEXTURE0) 63 | defer texture.UnBind() 64 | 65 | // set the texture wrapping/filtering options (applies to current bound texture obj) 66 | // TODO-cs 67 | gl.TexParameteri(texture.target, gl.TEXTURE_WRAP_R, wrapR) 68 | gl.TexParameteri(texture.target, gl.TEXTURE_WRAP_S, wrapS) 69 | gl.TexParameteri(texture.target, gl.TEXTURE_MIN_FILTER, gl.LINEAR) // minification filter 70 | gl.TexParameteri(texture.target, gl.TEXTURE_MAG_FILTER, gl.LINEAR) // magnification filter 71 | 72 | 73 | gl.TexImage2D(target, 0, internalFmt, width, height, 0, format, pixType, dataPtr) 74 | 75 | gl.GenerateMipmap(texture.handle) 76 | 77 | return &texture, nil 78 | } 79 | 80 | func (tex *Texture) Bind(texUnit uint32) { 81 | gl.ActiveTexture(texUnit) 82 | gl.BindTexture(tex.target, tex.handle) 83 | tex.texUnit = texUnit 84 | } 85 | 86 | func (tex *Texture) UnBind() { 87 | tex.texUnit = 0 88 | gl.BindTexture(tex.target, 0) 89 | } 90 | 91 | func (tex *Texture) SetUniform(uniformLoc int32) error { 92 | if tex.texUnit == 0 { 93 | return errTextureNotBound 94 | } 95 | gl.Uniform1i(uniformLoc, int32(tex.texUnit - gl.TEXTURE0)) 96 | return nil 97 | } 98 | 99 | func loadImageFile(file string) (image.Image, error) { 100 | infile, err := os.Open(file) 101 | if err != nil { 102 | return nil, err 103 | } 104 | defer infile.Close() 105 | 106 | // Decode automatically figures out the type of immage in the file 107 | // as long as its image/ is imported 108 | img, _, err := image.Decode(infile) 109 | return img, err 110 | } 111 | -------------------------------------------------------------------------------- /light-maps/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | http://www.learnopengl.com/#!Lighting/Materials 5 | 6 | Shows basic materials with phong lighting 7 | */ 8 | 9 | import ( 10 | "log" 11 | "runtime" 12 | "math" 13 | 14 | "github.com/go-gl/gl/v4.1-core/gl" 15 | "github.com/go-gl/glfw/v3.1/glfw" 16 | "github.com/go-gl/mathgl/mgl32" 17 | 18 | "github.com/cstegel/opengl-samples-golang/light-maps/gfx" 19 | "github.com/cstegel/opengl-samples-golang/light-maps/win" 20 | "github.com/cstegel/opengl-samples-golang/light-maps/cam" 21 | ) 22 | 23 | // vertices to draw 6 faces of a cube 24 | var cubeVertices = []float32{ 25 | // position // normal vector 26 | -0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 27 | 0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 28 | 0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 29 | 0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 30 | -0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 31 | -0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 32 | 33 | -0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 34 | 0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 35 | 0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 36 | 0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 37 | -0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 38 | -0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 39 | 40 | -0.5, 0.5, 0.5, -1.0, 0.0, 0.0, 41 | -0.5, 0.5, -0.5, -1.0, 0.0, 0.0, 42 | -0.5, -0.5, -0.5, -1.0, 0.0, 0.0, 43 | -0.5, -0.5, -0.5, -1.0, 0.0, 0.0, 44 | -0.5, -0.5, 0.5, -1.0, 0.0, 0.0, 45 | -0.5, 0.5, 0.5, -1.0, 0.0, 0.0, 46 | 47 | 0.5, 0.5, 0.5, 1.0, 0.0, 0.0, 48 | 0.5, 0.5, -0.5, 1.0, 0.0, 0.0, 49 | 0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 50 | 0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 51 | 0.5, -0.5, 0.5, 1.0, 0.0, 0.0, 52 | 0.5, 0.5, 0.5, 1.0, 0.0, 0.0, 53 | 54 | -0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 55 | 0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 56 | 0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 57 | 0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 58 | -0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 59 | -0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 60 | 61 | -0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 62 | 0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 63 | 0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 64 | 0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 65 | -0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 66 | -0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 67 | } 68 | 69 | var cubePositions = [][]float32 { 70 | { 0.0, 0.0, -3.0}, 71 | { 2.0, 5.0, -15.0}, 72 | {-1.5, -2.2, -2.5 }, 73 | {-3.8, -2.0, -12.3}, 74 | { 2.4, -0.4, -3.5 }, 75 | {-1.7, 3.0, -7.5 }, 76 | { 1.3, -2.0, -2.5 }, 77 | { 1.5, 2.0, -2.5 }, 78 | { 1.5, 0.2, -1.5 }, 79 | {-1.3, 1.0, -1.5 }, 80 | } 81 | 82 | func init() { 83 | // GLFW event handling must be run on the main OS thread 84 | runtime.LockOSThread() 85 | } 86 | 87 | func main() { 88 | if err := glfw.Init(); err != nil { 89 | log.Fatalln("failed to inifitialize glfw:", err) 90 | } 91 | defer glfw.Terminate() 92 | 93 | glfw.WindowHint(glfw.Resizable, glfw.False) 94 | glfw.WindowHint(glfw.ContextVersionMajor, 4) 95 | glfw.WindowHint(glfw.ContextVersionMinor, 1) 96 | glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) 97 | glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) 98 | 99 | window := win.NewWindow(1280, 720, "Lighting maps") 100 | 101 | // Initialize Glow (go function bindings) 102 | if err := gl.Init(); err != nil { 103 | panic(err) 104 | } 105 | 106 | err := programLoop(window) 107 | if err != nil { 108 | log.Fatalln(err) 109 | } 110 | } 111 | 112 | /* 113 | * Creates the Vertex Array Object for a triangle. 114 | * indices is leftover from earlier samples and not used here. 115 | */ 116 | func createVAO(vertices []float32, indices []uint32) uint32 { 117 | 118 | var VAO uint32 119 | gl.GenVertexArrays(1, &VAO) 120 | 121 | var VBO uint32 122 | gl.GenBuffers(1, &VBO) 123 | 124 | var EBO uint32; 125 | gl.GenBuffers(1, &EBO) 126 | 127 | // Bind the Vertex Array Object first, then bind and set vertex buffer(s) and attribute pointers() 128 | gl.BindVertexArray(VAO) 129 | 130 | // copy vertices data into VBO (it needs to be bound first) 131 | gl.BindBuffer(gl.ARRAY_BUFFER, VBO) 132 | gl.BufferData(gl.ARRAY_BUFFER, len(vertices)*4, gl.Ptr(vertices), gl.STATIC_DRAW) 133 | 134 | // size of one whole vertex (sum of attrib sizes) 135 | var stride int32 = 3*4 + 3*4 136 | var offset int = 0 137 | 138 | // position 139 | gl.VertexAttribPointer(0, 3, gl.FLOAT, false, stride, gl.PtrOffset(offset)) 140 | gl.EnableVertexAttribArray(0) 141 | offset += 3*4 142 | 143 | // normal 144 | gl.VertexAttribPointer(1, 3, gl.FLOAT, false, stride, gl.PtrOffset(offset)) 145 | gl.EnableVertexAttribArray(1) 146 | offset += 3*4 147 | 148 | // unbind the VAO (safe practice so we don't accidentally (mis)configure it later) 149 | gl.BindVertexArray(0) 150 | 151 | return VAO 152 | } 153 | 154 | func programLoop(window *win.Window) error { 155 | 156 | // the linked shader program determines how the data will be rendered 157 | vertShader, err := gfx.NewShaderFromFile("shaders/phong.vert", gl.VERTEX_SHADER) 158 | if err != nil { 159 | return err 160 | } 161 | 162 | fragShader, err := gfx.NewShaderFromFile("shaders/phong.frag", gl.FRAGMENT_SHADER) 163 | if err != nil { 164 | return err 165 | } 166 | 167 | program, err := gfx.NewProgram(vertShader, fragShader) 168 | if err != nil { 169 | return err 170 | } 171 | defer program.Delete() 172 | 173 | lightFragShader, err := gfx.NewShaderFromFile("shaders/light.frag", gl.FRAGMENT_SHADER) 174 | if err != nil { 175 | return err 176 | } 177 | 178 | // special shader program so that lights themselves are not affected by lighting 179 | lightProgram, err := gfx.NewProgram(vertShader, lightFragShader) 180 | if err != nil { 181 | return err 182 | } 183 | 184 | VAO := createVAO(cubeVertices, nil) 185 | lightVAO := createVAO(cubeVertices, nil) 186 | 187 | // ensure that triangles that are "behind" others do not draw over top of them 188 | gl.Enable(gl.DEPTH_TEST) 189 | 190 | camera := cam.NewFpsCamera(mgl32.Vec3{0, 0, 3}, mgl32.Vec3{0, 1, 0}, -90, 0, window.InputManager()) 191 | 192 | for !window.ShouldClose() { 193 | 194 | // swaps in last buffer, polls for window events, and generally sets up for a new render frame 195 | window.StartFrame() 196 | 197 | // update camera position and direction from input evevnts 198 | camera.Update(window.SinceLastFrame()) 199 | 200 | // background color 201 | gl.ClearColor(0, 0, 0, 1.0) 202 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) // depth buffer needed for DEPTH_TEST 203 | 204 | 205 | // cube rotation matrices 206 | rotateX := (mgl32.Rotate3DX(mgl32.DegToRad(-45 * float32(glfw.GetTime())))) 207 | rotateY := (mgl32.Rotate3DY(mgl32.DegToRad(-45 * float32(glfw.GetTime())))) 208 | rotateZ := (mgl32.Rotate3DZ(mgl32.DegToRad(-45 * float32(glfw.GetTime())))) 209 | 210 | // creates perspective 211 | fov := float32(60.0) 212 | projectTransform := mgl32.Perspective(mgl32.DegToRad(fov), 213 | float32(window.Width())/float32(window.Height()), 214 | 0.1, 215 | 100.0) 216 | 217 | camTransform := camera.GetTransform() 218 | lightPos := mgl32.Vec3{0.6, 1, 0.1} 219 | lightTransform := mgl32.Translate3D(lightPos.X(), lightPos.Y(), lightPos.Z()).Mul4( 220 | mgl32.Scale3D(0.2, 0.2, 0.2)) 221 | 222 | program.Use() 223 | gl.UniformMatrix4fv(program.GetUniformLocation("view"), 1, false, &camTransform[0]) 224 | gl.UniformMatrix4fv(program.GetUniformLocation("project"), 1, false, 225 | &projectTransform[0]) 226 | 227 | gl.BindVertexArray(VAO) 228 | 229 | // draw each cube after all coordinate system transforms are bound 230 | 231 | // obj is colored, light is white 232 | gl.Uniform3f(program.GetUniformLocation("material.ambient"), 1.0, 0.5, 0.31) 233 | gl.Uniform3f(program.GetUniformLocation("material.diffuse"), 1.0, 0.5, 0.31) 234 | gl.Uniform3f(program.GetUniformLocation("material.specular"), 0.5, 0.5, 0.5) 235 | gl.Uniform1f(program.GetUniformLocation("material.shininess"), 32.0) 236 | 237 | lightColor := mgl32.Vec3{ 238 | float32(math.Sin(glfw.GetTime() * 1)), 239 | float32(math.Sin(glfw.GetTime() * 0.35)), 240 | float32(math.Sin(glfw.GetTime() * 0.65)), 241 | } 242 | 243 | diffuseColor := mgl32.Vec3{ 244 | 0.5 * lightColor[0], 245 | 0.5 * lightColor[1], 246 | 0.5 * lightColor[2], 247 | } 248 | ambientColor := mgl32.Vec3{ 249 | 0.2 * lightColor[0], 250 | 0.2 * lightColor[1], 251 | 0.2 * lightColor[2], 252 | } 253 | 254 | gl.Uniform3f(program.GetUniformLocation("light.ambient"), 255 | ambientColor[0], ambientColor[1], ambientColor[2]) 256 | gl.Uniform3f(program.GetUniformLocation("light.diffuse"), 257 | diffuseColor[0], diffuseColor[1], diffuseColor[2]) 258 | gl.Uniform3f(program.GetUniformLocation("light.specular"), 1.0, 1.0, 1.0) 259 | gl.Uniform3f(program.GetUniformLocation("light.position"), lightPos.X(), lightPos.Y(), lightPos.Z()) 260 | 261 | for _, pos := range cubePositions { 262 | 263 | // turn the cubes into rectangular prisms for more fun 264 | worldTranslate := mgl32.Translate3D(pos[0], pos[1], pos[2]) 265 | worldTransform := worldTranslate.Mul4( 266 | rotateX.Mul3(rotateY).Mul3(rotateZ).Mat4(), 267 | ) 268 | 269 | gl.UniformMatrix4fv(program.GetUniformLocation("model"), 1, false, 270 | &worldTransform[0]) 271 | 272 | gl.DrawArrays(gl.TRIANGLES, 0, 36) 273 | } 274 | gl.BindVertexArray(0) 275 | 276 | // Draw the light obj after the other boxes using its separate shader program 277 | // this means that we must re-bind any uniforms 278 | lightProgram.Use() 279 | gl.BindVertexArray(lightVAO) 280 | gl.UniformMatrix4fv(lightProgram.GetUniformLocation("model"), 1, false, &lightTransform[0]) 281 | gl.UniformMatrix4fv(lightProgram.GetUniformLocation("view"), 1, false, &camTransform[0]) 282 | gl.UniformMatrix4fv(lightProgram.GetUniformLocation("project"), 1, false, &projectTransform[0]) 283 | gl.DrawArrays(gl.TRIANGLES, 0, 36) 284 | 285 | gl.BindVertexArray(0) 286 | 287 | // end of draw loop 288 | } 289 | 290 | return nil 291 | } 292 | -------------------------------------------------------------------------------- /light-maps/shaders/light.frag: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | // special fragment shader that is not affected by lighting 4 | // useful for debugging like showing locations of lights 5 | 6 | out vec4 color; 7 | 8 | void main() 9 | { 10 | color = vec4(1.0f); // color white 11 | } 12 | -------------------------------------------------------------------------------- /light-maps/shaders/phong.frag: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | struct Material { 4 | vec3 ambient; 5 | vec3 diffuse; 6 | vec3 specular; 7 | float shininess; 8 | }; 9 | 10 | struct Light { 11 | vec3 position; 12 | 13 | vec3 ambient; 14 | vec3 diffuse; 15 | vec3 specular; 16 | }; 17 | 18 | in vec3 Normal; 19 | in vec3 FragPos; 20 | in vec3 LightPos; 21 | out vec4 color; 22 | 23 | uniform Material material; 24 | uniform Light light; 25 | 26 | void main() 27 | { 28 | // ambient 29 | vec3 ambient = light.ambient * material.ambient; 30 | 31 | // diffuse 32 | vec3 norm = normalize(Normal); 33 | vec3 dirToLight = normalize(LightPos - FragPos); 34 | float lightNormalDiff = max(dot(norm, dirToLight), 0.0); 35 | vec3 diffuse = light.diffuse * (material.diffuse * lightNormalDiff); 36 | 37 | // specular 38 | vec3 viewPos = vec3(0.0f, 0.0f, 0.0f); 39 | vec3 dirToView = normalize(viewPos - FragPos); 40 | vec3 reflectDir = reflect(-dirToLight, norm); 41 | float spec = pow(max(dot(dirToView, reflectDir), 0.0), material.shininess); 42 | vec3 specular = light.specular * (spec * material.specular); 43 | 44 | vec3 result = diffuse + specular + ambient; 45 | color = vec4(result, 1.0f); 46 | } 47 | -------------------------------------------------------------------------------- /light-maps/shaders/phong.vert: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | layout (location = 0) in vec3 position; 4 | layout (location = 1) in vec3 normal; 5 | 6 | uniform mat4 model; 7 | uniform mat4 view; 8 | uniform mat4 project; 9 | 10 | uniform vec3 lightPos; // only need one light for a basic example 11 | 12 | out vec3 Normal; 13 | out vec3 FragPos; 14 | out vec3 LightPos; 15 | 16 | void main() 17 | { 18 | gl_Position = project * view * model * vec4(position, 1.0); 19 | 20 | // we transform positions and vectors to view space before performing lighting 21 | // calculations in the fragment shader so that we know that the viewer position is (0,0,0) 22 | // FragPos = vec3(model * vec4(position, 1.0)); 23 | FragPos = vec3(view * model * vec4(position, 1.0)); 24 | 25 | // LightPos = vec3(view * vec4(lightPos, 1.0)); 26 | LightPos = vec3(view * vec4(lightPos, 1.0)); 27 | 28 | // transform the normals to the view space 29 | // this is different from just multiplying by the model then view matrix since 30 | // normals can't translate and are changed by non-uniform scaling 31 | // instead we take the upper left 3x3 matrix of the transpose of the inverse of each transform 32 | // that we are transforming across 33 | // see here for more details: http://www.lighthouse3d.com/tutorials/glsl-tutorial/the-normal-matrix/ 34 | mat3 normMatrix = mat3(transpose(inverse(view))) * mat3(transpose(inverse(model))); 35 | Normal = normMatrix * normal; 36 | } 37 | -------------------------------------------------------------------------------- /light-maps/win/input.go: -------------------------------------------------------------------------------- 1 | package win 2 | 3 | import ( 4 | "github.com/go-gl/glfw/v3.1/glfw" 5 | "github.com/go-gl/mathgl/mgl64" 6 | ) 7 | 8 | // Action is a configurable abstraction of a key press 9 | type Action int 10 | 11 | const ( 12 | PLAYER_FORWARD Action = iota 13 | PLAYER_BACKWARD Action = iota 14 | PLAYER_LEFT Action = iota 15 | PLAYER_RIGHT Action = iota 16 | PROGRAM_QUIT Action = iota 17 | ) 18 | 19 | type InputManager struct { 20 | actionToKeyMap map[Action]glfw.Key 21 | keysPressed [glfw.KeyLast]bool 22 | 23 | firstCursorAction bool 24 | cursor mgl64.Vec2 25 | cursorChange mgl64.Vec2 26 | cursorLast mgl64.Vec2 27 | bufferedCursorChange mgl64.Vec2 28 | } 29 | 30 | func NewInputManager() *InputManager { 31 | actionToKeyMap := map[Action]glfw.Key{ 32 | PLAYER_FORWARD: glfw.KeyW, 33 | PLAYER_BACKWARD: glfw.KeyS, 34 | PLAYER_LEFT: glfw.KeyA, 35 | PLAYER_RIGHT: glfw.KeyD, 36 | PROGRAM_QUIT: glfw.KeyEscape, 37 | } 38 | 39 | return &InputManager{ 40 | actionToKeyMap: actionToKeyMap, 41 | firstCursorAction: false, 42 | } 43 | } 44 | 45 | // IsActive returns whether the given Action is currently active 46 | func (im *InputManager) IsActive(a Action) bool { 47 | return im.keysPressed[im.actionToKeyMap[a]] 48 | } 49 | 50 | // Cursor returns the value of the cursor at the last time that CheckpointCursorChange() was called. 51 | func (im *InputManager) Cursor() mgl64.Vec2 { 52 | return im.cursor 53 | } 54 | 55 | // CursorChange returns the amount of change in the underlying cursor 56 | // since the last time CheckpointCursorChange was called 57 | func (im *InputManager) CursorChange() mgl64.Vec2 { 58 | return im.cursorChange 59 | } 60 | 61 | // CheckpointCursorChange updates the publicly available Cursor() and CursorChange() 62 | // methods to return the current Cursor and change since last time this method was called. 63 | func (im *InputManager) CheckpointCursorChange() { 64 | im.cursorChange[0] = im.bufferedCursorChange[0] 65 | im.cursorChange[1] = im.bufferedCursorChange[1] 66 | im.cursor[0] = im.cursorLast[0] 67 | im.cursor[1] = im.cursorLast[1] 68 | 69 | im.bufferedCursorChange[0] = 0 70 | im.bufferedCursorChange[1] = 0 71 | } 72 | 73 | func (im *InputManager) keyCallback(window *glfw.Window, key glfw.Key, scancode int, 74 | action glfw.Action, mods glfw.ModifierKey) { 75 | 76 | // timing for key events occurs differently from what the program loop requires 77 | // so just track what key actions occur and then access them in the program loop 78 | switch action { 79 | case glfw.Press: 80 | im.keysPressed[key] = true 81 | case glfw.Release: 82 | im.keysPressed[key] = false 83 | } 84 | } 85 | 86 | func (im *InputManager) mouseCallback(window *glfw.Window, xpos, ypos float64) { 87 | 88 | if im.firstCursorAction { 89 | im.cursorLast[0] = xpos 90 | im.cursorLast[1] = ypos 91 | im.firstCursorAction = false 92 | } 93 | 94 | im.bufferedCursorChange[0] += xpos - im.cursorLast[0] 95 | im.bufferedCursorChange[1] += ypos - im.cursorLast[1] 96 | 97 | im.cursorLast[0] = xpos 98 | im.cursorLast[1] = ypos 99 | } 100 | -------------------------------------------------------------------------------- /light-maps/win/window.go: -------------------------------------------------------------------------------- 1 | package win 2 | 3 | import ( 4 | "log" 5 | "github.com/go-gl/glfw/v3.1/glfw" 6 | ) 7 | 8 | type Window struct { 9 | width int 10 | height int 11 | glfw *glfw.Window 12 | 13 | inputManager *InputManager 14 | firstFrame bool 15 | dTime float64 16 | lastFrameTime float64 17 | } 18 | 19 | func (w *Window) InputManager() *InputManager { 20 | return w.inputManager 21 | } 22 | 23 | func NewWindow(width, height int, title string) *Window { 24 | 25 | gWindow, err := glfw.CreateWindow(width, height, title, nil, nil) 26 | if err != nil { 27 | log.Fatalln(err) 28 | } 29 | 30 | gWindow.MakeContextCurrent() 31 | gWindow.SetInputMode(glfw.CursorMode, glfw.CursorDisabled) 32 | 33 | im := NewInputManager() 34 | 35 | gWindow.SetKeyCallback(im.keyCallback) 36 | gWindow.SetCursorPosCallback(im.mouseCallback) 37 | 38 | return &Window{ 39 | width: width, 40 | height: height, 41 | glfw: gWindow, 42 | inputManager: im, 43 | firstFrame: true, 44 | } 45 | } 46 | 47 | func (w *Window) Width() int { 48 | return w.width 49 | } 50 | 51 | func (w *Window) Height() int { 52 | return w.height 53 | } 54 | 55 | func (w *Window) ShouldClose() bool { 56 | return w.glfw.ShouldClose() 57 | } 58 | 59 | // StartFrame sets everything up to start rendering a new frame. 60 | // This includes swapping in last rendered buffer, polling for window events, 61 | // checkpointing cursor tracking, and updating the time since last frame. 62 | func (w *Window) StartFrame() { 63 | // swap in the previous rendered buffer 64 | w.glfw.SwapBuffers() 65 | 66 | // poll for UI window events 67 | glfw.PollEvents() 68 | 69 | if w.inputManager.IsActive(PROGRAM_QUIT) { 70 | w.glfw.SetShouldClose(true) 71 | } 72 | 73 | // base calculations of time since last frame (basic program loop idea) 74 | // For better advanced impl, read: http://gafferongames.com/game-physics/fix-your-timestep/ 75 | curFrameTime := glfw.GetTime() 76 | 77 | if w.firstFrame { 78 | w.lastFrameTime = curFrameTime 79 | w.firstFrame = false 80 | } 81 | 82 | w.dTime = curFrameTime - w.lastFrameTime 83 | w.lastFrameTime = curFrameTime 84 | 85 | w.inputManager.CheckpointCursorChange() 86 | } 87 | 88 | func (w *Window) SinceLastFrame() float64 { 89 | return w.dTime 90 | } 91 | -------------------------------------------------------------------------------- /materials/.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | -------------------------------------------------------------------------------- /materials/cam/camera.go: -------------------------------------------------------------------------------- 1 | package cam 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/go-gl/mathgl/mgl32" 7 | "github.com/go-gl/mathgl/mgl64" 8 | 9 | "github.com/cstegel/opengl-samples-golang/materials/win" 10 | ) 11 | 12 | type FpsCamera struct { 13 | // Camera options 14 | moveSpeed float64 15 | cursorSensitivity float64 16 | 17 | // Eular Angles 18 | pitch float64 19 | yaw float64 20 | 21 | // Camera attributes 22 | pos mgl32.Vec3 23 | front mgl32.Vec3 24 | up mgl32.Vec3 25 | right mgl32.Vec3 26 | worldUp mgl32.Vec3 27 | 28 | inputManager *win.InputManager 29 | } 30 | 31 | func NewFpsCamera(position, worldUp mgl32.Vec3, yaw, pitch float64, im *win.InputManager) (*FpsCamera) { 32 | cam := FpsCamera { 33 | moveSpeed: 5.00, 34 | cursorSensitivity: 0.05, 35 | pitch: pitch, 36 | yaw: yaw, 37 | pos: position, 38 | up: mgl32.Vec3{0, 1, 0}, 39 | worldUp: worldUp, 40 | inputManager: im, 41 | } 42 | 43 | return &cam 44 | } 45 | 46 | func (c *FpsCamera) Update(dTime float64) { 47 | c.updatePosition(dTime) 48 | c.updateDirection() 49 | } 50 | 51 | // UpdatePosition updates this camera's position by giving directions that 52 | // the camera is to travel in and for how long 53 | func (c *FpsCamera) updatePosition(dTime float64) { 54 | adjustedSpeed := float32(dTime * c.moveSpeed) 55 | 56 | if c.inputManager.IsActive(win.PLAYER_FORWARD) { 57 | c.pos = c.pos.Add(c.front.Mul(adjustedSpeed)) 58 | } 59 | if c.inputManager.IsActive(win.PLAYER_BACKWARD) { 60 | c.pos = c.pos.Sub(c.front.Mul(adjustedSpeed)) 61 | } 62 | if c.inputManager.IsActive(win.PLAYER_LEFT) { 63 | c.pos = c.pos.Sub(c.front.Cross(c.up).Normalize().Mul(adjustedSpeed)) 64 | } 65 | if c.inputManager.IsActive(win.PLAYER_RIGHT) { 66 | c.pos = c.pos.Add(c.front.Cross(c.up).Normalize().Mul(adjustedSpeed)) 67 | } 68 | } 69 | 70 | // UpdateCursor updates the direction of the camera by giving it delta x/y values 71 | // that came from a cursor input device 72 | func (c *FpsCamera) updateDirection() { 73 | dCursor := c.inputManager.CursorChange() 74 | 75 | dx := -c.cursorSensitivity * dCursor[0] 76 | dy := c.cursorSensitivity * dCursor[1] 77 | 78 | c.pitch += dy 79 | if c.pitch > 89.0 { 80 | c.pitch = 89.0 81 | } else if c.pitch < -89.0 { 82 | c.pitch = -89.0 83 | } 84 | 85 | c.yaw = math.Mod(c.yaw + dx, 360) 86 | c.updateVectors() 87 | } 88 | 89 | func (c *FpsCamera) updateVectors() { 90 | // x, y, z 91 | c.front[0] = float32(math.Cos(mgl64.DegToRad(c.pitch)) * math.Cos(mgl64.DegToRad(c.yaw))) 92 | c.front[1] = float32(math.Sin(mgl64.DegToRad(c.pitch))) 93 | c.front[2] = float32(math.Cos(mgl64.DegToRad(c.pitch)) * math.Sin(mgl64.DegToRad(c.yaw))) 94 | c.front = c.front.Normalize() 95 | 96 | // Gram-Schmidt process to figure out right and up vectors 97 | c.right = c.worldUp.Cross(c.front).Normalize() 98 | c.up = c.right.Cross(c.front).Normalize() 99 | } 100 | 101 | // GetCameraTransform gets the matrix to transform from world coordinates to 102 | // this camera's coordinates. 103 | func (camera *FpsCamera) GetTransform() mgl32.Mat4 { 104 | cameraTarget := camera.pos.Add(camera.front) 105 | 106 | return mgl32.LookAt( 107 | camera.pos.X(), camera.pos.Y(), camera.pos.Z(), 108 | cameraTarget.X(), cameraTarget.Y(), cameraTarget.Z(), 109 | camera.up.X(), camera.up.Y(), camera.up.Z(), 110 | ) 111 | } 112 | -------------------------------------------------------------------------------- /materials/gfx/shader.go: -------------------------------------------------------------------------------- 1 | package gfx 2 | 3 | import ( 4 | "io/ioutil" 5 | "strings" 6 | "fmt" 7 | 8 | "github.com/go-gl/gl/v4.1-core/gl" 9 | ) 10 | 11 | type Shader struct { 12 | handle uint32 13 | } 14 | 15 | type Program struct { 16 | handle uint32 17 | shaders []*Shader 18 | } 19 | 20 | func (shader *Shader) Delete() { 21 | gl.DeleteShader(shader.handle) 22 | } 23 | 24 | func (prog *Program) Delete() { 25 | for _, shader := range prog.shaders { 26 | shader.Delete() 27 | } 28 | gl.DeleteProgram(prog.handle) 29 | } 30 | 31 | func (prog *Program) Attach(shaders ...*Shader) { 32 | for _, shader := range shaders { 33 | gl.AttachShader(prog.handle, shader.handle) 34 | prog.shaders = append(prog.shaders, shader) 35 | } 36 | } 37 | 38 | func (prog *Program) Use() { 39 | gl.UseProgram(prog.handle) 40 | } 41 | 42 | func (prog *Program) Link() error { 43 | gl.LinkProgram(prog.handle) 44 | return getGlError(prog.handle, gl.LINK_STATUS, gl.GetProgramiv, gl.GetProgramInfoLog, 45 | "PROGRAM::LINKING_FAILURE") 46 | } 47 | 48 | func (prog *Program) GetUniformLocation(name string) int32 { 49 | return gl.GetUniformLocation(prog.handle, gl.Str(name + "\x00")) 50 | } 51 | 52 | func NewProgram(shaders ...*Shader) (*Program, error) { 53 | prog := &Program{handle:gl.CreateProgram()} 54 | prog.Attach(shaders...) 55 | 56 | if err := prog.Link(); err != nil { 57 | return nil, err 58 | } 59 | 60 | return prog, nil 61 | } 62 | 63 | func NewShader(src string, sType uint32) (*Shader, error) { 64 | 65 | handle := gl.CreateShader(sType) 66 | glSrc, freeFn := gl.Strs(src + "\x00") 67 | defer freeFn() 68 | gl.ShaderSource(handle, 1, glSrc, nil) 69 | gl.CompileShader(handle) 70 | err := getGlError(handle, gl.COMPILE_STATUS, gl.GetShaderiv, gl.GetShaderInfoLog, 71 | "SHADER::COMPILE_FAILURE::") 72 | if err != nil { 73 | return nil, err 74 | } 75 | return &Shader{handle:handle}, nil 76 | } 77 | 78 | func NewShaderFromFile(file string, sType uint32) (*Shader, error) { 79 | src, err := ioutil.ReadFile(file) 80 | if err != nil { 81 | return nil, err 82 | } 83 | handle := gl.CreateShader(sType) 84 | glSrc, freeFn := gl.Strs(string(src) + "\x00") 85 | defer freeFn() 86 | gl.ShaderSource(handle, 1, glSrc, nil) 87 | gl.CompileShader(handle) 88 | err = getGlError(handle, gl.COMPILE_STATUS, gl.GetShaderiv, gl.GetShaderInfoLog, 89 | "SHADER::COMPILE_FAILURE::" + file) 90 | if err != nil { 91 | return nil, err 92 | } 93 | return &Shader{handle:handle}, nil 94 | } 95 | 96 | type getObjIv func(uint32, uint32, *int32) 97 | type getObjInfoLog func(uint32, int32, *int32, *uint8) 98 | 99 | func getGlError(glHandle uint32, checkTrueParam uint32, getObjIvFn getObjIv, 100 | getObjInfoLogFn getObjInfoLog, failMsg string) error { 101 | 102 | var success int32 103 | getObjIvFn(glHandle, checkTrueParam, &success) 104 | 105 | if success == gl.FALSE { 106 | var logLength int32 107 | getObjIvFn(glHandle, gl.INFO_LOG_LENGTH, &logLength) 108 | 109 | log := gl.Str(strings.Repeat("\x00", int(logLength))) 110 | getObjInfoLogFn(glHandle, logLength, nil, log) 111 | 112 | return fmt.Errorf("%s: %s", failMsg, gl.GoStr(log)) 113 | } 114 | 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /materials/gfx/texture.go: -------------------------------------------------------------------------------- 1 | package gfx 2 | 3 | import ( 4 | "os" 5 | "errors" 6 | "image" 7 | "image/draw" 8 | _ "image/png" 9 | _ "image/jpeg" 10 | 11 | "github.com/go-gl/gl/v4.1-core/gl" 12 | ) 13 | 14 | type Texture struct { 15 | handle uint32 16 | target uint32 // same target as gl.BindTexture(, ...) 17 | texUnit uint32 // Texture unit that is currently bound to ex: gl.TEXTURE0 18 | } 19 | 20 | var errUnsupportedStride = errors.New("unsupported stride, only 32-bit colors supported") 21 | 22 | var errTextureNotBound = errors.New("texture not bound") 23 | 24 | func NewTextureFromFile(file string, wrapR, wrapS int32) (*Texture, error) { 25 | imgFile, err := os.Open(file) 26 | if err != nil { 27 | return nil, err 28 | } 29 | defer imgFile.Close() 30 | 31 | // Decode detexts the type of image as long as its image/ is imported 32 | img, _, err := image.Decode(imgFile) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return NewTexture(img, wrapR, wrapS) 37 | } 38 | 39 | func NewTexture(img image.Image, wrapR, wrapS int32) (*Texture, error) { 40 | rgba := image.NewRGBA(img.Bounds()) 41 | draw.Draw(rgba, rgba.Bounds(), img, image.Pt(0, 0), draw.Src) 42 | if rgba.Stride != rgba.Rect.Size().X*4 { // TODO-cs: why? 43 | return nil, errUnsupportedStride 44 | } 45 | 46 | var handle uint32 47 | gl.GenTextures(1, &handle) 48 | 49 | target := uint32(gl.TEXTURE_2D) 50 | internalFmt := int32(gl.SRGB_ALPHA) 51 | format := uint32(gl.RGBA) 52 | width := int32(rgba.Rect.Size().X) 53 | height := int32(rgba.Rect.Size().Y) 54 | pixType := uint32(gl.UNSIGNED_BYTE) 55 | dataPtr := gl.Ptr(rgba.Pix) 56 | 57 | texture := Texture{ 58 | handle:handle, 59 | target:target, 60 | } 61 | 62 | texture.Bind(gl.TEXTURE0) 63 | defer texture.UnBind() 64 | 65 | // set the texture wrapping/filtering options (applies to current bound texture obj) 66 | // TODO-cs 67 | gl.TexParameteri(texture.target, gl.TEXTURE_WRAP_R, wrapR) 68 | gl.TexParameteri(texture.target, gl.TEXTURE_WRAP_S, wrapS) 69 | gl.TexParameteri(texture.target, gl.TEXTURE_MIN_FILTER, gl.LINEAR) // minification filter 70 | gl.TexParameteri(texture.target, gl.TEXTURE_MAG_FILTER, gl.LINEAR) // magnification filter 71 | 72 | 73 | gl.TexImage2D(target, 0, internalFmt, width, height, 0, format, pixType, dataPtr) 74 | 75 | gl.GenerateMipmap(texture.handle) 76 | 77 | return &texture, nil 78 | } 79 | 80 | func (tex *Texture) Bind(texUnit uint32) { 81 | gl.ActiveTexture(texUnit) 82 | gl.BindTexture(tex.target, tex.handle) 83 | tex.texUnit = texUnit 84 | } 85 | 86 | func (tex *Texture) UnBind() { 87 | tex.texUnit = 0 88 | gl.BindTexture(tex.target, 0) 89 | } 90 | 91 | func (tex *Texture) SetUniform(uniformLoc int32) error { 92 | if tex.texUnit == 0 { 93 | return errTextureNotBound 94 | } 95 | gl.Uniform1i(uniformLoc, int32(tex.texUnit - gl.TEXTURE0)) 96 | return nil 97 | } 98 | 99 | func loadImageFile(file string) (image.Image, error) { 100 | infile, err := os.Open(file) 101 | if err != nil { 102 | return nil, err 103 | } 104 | defer infile.Close() 105 | 106 | // Decode automatically figures out the type of immage in the file 107 | // as long as its image/ is imported 108 | img, _, err := image.Decode(infile) 109 | return img, err 110 | } 111 | -------------------------------------------------------------------------------- /materials/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | http://www.learnopengl.com/#!Lighting/Materials 5 | 6 | Shows basic materials with phong lighting 7 | */ 8 | 9 | import ( 10 | "log" 11 | "runtime" 12 | "math" 13 | 14 | "github.com/go-gl/gl/v4.1-core/gl" 15 | "github.com/go-gl/glfw/v3.1/glfw" 16 | "github.com/go-gl/mathgl/mgl32" 17 | 18 | "github.com/cstegel/opengl-samples-golang/materials/gfx" 19 | "github.com/cstegel/opengl-samples-golang/materials/win" 20 | "github.com/cstegel/opengl-samples-golang/materials/cam" 21 | ) 22 | 23 | // vertices to draw 6 faces of a cube 24 | var cubeVertices = []float32{ 25 | // position // normal vector 26 | -0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 27 | 0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 28 | 0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 29 | 0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 30 | -0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 31 | -0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 32 | 33 | -0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 34 | 0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 35 | 0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 36 | 0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 37 | -0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 38 | -0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 39 | 40 | -0.5, 0.5, 0.5, -1.0, 0.0, 0.0, 41 | -0.5, 0.5, -0.5, -1.0, 0.0, 0.0, 42 | -0.5, -0.5, -0.5, -1.0, 0.0, 0.0, 43 | -0.5, -0.5, -0.5, -1.0, 0.0, 0.0, 44 | -0.5, -0.5, 0.5, -1.0, 0.0, 0.0, 45 | -0.5, 0.5, 0.5, -1.0, 0.0, 0.0, 46 | 47 | 0.5, 0.5, 0.5, 1.0, 0.0, 0.0, 48 | 0.5, 0.5, -0.5, 1.0, 0.0, 0.0, 49 | 0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 50 | 0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 51 | 0.5, -0.5, 0.5, 1.0, 0.0, 0.0, 52 | 0.5, 0.5, 0.5, 1.0, 0.0, 0.0, 53 | 54 | -0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 55 | 0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 56 | 0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 57 | 0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 58 | -0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 59 | -0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 60 | 61 | -0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 62 | 0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 63 | 0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 64 | 0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 65 | -0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 66 | -0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 67 | } 68 | 69 | var cubePositions = [][]float32 { 70 | { 0.0, 0.0, -3.0}, 71 | { 2.0, 5.0, -15.0}, 72 | {-1.5, -2.2, -2.5 }, 73 | {-3.8, -2.0, -12.3}, 74 | { 2.4, -0.4, -3.5 }, 75 | {-1.7, 3.0, -7.5 }, 76 | { 1.3, -2.0, -2.5 }, 77 | { 1.5, 2.0, -2.5 }, 78 | { 1.5, 0.2, -1.5 }, 79 | {-1.3, 1.0, -1.5 }, 80 | } 81 | 82 | func init() { 83 | // GLFW event handling must be run on the main OS thread 84 | runtime.LockOSThread() 85 | } 86 | 87 | func main() { 88 | if err := glfw.Init(); err != nil { 89 | log.Fatalln("failed to inifitialize glfw:", err) 90 | } 91 | defer glfw.Terminate() 92 | 93 | glfw.WindowHint(glfw.Resizable, glfw.False) 94 | glfw.WindowHint(glfw.ContextVersionMajor, 4) 95 | glfw.WindowHint(glfw.ContextVersionMinor, 1) 96 | glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) 97 | glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) 98 | 99 | window := win.NewWindow(1280, 720, "Materials") 100 | 101 | // Initialize Glow (go function bindings) 102 | if err := gl.Init(); err != nil { 103 | panic(err) 104 | } 105 | 106 | err := programLoop(window) 107 | if err != nil { 108 | log.Fatalln(err) 109 | } 110 | } 111 | 112 | /* 113 | * Creates the Vertex Array Object for a triangle. 114 | * indices is leftover from earlier samples and not used here. 115 | */ 116 | func createVAO(vertices []float32, indices []uint32) uint32 { 117 | 118 | var VAO uint32 119 | gl.GenVertexArrays(1, &VAO) 120 | 121 | var VBO uint32 122 | gl.GenBuffers(1, &VBO) 123 | 124 | var EBO uint32; 125 | gl.GenBuffers(1, &EBO) 126 | 127 | // Bind the Vertex Array Object first, then bind and set vertex buffer(s) and attribute pointers() 128 | gl.BindVertexArray(VAO) 129 | 130 | // copy vertices data into VBO (it needs to be bound first) 131 | gl.BindBuffer(gl.ARRAY_BUFFER, VBO) 132 | gl.BufferData(gl.ARRAY_BUFFER, len(vertices)*4, gl.Ptr(vertices), gl.STATIC_DRAW) 133 | 134 | // size of one whole vertex (sum of attrib sizes) 135 | var stride int32 = 3*4 + 3*4 136 | var offset int = 0 137 | 138 | // position 139 | gl.VertexAttribPointer(0, 3, gl.FLOAT, false, stride, gl.PtrOffset(offset)) 140 | gl.EnableVertexAttribArray(0) 141 | offset += 3*4 142 | 143 | // normal 144 | gl.VertexAttribPointer(1, 3, gl.FLOAT, false, stride, gl.PtrOffset(offset)) 145 | gl.EnableVertexAttribArray(1) 146 | offset += 3*4 147 | 148 | // unbind the VAO (safe practice so we don't accidentally (mis)configure it later) 149 | gl.BindVertexArray(0) 150 | 151 | return VAO 152 | } 153 | 154 | func programLoop(window *win.Window) error { 155 | 156 | // the linked shader program determines how the data will be rendered 157 | vertShader, err := gfx.NewShaderFromFile("shaders/phong.vert", gl.VERTEX_SHADER) 158 | if err != nil { 159 | return err 160 | } 161 | 162 | fragShader, err := gfx.NewShaderFromFile("shaders/phong.frag", gl.FRAGMENT_SHADER) 163 | if err != nil { 164 | return err 165 | } 166 | 167 | program, err := gfx.NewProgram(vertShader, fragShader) 168 | if err != nil { 169 | return err 170 | } 171 | defer program.Delete() 172 | 173 | lightFragShader, err := gfx.NewShaderFromFile("shaders/light.frag", gl.FRAGMENT_SHADER) 174 | if err != nil { 175 | return err 176 | } 177 | 178 | // special shader program so that lights themselves are not affected by lighting 179 | lightProgram, err := gfx.NewProgram(vertShader, lightFragShader) 180 | if err != nil { 181 | return err 182 | } 183 | 184 | VAO := createVAO(cubeVertices, nil) 185 | lightVAO := createVAO(cubeVertices, nil) 186 | 187 | // ensure that triangles that are "behind" others do not draw over top of them 188 | gl.Enable(gl.DEPTH_TEST) 189 | 190 | camera := cam.NewFpsCamera(mgl32.Vec3{0, 0, 3}, mgl32.Vec3{0, 1, 0}, -90, 0, window.InputManager()) 191 | 192 | for !window.ShouldClose() { 193 | 194 | // swaps in last buffer, polls for window events, and generally sets up for a new render frame 195 | window.StartFrame() 196 | 197 | // update camera position and direction from input evevnts 198 | camera.Update(window.SinceLastFrame()) 199 | 200 | // background color 201 | gl.ClearColor(0, 0, 0, 1.0) 202 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) // depth buffer needed for DEPTH_TEST 203 | 204 | 205 | // cube rotation matrices 206 | rotateX := (mgl32.Rotate3DX(mgl32.DegToRad(-45 * float32(glfw.GetTime())))) 207 | rotateY := (mgl32.Rotate3DY(mgl32.DegToRad(-45 * float32(glfw.GetTime())))) 208 | rotateZ := (mgl32.Rotate3DZ(mgl32.DegToRad(-45 * float32(glfw.GetTime())))) 209 | 210 | // creates perspective 211 | fov := float32(60.0) 212 | projectTransform := mgl32.Perspective(mgl32.DegToRad(fov), 213 | float32(window.Width())/float32(window.Height()), 214 | 0.1, 215 | 100.0) 216 | 217 | camTransform := camera.GetTransform() 218 | lightPos := mgl32.Vec3{0.6, 1, 0.1} 219 | lightTransform := mgl32.Translate3D(lightPos.X(), lightPos.Y(), lightPos.Z()).Mul4( 220 | mgl32.Scale3D(0.2, 0.2, 0.2)) 221 | 222 | program.Use() 223 | gl.UniformMatrix4fv(program.GetUniformLocation("view"), 1, false, &camTransform[0]) 224 | gl.UniformMatrix4fv(program.GetUniformLocation("project"), 1, false, 225 | &projectTransform[0]) 226 | 227 | gl.BindVertexArray(VAO) 228 | 229 | // draw each cube after all coordinate system transforms are bound 230 | 231 | // obj is colored, light is white 232 | gl.Uniform3f(program.GetUniformLocation("material.ambient"), 1.0, 0.5, 0.31) 233 | gl.Uniform3f(program.GetUniformLocation("material.diffuse"), 1.0, 0.5, 0.31) 234 | gl.Uniform3f(program.GetUniformLocation("material.specular"), 0.5, 0.5, 0.5) 235 | gl.Uniform1f(program.GetUniformLocation("material.shininess"), 32.0) 236 | 237 | lightColor := mgl32.Vec3{ 238 | float32(math.Sin(glfw.GetTime() * 1)), 239 | float32(math.Sin(glfw.GetTime() * 0.35)), 240 | float32(math.Sin(glfw.GetTime() * 0.65)), 241 | } 242 | 243 | diffuseColor := mgl32.Vec3{ 244 | 0.5 * lightColor[0], 245 | 0.5 * lightColor[1], 246 | 0.5 * lightColor[2], 247 | } 248 | ambientColor := mgl32.Vec3{ 249 | 0.2 * lightColor[0], 250 | 0.2 * lightColor[1], 251 | 0.2 * lightColor[2], 252 | } 253 | 254 | gl.Uniform3f(program.GetUniformLocation("light.ambient"), 255 | ambientColor[0], ambientColor[1], ambientColor[2]) 256 | gl.Uniform3f(program.GetUniformLocation("light.diffuse"), 257 | diffuseColor[0], diffuseColor[1], diffuseColor[2]) 258 | gl.Uniform3f(program.GetUniformLocation("light.specular"), 1.0, 1.0, 1.0) 259 | gl.Uniform3f(program.GetUniformLocation("light.position"), lightPos.X(), lightPos.Y(), lightPos.Z()) 260 | 261 | for _, pos := range cubePositions { 262 | 263 | // turn the cubes into rectangular prisms for more fun 264 | worldTranslate := mgl32.Translate3D(pos[0], pos[1], pos[2]) 265 | worldTransform := worldTranslate.Mul4( 266 | rotateX.Mul3(rotateY).Mul3(rotateZ).Mat4(), 267 | ) 268 | 269 | gl.UniformMatrix4fv(program.GetUniformLocation("model"), 1, false, 270 | &worldTransform[0]) 271 | 272 | gl.DrawArrays(gl.TRIANGLES, 0, 36) 273 | } 274 | gl.BindVertexArray(0) 275 | 276 | // Draw the light obj after the other boxes using its separate shader program 277 | // this means that we must re-bind any uniforms 278 | lightProgram.Use() 279 | gl.BindVertexArray(lightVAO) 280 | gl.UniformMatrix4fv(lightProgram.GetUniformLocation("model"), 1, false, &lightTransform[0]) 281 | gl.UniformMatrix4fv(lightProgram.GetUniformLocation("view"), 1, false, &camTransform[0]) 282 | gl.UniformMatrix4fv(lightProgram.GetUniformLocation("project"), 1, false, &projectTransform[0]) 283 | gl.DrawArrays(gl.TRIANGLES, 0, 36) 284 | 285 | gl.BindVertexArray(0) 286 | 287 | // end of draw loop 288 | } 289 | 290 | return nil 291 | } 292 | -------------------------------------------------------------------------------- /materials/shaders/light.frag: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | // special fragment shader that is not affected by lighting 4 | // useful for debugging like showing locations of lights 5 | 6 | out vec4 color; 7 | 8 | void main() 9 | { 10 | color = vec4(1.0f); // color white 11 | } 12 | -------------------------------------------------------------------------------- /materials/shaders/phong.frag: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | struct Material { 4 | vec3 ambient; 5 | vec3 diffuse; 6 | vec3 specular; 7 | float shininess; 8 | }; 9 | 10 | struct Light { 11 | vec3 position; 12 | 13 | vec3 ambient; 14 | vec3 diffuse; 15 | vec3 specular; 16 | }; 17 | 18 | in vec3 Normal; 19 | in vec3 FragPos; 20 | in vec3 LightPos; 21 | out vec4 color; 22 | 23 | uniform Material material; 24 | uniform Light light; 25 | 26 | void main() 27 | { 28 | // ambient 29 | vec3 ambient = light.ambient * material.ambient; 30 | 31 | // diffuse 32 | vec3 norm = normalize(Normal); 33 | vec3 dirToLight = normalize(LightPos - FragPos); 34 | float lightNormalDiff = max(dot(norm, dirToLight), 0.0); 35 | vec3 diffuse = light.diffuse * (material.diffuse * lightNormalDiff); 36 | 37 | // specular 38 | vec3 viewPos = vec3(0.0f, 0.0f, 0.0f); 39 | vec3 dirToView = normalize(viewPos - FragPos); 40 | vec3 reflectDir = reflect(-dirToLight, norm); 41 | float spec = pow(max(dot(dirToView, reflectDir), 0.0), material.shininess); 42 | vec3 specular = light.specular * (spec * material.specular); 43 | 44 | vec3 result = diffuse + specular + ambient; 45 | color = vec4(result, 1.0f); 46 | } 47 | -------------------------------------------------------------------------------- /materials/shaders/phong.vert: -------------------------------------------------------------------------------- 1 | #version 410 core 2 | 3 | layout (location = 0) in vec3 position; 4 | layout (location = 1) in vec3 normal; 5 | 6 | uniform mat4 model; 7 | uniform mat4 view; 8 | uniform mat4 project; 9 | 10 | uniform vec3 lightPos; // only need one light for a basic example 11 | 12 | out vec3 Normal; 13 | out vec3 FragPos; 14 | out vec3 LightPos; 15 | 16 | void main() 17 | { 18 | gl_Position = project * view * model * vec4(position, 1.0); 19 | 20 | // we transform positions and vectors to view space before performing lighting 21 | // calculations in the fragment shader so that we know that the viewer position is (0,0,0) 22 | // FragPos = vec3(model * vec4(position, 1.0)); 23 | FragPos = vec3(view * model * vec4(position, 1.0)); 24 | 25 | // LightPos = vec3(view * vec4(lightPos, 1.0)); 26 | LightPos = vec3(view * vec4(lightPos, 1.0)); 27 | 28 | // transform the normals to the view space 29 | // this is different from just multiplying by the model then view matrix since 30 | // normals can't translate and are changed by non-uniform scaling 31 | // instead we take the upper left 3x3 matrix of the transpose of the inverse of each transform 32 | // that we are transforming across 33 | // see here for more details: http://www.lighthouse3d.com/tutorials/glsl-tutorial/the-normal-matrix/ 34 | mat3 normMatrix = mat3(transpose(inverse(view))) * mat3(transpose(inverse(model))); 35 | Normal = normMatrix * normal; 36 | } 37 | -------------------------------------------------------------------------------- /materials/win/input.go: -------------------------------------------------------------------------------- 1 | package win 2 | 3 | import ( 4 | "github.com/go-gl/glfw/v3.1/glfw" 5 | "github.com/go-gl/mathgl/mgl64" 6 | ) 7 | 8 | // Action is a configurable abstraction of a key press 9 | type Action int 10 | 11 | const ( 12 | PLAYER_FORWARD Action = iota 13 | PLAYER_BACKWARD Action = iota 14 | PLAYER_LEFT Action = iota 15 | PLAYER_RIGHT Action = iota 16 | PROGRAM_QUIT Action = iota 17 | ) 18 | 19 | type InputManager struct { 20 | actionToKeyMap map[Action]glfw.Key 21 | keysPressed [glfw.KeyLast]bool 22 | 23 | firstCursorAction bool 24 | cursor mgl64.Vec2 25 | cursorChange mgl64.Vec2 26 | cursorLast mgl64.Vec2 27 | bufferedCursorChange mgl64.Vec2 28 | } 29 | 30 | func NewInputManager() *InputManager { 31 | actionToKeyMap := map[Action]glfw.Key{ 32 | PLAYER_FORWARD: glfw.KeyW, 33 | PLAYER_BACKWARD: glfw.KeyS, 34 | PLAYER_LEFT: glfw.KeyA, 35 | PLAYER_RIGHT: glfw.KeyD, 36 | PROGRAM_QUIT: glfw.KeyEscape, 37 | } 38 | 39 | return &InputManager{ 40 | actionToKeyMap: actionToKeyMap, 41 | firstCursorAction: false, 42 | } 43 | } 44 | 45 | // IsActive returns whether the given Action is currently active 46 | func (im *InputManager) IsActive(a Action) bool { 47 | return im.keysPressed[im.actionToKeyMap[a]] 48 | } 49 | 50 | // Cursor returns the value of the cursor at the last time that CheckpointCursorChange() was called. 51 | func (im *InputManager) Cursor() mgl64.Vec2 { 52 | return im.cursor 53 | } 54 | 55 | // CursorChange returns the amount of change in the underlying cursor 56 | // since the last time CheckpointCursorChange was called 57 | func (im *InputManager) CursorChange() mgl64.Vec2 { 58 | return im.cursorChange 59 | } 60 | 61 | // CheckpointCursorChange updates the publicly available Cursor() and CursorChange() 62 | // methods to return the current Cursor and change since last time this method was called. 63 | func (im *InputManager) CheckpointCursorChange() { 64 | im.cursorChange[0] = im.bufferedCursorChange[0] 65 | im.cursorChange[1] = im.bufferedCursorChange[1] 66 | im.cursor[0] = im.cursorLast[0] 67 | im.cursor[1] = im.cursorLast[1] 68 | 69 | im.bufferedCursorChange[0] = 0 70 | im.bufferedCursorChange[1] = 0 71 | } 72 | 73 | func (im *InputManager) keyCallback(window *glfw.Window, key glfw.Key, scancode int, 74 | action glfw.Action, mods glfw.ModifierKey) { 75 | 76 | // timing for key events occurs differently from what the program loop requires 77 | // so just track what key actions occur and then access them in the program loop 78 | switch action { 79 | case glfw.Press: 80 | im.keysPressed[key] = true 81 | case glfw.Release: 82 | im.keysPressed[key] = false 83 | } 84 | } 85 | 86 | func (im *InputManager) mouseCallback(window *glfw.Window, xpos, ypos float64) { 87 | 88 | if im.firstCursorAction { 89 | im.cursorLast[0] = xpos 90 | im.cursorLast[1] = ypos 91 | im.firstCursorAction = false 92 | } 93 | 94 | im.bufferedCursorChange[0] += xpos - im.cursorLast[0] 95 | im.bufferedCursorChange[1] += ypos - im.cursorLast[1] 96 | 97 | im.cursorLast[0] = xpos 98 | im.cursorLast[1] = ypos 99 | } 100 | -------------------------------------------------------------------------------- /materials/win/window.go: -------------------------------------------------------------------------------- 1 | package win 2 | 3 | import ( 4 | "log" 5 | "github.com/go-gl/glfw/v3.1/glfw" 6 | ) 7 | 8 | type Window struct { 9 | width int 10 | height int 11 | glfw *glfw.Window 12 | 13 | inputManager *InputManager 14 | firstFrame bool 15 | dTime float64 16 | lastFrameTime float64 17 | } 18 | 19 | func (w *Window) InputManager() *InputManager { 20 | return w.inputManager 21 | } 22 | 23 | func NewWindow(width, height int, title string) *Window { 24 | 25 | gWindow, err := glfw.CreateWindow(width, height, title, nil, nil) 26 | if err != nil { 27 | log.Fatalln(err) 28 | } 29 | 30 | gWindow.MakeContextCurrent() 31 | gWindow.SetInputMode(glfw.CursorMode, glfw.CursorDisabled) 32 | 33 | im := NewInputManager() 34 | 35 | gWindow.SetKeyCallback(im.keyCallback) 36 | gWindow.SetCursorPosCallback(im.mouseCallback) 37 | 38 | return &Window{ 39 | width: width, 40 | height: height, 41 | glfw: gWindow, 42 | inputManager: im, 43 | firstFrame: true, 44 | } 45 | } 46 | 47 | func (w *Window) Width() int { 48 | return w.width 49 | } 50 | 51 | func (w *Window) Height() int { 52 | return w.height 53 | } 54 | 55 | func (w *Window) ShouldClose() bool { 56 | return w.glfw.ShouldClose() 57 | } 58 | 59 | // StartFrame sets everything up to start rendering a new frame. 60 | // This includes swapping in last rendered buffer, polling for window events, 61 | // checkpointing cursor tracking, and updating the time since last frame. 62 | func (w *Window) StartFrame() { 63 | // swap in the previous rendered buffer 64 | w.glfw.SwapBuffers() 65 | 66 | // poll for UI window events 67 | glfw.PollEvents() 68 | 69 | if w.inputManager.IsActive(PROGRAM_QUIT) { 70 | w.glfw.SetShouldClose(true) 71 | } 72 | 73 | // base calculations of time since last frame (basic program loop idea) 74 | // For better advanced impl, read: http://gafferongames.com/game-physics/fix-your-timestep/ 75 | curFrameTime := glfw.GetTime() 76 | 77 | if w.firstFrame { 78 | w.lastFrameTime = curFrameTime 79 | w.firstFrame = false 80 | } 81 | 82 | w.dTime = curFrameTime - w.lastFrameTime 83 | w.lastFrameTime = curFrameTime 84 | 85 | w.inputManager.CheckpointCursorChange() 86 | } 87 | 88 | func (w *Window) SinceLastFrame() float64 { 89 | return w.dTime 90 | } 91 | --------------------------------------------------------------------------------