mirror of
https://github.com/bloeys/nmage.git
synced 2025-12-29 13:28:20 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ddd8db3cb0 |
295
buffers/framebuffer.go
Executable file
295
buffers/framebuffer.go
Executable file
@ -0,0 +1,295 @@
|
||||
package buffers
|
||||
|
||||
import (
|
||||
"github.com/bloeys/nmage/logging"
|
||||
"github.com/go-gl/gl/v4.1-core/gl"
|
||||
)
|
||||
|
||||
type FramebufferAttachmentType int32
|
||||
|
||||
const (
|
||||
FramebufferAttachmentType_Unknown FramebufferAttachmentType = iota
|
||||
FramebufferAttachmentType_Texture
|
||||
FramebufferAttachmentType_Renderbuffer
|
||||
)
|
||||
|
||||
func (f FramebufferAttachmentType) IsValid() bool {
|
||||
|
||||
switch f {
|
||||
case FramebufferAttachmentType_Texture:
|
||||
fallthrough
|
||||
case FramebufferAttachmentType_Renderbuffer:
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type FramebufferAttachmentDataFormat int32
|
||||
|
||||
const (
|
||||
FramebufferAttachmentDataFormat_Unknown FramebufferAttachmentDataFormat = iota
|
||||
FramebufferAttachmentDataFormat_R32Int
|
||||
FramebufferAttachmentDataFormat_RGBA8
|
||||
FramebufferAttachmentDataFormat_SRGBA
|
||||
FramebufferAttachmentDataFormat_Depth24Stencil8
|
||||
)
|
||||
|
||||
func (f FramebufferAttachmentDataFormat) IsColorFormat() bool {
|
||||
return f == FramebufferAttachmentDataFormat_R32Int ||
|
||||
f == FramebufferAttachmentDataFormat_RGBA8 ||
|
||||
f == FramebufferAttachmentDataFormat_SRGBA
|
||||
}
|
||||
|
||||
func (f FramebufferAttachmentDataFormat) IsDepthFormat() bool {
|
||||
return f == FramebufferAttachmentDataFormat_Depth24Stencil8
|
||||
}
|
||||
|
||||
func (f FramebufferAttachmentDataFormat) GlInternalFormat() int32 {
|
||||
|
||||
switch f {
|
||||
case FramebufferAttachmentDataFormat_R32Int:
|
||||
return gl.R32I
|
||||
case FramebufferAttachmentDataFormat_RGBA8:
|
||||
return gl.RGB8
|
||||
case FramebufferAttachmentDataFormat_SRGBA:
|
||||
return gl.SRGB_ALPHA
|
||||
case FramebufferAttachmentDataFormat_Depth24Stencil8:
|
||||
return gl.DEPTH24_STENCIL8
|
||||
default:
|
||||
logging.ErrLog.Fatalf("unknown framebuffer attachment data format. Format=%d\n", f)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (f FramebufferAttachmentDataFormat) GlFormat() uint32 {
|
||||
|
||||
switch f {
|
||||
case FramebufferAttachmentDataFormat_R32Int:
|
||||
return gl.RED_INTEGER
|
||||
|
||||
case FramebufferAttachmentDataFormat_RGBA8:
|
||||
fallthrough
|
||||
case FramebufferAttachmentDataFormat_SRGBA:
|
||||
return gl.RGBA
|
||||
|
||||
case FramebufferAttachmentDataFormat_Depth24Stencil8:
|
||||
return gl.DEPTH_STENCIL
|
||||
|
||||
default:
|
||||
logging.ErrLog.Fatalf("unknown framebuffer attachment data format. Format=%d\n", f)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
type FramebufferAttachment struct {
|
||||
Id uint32
|
||||
Type FramebufferAttachmentType
|
||||
Format FramebufferAttachmentDataFormat
|
||||
}
|
||||
|
||||
type Framebuffer struct {
|
||||
Id uint32
|
||||
Attachments []FramebufferAttachment
|
||||
ColorAttachmentsCount uint32
|
||||
Width uint32
|
||||
Height uint32
|
||||
}
|
||||
|
||||
func (fbo *Framebuffer) Bind() {
|
||||
gl.BindFramebuffer(gl.FRAMEBUFFER, fbo.Id)
|
||||
}
|
||||
|
||||
func (fbo *Framebuffer) BindWithViewport() {
|
||||
gl.BindFramebuffer(gl.FRAMEBUFFER, fbo.Id)
|
||||
gl.Viewport(0, 0, int32(fbo.Width), int32(fbo.Height))
|
||||
}
|
||||
|
||||
func (fbo *Framebuffer) UnBind() {
|
||||
gl.BindFramebuffer(gl.FRAMEBUFFER, 0)
|
||||
}
|
||||
|
||||
func (fbo *Framebuffer) UnBindWithViewport(width, height uint32) {
|
||||
gl.BindFramebuffer(gl.FRAMEBUFFER, 0)
|
||||
gl.Viewport(0, 0, int32(width), int32(height))
|
||||
}
|
||||
|
||||
// IsComplete returns true if OpenGL reports that the fbo is complete/usable.
|
||||
// Note that this function binds and then unbinds the fbo
|
||||
func (fbo *Framebuffer) IsComplete() bool {
|
||||
fbo.Bind()
|
||||
isComplete := gl.CheckFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE
|
||||
fbo.UnBind()
|
||||
return isComplete
|
||||
}
|
||||
|
||||
func (fbo *Framebuffer) HasColorAttachment() bool {
|
||||
return fbo.ColorAttachmentsCount > 0
|
||||
}
|
||||
|
||||
func (fbo *Framebuffer) HasDepthAttachment() bool {
|
||||
|
||||
for i := 0; i < len(fbo.Attachments); i++ {
|
||||
|
||||
a := &fbo.Attachments[i]
|
||||
if a.Format.IsDepthFormat() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (fbo *Framebuffer) NewColorAttachment(
|
||||
attachType FramebufferAttachmentType,
|
||||
attachFormat FramebufferAttachmentDataFormat,
|
||||
) {
|
||||
|
||||
if fbo.ColorAttachmentsCount == 8 {
|
||||
logging.ErrLog.Fatalf("failed creating color attachment for framebuffer due it already having %d attached\n", fbo.ColorAttachmentsCount)
|
||||
}
|
||||
|
||||
if !attachType.IsValid() {
|
||||
logging.ErrLog.Fatalf("failed creating color attachment for framebuffer due to unknown attachment type. Type=%d\n", attachType)
|
||||
}
|
||||
|
||||
if !attachFormat.IsColorFormat() {
|
||||
logging.ErrLog.Fatalf("failed creating color attachment for framebuffer due to attachment data format not being a valid color type. Data format=%d\n", attachFormat)
|
||||
}
|
||||
|
||||
a := FramebufferAttachment{
|
||||
Type: attachType,
|
||||
Format: attachFormat,
|
||||
}
|
||||
|
||||
fbo.Bind()
|
||||
|
||||
if attachType == FramebufferAttachmentType_Texture {
|
||||
|
||||
// Create texture
|
||||
gl.GenTextures(1, &a.Id)
|
||||
if a.Id == 0 {
|
||||
logging.ErrLog.Fatalf("failed to generate texture for framebuffer. GlError=%d\n", gl.GetError())
|
||||
}
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_2D, a.Id)
|
||||
gl.TexImage2D(gl.TEXTURE_2D, 0, attachFormat.GlInternalFormat(), int32(fbo.Width), int32(fbo.Height), 0, attachFormat.GlFormat(), gl.UNSIGNED_BYTE, nil)
|
||||
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
||||
gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||
|
||||
// Attach to fbo
|
||||
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0+fbo.ColorAttachmentsCount, gl.TEXTURE_2D, a.Id, 0)
|
||||
|
||||
} else if attachType == FramebufferAttachmentType_Renderbuffer {
|
||||
|
||||
// Create rbo
|
||||
gl.GenRenderbuffers(1, &a.Id)
|
||||
if a.Id == 0 {
|
||||
logging.ErrLog.Fatalf("failed to generate render buffer for framebuffer. GlError=%d\n", gl.GetError())
|
||||
}
|
||||
|
||||
gl.BindRenderbuffer(gl.RENDERBUFFER, a.Id)
|
||||
gl.RenderbufferStorage(gl.RENDERBUFFER, uint32(attachFormat.GlInternalFormat()), int32(fbo.Width), int32(fbo.Height))
|
||||
gl.BindRenderbuffer(gl.RENDERBUFFER, 0)
|
||||
|
||||
// Attach to fbo
|
||||
gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0+fbo.ColorAttachmentsCount, gl.RENDERBUFFER, a.Id)
|
||||
}
|
||||
|
||||
fbo.UnBind()
|
||||
fbo.ColorAttachmentsCount++
|
||||
fbo.Attachments = append(fbo.Attachments, a)
|
||||
}
|
||||
|
||||
func (fbo *Framebuffer) NewDepthStencilAttachment(
|
||||
attachType FramebufferAttachmentType,
|
||||
attachFormat FramebufferAttachmentDataFormat,
|
||||
) {
|
||||
|
||||
if fbo.HasDepthAttachment() {
|
||||
logging.ErrLog.Fatalf("failed creating depth-stencil attachment for framebuffer because a depth-stencil attachment already exists\n")
|
||||
}
|
||||
|
||||
if !attachType.IsValid() {
|
||||
logging.ErrLog.Fatalf("failed creating depth-stencil attachment for framebuffer due to unknown attachment type. Type=%d\n", attachType)
|
||||
}
|
||||
|
||||
if !attachFormat.IsDepthFormat() {
|
||||
logging.ErrLog.Fatalf("failed creating depth-stencil attachment for framebuffer due to attachment data format not being a valid depth-stencil type. Data format=%d\n", attachFormat)
|
||||
}
|
||||
|
||||
a := FramebufferAttachment{
|
||||
Type: attachType,
|
||||
Format: attachFormat,
|
||||
}
|
||||
|
||||
fbo.Bind()
|
||||
|
||||
if attachType == FramebufferAttachmentType_Texture {
|
||||
|
||||
// Create texture
|
||||
gl.GenTextures(1, &a.Id)
|
||||
if a.Id == 0 {
|
||||
logging.ErrLog.Fatalf("failed to generate texture for framebuffer. GlError=%d\n", gl.GetError())
|
||||
}
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_2D, a.Id)
|
||||
gl.TexImage2D(gl.TEXTURE_2D, 0, attachFormat.GlInternalFormat(), int32(fbo.Width), int32(fbo.Height), 0, attachFormat.GlFormat(), gl.UNSIGNED_INT_24_8, nil)
|
||||
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
||||
gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||
|
||||
// Attach to fbo
|
||||
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.TEXTURE_2D, a.Id, 0)
|
||||
|
||||
} else if attachType == FramebufferAttachmentType_Renderbuffer {
|
||||
|
||||
// Create rbo
|
||||
gl.GenRenderbuffers(1, &a.Id)
|
||||
if a.Id == 0 {
|
||||
logging.ErrLog.Fatalf("failed to generate render buffer for framebuffer. GlError=%d\n", gl.GetError())
|
||||
}
|
||||
|
||||
gl.BindRenderbuffer(gl.RENDERBUFFER, a.Id)
|
||||
gl.RenderbufferStorage(gl.RENDERBUFFER, uint32(attachFormat.GlInternalFormat()), int32(fbo.Width), int32(fbo.Height))
|
||||
gl.BindRenderbuffer(gl.RENDERBUFFER, 0)
|
||||
|
||||
// Attach to fbo
|
||||
gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, a.Id)
|
||||
}
|
||||
|
||||
fbo.UnBind()
|
||||
fbo.Attachments = append(fbo.Attachments, a)
|
||||
}
|
||||
|
||||
func (fbo *Framebuffer) Delete() {
|
||||
|
||||
if fbo.Id == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
gl.DeleteFramebuffers(1, &fbo.Id)
|
||||
fbo.Id = 0
|
||||
}
|
||||
|
||||
func NewFramebuffer(width, height uint32) Framebuffer {
|
||||
|
||||
// It is allowed to have attachments of differnt sizes in one FBO,
|
||||
// but that complicates things (e.g. which size to use for gl.viewport) and I don't see much use
|
||||
// for it now, so we will have all attachments share size
|
||||
fbo := Framebuffer{
|
||||
Width: width,
|
||||
Height: height,
|
||||
}
|
||||
|
||||
gl.GenFramebuffers(1, &fbo.Id)
|
||||
if fbo.Id == 0 {
|
||||
logging.ErrLog.Fatalf("failed to generate framebuffer. GlError=%d\n", gl.GetError())
|
||||
}
|
||||
|
||||
return fbo
|
||||
}
|
||||
75
main.go
75
main.go
@ -8,6 +8,7 @@ import (
|
||||
imgui "github.com/AllenDang/cimgui-go"
|
||||
"github.com/bloeys/gglm/gglm"
|
||||
"github.com/bloeys/nmage/assets"
|
||||
"github.com/bloeys/nmage/buffers"
|
||||
"github.com/bloeys/nmage/camera"
|
||||
"github.com/bloeys/nmage/engine"
|
||||
"github.com/bloeys/nmage/entity"
|
||||
@ -99,6 +100,14 @@ var (
|
||||
yaw float32 = -1.5
|
||||
cam *camera.Camera
|
||||
|
||||
renderToFbo = true
|
||||
fboRenderDirectly = true
|
||||
fboScale = gglm.NewVec2(0.25, 0.25)
|
||||
fboOffset = gglm.NewVec2(0.75, -0.75)
|
||||
fbo buffers.Framebuffer
|
||||
|
||||
screenQuadMat *materials.Material
|
||||
unlitMat *materials.Material
|
||||
whiteMat *materials.Material
|
||||
containerMat *materials.Material
|
||||
palleteMat *materials.Material
|
||||
@ -386,6 +395,15 @@ func (g *Game) Init() {
|
||||
}
|
||||
|
||||
// Create materials and assign any unused texture slots to black
|
||||
screenQuadMat = materials.NewMaterial("Screen Quad Mat", "./res/shaders/screen-quad.glsl")
|
||||
screenQuadMat.SetUnifVec2("scale", fboScale)
|
||||
screenQuadMat.SetUnifVec2("offset", fboOffset)
|
||||
screenQuadMat.SetUnifInt32("material.diffuse", 0)
|
||||
|
||||
unlitMat = materials.NewMaterial("Unlit mat", "./res/shaders/simple-unlit.glsl")
|
||||
unlitMat.SetUnifInt32("material.diffuse", 0)
|
||||
unlitMat.SetUnifMat4("projMat", &cam.ProjMat)
|
||||
|
||||
whiteMat = materials.NewMaterial("White mat", "./res/shaders/simple.glsl")
|
||||
whiteMat.Shininess = 64
|
||||
whiteMat.DiffuseTex = whiteTex.TexID
|
||||
@ -449,6 +467,28 @@ func (g *Game) Init() {
|
||||
|
||||
g.updateLights()
|
||||
updateViewMat()
|
||||
|
||||
g.initFbo()
|
||||
}
|
||||
|
||||
func (g *Game) initFbo() {
|
||||
|
||||
fbWidth, fbHeight := g.Win.SDLWin.GLGetDrawableSize()
|
||||
if fbWidth <= 0 || fbHeight <= 0 {
|
||||
panic("what?")
|
||||
}
|
||||
|
||||
fbo = buffers.NewFramebuffer(uint32(fbWidth), uint32(fbHeight))
|
||||
|
||||
fbo.NewColorAttachment(
|
||||
buffers.FramebufferAttachmentType_Texture,
|
||||
buffers.FramebufferAttachmentDataFormat_SRGBA,
|
||||
)
|
||||
|
||||
fbo.NewDepthStencilAttachment(
|
||||
buffers.FramebufferAttachmentType_Renderbuffer,
|
||||
buffers.FramebufferAttachmentDataFormat_Depth24Stencil8,
|
||||
)
|
||||
}
|
||||
|
||||
func (g *Game) updateLights() {
|
||||
@ -693,6 +733,20 @@ func (g *Game) showDebugWindow() {
|
||||
imgui.EndListBox()
|
||||
}
|
||||
|
||||
// Fbo
|
||||
imgui.Text("Framebuffer")
|
||||
|
||||
imgui.Checkbox("Render to FBO", &renderToFbo)
|
||||
imgui.Checkbox("Render Directly", &fboRenderDirectly)
|
||||
|
||||
if imgui.DragFloat2("Scale", &fboScale.Data) {
|
||||
screenQuadMat.SetUnifVec2("scale", fboScale)
|
||||
}
|
||||
|
||||
if imgui.DragFloat2("Offset", &fboOffset.Data) {
|
||||
screenQuadMat.SetUnifVec2("offset", fboOffset)
|
||||
}
|
||||
|
||||
// Other
|
||||
imgui.Text("Other Settings")
|
||||
|
||||
@ -762,6 +816,11 @@ func (g *Game) updateCameraPos() {
|
||||
|
||||
func (g *Game) Render() {
|
||||
|
||||
if renderToFbo {
|
||||
fbo.Bind()
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
|
||||
}
|
||||
|
||||
tempModelMatrix := cubeModelMat.Clone()
|
||||
|
||||
whiteMat.SetUnifVec3("camPos", &cam.Pos)
|
||||
@ -806,6 +865,21 @@ func (g *Game) Render() {
|
||||
if drawSkybox {
|
||||
g.DrawSkybox()
|
||||
}
|
||||
|
||||
if renderToFbo {
|
||||
|
||||
fbo.UnBind()
|
||||
|
||||
if fboRenderDirectly {
|
||||
renderToFbo = false
|
||||
g.Render()
|
||||
renderToFbo = true
|
||||
}
|
||||
|
||||
screenQuadMat.DiffuseTex = fbo.Attachments[0].Id
|
||||
screenQuadMat.Bind()
|
||||
gl.DrawArrays(gl.TRIANGLES, 0, 6)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) DrawSkybox() {
|
||||
@ -845,6 +919,7 @@ func (g *Game) DeInit() {
|
||||
|
||||
func updateViewMat() {
|
||||
cam.Update()
|
||||
unlitMat.SetUnifMat4("viewMat", &cam.ViewMat)
|
||||
whiteMat.SetUnifMat4("viewMat", &cam.ViewMat)
|
||||
containerMat.SetUnifMat4("viewMat", &cam.ViewMat)
|
||||
palleteMat.SetUnifMat4("viewMat", &cam.ViewMat)
|
||||
|
||||
45
res/shaders/screen-quad.glsl
Executable file
45
res/shaders/screen-quad.glsl
Executable file
@ -0,0 +1,45 @@
|
||||
//shader:vertex
|
||||
#version 410
|
||||
|
||||
out vec2 vertUV0;
|
||||
|
||||
// Hardcoded vertex positions for a fullscreen quad.
|
||||
// Format: vec4(pos.x, pos.y, uv0.x, uv0.y)
|
||||
vec4 quadData[6] = vec4[](
|
||||
vec4(-1.0, 1.0, 0.0, 1.0),
|
||||
vec4(-1.0, -1.0, 0.0, 0.0),
|
||||
vec4(1.0, -1.0, 1.0, 0.0),
|
||||
vec4(-1.0, 1.0, 0.0, 1.0),
|
||||
vec4(1.0, -1.0, 1.0, 0.0),
|
||||
vec4(1.0, 1.0, 1.0, 1.0)
|
||||
);
|
||||
|
||||
uniform vec2 scale = vec2(1, 1);
|
||||
uniform vec2 offset = vec2(0, 0);
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 vertData = quadData[gl_VertexID];
|
||||
|
||||
vertUV0 = vertData.zw;
|
||||
gl_Position = vec4((vertData.xy * scale) + offset, 0.0, 1.0);
|
||||
}
|
||||
|
||||
//shader:fragment
|
||||
#version 410
|
||||
|
||||
struct Material {
|
||||
sampler2D diffuse;
|
||||
};
|
||||
|
||||
uniform Material material;
|
||||
|
||||
in vec2 vertUV0;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 diffuseTexColor = texture(material.diffuse, vertUV0);
|
||||
fragColor = vec4(diffuseTexColor.rgb, 1);
|
||||
}
|
||||
54
res/shaders/simple-unlit.glsl
Executable file
54
res/shaders/simple-unlit.glsl
Executable file
@ -0,0 +1,54 @@
|
||||
//shader:vertex
|
||||
#version 410
|
||||
|
||||
layout(location=0) in vec3 vertPosIn;
|
||||
layout(location=1) in vec3 vertNormalIn;
|
||||
layout(location=2) in vec2 vertUV0In;
|
||||
layout(location=3) in vec3 vertColorIn;
|
||||
|
||||
out vec3 vertNormal;
|
||||
out vec2 vertUV0;
|
||||
out vec3 vertColor;
|
||||
out vec3 fragPos;
|
||||
|
||||
//MVP = Model View Projection
|
||||
uniform mat4 modelMat;
|
||||
uniform mat4 viewMat;
|
||||
uniform mat4 projMat;
|
||||
|
||||
void main()
|
||||
{
|
||||
// @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));
|
||||
|
||||
gl_Position = projMat * viewMat * modelMat * vec4(vertPosIn, 1.0);
|
||||
}
|
||||
|
||||
//shader:fragment
|
||||
#version 410
|
||||
|
||||
struct Material {
|
||||
sampler2D diffuse;
|
||||
};
|
||||
|
||||
uniform Material material;
|
||||
|
||||
in vec3 vertColor;
|
||||
in vec3 vertNormal;
|
||||
in vec2 vertUV0;
|
||||
in vec3 fragPos;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 diffuseTexColor = texture(material.diffuse, vertUV0);
|
||||
fragColor = vec4(diffuseTexColor.rgb, 1);
|
||||
}
|
||||
Reference in New Issue
Block a user