Adding LightGroups.

LightGroups exist to light Models using specific Lights, rather than all lights in a Scene.
Fixing Lighting blend file's textures (the blend file pointed to an atlas rather than individual textures).
Adding LightGroups lighting example.
CacheOptimizations2
SolarLune 2022-08-16 15:44:59 -07:00
parent 50c6e5020f
commit 1e435319c2
12 changed files with 812 additions and 77 deletions

View File

@ -602,23 +602,24 @@ func (camera *Camera) Render(scene *Scene, models ...*Model) {
frametimeStart := time.Now()
lights := []ILight{}
sceneLights := []ILight{}
lights := sceneLights
if scene.World != nil && scene.World.LightingOn {
if scene.World == nil || scene.World.LightingOn {
for _, l := range scene.Root.ChildrenRecursive() {
if light, isLight := l.(ILight); isLight {
camera.DebugInfo.LightCount++
if light.IsOn() {
lights = append(lights, light)
sceneLights = append(sceneLights, light)
light.beginRender()
camera.DebugInfo.ActiveLightCount++
}
}
}
if scene.World.AmbientLight != nil && scene.World.AmbientLight.IsOn() {
lights = append(lights, scene.World.AmbientLight)
if scene.World != nil && scene.World.AmbientLight != nil && scene.World.AmbientLight.IsOn() {
sceneLights = append(sceneLights, scene.World.AmbientLight)
}
}
@ -807,6 +808,15 @@ func (camera *Camera) Render(scene *Scene, models ...*Model) {
t := time.Now()
lights = sceneLights
if model.LightGroup != nil && model.LightGroup.Active {
lights = model.LightGroup.Lights
for _, l := range model.LightGroup.Lights {
l.beginRender() // Call this because it's relatively cheap and necessary if a light doesn't exist in the Scene
}
}
for _, light := range lights {
light.beginModel(model)
}

View File

@ -308,7 +308,7 @@ func (g *Game) Layout(w, h int) (int, int) {
}
func main() {
ebiten.SetWindowTitle("Tetra3d - Lighting Test")
ebiten.SetWindowTitle("Tetra3d - Baked Lighting Test")
ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled)
game := NewGame()

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,252 @@
package main
import (
"errors"
"fmt"
"image/png"
"math"
"os"
"runtime/pprof"
"time"
_ "embed"
"github.com/kvartborg/vector"
"github.com/solarlune/tetra3d"
"github.com/solarlune/tetra3d/colors"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
)
//go:embed lighting.gltf
var gltfData []byte
type Game struct {
Width, Height int
Library *tetra3d.Library
Scene *tetra3d.Scene
Camera *tetra3d.Camera
CameraTilt float64
CameraRotate float64
DrawDebugText bool
DrawDebugDepth bool
PrevMousePosition vector.Vector
Time float64
}
func NewGame() *Game {
game := &Game{
Width: 796,
Height: 448,
PrevMousePosition: vector.Vector{},
}
game.Init()
return game
}
func (g *Game) Init() {
opt := tetra3d.DefaultGLTFLoadOptions()
opt.CameraWidth = g.Width
opt.CameraHeight = g.Height
library, err := tetra3d.LoadGLTFData(gltfData, opt)
if err != nil {
panic(err)
}
g.Library = library
g.Scene = library.Scenes[0]
g.Camera = tetra3d.NewCamera(g.Width, g.Height)
g.Camera.SetLocalPosition(vector.Vector{0, 2, 15})
g.Scene.Root.AddChildren(g.Camera)
gt := g.Scene.Root.Get("OnlyGreen").(*tetra3d.Model)
gt.LightGroup = tetra3d.NewLightGroup(g.Scene.Root.Get("GreenLight").(*tetra3d.PointLight))
rt := g.Scene.Root.Get("OnlyRed").(*tetra3d.Model)
rt.LightGroup = tetra3d.NewLightGroup(g.Scene.Root.Get("RedLight").(*tetra3d.PointLight))
ebiten.SetCursorMode(ebiten.CursorModeCaptured)
}
func (g *Game) Update() error {
g.Time += 1.0 / 60.0
var err error
moveSpd := 0.05
if ebiten.IsKeyPressed(ebiten.KeyEscape) {
err = errors.New("quit")
}
// Moving the Camera
// We use Camera.Rotation.Forward().Invert() because the camera looks down -Z (so its forward vector is inverted)
forward := g.Camera.LocalRotation().Forward().Invert()
right := g.Camera.LocalRotation().Right()
pos := g.Camera.LocalPosition()
if ebiten.IsKeyPressed(ebiten.KeyW) {
pos = pos.Add(forward.Scale(moveSpd))
}
if ebiten.IsKeyPressed(ebiten.KeyD) {
pos = pos.Add(right.Scale(moveSpd))
}
if ebiten.IsKeyPressed(ebiten.KeyS) {
pos = pos.Add(forward.Scale(-moveSpd))
}
if ebiten.IsKeyPressed(ebiten.KeyA) {
pos = pos.Add(right.Scale(-moveSpd))
}
if ebiten.IsKeyPressed(ebiten.KeySpace) {
pos[1] += moveSpd
}
if ebiten.IsKeyPressed(ebiten.KeyControl) {
pos[1] -= moveSpd
}
g.Camera.SetLocalPosition(pos)
if inpututil.IsKeyJustPressed(ebiten.KeyF4) {
ebiten.SetFullscreen(!ebiten.IsFullscreen())
}
armature := g.Scene.Root.Get("Armature")
if ebiten.IsKeyPressed(ebiten.KeyLeft) {
armature.Move(-0.1, 0, 0)
} else if ebiten.IsKeyPressed(ebiten.KeyRight) {
armature.Move(0.1, 0, 0)
}
// Rotating the camera with the mouse
// Rotate and tilt the camera according to mouse movements
mx, my := ebiten.CursorPosition()
mv := vector.Vector{float64(mx), float64(my)}
diff := mv.Sub(g.PrevMousePosition)
g.CameraTilt -= diff[1] * 0.005
g.CameraRotate -= diff[0] * 0.005
g.CameraTilt = math.Max(math.Min(g.CameraTilt, math.Pi/2-0.1), -math.Pi/2+0.1)
tilt := tetra3d.NewMatrix4Rotate(1, 0, 0, g.CameraTilt)
rotate := tetra3d.NewMatrix4Rotate(0, 1, 0, g.CameraRotate)
// Order of this is important - tilt * rotate works, rotate * tilt does not, lol
g.Camera.SetLocalRotation(tilt.Mult(rotate))
g.PrevMousePosition = mv.Clone()
if inpututil.IsKeyJustPressed(ebiten.KeyF12) {
f, err := os.Create("screenshot" + time.Now().Format("2006-01-02 15:04:05") + ".png")
if err != nil {
fmt.Println(err)
}
defer f.Close()
png.Encode(f, g.Camera.ColorTexture())
}
if ebiten.IsKeyPressed(ebiten.KeyR) {
g.Init()
}
if inpututil.IsKeyJustPressed(ebiten.KeyP) {
g.StartProfiling()
}
if inpututil.IsKeyJustPressed(ebiten.KeyF1) {
g.DrawDebugText = !g.DrawDebugText
}
if inpututil.IsKeyJustPressed(ebiten.KeyF5) {
g.DrawDebugDepth = !g.DrawDebugDepth
}
if inpututil.IsKeyJustPressed(ebiten.Key1) {
green := g.Scene.Root.Get("OnlyGreen").(*tetra3d.Model)
green.LightGroup.Active = !green.LightGroup.Active
red := g.Scene.Root.Get("OnlyRed").(*tetra3d.Model)
red.LightGroup.Active = !red.LightGroup.Active
}
if inpututil.IsKeyJustPressed(ebiten.Key2) {
g.Scene.World.LightingOn = !g.Scene.World.LightingOn
}
return err
}
func (g *Game) Draw(screen *ebiten.Image) {
// Clear, but with a color - we can use the world lighting color for this.
screen.Fill(g.Scene.World.ClearColor.ToRGBA64())
// Clear the Camera
g.Camera.Clear()
// Render the scene
g.Camera.RenderNodes(g.Scene, g.Scene.Root)
// We rescale the depth or color textures here just in case we render at a different resolution than the window's; this isn't necessary,
// we could just draw the images straight.
if g.DrawDebugDepth {
screen.DrawImage(g.Camera.DepthTexture(), nil)
} else {
screen.DrawImage(g.Camera.ColorTexture(), nil)
}
if g.DrawDebugText {
g.Camera.DrawDebugRenderInfo(screen, 1, colors.White())
txt := "F1 to toggle this text\nWASD: Move, Mouse: Look\n\nThis example shows light groups.\nLight groups allow you to control\nwhich lights influence Models\nwhile having them remain available for others.\n\nThere's two point lights in this scene - a red one\nand a green one. The left cube is only\nlit by the green light, while the right\nis only lit by the red one.\n\n1 Key: Toggle light groups being active\n2 Key: Toggle all lighting\nF5: Toggle depth debug view\nF4: Toggle fullscreen\nESC: Quit"
g.Camera.DebugDrawText(screen, txt, 0, 150, 1, colors.LightGray())
}
}
func (g *Game) StartProfiling() {
outFile, err := os.Create("./cpu.pprof")
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println("Beginning CPU profiling...")
pprof.StartCPUProfile(outFile)
go func() {
time.Sleep(2 * time.Second)
pprof.StopCPUProfile()
fmt.Println("CPU profiling finished.")
}()
}
func (g *Game) Layout(w, h int) (int, int) {
return g.Width, g.Height
}
func main() {
ebiten.SetWindowTitle("Tetra3d - LightGroup Test")
ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled)
game := NewGame()
if err := ebiten.RunGame(game); err != nil {
panic(err)
}
}

Binary file not shown.

File diff suppressed because one or more lines are too long

28
lightGroup.go Normal file
View File

@ -0,0 +1,28 @@
package tetra3d
// LightGroup represents a grouping of lights. This is used on Models to control which lights are used to light them.
type LightGroup struct {
Lights []ILight // The collection of lights present in the LightGroup.
Active bool // If the LightGroup is active or not; when a Model's LightGroup is inactive, Models will fallback to the lights present under the Scene's root.
}
// NewLightGroup creates a new LightGroup.
func NewLightGroup(lights ...ILight) *LightGroup {
lg := &LightGroup{
Active: true,
}
lg.Add(lights...)
return lg
}
// Add adds the passed ILights to the LightGroup.
func (lg *LightGroup) Add(lights ...ILight) {
lg.Lights = append(lg.Lights, lights...)
}
// Clone clones the LightGroup.
func (lg *LightGroup) Clone() *LightGroup {
newLG := NewLightGroup(lg.Lights...)
newLG.Active = lg.Active
return newLG
}

View File

@ -28,6 +28,10 @@ type Model struct {
skinMatrix Matrix4
bones [][]*Node // The bones (nodes) of the Model, assuming it has been skinned. A Mesh's bones slice will point to indices indicating bones in the Model.
skinVectorPool *VectorPool
// A LightGroup indicates if a Model should be lit by a specific group of Lights. This allows you to control the overall lighting of scenes more accurately.
// If a Model has no LightGroup, the Model is lit by the lights present in the Scene.
LightGroup *LightGroup
}
// NewModel creates a new Model (or instance) of the Mesh and Name provided. A Model represents a singular visual instantiation of a Mesh.
@ -86,6 +90,10 @@ func (model *Model) Clone() INode {
newModel.originalLocalPosition = model.originalLocalPosition.Clone()
if model.LightGroup != nil {
newModel.LightGroup = model.LightGroup.Clone()
}
return newModel
}