Compare commits

...

8 Commits

Author SHA1 Message Date
692167ada2 Split buffer struct into VAO+VBO+IBO structs 2024-04-13 02:59:31 +04:00
524ef068f0 Add comment 2024-04-12 23:57:24 +04:00
b060dcdbe9 Go 1.22+fix input bug 2024-04-12 23:55:21 +04:00
e22525e2ee Default to srgba textures 2024-04-12 23:38:51 +04:00
ee61373069 Blinn-phong 2024-04-12 23:28:59 +04:00
1f922b6a47 Enable stencil test 2024-04-12 23:02:27 +04:00
9d7bdc0196 Improve error messages 2024-04-12 22:40:08 +04:00
83922f1908 Spot lights 2024-04-12 21:09:14 +04:00
14 changed files with 423 additions and 204 deletions

View File

@ -12,7 +12,7 @@ jobs:
- name: Install golang
uses: actions/setup-go@v3
with:
go-version: '>=1.18'
go-version: '>=1.22'
- name: Install assimp-go dylib
run: sudo mkdir -p /usr/local/lib && sudo wget https://github.com/bloeys/assimp-go/releases/download/v0.4.2/libassimp_darwin_amd64.dylib -O /usr/local/lib/libassimp.5.dylib

View File

@ -45,7 +45,7 @@ type TextureLoadOptions struct {
WriteToCache bool
GenMipMaps bool
KeepPixelsInMem bool
TextureIsSrgba bool
NoSrgba bool
}
type Cubemap struct {
@ -103,9 +103,9 @@ func LoadTexturePNG(file string, loadOptions *TextureLoadOptions) (Texture, erro
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
// load and generate the texture
internalFormat := int32(gl.RGBA8)
if loadOptions.TextureIsSrgba {
internalFormat = gl.SRGB_ALPHA
internalFormat := int32(gl.SRGB_ALPHA)
if loadOptions.NoSrgba {
internalFormat = gl.RGBA8
}
gl.TexImage2D(gl.TEXTURE_2D, 0, internalFormat, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0]))
@ -151,9 +151,9 @@ func LoadTextureInMemPngImg(img image.Image, loadOptions *TextureLoadOptions) (T
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
// load and generate the texture
internalFormat := int32(gl.RGBA8)
if loadOptions.TextureIsSrgba {
internalFormat = gl.SRGB_ALPHA
internalFormat := int32(gl.SRGB_ALPHA)
if loadOptions.NoSrgba {
internalFormat = gl.RGBA8
}
gl.TexImage2D(gl.TEXTURE_2D, 0, internalFormat, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0]))
@ -216,9 +216,9 @@ func LoadTextureJpeg(file string, loadOptions *TextureLoadOptions) (Texture, err
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
// load and generate the texture
internalFormat := int32(gl.RGBA8)
if loadOptions.TextureIsSrgba {
internalFormat = gl.SRGB_ALPHA
internalFormat := int32(gl.SRGB_ALPHA)
if loadOptions.NoSrgba {
internalFormat = gl.RGBA8
}
gl.TexImage2D(gl.TEXTURE_2D, 0, internalFormat, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0]))
@ -288,9 +288,9 @@ func LoadCubemapTextures(rightTex, leftTex, topTex, botTex, frontTex, backTex st
height := int32(nrgbaImg.Bounds().Dy())
width := int32(nrgbaImg.Bounds().Dx())
internalFormat := int32(gl.RGBA8)
if loadOptions.TextureIsSrgba {
internalFormat = gl.SRGB_ALPHA
internalFormat := int32(gl.SRGB_ALPHA)
if loadOptions.NoSrgba {
internalFormat = gl.RGBA8
}
gl.TexImage2D(uint32(gl.TEXTURE_CUBE_MAP_POSITIVE_X)+i, 0, internalFormat, int32(width), int32(height), 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&nrgbaImg.Pix[0]))

View File

@ -1,134 +0,0 @@
package buffers
import (
"github.com/bloeys/nmage/logging"
"github.com/go-gl/gl/v4.1-core/gl"
)
type Buffer struct {
VAOID uint32
// BufID is the ID of the VBO
BufID uint32
// IndexBufID is the ID of the index/element buffer
IndexBufID uint32
// IndexBufCount is the number of elements in the index buffer
// Updated on SetIndexBufData
IndexBufCount int32
// IndexBufCount int32
Stride int32
layout []Element
}
func (b *Buffer) Bind() {
gl.BindVertexArray(b.VAOID)
}
func (b *Buffer) UnBind() {
gl.BindVertexArray(0)
}
func (b *Buffer) SetData(values []float32) {
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), 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)
}
func (b *Buffer) SetIndexBufData(values []uint32) {
b.IndexBufCount = int32(len(values))
gl.BindVertexArray(b.VAOID)
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, b.IndexBufID)
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)
}
func (b *Buffer) GetLayout() []Element {
e := make([]Element, len(b.layout))
copy(e, b.layout)
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
b.Stride = 0
for i := 0; i < len(b.layout); i++ {
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.VertexAttribPointerWithOffset(uint32(i), layout[i].ElementType.CompCount(), layout[i].ElementType.GLType(), false, b.Stride, uintptr(layout[i].Offset))
}
b.UnBind()
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
}
func NewBuffer(layout ...Element) Buffer {
b := Buffer{}
gl.GenVertexArrays(1, &b.VAOID)
if b.VAOID == 0 {
logging.ErrLog.Println("Failed to create openGL vertex array object")
}
gl.GenBuffers(1, &b.BufID)
if b.BufID == 0 {
logging.ErrLog.Println("Failed to create openGL buffer")
}
gl.GenBuffers(1, &b.IndexBufID)
if b.IndexBufID == 0 {
logging.ErrLog.Println("Failed to create openGL buffer")
}
b.SetLayout(layout...)
return b
}

