Compare commits

...

16 Commits

Author SHA1 Message Date
581d17d1d9 Frame time graph 2024-05-13 05:18:21 +04:00
3795a7123f Ensure renderer calls aren't virtual 2024-05-13 04:57:16 +04:00
5aa0f41085 Internal material func rename 2024-05-13 04:36:53 +04:00
c782e8c312 Get rid of allocations on SetUniform calls, allowing us to pass ref again 2024-05-13 04:33:54 +04:00
f0a12879f8 Add todo 2024-05-13 03:45:16 +04:00
6ea08e9826 Get rid of more pointers to make allocs predictable 2024-05-13 03:42:52 +04:00
83c6f635e5 Show fps in debug window 2024-05-13 03:21:47 +04:00
cf6b2655e7 After all why not, why shouldn't we have HDR 2024-05-12 06:46:46 +04:00
7b1e3ea7b4 Default textures for diffuse/specular/normal/emission mat slots 2024-05-11 05:11:54 +04:00
c884d2624d Normal mapping 2024-05-07 05:23:36 +04:00
8c6b1d5821 Adjust shadow map texture sizes 2024-05-06 23:23:52 +04:00
dfd1fe9c5e Material settings+normal matrices on CPU 2024-05-06 22:55:57 +04:00
24613823a7 Fix gitignore 2024-05-06 22:18:15 +04:00
0386f441d6 Profiling 2024-05-06 22:16:20 +04:00
57ab851534 Update gglm 2024-05-05 00:34:38 +04:00
d523c0951b Get rid of unneeded pointers+update todos 2024-05-01 01:16:33 +04:00
22 changed files with 998 additions and 351 deletions

3
.gitignore vendored
View File

@ -16,3 +16,6 @@ vendor/
.vscode/
imgui.ini
*~
# Custom
*.pprof

View File

