Framebuffers+unlit shader+screen quad shader

This commit is contained in:
bloeys
2024-04-13 08:03:17 +04:00
parent 692167ada2
commit ddd8db3cb0
4 changed files with 469 additions and 0 deletions

295
buffers/framebuffer.go Executable file
View 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
View File

@ -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
View 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
View 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);
}