46
buffers/index_buffer.go Executable file
View File

@ -0,0 +1,46 @@
package buffers
import (
"github.com/bloeys/nmage/logging"
"github.com/go-gl/gl/v4.1-core/gl"
)
type IndexBuffer struct {
Id uint32
// IndexBufCount is the number of elements in the index buffer. Updated in IndexBuffer.SetData
IndexBufCount int32
}
func (ib *IndexBuffer) Bind() {
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ib.Id)
}
func (ib *IndexBuffer) UnBind() {
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
}
func (ib *IndexBuffer) SetData(values []uint32) {
ib.Bind()
sizeInBytes := len(values) * 4
ib.IndexBufCount = int32(len(values))
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())
}
}
func NewIndexBuffer() IndexBuffer {
ib := IndexBuffer{}
gl.GenBuffers(1, &ib.Id)
if ib.Id == 0 {
logging.ErrLog.Println("Failed to create OpenGL buffer")
}
return ib
}

54
buffers/vertex_array.go Executable file
View File

@ -0,0 +1,54 @@
package buffers
import (
"github.com/bloeys/nmage/logging"
"github.com/go-gl/gl/v4.1-core/gl"
)
type VertexArray struct {
Id uint32
Vbos []VertexBuffer
IndexBuffer IndexBuffer
}
func (va *VertexArray) Bind() {
gl.BindVertexArray(va.Id)
}
func (va *VertexArray) UnBind() {
gl.BindVertexArray(0)
}
func (va *VertexArray) AddVertexBuffer(vbo VertexBuffer) {
// NOTE: VBOs are only bound at 'VertexAttribPointer' (and related) calls
va.Bind()
vbo.Bind()
for i := 0; i < len(vbo.layout); i++ {
l := &vbo.layout[i]
gl.EnableVertexAttribArray(uint32(i))
gl.VertexAttribPointerWithOffset(uint32(i), l.ElementType.CompCount(), l.ElementType.GLType(), false, vbo.Stride, uintptr(l.Offset))
}
}
func (va *VertexArray) SetIndexBuffer(ib IndexBuffer) {
va.Bind()
ib.Bind()
va.IndexBuffer = ib
}
func NewVertexArray() VertexArray {
vao := VertexArray{}
gl.GenVertexArrays(1, &vao.Id)
if vao.Id == 0 {
logging.ErrLog.Println("Failed to create OpenGL vertex array object")
}
return vao
}

63
buffers/vertex_buffer.go Executable file
View File