@ -23,6 +23,15 @@ const (
ColorFormat_RGBA8
)
var (
DefaultBlackTexId Texture
DefaultWhiteTexId Texture
DefaultDiffuseTexId Texture
DefaultSpecularTexId Texture
DefaultNormalTexId Texture
DefaultEmissionTexId Texture
)
type Texture struct {
// Path only exists for textures loaded from disk
Path string

View File

@ -42,6 +42,7 @@ const (
FramebufferAttachmentDataFormat_Unknown FramebufferAttachmentDataFormat = iota
FramebufferAttachmentDataFormat_R32Int
FramebufferAttachmentDataFormat_RGBA8
FramebufferAttachmentDataFormat_RGBAF16
FramebufferAttachmentDataFormat_SRGBA
FramebufferAttachmentDataFormat_DepthF32
FramebufferAttachmentDataFormat_Depth24Stencil8
@ -50,7 +51,8 @@ const (
func (f FramebufferAttachmentDataFormat) IsColorFormat() bool {
return f == FramebufferAttachmentDataFormat_R32Int ||
f == FramebufferAttachmentDataFormat_RGBA8 ||
f == FramebufferAttachmentDataFormat_SRGBA
f == FramebufferAttachmentDataFormat_SRGBA ||
f == FramebufferAttachmentDataFormat_RGBAF16
}
func (f FramebufferAttachmentDataFormat) IsDepthFormat() bool {
@ -65,6 +67,8 @@ func (f FramebufferAttachmentDataFormat) GlInternalFormat() int32 {
return gl.R32I
case FramebufferAttachmentDataFormat_RGBA8:
return gl.RGB8
case FramebufferAttachmentDataFormat_RGBAF16:
return gl.RGBA16F
case FramebufferAttachmentDataFormat_SRGBA:
return gl.SRGB_ALPHA
case FramebufferAttachmentDataFormat_DepthF32:
@ -85,6 +89,8 @@ func (f FramebufferAttachmentDataFormat) GlFormat() uint32 {
case FramebufferAttachmentDataFormat_RGBA8:
fallthrough
case FramebufferAttachmentDataFormat_RGBAF16:
fallthrough
case FramebufferAttachmentDataFormat_SRGBA:
return gl.RGBA
@ -100,6 +106,33 @@ func (f FramebufferAttachmentDataFormat) GlFormat() uint32 {
}
}
func (f FramebufferAttachmentDataFormat) GlComponentType() uint32 {
switch f {
case FramebufferAttachmentDataFormat_R32Int:
return gl.INT
case FramebufferAttachmentDataFormat_RGBA8:
fallthrough
case FramebufferAttachmentDataFormat_SRGBA:
return gl.UNSIGNED_BYTE
case FramebufferAttachmentDataFormat_RGBAF16:
// Seems this is fine to be float instead of half float
fallthrough
case FramebufferAttachmentDataFormat_DepthF32:
return gl.FLOAT
case FramebufferAttachmentDataFormat_Depth24Stencil8:
return gl.UNSIGNED_INT_24_8
default:
logging.ErrLog.Fatalf("unknown framebuffer attachment data format. Format=%d\n", f)
return 0
}
}
type FramebufferAttachment struct {
Id uint32
Type FramebufferAttachmentType
@ -124,7 +157,7 @@ func (fbo *Framebuffer) BindWithViewport() {
gl.Viewport(0, 0, int32(fbo.Width), int32(fbo.Height))
}
// Clear calls gl.Clear with the fob's clear flags.
// Clear calls gl.Clear with the fbo's clear flags.
// Note that the fbo must be complete and bound.
// Calling this without a bound fbo will clear something else, like your screen.
func (fbo *Framebuffer) Clear() {
@ -207,7 +240,17 @@ func (fbo *Framebuffer) NewColorAttachment(
}
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.TexImage2D(
gl.TEXTURE_2D,
0,
attachFormat.GlInternalFormat(),
int32(fbo.Width),
int32(fbo.Height),
0,
attachFormat.GlFormat(),
attachFormat.GlComponentType(),
nil,
)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
@ -298,7 +341,17 @@ func (fbo *Framebuffer) NewDepthAttachment(
}
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.FLOAT, nil)
gl.TexImage2D(
gl.TEXTURE_2D,
0,
attachFormat.GlInternalFormat(),
int32(fbo.Width),
int32(fbo.Height),
0,
attachFormat.GlFormat(),
attachFormat.GlComponentType(),
nil,
)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
@ -341,7 +394,17 @@ func (fbo *Framebuffer) NewDepthAttachment(
gl.BindTexture(gl.TEXTURE_CUBE_MAP, a.Id)
for i := 0; i < 6; i++ {
gl.TexImage2D(uint32(gl.TEXTURE_CUBE_MAP_POSITIVE_X+i), 0, attachFormat.GlInternalFormat(), int32(fbo.Width), int32(fbo.Height), 0, attachFormat.GlFormat(), gl.FLOAT, nil)
gl.TexImage2D(
uint32(gl.TEXTURE_CUBE_MAP_POSITIVE_X+i),
0,
attachFormat.GlInternalFormat(),
int32(fbo.Width),
int32(fbo.Height),
0,
attachFormat.GlFormat(),
attachFormat.GlComponentType(),
nil,
)
}
gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
@ -398,7 +461,7 @@ func (fbo *Framebuffer) NewDepthCubemapArrayAttachment(
6*numCubemaps,
0,
attachFormat.GlFormat(),
gl.FLOAT,
attachFormat.GlComponentType(),
nil,
)
@ -455,7 +518,7 @@ func (fbo *Framebuffer) NewDepthTextureArrayAttachment(
numTextures,
0,
attachFormat.GlFormat(),
gl.FLOAT,
attachFormat.GlComponentType(),
nil,
)
@ -513,7 +576,17 @@ func (fbo *Framebuffer) NewDepthStencilAttachment(
}
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.TexImage2D(
gl.TEXTURE_2D,
0,
attachFormat.GlInternalFormat(),
int32(fbo.Width),
int32(fbo.Height),
0,
attachFormat.GlFormat(),
attachFormat.GlComponentType(),
nil,
)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)

View File

@ -41,7 +41,7 @@ func (c *Camera) Update() {
c.ViewMat = gglm.LookAtRH(&c.Pos, c.Pos.Clone().Add(&c.Forward), &c.WorldUp).Mat4
if c.Type == Type_Perspective {
c.ProjMat = *gglm.Perspective(c.Fov, c.AspectRatio, c.NearClip, c.FarClip)
c.ProjMat = gglm.Perspective(c.Fov, c.AspectRatio, c.NearClip, c.FarClip)
} else {
c.ProjMat = gglm.Ortho(c.Left, c.Right, c.Top, c.Bottom, c.NearClip, c.FarClip).Mat4
}
@ -59,9 +59,9 @@ func (c *Camera) UpdateRotation(pitch, yaw float32) {
c.Update()
}
func NewPerspective(pos, forward, worldUp *gglm.Vec3, nearClip, farClip, fovRadians, aspectRatio float32) *Camera {
func NewPerspective(pos, forward, worldUp *gglm.Vec3, nearClip, farClip, fovRadians, aspectRatio float32) Camera {
cam := &Camera{
cam := Camera{
Type: Type_Perspective,
Pos: *pos,
Forward: *forward,
@ -78,9 +78,9 @@ func NewPerspective(pos, forward, worldUp *gglm.Vec3, nearClip, farClip, fovRadi
return cam
}
func NewOrthographic(pos, forward, worldUp *gglm.Vec3, nearClip, farClip, left, right, top, bottom float32) *Camera {
func NewOrthographic(pos, forward, worldUp *gglm.Vec3, nearClip, farClip, left, right, top, bottom float32) Camera {
cam := &Camera{
cam := Camera{
Type: Type_Orthographic,
Pos: *pos,
Forward: *forward,

View File

@ -1,12 +1,14 @@
package engine
import (
"image"
"image/color"
"runtime"
imgui "github.com/AllenDang/cimgui-go"
"github.com/bloeys/nmage/assert"
"github.com/bloeys/nmage/assets"
"github.com/bloeys/nmage/input"
"github.com/bloeys/nmage/renderer"
"github.com/bloeys/nmage/timing"
nmageimgui "github.com/bloeys/nmage/ui/imgui"
"github.com/go-gl/gl/v4.1-core/gl"
@ -28,7 +30,6 @@ type Window struct {
SDLWin *sdl.Window
GlCtx sdl.GLContext
EventCallbacks []func(sdl.Event)
Rend renderer.Render
}
func (w *Window) handleInputs() {
@ -167,42 +168,45 @@ func initSDL() error {
return nil
}
func CreateOpenGLWindow(title string, x, y, width, height int32, flags WindowFlags, rend renderer.Render) (*Window, error) {
return createWindow(title, x, y, width, height, WindowFlags_OPENGL|flags, rend)
func CreateOpenGLWindow(title string, x, y, width, height int32, flags WindowFlags) (Window, error) {
return createWindow(title, x, y, width, height, WindowFlags_OPENGL|flags)
}
func CreateOpenGLWindowCentered(title string, width, height int32, flags WindowFlags, rend renderer.Render) (*Window, error) {
return createWindow(title, sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, width, height, WindowFlags_OPENGL|flags, rend)
func CreateOpenGLWindowCentered(title string, width, height int32, flags WindowFlags) (Window, error) {
return createWindow(title, sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, width, height, WindowFlags_OPENGL|flags)
}
func createWindow(title string, x, y, width, height int32, flags WindowFlags, rend renderer.Render) (*Window, error) {
func createWindow(title string, x, y, width, height int32, flags WindowFlags) (Window, error) {
assert.T(isInited, "engine.Init() was not called!")
sdlWin, err := sdl.CreateWindow(title, x, y, width, height, uint32(flags))
if err != nil {
return nil, err
}
win := &Window{
SDLWin: sdlWin,
win := Window{
SDLWin: nil,
EventCallbacks: make([]func(sdl.Event), 0),
Rend: rend,
}
win.GlCtx, err = sdlWin.GLCreateContext()
var err error
win.SDLWin, err = sdl.CreateWindow(title, x, y, width, height, uint32(flags))
if err != nil {
return nil, err
return win, err
}
win.GlCtx, err = win.SDLWin.GLCreateContext()
if err != nil {
return win, err
}
err = initOpenGL()
if err != nil {
return nil, err
return win, err
}
setupDefaultTextures()
// Get rid of the blinding white startup screen (unfortunately there is still one frame of white)
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
sdlWin.GLSwap()
win.SDLWin.GLSwap()
return win, err
}
@ -229,6 +233,57 @@ func initOpenGL() error {
return nil
}
func setupDefaultTextures() error {
// 1x1 black texture
defaultBlackImg := image.NewNRGBA(image.Rect(0, 0, 1, 1))
defaultBlackImg.Set(0, 0, color.NRGBA{R: 0, G: 0, B: 0, A: 1})
defaultBlackImgTex, err := assets.LoadTextureInMemPngImg(defaultBlackImg, &assets.TextureLoadOptions{NoSrgba: true})
if err != nil {
return err
}
assets.DefaultBlackTexId = defaultBlackImgTex
// 1x1 white texture
defaultWhiteImg := image.NewNRGBA(image.Rect(0, 0, 1, 1))
defaultWhiteImg.Set(0, 0, color.NRGBA{R: 255, G: 255, B: 255, A: 1})
defaultWhiteImgTex, err := assets.LoadTextureInMemPngImg(defaultWhiteImg, &assets.TextureLoadOptions{NoSrgba: true})
if err != nil {
return err
}
assets.DefaultWhiteTexId = defaultWhiteImgTex
// Default diffuse
assets.DefaultDiffuseTexId = defaultWhiteImgTex
// Default specular
assets.DefaultSpecularTexId = defaultBlackImgTex
// Default Normal map which is created to be RGB(0.5,0.5,1), which when multiplied by TBN matrix gives the vertex normal.
// 128 is better than 127 for normal maps. See 'Flat Color' section here: http://wiki.polycount.com/wiki/Normal_map
// Basically, 127 can create seams while 128 looks correct
defaultNormalMapImg := image.NewNRGBA(image.Rect(0, 0, 1, 1))
defaultNormalMapImg.Set(0, 0, color.NRGBA{R: 128, G: 128, B: 255, A: 1})
defaultNormalMapTex, err := assets.LoadTextureInMemPngImg(defaultNormalMapImg, &assets.TextureLoadOptions{NoSrgba: true})
if err != nil {
return err
}
assets.DefaultNormalTexId = defaultNormalMapTex
// Default emission
assets.DefaultEmissionTexId = defaultBlackImgTex
assert.T(assets.DefaultBlackTexId.TexID != 0, "The default black texture handle is zero. Either texture wasn't created or handle wasn't updated")
assert.T(assets.DefaultWhiteTexId.TexID != 0, "The default white texture handle is zero. Either texture wasn't created or handle wasn't updated")
assert.T(assets.DefaultDiffuseTexId.TexID != 0, "The default diffuse texture handle is zero. Either texture wasn't created or handle wasn't updated")
assert.T(assets.DefaultSpecularTexId.TexID != 0, "The default specular texture handle is zero. Either texture wasn't created or handle wasn't updated")
assert.T(assets.DefaultNormalTexId.TexID != 0, "The default normal texture handle is zero. Either texture wasn't created or handle wasn't updated")
assert.T(assets.DefaultEmissionTexId.TexID != 0, "The default emission texture handle is zero. Either texture wasn't created or handle wasn't updated")
return nil
}
func SetSrgbFramebuffer(isEnabled bool) {
if isEnabled {

View File

@ -1,6 +1,7 @@
package engine
import (
"github.com/bloeys/nmage/renderer"
"github.com/bloeys/nmage/timing"
nmageimgui "github.com/bloeys/nmage/ui/imgui"
"github.com/go-gl/gl/v4.1-core/gl"
@ -20,7 +21,7 @@ type Game interface {
DeInit()
}
func Run(g Game, w *Window, ui nmageimgui.ImguiInfo) {
func Run(g Game, w *Window, rend renderer.Render, ui nmageimgui.ImguiInfo) {
isRunning = true
@ -56,7 +57,7 @@ func Run(g Game, w *Window, ui nmageimgui.ImguiInfo) {
w.SDLWin.GLSwap()
g.FrameEnd()
w.Rend.FrameEnd()
rend.FrameEnd()
timing.FrameEnded()
}

2
go.mod
View File

@ -8,7 +8,7 @@ require github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6
require (
github.com/bloeys/assimp-go v0.4.4
github.com/bloeys/gglm v0.43.0
github.com/bloeys/gglm v0.49.0
)
require (

4
go.sum
View File

@ -2,8 +2,8 @@ github.com/AllenDang/cimgui-go v0.0.0-20230720025235-f2ff398a66b2 h1:3HA/5qD8Rim
github.com/AllenDang/cimgui-go v0.0.0-20230720025235-f2ff398a66b2/go.mod h1:iNfbIyOBN8k3XScMxULbrwYbPsXEAUD0Jb6UwrspQb8=
github.com/bloeys/assimp-go v0.4.4 h1:Yn5e/RpE0Oes0YMBy8O7KkwAO4R/RpgrZPJCt08dVIU=
github.com/bloeys/assimp-go v0.4.4/go.mod h1:my3yRxT7CfOztmvi+0svmwbaqw0KFrxaHxncoyaEIP0=
github.com/bloeys/gglm v0.43.0 h1:ZpOghR3PHfpkigTDh+FqxLsF0gN8CD6s/bWoei6LyxI=
github.com/bloeys/gglm v0.43.0/go.mod h1:qwJQ0WzV191wAMwlGicbfbChbKoSedMk7gFFX6GnyOk=
github.com/bloeys/gglm v0.49.0 h1:YtbyHpszYhjnxw7KVV0LaCdBktRMqfGx/i37EMomxsE=
github.com/bloeys/gglm v0.49.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=

501
main.go

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,26 @@
package materials
import (
_ "unsafe"
"github.com/bloeys/gglm/gglm"
"github.com/bloeys/nmage/assert"
"github.com/bloeys/nmage/assets"
"github.com/bloeys/nmage/logging"
"github.com/bloeys/nmage/shaders"
"github.com/go-gl/gl/v4.1-core/gl"
)
// @TODO: This noescape magic is to avoid heap allocations done when
// passing vectors or matrices into cgo via set uniform calls.
//
// But I would rather this kind of stuff is done on the gl wrapper level.
// Should we wrap the OpenGL APIs we use ourself?
var (
lastMatId uint32
)
type TextureSlot uint32
const (
@ -21,14 +34,36 @@ const (
TextureSlot_ShadowMap_Array1 TextureSlot = 13
)
type MaterialSettings uint64
const (
MaterialSettings_None MaterialSettings = iota
MaterialSettings_HasModelMtx MaterialSettings = 1 << (iota - 1)
MaterialSettings_HasNormalMtx
)
func (ms *MaterialSettings) Set(flags MaterialSettings) {
*ms |= flags
}
func (ms *MaterialSettings) Remove(flags MaterialSettings) {
*ms &= ^flags
}
func (ms *MaterialSettings) Has(flags MaterialSettings) bool {
return *ms&flags == flags
}
type Material struct {
Id uint32
Name string
ShaderProg shaders.ShaderProgram
Settings MaterialSettings
UnifLocs map[string]int32
AttribLocs map[string]int32
// @TODO do this in a better way. Perhaps something like how we do fbo attachments
// @TODO: Do this in a better way?. Perhaps something like how we do fbo attachments? Or keep it?
// Phong shading
DiffuseTex uint32
SpecularTex uint32
@ -51,26 +86,19 @@ func (m *Material) Bind() {
m.ShaderProg.Bind()
if m.DiffuseTex != 0 {
gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Diffuse))
gl.BindTexture(gl.TEXTURE_2D, m.DiffuseTex)
}
if m.SpecularTex != 0 {
gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Specular))
gl.BindTexture(gl.TEXTURE_2D, m.SpecularTex)
}
if m.NormalTex != 0 {
gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Normal))
gl.BindTexture(gl.TEXTURE_2D, m.NormalTex)
}
if m.EmissionTex != 0 {
gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Emission))
gl.BindTexture(gl.TEXTURE_2D, m.EmissionTex)
}
// @TODO: Have defaults for these
if m.CubemapTex != 0 {
gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Cubemap))
gl.BindTexture(gl.TEXTURE_CUBE_MAP, m.CubemapTex)
@ -139,49 +167,124 @@ func (m *Material) SetUnifFloat32(uniformName string, val float32) {
}
func (m *Material) SetUnifVec2(uniformName string, vec2 *gglm.Vec2) {
gl.ProgramUniform2fv(m.ShaderProg.Id, m.GetUnifLoc(uniformName), 1, &vec2.Data[0])
internalSetUnifVec2(m.ShaderProg.Id, m.GetUnifLoc(uniformName), vec2)
}
//go:noescape
//go:linkname internalSetUnifVec2 github.com/bloeys/nmage/materials.SetUnifVec2
func internalSetUnifVec2(shaderProgId uint32, unifLoc int32, vec2 *gglm.Vec2)
func SetUnifVec2(shaderProgId uint32, unifLoc int32, vec2 *gglm.Vec2) {
gl.ProgramUniform2fv(shaderProgId, unifLoc, 1, &vec2.Data[0])
}
func (m *Material) SetUnifVec3(uniformName string, vec3 *gglm.Vec3) {
gl.ProgramUniform3fv(m.ShaderProg.Id, m.GetUnifLoc(uniformName), 1, &vec3.Data[0])
internalSetUnifVec3(m.ShaderProg.Id, m.GetUnifLoc(uniformName), vec3)
}
//go:noescape
//go:linkname internalSetUnifVec3 github.com/bloeys/nmage/materials.SetUnifVec3
func internalSetUnifVec3(shaderProgId uint32, unifLoc int32, vec3 *gglm.Vec3)
func SetUnifVec3(shaderProgId uint32, unifLoc int32, vec3 *gglm.Vec3) {
gl.ProgramUniform3fv(shaderProgId, unifLoc, 1, &vec3.Data[0])
}
func (m *Material) SetUnifVec4(uniformName string, vec4 *gglm.Vec4) {
gl.ProgramUniform4fv(m.ShaderProg.Id, m.GetUnifLoc(uniformName), 1, &vec4.Data[0])
internalSetUnifVec4(m.ShaderProg.Id, m.GetUnifLoc(uniformName), vec4)
}
//go:noescape
//go:linkname internalSetUnifVec4 github.com/bloeys/nmage/materials.SetUnifVec4
func internalSetUnifVec4(shaderProgId uint32, unifLoc int32, vec4 *gglm.Vec4)
func SetUnifVec4(shaderProgId uint32, unifLoc int32, vec4 *gglm.Vec4) {
gl.ProgramUniform4fv(shaderProgId, unifLoc, 1, &vec4.Data[0])
}
func (m *Material) SetUnifMat2(uniformName string, mat2 *gglm.Mat2) {
gl.ProgramUniformMatrix2fv(m.ShaderProg.Id, m.GetUnifLoc(uniformName), 1, false, &mat2.Data[0][0])
internalSetUnifMat2(m.ShaderProg.Id, m.GetUnifLoc(uniformName), mat2)
}
//go:noescape
//go:linkname internalSetUnifMat2 github.com/bloeys/nmage/materials.SetUnifMat2
func internalSetUnifMat2(shaderProgId uint32, unifLoc int32, mat2 *gglm.Mat2)
func SetUnifMat2(shaderProgId uint32, unifLoc int32, mat2 *gglm.Mat2) {
gl.ProgramUniformMatrix2fv(shaderProgId, unifLoc, 1, false, &mat2.Data[0][0])
}
func (m *Material) SetUnifMat3(uniformName string, mat3 *gglm.Mat3) {
gl.ProgramUniformMatrix3fv(m.ShaderProg.Id, m.GetUnifLoc(uniformName), 1, false, &mat3.Data[0][0])
internalSetUnifMat3(m.ShaderProg.Id, m.GetUnifLoc(uniformName), mat3)
}
//go:noescape
//go:linkname internalSetUnifMat3 github.com/bloeys/nmage/materials.SetUnifMat3
func internalSetUnifMat3(shaderProgId uint32, unifLoc int32, mat3 *gglm.Mat3)
func SetUnifMat3(shaderProgId uint32, unifLoc int32, mat3 *gglm.Mat3) {
gl.ProgramUniformMatrix3fv(shaderProgId, unifLoc, 1, false, &mat3.Data[0][0])
}
func (m *Material) SetUnifMat4(uniformName string, mat4 *gglm.Mat4) {
gl.ProgramUniformMatrix4fv(m.ShaderProg.Id, m.GetUnifLoc(uniformName), 1, false, &mat4.Data[0][0])
internalSetUnifMat4(m.ShaderProg.Id, m.GetUnifLoc(uniformName), mat4)
}
//go:noescape
//go:linkname internalSetUnifMat4 github.com/bloeys/nmage/materials.SetUnifMat4
func internalSetUnifMat4(shaderProgId uint32, unifLoc int32, mat4 *gglm.Mat4)
func SetUnifMat4(shaderProgId uint32, unifLoc int32, mat4 *gglm.Mat4) {
gl.ProgramUniformMatrix4fv(shaderProgId, unifLoc, 1, false, &mat4.Data[0][0])
}
func (m *Material) Delete() {
gl.DeleteProgram(m.ShaderProg.Id)
}
func NewMaterial(matName, shaderPath string) *Material {
func getNewMatId() uint32 {
lastMatId++
return lastMatId
}
func NewMaterial(matName, shaderPath string) Material {
shdrProg, err := shaders.LoadAndCompileCombinedShader(shaderPath)
if err != nil {
logging.ErrLog.Fatalf("Failed to create new material '%s'. Err: %s\n", matName, err.Error())
}
return &Material{Name: matName, ShaderProg: shdrProg, UnifLocs: make(map[string]int32), AttribLocs: make(map[string]int32)}
return Material{
Id: getNewMatId(),
Name: matName,
ShaderProg: shdrProg,
UnifLocs: make(map[string]int32),
AttribLocs: make(map[string]int32),
DiffuseTex: assets.DefaultDiffuseTexId.TexID,
SpecularTex: assets.DefaultSpecularTexId.TexID,
NormalTex: assets.DefaultNormalTexId.TexID,
EmissionTex: assets.DefaultEmissionTexId.TexID,
}
}
func NewMaterialSrc(matName string, shaderSrc []byte) *Material {
func NewMaterialSrc(matName string, shaderSrc []byte) Material {
shdrProg, err := shaders.LoadAndCompileCombinedShaderSrc(shaderSrc)
if err != nil {
logging.ErrLog.Fatalf("Failed to create new material '%s'. Err: %s\n", matName, err.Error())
}
return &Material{Name: matName, ShaderProg: shdrProg, UnifLocs: make(map[string]int32), AttribLocs: make(map[string]int32)}
return Material{
Id: getNewMatId(),
Name: matName,
ShaderProg: shdrProg,
UnifLocs: make(map[string]int32),
AttribLocs: make(map[string]int32),
DiffuseTex: assets.DefaultDiffuseTexId.TexID,
SpecularTex: assets.DefaultSpecularTexId.TexID,
NormalTex: assets.DefaultNormalTexId.TexID,
EmissionTex: assets.DefaultEmissionTexId.TexID,
}
}

View File

@ -17,23 +17,47 @@ type SubMesh struct {
type Mesh struct {
Name string
/*
Vao has the following shader attribute layout:
- Loc0: Pos
- Loc1: Normal
- Loc2: UV0
- Loc3: Tangent
- (Optional) Color
Optional stuff appear in the order in this list, depending on what other optional stuff exists.
For example:
- If color exists it will be in Loc3, otherwise it is unset
*/
Vao buffers.VertexArray
SubMeshes []SubMesh
}
func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh, error) {
var (
// DefaultMeshLoadFlags are the flags always applied when loading a new mesh regardless
// of what post process flags are used when loading a mesh.
//
// Defaults to: asig.PostProcessTriangulate | asig.PostProcessCalcTangentSpace;
// Note: changing this will break the normal lit shaders, which expect tangents to be there
DefaultMeshLoadFlags asig.PostProcess = asig.PostProcessTriangulate | asig.PostProcessCalcTangentSpace
)
scene, release, err := asig.ImportFile(modelPath, asig.PostProcessTriangulate|postProcessFlags)
func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (Mesh, error) {
finalPostProcessFlags := DefaultMeshLoadFlags | postProcessFlags
scene, release, err := asig.ImportFile(modelPath, finalPostProcessFlags)
if err != nil {
return nil, errors.New("Failed to load model. Err: " + err.Error())
return Mesh{}, errors.New("Failed to load model. Err: " + err.Error())
}
defer release()
if len(scene.Meshes) == 0 {
return nil, errors.New("No meshes found in file: " + modelPath)
return Mesh{}, errors.New("No meshes found in file: " + modelPath)
}
mesh := &Mesh{
mesh := Mesh{
Name: name,
Vao: buffers.NewVertexArray(),
SubMeshes: make([]SubMesh, 0, 1),
@ -42,8 +66,17 @@ func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh,
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)
// Estimate a useful prealloc capacity based on the first submesh that has vertex pos+normals+tangents+texCoords
vertexBufDataCapacity := len(scene.Meshes[0].Vertices) * 3 * 3 * 3 * 2
// Increase capacity depending on what the mesh has
if len(scene.Meshes[0].ColorSets) > 0 && len(scene.Meshes[0].ColorSets[0]) > 0 {
vertexBufDataCapacity *= 4
}
var vertexBufData []float32 = make([]float32, 0, vertexBufDataCapacity)
// Initial size assumes 3 indices per face
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))
@ -52,12 +85,25 @@ func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh,
sceneMesh := scene.Meshes[i]
// We always want tangents and UV0
if len(sceneMesh.Tangents) == 0 {
sceneMesh.Tangents = make([]gglm.Vec3, len(sceneMesh.Vertices))
}
if len(sceneMesh.TexCoords[0]) == 0 {
sceneMesh.TexCoords[0] = make([]gglm.Vec3, len(sceneMesh.Vertices))
}
layoutToUse := []buffers.Element{{ElementType: buffers.DataTypeVec3}, {ElementType: buffers.DataTypeVec3}, {ElementType: buffers.DataTypeVec2}}
if len(sceneMesh.ColorSets) > 0 && len(sceneMesh.ColorSets[0]) > 0 {
hasColorSet0 := len(sceneMesh.ColorSets) > 0 && len(sceneMesh.ColorSets[0]) > 0
layoutToUse := []buffers.Element{
{ElementType: buffers.DataTypeVec3}, // Position
{ElementType: buffers.DataTypeVec3}, // Normals
{ElementType: buffers.DataTypeVec3}, // Tangents
{ElementType: buffers.DataTypeVec2}, // UV0
}
if hasColorSet0 {
layoutToUse = append(layoutToUse, buffers.Element{ElementType: buffers.DataTypeVec4})
}
@ -79,8 +125,14 @@ func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh,
}
}
arrs := []arrToInterleave{{V3s: sceneMesh.Vertices}, {V3s: sceneMesh.Normals}, {V2s: v3sToV2s(sceneMesh.TexCoords[0])}}
if len(sceneMesh.ColorSets) > 0 && len(sceneMesh.ColorSets[0]) > 0 {
arrs := []arrToInterleave{
{V3s: sceneMesh.Vertices},
{V3s: sceneMesh.Normals},
{V3s: sceneMesh.Tangents},
{V2s: v3sToV2s(sceneMesh.TexCoords[0])},
}
if hasColorSet0 {
arrs = append(arrs, arrToInterleave{V4s: sceneMesh.ColorSets[0]})
}

View File

@ -12,24 +12,31 @@ import (
var _ renderer.Render = &Rend3DGL{}
type Rend3DGL struct {
BoundVao *buffers.VertexArray
BoundMesh *meshes.Mesh
BoundMat *materials.Material
BoundVaoId uint32
BoundMatId uint32
BoundMeshVaoId uint32
}
func (r *Rend3DGL) DrawMesh(mesh *meshes.Mesh, modelMat *gglm.TrMat, mat *materials.Material) {
if mesh != r.BoundMesh {
if mesh.Vao.Id != r.BoundMeshVaoId {
mesh.Vao.Bind()
r.BoundMesh = mesh
r.BoundMeshVaoId = mesh.Vao.Id
}
if mat != r.BoundMat {
if mat.Id != r.BoundMatId {
mat.Bind()
r.BoundMat = mat
r.BoundMatId = mat.Id
}
if mat.Settings.Has(materials.MaterialSettings_HasModelMtx) {
mat.SetUnifMat4("modelMat", &modelMat.Mat4)
}
if mat.Settings.Has(materials.MaterialSettings_HasNormalMtx) {
normalMat := modelMat.Clone().InvertAndTranspose().ToMat3()
mat.SetUnifMat3("normalMat", &normalMat)
}
for i := 0; i < len(mesh.SubMeshes); i++ {
gl.DrawElementsBaseVertexWithOffset(gl.TRIANGLES, mesh.SubMeshes[i].IndexCount, gl.UNSIGNED_INT, uintptr(mesh.SubMeshes[i].BaseIndex), mesh.SubMeshes[i].BaseVertex)
@ -38,14 +45,14 @@ func (r *Rend3DGL) DrawMesh(mesh *meshes.Mesh, modelMat *gglm.TrMat, mat *materi
func (r *Rend3DGL) DrawVertexArray(mat *materials.Material, vao *buffers.VertexArray, firstElement int32, elementCount int32) {
if vao != r.BoundVao {
if vao.Id != r.BoundVaoId {
vao.Bind()
r.BoundVao = vao
r.BoundVaoId = vao.Id
}
if mat != r.BoundMat {
if mat.Id != r.BoundMatId {
mat.Bind()
r.BoundMat = mat
r.BoundMatId = mat.Id
}
gl.DrawArrays(gl.TRIANGLES, firstElement, elementCount)
@ -53,14 +60,14 @@ func (r *Rend3DGL) DrawVertexArray(mat *materials.Material, vao *buffers.VertexA
func (r *Rend3DGL) DrawCubemap(mesh *meshes.Mesh, mat *materials.Material) {
if mesh != r.BoundMesh {
if mesh.Vao.Id != r.BoundMeshVaoId {
mesh.Vao.Bind()
r.BoundMesh = mesh
r.BoundMeshVaoId = mesh.Vao.Id
}
if mat != r.BoundMat {
if mat.Id != r.BoundMatId {
mat.Bind()
r.BoundMat = mat
r.BoundMatId = mat.Id
}
for i := 0; i < len(mesh.SubMeshes); i++ {
@ -69,8 +76,9 @@ func (r *Rend3DGL) DrawCubemap(mesh *meshes.Mesh, mat *materials.Material) {
}
func (r3d *Rend3DGL) FrameEnd() {
r3d.BoundMesh = nil
r3d.BoundMat = nil
r3d.BoundVaoId = 0
r3d.BoundMatId = 0
r3d.BoundMeshVaoId = 0
}
func NewRend3DGL() *Rend3DGL {

View File

@ -2,22 +2,19 @@
#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;
layout(location=2) in vec3 vertTangentIn;
layout(location=3) in vec2 vertUV0In;
layout(location=4) in vec3 vertColorIn;
out vec3 vertNormal;
out vec2 vertUV0;
out vec3 vertColor;
out vec3 fragPos;
//MVP = Model View Projection
uniform mat4 modelMat;
uniform mat4 projViewMat;
void main()
{
vertNormal = mat3(transpose(inverse(modelMat))) * vertNormalIn;
vertUV0 = vertUV0In;
vertColor = vertColorIn;
@ -31,7 +28,6 @@ void main()
#version 410
in vec3 vertColor;
in vec3 vertNormal;
in vec2 vertUV0;
in vec3 fragPos;

View File

@ -3,26 +3,19 @@
layout(location=0) in vec3 vertPosIn;
layout(location=1) in vec3 vertNormalIn;
layout(location=2) in vec2 vertUV0In;
layout(location=3) in vec3 vertColorIn;
layout(location=2) in vec3 vertTangentIn;
layout(location=3) in vec2 vertUV0In;
layout(location=4) in vec3 vertColorIn;
out vec3 vertNormal;
out vec2 vertUV0;
out vec3 vertColor;
out vec3 fragPos;
//MVP = Model View Projection
uniform mat4 modelMat;
uniform mat4 projViewMat;
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;
@ -41,7 +34,6 @@ struct Material {
uniform Material material;
in vec3 vertColor;
in vec3 vertNormal;
in vec2 vertUV0;
in vec3 fragPos;

View File

@ -1,66 +1,34 @@
//shader:vertex
#version 410
#define NUM_SPOT_LIGHTS 4
#define NUM_POINT_LIGHTS 8
//
// Inputs
//
layout(location=0) in vec3 vertPosIn;
layout(location=1) in vec3 vertNormalIn;
layout(location=2) in vec2 vertUV0In;
layout(location=3) in vec3 vertColorIn;
layout(location=2) in vec3 vertTangentIn;
layout(location=3) in vec2 vertUV0In;
layout(location=4) in vec3 vertColorIn;
//
// Uniforms
//
uniform vec3 camPos;
uniform mat4 modelMat;
uniform mat3 normalMat;
uniform mat4 projViewMat;
uniform mat4 dirLightProjViewMat;
#define NUM_SPOT_LIGHTS 4
uniform mat4 spotLightProjViewMats[NUM_SPOT_LIGHTS];
out vec3 vertNormal;
out vec2 vertUV0;
out vec3 vertColor;
out vec3 fragPos;
out vec4 fragPosDirLight;
out vec4 fragPosSpotLight[NUM_SPOT_LIGHTS];
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;
vec4 modelVert = modelMat * vec4(vertPosIn, 1);
fragPos = modelVert.xyz;
fragPosDirLight = dirLightProjViewMat * vec4(fragPos, 1);
for (int i = 0; i < NUM_SPOT_LIGHTS; i++)
fragPosSpotLight[i] = spotLightProjViewMats[i] * vec4(fragPos, 1);
gl_Position = projViewMat * modelVert;
}
//shader:fragment
#version 410
struct Material {
sampler2D diffuse;
sampler2D specular;
// sampler2D normal;
sampler2D emission;
float shininess;
};
uniform Material material;
struct DirLight {
vec3 dir;
vec3 diffuseColor;
vec3 specularColor;
sampler2D shadowMap;
};
uniform DirLight dirLight;
struct PointLight {
@ -72,8 +40,133 @@ struct PointLight {
float quadratic;
float farPlane;
};
uniform PointLight pointLights[NUM_POINT_LIGHTS];
struct SpotLight {
vec3 pos;
vec3 dir;
vec3 diffuseColor;
vec3 specularColor;
float innerCutoff;
float outerCutoff;
};
uniform SpotLight spotLights[NUM_SPOT_LIGHTS];
//
// Outputs
//
out vec2 vertUV0;
out vec3 vertColor;
out vec3 fragPos;
out vec3 fragPosDirLight;
out vec4 fragPosSpotLight[NUM_SPOT_LIGHTS];
out vec3 tangentCamPos;
out vec3 tangentFragPos;
out vec3 tangentDirLightDir;
out vec3 tangentSpotLightPositions[NUM_SPOT_LIGHTS];
out vec3 tangentSpotLightDirections[NUM_SPOT_LIGHTS];
out vec3 tangentPointLightPositions[NUM_POINT_LIGHTS];
void main()
{
vertUV0 = vertUV0In;
vertColor = vertColorIn;
vec4 modelVert = modelMat * vec4(vertPosIn, 1);
// Tangent-BiTangent-Normal matrix for normal mapping
vec3 T = normalize(vec3(modelMat * vec4(vertTangentIn, 0.0)));
vec3 N = normalize(vec3(modelMat * vec4(vertNormalIn, 0.0)));
// Ensure T is orthogonal with respect to N
T = normalize(T - dot(T, N) * N);
vec3 B = cross(N, T);
mat3 tbnMtx = transpose(mat3(T, B, N));
// Lighting related
fragPos = modelVert.xyz;
fragPosDirLight = vec3(dirLightProjViewMat * vec4(fragPos, 1));
tangentCamPos = tbnMtx * camPos;
tangentFragPos = tbnMtx * fragPos;
tangentDirLightDir = tbnMtx * dirLight.dir;
for (int i = 0; i < NUM_POINT_LIGHTS; i++)
tangentPointLightPositions[i] = tbnMtx * pointLights[i].pos;
for (int i = 0; i < NUM_SPOT_LIGHTS; i++)
{
fragPosSpotLight[i] = spotLightProjViewMats[i] * vec4(fragPos, 1);
tangentSpotLightPositions[i] = tbnMtx * spotLights[i].pos;
tangentSpotLightDirections[i] = tbnMtx * spotLights[i].dir;
}
gl_Position = projViewMat * modelVert;
}
//shader:fragment
#version 410
/*
Note that while all lighting calculations are done in tangent space,
shadow mapping is done in world space.
The exception is the bias calculation. Since the bias relies on the normal
and the normal is in tangent space, we use a tangent space fragment position
with it, but the rest of shadow processing is in world space.
*/
#define NUM_SPOT_LIGHTS 4
#define NUM_POINT_LIGHTS 8
//
// Inputs
//
in vec3 fragPos;
in vec2 vertUV0;
in vec3 vertColor;
in vec3 fragPosDirLight;
in vec4 fragPosSpotLight[NUM_SPOT_LIGHTS];
in vec3 tangentCamPos;
in vec3 tangentFragPos;
in vec3 tangentDirLightDir;
in vec3 tangentSpotLightPositions[NUM_SPOT_LIGHTS];
in vec3 tangentSpotLightDirections[NUM_SPOT_LIGHTS];
in vec3 tangentPointLightPositions[NUM_POINT_LIGHTS];
//
// Uniforms
//
struct Material {
sampler2D diffuse;
sampler2D specular;
sampler2D normal;
sampler2D emission;
float shininess;
};
uniform Material material;
struct DirLight {
vec3 dir;
vec3 diffuseColor;
vec3 specularColor;
sampler2D shadowMap;
};
uniform DirLight dirLight;
struct PointLight {
vec3 pos;
vec3 diffuseColor;
vec3 specularColor;
float constant;
float linear;
float quadratic;
float farPlane;
};
uniform PointLight pointLights[NUM_POINT_LIGHTS];
uniform samplerCubeArray pointLightCubeShadowMaps;
@ -85,37 +178,29 @@ struct SpotLight {
float innerCutoff;
float outerCutoff;
};
#define NUM_SPOT_LIGHTS 4
uniform SpotLight spotLights[NUM_SPOT_LIGHTS];
uniform sampler2DArray spotLightShadowMaps;
uniform vec3 camPos;
uniform vec3 ambientColor = vec3(0.2, 0.2, 0.2);
in vec3 vertColor;
in vec3 vertNormal;
in vec2 vertUV0;
in vec3 fragPos;
in vec4 fragPosDirLight;
in vec4 fragPosSpotLight[NUM_SPOT_LIGHTS];
//
// Outputs
//
out vec4 fragColor;
//
// Global variables used as cache for lighting calculations
//
vec3 tangentViewDir;
vec4 diffuseTexColor;
vec4 specularTexColor;
vec4 emissionTexColor;
vec3 normalizedVertNorm;
vec3 viewDir;
float CalcDirShadow(sampler2D shadowMap, vec3 lightDir)
float CalcDirShadow(sampler2D shadowMap, vec3 tangentLightDir)
{
// Move from clip space to NDC
vec3 projCoords = fragPosDirLight.xyz / fragPosDirLight.w;
// Move from [-1,1] to [0, 1]
projCoords = projCoords * 0.5 + 0.5;
vec3 projCoords = fragPosDirLight * 0.5 + 0.5;
// If sampling outside the depth texture then force 'no shadow'
if(projCoords.z > 1)
@ -126,7 +211,7 @@ float CalcDirShadow(sampler2D shadowMap, vec3 lightDir)
// Bias in the range [0.005, 0.05] depending on the angle, where a higher
// angle gives a higher bias, as shadow acne gets worse with angle
float bias = max(0.05 * (1 - dot(normalizedVertNorm, lightDir)), 0.005);
float bias = max(0.05 * (1 - dot(normalizedVertNorm, tangentLightDir)), 0.005);
// 'Percentage Close Filtering'.
// Basically get soft shadows by averaging this texel and surrounding ones
@ -151,14 +236,14 @@ float CalcDirShadow(sampler2D shadowMap, vec3 lightDir)
vec3 CalcDirLight()
{
vec3 lightDir = normalize(-dirLight.dir);
vec3 lightDir = normalize(-tangentDirLightDir);
// Diffuse
float diffuseAmount = max(0.0, dot(normalizedVertNorm, lightDir));
vec3 finalDiffuse = diffuseAmount * dirLight.diffuseColor * diffuseTexColor.rgb;
// Specular
vec3 halfwayDir = normalize(lightDir + viewDir);
vec3 halfwayDir = normalize(lightDir + tangentViewDir);
float specularAmount = pow(max(dot(normalizedVertNorm, halfwayDir), 0.0), material.shininess);
vec3 finalSpecular = specularAmount * dirLight.specularColor * specularTexColor.rgb;
@ -168,9 +253,9 @@ vec3 CalcDirLight()
return (finalDiffuse + finalSpecular) * (1 - shadow);
}
float CalcPointShadow(int lightIndex, vec3 lightPos, vec3 lightDir, float farPlane) {
float CalcPointShadow(int lightIndex, vec3 worldLightPos, vec3 tangentLightDir, float farPlane) {
vec3 lightToFrag = fragPos - lightPos;
vec3 lightToFrag = fragPos - worldLightPos;
float closestDepth = texture(pointLightCubeShadowMaps, vec4(lightToFrag, lightIndex)).r;
@ -180,7 +265,8 @@ float CalcPointShadow(int lightIndex, vec3 lightPos, vec3 lightDir, float farPla
// Get depth of current fragment
float currentDepth = length(lightToFrag);
float bias = max(0.05 * (1 - dot(normalizedVertNorm, lightDir)), 0.005);
float bias = max(0.05 * (1 - dot(normalizedVertNorm, tangentLightDir)), 0.005);
float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0;
return shadow;
@ -193,28 +279,29 @@ vec3 CalcPointLight(PointLight pointLight, int lightIndex)
return vec3(0);
}
vec3 lightDir = normalize(pointLight.pos - fragPos);
vec3 tangentLightPos = tangentPointLightPositions[lightIndex];
vec3 tangentLightDir = normalize(tangentLightPos - tangentFragPos);
// Diffuse
float diffuseAmount = max(0.0, dot(normalizedVertNorm, lightDir));
float diffuseAmount = max(0.0, dot(normalizedVertNorm, tangentLightDir));
vec3 finalDiffuse = diffuseAmount * pointLight.diffuseColor * diffuseTexColor.rgb;
// Specular
vec3 halfwayDir = normalize(lightDir + viewDir);
vec3 halfwayDir = normalize(tangentLightDir + tangentViewDir);
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 distToLight = length(tangentLightPos - tangentFragPos);
float attenuation = 1 / (pointLight.constant + pointLight.linear * distToLight + pointLight.quadratic * (distToLight * distToLight));
// Shadow
float shadow = CalcPointShadow(lightIndex, pointLight.pos, lightDir, pointLight.farPlane);
float shadow = CalcPointShadow(lightIndex, pointLight.pos, tangentLightDir, pointLight.farPlane);
return (finalDiffuse + finalSpecular) * attenuation * (1 - shadow);
}
float CalcSpotShadow(vec3 lightDir, int lightIndex)
float CalcSpotShadow(vec3 tangentLightDir, int lightIndex)
{
// Move from clip space to NDC
vec3 projCoords = fragPosSpotLight[lightIndex].xyz / fragPosSpotLight[lightIndex].w;
@ -231,7 +318,7 @@ float CalcSpotShadow(vec3 lightDir, int lightIndex)
// Bias in the range [0.005, 0.05] depending on the angle, where a higher
// angle gives a higher bias, as shadow acne gets worse with angle
float bias = max(0.05 * (1 - dot(normalizedVertNorm, lightDir)), 0.005);
float bias = max(0.05 * (1 - dot(normalizedVertNorm, tangentLightDir)), 0.005);
// 'Percentage Close Filtering'.
// Basically get soft shadows by averaging this texel and surrounding ones
@ -259,12 +346,13 @@ vec3 CalcSpotLight(SpotLight light, int lightIndex)
if (light.innerCutoff == 0)
return vec3(0);
vec3 fragToLightDir = normalize(light.pos - fragPos);
vec3 tangentLightDir = tangentSpotLightDirections[lightIndex];
vec3 fragToLightDir = normalize(tangentSpotLightPositions[lightIndex] - tangentFragPos);
// 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 theta = dot(fragToLightDir, normalize(-tangentLightDir));
float epsilon = (light.innerCutoff - light.outerCutoff);
float intensity = clamp((theta - light.outerCutoff) / epsilon, float(0), float(1));
@ -276,7 +364,7 @@ vec3 CalcSpotLight(SpotLight light, int lightIndex)
vec3 finalDiffuse = diffuseAmount * light.diffuseColor * diffuseTexColor.rgb;
// Specular
vec3 halfwayDir = normalize(fragToLightDir + viewDir);
vec3 halfwayDir = normalize(fragToLightDir + tangentViewDir);
float specularAmount = pow(max(dot(normalizedVertNorm, halfwayDir), 0.0), material.shininess);
vec3 finalSpecular = specularAmount * light.specularColor * specularTexColor.rgb;
@ -286,15 +374,21 @@ vec3 CalcSpotLight(SpotLight light, int lightIndex)
return (finalDiffuse + finalSpecular) * intensity * (1 - shadow);
}
#define DRAW_NORMALS false
void main()
{
// Shared values
tangentViewDir = normalize(tangentCamPos - tangentFragPos);
diffuseTexColor = texture(material.diffuse, vertUV0);
specularTexColor = texture(material.specular, vertUV0);
emissionTexColor = texture(material.emission, vertUV0);
normalizedVertNorm = normalize(vertNormal);
viewDir = normalize(camPos - fragPos);
// Read normal data encoded [0,1]
normalizedVertNorm = texture(material.normal, vertUV0).rgb;
// Remap normal to [-1,1]
normalizedVertNorm = normalize(normalizedVertNorm * 2.0 - 1.0);
// Light contributions
vec3 finalColor = CalcDirLight();
@ -313,4 +407,9 @@ void main()
vec3 finalAmbient = ambientColor * diffuseTexColor.rgb;
fragColor = vec4(finalColor + finalAmbient + finalEmission, 1);
if (DRAW_NORMALS)
{
fragColor = vec4(texture(material.normal, vertUV0).rgb, 1);
}
}

View File

@ -3,8 +3,9 @@
layout(location=0) in vec3 vertPosIn;
layout(location=1) in vec3 vertNormalIn;
layout(location=2) in vec2 vertUV0In;
layout(location=3) in vec3 vertColorIn;
layout(location=2) in vec3 vertTangentIn;
layout(location=3) in vec2 vertUV0In;
layout(location=4) in vec3 vertColorIn;
out vec3 vertUV0;

View File

@ -0,0 +1,50 @@
//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)
);
void main()
{
vec4 vertData = quadData[gl_VertexID];
vertUV0 = vertData.zw;
gl_Position = vec4(vertData.xy, 0.0, 1.0);
}
//shader:fragment
#version 410
struct Material {
sampler2D diffuse;
};
uniform float exposure = 1;
uniform Material material;
in vec2 vertUV0;
out vec4 fragColor;
void main()
{
vec4 diffuseTexColor = texture(material.diffuse, vertUV0);
// Reinhard tone mapping
// vec3 mappedColor = diffuseTexColor.rgb / (diffuseTexColor.rgb + vec3(1.0));
// Exposure tone mapping
vec3 mappedColor = vec3(1.0) - exp(-diffuseTexColor.rgb * exposure);
fragColor = vec4(mappedColor, 1);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 B

BIN
res/textures/brickwall-normal.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
res/textures/brickwall.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 B

View File

@ -12,7 +12,7 @@ import (
type ImguiInfo struct {
ImCtx imgui.Context
Mat *materials.Material
Mat materials.Material
VaoID uint32
VboID uint32
IndexBufID uint32
@ -204,7 +204,7 @@ void main()
// If the path is empty a default nMage shader is used
func NewImGui(shaderPath string) ImguiInfo {
var imguiMat *materials.Material
var imguiMat materials.Material
if shaderPath == "" {
imguiMat = materials.NewMaterialSrc("ImGUI Mat", []byte(DefaultImguiShader))
} else {