zworld/engine/render/pass/shadow.go
2024-01-14 22:56:06 +08:00

207 lines
5.4 KiB
Go

package pass
import (
"fmt"
"github.com/vkngwrapper/core/v2/core1_0"
"log"
"zworld/engine/object"
"zworld/engine/object/light"
"zworld/engine/object/mesh"
"zworld/engine/renderapi"
"zworld/engine/renderapi/command"
"zworld/engine/renderapi/framebuffer"
"zworld/engine/renderapi/renderpass"
"zworld/engine/renderapi/renderpass/attachment"
"zworld/engine/renderapi/texture"
"zworld/engine/renderapi/vulkan"
)
type Shadow interface {
Pass
Shadowmap(lit light.T, cascade int) texture.T
}
type shadowpass struct {
app vulkan.App
target vulkan.Target
pass renderpass.T
size int
// should be replaced with a proper cache that will evict unused maps
shadowmaps map[light.T]Shadowmap
lightQuery *object.Query[light.T]
meshQuery *object.Query[mesh.Mesh]
}
type Shadowmap struct {
Cascades []Cascade
}
type Cascade struct {
Texture texture.T
Frame framebuffer.T
Mats MaterialCache
}
func NewShadowPass(app vulkan.App, target vulkan.Target) Shadow {
pass := renderpass.New(app.Device(), renderpass.Args{
Name: "Shadow",
DepthAttachment: &attachment.Depth{
Image: attachment.NewImage("shadowmap", core1_0.FormatD32SignedFloat, core1_0.ImageUsageDepthStencilAttachment|core1_0.ImageUsageInputAttachment|core1_0.ImageUsageSampled),
LoadOp: core1_0.AttachmentLoadOpClear,
StencilLoadOp: core1_0.AttachmentLoadOpClear,
StoreOp: core1_0.AttachmentStoreOpStore,
FinalLayout: core1_0.ImageLayoutShaderReadOnlyOptimal,
ClearDepth: 1,
},
Subpasses: []renderpass.Subpass{
{
Name: MainSubpass,
Depth: true,
},
},
Dependencies: []renderpass.SubpassDependency{
{
Src: renderpass.ExternalSubpass,
Dst: MainSubpass,
Flags: core1_0.DependencyByRegion,
// External passes must finish reading depth textures in fragment shaders
SrcStageMask: core1_0.PipelineStageEarlyFragmentTests | core1_0.PipelineStageLateFragmentTests,
SrcAccessMask: core1_0.AccessDepthStencilAttachmentRead,
// Before we can write to the depth buffer
DstStageMask: core1_0.PipelineStageEarlyFragmentTests | core1_0.PipelineStageLateFragmentTests,
DstAccessMask: core1_0.AccessDepthStencilAttachmentWrite,
},
{
Src: MainSubpass,
Dst: renderpass.ExternalSubpass,
Flags: core1_0.DependencyByRegion,
// The shadow pass must finish writing the depth attachment
SrcStageMask: core1_0.PipelineStageEarlyFragmentTests | core1_0.PipelineStageLateFragmentTests,
SrcAccessMask: core1_0.AccessDepthStencilAttachmentWrite,
// Before it can be used as a shadow map texture in a fragment shader
DstStageMask: core1_0.PipelineStageFragmentShader,
DstAccessMask: core1_0.AccessShaderRead,
},
},
})
return &shadowpass{
app: app,
target: target,
pass: pass,
shadowmaps: make(map[light.T]Shadowmap),
size: 2048,
meshQuery: object.NewQuery[mesh.Mesh](),
lightQuery: object.NewQuery[light.T](),
}
}
func (p *shadowpass) Name() string {
return "Shadow"
}
func (p *shadowpass) createShadowmap(light light.T) Shadowmap {
log.Println("creating shadowmap for", light.Name())
cascades := make([]Cascade, light.Shadowmaps())
for i := range cascades {
key := fmt.Sprintf("%s-%d", object.Key("light", light), i)
fbuf, err := framebuffer.New(p.app.Device(), key, p.size, p.size, p.pass)
if err != nil {
panic(err)
}
// the frame buffer object will allocate a new depth image for us
view := fbuf.Attachment(attachment.DepthName)
tex, err := texture.FromView(p.app.Device(), key, view, texture.Args{
Aspect: core1_0.ImageAspectDepth,
})
if err != nil {
panic(err)
}
cascades[i].Texture = tex
cascades[i].Frame = fbuf
// each light cascade needs its own shadow materials - or rather, their own descriptors
// cheating a bit by creating entire materials for each light, fix it later.
mats := NewShadowMaterialMaker(p.app, p.pass, p.target.Frames())
cascades[i].Mats = mats
}
shadowmap := Shadowmap{
Cascades: cascades,
}
p.shadowmaps[light] = shadowmap
return shadowmap
}
func (p *shadowpass) Record(cmds command.Recorder, args renderapi.Args, scene object.Component) {
lights := p.lightQuery.
Reset().
Where(func(lit light.T) bool { return lit.Type() == light.TypeDirectional && lit.CastShadows() }).
Collect(scene)
for _, light := range lights {
shadowmap, mapExists := p.shadowmaps[light]
if !mapExists {
shadowmap = p.createShadowmap(light)
}
for index, cascade := range shadowmap.Cascades {
camera := light.ShadowProjection(index)
frame := cascade.Frame
cmds.Record(func(cmd command.Buffer) {
cmd.CmdBeginRenderPass(p.pass, frame)
})
// todo: filter only meshes that cast shadows
meshes := p.meshQuery.
Reset().
Where(castsShadows).
Collect(scene)
groups := MaterialGroups(cascade.Mats, args.Frame, meshes)
groups.Draw(cmds, camera, nil)
cmds.Record(func(cmd command.Buffer) {
cmd.CmdEndRenderPass()
})
}
}
}
func castsShadows(m mesh.Mesh) bool {
return m.CastShadows()
}
func (p *shadowpass) Shadowmap(light light.T, cascade int) texture.T {
if shadowmap, exists := p.shadowmaps[light]; exists {
return shadowmap.Cascades[cascade].Texture
}
return nil
}
func (p *shadowpass) Destroy() {
for _, shadowmap := range p.shadowmaps {
for _, cascade := range shadowmap.Cascades {
cascade.Frame.Destroy()
cascade.Texture.Destroy()
cascade.Mats.Destroy()
}
}
p.shadowmaps = nil
p.pass.Destroy()
p.pass = nil
}