@ -0,0 +1,63 @@
package buffers
import (
"github.com/bloeys/nmage/logging"
"github.com/go-gl/gl/v4.1-core/gl"
)
type VertexBuffer struct {
Id uint32
Stride int32
layout []Element
}
func (vb *VertexBuffer) Bind() {
gl.BindBuffer(gl.ARRAY_BUFFER, vb.Id)
}
func (vb *VertexBuffer) UnBind() {
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
}
func (vb *VertexBuffer) SetData(values []float32, usage BufUsage) {
vb.Bind()
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())
}
}
func (vb *VertexBuffer) GetLayout() []Element {
e := make([]Element, len(vb.layout))
copy(e, vb.layout)
return e
}
func (vb *VertexBuffer) SetLayout(layout ...Element) {
vb.Stride = 0
vb.layout = layout
for i := 0; i < len(vb.layout); i++ {
vb.layout[i].Offset = int(vb.Stride)
vb.Stride += vb.layout[i].Size()
}
}
func NewVertexBuffer(layout ...Element) VertexBuffer {
vb := VertexBuffer{}
gl.GenBuffers(1, &vb.Id)
if vb.Id == 0 {
logging.ErrLog.Println("Failed to create OpenGL buffer")
}
vb.SetLayout(layout...)
return vb
}

View File

