Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 692167ada2 | |||
| 524ef068f0 | |||
| b060dcdbe9 | |||
| e22525e2ee | |||
| ee61373069 | |||
| 1f922b6a47 | |||
| 9d7bdc0196 | |||
| 83922f1908 | |||
| c00f6d97dd | |||
| 3c0f82a735 | |||
| c058b82a92 | |||
| 908e5e96aa | |||
| c83e263476 | |||
| 01f06cce1e | |||
| 20ed804d2a | |||
| 80ce6d60d2 | |||
| c998fc26ce | |||
| 81b515197d | |||
| d703a5270c | |||
| caa76c2a5e | |||
| da50d597f9 | |||
| 9f9744a142 | |||
| b101d54049 | |||
| 41b5aea185 | |||
| 3574318552 | |||
| 05ccf3e158 |
4
.github/workflows/build-nmage.yml
vendored
@ -9,10 +9,10 @@ jobs:
|
||||
runs-on: macos-12
|
||||
steps:
|
||||
|
||||
- name: Install golang 1.18
|
||||
- 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
|
||||
|
||||
@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"io"
|
||||
@ -14,6 +13,7 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/go-gl/gl/v4.1-core/gl"
|
||||
"github.com/mandykoh/prism"
|
||||
)
|
||||
|
||||
type ColorFormat int
|
||||
@ -23,11 +23,20 @@ const (
|
||||
)
|
||||
|
||||
type Texture struct {
|
||||
//Path only exists for textures loaded from disk
|
||||
Path string
|
||||
TexID uint32
|
||||
Width int32
|
||||
// Path only exists for textures loaded from disk
|
||||
Path string
|
||||
|
||||
TexID uint32
|
||||
|
||||
// Width is the width of the texture in pixels (pixels per row).
|
||||
// Note that the number of bytes constituting a row is MORE than this (e.g. for RGBA8, bytesPerRow=width*4, since we have 4 bytes per pixel)
|
||||
Width int32
|
||||
|
||||
// Height is the height of the texture in pixels (pixels per column).
|
||||
// Note that the number of bytes constituting a column is MORE than this (e.g. for RGBA8, bytesPerColumn=height*4, since we have 4 bytes per pixel)
|
||||
Height int32
|
||||
|
||||
// Pixels usually stored in RGBA format
|
||||
Pixels []byte
|
||||
}
|
||||
|
||||
@ -36,6 +45,7 @@ type TextureLoadOptions struct {
|
||||
WriteToCache bool
|
||||
GenMipMaps bool
|
||||
KeepPixelsInMem bool
|
||||
NoSrgba bool
|
||||
}
|
||||
|
||||
type Cubemap struct {
|
||||
@ -67,16 +77,20 @@ func LoadTexturePNG(file string, loadOptions *TextureLoadOptions) (Texture, erro
|
||||
return Texture{}, err
|
||||
}
|
||||
|
||||
img, err := png.Decode(bytes.NewReader(fileBytes))
|
||||
bytesReader := bytes.NewReader(fileBytes)
|
||||
img, err := png.Decode(bytesReader)
|
||||
if err != nil {
|
||||
return Texture{}, err
|
||||
}
|
||||
|
||||
nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
|
||||
tex := Texture{
|
||||
Path: file,
|
||||
Path: file,
|
||||
Pixels: nrgbaImg.Pix,
|
||||
Width: int32(nrgbaImg.Bounds().Dx()),
|
||||
Height: int32(nrgbaImg.Bounds().Dy()),
|
||||
}
|
||||
|
||||
tex.Pixels, tex.Width, tex.Height = pixelsFromNrgbaPng(img)
|
||||
flipImgPixelsVertically(tex.Pixels, int(tex.Width), int(tex.Height), 4)
|
||||
|
||||
//Prepare opengl stuff
|
||||
gl.GenTextures(1, &tex.TexID)
|
||||
@ -89,7 +103,12 @@ func LoadTexturePNG(file string, loadOptions *TextureLoadOptions) (Texture, erro
|
||||
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]))
|
||||
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]))
|
||||
|
||||
if loadOptions.GenMipMaps {
|
||||
gl.GenerateMipmap(tex.TexID)
|
||||
@ -112,8 +131,14 @@ func LoadTextureInMemPngImg(img image.Image, loadOptions *TextureLoadOptions) (T
|
||||
loadOptions = &TextureLoadOptions{}
|
||||
}
|
||||
|
||||
tex := Texture{}
|
||||
tex.Pixels, tex.Width, tex.Height = pixelsFromNrgbaPng(img)
|
||||
nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
|
||||
tex := Texture{
|
||||
Path: "",
|
||||
Pixels: nrgbaImg.Pix,
|
||||
Height: int32(nrgbaImg.Bounds().Dy()),
|
||||
Width: int32(nrgbaImg.Bounds().Dx()),
|
||||
}
|
||||
flipImgPixelsVertically(tex.Pixels, int(tex.Width), int(tex.Height), 4)
|
||||
|
||||
//Prepare opengl stuff
|
||||
gl.GenTextures(1, &tex.TexID)
|
||||
@ -126,7 +151,12 @@ func LoadTextureInMemPngImg(img image.Image, loadOptions *TextureLoadOptions) (T
|
||||
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]))
|
||||
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]))
|
||||
|
||||
if loadOptions.GenMipMaps {
|
||||
gl.GenerateMipmap(tex.TexID)
|
||||
@ -166,11 +196,14 @@ func LoadTextureJpeg(file string, loadOptions *TextureLoadOptions) (Texture, err
|
||||
return Texture{}, err
|
||||
}
|
||||
|
||||
nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
|
||||
tex := Texture{
|
||||
Path: file,
|
||||
Path: file,
|
||||
Pixels: nrgbaImg.Pix,
|
||||
Height: int32(nrgbaImg.Bounds().Dy()),
|
||||
Width: int32(nrgbaImg.Bounds().Dx()),
|
||||
}
|
||||
|
||||
tex.Pixels, tex.Width, tex.Height = pixelsFromNrgbaPng(img)
|
||||
flipImgPixelsVertically(tex.Pixels, int(tex.Width), int(tex.Height), 4)
|
||||
|
||||
//Prepare opengl stuff
|
||||
gl.GenTextures(1, &tex.TexID)
|
||||
@ -183,7 +216,12 @@ func LoadTextureJpeg(file string, loadOptions *TextureLoadOptions) (Texture, err
|
||||
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]))
|
||||
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]))
|
||||
|
||||
if loadOptions.GenMipMaps {
|
||||
gl.GenerateMipmap(tex.TexID)
|
||||
@ -200,65 +238,19 @@ func LoadTextureJpeg(file string, loadOptions *TextureLoadOptions) (Texture, err
|
||||
return tex, nil
|
||||
}
|
||||
|
||||
func pixelsFromNrgbaPng(img image.Image) (pixels []byte, width, height int32) {
|
||||
// LoadCubemapTextures only supports the 'TextureIsSrgba' option
|
||||
func LoadCubemapTextures(rightTex, leftTex, topTex, botTex, frontTex, backTex string, loadOptions *TextureLoadOptions) (Cubemap, error) {
|
||||
|
||||
//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
|
||||
width, height = int32(img.Bounds().Dx()), int32(img.Bounds().Dy())
|
||||
pixels = make([]byte, img.Bounds().Dx()*img.Bounds().Dy()*4)
|
||||
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)
|
||||
|
||||
pixels[i] = c.R
|
||||
pixels[i+1] = c.G
|
||||
pixels[i+2] = c.B
|
||||
pixels[i+3] = c.A
|
||||
|
||||
i += 4
|
||||
}
|
||||
if loadOptions == nil {
|
||||
loadOptions = &TextureLoadOptions{}
|
||||
}
|
||||
|
||||
return pixels, width, height
|
||||
}
|
||||
|
||||
func pixelsFromNrgbaJpg(img image.Image) (pixels []byte, width, height int32) {
|
||||
|
||||
//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
|
||||
width, height = int32(img.Bounds().Dx()), int32(img.Bounds().Dy())
|
||||
pixels = make([]byte, img.Bounds().Dx()*img.Bounds().Dy()*4)
|
||||
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)
|
||||
|
||||
pixels[i] = c.R
|
||||
pixels[i+1] = c.G
|
||||
pixels[i+2] = c.B
|
||||
pixels[i+3] = c.A
|
||||
|
||||
i += 4
|
||||
}
|
||||
}
|
||||
|
||||
return pixels, width, height
|
||||
}
|
||||
|
||||
func LoadCubemapTextures(rightTex, leftTex, topTex, botTex, frontTex, backTex string) (Cubemap, error) {
|
||||
|
||||
var imgDecoder func(r io.Reader) (image.Image, error)
|
||||
var pixelDecoder func(image.Image) ([]byte, int32, int32)
|
||||
ext := strings.ToLower(path.Ext(rightTex))
|
||||
if ext == ".jpg" || ext == ".jpeg" {
|
||||
imgDecoder = jpeg.Decode
|
||||
pixelDecoder = pixelsFromNrgbaJpg
|
||||
} else if ext == ".png" {
|
||||
imgDecoder = png.Decode
|
||||
pixelDecoder = pixelsFromNrgbaPng
|
||||
} else {
|
||||
return Cubemap{}, fmt.Errorf("unknown image extension: %s. Expected one of: .jpg, .jpeg, .png", ext)
|
||||
}
|
||||
@ -292,9 +284,16 @@ func LoadCubemapTextures(rightTex, leftTex, topTex, botTex, frontTex, backTex st
|
||||
return Cubemap{}, err
|
||||
}
|
||||
|
||||
pixels, width, height := pixelDecoder(img)
|
||||
nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
|
||||
height := int32(nrgbaImg.Bounds().Dy())
|
||||
width := int32(nrgbaImg.Bounds().Dx())
|
||||
|
||||
gl.TexImage2D(uint32(gl.TEXTURE_CUBE_MAP_POSITIVE_X)+i, 0, gl.RGBA8, int32(width), int32(height), 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&pixels[0]))
|
||||
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]))
|
||||
}
|
||||
|
||||
// set the texture wrapping/filtering options (on the currently bound texture object)
|
||||
@ -306,3 +305,21 @@ func LoadCubemapTextures(rightTex, leftTex, topTex, botTex, frontTex, backTex st
|
||||
|
||||
return cmap, nil
|
||||
}
|
||||
|
||||
func flipImgPixelsVertically(bytes []byte, width, height, bytesPerPixel int) {
|
||||
|
||||
// Flip the image vertically such that (e.g. in an image of 10 rows) rows 0<->9, 1<->8, 2<->7 etc are swapped.
|
||||
// We do this because images are usually stored top-left to bottom-right, while opengl stores textures bottom-left to top-right, so if we don't swap
|
||||
// rows textures will appear inverted
|
||||
widthInBytes := width * bytesPerPixel
|
||||
rowData := make([]byte, width*bytesPerPixel)
|
||||
for rowNum := 0; rowNum < height/2; rowNum++ {
|
||||
|
||||
upperRowStartIndex := rowNum * widthInBytes
|
||||
lowerRowStartIndex := (height - rowNum - 1) * widthInBytes
|
||||
copy(rowData, bytes[upperRowStartIndex:upperRowStartIndex+widthInBytes])
|
||||
copy(bytes[upperRowStartIndex:upperRowStartIndex+widthInBytes], bytes[lowerRowStartIndex:lowerRowStartIndex+widthInBytes])
|
||||
copy(bytes[lowerRowStartIndex:lowerRowStartIndex+widthInBytes], rowData)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
@ -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
@ -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
@ -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
|
||||
}
|
||||
112
engine/engine.go
@ -15,6 +15,13 @@ import (
|
||||
|
||||
var (
|
||||
isInited = false
|
||||
|
||||
isSdlButtonLeftDown = false
|
||||
isSdlButtonMiddleDown = false
|
||||
isSdlButtonRightDown = false
|
||||
|
||||
ImguiRelativeMouseModePosX float32
|
||||
ImguiRelativeMouseModePosY float32
|
||||
)
|
||||
|
||||
type Window struct {
|
||||
@ -29,7 +36,23 @@ func (w *Window) handleInputs() {
|
||||
input.EventLoopStart()
|
||||
imIo := imgui.CurrentIO()
|
||||
|
||||
// @TODO: Would be nice to have imgui package process its own events via a callback instead of it being part of engine code
|
||||
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
|
||||
@ -42,14 +65,18 @@ func (w *Window) handleInputs() {
|
||||
|
||||
case *sdl.MouseWheelEvent:
|
||||
|
||||
input.HandleMouseWheelEvent(e)
|
||||
if !imguiCaptureMouse {
|
||||
input.HandleMouseWheelEvent(e)
|
||||
}
|
||||
|
||||
xDelta, yDelta := input.GetMouseWheelMotion()
|
||||
imIo.AddMouseWheelDelta(float32(xDelta), float32(yDelta))
|
||||
imIo.AddMouseWheelDelta(float32(e.X), float32(e.Y))
|
||||
|
||||
case *sdl.KeyboardEvent:
|
||||
|
||||
input.HandleKeyboardEvent(e)
|
||||
if !imguiCaptureKeyboard {
|
||||
input.HandleKeyboardEvent(e)
|
||||
}
|
||||
|
||||
imIo.AddKeyEvent(nmageimgui.SdlScancodeToImGuiKey(e.Keysym.Scancode), e.Type == sdl.KEYDOWN)
|
||||
|
||||
// Send modifier key updates to imgui
|
||||
@ -73,12 +100,29 @@ func (w *Window) handleInputs() {
|
||||
imIo.AddInputCharactersUTF8(e.GetText())
|
||||
|
||||
case *sdl.MouseButtonEvent:
|
||||
input.HandleMouseBtnEvent(e)
|
||||
|
||||
if !imguiCaptureMouse {
|
||||
input.HandleMouseBtnEvent(e)
|
||||
}
|
||||
|
||||
isPressed := e.State == sdl.PRESSED
|
||||
|
||||
if e.Button == sdl.BUTTON_LEFT {
|
||||
isSdlButtonLeftDown = isPressed
|
||||
} else if e.Button == sdl.BUTTON_MIDDLE {
|
||||
isSdlButtonMiddleDown = isPressed
|
||||
} else if e.Button == sdl.BUTTON_RIGHT {
|
||||
isSdlButtonRightDown = isPressed
|
||||
}
|
||||
|
||||
case *sdl.MouseMotionEvent:
|
||||
input.HandleMouseMotionEvent(e)
|
||||
|
||||
if !imguiCaptureMouse {
|
||||
input.HandleMouseMotionEvent(e)
|
||||
}
|
||||
|
||||
case *sdl.WindowEvent:
|
||||
|
||||
if e.Event == sdl.WINDOWEVENT_SIZE_CHANGED {
|
||||
w.handleWindowResize()
|
||||
}
|
||||
@ -88,13 +132,17 @@ func (w *Window) handleInputs() {
|
||||
}
|
||||
}
|
||||
|
||||
// If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame.
|
||||
x, y, _ := sdl.GetMouseState()
|
||||
imIo.SetMousePos(imgui.Vec2{X: float32(x), Y: float32(y)})
|
||||
if sdl.GetRelativeMouseMode() {
|
||||
imIo.SetMousePos(imgui.Vec2{X: ImguiRelativeMouseModePosX, Y: ImguiRelativeMouseModePosY})
|
||||
} else {
|
||||
x, y, _ := sdl.GetMouseState()
|
||||
imIo.SetMousePos(imgui.Vec2{X: float32(x), Y: float32(y)})
|
||||
}
|
||||
|
||||
imIo.SetMouseButtonDown(0, input.MouseDown(sdl.BUTTON_LEFT))
|
||||
imIo.SetMouseButtonDown(1, input.MouseDown(sdl.BUTTON_RIGHT))
|
||||
imIo.SetMouseButtonDown(2, input.MouseDown(sdl.BUTTON_MIDDLE))
|
||||
// If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame.
|
||||
imIo.SetMouseButtonDown(imgui.MouseButtonLeft, isSdlButtonLeftDown)
|
||||
imIo.SetMouseButtonDown(imgui.MouseButtonRight, isSdlButtonRightDown)
|
||||
imIo.SetMouseButtonDown(imgui.MouseButtonMiddle, isSdlButtonMiddleDown)
|
||||
}
|
||||
|
||||
func (w *Window) handleWindowResize() {
|
||||
@ -133,15 +181,21 @@ func initSDL() error {
|
||||
sdl.GLSetAttribute(sdl.MAJOR_VERSION, 4)
|
||||
sdl.GLSetAttribute(sdl.MINOR_VERSION, 1)
|
||||
|
||||
// R(0-255) G(0-255) B(0-255)
|
||||
sdl.GLSetAttribute(sdl.GL_RED_SIZE, 8)
|
||||
sdl.GLSetAttribute(sdl.GL_GREEN_SIZE, 8)
|
||||
sdl.GLSetAttribute(sdl.GL_BLUE_SIZE, 8)
|
||||
sdl.GLSetAttribute(sdl.GL_ALPHA_SIZE, 8)
|
||||
|
||||
sdl.GLSetAttribute(sdl.GL_DOUBLEBUFFER, 1)
|
||||
sdl.GLSetAttribute(sdl.GL_DEPTH_SIZE, 24)
|
||||
sdl.GLSetAttribute(sdl.GL_STENCIL_SIZE, 8)
|
||||
|
||||
sdl.GLSetAttribute(sdl.GL_FRAMEBUFFER_SRGB_CAPABLE, 1)
|
||||
|
||||
// Allows us to do MSAA
|
||||
sdl.GLSetAttribute(sdl.GL_MULTISAMPLEBUFFERS, 1)
|
||||
sdl.GLSetAttribute(sdl.GL_MULTISAMPLESAMPLES, 4)
|
||||
|
||||
sdl.GLSetAttribute(sdl.GL_CONTEXT_PROFILE_MASK, sdl.GL_CONTEXT_PROFILE_CORE)
|
||||
|
||||
return nil
|
||||
@ -152,16 +206,12 @@ func CreateOpenGLWindow(title string, x, y, width, height int32, flags WindowFla
|
||||
}
|
||||
|
||||
func CreateOpenGLWindowCentered(title string, width, height int32, flags WindowFlags, rend renderer.Render) (*Window, error) {
|
||||
return createWindow(title, -1, -1, width, height, WindowFlags_OPENGL|flags, rend)
|
||||
return createWindow(title, sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, width, height, WindowFlags_OPENGL|flags, rend)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
assert.T(isInited, "engine.Init() was not called!")
|
||||
|
||||
sdlWin, err := sdl.CreateWindow(title, x, y, width, height, uint32(flags))
|
||||
if err != nil {
|
||||
@ -193,19 +243,30 @@ 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)
|
||||
|
||||
gl.Enable(gl.BLEND)
|
||||
gl.Enable(gl.MULTISAMPLE)
|
||||
gl.Enable(gl.FRAMEBUFFER_SRGB)
|
||||
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
|
||||
|
||||
gl.ClearColor(0, 0, 0, 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetSrgbFramebuffer(isEnabled bool) {
|
||||
|
||||
if isEnabled {
|
||||
gl.Enable(gl.FRAMEBUFFER_SRGB)
|
||||
} else {
|
||||
gl.Disable(gl.FRAMEBUFFER_SRGB)
|
||||
}
|
||||
}
|
||||
|
||||
func SetVSync(enabled bool) {
|
||||
assert.T(isInited, "engine.Init was not called!")
|
||||
|
||||
if enabled {
|
||||
sdl.GLSetSwapInterval(1)
|
||||
@ -213,3 +274,12 @@ func SetVSync(enabled bool) {
|
||||
sdl.GLSetSwapInterval(0)
|
||||
}
|
||||
}
|
||||
|
||||
func SetMSAA(isEnabled bool) {
|
||||
|
||||
if isEnabled {
|
||||
gl.Enable(gl.MULTISAMPLE)
|
||||
} else {
|
||||
gl.Disable(gl.MULTISAMPLE)
|
||||
}
|
||||
}
|
||||
|
||||
9
go.mod
@ -1,6 +1,6 @@
|
||||
module github.com/bloeys/nmage
|
||||
|
||||
go 1.18
|
||||
go 1.22
|
||||
|
||||
require github.com/veandco/go-sdl2 v0.4.35
|
||||
|
||||
@ -11,4 +11,9 @@ require (
|
||||
github.com/bloeys/gglm v0.43.0
|
||||
)
|
||||
|
||||
require github.com/AllenDang/cimgui-go v0.0.0-20230720025235-f2ff398a66b2
|
||||
require (
|
||||
github.com/AllenDang/cimgui-go v0.0.0-20230720025235-f2ff398a66b2
|
||||
github.com/mandykoh/prism v0.35.1
|
||||
)
|
||||
|
||||
require github.com/mandykoh/go-parallel v0.1.0 // indirect
|
||||
|
||||
30
go.sum
@ -6,5 +6,35 @@ github.com/bloeys/gglm v0.43.0 h1:ZpOghR3PHfpkigTDh+FqxLsF0gN8CD6s/bWoei6LyxI=
|
||||
github.com/bloeys/gglm v0.43.0/go.mod h1:qwJQ0WzV191wAMwlGicbfbChbKoSedMk7gFFX6GnyOk=
|
||||
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/mandykoh/go-parallel v0.1.0 h1:7vJMNMC4dsbgZdkAb2A8tV5ENY1v7VxIO1wzQWZoT8k=
|
||||
github.com/mandykoh/go-parallel v0.1.0/go.mod h1:lkYHqG1JNTaSS6lG+PgFCnyMd2VDy8pH9jN9pY899ig=
|
||||
github.com/mandykoh/prism v0.35.1 h1:JbQfQarANxSWlgJEpjv+E7DvtrqBaVP1YgJfZPvo6ME=
|
||||
github.com/mandykoh/prism v0.35.1/go.mod h1:3miB3EAJ0IggYl/4eBB5MmawRbyJI1gKDtbrVvk8Q9I=
|
||||
github.com/veandco/go-sdl2 v0.4.35 h1:NohzsfageDWGtCd9nf7Pc3sokMK/MOK+UA2QMJARWzQ=
|
||||
github.com/veandco/go-sdl2 v0.4.35/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
|
||||
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@ -31,8 +31,8 @@ type mouseWheelState struct {
|
||||
}
|
||||
|
||||
var (
|
||||
keyMap = make(map[sdl.Keycode]*keyState)
|
||||
mouseBtnMap = make(map[int]*mouseBtnState)
|
||||
keyMap = make(map[sdl.Keycode]keyState)
|
||||
mouseBtnMap = make(map[int]mouseBtnState)
|
||||
mouseMotion = mouseMotionState{}
|
||||
mouseWheel = mouseWheelState{}
|
||||
quitRequested bool
|
||||
@ -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
|
||||
}
|
||||
@ -70,29 +82,31 @@ func IsQuitClicked() bool {
|
||||
|
||||
func HandleKeyboardEvent(e *sdl.KeyboardEvent) {
|
||||
|
||||
ks := keyMap[e.Keysym.Sym]
|
||||
if ks == nil {
|
||||
ks = &keyState{Key: e.Keysym.Sym}
|
||||
keyMap[ks.Key] = ks
|
||||
ks, ok := keyMap[e.Keysym.Sym]
|
||||
if !ok {
|
||||
ks = keyState{Key: e.Keysym.Sym}
|
||||
}
|
||||
|
||||
ks.State = int(e.State)
|
||||
ks.IsPressedThisFrame = e.State == sdl.PRESSED && e.Repeat == 0
|
||||
ks.IsReleasedThisFrame = e.State == sdl.RELEASED && e.Repeat == 0
|
||||
|
||||
keyMap[ks.Key] = ks
|
||||
}
|
||||
|
||||
func HandleMouseBtnEvent(e *sdl.MouseButtonEvent) {
|
||||
|
||||
mb := mouseBtnMap[int(e.Button)]
|
||||
if mb == nil {
|
||||
mb = &mouseBtnState{Btn: int(e.Button)}
|
||||
mouseBtnMap[int(e.Button)] = mb
|
||||
mb, ok := mouseBtnMap[int(e.Button)]
|
||||
if !ok {
|
||||
mb = mouseBtnState{Btn: int(e.Button)}
|
||||
}
|
||||
|
||||
mb.State = int(e.State)
|
||||
mb.IsDoubleClicked = e.Clicks == 2 && e.State == sdl.PRESSED
|
||||
mb.IsPressedThisFrame = e.State == sdl.PRESSED
|
||||
mb.IsReleasedThisFrame = e.State == sdl.RELEASED
|
||||
|
||||
mouseBtnMap[int(e.Button)] = mb
|
||||
}
|
||||
|
||||
func HandleMouseMotionEvent(e *sdl.MouseMotionEvent) {
|
||||
@ -109,12 +123,12 @@ func HandleMouseWheelEvent(e *sdl.MouseWheelEvent) {
|
||||
mouseWheel.YDelta = e.Y
|
||||
}
|
||||
|
||||
//GetMousePos returns the window coordinates of the mouse
|
||||
// GetMousePos returns the window coordinates of the mouse
|
||||
func GetMousePos() (x, y int32) {
|
||||
return mouseMotion.XPos, mouseMotion.YPos
|
||||
}
|
||||
|
||||
//GetMouseMotion returns how many pixels were moved last frame
|
||||
// GetMouseMotion returns how many pixels were moved last frame
|
||||
func GetMouseMotion() (xDelta, yDelta int32) {
|
||||
return mouseMotion.XDelta, mouseMotion.YDelta
|
||||
}
|
||||
@ -141,7 +155,7 @@ func GetMouseWheelMotion() (xDelta, yDelta int32) {
|
||||
return mouseWheel.XDelta, mouseWheel.YDelta
|
||||
}
|
||||
|
||||
//GetMouseWheelXNorm returns 1 if mouse wheel xDelta > 0, -1 if xDelta < 0, and 0 otherwise
|
||||
// GetMouseWheelXNorm returns 1 if mouse wheel xDelta > 0, -1 if xDelta < 0, and 0 otherwise
|
||||
func GetMouseWheelXNorm() int32 {
|
||||
|
||||
if mouseWheel.XDelta > 0 {
|
||||
@ -153,7 +167,7 @@ func GetMouseWheelXNorm() int32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
//returns 1 if mouse wheel yDelta > 0, -1 if yDelta < 0, and 0 otherwise
|
||||
// returns 1 if mouse wheel yDelta > 0, -1 if yDelta < 0, and 0 otherwise
|
||||
func GetMouseWheelYNorm() int32 {
|
||||
|
||||
if mouseWheel.YDelta > 0 {
|
||||
@ -167,8 +181,8 @@ func GetMouseWheelYNorm() int32 {
|
||||
|
||||
func KeyClicked(kc sdl.Keycode) bool {
|
||||
|
||||
ks := keyMap[kc]
|
||||
if ks == nil {
|
||||
ks, ok := keyMap[kc]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -177,8 +191,8 @@ func KeyClicked(kc sdl.Keycode) bool {
|
||||
|
||||
func KeyReleased(kc sdl.Keycode) bool {
|
||||
|
||||
ks := keyMap[kc]
|
||||
if ks == nil {
|
||||
ks, ok := keyMap[kc]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -187,8 +201,8 @@ func KeyReleased(kc sdl.Keycode) bool {
|
||||
|
||||
func KeyDown(kc sdl.Keycode) bool {
|
||||
|
||||
ks := keyMap[kc]
|
||||
if ks == nil {
|
||||
ks, ok := keyMap[kc]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -197,8 +211,8 @@ func KeyDown(kc sdl.Keycode) bool {
|
||||
|
||||
func KeyUp(kc sdl.Keycode) bool {
|
||||
|
||||
ks := keyMap[kc]
|
||||
if ks == nil {
|
||||
ks, ok := keyMap[kc]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -207,8 +221,8 @@ func KeyUp(kc sdl.Keycode) bool {
|
||||
|
||||
func MouseClicked(mb int) bool {
|
||||
|
||||
btn := mouseBtnMap[mb]
|
||||
if btn == nil {
|
||||
btn, ok := mouseBtnMap[mb]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -217,8 +231,8 @@ func MouseClicked(mb int) bool {
|
||||
|
||||
func MouseDoubleClicked(mb int) bool {
|
||||
|
||||
btn := mouseBtnMap[mb]
|
||||
if btn == nil {
|
||||
btn, ok := mouseBtnMap[mb]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -226,8 +240,8 @@ func MouseDoubleClicked(mb int) bool {
|
||||
}
|
||||
|
||||
func MouseReleased(mb int) bool {
|
||||
btn := mouseBtnMap[mb]
|
||||
if btn == nil {
|
||||
btn, ok := mouseBtnMap[mb]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -236,8 +250,8 @@ func MouseReleased(mb int) bool {
|
||||
|
||||
func MouseDown(mb int) bool {
|
||||
|
||||
btn := mouseBtnMap[mb]
|
||||
if btn == nil {
|
||||
btn, ok := mouseBtnMap[mb]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -246,8 +260,8 @@ func MouseDown(mb int) bool {
|
||||
|
||||
func MouseUp(mb int) bool {
|
||||
|
||||
btn := mouseBtnMap[mb]
|
||||
if btn == nil {
|
||||
btn, ok := mouseBtnMap[mb]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
600
main.go
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
imgui "github.com/AllenDang/cimgui-go"
|
||||
"github.com/bloeys/gglm/gglm"
|
||||
@ -22,16 +23,66 @@ import (
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
// @Todo:
|
||||
// Integrate physx
|
||||
// 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
|
||||
// Frustum culling
|
||||
// Material system editor with fields automatically extracted from the shader
|
||||
/*
|
||||
@TODO:
|
||||
- Rendering:
|
||||
- Blinn-Phong lighting model ✅
|
||||
- Directional lights ✅
|
||||
- Point lights ✅
|
||||
- Spotlights ✅
|
||||
- HDR
|
||||
- Cascaded shadow mapping
|
||||
- Skeletal animations
|
||||
- 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?
|
||||
- Abstract keys enum away from sdl
|
||||
- Proper Asset loading
|
||||
- Frustum culling
|
||||
- Material system editor with fields automatically extracted from the shader
|
||||
*/
|
||||
|
||||
type DirLight struct {
|
||||
Dir gglm.Vec3
|
||||
DiffuseColor gglm.Vec3
|
||||
SpecularColor gglm.Vec3
|
||||
}
|
||||
|
||||
// Check https://wiki.ogre3d.org/tiki-index.php?page=-Point+Light+Attenuation for values
|
||||
type PointLight struct {
|
||||
Pos gglm.Vec3
|
||||
DiffuseColor gglm.Vec3
|
||||
SpecularColor gglm.Vec3
|
||||
|
||||
Radius float32
|
||||
|
||||
Constant float32
|
||||
Linear float32
|
||||
Quadratic float32
|
||||
}
|
||||
|
||||
type SpotLight struct {
|
||||
Pos gglm.Vec3
|
||||
Dir gglm.Vec3
|
||||
DiffuseColor gglm.Vec3
|
||||
SpecularColor gglm.Vec3
|
||||
InnerCutoff float32
|
||||
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
|
||||
@ -45,30 +96,87 @@ var (
|
||||
window *engine.Window
|
||||
|
||||
pitch float32 = 0
|
||||
yaw float32 = -90
|
||||
yaw float32 = -1.5
|
||||
cam *camera.Camera
|
||||
|
||||
simpleMat *materials.Material
|
||||
skyboxMat *materials.Material
|
||||
whiteMat *materials.Material
|
||||
containerMat *materials.Material
|
||||
palleteMat *materials.Material
|
||||
skyboxMat *materials.Material
|
||||
debugDepthMat *materials.Material
|
||||
|
||||
chairMesh *meshes.Mesh
|
||||
cubeMesh *meshes.Mesh
|
||||
sphereMesh *meshes.Mesh
|
||||
chairMesh *meshes.Mesh
|
||||
skyboxMesh *meshes.Mesh
|
||||
|
||||
cubeModelMat = gglm.NewTrMatId()
|
||||
|
||||
lightPos1 = gglm.NewVec3(-2, 0, 2)
|
||||
lightColor1 = gglm.NewVec3(1, 1, 1)
|
||||
|
||||
debugDepthMat *materials.Material
|
||||
drawSkybox = true
|
||||
debugDrawDepthBuffer bool
|
||||
|
||||
skyboxCmap assets.Cubemap
|
||||
|
||||
dpiScaling float32
|
||||
|
||||
// Light settings
|
||||
ambientColor = gglm.NewVec3(0, 0, 0)
|
||||
|
||||
// Lights
|
||||
dirLight = DirLight{
|
||||
Dir: *gglm.NewVec3(0, -0.8, 0.2).Normalize(),
|
||||
DiffuseColor: *gglm.NewVec3(0, 0, 0),
|
||||
SpecularColor: *gglm.NewVec3(0, 0, 0),
|
||||
}
|
||||
pointLights = [...]PointLight{
|
||||
{
|
||||
Pos: *gglm.NewVec3(0, 5, 0),
|
||||
DiffuseColor: *gglm.NewVec3(1, 0, 0),
|
||||
SpecularColor: *gglm.NewVec3(1, 1, 1),
|
||||
// These values are for 50m range
|
||||
Constant: 1.0,
|
||||
Linear: 0.09,
|
||||
Quadratic: 0.032,
|
||||
},
|
||||
{
|
||||
Pos: *gglm.NewVec3(0, -5, 0),
|
||||
DiffuseColor: *gglm.NewVec3(0, 1, 0),
|
||||
SpecularColor: *gglm.NewVec3(1, 1, 1),
|
||||
Constant: 1.0,
|
||||
Linear: 0.09,
|
||||
Quadratic: 0.032,
|
||||
},
|
||||
{
|
||||
Pos: *gglm.NewVec3(5, 0, 0),
|
||||
DiffuseColor: *gglm.NewVec3(1, 1, 1),
|
||||
SpecularColor: *gglm.NewVec3(1, 1, 1),
|
||||
Constant: 1.0,
|
||||
Linear: 0.09,
|
||||
Quadratic: 0.032,
|
||||
},
|
||||
{
|
||||
Pos: *gglm.NewVec3(-4, 0, 0),
|
||||
DiffuseColor: *gglm.NewVec3(0, 0, 1),
|
||||
SpecularColor: *gglm.NewVec3(1, 1, 1),
|
||||
Constant: 1.0,
|
||||
Linear: 0.09,
|
||||
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 OurGame struct {
|
||||
type Game struct {
|
||||
Win *engine.Window
|
||||
ImGUIInfo nmageimgui.ImguiInfo
|
||||
}
|
||||
@ -141,18 +249,20 @@ func main() {
|
||||
}
|
||||
defer window.Destroy()
|
||||
|
||||
engine.SetMSAA(true)
|
||||
engine.SetVSync(false)
|
||||
engine.SetSrgbFramebuffer(true)
|
||||
|
||||
game := &OurGame{
|
||||
game := &Game{
|
||||
Win: window,
|
||||
ImGUIInfo: nmageimgui.NewImGui(),
|
||||
ImGUIInfo: nmageimgui.NewImGui("./res/shaders/imgui.glsl"),
|
||||
}
|
||||
window.EventCallbacks = append(window.EventCallbacks, game.handleWindowEvents)
|
||||
|
||||
engine.Run(game, window, game.ImGUIInfo)
|
||||
}
|
||||
|
||||
func (g *OurGame) handleWindowEvents(e sdl.Event) {
|
||||
func (g *Game) handleWindowEvents(e sdl.Event) {
|
||||
|
||||
switch e := e.(type) {
|
||||
case *sdl.WindowEvent:
|
||||
@ -163,7 +273,7 @@ func (g *OurGame) handleWindowEvents(e sdl.Event) {
|
||||
cam.AspectRatio = float32(width) / float32(height)
|
||||
cam.Update()
|
||||
|
||||
simpleMat.SetUnifMat4("projMat", &cam.ProjMat)
|
||||
palleteMat.SetUnifMat4("projMat", &cam.ProjMat)
|
||||
debugDepthMat.SetUnifMat4("projMat", &cam.ProjMat)
|
||||
}
|
||||
}
|
||||
@ -203,17 +313,28 @@ func getDpiScaling(unscaledWindowWidth, unscaledWindowHeight int32) float32 {
|
||||
return dpiScaling
|
||||
}
|
||||
|
||||
func (g *OurGame) Init() {
|
||||
func (g *Game) Init() {
|
||||
|
||||
var err error
|
||||
|
||||
//Create materials
|
||||
simpleMat = materials.NewMaterial("Simple mat", "./res/shaders/simple.glsl")
|
||||
debugDepthMat = materials.NewMaterial("Debug depth mat", "./res/shaders/debug-depth.glsl")
|
||||
skyboxMat = materials.NewMaterial("Skybox mat", "./res/shaders/skybox.glsl")
|
||||
// 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, 200,
|
||||
45*gglm.Deg2Rad,
|
||||
float32(winWidth)/float32(winHeight),
|
||||
)
|
||||
|
||||
//Load meshes
|
||||
cubeMesh, err = meshes.NewMesh("Cube", "./res/models/tex-cube.fbx", 0)
|
||||
cubeMesh, err = meshes.NewMesh("Cube", "./res/models/cube.fbx", 0)
|
||||
if err != nil {
|
||||
logging.ErrLog.Fatalln("Failed to load mesh. Err: ", err)
|
||||
}
|
||||
|
||||
sphereMesh, err = meshes.NewMesh("Sphere", "./res/models/sphere.fbx", 0)
|
||||
if err != nil {
|
||||
logging.ErrLog.Fatalln("Failed to load mesh. Err: ", err)
|
||||
}
|
||||
@ -229,7 +350,27 @@ func (g *OurGame) Init() {
|
||||
}
|
||||
|
||||
//Load textures
|
||||
tex, err := assets.LoadTexturePNG("./res/textures/pallete-endesga-64-1x.png", nil)
|
||||
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{})
|
||||
if err != nil {
|
||||
logging.ErrLog.Fatalln("Failed to load texture. Err: ", err)
|
||||
}
|
||||
|
||||
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{})
|
||||
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{})
|
||||
if err != nil {
|
||||
logging.ErrLog.Fatalln("Failed to load texture. Err: ", err)
|
||||
}
|
||||
@ -238,42 +379,142 @@ func (g *OurGame) 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{},
|
||||
)
|
||||
if err != nil {
|
||||
logging.ErrLog.Fatalln("Failed to load cubemap. Err: ", err)
|
||||
}
|
||||
|
||||
// Configure materials
|
||||
simpleMat.DiffuseTex = tex.TexID
|
||||
// Create materials and assign any unused texture slots to black
|
||||
whiteMat = materials.NewMaterial("White mat", "./res/shaders/simple.glsl")
|
||||
whiteMat.Shininess = 64
|
||||
whiteMat.DiffuseTex = whiteTex.TexID
|
||||
whiteMat.SpecularTex = blackTex.TexID
|
||||
whiteMat.NormalTex = blackTex.TexID
|
||||
whiteMat.EmissionTex = blackTex.TexID
|
||||
whiteMat.SetUnifInt32("material.diffuse", 0)
|
||||
whiteMat.SetUnifInt32("material.specular", 1)
|
||||
// whiteMat.SetUnifInt32("material.normal", 2)
|
||||
whiteMat.SetUnifInt32("material.emission", 3)
|
||||
whiteMat.SetUnifMat4("projMat", &cam.ProjMat)
|
||||
whiteMat.SetUnifVec3("ambientColor", ambientColor)
|
||||
whiteMat.SetUnifFloat32("material.shininess", whiteMat.Shininess)
|
||||
whiteMat.SetUnifVec3("dirLight.dir", &dirLight.Dir)
|
||||
whiteMat.SetUnifVec3("dirLight.diffuseColor", &dirLight.DiffuseColor)
|
||||
whiteMat.SetUnifVec3("dirLight.specularColor", &dirLight.SpecularColor)
|
||||
|
||||
//Movement, scale and rotation
|
||||
containerMat = materials.NewMaterial("Container mat", "./res/shaders/simple.glsl")
|
||||
containerMat.Shininess = 64
|
||||
containerMat.DiffuseTex = containerDiffuseTex.TexID
|
||||
containerMat.SpecularTex = containerSpecularTex.TexID
|
||||
containerMat.NormalTex = blackTex.TexID
|
||||
containerMat.EmissionTex = blackTex.TexID
|
||||
containerMat.SetUnifInt32("material.diffuse", 0)
|
||||
containerMat.SetUnifInt32("material.specular", 1)
|
||||
// containerMat.SetUnifInt32("material.normal", 2)
|
||||
containerMat.SetUnifInt32("material.emission", 3)
|
||||
containerMat.SetUnifMat4("projMat", &cam.ProjMat)
|
||||
containerMat.SetUnifVec3("ambientColor", ambientColor)
|
||||
containerMat.SetUnifFloat32("material.shininess", containerMat.Shininess)
|
||||
containerMat.SetUnifVec3("dirLight.dir", &dirLight.Dir)
|
||||
containerMat.SetUnifVec3("dirLight.diffuseColor", &dirLight.DiffuseColor)
|
||||
containerMat.SetUnifVec3("dirLight.specularColor", &dirLight.SpecularColor)
|
||||
|
||||
palleteMat = materials.NewMaterial("Pallete mat", "./res/shaders/simple.glsl")
|
||||
palleteMat.Shininess = 64
|
||||
palleteMat.DiffuseTex = palleteTex.TexID
|
||||
palleteMat.SpecularTex = blackTex.TexID
|
||||
palleteMat.NormalTex = blackTex.TexID
|
||||
palleteMat.EmissionTex = blackTex.TexID
|
||||
palleteMat.SetUnifInt32("material.diffuse", 0)
|
||||
palleteMat.SetUnifInt32("material.specular", 1)
|
||||
// palleteMat.SetUnifInt32("material.normal", 2)
|
||||
palleteMat.SetUnifInt32("material.emission", 3)
|
||||
palleteMat.SetUnifMat4("projMat", &cam.ProjMat)
|
||||
palleteMat.SetUnifVec3("ambientColor", ambientColor)
|
||||
palleteMat.SetUnifFloat32("material.shininess", palleteMat.Shininess)
|
||||
palleteMat.SetUnifVec3("dirLight.diffuseColor", &dirLight.DiffuseColor)
|
||||
palleteMat.SetUnifVec3("dirLight.specularColor", &dirLight.SpecularColor)
|
||||
|
||||
debugDepthMat = materials.NewMaterial("Debug depth mat", "./res/shaders/debug-depth.glsl")
|
||||
debugDepthMat.SetUnifMat4("projMat", &cam.ProjMat)
|
||||
|
||||
skyboxMat = materials.NewMaterial("Skybox mat", "./res/shaders/skybox.glsl")
|
||||
|
||||
// Movement, scale and rotation
|
||||
translationMat := gglm.NewTranslationMat(gglm.NewVec3(0, 0, 0))
|
||||
scaleMat := gglm.NewScaleMat(gglm.NewVec3(1, 1, 1))
|
||||
rotMat := gglm.NewRotMat(gglm.NewQuatEuler(gglm.NewVec3(-90, -90, 0).AsRad()))
|
||||
|
||||
cubeModelMat.Mul(translationMat.Mul(rotMat.Mul(scaleMat)))
|
||||
|
||||
// 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, 200,
|
||||
45*gglm.Deg2Rad,
|
||||
float32(winWidth)/float32(winHeight),
|
||||
)
|
||||
simpleMat.SetUnifMat4("projMat", &cam.ProjMat)
|
||||
debugDepthMat.SetUnifMat4("projMat", &cam.ProjMat)
|
||||
|
||||
g.updateLights()
|
||||
updateViewMat()
|
||||
|
||||
//Lights
|
||||
simpleMat.SetUnifVec3("lightPos1", lightPos1)
|
||||
simpleMat.SetUnifVec3("lightColor1", lightColor1)
|
||||
}
|
||||
|
||||
func (g *OurGame) Update() {
|
||||
func (g *Game) updateLights() {
|
||||
|
||||
for i := 0; i < len(pointLights); i++ {
|
||||
|
||||
pl := &pointLights[i]
|
||||
indexString := "pointLights[" + strconv.Itoa(i) + "]"
|
||||
|
||||
whiteMat.SetUnifVec3(indexString+".pos", &pl.Pos)
|
||||
containerMat.SetUnifVec3(indexString+".pos", &pl.Pos)
|
||||
palleteMat.SetUnifVec3(indexString+".pos", &pl.Pos)
|
||||
|
||||
whiteMat.SetUnifVec3(indexString+".diffuseColor", &pl.DiffuseColor)
|
||||
containerMat.SetUnifVec3(indexString+".diffuseColor", &pl.DiffuseColor)
|
||||
palleteMat.SetUnifVec3(indexString+".diffuseColor", &pl.DiffuseColor)
|
||||
|
||||
whiteMat.SetUnifVec3(indexString+".specularColor", &pl.SpecularColor)
|
||||
containerMat.SetUnifVec3(indexString+".specularColor", &pl.SpecularColor)
|
||||
palleteMat.SetUnifVec3(indexString+".specularColor", &pl.SpecularColor)
|
||||
|
||||
whiteMat.SetUnifFloat32(indexString+".constant", pl.Constant)
|
||||
containerMat.SetUnifFloat32(indexString+".constant", pl.Constant)
|
||||
palleteMat.SetUnifFloat32(indexString+".constant", pl.Constant)
|
||||
|
||||
whiteMat.SetUnifFloat32(indexString+".linear", pl.Linear)
|
||||
containerMat.SetUnifFloat32(indexString+".linear", pl.Linear)
|
||||
palleteMat.SetUnifFloat32(indexString+".linear", pl.Linear)
|
||||
|
||||
whiteMat.SetUnifFloat32(indexString+".quadratic", pl.Quadratic)
|
||||
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() {
|
||||
|
||||
if input.IsQuitClicked() || input.KeyClicked(sdl.K_ESCAPE) {
|
||||
engine.Quit()
|
||||
@ -282,31 +523,12 @@ func (g *OurGame) Update() {
|
||||
g.updateCameraLookAround()
|
||||
g.updateCameraPos()
|
||||
|
||||
imgui.ShowDemoWindow()
|
||||
|
||||
//Rotating cubes
|
||||
if input.KeyDown(sdl.K_SPACE) {
|
||||
cubeModelMat.Rotate(10*timing.DT()*gglm.Deg2Rad, gglm.NewVec3(1, 1, 1).Normalize())
|
||||
}
|
||||
|
||||
imgui.Begin("Debug controls")
|
||||
if imgui.DragFloat3("Cam Pos", &cam.Pos.Data) {
|
||||
updateViewMat()
|
||||
}
|
||||
if imgui.DragFloat3("Cam Forward", &cam.Forward.Data) {
|
||||
updateViewMat()
|
||||
}
|
||||
|
||||
if imgui.DragFloat3("Light Pos 1", &lightPos1.Data) {
|
||||
simpleMat.SetUnifVec3("lightPos1", lightPos1)
|
||||
}
|
||||
|
||||
if imgui.DragFloat3("Light Color 1", &lightColor1.Data) {
|
||||
simpleMat.SetUnifVec3("lightColor1", lightColor1)
|
||||
}
|
||||
|
||||
imgui.Checkbox("Debug depth buffer", &debugDrawDepthBuffer)
|
||||
imgui.End()
|
||||
g.showDebugWindow()
|
||||
|
||||
if input.KeyClicked(sdl.K_F4) {
|
||||
fmt.Printf("Pos: %s; Forward: %s; |Forward|: %f\n", cam.Pos.String(), cam.Forward.String(), cam.Forward.Mag())
|
||||
@ -315,7 +537,172 @@ func (g *OurGame) Update() {
|
||||
g.Win.SDLWin.SetTitle(fmt.Sprint("nMage (", timing.GetAvgFPS(), " fps)"))
|
||||
}
|
||||
|
||||
func (g *OurGame) updateCameraLookAround() {
|
||||
func (g *Game) showDebugWindow() {
|
||||
|
||||
imgui.ShowDemoWindow()
|
||||
|
||||
imgui.Begin("Debug controls")
|
||||
|
||||
// Camera
|
||||
imgui.Text("Camera")
|
||||
if imgui.DragFloat3("Cam Pos", &cam.Pos.Data) {
|
||||
updateViewMat()
|
||||
}
|
||||
if imgui.DragFloat3("Cam Forward", &cam.Forward.Data) {
|
||||
updateViewMat()
|
||||
}
|
||||
|
||||
imgui.Spacing()
|
||||
|
||||
// Ambient light
|
||||
imgui.Text("Ambient Light")
|
||||
|
||||
if imgui.DragFloat3("Ambient Color", &ambientColor.Data) {
|
||||
whiteMat.SetUnifVec3("ambientColor", ambientColor)
|
||||
containerMat.SetUnifVec3("ambientColor", ambientColor)
|
||||
palleteMat.SetUnifVec3("ambientColor", ambientColor)
|
||||
}
|
||||
|
||||
imgui.Spacing()
|
||||
|
||||
// Specular
|
||||
imgui.Text("Specular Settings")
|
||||
|
||||
if imgui.DragFloat("Specular Shininess", &whiteMat.Shininess) {
|
||||
whiteMat.SetUnifFloat32("material.shininess", whiteMat.Shininess)
|
||||
containerMat.SetUnifFloat32("material.shininess", whiteMat.Shininess)
|
||||
palleteMat.SetUnifFloat32("material.shininess", whiteMat.Shininess)
|
||||
}
|
||||
|
||||
imgui.Spacing()
|
||||
|
||||
// Directional light
|
||||
imgui.Text("Directional Light")
|
||||
|
||||
if imgui.DragFloat3("Direction", &dirLight.Dir.Data) {
|
||||
whiteMat.SetUnifVec3("dirLight.dir", &dirLight.Dir)
|
||||
containerMat.SetUnifVec3("dirLight.dir", &dirLight.Dir)
|
||||
palleteMat.SetUnifVec3("dirLight.dir", &dirLight.Dir)
|
||||
}
|
||||
|
||||
if imgui.DragFloat3("Diffuse Color", &dirLight.DiffuseColor.Data) {
|
||||
whiteMat.SetUnifVec3("dirLight.diffuseColor", &dirLight.DiffuseColor)
|
||||
containerMat.SetUnifVec3("dirLight.diffuseColor", &dirLight.DiffuseColor)
|
||||
palleteMat.SetUnifVec3("dirLight.diffuseColor", &dirLight.DiffuseColor)
|
||||
}
|
||||
|
||||
if imgui.DragFloat3("Specular Color", &dirLight.SpecularColor.Data) {
|
||||
whiteMat.SetUnifVec3("dirLight.specularColor", &dirLight.SpecularColor)
|
||||
containerMat.SetUnifVec3("dirLight.specularColor", &dirLight.SpecularColor)
|
||||
palleteMat.SetUnifVec3("dirLight.specularColor", &dirLight.SpecularColor)
|
||||
}
|
||||
|
||||
imgui.Spacing()
|
||||
|
||||
// Point lights
|
||||
if imgui.BeginListBoxV("Point Lights", imgui.Vec2{Y: 200}) {
|
||||
|
||||
for i := 0; i < len(pointLights); i++ {
|
||||
|
||||
pl := &pointLights[i]
|
||||
indexNumString := strconv.Itoa(i)
|
||||
|
||||
if !imgui.TreeNodeExStrV("Point Light "+indexNumString, imgui.TreeNodeFlagsSpanAvailWidth) {
|
||||
continue
|
||||
}
|
||||
|
||||
indexString := "pointLights[" + indexNumString + "]"
|
||||
|
||||
if imgui.DragFloat3("Pos", &pl.Pos.Data) {
|
||||
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(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(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()
|
||||
}
|
||||
|
||||
imgui.EndListBox()
|
||||
}
|
||||
|
||||
// Other
|
||||
imgui.Text("Other Settings")
|
||||
|
||||
imgui.Checkbox("Draw Skybox", &drawSkybox)
|
||||
imgui.Checkbox("Debug depth buffer", &debugDrawDepthBuffer)
|
||||
|
||||
imgui.End()
|
||||
}
|
||||
|
||||
func (g *Game) updateCameraLookAround() {
|
||||
|
||||
mouseX, mouseY := input.GetMouseMotion()
|
||||
if (mouseX == 0 && mouseY == 0) || !input.MouseDown(sdl.BUTTON_RIGHT) {
|
||||
@ -327,12 +714,12 @@ func (g *OurGame) updateCameraLookAround() {
|
||||
|
||||
// Pitch
|
||||
pitch += float32(-mouseY) * mouseSensitivity * timing.DT()
|
||||
if pitch > 89.0 {
|
||||
pitch = 89.0
|
||||
if pitch > 1.5 {
|
||||
pitch = 1.5
|
||||
}
|
||||
|
||||
if pitch < -89.0 {
|
||||
pitch = -89.0
|
||||
if pitch < -1.5 {
|
||||
pitch = -1.5
|
||||
}
|
||||
|
||||
// Update cam forward
|
||||
@ -341,7 +728,7 @@ func (g *OurGame) updateCameraLookAround() {
|
||||
updateViewMat()
|
||||
}
|
||||
|
||||
func (g *OurGame) updateCameraPos() {
|
||||
func (g *Game) updateCameraPos() {
|
||||
|
||||
update := false
|
||||
|
||||
@ -373,33 +760,59 @@ func (g *OurGame) updateCameraPos() {
|
||||
}
|
||||
}
|
||||
|
||||
func (g *OurGame) Render() {
|
||||
|
||||
matToUse := simpleMat
|
||||
if debugDrawDepthBuffer {
|
||||
matToUse = debugDepthMat
|
||||
}
|
||||
func (g *Game) Render() {
|
||||
|
||||
tempModelMatrix := cubeModelMat.Clone()
|
||||
window.Rend.Draw(chairMesh, tempModelMatrix, matToUse)
|
||||
|
||||
whiteMat.SetUnifVec3("camPos", &cam.Pos)
|
||||
containerMat.SetUnifVec3("camPos", &cam.Pos)
|
||||
palleteMat.SetUnifVec3("camPos", &cam.Pos)
|
||||
|
||||
sunMat := palleteMat
|
||||
chairMat := palleteMat
|
||||
cubeMat := containerMat
|
||||
if debugDrawDepthBuffer {
|
||||
sunMat = debugDepthMat
|
||||
chairMat = debugDepthMat
|
||||
cubeMat = debugDepthMat
|
||||
}
|
||||
|
||||
// Draw dir light
|
||||
window.Rend.Draw(sphereMesh, gglm.NewTrMatId().Translate(gglm.NewVec3(0, 10, 0)).Scale(gglm.NewVec3(0.1, 0.1, 0.1)), sunMat)
|
||||
|
||||
// Draw point lights
|
||||
for i := 0; i < len(pointLights); i++ {
|
||||
|
||||
pl := &pointLights[i]
|
||||
window.Rend.Draw(cubeMesh, gglm.NewTrMatId().Translate(&pl.Pos).Scale(gglm.NewVec3(0.1, 0.1, 0.1)), sunMat)
|
||||
}
|
||||
|
||||
// Chair
|
||||
window.Rend.Draw(chairMesh, tempModelMatrix, chairMat)
|
||||
|
||||
// Ground
|
||||
window.Rend.Draw(cubeMesh, gglm.NewTrMatId().Translate(gglm.NewVec3(0, -3, 0)).Scale(gglm.NewVec3(20, 1, 20)), cubeMat)
|
||||
|
||||
// Cubes
|
||||
rowSize := 1
|
||||
for y := 0; y < rowSize; y++ {
|
||||
for x := 0; x < rowSize; x++ {
|
||||
tempModelMatrix.Translate(gglm.NewVec3(-6, 0, 0))
|
||||
window.Rend.Draw(cubeMesh, tempModelMatrix, matToUse)
|
||||
window.Rend.Draw(cubeMesh, tempModelMatrix, cubeMat)
|
||||
}
|
||||
tempModelMatrix.Translate(gglm.NewVec3(float32(rowSize), -1, 0))
|
||||
}
|
||||
|
||||
g.DrawSkybox()
|
||||
if drawSkybox {
|
||||
g.DrawSkybox()
|
||||
}
|
||||
}
|
||||
|
||||
func (g *OurGame) DrawSkybox() {
|
||||
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)
|
||||
@ -415,7 +828,6 @@ func (g *OurGame) DrawSkybox() {
|
||||
|
||||
skyboxMat.SetUnifMat4("viewMat", viewMat)
|
||||
skyboxMat.SetUnifMat4("projMat", &cam.ProjMat)
|
||||
// window.Rend.Draw(cubeMesh, gglm.NewTrMatId(), skyboxMat)
|
||||
for i := 0; i < len(skyboxMesh.SubMeshes); i++ {
|
||||
gl.DrawElementsBaseVertexWithOffset(gl.TRIANGLES, skyboxMesh.SubMeshes[i].IndexCount, gl.UNSIGNED_INT, uintptr(skyboxMesh.SubMeshes[i].BaseIndex), skyboxMesh.SubMeshes[i].BaseVertex)
|
||||
}
|
||||
@ -424,15 +836,17 @@ func (g *OurGame) DrawSkybox() {
|
||||
gl.Enable(gl.CULL_FACE)
|
||||
}
|
||||
|
||||
func (g *OurGame) FrameEnd() {
|
||||
func (g *Game) FrameEnd() {
|
||||
}
|
||||
|
||||
func (g *OurGame) DeInit() {
|
||||
func (g *Game) DeInit() {
|
||||
g.Win.Destroy()
|
||||
}
|
||||
|
||||
func updateViewMat() {
|
||||
cam.Update()
|
||||
simpleMat.SetUnifMat4("viewMat", &cam.ViewMat)
|
||||
whiteMat.SetUnifMat4("viewMat", &cam.ViewMat)
|
||||
containerMat.SetUnifMat4("viewMat", &cam.ViewMat)
|
||||
palleteMat.SetUnifMat4("viewMat", &cam.ViewMat)
|
||||
debugDepthMat.SetUnifMat4("viewMat", &cam.ViewMat)
|
||||
}
|
||||
|
||||
@ -12,10 +12,16 @@ type Material struct {
|
||||
Name string
|
||||
ShaderProg shaders.ShaderProgram
|
||||
|
||||
DiffuseTex uint32
|
||||
|
||||
UnifLocs map[string]int32
|
||||
AttribLocs map[string]int32
|
||||
|
||||
// Phong shading
|
||||
DiffuseTex uint32
|
||||
SpecularTex uint32
|
||||
NormalTex uint32
|
||||
EmissionTex uint32
|
||||
|
||||
Shininess float32
|
||||
}
|
||||
|
||||
func (m *Material) Bind() {
|
||||
@ -24,6 +30,15 @@ func (m *Material) Bind() {
|
||||
|
||||
gl.ActiveTexture(gl.TEXTURE0)
|
||||
gl.BindTexture(gl.TEXTURE_2D, m.DiffuseTex)
|
||||
|
||||
gl.ActiveTexture(gl.TEXTURE1)
|
||||
gl.BindTexture(gl.TEXTURE_2D, m.SpecularTex)
|
||||
|
||||
gl.ActiveTexture(gl.TEXTURE2)
|
||||
gl.BindTexture(gl.TEXTURE_2D, m.NormalTex)
|
||||
|
||||
gl.ActiveTexture(gl.TEXTURE3)
|
||||
gl.BindTexture(gl.TEXTURE_2D, m.EmissionTex)
|
||||
}
|
||||
|
||||
func (m *Material) UnBind() {
|
||||
@ -32,6 +47,15 @@ func (m *Material) UnBind() {
|
||||
//TODO: Should we unbind textures here? Are these two lines needed?
|
||||
// gl.ActiveTexture(gl.TEXTURE0)
|
||||
// gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||
|
||||
// gl.ActiveTexture(gl.TEXTURE1)
|
||||
// gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||
|
||||
// gl.ActiveTexture(gl.TEXTURE2)
|
||||
// gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||
|
||||
// gl.ActiveTexture(gl.TEXTURE3)
|
||||
// gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||
}
|
||||
|
||||
func (m *Material) GetAttribLoc(attribName string) int32 {
|
||||
|
||||
@ -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++ {
|
||||
|
||||
@ -29,12 +29,10 @@ func (it *Iterator[T]) Next() (*T, Handle) {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// This does two things:
|
||||
//
|
||||
// First is if IsDone() only checked 'remainingItems', then when Next() returns the last item IsDone() will immediately be true which will cause loops to exit before processing that last item!
|
||||
// If IsDone() only checked 'remainingItems', then when Next() returns the last item IsDone() will immediately be true which will cause loops to exit before processing the last item!
|
||||
// However, with this check IsDone will remain false until Next() is called at least one more time after returning the last item which ensures the last item is processed in the loop.
|
||||
//
|
||||
// Secondly, if the iterator is created on an empty registry, the IsDone() check above won't pass, however the check here will correctly handle the case and make IsDone start returning true
|
||||
// In cases where iterator is created on an empty registry, IsDone() will report true and the above check will return early
|
||||
if it.remainingItems == 0 {
|
||||
it.currIndex = -1
|
||||
return nil, 0
|
||||
@ -47,9 +45,10 @@ func (it *Iterator[T]) Next() (*T, Handle) {
|
||||
continue
|
||||
}
|
||||
|
||||
it.remainingItems--
|
||||
item := &it.registry.Items[it.currIndex]
|
||||
it.currIndex++
|
||||
return &it.registry.Items[it.currIndex], handle
|
||||
it.remainingItems--
|
||||
return item, handle
|
||||
}
|
||||
|
||||
// If we reached here means we iterated to the end and didn't find anything, which probably
|
||||
@ -62,5 +61,13 @@ func (it *Iterator[T]) Next() (*T, Handle) {
|
||||
}
|
||||
|
||||
func (it *Iterator[T]) IsDone() bool {
|
||||
return it.currIndex == -1 && it.remainingItems == 0
|
||||
|
||||
if it.remainingItems != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// We have two cases here:
|
||||
// 1. Index of zero means Next() never returned an item. Remaining items of zero without returning anything means we have an empty registry and so its safe to report done
|
||||
// 2. Negative index means Next() has detected we reached the end and that its safe to report being done
|
||||
return it.currIndex <= 0
|
||||
}
|
||||
|
||||
@ -77,6 +77,10 @@ func (r *Registry[T]) New() (*T, Handle) {
|
||||
|
||||
func (r *Registry[T]) Get(id Handle) *T {
|
||||
|
||||
if id.IsZero() {
|
||||
return nil
|
||||
}
|
||||
|
||||
index := id.Index()
|
||||
assert.T(index < uint64(len(r.Handles)), "Failed to get entity because of invalid entity handle. Handle index is %d while registry only has %d slots. Handle: %+v", index, r.ItemCount, id)
|
||||
|
||||
|
||||
@ -16,6 +16,12 @@ const (
|
||||
// Byte 1: Generation; Byte 2: Flags; Bytes 3-8: Index
|
||||
type Handle uint64
|
||||
|
||||
// IsZero reports whether the handle is in its default 'zero' state.
|
||||
// A zero handle is an invalid handle that does NOT point to any entity
|
||||
func (h Handle) IsZero() bool {
|
||||
return h == 0
|
||||
}
|
||||
|
||||
func (h Handle) HasFlag(ef HandleFlag) bool {
|
||||
return h.Flags()&ef > 0
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
BIN
res/models/cube.fbx
Executable file
BIN
res/models/plane.fbx
Executable file
BIN
res/models/sphere.fbx
Executable file
47
res/shaders/imgui.glsl
Executable file
@ -0,0 +1,47 @@
|
||||
//shader:vertex
|
||||
#version 410
|
||||
|
||||
uniform mat4 ProjMtx;
|
||||
|
||||
in vec2 Position;
|
||||
in vec2 UV;
|
||||
in vec4 Color;
|
||||
|
||||
out vec2 Frag_UV;
|
||||
out vec4 Frag_Color;
|
||||
|
||||
// Imgui doesn't handle srgb correctly, and looks too bright and wrong in srgb buffers (see: https://github.com/ocornut/imgui/issues/578).
|
||||
// While not a complete fix (that would require changes in imgui itself), moving incoming srgba colors to linear in the vertex shader helps make things look better.
|
||||
vec4 srgba_to_linear(vec4 srgbaColor){
|
||||
|
||||
#define gamma_correction 2.2
|
||||
|
||||
return vec4(
|
||||
pow(srgbaColor.r, gamma_correction),
|
||||
pow(srgbaColor.g, gamma_correction),
|
||||
pow(srgbaColor.b, gamma_correction),
|
||||
srgbaColor.a
|
||||
);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
Frag_UV = UV;
|
||||
Frag_Color = srgba_to_linear(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);
|
||||
}
|
||||
@ -18,7 +18,12 @@ uniform mat4 projMat;
|
||||
|
||||
void main()
|
||||
{
|
||||
// @TODO: Calculate this on the CPU and send it as a uniform
|
||||
|
||||
// This produces the normal matrix that multiplies with the model normal to produce the
|
||||
// world space normal. Based on 'One last thing' section from: https://learnopengl.com/Lighting/Basic-Lighting
|
||||
vertNormal = mat3(transpose(inverse(modelMat))) * vertNormalIn;
|
||||
|
||||
vertUV0 = vertUV0In;
|
||||
vertColor = vertColorIn;
|
||||
fragPos = vec3(modelMat * vec4(vertPosIn, 1.0));
|
||||
@ -29,13 +34,50 @@ void main()
|
||||
//shader:fragment
|
||||
#version 410
|
||||
|
||||
uniform float ambientStrength = 0;
|
||||
uniform vec3 ambientLightColor = vec3(1, 1, 1);
|
||||
struct Material {
|
||||
sampler2D diffuse;
|
||||
sampler2D specular;
|
||||
// sampler2D normal;
|
||||
sampler2D emission;
|
||||
float shininess;
|
||||
};
|
||||
|
||||
uniform vec3 lightPos1;
|
||||
uniform vec3 lightColor1;
|
||||
uniform Material material;
|
||||
|
||||
uniform sampler2D diffTex;
|
||||
struct DirLight {
|
||||
vec3 dir;
|
||||
vec3 diffuseColor;
|
||||
vec3 specularColor;
|
||||
};
|
||||
|
||||
uniform DirLight dirLight;
|
||||
|
||||
struct PointLight {
|
||||
vec3 pos;
|
||||
vec3 diffuseColor;
|
||||
vec3 specularColor;
|
||||
float constant;
|
||||
float linear;
|
||||
float quadratic;
|
||||
};
|
||||
|
||||
#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);
|
||||
|
||||
in vec3 vertColor;
|
||||
in vec3 vertNormal;
|
||||
@ -44,12 +86,108 @@ in vec3 fragPos;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
// Global variables used as cache for lighting calculations
|
||||
vec4 diffuseTexColor;
|
||||
vec4 specularTexColor;
|
||||
vec4 emissionTexColor;
|
||||
vec3 normalizedVertNorm;
|
||||
vec3 viewDir;
|
||||
|
||||
vec3 CalcDirLight()
|
||||
{
|
||||
vec3 lightDir = normalize(-dirLight.dir);
|
||||
|
||||
// Diffuse
|
||||
float diffuseAmount = max(0.0, dot(normalizedVertNorm, lightDir));
|
||||
vec3 finalDiffuse = diffuseAmount * dirLight.diffuseColor * diffuseTexColor.rgb;
|
||||
|
||||
// Specular
|
||||
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;
|
||||
}
|
||||
|
||||
vec3 CalcPointLight(PointLight pointLight)
|
||||
{
|
||||
// Ignore unset lights
|
||||
if (pointLight.constant == 0){
|
||||
return vec3(0);
|
||||
}
|
||||
|
||||
vec3 lightDir = normalize(pointLight.pos - fragPos);
|
||||
|
||||
// Diffuse
|
||||
float diffuseAmount = max(0.0, dot(normalizedVertNorm, lightDir));
|
||||
vec3 finalDiffuse = diffuseAmount * pointLight.diffuseColor * diffuseTexColor.rgb;
|
||||
|
||||
// Specular
|
||||
vec3 halfwayDir = normalize(lightDir + viewDir);
|
||||
float specularAmount = pow(max(dot(normalizedVertNorm, halfwayDir), 0.0), material.shininess);
|
||||
vec3 finalSpecular = specularAmount * pointLight.specularColor * specularTexColor.rgb;
|
||||
|
||||
// attenuation
|
||||
float distToLight = length(pointLight.pos - fragPos);
|
||||
float attenuation = 1.0 / (pointLight.constant + pointLight.linear * distToLight + pointLight.quadratic * (distToLight * distToLight));
|
||||
|
||||
return (finalDiffuse + finalSpecular) * attenuation;
|
||||
}
|
||||
|
||||
vec3 CalcSpotLight(SpotLight light)
|
||||
{
|
||||
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()
|
||||
{
|
||||
vec3 lightDir = normalize(lightPos1 - fragPos);
|
||||
float diffStrength = max(0.0, dot(normalize(vertNormal), lightDir));
|
||||
// Shared values
|
||||
diffuseTexColor = texture(material.diffuse, vertUV0);
|
||||
specularTexColor = texture(material.specular, vertUV0);
|
||||
emissionTexColor = texture(material.emission, vertUV0);
|
||||
|
||||
vec3 finalAmbientColor = ambientLightColor * ambientStrength;
|
||||
vec4 texColor = texture(diffTex, vertUV0);
|
||||
fragColor = vec4(texColor.rgb * vertColor * (finalAmbientColor + diffStrength*lightColor1) , texColor.a);
|
||||
}
|
||||
normalizedVertNorm = normalize(vertNormal);
|
||||
viewDir = normalize(camPos - fragPos);
|
||||
|
||||
// Light contributions
|
||||
vec3 finalColor = CalcDirLight();
|
||||
|
||||
for (int i = 0; i < NUM_POINT_LIGHTS; i++)
|
||||
{
|
||||
finalColor += CalcPointLight(pointLights[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < NUM_SPOT_LIGHTS; i++)
|
||||
{
|
||||
finalColor += CalcSpotLight(spotLights[i]);
|
||||
}
|
||||
|
||||
vec3 finalEmission = emissionTexColor.rgb;
|
||||
vec3 finalAmbient = ambientColor * diffuseTexColor.rgb;
|
||||
|
||||
fragColor = vec4(finalColor + finalAmbient + finalEmission, 1);
|
||||
}
|
||||
|
||||
BIN
res/textures/black.png
Executable file
|
After Width: | Height: | Size: 142 B |
BIN
res/textures/container-diffuse.png
Executable file
|
After Width: | Height: | Size: 457 KiB |
BIN
res/textures/container-specular.png
Executable file
|
After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 1008 KiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 274 KiB After Width: | Height: | Size: 617 KiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 338 KiB After Width: | Height: | Size: 770 KiB |
BIN
res/textures/white.png
Executable file
|
After Width: | Height: | Size: 232 B |
@ -16,7 +16,8 @@ type ImguiInfo struct {
|
||||
VaoID uint32
|
||||
VboID uint32
|
||||
IndexBufID uint32
|
||||
TexID uint32
|
||||
// This is a pointer so we can send a stable pointer to C code
|
||||
TexID *uint32
|
||||
}
|
||||
|
||||
func (i *ImguiInfo) FrameStart(winWidth, winHeight float32) {
|
||||
@ -67,7 +68,7 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
|
||||
i.Mat.Bind()
|
||||
i.Mat.SetUnifInt32("Texture", 0)
|
||||
|
||||
//PERF: only update the ortho matrix on window resize
|
||||
// @PERF: only update the ortho matrix on window resize
|
||||
orthoMat := gglm.Ortho(0, float32(winWidth), 0, float32(winHeight), 0, 20)
|
||||
i.Mat.SetUnifMat4("ProjMtx", &orthoMat.Mat4)
|
||||
gl.BindSampler(0, 0) // Rely on combined texture/sampler state.
|
||||
@ -108,11 +109,12 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
|
||||
cmd.CallUserCallback(list)
|
||||
} else {
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_2D, i.TexID)
|
||||
gl.ActiveTexture(gl.TEXTURE0)
|
||||
gl.BindTexture(gl.TEXTURE_2D, *i.TexID)
|
||||
clipRect := cmd.ClipRect()
|
||||
gl.Scissor(int32(clipRect.X), int32(fbHeight)-int32(clipRect.W), int32(clipRect.Z-clipRect.X), int32(clipRect.W-clipRect.Y))
|
||||
|
||||
gl.DrawElementsBaseVertex(gl.TRIANGLES, int32(cmd.ElemCount()), uint32(drawType), gl.PtrOffset(int(cmd.IdxOffset())*indexSize), int32(cmd.VtxOffset()))
|
||||
gl.DrawElementsBaseVertexWithOffset(gl.TRIANGLES, int32(cmd.ElemCount()), uint32(drawType), uintptr(int(cmd.IdxOffset())*indexSize), int32(cmd.VtxOffset()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -141,13 +143,14 @@ func (i *ImguiInfo) AddFontTTF(fontPath string, fontSize float32, fontConfig *im
|
||||
f := a.AddFontFromFileTTFV(fontPath, fontSize, fontConfigToUse, glyphRangesToUse.Data())
|
||||
pixels, width, height, _ := a.GetTextureDataAsAlpha8()
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_2D, i.TexID)
|
||||
gl.ActiveTexture(gl.TEXTURE0)
|
||||
gl.BindTexture(gl.TEXTURE_2D, *i.TexID)
|
||||
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(width), int32(height), 0, gl.RED, gl.UNSIGNED_BYTE, pixels)
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
const imguiShdrSrc = `
|
||||
const DefaultImguiShader = `
|
||||
//shader:vertex
|
||||
#version 410
|
||||
|
||||
@ -160,10 +163,24 @@ in vec4 Color;
|
||||
out vec2 Frag_UV;
|
||||
out vec4 Frag_Color;
|
||||
|
||||
// Imgui doesn't handle srgb correctly, and looks too bright and wrong in srgb buffers (see: https://github.com/ocornut/imgui/issues/578).
|
||||
// While not a complete fix (that would require changes in imgui itself), moving incoming srgba colors to linear in the vertex shader helps make things look better.
|
||||
vec4 srgba_to_linear(vec4 srgbaColor){
|
||||
|
||||
#define gamma_correction 2.2
|
||||
|
||||
return vec4(
|
||||
pow(srgbaColor.r, gamma_correction),
|
||||
pow(srgbaColor.g, gamma_correction),
|
||||
pow(srgbaColor.b, gamma_correction),
|
||||
srgbaColor.a
|
||||
);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
Frag_UV = UV;
|
||||
Frag_Color = Color;
|
||||
Frag_Color = srgba_to_linear(Color);
|
||||
gl_Position = ProjMtx * vec4(Position.xy, 0, 1);
|
||||
}
|
||||
|
||||
@ -183,11 +200,21 @@ void main()
|
||||
}
|
||||
`
|
||||
|
||||
func NewImGui() ImguiInfo {
|
||||
// NewImGui setups imgui using the passed shader.
|
||||
// If the path is empty a default nMage shader is used
|
||||
func NewImGui(shaderPath string) ImguiInfo {
|
||||
|
||||
var imguiMat *materials.Material
|
||||
if shaderPath == "" {
|
||||
imguiMat = materials.NewMaterialSrc("ImGUI Mat", []byte(DefaultImguiShader))
|
||||
} else {
|
||||
imguiMat = materials.NewMaterial("ImGUI Mat", shaderPath)
|
||||
}
|
||||
|
||||
imguiInfo := ImguiInfo{
|
||||
ImCtx: imgui.CreateContext(),
|
||||
Mat: materials.NewMaterialSrc("ImGUI Mat", []byte(imguiShdrSrc)),
|
||||
Mat: imguiMat,
|
||||
TexID: new(uint32),
|
||||
}
|
||||
|
||||
io := imgui.CurrentIO()
|
||||
@ -197,10 +224,11 @@ func NewImGui() ImguiInfo {
|
||||
gl.GenVertexArrays(1, &imguiInfo.VaoID)
|
||||
gl.GenBuffers(1, &imguiInfo.VboID)
|
||||
gl.GenBuffers(1, &imguiInfo.IndexBufID)
|
||||
gl.GenTextures(1, &imguiInfo.TexID)
|
||||
gl.GenTextures(1, imguiInfo.TexID)
|
||||
|
||||
// Upload font to gpu
|
||||
gl.BindTexture(gl.TEXTURE_2D, imguiInfo.TexID)
|
||||
gl.ActiveTexture(gl.TEXTURE0)
|
||||
gl.BindTexture(gl.TEXTURE_2D, *imguiInfo.TexID)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
||||
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0)
|
||||
@ -209,7 +237,7 @@ func NewImGui() ImguiInfo {
|
||||
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(width), int32(height), 0, gl.RED, gl.UNSIGNED_BYTE, pixels)
|
||||
|
||||
// Store our identifier
|
||||
io.Fonts().SetTexID(imgui.TextureID(uintptr(imguiInfo.TexID)))
|
||||
io.Fonts().SetTexID(imgui.TextureID(imguiInfo.TexID))
|
||||
|
||||
//Shader attributes
|
||||
imguiInfo.Mat.Bind()
|
||||
|
||||