207 lines
5.4 KiB
Go
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
|
|
}
|