Compare commits

...

18 Commits

25 changed files with 888 additions and 329 deletions

View File

@ -1,4 +1,4 @@
package asserts
package assert
import (
"github.com/bloeys/nmage/consts"

View File

@ -5,23 +5,28 @@ var (
TexturePaths map[string]uint32 = make(map[string]uint32)
)
func SetTexture(t Texture) {
func AddTextureToCache(t Texture) {
if _, ok := TexturePaths[t.Path]; ok {
if t.Path != "" {
if _, ok := TexturePaths[t.Path]; ok {
return
}
println("Loaded texture from path:", t.Path)
Textures[t.TexID] = t
TexturePaths[t.Path] = t.TexID
return
}
println("Loaded texture:", t.Path)
Textures[t.TexID] = t
println("Loaded in-mem texture with ID:", t.TexID)
TexturePaths[t.Path] = t.TexID
}
func GetTexture(texID uint32) (Texture, bool) {
func GetTextureFromCacheID(texID uint32) (Texture, bool) {
tex, ok := Textures[texID]
return tex, ok
}
func GetTexturePath(path string) (Texture, bool) {
func GetTextureFromCachePath(path string) (Texture, bool) {
tex, ok := Textures[TexturePaths[path]]
return tex, ok
}

View File

@ -2,6 +2,7 @@ package assets
import (
"bytes"
"image"
"image/color"
"image/png"
"os"
@ -17,6 +18,7 @@ const (
)
type Texture struct {
//Path only exists for textures loaded from disk
Path string
TexID uint32
Width int32
@ -24,10 +26,22 @@ type Texture struct {
Pixels []byte
}
func LoadPNGTexture(file string) (Texture, error) {
type TextureLoadOptions struct {
TryLoadFromCache bool
WriteToCache bool
GenMipMaps bool
}
if tex, ok := GetTexturePath(file); ok {
return tex, nil
func LoadTexturePNG(file string, loadOptions *TextureLoadOptions) (Texture, error) {
if loadOptions == nil {
loadOptions = &TextureLoadOptions{}
}
if loadOptions.TryLoadFromCache {
if tex, ok := GetTextureFromCachePath(file); ok {
return tex, nil
}
}
//Load from disk
@ -72,14 +86,72 @@ func LoadPNGTexture(file string) (Texture, error) {
// set the texture wrapping/filtering options (on the currently bound texture object)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
// load and generate the texture
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0]))
gl.GenerateMipmap(gl.TEXTURE_2D)
SetTexture(tex)
if loadOptions.GenMipMaps {
gl.GenerateMipmap(tex.TexID)
}
if loadOptions.WriteToCache {
AddTextureToCache(tex)
}
return tex, nil
}
func LoadTextureInMemImg(img image.Image, loadOptions *TextureLoadOptions) (Texture, error) {
if loadOptions == nil {
loadOptions = &TextureLoadOptions{}
}
tex := Texture{
Width: int32(img.Bounds().Dx()),
Height: int32(img.Bounds().Dy()),
Pixels: make([]byte, img.Bounds().Dx()*img.Bounds().Dy()*4),
}
//NOTE: Load bottom left to top right because this is the texture coordinate system used by OpenGL
//NOTE: We only support 8-bit channels (32-bit colors) for now
i := 0
for y := img.Bounds().Dy() - 1; y >= 0; y-- {
for x := 0; x < img.Bounds().Dx(); x++ {
c := color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA)
tex.Pixels[i] = c.R
tex.Pixels[i+1] = c.G
tex.Pixels[i+2] = c.B
tex.Pixels[i+3] = c.A
i += 4
}
}
//Prepare opengl stuff
gl.GenTextures(1, &tex.TexID)
gl.BindTexture(gl.TEXTURE_2D, tex.TexID)
// set the texture wrapping/filtering options (on the currently bound texture object)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
// load and generate the texture
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0]))
if loadOptions.GenMipMaps {
gl.GenerateMipmap(tex.TexID)
}
if loadOptions.WriteToCache {
AddTextureToCache(tex)
}
return tex, nil
}

View File

@ -3,7 +3,7 @@ package buffers
import (
"fmt"
"github.com/bloeys/nmage/asserts"
"github.com/bloeys/nmage/assert"
"github.com/go-gl/gl/v4.1-core/gl"
)
@ -28,6 +28,6 @@ func (b BufUsage) ToGL() uint32 {
return gl.STREAM_DRAW
}
asserts.T(false, fmt.Sprintf("Unexpected BufUsage value '%v'", b))
assert.T(false, fmt.Sprintf("Unexpected BufUsage value '%v'", b))
return 0
}

View File

@ -30,7 +30,28 @@ func (b *Buffer) SetData(values []float32) {
gl.BindVertexArray(b.VAOID)
gl.BindBuffer(gl.ARRAY_BUFFER, b.BufID)
gl.BufferData(gl.ARRAY_BUFFER, len(values)*4, gl.Ptr(values), BufUsage_Static.ToGL())
sizeInBytes := len(values) * 4
if sizeInBytes == 0 {
gl.BufferData(gl.ARRAY_BUFFER, 0, gl.Ptr(nil), BufUsage_Static.ToGL())
} else {
gl.BufferData(gl.ARRAY_BUFFER, sizeInBytes, gl.Ptr(&values[0]), BufUsage_Static.ToGL())
}
gl.BindVertexArray(0)
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
}
func (b *Buffer) SetDataWithUsage(values []float32, usage BufUsage) {
gl.BindVertexArray(b.VAOID)
gl.BindBuffer(gl.ARRAY_BUFFER, b.BufID)
sizeInBytes := len(values) * 4
if sizeInBytes == 0 {
gl.BufferData(gl.ARRAY_BUFFER, 0, gl.Ptr(nil), usage.ToGL())
} else {
gl.BufferData(gl.ARRAY_BUFFER, sizeInBytes, gl.Ptr(&values[0]), usage.ToGL())
}
gl.BindVertexArray(0)
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
@ -42,7 +63,12 @@ func (b *Buffer) SetIndexBufData(values []uint32) {
gl.BindVertexArray(b.VAOID)
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, b.IndexBufID)
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(values)*4, gl.Ptr(values), BufUsage_Static.ToGL())
sizeInBytes := len(values) * 4
if sizeInBytes == 0 {
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, 0, gl.Ptr(nil), BufUsage_Static.ToGL())
} else {
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, sizeInBytes, gl.Ptr(&values[0]), BufUsage_Static.ToGL())
}
gl.BindVertexArray(0)
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
@ -54,6 +80,8 @@ func (b *Buffer) GetLayout() []Element {
return e
}
//SetLayout updates the layout object and the corresponding vertex attributes.
//Vertex attributes are also enabled.
func (b *Buffer) SetLayout(layout ...Element) {
b.layout = layout
@ -64,6 +92,20 @@ func (b *Buffer) SetLayout(layout ...Element) {
b.layout[i].Offset = int(b.Stride)
b.Stride += b.layout[i].Size()
}
//Set opengl stuff
b.Bind()
//NOTE: VBOs are only bound at 'VertexAttribPointer', not BindBUffer, so we need to bind the buffer and vao here
gl.BindBuffer(gl.ARRAY_BUFFER, b.BufID)
for i := 0; i < len(layout); i++ {
gl.EnableVertexAttribArray(uint32(i))
gl.VertexAttribPointer(uint32(i), layout[i].ElementType.CompCount(), layout[i].ElementType.GLType(), false, b.Stride, gl.PtrOffset(layout[i].Offset))
}
b.UnBind()
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
}
func NewBuffer(layout ...Element) Buffer {

View File

@ -3,7 +3,7 @@ package buffers
import (
"fmt"
"github.com/bloeys/nmage/asserts"
"github.com/bloeys/nmage/assert"
"github.com/go-gl/gl/v4.1-core/gl"
)
@ -45,7 +45,7 @@ func (dt ElementType) GLType() uint32 {
return gl.FLOAT
default:
asserts.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
assert.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
return 0
}
}
@ -68,7 +68,7 @@ func (dt ElementType) CompSize() int32 {
return 4
default:
asserts.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
assert.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
return 0
}
}
@ -92,7 +92,7 @@ func (dt ElementType) CompCount() int32 {
return 4
default:
asserts.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
assert.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
return 0
}
}
@ -116,7 +116,7 @@ func (dt ElementType) Size() int32 {
return 4 * 4
default:
asserts.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
assert.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
return 0
}
}