@ -39,6 +39,20 @@ func (w *Window) handleInputs() {
imguiCaptureMouse := imIo.WantCaptureMouse()
imguiCaptureKeyboard := imIo.WantCaptureKeyboard()
// These two are to fix a bug where state isn't cleared
// even after imgui captures the keyboard/mouse.
//
// For example, if player is moving due to key held and then imgui captures the keyboard,
// the player keeps moving even when the key is no longer pressed because the input system never
// receives the key up event.
if imguiCaptureMouse {
input.ClearMouseState()
}
if imguiCaptureKeyboard {
input.ClearKeyboardState()
}
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
//Fire callbacks
@ -229,6 +243,7 @@ func initOpenGL() error {
}
gl.Enable(gl.DEPTH_TEST)
gl.Enable(gl.STENCIL_TEST)
gl.Enable(gl.CULL_FACE)
gl.CullFace(gl.BACK)
gl.FrontFace(gl.CCW)
@ -252,7 +267,6 @@ func SetSrgbFramebuffer(isEnabled bool) {
}
func SetVSync(enabled bool) {
assert.T(isInited, "engine.Init was not called!")
if enabled {
sdl.GLSetSwapInterval(1)

2
go.mod
View File

@ -1,6 +1,6 @@
module github.com/bloeys/nmage
go 1.18
go 1.22
require github.com/veandco/go-sdl2 v0.4.35

View File

@ -40,15 +40,17 @@ var (
func EventLoopStart() {
for _, v := range keyMap {
for k, v := range keyMap {
v.IsPressedThisFrame = false
v.IsReleasedThisFrame = false
keyMap[k] = v
}
for _, v := range mouseBtnMap {
for k, v := range mouseBtnMap {
v.IsPressedThisFrame = false
v.IsReleasedThisFrame = false
v.IsDoubleClicked = false
mouseBtnMap[k] = v
}
mouseMotion.XDelta = 0
@ -60,6 +62,16 @@ func EventLoopStart() {
quitRequested = false
}
func ClearKeyboardState() {
clear(keyMap)
}
func ClearMouseState() {
clear(mouseBtnMap)
mouseMotion = mouseMotionState{}
mouseWheel = mouseWheelState{}
}
func HandleQuitEvent(e *sdl.QuitEvent) {
quitRequested = true
}

162
main.go
View File

@ -26,13 +26,15 @@ import (
/*
@TODO:
- Rendering:
- Phong lighting model
- Point lights
- Spotlights
- Blinn-Phong lighting model
- Directional lights
- Point lights
- Spotlights ✅
- HDR
- Cascaded shadow mapping
- Skeletal animations
- Create VAO struct independent from VBO to support multi-VBO use cases (e.g. instancing)
- Proper model loading (i.e. load model by reading all its meshes, textures, and so on together)
- 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?
@ -62,6 +64,7 @@ type PointLight struct {
}
type SpotLight struct {
Pos gglm.Vec3
Dir gglm.Vec3
DiffuseColor gglm.Vec3
SpecularColor gglm.Vec3
@ -69,6 +72,18 @@ type SpotLight struct {
OuterCutoff float32
}
// SetCutoffs properly sets the cosine values of the cutoffs using the passed
// degrees.
//
// The light has full intensity within the inner cutoff, falloff between
// inner-outer cutoff, and zero light beyond the outer cutoff.
//
// The inner cuttoff degree must be *smaller* than the outer cutoff
func (s *SpotLight) SetCutoffs(innerCutoffAngleDeg, outerCutoffAngleDeg float32) {
s.InnerCutoff = gglm.Cos32(innerCutoffAngleDeg * gglm.Deg2Rad)
s.OuterCutoff = gglm.Cos32(outerCutoffAngleDeg * gglm.Deg2Rad)
}
const (
camSpeed = 15
mouseSensitivity = 0.5
@ -148,6 +163,17 @@ var (
Quadratic: 0.032,
},
}
spotLights = [...]SpotLight{
{
Pos: *gglm.NewVec3(0, 5, 0),
Dir: *gglm.NewVec3(0, -1, 0),
DiffuseColor: *gglm.NewVec3(0, 1, 1),
SpecularColor: *gglm.NewVec3(1, 1, 1),
// These must be cosine values
InnerCutoff: gglm.Cos32(15 * gglm.Deg2Rad),
OuterCutoff: gglm.Cos32(20 * gglm.Deg2Rad),
},
}
)
type Game struct {
@ -324,27 +350,27 @@ func (g *Game) Init() {
}
//Load textures
whiteTex, err := assets.LoadTexturePNG("./res/textures/white.png", &assets.TextureLoadOptions{TextureIsSrgba: true})
whiteTex, err := assets.LoadTexturePNG("./res/textures/white.png", &assets.TextureLoadOptions{})
if err != nil {
logging.ErrLog.Fatalln("Failed to load texture. Err: ", err)
}
blackTex, err := assets.LoadTexturePNG("./res/textures/black.png", &assets.TextureLoadOptions{TextureIsSrgba: true})
blackTex, err := assets.LoadTexturePNG("./res/textures/black.png", &assets.TextureLoadOptions{})
if err != nil {
logging.ErrLog.Fatalln("Failed to load texture. Err: ", err)
}
containerDiffuseTex, err := assets.LoadTexturePNG("./res/textures/container-diffuse.png", &assets.TextureLoadOptions{TextureIsSrgba: true})
containerDiffuseTex, err := assets.LoadTexturePNG("./res/textures/container-diffuse.png", &assets.TextureLoadOptions{})
if err != nil {
logging.ErrLog.Fatalln("Failed to load texture. Err: ", err)
}
containerSpecularTex, err := assets.LoadTexturePNG("./res/textures/container-specular.png", &assets.TextureLoadOptions{TextureIsSrgba: true})
containerSpecularTex, err := assets.LoadTexturePNG("./res/textures/container-specular.png", &assets.TextureLoadOptions{})
if err != nil {
logging.ErrLog.Fatalln("Failed to load texture. Err: ", err)
}
palleteTex, err := assets.LoadTexturePNG("./res/textures/pallete-endesga-64-1x.png", &assets.TextureLoadOptions{TextureIsSrgba: true})
palleteTex, err := assets.LoadTexturePNG("./res/textures/pallete-endesga-64-1x.png", &assets.TextureLoadOptions{})
if err != nil {
logging.ErrLog.Fatalln("Failed to load texture. Err: ", err)
}
@ -353,7 +379,7 @@ func (g *Game) Init() {
"./res/textures/sb-right.jpg", "./res/textures/sb-left.jpg",
"./res/textures/sb-top.jpg", "./res/textures/sb-bottom.jpg",
"./res/textures/sb-front.jpg", "./res/textures/sb-back.jpg",
&assets.TextureLoadOptions{TextureIsSrgba: true},
&assets.TextureLoadOptions{},
)
if err != nil {
logging.ErrLog.Fatalln("Failed to load cubemap. Err: ", err)
@ -456,6 +482,36 @@ func (g *Game) updateLights() {
containerMat.SetUnifFloat32(indexString+".quadratic", pl.Quadratic)
palleteMat.SetUnifFloat32(indexString+".quadratic", pl.Quadratic)
}
for i := 0; i < len(spotLights); i++ {
l := &spotLights[i]
indexString := "spotLights[" + strconv.Itoa(i) + "]"
whiteMat.SetUnifVec3(indexString+".pos", &l.Pos)
containerMat.SetUnifVec3(indexString+".pos", &l.Pos)
palleteMat.SetUnifVec3(indexString+".pos", &l.Pos)
whiteMat.SetUnifVec3(indexString+".dir", &l.Dir)
containerMat.SetUnifVec3(indexString+".dir", &l.Dir)
palleteMat.SetUnifVec3(indexString+".dir", &l.Dir)
whiteMat.SetUnifVec3(indexString+".diffuseColor", &l.DiffuseColor)
containerMat.SetUnifVec3(indexString+".diffuseColor", &l.DiffuseColor)
palleteMat.SetUnifVec3(indexString+".diffuseColor", &l.DiffuseColor)
whiteMat.SetUnifVec3(indexString+".specularColor", &l.SpecularColor)
containerMat.SetUnifVec3(indexString+".specularColor", &l.SpecularColor)
palleteMat.SetUnifVec3(indexString+".specularColor", &l.SpecularColor)
whiteMat.SetUnifFloat32(indexString+".innerCutoff", l.InnerCutoff)
containerMat.SetUnifFloat32(indexString+".innerCutoff", l.InnerCutoff)
palleteMat.SetUnifFloat32(indexString+".innerCutoff", l.InnerCutoff)
whiteMat.SetUnifFloat32(indexString+".outerCutoff", l.OuterCutoff)
containerMat.SetUnifFloat32(indexString+".outerCutoff", l.OuterCutoff)
palleteMat.SetUnifFloat32(indexString+".outerCutoff", l.OuterCutoff)
}
}
func (g *Game) Update() {
@ -544,35 +600,91 @@ func (g *Game) showDebugWindow() {
imgui.Spacing()
// Point lights
imgui.Text("Point Lights")
if imgui.BeginListBoxV("", imgui.Vec2{Y: 200}) {
if imgui.BeginListBoxV("Point Lights", imgui.Vec2{Y: 200}) {
for i := 0; i < len(pointLights); i++ {
pl := &pointLights[i]
indexString := strconv.Itoa(i)
indexNumString := strconv.Itoa(i)
if !imgui.TreeNodeExStrV("Light "+indexString, imgui.TreeNodeFlagsSpanAvailWidth) {
if !imgui.TreeNodeExStrV("Point Light "+indexNumString, imgui.TreeNodeFlagsSpanAvailWidth) {
continue
}
indexString := "pointLights[" + indexNumString + "]"
if imgui.DragFloat3("Pos", &pl.Pos.Data) {
whiteMat.SetUnifVec3("pointLights["+indexString+"].pos", &pl.Pos)
containerMat.SetUnifVec3("pointLights["+indexString+"].pos", &pl.Pos)
palleteMat.SetUnifVec3("pointLights["+indexString+"].pos", &pl.Pos)
whiteMat.SetUnifVec3(indexString+".pos", &pl.Pos)
containerMat.SetUnifVec3(indexString+".pos", &pl.Pos)
palleteMat.SetUnifVec3(indexString+".pos", &pl.Pos)
}
if imgui.DragFloat3("Diffuse Color", &pl.DiffuseColor.Data) {
whiteMat.SetUnifVec3("pointLights["+indexString+"].diffuseColor", &pl.DiffuseColor)
containerMat.SetUnifVec3("pointLights["+indexString+"].diffuseColor", &pl.DiffuseColor)
palleteMat.SetUnifVec3("pointLights["+indexString+"].diffuseColor", &pl.DiffuseColor)
whiteMat.SetUnifVec3(indexString+".diffuseColor", &pl.DiffuseColor)
containerMat.SetUnifVec3(indexString+".diffuseColor", &pl.DiffuseColor)
palleteMat.SetUnifVec3(indexString+".diffuseColor", &pl.DiffuseColor)
}
if imgui.DragFloat3("Specular Color", &pl.SpecularColor.Data) {
whiteMat.SetUnifVec3("pointLights["+indexString+"].specularColor", &pl.SpecularColor)
containerMat.SetUnifVec3("pointLights["+indexString+"].specularColor", &pl.SpecularColor)
palleteMat.SetUnifVec3("pointLights["+indexString+"].specularColor", &pl.SpecularColor)
whiteMat.SetUnifVec3(indexString+".specularColor", &pl.SpecularColor)
containerMat.SetUnifVec3(indexString+".specularColor", &pl.SpecularColor)
palleteMat.SetUnifVec3(indexString+".specularColor", &pl.SpecularColor)
}
imgui.TreePop()
}
imgui.EndListBox()
}
// Spot lights
if imgui.BeginListBoxV("Spot Lights", imgui.Vec2{Y: 200}) {
for i := 0; i < len(spotLights); i++ {
l := &spotLights[i]
indexNumString := strconv.Itoa(i)
if !imgui.TreeNodeExStrV("Spot Light "+indexNumString, imgui.TreeNodeFlagsSpanAvailWidth) {
continue
}
indexString := "spotLights[" + indexNumString + "]"
if imgui.DragFloat3("Pos", &l.Pos.Data) {
whiteMat.SetUnifVec3(indexString+".pos", &l.Pos)
containerMat.SetUnifVec3(indexString+".pos", &l.Pos)
palleteMat.SetUnifVec3(indexString+".pos", &l.Pos)
}
if imgui.DragFloat3("Dir", &l.Dir.Data) {
whiteMat.SetUnifVec3(indexString+".dir", &l.Dir)
containerMat.SetUnifVec3(indexString+".dir", &l.Dir)
palleteMat.SetUnifVec3(indexString+".dir", &l.Dir)
}
if imgui.DragFloat3("Diffuse Color", &l.DiffuseColor.Data) {
whiteMat.SetUnifVec3(indexString+".diffuseColor", &l.DiffuseColor)
containerMat.SetUnifVec3(indexString+".diffuseColor", &l.DiffuseColor)
palleteMat.SetUnifVec3(indexString+".diffuseColor", &l.DiffuseColor)
}
if imgui.DragFloat3("Specular Color", &l.SpecularColor.Data) {
whiteMat.SetUnifVec3(indexString+".specularColor", &l.SpecularColor)
containerMat.SetUnifVec3(indexString+".specularColor", &l.SpecularColor)
palleteMat.SetUnifVec3(indexString+".specularColor", &l.SpecularColor)
}
if imgui.DragFloat("Inner Cutoff", &l.InnerCutoff) {
whiteMat.SetUnifFloat32(indexString+".innerCutoff", l.InnerCutoff)
containerMat.SetUnifFloat32(indexString+".innerCutoff", l.InnerCutoff)
palleteMat.SetUnifFloat32(indexString+".innerCutoff", l.InnerCutoff)
}
if imgui.DragFloat("Outer Cutoff", &l.OuterCutoff) {
whiteMat.SetUnifFloat32(indexString+".outerCutoff", l.OuterCutoff)
containerMat.SetUnifFloat32(indexString+".outerCutoff", l.OuterCutoff)
palleteMat.SetUnifFloat32(indexString+".outerCutoff", l.OuterCutoff)
}
imgui.TreePop()
@ -700,7 +812,7 @@ func (g *Game) DrawSkybox() {
gl.Disable(gl.CULL_FACE)
gl.DepthFunc(gl.LEQUAL)
skyboxMesh.Buf.Bind()
skyboxMesh.Vao.Bind()
skyboxMat.Bind()
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_CUBE_MAP, skyboxCmap.TexID)

View File

@ -2,7 +2,6 @@ package meshes
import (
"errors"
"fmt"
"github.com/bloeys/assimp-go/asig"
"github.com/bloeys/gglm/gglm"
@ -18,7 +17,7 @@ type SubMesh struct {
type Mesh struct {
Name string
Buf buffers.Buffer
Vao buffers.VertexArray
SubMeshes []SubMesh
}
@ -36,21 +35,25 @@ func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh,
mesh := &Mesh{
Name: name,
Buf: buffers.NewBuffer(),
Vao: buffers.NewVertexArray(),
SubMeshes: make([]SubMesh, 0, 1),
}
vbo := buffers.NewVertexBuffer()
ibo := buffers.NewIndexBuffer()
// Initial sizes assuming one submesh that has vertex pos+normals+texCoords, and 3 indices per face
var vertexBufData []float32 = make([]float32, 0, len(scene.Meshes[0].Vertices)*3*3*2)
var indexBufData []uint32 = make([]uint32, 0, len(scene.Meshes[0].Faces)*3)
// fmt.Printf("\nMesh %s has %d meshe(s) with first mesh having %d vertices\n", name, len(scene.Meshes), len(scene.Meshes[0].Vertices))
for i := 0; i < len(scene.Meshes); i++ {
sceneMesh := scene.Meshes[i]
if len(sceneMesh.TexCoords[0]) == 0 {
sceneMesh.TexCoords[0] = make([]gglm.Vec3, len(sceneMesh.Vertices))
println("Zeroing tex coords for submesh", i)
}
layoutToUse := []buffers.Element{{ElementType: buffers.DataTypeVec3}, {ElementType: buffers.DataTypeVec3}, {ElementType: buffers.DataTypeVec2}}
@ -59,17 +62,20 @@ func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh,
}
if i == 0 {
mesh.Buf.SetLayout(layoutToUse...)
vbo.SetLayout(layoutToUse...)
} else {
// @NOTE: Require that all submeshes have the same vertex buffer layout
firstSubmeshLayout := mesh.Buf.GetLayout()
assert.T(len(firstSubmeshLayout) == len(layoutToUse), fmt.Sprintf("Vertex layout of submesh %d does not equal vertex layout of the first submesh. Original layout: %v; This layout: %v", i, firstSubmeshLayout, layoutToUse))
// @TODO @NOTE: This requirement is because we are using one VAO+VBO for all
// the meshes and so the buffer must have one format.
//
// If we want to allow different layouts then we can simply create one vbo per layout and put
// meshes of the same layout in the same vbo, and we store the index of the vbo the mesh
// uses in the submesh struct.
firstSubmeshLayout := vbo.GetLayout()
assert.T(len(firstSubmeshLayout) == len(layoutToUse), "Vertex layout of submesh '%d' of mesh '%s' at path '%s' does not equal vertex layout of the first submesh. Original layout: %v; This layout: %v", i, name, modelPath, firstSubmeshLayout, layoutToUse)
for i := 0; i < len(firstSubmeshLayout); i++ {
if firstSubmeshLayout[i].ElementType != layoutToUse[i].ElementType {
panic(fmt.Sprintf("Vertex layout of submesh %d does not equal vertex layout of the first submesh. Original layout: %v; This layout: %v", i, firstSubmeshLayout, layoutToUse))
}
assert.T(firstSubmeshLayout[i].ElementType == layoutToUse[i].ElementType, "Vertex layout of submesh '%d' of mesh '%s' at path '%s' does not equal vertex layout of the first submesh. Original layout: %v; This layout: %v", i, name, modelPath, firstSubmeshLayout, layoutToUse)
}
}
@ -82,7 +88,7 @@ func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh,
mesh.SubMeshes = append(mesh.SubMeshes, SubMesh{
// Index of the vertex to start from (e.g. if index buffer says use vertex 5, and BaseVertex=3, the vertex used will be vertex 8)
BaseVertex: int32(len(vertexBufData)*4) / mesh.Buf.Stride,
BaseVertex: int32(len(vertexBufData)*4) / vbo.Stride,
// Which index (in the index buffer) to start from
BaseIndex: uint32(len(indexBufData)),
// How many indices in this submesh
@ -93,9 +99,16 @@ func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh,
indexBufData = append(indexBufData, indices...)
}
// fmt.Printf("!!! Vertex count: %d; Submeshes: %+v\n", len(vertexBufData)*4/int(mesh.Buf.Stride), mesh.SubMeshes)
mesh.Buf.SetData(vertexBufData)
mesh.Buf.SetIndexBufData(indexBufData)
vbo.SetData(vertexBufData, buffers.BufUsage_Static)
ibo.SetData(indexBufData)
mesh.Vao.AddVertexBuffer(vbo)
mesh.Vao.SetIndexBuffer(ibo)
// This is needed so that if you load meshes one after the other the
// following mesh doesn't attach its vbo/ibo to this vao
mesh.Vao.UnBind()
return mesh, nil
}
@ -119,9 +132,9 @@ type arrToInterleave struct {
func (a *arrToInterleave) get(i int) []float32 {
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")
assert.T(len(a.V2s) == 0 || len(a.V3s) == 0, "One array should be set in arrToInterleave, but multiple arrays are set")
assert.T(len(a.V2s) == 0 || len(a.V4s) == 0, "One array should be set in arrToInterleave, but multiple arrays are set")
assert.T(len(a.V3s) == 0 || len(a.V4s) == 0, "One array should be set in arrToInterleave, but multiple arrays are set")
if len(a.V2s) > 0 {
return a.V2s[i].Data[:]
@ -173,7 +186,7 @@ func interleave(arrs ...arrToInterleave) []float32 {
func flattenFaces(faces []asig.Face) []uint32 {
assert.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, "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

@ -18,7 +18,7 @@ type Rend3DGL struct {
func (r3d *Rend3DGL) Draw(mesh *meshes.Mesh, trMat *gglm.TrMat, mat *materials.Material) {
if mesh != r3d.BoundMesh {
mesh.Buf.Bind()
mesh.Vao.Bind()
r3d.BoundMesh = mesh
}

Binary file not shown.

View File

@ -64,6 +64,18 @@ struct PointLight {
#define NUM_POINT_LIGHTS 16
uniform PointLight pointLights[NUM_POINT_LIGHTS];
struct SpotLight {
vec3 pos;
vec3 dir;
vec3 diffuseColor;
vec3 specularColor;
float innerCutoff;
float outerCutoff;
};
#define NUM_SPOT_LIGHTS 4
uniform SpotLight spotLights[NUM_SPOT_LIGHTS];
uniform vec3 camPos;
uniform vec3 ambientColor = vec3(0.2, 0.2, 0.2);
@ -90,8 +102,8 @@ vec3 CalcDirLight()
vec3 finalDiffuse = diffuseAmount * dirLight.diffuseColor * diffuseTexColor.rgb;
// Specular
vec3 reflectDir = reflect(-lightDir, normalizedVertNorm);
float specularAmount = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 halfwayDir = normalize(lightDir + viewDir);
float specularAmount = pow(max(dot(normalizedVertNorm, halfwayDir), 0.0), material.shininess);
vec3 finalSpecular = specularAmount * dirLight.specularColor * specularTexColor.rgb;
return finalDiffuse + finalSpecular;
@ -111,8 +123,8 @@ vec3 CalcPointLight(PointLight pointLight)
vec3 finalDiffuse = diffuseAmount * pointLight.diffuseColor * diffuseTexColor.rgb;
// Specular
vec3 reflectDir = reflect(-lightDir, normalizedVertNorm);
float specularAmount = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 halfwayDir = normalize(lightDir + viewDir);
float specularAmount = pow(max(dot(normalizedVertNorm, halfwayDir), 0.0), material.shininess);
vec3 finalSpecular = specularAmount * pointLight.specularColor * specularTexColor.rgb;
// attenuation
@ -122,9 +134,33 @@ vec3 CalcPointLight(PointLight pointLight)
return (finalDiffuse + finalSpecular) * attenuation;
}
vec3 CalcSpotLight()
vec3 CalcSpotLight(SpotLight light)
{
return vec3(0);
if (light.innerCutoff == 0)
return vec3(0);
vec3 fragToLightDir = normalize(light.pos - fragPos);
// Spot light cone with full intensity within inner cutoff,
// and falloff between inner-outer cutoffs, and zero
// light after outer cutoff
float theta = dot(fragToLightDir, normalize(-light.dir));
float epsilon = (light.innerCutoff - light.outerCutoff);
float intensity = clamp((theta - light.outerCutoff) / epsilon, 0.0, 1.0);
if (intensity == 0)
return vec3(0);
// Diffuse
float diffuseAmount = max(0.0, dot(normalizedVertNorm, fragToLightDir));
vec3 finalDiffuse = diffuseAmount * light.diffuseColor * diffuseTexColor.rgb;
// Specular
vec3 halfwayDir = normalize(fragToLightDir + viewDir);
float specularAmount = pow(max(dot(normalizedVertNorm, halfwayDir), 0.0), material.shininess);
vec3 finalSpecular = specularAmount * light.specularColor * specularTexColor.rgb;
return (finalDiffuse + finalSpecular) * intensity;
}
void main()
@ -145,7 +181,10 @@ void main()
finalColor += CalcPointLight(pointLights[i]);
}
finalColor += CalcSpotLight();
for (int i = 0; i < NUM_SPOT_LIGHTS; i++)
{
finalColor += CalcSpotLight(spotLights[i]);
}
vec3 finalEmission = emissionTexColor.rgb;
vec3 finalAmbient = ambientColor * diffuseTexColor.rgb;