237 lines
6.2 KiB
Go
237 lines
6.2 KiB
Go
|
|
package light
|
||
|
|
|
||
|
|
import (
|
||
|
|
"zworld/engine/object"
|
||
|
|
"zworld/engine/render/uniform"
|
||
|
|
"zworld/engine/renderapi"
|
||
|
|
"zworld/engine/renderapi/color"
|
||
|
|
"zworld/plugins/math"
|
||
|
|
"zworld/plugins/math/mat4"
|
||
|
|
"zworld/plugins/math/vec3"
|
||
|
|
"zworld/plugins/math/vec4"
|
||
|
|
)
|
||
|
|
|
||
|
|
type DirectionalArgs struct {
|
||
|
|
Color color.T
|
||
|
|
Intensity float32
|
||
|
|
Shadows bool
|
||
|
|
Cascades int
|
||
|
|
}
|
||
|
|
|
||
|
|
type Cascade struct {
|
||
|
|
View mat4.T
|
||
|
|
Proj mat4.T
|
||
|
|
ViewProj mat4.T
|
||
|
|
NearSplit float32
|
||
|
|
FarSplit float32
|
||
|
|
}
|
||
|
|
|
||
|
|
type Directional struct {
|
||
|
|
object.Component
|
||
|
|
cascades []Cascade
|
||
|
|
|
||
|
|
Color object.Property[color.T]
|
||
|
|
Intensity object.Property[float32]
|
||
|
|
Shadows object.Property[bool]
|
||
|
|
|
||
|
|
CascadeLambda object.Property[float32]
|
||
|
|
CascadeBlend object.Property[float32]
|
||
|
|
}
|
||
|
|
|
||
|
|
var _ T = &Directional{}
|
||
|
|
|
||
|
|
func init() {
|
||
|
|
object.Register[*Directional](DeserializeDirectional)
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewDirectional(args DirectionalArgs) *Directional {
|
||
|
|
lit := object.NewComponent(&Directional{
|
||
|
|
cascades: make([]Cascade, args.Cascades),
|
||
|
|
|
||
|
|
Color: object.NewProperty(args.Color),
|
||
|
|
Intensity: object.NewProperty(args.Intensity),
|
||
|
|
Shadows: object.NewProperty(args.Shadows),
|
||
|
|
|
||
|
|
CascadeLambda: object.NewProperty[float32](0.9),
|
||
|
|
CascadeBlend: object.NewProperty[float32](3.0),
|
||
|
|
})
|
||
|
|
return lit
|
||
|
|
}
|
||
|
|
|
||
|
|
func (lit *Directional) Name() string { return "DirectionalLight" }
|
||
|
|
func (lit *Directional) Type() Type { return TypeDirectional }
|
||
|
|
func (lit *Directional) CastShadows() bool { return lit.Shadows.Get() }
|
||
|
|
|
||
|
|
func farSplitDist(cascade, cascades int, near, far, splitLambda float32) float32 {
|
||
|
|
clipRange := far - near
|
||
|
|
minZ := near
|
||
|
|
maxZ := near + clipRange
|
||
|
|
|
||
|
|
rnge := maxZ - minZ
|
||
|
|
ratio := maxZ / minZ
|
||
|
|
|
||
|
|
// Calculate split depths based on view camera frustum
|
||
|
|
// Based on method presented in https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch10.html
|
||
|
|
p := (float32(cascade) + 1) / float32(cascades)
|
||
|
|
log := minZ * math.Pow(ratio, p)
|
||
|
|
uniform := minZ + rnge*p
|
||
|
|
d := splitLambda*(log-uniform) + uniform
|
||
|
|
return (d - near) / clipRange
|
||
|
|
}
|
||
|
|
|
||
|
|
func nearSplitDist(cascade, cascades int, near, far, splitLambda float32) float32 {
|
||
|
|
if cascade == 0 {
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
return farSplitDist(cascade-1, cascades, near, far, splitLambda)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (lit *Directional) PreDraw(args renderapi.Args, scene object.Object) error {
|
||
|
|
// update cascades
|
||
|
|
for i, _ := range lit.cascades {
|
||
|
|
lit.cascades[i] = lit.calculateCascade(args, i, len(lit.cascades))
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (lit *Directional) calculateCascade(args renderapi.Args, cascade, cascades int) Cascade {
|
||
|
|
texSize := float32(2048)
|
||
|
|
|
||
|
|
frustumCorners := []vec3.T{
|
||
|
|
vec3.New(-1, 1, -1), // NTL
|
||
|
|
vec3.New(1, 1, -1), // NTR
|
||
|
|
vec3.New(-1, -1, -1), // NBL
|
||
|
|
vec3.New(1, -1, -1), // NBR
|
||
|
|
vec3.New(-1, 1, 1), // FTL
|
||
|
|
vec3.New(1, 1, 1), // FTR
|
||
|
|
vec3.New(-1, -1, 1), // FBL
|
||
|
|
vec3.New(1, -1, 1), // FBR
|
||
|
|
}
|
||
|
|
|
||
|
|
// transform frustum into world space
|
||
|
|
for i, corner := range frustumCorners {
|
||
|
|
frustumCorners[i] = args.VPInv.TransformPoint(corner)
|
||
|
|
}
|
||
|
|
|
||
|
|
// squash
|
||
|
|
nearSplit := nearSplitDist(cascade, cascades, args.Near, args.Far, lit.CascadeLambda.Get())
|
||
|
|
farSplit := farSplitDist(cascade, cascades, args.Near, args.Far, lit.CascadeLambda.Get())
|
||
|
|
for i := 0; i < 4; i++ {
|
||
|
|
dist := frustumCorners[i+4].Sub(frustumCorners[i])
|
||
|
|
frustumCorners[i] = frustumCorners[i].Add(dist.Scaled(nearSplit))
|
||
|
|
frustumCorners[i+4] = frustumCorners[i].Add(dist.Scaled(farSplit))
|
||
|
|
}
|
||
|
|
|
||
|
|
// calculate frustum center
|
||
|
|
center := vec3.Zero
|
||
|
|
for _, corner := range frustumCorners {
|
||
|
|
center = center.Add(corner)
|
||
|
|
}
|
||
|
|
center = center.Scaled(float32(1) / 8)
|
||
|
|
|
||
|
|
radius := float32(0)
|
||
|
|
for _, corner := range frustumCorners {
|
||
|
|
distance := vec3.Distance(corner, center)
|
||
|
|
radius = math.Max(radius, distance)
|
||
|
|
}
|
||
|
|
radius = math.Snap(radius, 16)
|
||
|
|
|
||
|
|
// create light view matrix looking at the center of the
|
||
|
|
// camera frustum
|
||
|
|
ldir := lit.Transform().Forward()
|
||
|
|
position := center.Sub(ldir.Scaled(radius))
|
||
|
|
lview := mat4.LookAt(position, center, vec3.UnitY)
|
||
|
|
|
||
|
|
lproj := mat4.Orthographic(
|
||
|
|
-radius-0.01, radius+0.01,
|
||
|
|
-radius-0.01, radius+0.01,
|
||
|
|
0, 2*radius)
|
||
|
|
|
||
|
|
lvp := lproj.Mul(&lview)
|
||
|
|
|
||
|
|
// round the center of the lights projection to the nearest texel
|
||
|
|
origin := lvp.TransformPoint(vec3.New(0, 0, 0)).Scaled(texSize / 2.0)
|
||
|
|
offset := origin.Round().Sub(origin)
|
||
|
|
offset.Scale(2.0 / texSize)
|
||
|
|
lproj[12] = offset.X
|
||
|
|
lproj[13] = offset.Y
|
||
|
|
|
||
|
|
// re-create view-projection after rounding
|
||
|
|
lvp = lproj.Mul(&lview)
|
||
|
|
|
||
|
|
return Cascade{
|
||
|
|
Proj: lproj,
|
||
|
|
View: lview,
|
||
|
|
ViewProj: lvp,
|
||
|
|
NearSplit: nearSplit * args.Far,
|
||
|
|
FarSplit: farSplit * args.Far,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (lit *Directional) LightData(shadowmaps ShadowmapStore) uniform.Light {
|
||
|
|
ldir := lit.Transform().Forward()
|
||
|
|
entry := uniform.Light{
|
||
|
|
Type: uint32(TypeDirectional),
|
||
|
|
Position: vec4.Extend(ldir, 0),
|
||
|
|
Color: lit.Color.Get(),
|
||
|
|
Intensity: lit.Intensity.Get(),
|
||
|
|
Range: lit.CascadeBlend.Get(),
|
||
|
|
}
|
||
|
|
|
||
|
|
for cascadeIndex, cascade := range lit.cascades {
|
||
|
|
entry.ViewProj[cascadeIndex] = cascade.ViewProj
|
||
|
|
entry.Distance[cascadeIndex] = cascade.FarSplit
|
||
|
|
if handle, exists := shadowmaps.Lookup(lit, cascadeIndex); exists {
|
||
|
|
entry.Shadowmap[cascadeIndex] = uint32(handle)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return entry
|
||
|
|
}
|
||
|
|
|
||
|
|
func (lit *Directional) Shadowmaps() int {
|
||
|
|
return len(lit.cascades)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (lit *Directional) ShadowProjection(mapIndex int) uniform.Camera {
|
||
|
|
cascade := lit.cascades[mapIndex]
|
||
|
|
return uniform.Camera{
|
||
|
|
Proj: cascade.Proj,
|
||
|
|
View: cascade.View,
|
||
|
|
ViewProj: cascade.ViewProj,
|
||
|
|
ProjInv: cascade.Proj.Invert(),
|
||
|
|
ViewInv: cascade.View.Invert(),
|
||
|
|
ViewProjInv: cascade.ViewProj.Invert(),
|
||
|
|
Eye: vec4.Extend(lit.Transform().Position(), 0),
|
||
|
|
Forward: vec4.Extend(lit.Transform().Forward(), 0),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
type DirectionalState struct {
|
||
|
|
object.ComponentState
|
||
|
|
DirectionalArgs
|
||
|
|
}
|
||
|
|
|
||
|
|
func (lit *Directional) Serialize(enc object.Encoder) error {
|
||
|
|
return enc.Encode(DirectionalState{
|
||
|
|
// send help
|
||
|
|
ComponentState: object.NewComponentState(lit.Component),
|
||
|
|
DirectionalArgs: DirectionalArgs{
|
||
|
|
Color: lit.Color.Get(),
|
||
|
|
Intensity: lit.Intensity.Get(),
|
||
|
|
Shadows: lit.Shadows.Get(),
|
||
|
|
},
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func DeserializeDirectional(dec object.Decoder) (object.Component, error) {
|
||
|
|
var state DirectionalState
|
||
|
|
if err := dec.Decode(&state); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
obj := NewDirectional(state.DirectionalArgs)
|
||
|
|
obj.Component = state.ComponentState.New()
|
||
|
|
return obj, nil
|
||
|
|
}
|