100
camera/camera.go Executable file
View File

@ -0,0 +1,100 @@
package camera
import (
"github.com/bloeys/gglm/gglm"
)
type Type int32
const (
Type_Unknown Type = iota
Type_Perspective
Type_Orthographic
)
type Camera struct {
Type Type
Pos gglm.Vec3
Forward gglm.Vec3
WorldUp gglm.Vec3
NearClip float32
FarClip float32
// Perspective data
Fov float32
AspectRatio float32
// Ortho data
Left, Right, Top, Bottom float32
// Matrices
ViewMat gglm.Mat4
ProjMat gglm.Mat4
}
// Update recalculates view matrix and projection matrix.
// Should be called whenever a camera parameter changes
func (c *Camera) Update() {
c.ViewMat = gglm.LookAtRH(&c.Pos, c.Pos.Clone().Add(&c.Forward), &c.WorldUp).Mat4
if c.Type == Type_Perspective {
c.ProjMat = *gglm.Perspective(c.Fov, c.AspectRatio, c.NearClip, c.FarClip)
} else {
c.ProjMat = gglm.Ortho(c.Left, c.Right, c.Top, c.Bottom, c.NearClip, c.FarClip).Mat4
}
}
// UpdateRotation calculates a new forward vector and then calls camera.Update()
func (c *Camera) UpdateRotation(pitch, yaw float32) {
dir := gglm.NewVec3(
gglm.Cos32(yaw)*gglm.Cos32(pitch),
gglm.Sin32(pitch),
gglm.Sin32(yaw)*gglm.Cos32(pitch),
)
c.Forward = *dir.Normalize()
c.Update()
}
func NewPerspective(pos, forward, worldUp *gglm.Vec3, nearClip, farClip, fovRadians, aspectRatio float32) *Camera {
cam := &Camera{
Type: Type_Perspective,
Pos: *pos,
Forward: *forward,
WorldUp: *worldUp,
NearClip: nearClip,
FarClip: farClip,
Fov: fovRadians,
AspectRatio: aspectRatio,
}
cam.Update()
return cam
}
func NewOrthographic(pos, forward, worldUp *gglm.Vec3, nearClip, farClip, left, right, top, bottom float32) *Camera {
cam := &Camera{
Type: Type_Orthographic,
Pos: *pos,
Forward: *forward,
WorldUp: *worldUp,
NearClip: nearClip,
FarClip: farClip,
Left: left,
Right: right,
Top: top,
Bottom: bottom,
}
cam.Update()
return cam
}

View File

