From 3a5b66a20c66e66aae61c9cc6e3dc99fdfd5948f Mon Sep 17 00:00:00 2001 From: Marvin Blum Date: Fri, 6 May 2016 16:53:27 +0200 Subject: [PATCH] Rendering now works! Added shaders for reference. --- ToDo.md | 1 + game.go | 129 +++++++++++++++++++++++++++--------- gl_util.go | 69 ++++++++++++++++++++ pos.go | 85 ++++++++++++++++++++++++ shader.go | 21 ++++-- shader/basic2D.fs | 12 ++++ shader/basic2D.vs | 13 ++++ shader/basic3D.fs | 12 ++++ shader/basic3D.vs | 13 ++++ shader/text.fs | 13 ++++ shader/text.vs | 13 ++++ sprite.go | 163 ++++++++++++++++++++++++++++++++++++++++++++++ system.go | 4 +- 13 files changed, 508 insertions(+), 40 deletions(-) create mode 100644 pos.go create mode 100644 shader/basic2D.fs create mode 100644 shader/basic2D.vs create mode 100644 shader/basic3D.fs create mode 100644 shader/basic3D.vs create mode 100644 shader/text.fs create mode 100644 shader/text.vs create mode 100644 sprite.go diff --git a/ToDo.md b/ToDo.md index e7c76a4..46558b9 100644 --- a/ToDo.md +++ b/ToDo.md @@ -3,3 +3,4 @@ * ~~cleanup resources~~ * more logging * limit FPS +* fullscreen diff --git a/game.go b/game.go index c561917..8a8f7ea 100644 --- a/game.go +++ b/game.go @@ -14,6 +14,31 @@ const ( default_height = uint32(400) default_title = "Game" default_exit_on_close = true + + // constants for default 2D shader + Default_shader_2D_vertex_attrib = "vertex" + Default_shader_2D_texcoord_attrib = "texCoord" + Default_shader_2D_ortho = "o" + Default_shader_2D_model = "m" + Default_shader_2D_tex = "tex" + + default_shader_2d_vertex_src = `#version 130 + uniform mat3 o, m; + in vec2 vertex; + in vec2 texCoord; + out vec2 tc; + void main(){ + tc = texCoord; + gl_Position = vec4(o*m*vec3(vertex, 1.0), 1.0); + }` + default_shader_2d_fragment_src = `#version 130 + precision highp float; + uniform sampler2D tex; + in vec2 tc; + out vec4 color; + void main(){ + color = texture(tex, tc); + }` ) // If set in RunOptions, the function will be called on window resize. @@ -46,6 +71,10 @@ var ( clearBuffer []uint32 viewportWidth int viewportHeight int + + // Default resources + DefaultCamera *Camera + Default2DShader *Shader ) func init() { @@ -130,7 +159,7 @@ func Run(game Game, options *RunOptions) { // init go-game log.Print("Initializing goga") - initGoga() + initGoga(int(width), int(height)) if options != nil && options.Width > 0 && options.Height > 0 { SetViewport(0, 0, int32(options.Width), int32(options.Height)) @@ -176,6 +205,60 @@ func Run(game Game, options *RunOptions) { } } +func initGoga(width, height int) { + // default camera + DefaultCamera = NewCamera(0, 0, width, height) + DefaultCamera.CalcRatio() + DefaultCamera.CalcOrtho() + + // default shader + shader, err := NewShader(default_shader_2d_vertex_src, default_shader_2d_fragment_src) + + if err != nil { + panic(err) + } + + Default2DShader = shader + Default2DShader.BindAttrib(Default_shader_2D_vertex_attrib) + Default2DShader.BindAttrib(Default_shader_2D_texcoord_attrib) + + // settings and registration + ClearColorBuffer(true) + EnableAlphaBlending(true) + AddLoader(&PngLoader{gl.LINEAR, false}) + AddSystem(NewSpriteRenderer(nil, nil, false)) +} + +func cleanup() { + // cleanup resources + log.Printf("Cleaning up %v resources", len(resources)) + + for _, res := range resources { + if drop, ok := res.(Dropable); ok { + drop.Drop() + } + } + + // cleanup systems + log.Printf("Cleaning up %v systems", len(systems)) + + for _, system := range systems { + system.Cleanup() + } + + // cleanup scenes + log.Printf("Cleaning up %v scenes", len(scenes)) + + for _, scene := range scenes { + scene.Cleanup() + } + + // cleanup default + log.Print("Cleaning up default resources") + + Default2DShader.Drop() +} + // Stops the game and closes the window. func Stop() { log.Print("Stopping main loop") @@ -202,10 +285,24 @@ func ClearDepthBuffer(do bool) { } } +// Enables/Disables alpha blending by source alpha channel. +// BLEND = SRC_ALPHA | ONE_MINUS_SRC_ALPHA +func EnableAlphaBlending(enable bool) { + if enable { + gl.Enable(gl.BLEND) + gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) + } else { + gl.Disable(gl.BLEND) + } +} + // Sets GL viewport. func SetViewport(x, y, width, height int32) { viewportWidth = int(width) viewportHeight = int(height) + DefaultCamera.SetViewport(int(x), int(y), viewportWidth, viewportHeight) + DefaultCamera.CalcRatio() + DefaultCamera.CalcOrtho() gl.Viewport(x, y, width, height) } @@ -232,33 +329,3 @@ func removeClearBuffer(buffer uint32) { } } } - -func initGoga() { - ClearColorBuffer(true) - AddLoader(&PngLoader{gl.LINEAR, false}) -} - -func cleanup() { - // cleanup resources - log.Print("Cleaning up resources") - - for _, res := range resources { - if drop, ok := res.(Dropable); ok { - drop.Drop() - } - } - - // cleanup systems - log.Print("Cleaning up systems") - - for _, system := range systems { - system.Cleanup() - } - - // cleanup scenes - log.Print("Cleaning up scenes") - - for _, scene := range scenes { - scene.Cleanup() - } -} diff --git a/gl_util.go b/gl_util.go index 86461b6..6f46f10 100644 --- a/gl_util.go +++ b/gl_util.go @@ -13,3 +13,72 @@ func CheckGLError() { log.Print(error) } } + +// Creates three VBOs for a 2D rectangle. +func CreateRectMesh(flip bool) (*VBO, *VBO, *VBO) { + ib, vb, tb := createIndexVertexTexCoordBuffer() + + indexData := []uint32{0, 1, 2, 1, 2, 3} + vertexData := []float32{0, 0, 1, 0, 0, 1, 1, 1} + texData := make([]float32, 8) + + if flip { + texData = []float32{0, 0, 1, 0, 0, 1, 1, 1} + } else { + texData = []float32{0, 1, 1, 1, 0, 0, 1, 0} + } + + ib.Fill(gl.Ptr(indexData), 4, 6, gl.STATIC_DRAW) + vb.Fill(gl.Ptr(vertexData), 4, 8, gl.STATIC_DRAW) + tb.Fill(gl.Ptr(texData), 4, 8, gl.STATIC_DRAW) + + return ib, vb, tb +} + +// Creates three VBOs for a 3D cube mesh. +// Texture coordinates won't map properly. +// This function is supposed to be used for 3D testing. +func CreateCubeMesh() (*VBO, *VBO, *VBO) { + ib, vb, tb := createIndexVertexTexCoordBuffer() + + indexData := []uint32{3, 1, 0, + 0, 2, 3, + 7, 5, 1, + 1, 3, 7, + 6, 4, 5, + 5, 7, 6, + 2, 0, 4, + 4, 6, 2, + 7, 3, 2, + 2, 6, 7, + 1, 5, 4, + 4, 0, 1} + vertexData := []float32{0, 0, 0, + 1, 0, 0, + 0, 1, 0, + 1, 1, 0, + 0, 0, 1, + 1, 0, 1, + 0, 1, 1, + 1, 1, 1} + texData := []float32{0, 0, + 1, 0, + 0, 1, + 1, 1, + 1, 1, + 0, 1, + 1, 0, + 0, 0} + + ib.Fill(gl.Ptr(indexData), 4, 36, gl.STATIC_DRAW) + vb.Fill(gl.Ptr(vertexData), 4, 24, gl.STATIC_DRAW) + tb.Fill(gl.Ptr(texData), 4, 12, gl.STATIC_DRAW) + + return ib, vb, tb +} + +func createIndexVertexTexCoordBuffer() (*VBO, *VBO, *VBO) { + return NewVBO(gl.ELEMENT_ARRAY_BUFFER), + NewVBO(gl.ARRAY_BUFFER), + NewVBO(gl.ARRAY_BUFFER) +} diff --git a/pos.go b/pos.go new file mode 100644 index 0000000..9cbdbc7 --- /dev/null +++ b/pos.go @@ -0,0 +1,85 @@ +package goga + +// Position component for 2D objects. +type Pos2D struct { + Pos, Size, Scale, RotPoint Vec2 + Rot float64 + Visible bool + M Mat3 +} + +// Creates a default initialized Pos2D. +func NewPos2D() *Pos2D { + m := Mat3{} + m.Identity() + return &Pos2D{Size: Vec2{1, 1}, Scale: Vec2{1, 1}, Visible: true, M: m} +} + +// Calculates model matrix for 2D positioning. +func (p *Pos2D) CalcModel() *Mat3 { + p.M.Identity() + p.M.Translate(Vec2{p.Pos.X + p.RotPoint.X, p.Pos.Y + p.RotPoint.Y}) + p.M.Rotate(p.Rot) + p.M.Translate(Vec2{-p.RotPoint.X, -p.RotPoint.Y}) + p.M.Scale(p.Size) + p.M.Scale(p.Scale) + + return &p.M +} + +// Returns the center of object. +// Assumes y = 0 is bottom left corner, if not you have to subtract height of object. +func (p *Pos2D) GetCenter() Vec2 { + return Vec2{p.Pos.X + (p.Size.X*p.Scale.X)/2, p.Pos.Y + (p.Size.Y*p.Scale.Y)/2} +} + +// Returns true when given point is within rectangle of this object. +func (p *Pos2D) PointInRect(point Vec2) bool { + return point.X > p.Pos.X && point.X < p.Pos.X+p.Size.X*p.Scale.X && point.Y > p.Pos.Y && point.Y < p.Pos.Y+p.Size.Y*p.Scale.Y +} + +// Position component for 3D objects +type Pos3D struct { + Pos, Size, Scale, RotPoint, Rot Vec3 + Visible bool + M Mat4 +} + +// Creates a default initialized Pos3D. +func NewPos3D() *Pos3D { + m := Mat4{} + m.Identity() + return &Pos3D{Size: Vec3{1, 1, 1}, Scale: Vec3{1, 1, 1}, Visible: true, M: m} +} + +// Calculates model matrix for 3D positioning. +func (p *Pos3D) CalcModel() *Mat4 { + p.M.Identity() + p.M.Translate(Vec3{p.Pos.X + p.RotPoint.X, p.Pos.Y + p.RotPoint.Y, p.Pos.Z + p.RotPoint.Z}) + p.M.Rotate(p.Rot.X, Vec3{1, 0, 0}) + p.M.Rotate(p.Rot.Y, Vec3{0, 1, 0}) + p.M.Rotate(p.Rot.Z, Vec3{0, 0, 1}) + p.M.Translate(Vec3{-p.RotPoint.X, -p.RotPoint.Y, -p.RotPoint.Z}) + p.M.Scale(p.Size) + p.M.Scale(p.Scale) + + return &p.M +} + +// Returns the center of object. +// Assumes y = 0 is bottom left corner, if not you have to subtract height of object. +func (p *Pos3D) GetCenter() Vec3 { + return Vec3{p.Pos.X + (p.Size.X*p.Scale.X)/2, p.Pos.Y + (p.Size.Y*p.Scale.Y)/2, p.Pos.Z + (p.Size.Z*p.Scale.Z)/2} +} + +// Centers given sprite within rectangle. +// Does nothing if sprite is nil. +// TODO +/*func CenterSprite(sprite *Sprite, x, y, width, height int) { + if sprite == nil { + return + } + + sprite.Pos.X = (float64(width-x) - sprite.Size.X) / 2 + sprite.Pos.Y = (float64(height-y) - sprite.Size.Y) / 2 +}*/ diff --git a/shader.go b/shader.go index b6e21c6..33129bc 100644 --- a/shader.go +++ b/shader.go @@ -3,6 +3,7 @@ package goga import ( "errors" "github.com/go-gl/gl/v4.5-core/gl" + "log" "strings" ) @@ -26,8 +27,9 @@ func NewShader(vertexShader, fragmentShader string) (*Shader, error) { shader.program = gl.CreateProgram() shader.vertex = gl.CreateShader(gl.VERTEX_SHADER) shader.fragment = gl.CreateShader(gl.FRAGMENT_SHADER) + CheckGLError() - if err := compileShader(&shader.vertex, vertexShader+NullTerminator); err != nil { + if err := compileShader(shader.vertex, vertexShader+NullTerminator); err != nil { gl.DeleteShader(shader.vertex) gl.DeleteShader(shader.fragment) shader.Drop() @@ -35,7 +37,7 @@ func NewShader(vertexShader, fragmentShader string) (*Shader, error) { return nil, err } - if err := compileShader(&shader.fragment, fragmentShader+NullTerminator); err != nil { + if err := compileShader(shader.fragment, fragmentShader+NullTerminator); err != nil { gl.DeleteShader(shader.vertex) gl.DeleteShader(shader.fragment) shader.Drop() @@ -54,21 +56,26 @@ func NewShader(vertexShader, fragmentShader string) (*Shader, error) { return nil, err } + CheckGLError() + // we don't need to keep them in memory gl.DetachShader(shader.program, shader.vertex) gl.DetachShader(shader.program, shader.fragment) gl.DeleteShader(shader.vertex) gl.DeleteShader(shader.fragment) + CheckGLError() return shader, nil } -func compileShader(shader *uint32, source string) error { - csrc := gl.Str(source) - gl.ShaderSource(*shader, 1, &csrc, nil) - gl.CompileShader(*shader) +func compileShader(shader uint32, source string) error { + log.Print("Compiling shader: " + source) + csrc, free := gl.Strs(source) + gl.ShaderSource(shader, 1, csrc, nil) + gl.CompileShader(shader) + free() - if err := shaderCheckError(*shader); err != nil { + if err := shaderCheckError(shader); err != nil { return err } diff --git a/shader/basic2D.fs b/shader/basic2D.fs new file mode 100644 index 0000000..71bd6c0 --- /dev/null +++ b/shader/basic2D.fs @@ -0,0 +1,12 @@ +#version 130 +precision highp float; + +uniform sampler2D tex; + +in vec2 tc; + +out vec4 color; + +void main(){ + color = texture(tex, tc); +} diff --git a/shader/basic2D.vs b/shader/basic2D.vs new file mode 100644 index 0000000..1076653 --- /dev/null +++ b/shader/basic2D.vs @@ -0,0 +1,13 @@ +#version 130 + +uniform mat3 o, m; + +in vec2 vertex; +in vec2 texCoord; + +out vec2 tc; + +void main(){ + tc = texCoord; + gl_Position = vec4(o*m*vec3(vertex, 1.0), 1.0); +} diff --git a/shader/basic3D.fs b/shader/basic3D.fs new file mode 100644 index 0000000..71bd6c0 --- /dev/null +++ b/shader/basic3D.fs @@ -0,0 +1,12 @@ +#version 130 +precision highp float; + +uniform sampler2D tex; + +in vec2 tc; + +out vec4 color; + +void main(){ + color = texture(tex, tc); +} diff --git a/shader/basic3D.vs b/shader/basic3D.vs new file mode 100644 index 0000000..1d23790 --- /dev/null +++ b/shader/basic3D.vs @@ -0,0 +1,13 @@ +#version 130 + +uniform mat4 pv, m; + +in vec3 vertex; +in vec2 texCoord; + +out vec2 tc; + +void main(){ + tc = texCoord; + gl_Position = pv*m*vec4(vertex, 1.0); +} diff --git a/shader/text.fs b/shader/text.fs new file mode 100644 index 0000000..5618a3a --- /dev/null +++ b/shader/text.fs @@ -0,0 +1,13 @@ +#version 130 +precision highp float; + +uniform sampler2D tex; +uniform vec4 color; + +in vec2 tc; + +out vec4 c; + +void main(){ + c = texture(tex, tc)*color; +} diff --git a/shader/text.vs b/shader/text.vs new file mode 100644 index 0000000..1076653 --- /dev/null +++ b/shader/text.vs @@ -0,0 +1,13 @@ +#version 130 + +uniform mat3 o, m; + +in vec2 vertex; +in vec2 texCoord; + +out vec2 tc; + +void main(){ + tc = texCoord; + gl_Position = vec4(o*m*vec3(vertex, 1.0), 1.0); +} diff --git a/sprite.go b/sprite.go new file mode 100644 index 0000000..c8dda50 --- /dev/null +++ b/sprite.go @@ -0,0 +1,163 @@ +package goga + +import ( + "github.com/go-gl/gl/v4.5-core/gl" +) + +const ( + sprite_renderer_name = "spriteRenderer" +) + +// Sprite is an actor having a 2D position and a texture. +type Sprite struct { + *Actor + *Pos2D + *Tex +} + +// The sprite renderer is a system rendering sprites. +// It has a 2D position componente, to move all sprites at once. +type SpriteRenderer struct { + Pos2D + + Shader *Shader + Camera *Camera + + sprites []Sprite + index, vertex, texCoord *VBO + vao *VAO +} + +// Creates a new sprite with given texture. +func NewSprite(tex *Tex) *Sprite { + sprite := &Sprite{} + sprite.Actor = NewActor() + sprite.Pos2D = NewPos2D() + sprite.Tex = tex + sprite.Size = Vec2{tex.GetSize().X, tex.GetSize().Y} + sprite.Scale = Vec2{1, 1} + sprite.Visible = true + + CheckGLError() + + return sprite +} + +// Creates a new sprite renderer using given shader and camera. +// If shader and/or camera are nil, the default one will be used. +func NewSpriteRenderer(shader *Shader, camera *Camera, flip bool) *SpriteRenderer { + if shader == nil { + shader = Default2DShader + } + + if camera == nil { + camera = DefaultCamera + } + + renderer := &SpriteRenderer{} + renderer.Shader = shader + renderer.Camera = camera + renderer.sprites = make([]Sprite, 0) + renderer.index, renderer.vertex, renderer.texCoord = CreateRectMesh(flip) + renderer.Size = Vec2{1, 1} + renderer.Scale = Vec2{1, 1} + + renderer.vao = NewVAO() + renderer.vao.Bind() + renderer.Shader.EnableVertexAttribArrays() + renderer.index.Bind() + renderer.vertex.Bind() + renderer.vertex.AttribPointer(shader.GetAttribLocation(Default_shader_2D_vertex_attrib), 2, gl.FLOAT, false, 0) + renderer.texCoord.Bind() + renderer.texCoord.AttribPointer(shader.GetAttribLocation(Default_shader_2D_texcoord_attrib), 2, gl.FLOAT, false, 0) + renderer.vao.Unbind() + + CheckGLError() + + return renderer +} + +// Frees recources created by sprite renderer. +// This is called automatically when system gets removed. +func (s *SpriteRenderer) Cleanup() { + s.index.Drop() + s.vertex.Drop() + s.texCoord.Drop() + s.vao.Drop() +} + +// Adds sprite to the renderer. +func (s *SpriteRenderer) Add(actor interface{}) bool { + sprite, ok := actor.(*Sprite) + + if !ok { + return false + } + + s.sprites = append(s.sprites, *sprite) + + return true +} + +// Removes sprite from renderer. +func (s *SpriteRenderer) Remove(actor interface{}) bool { + sprite, ok := actor.(Sprite) + + if !ok { + return false + } + + for i, sp := range s.sprites { + if sp == sprite { + s.sprites = append(s.sprites[:i], s.sprites[i+1:]...) + return true + } + } + + return false +} + +// Removes sprite from renderer by ID. +func (s *SpriteRenderer) RemoveById(id ActorId) bool { + for i, sprite := range s.sprites { + if sprite.Actor.GetId() == id { + s.sprites = append(s.sprites[:i], s.sprites[i+1:]...) + return true + } + } + + return false +} + +// Removes all sprites from renderer. +func (s *SpriteRenderer) RemoveAll() { + s.sprites = make([]Sprite, 0) +} + +// Returns number of sprites. +func (s *SpriteRenderer) Len() int { + return len(s.sprites) +} + +func (s *SpriteRenderer) GetName() string { + return sprite_renderer_name +} + +// Renders sprites. +func (s *SpriteRenderer) Update(delta float64) { + s.Shader.Bind() + s.Shader.SendMat3(Default_shader_2D_ortho, *MultMat3(s.Camera.CalcOrtho(), s.CalcModel())) + s.Shader.SendUniform1i(Default_shader_2D_tex, 0) + s.vao.Bind() + + for i := range s.sprites { + if !s.sprites[i].Visible { + continue + } + + s.Shader.SendMat3(Default_shader_2D_model, *s.sprites[i].CalcModel()) + s.sprites[i].Tex.Bind() + + gl.DrawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, nil) + } +} diff --git a/system.go b/system.go index ac274b8..dd5c38d 100644 --- a/system.go +++ b/system.go @@ -10,8 +10,8 @@ import () type System interface { Update(float64) Cleanup() - Add(*Actor) bool - Remove(*Actor) bool + Add(interface{}) bool + Remove(interface{}) bool RemoveById(ActorId) bool RemoveAll() Len() int