268 lines
6.5 KiB
Go
268 lines
6.5 KiB
Go
package pass
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/vkngwrapper/core/v2/core1_0"
|
|
"unsafe"
|
|
"zworld/engine/object"
|
|
"zworld/engine/renderapi"
|
|
"zworld/engine/renderapi/command"
|
|
"zworld/engine/renderapi/descriptor"
|
|
"zworld/engine/renderapi/framebuffer"
|
|
"zworld/engine/renderapi/image"
|
|
"zworld/engine/renderapi/material"
|
|
"zworld/engine/renderapi/renderpass"
|
|
"zworld/engine/renderapi/renderpass/attachment"
|
|
"zworld/engine/renderapi/shader"
|
|
"zworld/engine/renderapi/texture"
|
|
"zworld/engine/renderapi/vertex"
|
|
"zworld/engine/renderapi/vulkan"
|
|
"zworld/plugins/math"
|
|
"zworld/plugins/math/mat4"
|
|
"zworld/plugins/math/random"
|
|
"zworld/plugins/math/vec3"
|
|
"zworld/plugins/math/vec4"
|
|
)
|
|
|
|
const SSAOSamples = 32
|
|
|
|
type AmbientOcclusionPass struct {
|
|
app vulkan.App
|
|
pass renderpass.T
|
|
fbuf framebuffer.Array
|
|
mat *material.Material[*AmbientOcclusionDescriptors]
|
|
desc []*material.Instance[*AmbientOcclusionDescriptors]
|
|
quad vertex.Mesh
|
|
|
|
scale float32
|
|
position []texture.T
|
|
normal []texture.T
|
|
kernel [SSAOSamples]vec4.T
|
|
noise *HemisphereNoise
|
|
}
|
|
|
|
var _ Pass = &AmbientOcclusionPass{}
|
|
|
|
type AmbientOcclusionParams struct {
|
|
Projection mat4.T
|
|
Kernel [SSAOSamples]vec4.T
|
|
Samples int32
|
|
Scale float32
|
|
Radius float32
|
|
Bias float32
|
|
Power float32
|
|
}
|
|
|
|
type AmbientOcclusionDescriptors struct {
|
|
descriptor.Set
|
|
Position *descriptor.Sampler
|
|
Normal *descriptor.Sampler
|
|
Noise *descriptor.Sampler
|
|
Params *descriptor.Uniform[AmbientOcclusionParams]
|
|
}
|
|
|
|
func NewAmbientOcclusionPass(app vulkan.App, target vulkan.Target, gbuffer GeometryBuffer) *AmbientOcclusionPass {
|
|
var err error
|
|
p := &AmbientOcclusionPass{
|
|
app: app,
|
|
scale: float32(gbuffer.Width()) / float32(target.Width()),
|
|
}
|
|
|
|
p.pass = renderpass.New(app.Device(), renderpass.Args{
|
|
Name: "AmbientOcclusion",
|
|
ColorAttachments: []attachment.Color{
|
|
{
|
|
Name: OutputAttachment,
|
|
Image: attachment.FromImageArray(target.Surfaces()),
|
|
LoadOp: core1_0.AttachmentLoadOpDontCare,
|
|
StoreOp: core1_0.AttachmentStoreOpStore,
|
|
FinalLayout: core1_0.ImageLayoutShaderReadOnlyOptimal,
|
|
},
|
|
},
|
|
Subpasses: []renderpass.Subpass{
|
|
{
|
|
Name: MainSubpass,
|
|
Depth: false,
|
|
|
|
ColorAttachments: []attachment.Name{OutputAttachment},
|
|
},
|
|
},
|
|
})
|
|
|
|
p.mat = material.New(
|
|
app.Device(),
|
|
material.Args{
|
|
Shader: app.Shaders().Fetch(shader.NewRef("ssao")),
|
|
Pass: p.pass,
|
|
Pointers: vertex.ParsePointers(vertex.T{}),
|
|
DepthTest: false,
|
|
DepthWrite: false,
|
|
},
|
|
&AmbientOcclusionDescriptors{
|
|
Position: &descriptor.Sampler{
|
|
Stages: core1_0.StageFragment,
|
|
},
|
|
Normal: &descriptor.Sampler{
|
|
Stages: core1_0.StageFragment,
|
|
},
|
|
Noise: &descriptor.Sampler{
|
|
Stages: core1_0.StageFragment,
|
|
},
|
|
Params: &descriptor.Uniform[AmbientOcclusionParams]{
|
|
Stages: core1_0.StageFragment,
|
|
},
|
|
})
|
|
|
|
p.fbuf, err = framebuffer.NewArray(target.Frames(), app.Device(), "ssao", target.Width(), target.Height(), p.pass)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
p.quad = vertex.ScreenQuad("ssao-pass-quad")
|
|
|
|
// create noise texture
|
|
p.noise = NewHemisphereNoise(4, 4)
|
|
|
|
// create sampler kernel
|
|
p.kernel = [SSAOSamples]vec4.T{}
|
|
for i := 0; i < len(p.kernel); i++ {
|
|
var sample vec3.T
|
|
for {
|
|
sample = vec3.Random(
|
|
vec3.New(-1, 0, -1),
|
|
vec3.New(1, 1, 1),
|
|
)
|
|
if sample.LengthSqr() > 1 {
|
|
continue
|
|
}
|
|
sample = sample.Normalized()
|
|
if vec3.Dot(sample, vec3.Up) < 0.5 {
|
|
continue
|
|
}
|
|
|
|
sample = sample.Scaled(random.Range(0, 1))
|
|
break
|
|
}
|
|
|
|
// we dont want a uniform sample distribution
|
|
// push samples closer to the origin
|
|
scale := float32(i) / float32(SSAOSamples)
|
|
scale = math.Lerp(0.1, 1.0, scale*scale)
|
|
sample = sample.Scaled(scale)
|
|
|
|
p.kernel[i] = vec4.Extend(sample, 0)
|
|
}
|
|
|
|
// todo: if we shuffle the kernel, it would be ok to use fewer samples
|
|
|
|
p.desc = p.mat.InstantiateMany(app.Pool(), target.Frames())
|
|
p.position = make([]texture.T, target.Frames())
|
|
p.normal = make([]texture.T, target.Frames())
|
|
for i := 0; i < target.Frames(); i++ {
|
|
posKey := fmt.Sprintf("ssao-position-%d", i)
|
|
p.position[i], err = texture.FromImage(app.Device(), posKey, gbuffer.Position()[i], texture.Args{
|
|
Filter: texture.FilterNearest,
|
|
Wrap: texture.WrapClamp,
|
|
})
|
|
if err != nil {
|
|
// todo: clean up
|
|
panic(err)
|
|
}
|
|
p.desc[i].Descriptors().Position.Set(p.position[i])
|
|
|
|
normKey := fmt.Sprintf("ssao-normal-%d", i)
|
|
p.normal[i], err = texture.FromImage(app.Device(), normKey, gbuffer.Normal()[i], texture.Args{
|
|
Filter: texture.FilterNearest,
|
|
Wrap: texture.WrapClamp,
|
|
})
|
|
if err != nil {
|
|
// todo: clean up
|
|
panic(err)
|
|
}
|
|
p.desc[i].Descriptors().Normal.Set(p.normal[i])
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
func (p *AmbientOcclusionPass) Record(cmds command.Recorder, args renderapi.Args, scene object.Component) {
|
|
quad := p.app.Meshes().Fetch(p.quad)
|
|
|
|
cmds.Record(func(cmd command.Buffer) {
|
|
cmd.CmdBeginRenderPass(p.pass, p.fbuf[args.Frame])
|
|
p.desc[args.Frame].Bind(cmd)
|
|
p.desc[args.Frame].Descriptors().Noise.Set(p.app.Textures().Fetch(p.noise))
|
|
p.desc[args.Frame].Descriptors().Params.Set(AmbientOcclusionParams{
|
|
Projection: args.Projection,
|
|
Kernel: p.kernel,
|
|
Samples: 32,
|
|
Scale: p.scale,
|
|
Radius: 0.4,
|
|
Bias: 0.02,
|
|
Power: 2.6,
|
|
})
|
|
quad.Draw(cmd, 0)
|
|
cmd.CmdEndRenderPass()
|
|
})
|
|
}
|
|
|
|
func (p *AmbientOcclusionPass) Destroy() {
|
|
p.pass.Destroy()
|
|
p.fbuf.Destroy()
|
|
for i := 0; i < len(p.position); i++ {
|
|
p.position[i].Destroy()
|
|
p.normal[i].Destroy()
|
|
}
|
|
p.mat.Destroy()
|
|
}
|
|
|
|
func (p *AmbientOcclusionPass) Name() string {
|
|
return "AmbientOcclusion"
|
|
}
|
|
|
|
type HemisphereNoise struct {
|
|
Width int
|
|
Height int
|
|
|
|
key string
|
|
}
|
|
|
|
func NewHemisphereNoise(width, height int) *HemisphereNoise {
|
|
return &HemisphereNoise{
|
|
key: fmt.Sprintf("noise-hemisphere-%dx%d", width, height),
|
|
Width: width,
|
|
Height: height,
|
|
}
|
|
}
|
|
|
|
func (n *HemisphereNoise) Key() string { return n.key }
|
|
func (n *HemisphereNoise) Version() int { return 1 }
|
|
|
|
func (n *HemisphereNoise) ImageData() *image.Data {
|
|
buffer := make([]vec4.T, 4*n.Width*n.Height)
|
|
for i := range buffer {
|
|
buffer[i] = vec4.Extend(vec3.Random(
|
|
vec3.New(-1, -1, 0),
|
|
vec3.New(1, 1, 0),
|
|
).Normalized(), 0)
|
|
}
|
|
|
|
// cast to byte array
|
|
ptr := (*byte)(unsafe.Pointer(&buffer[0]))
|
|
bytes := unsafe.Slice(ptr, int(unsafe.Sizeof(vec4.T{}))*len(buffer))
|
|
|
|
return &image.Data{
|
|
Width: n.Width,
|
|
Height: n.Height,
|
|
Format: core1_0.FormatR32G32B32A32SignedFloat,
|
|
Buffer: bytes,
|
|
}
|
|
}
|
|
|
|
func (n *HemisphereNoise) TextureArgs() texture.Args {
|
|
return texture.Args{
|
|
Filter: texture.FilterNearest,
|
|
Wrap: texture.WrapRepeat,
|
|
}
|
|
}
|