@ -3,6 +3,7 @@ package engine
import (
"runtime"
"github.com/bloeys/nmage/assert"
"github.com/bloeys/nmage/input"
"github.com/bloeys/nmage/renderer"
"github.com/bloeys/nmage/timing"
@ -11,6 +12,10 @@ import (
"github.com/veandco/go-sdl2/sdl"
)
var (
isInited = false
)
type Window struct {
SDLWin *sdl.Window
GlCtx sdl.GLContext
@ -96,6 +101,8 @@ func (w *Window) Destroy() error {
func Init() error {
isInited = true
runtime.LockOSThread()
timing.Init()
err := initSDL()
@ -139,6 +146,7 @@ func CreateOpenGLWindowCentered(title string, width, height int32, flags WindowF
func createWindow(title string, x, y, width, height int32, flags WindowFlags, rend renderer.Render) (*Window, error) {
assert.T(isInited, "engine.Init was not called!")
if x == -1 && y == -1 {
x = sdl.WINDOWPOS_CENTERED
y = sdl.WINDOWPOS_CENTERED
@ -186,6 +194,8 @@ func initOpenGL() error {
}
func SetVSync(enabled bool) {
assert.T(isInited, "engine.Init was not called!")
if enabled {
sdl.GLSetSwapInterval(1)
} else {

View File

@ -6,37 +6,32 @@ import (
"github.com/go-gl/gl/v4.1-core/gl"
)
var (
isRunning = false
)
type Game interface {
Init()
Start()
FrameStart()
Update()
Render()
FrameEnd()
ShouldRun() bool
GetWindow() *Window
GetImGUI() nmageimgui.ImguiInfo
Deinit()
DeInit()
}
func Run(g Game) {
w := g.GetWindow()
ui := g.GetImGUI()
func Run(g Game, w *Window, ui nmageimgui.ImguiInfo) {
isRunning = true
g.Init()
//Simulate an imgui frame during init so any imgui calls are allowed within init
tempWidth, tempHeight := w.SDLWin.GetSize()
tempFBWidth, tempFBHeight := w.SDLWin.GLGetDrawableSize()
ui.FrameStart(float32(tempWidth), float32(tempHeight))
g.Start()
ui.Render(float32(tempWidth), float32(tempHeight), tempFBWidth, tempFBHeight)
for g.ShouldRun() {
for isRunning {
//PERF: Cache these
width, height := w.SDLWin.GetSize()
@ -46,8 +41,6 @@ func Run(g Game) {
w.handleInputs()
ui.FrameStart(float32(width), float32(height))
g.FrameStart()
g.Update()
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
@ -60,5 +53,9 @@ func Run(g Game) {
timing.FrameEnded()
}
g.Deinit()
g.DeInit()
}
func Quit() {
isRunning = false
}

77
entity/entity.go Executable file
View File

@ -0,0 +1,77 @@
package entity
type EntityFlag byte
const (
EntityFlag_Unknown EntityFlag = 0
EntityFlag_Dead EntityFlag = 1 << (iota - 1)
EntityFlag_Alive
)
const (
GenerationShiftBits = 64 - 8
FlagsShiftBits = 64 - 16
IndexBitMask = 0x00_00_FFFF_FFFF_FFFF
)
type Entity struct {
// Byte 1: Generation; Byte 2: Flags; Bytes 3-8: Index
ID uint64
Comps []Comp
}
func GetGeneration(id uint64) byte {
return byte(id >> GenerationShiftBits)
}
func GetFlags(id uint64) byte {
return byte(id >> FlagsShiftBits)
}
func GetIndex(id uint64) uint64 {
return id & IndexBitMask
}
func (e *Entity) HasFlag(ef EntityFlag) bool {
return GetFlags(e.ID)&byte(ef) > 0
}
func NewEntityId(generation, flags byte, index uint64) uint64 {
return index | (uint64(generation) << GenerationShiftBits) | (uint64(flags) << FlagsShiftBits)
}
type Comp interface {
Name() string
}
func AddComp(e *Entity, c Comp) {
e.Comps = append(e.Comps, c)
}
func GetComp[T Comp](e *Entity) (out T) {
for i := 0; i < len(e.Comps); i++ {
comp, ok := e.Comps[i].(T)
if ok {
return comp
}
}
return out
}
func GetAllCompOfType[T Comp](e *Entity) (out []T) {
out = []T{}
for i := 0; i < len(e.Comps); i++ {
comp, ok := e.Comps[i].(T)
if ok {
out = append(out, comp)
}
}
return out
}

94
entity/registry.go Executable file
View File

@ -0,0 +1,94 @@
package entity
import (
"github.com/bloeys/nmage/assert"
)
type freeListitem struct {
EntityIndex uint64
nextFree *freeListitem
}
type Registry struct {
EntityCount uint64
Entities []Entity
FreeList *freeListitem
}
func (r *Registry) NewEntity() *Entity {
assert.T(r.EntityCount < uint64(len(r.Entities)), "Can not add more entities to registry because it is full")
entityToUseIndex := uint64(0)
var entityToUse *Entity = nil
if r.FreeList != nil {
entityToUseIndex = r.FreeList.EntityIndex
entityToUse = &r.Entities[entityToUseIndex]
r.FreeList = r.FreeList.nextFree
} else {
for i := 0; i < len(r.Entities); i++ {
e := &r.Entities[i]
if GetFlags(e.ID) != byte(EntityFlag_Unknown) && !e.HasFlag(EntityFlag_Dead) {
continue
}
entityToUse = e
entityToUseIndex = uint64(i)
break
}
}
if entityToUse == nil {
panic("failed to create new entity because we did not find a free spot in the registry. Why did the assert not go off?")
}
r.EntityCount++
entityToUse.ID = NewEntityId(GetGeneration(entityToUse.ID)+1, byte(EntityFlag_Alive), entityToUseIndex)
assert.T(entityToUse.ID != 0, "Entity ID must not be zero")
return entityToUse
}
func (r *Registry) GetEntity(id uint64) *Entity {
index := GetIndex(id)
gen := GetGeneration(id)
e := &r.Entities[index]
eGen := GetGeneration(e.ID)
if gen != eGen {
return nil
}
return e
}
func (r *Registry) FreeEntity(id uint64) {
e := r.GetEntity(id)
if e == nil {
return
}
r.EntityCount--
eIndex := GetIndex(e.ID)
e.Comps = []Comp{}
e.ID = NewEntityId(GetGeneration(e.ID), byte(EntityFlag_Dead), eIndex)
r.FreeList = &freeListitem{
EntityIndex: eIndex,
nextFree: r.FreeList,
}
}
func NewRegistry(size uint32) *Registry {
assert.T(size > 0, "Registry size must be more than zero")
return &Registry{
Entities: make([]Entity, size),
}
}

10
go.mod
View File

@ -1,13 +1,13 @@
module github.com/bloeys/nmage
go 1.17
go 1.18
require github.com/veandco/go-sdl2 v0.4.10
require github.com/veandco/go-sdl2 v0.4.25
require github.com/go-gl/gl v0.0.0-20211025173605-bda47ffaa784
require github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6
require (
github.com/bloeys/assimp-go v0.4.2
github.com/bloeys/gglm v0.3.1
github.com/inkyblackness/imgui-go/v4 v4.3.0
github.com/bloeys/gglm v0.42.0
github.com/inkyblackness/imgui-go/v4 v4.6.0
)

16
go.sum
View File

@ -1,17 +1,17 @@
github.com/bloeys/assimp-go v0.4.2 h1:ArVK74BCFcTO/rCGj2NgZG9xtbjnJdEn5npIeJx1Z04=
github.com/bloeys/assimp-go v0.4.2/go.mod h1:my3yRxT7CfOztmvi+0svmwbaqw0KFrxaHxncoyaEIP0=
github.com/bloeys/gglm v0.3.1 h1:Sy9upW7SBsBfDXrSmEhid3aQ+7J7itej+upwcxOnPMQ=
github.com/bloeys/gglm v0.3.1/go.mod h1:qwJQ0WzV191wAMwlGicbfbChbKoSedMk7gFFX6GnyOk=
github.com/bloeys/gglm v0.42.0 h1:UAUFGTaZv3dpZ0YSIQVum3bdeCZgNmx965VLnD2v11k=
github.com/bloeys/gglm v0.42.0/go.mod h1:qwJQ0WzV191wAMwlGicbfbChbKoSedMk7gFFX6GnyOk=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-gl/gl v0.0.0-20211025173605-bda47ffaa784 h1:1Zi56D0LNfvkzM+BdoxKryvUEdyWO7LP8oRT+oSYJW0=
github.com/go-gl/gl v0.0.0-20211025173605-bda47ffaa784/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
github.com/inkyblackness/imgui-go/v4 v4.3.0 h1:iyAzqWXq/dG5+6ckDPhGivtrIo6AywGQMvENKzun04s=
github.com/inkyblackness/imgui-go/v4 v4.3.0/go.mod h1:g8SAGtOYUP7rYaOB2AsVKCEHmPMDmJKgt4z6d+flhb0=
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk=
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
github.com/inkyblackness/imgui-go/v4 v4.6.0 h1:ShcnXEYl80+xREGBY9OpGWePA6FfJChY9Varsm+3jjE=
github.com/inkyblackness/imgui-go/v4 v4.6.0/go.mod h1:g8SAGtOYUP7rYaOB2AsVKCEHmPMDmJKgt4z6d+flhb0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/veandco/go-sdl2 v0.4.10 h1:8QoD2bhWl7SbQDflIAUYWfl9Vq+mT8/boJFAUzAScgY=
github.com/veandco/go-sdl2 v0.4.10/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
github.com/veandco/go-sdl2 v0.4.25 h1:J5ac3KKOccp/0xGJA1PaNYKPUcZm19IxhDGs8lJofPI=
github.com/veandco/go-sdl2 v0.4.25/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=

20
level/level.go Executable file
View File

@ -0,0 +1,20 @@
package level
import (
"github.com/bloeys/nmage/assert"
"github.com/bloeys/nmage/entity"
)
type Level struct {
*entity.Registry
Name string
}
func NewLevel(name string, maxEntities uint32) *Level {
assert.T(name != "", "Level name can not be empty")
return &Level{
Name: name,
Registry: entity.NewRegistry(maxEntities),
}
}

376
main.go
View File

@ -6,8 +6,11 @@ import (
"github.com/bloeys/assimp-go/asig"
"github.com/bloeys/gglm/gglm"
"github.com/bloeys/nmage/assets"
"github.com/bloeys/nmage/camera"
"github.com/bloeys/nmage/engine"
"github.com/bloeys/nmage/entity"
"github.com/bloeys/nmage/input"
"github.com/bloeys/nmage/level"
"github.com/bloeys/nmage/logging"
"github.com/bloeys/nmage/materials"
"github.com/bloeys/nmage/meshes"
@ -18,179 +21,91 @@ import (
"github.com/veandco/go-sdl2/sdl"
)
//BUGS:
// need to rebind the texutre if the texture (or any material values) change between draw calls
// DT handling for when it is zero is wrong! (Gives 1000 DT)
// @Todo:
// Complete entity registry (e.g. HasEntity, GetEntity, Generational Indices etc...)
// Helper functions to update active entities
//TODO: Tasks:
// Build simple game
// Integrate physx
// Entities and components
// Camera class
//Low Priority:
// Create VAO struct independent from VBO to support multi-VBO use cases (e.g. instancing)
// Renderer batching
// Scene graph
// Separate engine loop from rendering loop? or leave it to the user?
// Abstract keys enum away from sdl
// Proper Asset loading
// Audio
// Frustum culling
// Material system editor with fields automatically extracted from the shader
const (
camSpeed = 15
mouseSensitivity = 0.5
)
var (
isRunning bool = true
window *engine.Window
window *engine.Window
pitch float32 = 0
yaw float32 = -90
cam *camera.Camera
simpleMat *materials.Material
cubeMesh *meshes.Mesh
modelMat = gglm.NewTrMatId()
projMat = &gglm.Mat4{}
camPos = gglm.NewVec3(0, 0, -10)
camForward = gglm.NewVec3(0, 0, 1)
cubeModelMat = gglm.NewTrMatId()
lightPos1 = gglm.NewVec3(2, 2, 0)
lightPos1 = gglm.NewVec3(-2, 0, 2)
lightColor1 = gglm.NewVec3(1, 1, 1)
)
type OurGame struct {
Win *engine.Window
ImGUIInfo nmageimgui.ImguiInfo
Quitting bool
}
func (g *OurGame) Init() {
//Create materials
simpleMat = materials.NewMaterial("Simple Mat", "./res/shaders/simple")
//Load meshes
var err error
cubeMesh, err = meshes.NewMesh("Cube", "./res/models/tex-cube.fbx", asig.PostProcess(0))
if err != nil {
logging.ErrLog.Fatalln("Failed to load cube mesh. Err: ", err)
}
//Load textures
tex, err := assets.LoadPNGTexture("./res/textures/Low poly planet.png")
if err != nil {
logging.ErrLog.Fatalln("Failed to load texture. Err: ", err)
}
//Configure material
simpleMat.DiffuseTex = tex.TexID
simpleMat.SetAttribute(cubeMesh.Buf)
//Movement, scale and rotation
translationMat := gglm.NewTranslationMat(gglm.NewVec3(0, 0, 0))
scaleMat := gglm.NewScaleMat(gglm.NewVec3(0.25, 0.25, 0.25))
rotMat := gglm.NewRotMat(gglm.NewQuatEuler(gglm.NewVec3(0, 0, 0).AsRad()))
modelMat.Mul(translationMat.Mul(rotMat.Mul(scaleMat)))
simpleMat.SetUnifMat4("modelMat", &modelMat.Mat4)
//Moves objects into the cameras view
updateViewMat()
//Perspective/Depth
projMat := gglm.Perspective(45*gglm.Deg2Rad, float32(1280)/float32(720), 0.1, 500)
simpleMat.SetUnifMat4("projMat", projMat)
//Lights
simpleMat.SetUnifVec3("lightPos1", lightPos1)
simpleMat.SetUnifVec3("lightColor1", lightColor1)
type TransformComp struct {
Pos *gglm.Vec3
Rot *gglm.Quat
Scale *gglm.Vec3
}
func (g *OurGame) Start() {
func (t TransformComp) Name() string {
return "Transform Component"
}
func (g *OurGame) FrameStart() {
}
func Test() {
func (g *OurGame) Update() {
lvl := level.NewLevel("test level", 1000)
e1 := lvl.Registry.NewEntity()
if input.IsQuitClicked() {
g.Quitting = true
}
trComp := entity.GetComp[*TransformComp](e1)
fmt.Println("Got comp 1:", trComp)
winWidth, winHeight := g.Win.SDLWin.GetSize()
projMat = gglm.Perspective(45*gglm.Deg2Rad, float32(winWidth)/float32(winHeight), 0.1, 20)
simpleMat.SetUnifMat4("projMat", projMat)
e1.Comps = append(e1.Comps, &TransformComp{
Pos: gglm.NewVec3(0, 0, 0),
Rot: gglm.NewQuatEulerXYZ(0, 0, 0),
Scale: gglm.NewVec3(0, 0, 0),
}, &TransformComp{
Pos: gglm.NewVec3(0, 0, 0),
Rot: gglm.NewQuatEulerXYZ(0, 0, 0),
Scale: gglm.NewVec3(1, 1, 1),
})
//Camera movement
var camSpeed float32 = 15
if input.KeyDown(sdl.K_w) {
camPos.Data[1] += camSpeed * timing.DT()
updateViewMat()
}
if input.KeyDown(sdl.K_s) {
camPos.Data[1] -= camSpeed * timing.DT()
updateViewMat()
}
if input.KeyDown(sdl.K_d) {
camPos.Data[0] += camSpeed * timing.DT()
updateViewMat()
}
if input.KeyDown(sdl.K_a) {
camPos.Data[0] -= camSpeed * timing.DT()
updateViewMat()
}
trComp = entity.GetComp[*TransformComp](e1)
fmt.Println("Got comp 2:", trComp)
if input.GetMouseWheelYNorm() > 0 {
camPos.Data[2] += 1
updateViewMat()
} else if input.GetMouseWheelYNorm() < 0 {
camPos.Data[2] -= 1
updateViewMat()
}
trComps := entity.GetAllCompOfType[*TransformComp](e1)
fmt.Printf("Got comp 3: %+v, %+v\n", trComps[0], trComps[1])
//Rotating cubes
if input.KeyDown(sdl.K_SPACE) {
modelMat.Rotate(10*timing.DT()*gglm.Deg2Rad, gglm.NewVec3(1, 1, 1).Normalize())
simpleMat.SetUnifMat4("modelMat", &modelMat.Mat4)
}
imgui.DragFloat3("Cam Pos", &camPos.Data)
}
func (g *OurGame) Render() {
tempModelMat := modelMat.Clone()
rowSize := 100
for y := 0; y < rowSize; y++ {
for x := 0; x < rowSize; x++ {
tempModelMat.Translate(gglm.NewVec3(-1, 0, 0))
window.Rend.Draw(cubeMesh, tempModelMat, simpleMat)
}
tempModelMat.Translate(gglm.NewVec3(float32(rowSize), -1, 0))
}
g.GetWindow().SDLWin.SetTitle(fmt.Sprint("nMage (", timing.GetAvgFPS(), " fps)"))
}
func (g *OurGame) FrameEnd() {
}
func (g *OurGame) ShouldRun() bool {
return !g.Quitting
}
func (g *OurGame) GetWindow() *engine.Window {
return g.Win
}
func (g *OurGame) GetImGUI() nmageimgui.ImguiInfo {
return g.ImGUIInfo
}
func (g *OurGame) Deinit() {
g.Win.Destroy()
fmt.Printf("Entity: %+v\n", e1)
fmt.Printf("Entity: %+v\n", lvl.Registry.NewEntity())
fmt.Printf("Entity: %+v\n", lvl.Registry.NewEntity())
fmt.Printf("Entity: %+v\n", lvl.Registry.NewEntity())
}
func main() {
// Test()
// return
//Init engine
err := engine.Init()
if err != nil {
@ -210,13 +125,194 @@ func main() {
Win: window,
ImGUIInfo: nmageimgui.NewImGUI(),
}
window.EventCallbacks = append(window.EventCallbacks, game.handleWindowEvents)
engine.Run(game)
return
engine.Run(game, window, game.ImGUIInfo)
}
func (g *OurGame) handleWindowEvents(e sdl.Event) {
switch e := e.(type) {
case *sdl.WindowEvent:
if e.Event == sdl.WINDOWEVENT_SIZE_CHANGED {
width := e.Data1
height := e.Data2
cam.AspectRatio = float32(width) / float32(height)
cam.Update()
simpleMat.SetUnifMat4("projMat", &cam.ProjMat)
}
}
}
func (g *OurGame) Init() {
//Create materials
simpleMat = materials.NewMaterial("Simple Mat", "./res/shaders/simple.glsl")
//Load meshes
var err error
cubeMesh, err = meshes.NewMesh("Cube", "./res/models/tex-cube.fbx", asig.PostProcess(0))
if err != nil {
logging.ErrLog.Fatalln("Failed to load cube mesh. Err: ", err)
}
//Load textures
tex, err := assets.LoadTexturePNG("./res/textures/Low poly planet.png", nil)
if err != nil {
logging.ErrLog.Fatalln("Failed to load texture. Err: ", err)
}
//Configure material
simpleMat.DiffuseTex = tex.TexID
//Movement, scale and rotation
translationMat := gglm.NewTranslationMat(gglm.NewVec3(0, 0, 0))
scaleMat := gglm.NewScaleMat(gglm.NewVec3(0.25, 0.25, 0.25))
rotMat := gglm.NewRotMat(gglm.NewQuatEuler(gglm.NewVec3(0, 0, 0).AsRad()))
cubeModelMat.Mul(translationMat.Mul(rotMat.Mul(scaleMat)))
simpleMat.SetUnifMat4("modelMat", &cubeModelMat.Mat4)
// Camera
winWidth, winHeight := g.Win.SDLWin.GetSize()
cam = camera.NewPerspective(
gglm.NewVec3(0, 0, 10),
gglm.NewVec3(0, 0, -1),
gglm.NewVec3(0, 1, 0),
0.1, 20,
45*gglm.Deg2Rad,
float32(winWidth)/float32(winHeight),
)
simpleMat.SetUnifMat4("projMat", &cam.ProjMat)
updateViewMat()
//Lights
simpleMat.SetUnifVec3("lightPos1", lightPos1)
simpleMat.SetUnifVec3("lightColor1", lightColor1)
}
// @TODO: Add this to gglm
// func vecRotByQuat(v *gglm.Vec3, q *gglm.Quat) *gglm.Vec3 {
// // Reference: https://gamedev.stackexchange.com/questions/28395/rotating-vector3-by-a-quaternion
// qVec := gglm.NewVec3(q.X(), q.Y(), q.Z())
// rotatedVec := qVec.Clone().Scale(2 * gglm.DotVec3(v, qVec))
// t1 := q.W()*q.W() - gglm.DotVec3(qVec, qVec)
// rotatedVec.Add(v.Clone().Scale(t1))
// rotatedVec.Add(gglm.Cross(qVec, v).Scale(2 * q.W()))
// return rotatedVec
// }
func (g *OurGame) Update() {
if input.IsQuitClicked() || input.KeyClicked(sdl.K_ESCAPE) {
engine.Quit()
}
g.updateCameraLookAround()
g.updateCameraPos()
//Rotating cubes
if input.KeyDown(sdl.K_SPACE) {
cubeModelMat.Rotate(10*timing.DT()*gglm.Deg2Rad, gglm.NewVec3(1, 1, 1).Normalize())
simpleMat.SetUnifMat4("modelMat", &cubeModelMat.Mat4)
}
if imgui.DragFloat3("Cam Pos", &cam.Pos.Data) {
updateViewMat()
}
if imgui.DragFloat3("Cam Forward", &cam.Forward.Data) {
updateViewMat()
}
if input.KeyClicked(sdl.K_F4) {
fmt.Printf("Pos: %s; Forward: %s; |Forward|: %f\n", cam.Pos.String(), cam.Forward.String(), cam.Forward.Mag())
}
}
func (g *OurGame) updateCameraLookAround() {
mouseX, mouseY := input.GetMouseMotion()
if mouseX == 0 && mouseY == 0 {
return
}
// Yaw
yaw += float32(mouseX) * mouseSensitivity * timing.DT()
// Pitch
pitch += float32(-mouseY) * mouseSensitivity * timing.DT()
if pitch > 89.0 {
pitch = 89.0
}
if pitch < -89.0 {
pitch = -89.0
}
// Update cam forward
cam.UpdateRotation(pitch, yaw)
updateViewMat()
}
func (g *OurGame) updateCameraPos() {
update := false
// Forward and backward
if input.KeyDown(sdl.K_w) {
cam.Pos.Add(cam.Forward.Clone().Scale(camSpeed * timing.DT()))
update = true
} else if input.KeyDown(sdl.K_s) {
cam.Pos.Add(cam.Forward.Clone().Scale(-camSpeed * timing.DT()))
update = true
}
// Left and right
if input.KeyDown(sdl.K_d) {
cam.Pos.Add(gglm.Cross(&cam.Forward, &cam.WorldUp).Normalize().Scale(camSpeed * timing.DT()))
update = true
} else if input.KeyDown(sdl.K_a) {
cam.Pos.Add(gglm.Cross(&cam.Forward, &cam.WorldUp).Normalize().Scale(-camSpeed * timing.DT()))
update = true
}
if update {
updateViewMat()
}
}
func (g *OurGame) Render() {
tempModelMat := cubeModelMat.Clone()
rowSize := 100
for y := 0; y < rowSize; y++ {
for x := 0; x < rowSize; x++ {
tempModelMat.Translate(gglm.NewVec3(-1, 0, 0))
window.Rend.Draw(cubeMesh, tempModelMat, simpleMat)
}
tempModelMat.Translate(gglm.NewVec3(float32(rowSize), -1, 0))
}
g.Win.SDLWin.SetTitle(fmt.Sprint("nMage (", timing.GetAvgFPS(), " fps)"))
}
func (g *OurGame) FrameEnd() {
}
func (g *OurGame) DeInit() {
g.Win.Destroy()
}
func updateViewMat() {
targetPos := camPos.Clone().Add(camForward)
viewMat := gglm.LookAt(camPos, targetPos, gglm.NewVec3(0, 1, 0))
simpleMat.SetUnifMat4("viewMat", &viewMat.Mat4)
cam.Update()
simpleMat.SetUnifMat4("viewMat", &cam.ViewMat)
}

View File

@ -2,8 +2,7 @@ package materials
import (
"github.com/bloeys/gglm/gglm"
"github.com/bloeys/nmage/asserts"
"github.com/bloeys/nmage/buffers"
"github.com/bloeys/nmage/assert"
"github.com/bloeys/nmage/logging"
"github.com/bloeys/nmage/shaders"
"github.com/go-gl/gl/v4.1-core/gl"
@ -43,7 +42,7 @@ func (m *Material) GetAttribLoc(attribName string) int32 {
}
loc = gl.GetAttribLocation(m.ShaderProg.ID, gl.Str(attribName+"\x00"))
asserts.T(loc != -1, "Attribute '"+attribName+"' doesn't exist on material "+m.Name)
assert.T(loc != -1, "Attribute '"+attribName+"' doesn't exist on material "+m.Name)
m.AttribLocs[attribName] = loc
return loc
}
@ -56,28 +55,11 @@ func (m *Material) GetUnifLoc(uniformName string) int32 {
}
loc = gl.GetUniformLocation(m.ShaderProg.ID, gl.Str(uniformName+"\x00"))
asserts.T(loc != -1, "Uniform '"+uniformName+"' doesn't exist on material "+m.Name)
assert.T(loc != -1, "Uniform '"+uniformName+"' doesn't exist on material "+m.Name)
m.UnifLocs[uniformName] = loc
return loc
}
func (m *Material) SetAttribute(bufObj buffers.Buffer) {
bufObj.Bind()
//NOTE: VBOs are only bound at 'VertexAttribPointer', not BindBUffer, so we need to bind the buffer and vao here
gl.BindBuffer(gl.ARRAY_BUFFER, bufObj.BufID)
layout := bufObj.GetLayout()
for i := 0; i < len(layout); i++ {
gl.EnableVertexAttribArray(uint32(i))
gl.VertexAttribPointer(uint32(i), layout[i].ElementType.CompCount(), layout[i].ElementType.GLType(), false, bufObj.Stride, gl.PtrOffset(layout[i].Offset))
}
bufObj.UnBind()
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
}
func (m *Material) EnableAttribute(attribName string) {
gl.EnableVertexAttribArray(uint32(m.GetAttribLoc(attribName)))
}
@ -124,24 +106,20 @@ func (m *Material) Delete() {
func NewMaterial(matName, shaderPath string) *Material {
shdrProg, err := shaders.NewShaderProgram()
shdrProg, err := shaders.LoadAndCompileCombinedShader(shaderPath)
if err != nil {
logging.ErrLog.Fatalln("Failed to create new shader program. Err: ", err)
logging.ErrLog.Fatalln("Failed to create new material. Err: ", err)
}
return &Material{Name: matName, ShaderProg: shdrProg, UnifLocs: make(map[string]int32), AttribLocs: make(map[string]int32)}
}
func NewMaterialSrc(matName string, shaderSrc []byte) *Material {
shdrProg, err := shaders.LoadAndCompileCombinedShaderSrc(shaderSrc)
if err != nil {
logging.ErrLog.Fatalln("Failed to create new material. Err: ", err)
}
vertShader, err := shaders.LoadAndCompilerShader(shaderPath+".vert.glsl", shaders.VertexShaderType)
if err != nil {
logging.ErrLog.Fatalln("Failed to load and create vertex shader. Err: ", err)
}
fragShader, err := shaders.LoadAndCompilerShader(shaderPath+".frag.glsl", shaders.FragmentShaderType)
if err != nil {
logging.ErrLog.Fatalln("Failed to load and create fragment shader. Err: ", err)
}
shdrProg.AttachShader(vertShader)
shdrProg.AttachShader(fragShader)
shdrProg.Link()
return &Material{Name: matName, ShaderProg: shdrProg, UnifLocs: make(map[string]int32), AttribLocs: make(map[string]int32)}
}

View File

@ -6,7 +6,7 @@ import (
"github.com/bloeys/assimp-go/asig"
"github.com/bloeys/gglm/gglm"
"github.com/bloeys/nmage/asserts"
"github.com/bloeys/nmage/assert"
"github.com/bloeys/nmage/buffers"
)
@ -31,7 +31,11 @@ func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh,
sceneMesh := scene.Meshes[0]
mesh.Buf = buffers.NewBuffer()
asserts.T(len(sceneMesh.TexCoords[0]) > 0, "Mesh has no UV0")
// assert.T(len(sceneMesh.TexCoords[0]) > 0, "Mesh has no UV0")
if len(sceneMesh.TexCoords[0]) == 0 {
sceneMesh.TexCoords[0] = make([]gglm.Vec3, len(sceneMesh.Vertices))
}
layoutToUse := []buffers.Element{{ElementType: buffers.DataTypeVec3}, {ElementType: buffers.DataTypeVec3}, {ElementType: buffers.DataTypeVec2}}
if len(sceneMesh.ColorSets) > 0 && len(sceneMesh.ColorSets[0]) > 0 {
@ -73,9 +77,9 @@ type arrToInterleave struct {
func (a *arrToInterleave) get(i int) []float32 {
asserts.T(len(a.V2s) == 0 || len(a.V3s) == 0, "One array should be set in arrToInterleave, but both arrays are set")
asserts.T(len(a.V2s) == 0 || len(a.V4s) == 0, "One array should be set in arrToInterleave, but both arrays are set")
asserts.T(len(a.V3s) == 0 || len(a.V4s) == 0, "One array should be set in arrToInterleave, but both arrays are set")
assert.T(len(a.V2s) == 0 || len(a.V3s) == 0, "One array should be set in arrToInterleave, but both arrays are set")
assert.T(len(a.V2s) == 0 || len(a.V4s) == 0, "One array should be set in arrToInterleave, but both arrays are set")
assert.T(len(a.V3s) == 0 || len(a.V4s) == 0, "One array should be set in arrToInterleave, but both arrays are set")
if len(a.V2s) > 0 {
return a.V2s[i].Data[:]
@ -88,8 +92,8 @@ func (a *arrToInterleave) get(i int) []float32 {
func interleave(arrs ...arrToInterleave) []float32 {
asserts.T(len(arrs) > 0, "No input sent to interleave")
asserts.T(len(arrs[0].V2s) > 0 || len(arrs[0].V3s) > 0 || len(arrs[0].V4s) > 0, "Interleave arrays are empty")
assert.T(len(arrs) > 0, "No input sent to interleave")
assert.T(len(arrs[0].V2s) > 0 || len(arrs[0].V3s) > 0 || len(arrs[0].V4s) > 0, "Interleave arrays are empty")
elementCount := 0
if len(arrs[0].V2s) > 0 {
@ -104,7 +108,7 @@ func interleave(arrs ...arrToInterleave) []float32 {
totalSize := 0
for i := 0; i < len(arrs); i++ {
asserts.T(len(arrs[i].V2s) == elementCount || len(arrs[i].V3s) == elementCount || len(arrs[i].V4s) == elementCount, "Mesh vertex data given to interleave is not the same length")
assert.T(len(arrs[i].V2s) == elementCount || len(arrs[i].V3s) == elementCount || len(arrs[i].V4s) == elementCount, "Mesh vertex data given to interleave is not the same length")
if len(arrs[i].V2s) > 0 {
totalSize += len(arrs[i].V2s) * 2
@ -152,7 +156,7 @@ func flattenVec4(vec4s []gglm.Vec4) []float32 {
func flattenFaces(faces []asig.Face) []uint32 {
asserts.T(len(faces[0].Indices) == 3, fmt.Sprintf("Face doesn't have 3 indices. Index count: %v\n", len(faces[0].Indices)))
assert.T(len(faces[0].Indices) == 3, fmt.Sprintf("Face doesn't have 3 indices. Index count: %v\n", len(faces[0].Indices)))
uints := make([]uint32, len(faces)*3)
for i := 0; i < len(faces); i++ {

View File

@ -1,13 +0,0 @@
#version 410
uniform sampler2D Texture;
in vec2 Frag_UV;
in vec4 Frag_Color;
out vec4 Out_Color;
void main()
{
Out_Color = vec4(Frag_Color.rgb, Frag_Color.a * texture(Texture, Frag_UV.st).r);
}

View File

@ -1,17 +0,0 @@
#version 410
uniform mat4 ProjMtx;
in vec2 Position;
in vec2 UV;
in vec4 Color;
out vec2 Frag_UV;
out vec4 Frag_Color;
void main()
{
Frag_UV = UV;
Frag_Color = Color;
gl_Position = ProjMtx * vec4(Position.xy, 0, 1);
}

View File

@ -1,26 +0,0 @@
#version 410
uniform float ambientStrength = 0.1;
uniform vec3 ambientLightColor = vec3(1, 1, 1);
uniform vec3 lightPos1;
uniform vec3 lightColor1;
uniform sampler2D diffTex;
in vec3 vertColor;
in vec3 vertNormal;
in vec2 vertUV0;
in vec3 fragPos;
out vec4 fragColor;
void main()
{
vec3 lightDir = normalize(lightPos1 - fragPos);
float diffStrength = max(0.0, dot(normalize(vertNormal), lightDir));
vec3 finalAmbientColor = ambientLightColor * ambientStrength;
vec4 texColor = texture(diffTex, vertUV0);
fragColor = vec4(texColor.rgb * vertColor * (finalAmbientColor + diffStrength*lightColor1) , texColor.a);
}

55
res/shaders/simple.glsl Executable file
View File

@ -0,0 +1,55 @@
//shader:vertex
#version 410
layout(location=0) in vec3 vertPosIn;
layout(location=1) in vec3 vertNormalIn;
layout(location=2) in vec2 vertUV0In;
layout(location=3) in vec3 vertColorIn;
out vec3 vertNormal;
out vec2 vertUV0;
out vec3 vertColor;
out vec3 fragPos;
//MVP = Model View Projection
uniform mat4 modelMat;
uniform mat4 viewMat;
uniform mat4 projMat;
void main()
{
vertNormal = mat3(transpose(inverse(modelMat))) * vertNormalIn;
vertUV0 = vertUV0In;
vertColor = vertColorIn;
fragPos = vec3(modelMat * vec4(vertPosIn, 1.0));
gl_Position = projMat * viewMat * modelMat * vec4(vertPosIn, 1.0);
}
//shader:fragment
#version 410
uniform float ambientStrength = 0.1;
uniform vec3 ambientLightColor = vec3(1, 1, 1);
uniform vec3 lightPos1;
uniform vec3 lightColor1;
uniform sampler2D diffTex;
in vec3 vertColor;
in vec3 vertNormal;
in vec2 vertUV0;
in vec3 fragPos;
out vec4 fragColor;
void main()
{
vec3 lightDir = normalize(lightPos1 - fragPos);
float diffStrength = max(0.0, dot(normalize(vertNormal), lightDir));
vec3 finalAmbientColor = ambientLightColor * ambientStrength;
vec4 texColor = texture(diffTex, vertUV0);
fragColor = vec4(texColor.rgb * vertColor * (finalAmbientColor + diffStrength*lightColor1) , texColor.a);
}

View File

@ -1,26 +0,0 @@
#version 410
layout(location=0) in vec3 vertPosIn;
layout(location=1) in vec3 vertNormalIn;
layout(location=2) in vec2 vertUV0In;
layout(location=3) in vec3 vertColorIn;
out vec3 vertNormal;
out vec2 vertUV0;
out vec3 vertColor;
out vec3 fragPos;
//MVP = Model View Projection
uniform mat4 modelMat;
uniform mat4 viewMat;
uniform mat4 projMat;
void main()
{
vertNormal = mat3(transpose(inverse(modelMat))) * vertNormalIn;
vertUV0 = vertUV0In;
vertColor = vertColorIn;
fragPos = vec3(modelMat * vec4(vertPosIn, 1.0));
gl_Position = projMat * viewMat * modelMat * vec4(vertPosIn, 1.0);
}

View File

@ -1,6 +1,7 @@
package shaders
import (
"bytes"
"errors"
"os"
"strings"
@ -28,14 +29,69 @@ func NewShaderProgram() (ShaderProgram, error) {
return ShaderProgram{ID: id}, nil
}
func LoadAndCompilerShader(shaderPath string, shaderType ShaderType) (Shader, error) {
func LoadAndCompileCombinedShader(shaderPath string) (ShaderProgram, error) {
shaderSource, err := os.ReadFile(shaderPath)
combinedSource, err := os.ReadFile(shaderPath)
if err != nil {
logging.ErrLog.Println("Failed to read shader. Err: ", err)
return Shader{}, err
return ShaderProgram{}, err
}
return LoadAndCompileCombinedShaderSrc(combinedSource)
}
func LoadAndCompileCombinedShaderSrc(shaderSrc []byte) (ShaderProgram, error) {
shaderSources := bytes.Split(shaderSrc, []byte("//shader:"))
if len(shaderSources) == 1 {
return ShaderProgram{}, errors.New("failed to read combined shader. Did not find '//shader:vertex' or '//shader:fragment'")
}
shdrProg, err := NewShaderProgram()
if err != nil {
return ShaderProgram{}, errors.New("failed to create new shader program. Err: " + err.Error())
}
loadedShdrCount := 0
for i := 0; i < len(shaderSources); i++ {
src := shaderSources[i]
//This can happen when the shader type is at the start of the file
if len(bytes.TrimSpace(src)) == 0 {
continue
}
var shdrType ShaderType
if bytes.HasPrefix(src, []byte("vertex")) {
src = src[6:]
shdrType = VertexShaderType
} else if bytes.HasPrefix(src, []byte("fragment")) {
src = src[8:]
shdrType = FragmentShaderType
} else {
return ShaderProgram{}, errors.New("unknown shader type. Must be '//shader:vertex' or '//shader:fragment'")
}
shdr, err := CompileShaderOfType(src, shdrType)
if err != nil {
return ShaderProgram{}, err
}
loadedShdrCount++
shdrProg.AttachShader(shdr)
}
if loadedShdrCount == 0 {
return ShaderProgram{}, errors.New("no valid shaders found. Please put '//shader:vertex' or '//shader:fragment' before your shaders")
}
shdrProg.Link()
return shdrProg, nil
}
func CompileShaderOfType(shaderSource []byte, shaderType ShaderType) (Shader, error) {
shaderID := gl.CreateShader(uint32(shaderType))
if shaderID == 0 {
logging.ErrLog.Println("Failed to create shader.")

View File

@ -43,7 +43,7 @@ func FrameEnded() {
//Calculate new dt
dt = float32(time.Since(frameStart).Seconds())
if dt == 0 {
dt = float32(time.Microsecond)
dt = float32(time.Microsecond.Seconds())
}
}

View File

@ -2,7 +2,7 @@ package nmageimgui
import (
"github.com/bloeys/gglm/gglm"
"github.com/bloeys/nmage/asserts"
"github.com/bloeys/nmage/assert"
"github.com/bloeys/nmage/materials"
"github.com/bloeys/nmage/timing"
"github.com/go-gl/gl/v4.1-core/gl"
@ -23,7 +23,7 @@ type ImguiInfo struct {
func (i *ImguiInfo) FrameStart(winWidth, winHeight float32) {
if err := i.ImCtx.SetCurrent(); err != nil {
asserts.T(false, "Setting imgui ctx as current failed. Err: "+err.Error())
assert.T(false, "Setting imgui ctx as current failed. Err: "+err.Error())
}
imIO := imgui.CurrentIO()
@ -36,7 +36,7 @@ func (i *ImguiInfo) FrameStart(winWidth, winHeight float32) {
func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32) {
if err := i.ImCtx.SetCurrent(); err != nil {
asserts.T(false, "Setting imgui ctx as current failed. Err: "+err.Error())
assert.T(false, "Setting imgui ctx as current failed. Err: "+err.Error())
}
imgui.Render()
@ -66,8 +66,7 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
// DisplayMin is typically (0,0) for single viewport apps.
i.Mat.Bind()
gl.Uniform1i(gl.GetUniformLocation(i.Mat.ShaderProg.ID, gl.Str("Texture\x00")), 0)
i.Mat.SetUnifInt32("Texture", 0)
//PERF: only update the ortho matrix on window resize
orthoMat := gglm.Ortho(0, float32(winWidth), 0, float32(winHeight), 0, 20)
@ -149,11 +148,47 @@ func (i *ImguiInfo) AddFontTTF(fontPath string, fontSize float32, fontConfig *im
return f
}
const imguiShdrSrc = `
//shader:vertex
#version 410
uniform mat4 ProjMtx;
in vec2 Position;
in vec2 UV;
in vec4 Color;
out vec2 Frag_UV;
out vec4 Frag_Color;
void main()
{
Frag_UV = UV;
Frag_Color = Color;
gl_Position = ProjMtx * vec4(Position.xy, 0, 1);
}
//shader:fragment
#version 410
uniform sampler2D Texture;
in vec2 Frag_UV;
in vec4 Frag_Color;
out vec4 Out_Color;
void main()
{
Out_Color = vec4(Frag_Color.rgb, Frag_Color.a * texture(Texture, Frag_UV.st).r);
}
`
func NewImGUI() ImguiInfo {
imguiInfo := ImguiInfo{
ImCtx: imgui.CreateContext(nil),
Mat: materials.NewMaterial("ImGUI Mat", "./res/shaders/imgui"),
Mat: materials.NewMaterialSrc("ImGUI Mat", []byte(imguiShdrSrc)),
}
imIO := imgui.CurrentIO()