浏览代码

performance improvements

danaugrs 7 年之前
父节点
当前提交
e44f34f45b
共有 8 个文件被更改,包括 176 次插入134 次删除
  1. 62 22
      core/node.go
  2. 25 1
      graphic/graphic.go
  3. 9 15
      graphic/line_strip.go
  4. 11 16
      graphic/lines.go
  5. 18 21
      graphic/mesh.go
  6. 6 11
      graphic/points.go
  7. 10 10
      graphic/skybox.go
  8. 35 38
      renderer/renderer.go

+ 62 - 22
core/node.go

@@ -341,7 +341,7 @@ func (n *Node) RemoveAll(recurs bool) {
 }
 
 // DisposeChildren removes and disposes of all children.
-// If 'recurs' is true, call DisposeChidren on each child recursively.
+// If 'recurs' is true, call DisposeChildren on each child recursively.
 func (n *Node) DisposeChildren(recurs bool) {
 
 	for pos, ichild := range n.children {
@@ -575,16 +575,16 @@ func (n *Node) Matrix() math32.Matrix4 {
 	return n.matrix
 }
 
-// WorldPosition updates this node world matrix and gets
-// the current world position vector.
+// WorldPosition updates the world matrix and sets
+// the specified vector to the current world position of this node.
 func (n *Node) WorldPosition(result *math32.Vector3) {
 
 	n.UpdateMatrixWorld()
 	result.SetFromMatrixPosition(&n.matrixWorld)
 }
 
-// WorldQuaternion sets the specified result quaternion with
-// this node current world quaternion
+// WorldQuaternion updates the world matrix and sets
+// the specified quaternion to the current world quaternion of this node.
 func (n *Node) WorldQuaternion(result *math32.Quaternion) {
 
 	var position math32.Vector3
@@ -593,8 +593,8 @@ func (n *Node) WorldQuaternion(result *math32.Quaternion) {
 	n.matrixWorld.Decompose(&position, result, &scale)
 }
 
-// WorldRotation sets the specified result vector with
-// current world rotation of this node in Euler angles.
+// WorldRotation updates the world matrix and sets
+// the specified vector to the current world rotation of this node in Euler angles.
 func (n *Node) WorldRotation(result *math32.Vector3) {
 
 	var quaternion math32.Quaternion
@@ -602,8 +602,8 @@ func (n *Node) WorldRotation(result *math32.Vector3) {
 	result.SetFromQuaternion(&quaternion)
 }
 
-// WorldScale sets the specified result vector with
-// the current world scale of this node
+// WorldScale updates the world matrix and sets
+// the specified vector to the current world scale of this node.
 func (n *Node) WorldScale(result *math32.Vector3) {
 
 	var position math32.Vector3
@@ -612,8 +612,8 @@ func (n *Node) WorldScale(result *math32.Vector3) {
 	n.matrixWorld.Decompose(&position, &quaternion, result)
 }
 
-// WorldDirection updates this object world matrix and sets
-// the current world direction.
+// WorldDirection updates the world matrix and sets
+// the specified vector to the current world direction of this node.
 func (n *Node) WorldDirection(result *math32.Vector3) {
 
 	var quaternion math32.Quaternion
@@ -622,31 +622,71 @@ func (n *Node) WorldDirection(result *math32.Vector3) {
 	result.ApplyQuaternion(&quaternion)
 }
 
-// MatrixWorld returns a copy of this node matrix world
+// MatrixWorld returns a copy of the matrix world of this node.
 func (n *Node) MatrixWorld() math32.Matrix4 {
 
 	return n.matrixWorld
 }
 
-// UpdateMatrix updates this node local matrix transform from its
-// current position, quaternion and scale.
-func (n *Node) UpdateMatrix() {
+// UpdateMatrix updates (if necessary) the local transform matrix
+// of this node based on its position, quaternion, and scale.
+func (n *Node) UpdateMatrix() bool {
 
+	if !n.changed {
+		return false
+	}
 	n.matrix.Compose(&n.position, &n.quaternion, &n.scale)
+	n.changed = false
+	return true
 }
 
-// UpdateMatrixWorld updates this node world transform matrix and of all its children
+// UpdateMatrixWorld updates the world transform matrix for this node and for all of its children.
 func (n *Node) UpdateMatrixWorld() {
 
-	n.UpdateMatrix()
 	if n.parent == nil {
-		n.matrixWorld = n.matrix
+		n.updateMatrixWorld(&n.matrix)
 	} else {
 		parent := n.parent.GetNode()
-		n.matrixWorld.MultiplyMatrices(&parent.matrixWorld, &n.matrix)
+		n.updateMatrixWorld(&parent.matrixWorld)
 	}
-	// Update this Node children matrices
-	for _, ichild := range n.children {
-		ichild.UpdateMatrixWorld()
+}
+
+// updateMatrixWorld is used internally by UpdateMatrixWorld.
+// If the local transform matrix has changed, this method updates it and also the world matrix of this node.
+// Children are updated recursively. If any node has changed, then we update the world matrix
+// of all of its descendants regardless if their local matrices have changed.
+func (n *Node) updateMatrixWorld(parentMatrixWorld *math32.Matrix4) {
+
+	// If the local transform matrix for this node has changed then we need to update the local
+	// matrix for this node and also the world matrix for this and all subsequent nodes.
+	if n.UpdateMatrix() {
+		n.matrixWorld.MultiplyMatrices(parentMatrixWorld, &n.matrix)
+
+		// Update matrices of children recursively, always updating the world matrix
+		for _, ichild := range n.children {
+			ichild.GetNode().updateMatrixWorldNoCheck(&n.matrixWorld)
+		}
+	} else {
+		// Update matrices of children recursively, continuing to check for changes
+		for _, ichild := range n.children {
+			ichild.GetNode().updateMatrixWorld(&n.matrixWorld)
+		}
 	}
 }
+
+// updateMatrixWorldNoCheck is used internally by updateMatrixWorld.
+// This method should be called when a node has changed since it always updates the matrix world.
+func (n *Node) updateMatrixWorldNoCheck(parentMatrixWorld *math32.Matrix4) {
+
+	// Update the local transform matrix (if necessary)
+	n.UpdateMatrix()
+
+	// Always update the matrix world since an ancestor of this node has changed
+	// (i.e. and ancestor had its local transform matrix modified)
+	n.matrixWorld.MultiplyMatrices(parentMatrixWorld, &n.matrix)
+
+	// Update matrices of children recursively
+	for _, ichild := range n.children {
+		ichild.GetNode().updateMatrixWorldNoCheck(&n.matrixWorld)
+	}
+}

+ 25 - 1
graphic/graphic.go

@@ -9,6 +9,7 @@ import (
 	"github.com/g3n/engine/geometry"
 	"github.com/g3n/engine/gls"
 	"github.com/g3n/engine/material"
+	"github.com/g3n/engine/math32"
 )
 
 // Graphic is a Node which has a visible representation in the scene.
@@ -22,7 +23,9 @@ type Graphic struct {
 	mode       uint32             // OpenGL primitive
 	renderable bool               // Renderable flag
 	cullable   bool               // Cullable flag
-	// TODO store cached mv, mvp matrices ?
+
+	mvm        math32.Matrix4     // Cached ModelView matrix
+	mvpm       math32.Matrix4     // Cached ModelViewProjection matrix
 }
 
 // GraphicMaterial specifies the material to be used for
@@ -165,6 +168,27 @@ func (gr *Graphic) GetMaterial(vpos int) material.IMaterial {
 	return nil
 }
 
+// CalculateMatrices calculates the model view and model view projection matrices.
+func (g *Graphic) CalculateMatrices(gs *gls.GLS, rinfo *core.RenderInfo) {
+
+	// Calculate model view and model view projection matrices
+	mw := g.MatrixWorld()
+	g.mvm.MultiplyMatrices(&rinfo.ViewMatrix, &mw)
+	g.mvpm.MultiplyMatrices(&rinfo.ProjMatrix, &g.mvm)
+}
+
+// ModelViewMatrix returns the last cached model view matrix for this graphic.
+func (g *Graphic) ModelViewMatrix() *math32.Matrix4 {
+
+	return &g.mvm
+}
+
+// ModelViewProjectionMatrix returns the last cached model view projection matrix for this graphic.
+func (g *Graphic) ModelViewProjectionMatrix() *math32.Matrix4 {
+
+	return &g.mvpm
+}
+
 // GetMaterial returns the material associated with the GraphicMaterial.
 func (grmat *GraphicMaterial) GetMaterial() material.IMaterial {
 

+ 9 - 15
graphic/line_strip.go

@@ -9,42 +9,36 @@ import (
 	"github.com/g3n/engine/geometry"
 	"github.com/g3n/engine/gls"
 	"github.com/g3n/engine/material"
-	"github.com/g3n/engine/math32"
 )
 
-// LineStrip is a Graphic which is rendered as a collection of connected lines
+// LineStrip is a Graphic which is rendered as a collection of connected lines.
 type LineStrip struct {
 	Graphic             // Embedded graphic object
-	uniMVP  gls.Uniform // Model view projection matrix uniform location cache
+	uniMVPm gls.Uniform // Model view projection matrix uniform location cache
 }
 
 // NewLineStrip creates and returns a pointer to a new LineStrip graphic
-// with the specified geometry and material
+// with the specified geometry and material.
 func NewLineStrip(igeom geometry.IGeometry, imat material.IMaterial) *LineStrip {
 
 	l := new(LineStrip)
 	l.Graphic.Init(igeom, gls.LINE_STRIP)
 	l.AddMaterial(l, imat, 0, 0)
-	l.uniMVP.Init("MVP")
+	l.uniMVPm.Init("MVP")
 	return l
 }
 
-// RenderSetup is called by the engine before drawing this geometry
+// RenderSetup is called by the engine before drawing this geometry.
 func (l *LineStrip) RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo) {
 
-	// Calculates model view projection matrix and updates uniform
-	mw := l.MatrixWorld()
-	var mvpm math32.Matrix4
-	mvpm.MultiplyMatrices(&rinfo.ViewMatrix, &mw)
-	mvpm.MultiplyMatrices(&rinfo.ProjMatrix, &mvpm)
-
-	// Transfer mvpm uniform
-	location := l.uniMVP.Location(gs)
+	// Transfer model view projection matrix uniform
+	mvpm := l.ModelViewProjectionMatrix()
+	location := l.uniMVPm.Location(gs)
 	gs.UniformMatrix4fv(location, 1, false, &mvpm[0])
 }
 
 // Raycast satisfies the INode interface and checks the intersections
-// of this geometry with the specified raycaster
+// of this geometry with the specified raycaster.
 func (l *LineStrip) Raycast(rc *core.Raycaster, intersects *[]core.Intersect) {
 
 	lineRaycast(l, rc, intersects, 1)

+ 11 - 16
graphic/lines.go

@@ -12,21 +12,21 @@ import (
 	"github.com/g3n/engine/math32"
 )
 
-// Lines is a Graphic which is rendered as a collection of independent lines
+// Lines is a Graphic which is rendered as a collection of independent lines.
 type Lines struct {
 	Graphic             // Embedded graphic object
-	uniMVP  gls.Uniform // Model view projection matrix uniform location cache
+	uniMVPm gls.Uniform // Model view projection matrix uniform location cache
 }
 
-// Init initializes the Lines object and adds the specified material
+// Init initializes the Lines object and adds the specified material.
 func (l *Lines) Init(igeom geometry.IGeometry, imat material.IMaterial) {
 
 	l.Graphic.Init(igeom, gls.LINES)
 	l.AddMaterial(l, imat, 0, 0)
-	l.uniMVP.Init("MVP")
+	l.uniMVPm.Init("MVP")
 }
 
-// NewLines returns a pointer to a new Lines object
+// NewLines returns a pointer to a new Lines object.
 func NewLines(igeom geometry.IGeometry, imat material.IMaterial) *Lines {
 
 	l := new(Lines)
@@ -34,28 +34,23 @@ func NewLines(igeom geometry.IGeometry, imat material.IMaterial) *Lines {
 	return l
 }
 
-// RenderSetup is called by the engine before drawing this geometry
+// RenderSetup is called by the engine before drawing this geometry.
 func (l *Lines) RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo) {
 
-	// Calculates model view projection matrix and updates uniform
-	mw := l.MatrixWorld()
-	var mvpm math32.Matrix4
-	mvpm.MultiplyMatrices(&rinfo.ViewMatrix, &mw)
-	mvpm.MultiplyMatrices(&rinfo.ProjMatrix, &mvpm)
-
-	// Transfer mvpm uniform
-	location := l.uniMVP.Location(gs)
+	// Transfer model view projection matrix uniform
+	mvpm := l.ModelViewProjectionMatrix()
+	location := l.uniMVPm.Location(gs)
 	gs.UniformMatrix4fv(location, 1, false, &mvpm[0])
 }
 
 // Raycast satisfies the INode interface and checks the intersections
-// of this geometry with the specified raycaster
+// of this geometry with the specified raycaster.
 func (l *Lines) Raycast(rc *core.Raycaster, intersects *[]core.Intersect) {
 
 	lineRaycast(l, rc, intersects, 2)
 }
 
-// Internal function used by raycasting for Lines and LineStrip
+// Internal function used by raycasting for Lines and LineStrip.
 func lineRaycast(igr IGraphic, rc *core.Raycaster, intersects *[]core.Intersect, step int) {
 
 	// Get the bounding sphere

+ 18 - 21
graphic/mesh.go

@@ -15,9 +15,9 @@ import (
 // Mesh is a Graphic with uniforms for the model, view, projection, and normal matrices.
 type Mesh struct {
 	Graphic             // Embedded graphic
-	uniMVM  gls.Uniform // Model view matrix uniform location cache
-	uniMVPM gls.Uniform // Model view projection matrix uniform cache
-	uniNM   gls.Uniform // Normal matrix uniform cache
+	uniMVm  gls.Uniform // Model view matrix uniform location cache
+	uniMVPm gls.Uniform // Model view projection matrix uniform cache
+	uniNm   gls.Uniform // Normal matrix uniform cache
 }
 
 // NewMesh creates and returns a pointer to a mesh with the specified geometry and material.
@@ -30,15 +30,15 @@ func NewMesh(igeom geometry.IGeometry, imat material.IMaterial) *Mesh {
 	return m
 }
 
-// Init initializes the Mesh and its uniforms
+// Init initializes the Mesh and its uniforms.
 func (m *Mesh) Init(igeom geometry.IGeometry, imat material.IMaterial) {
 
 	m.Graphic.Init(igeom, gls.TRIANGLES)
 
 	// Initialize uniforms
-	m.uniMVM.Init("ModelViewMatrix")
-	m.uniMVPM.Init("MVP")
-	m.uniNM.Init("NormalMatrix")
+	m.uniMVm.Init("ModelViewMatrix")
+	m.uniMVPm.Init("MVP")
+	m.uniNm.Init("NormalMatrix")
 
 	// Adds single material if not nil
 	if imat != nil {
@@ -46,13 +46,13 @@ func (m *Mesh) Init(igeom geometry.IGeometry, imat material.IMaterial) {
 	}
 }
 
-// AddMaterial adds a material for the specified subset of vertices
+// AddMaterial adds a material for the specified subset of vertices.
 func (m *Mesh) AddMaterial(imat material.IMaterial, start, count int) {
 
 	m.Graphic.AddMaterial(m, imat, start, count)
 }
 
-// AddGroupMaterial adds a material for the specified geometry group
+// AddGroupMaterial adds a material for the specified geometry group.
 func (m *Mesh) AddGroupMaterial(imat material.IMaterial, gindex int) {
 
 	m.Graphic.AddGroupMaterial(m, imat, gindex)
@@ -63,23 +63,20 @@ func (m *Mesh) AddGroupMaterial(imat material.IMaterial, gindex int) {
 // the model matrices.
 func (m *Mesh) RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo) {
 
-	// Calculates model view matrix and transfer uniform
-	mw := m.MatrixWorld()
-	var mvm math32.Matrix4
-	mvm.MultiplyMatrices(&rinfo.ViewMatrix, &mw)
-	location := m.uniMVM.Location(gs)
+	// Transfer uniform for model view matrix
+	mvm := m.ModelViewMatrix()
+	location := m.uniMVm.Location(gs)
 	gs.UniformMatrix4fv(location, 1, false, &mvm[0])
 
-	// Calculates model view projection matrix and updates uniform
-	var mvpm math32.Matrix4
-	mvpm.MultiplyMatrices(&rinfo.ProjMatrix, &mvm)
-	location = m.uniMVPM.Location(gs)
+	// Transfer uniform for model view projection matrix
+	mvpm := m.ModelViewProjectionMatrix()
+	location = m.uniMVPm.Location(gs)
 	gs.UniformMatrix4fv(location, 1, false, &mvpm[0])
 
-	// Calculates normal matrix and updates uniform
+	// Calculates normal matrix and transfer uniform
 	var nm math32.Matrix3
-	nm.GetNormalMatrix(&mvm)
-	location = m.uniNM.Location(gs)
+	nm.GetNormalMatrix(mvm)
+	location = m.uniNm.Location(gs)
 	gs.UniformMatrix3fv(location, 1, false, &nm[0])
 }
 

+ 6 - 11
graphic/points.go

@@ -15,7 +15,7 @@ import (
 // Points represents a geometry containing only points
 type Points struct {
 	Graphic             // Embedded graphic
-	uniMVPM gls.Uniform // Model view projection matrix uniform location cache
+	uniMVPm gls.Uniform // Model view projection matrix uniform location cache
 }
 
 // NewPoints creates and returns a graphic points object with the specified
@@ -27,26 +27,21 @@ func NewPoints(igeom geometry.IGeometry, imat material.IMaterial) *Points {
 	if imat != nil {
 		p.AddMaterial(p, imat, 0, 0)
 	}
-	p.uniMVPM.Init("MVP")
+	p.uniMVPm.Init("MVP")
 	return p
 }
 
-// RenderSetup is called by the engine before rendering this graphic
+// RenderSetup is called by the engine before rendering this graphic.
 func (p *Points) RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo) {
 
-	// Calculates model view projection matrix
-	mw := p.MatrixWorld()
-	var mvpm math32.Matrix4
-	mvpm.MultiplyMatrices(&rinfo.ViewMatrix, &mw)
-	mvpm.MultiplyMatrices(&rinfo.ProjMatrix, &mvpm)
-
 	// Transfer model view projection matrix uniform
-	location := p.uniMVPM.Location(gs)
+	mvpm := p.ModelViewProjectionMatrix()
+	location := p.uniMVPm.Location(gs)
 	gs.UniformMatrix4fv(location, 1, false, &mvpm[0])
 }
 
 // Raycast satisfies the INode interface and checks the intersections
-// of this geometry with the specified raycaster
+// of this geometry with the specified raycaster.
 func (p *Points) Raycast(rc *core.Raycaster, intersects *[]core.Intersect) {
 
 	// Checks intersection with the bounding sphere transformed to world coordinates

+ 10 - 10
graphic/skybox.go

@@ -13,12 +13,12 @@ import (
 	"github.com/g3n/engine/texture"
 )
 
-// Skybox is the Graphic that represents a skybox
+// Skybox is the Graphic that represents a skybox.
 type Skybox struct {
 	Graphic             // embedded graphic object
-	uniMVM  gls.Uniform // model view matrix uniform location cache
-	uniMVPM gls.Uniform // model view projection matrix uniform cache
-	uniNM   gls.Uniform // normal matrix uniform cache
+	uniMVm  gls.Uniform // model view matrix uniform location cache
+	uniMVPm gls.Uniform // model view projection matrix uniform cache
+	uniNm   gls.Uniform // normal matrix uniform cache
 }
 
 // SkyboxData contains the data necessary to locate the textures for a Skybox in a concise manner.
@@ -56,9 +56,9 @@ func NewSkybox(data SkyboxData) (*Skybox, error) {
 	}
 
 	// Creates uniforms
-	skybox.uniMVM.Init("ModelViewMatrix")
-	skybox.uniMVPM.Init("MVP")
-	skybox.uniNM.Init("NormalMatrix")
+	skybox.uniMVm.Init("ModelViewMatrix")
+	skybox.uniMVPm.Init("MVP")
+	skybox.uniNm.Init("NormalMatrix")
 
 	return skybox, nil
 }
@@ -78,18 +78,18 @@ func (skybox *Skybox) RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo) {
 	// mvm.ExtractRotation(&rinfo.ViewMatrix) // TODO <- ExtractRotation does not work as expected?
 
 	// Transfer mvp uniform
-	location := skybox.uniMVM.Location(gs)
+	location := skybox.uniMVm.Location(gs)
 	gs.UniformMatrix4fv(location, 1, false, &mvm[0])
 
 	// Calculates model view projection matrix and updates uniform
 	var mvpm math32.Matrix4
 	mvpm.MultiplyMatrices(&rinfo.ProjMatrix, &mvm)
-	location = skybox.uniMVPM.Location(gs)
+	location = skybox.uniMVPm.Location(gs)
 	gs.UniformMatrix4fv(location, 1, false, &mvpm[0])
 
 	// Calculates normal matrix and updates uniform
 	var nm math32.Matrix3
 	nm.GetNormalMatrix(&mvm)
-	location = skybox.uniNM.Location(gs)
+	location = skybox.uniNm.Location(gs)
 	gs.UniformMatrix3fv(location, 1, false, &nm[0])
 }

+ 35 - 38
renderer/renderer.go

@@ -29,9 +29,10 @@ type Renderer struct {
 	pointLights  []*light.Point              // Array of point
 	spotLights   []*light.Spot               // Array of spot lights for the scene
 	others       []core.INode                // Other nodes (audio, players, etc)
+	rgraphics    []*graphic.Graphic          // Array of rendered graphics
+	cgraphics    []*graphic.Graphic          // Array of rendered graphics
 	grmatsOpaque []*graphic.GraphicMaterial  // Array of rendered opaque graphic materials for scene
 	grmatsTransp []*graphic.GraphicMaterial  // Array of rendered transparent graphic materials for scene
-	cgrmats      []*graphic.GraphicMaterial  // Array of culled graphic materials for scene
 	rinfo        core.RenderInfo             // Preallocated Render info
 	specs        ShaderSpecs                 // Preallocated Shader specs
 	sortObjects  bool                        // Flag indicating whether objects should be sorted before rendering
@@ -63,9 +64,10 @@ func NewRenderer(gs *gls.GLS) *Renderer {
 	r.pointLights = make([]*light.Point, 0)
 	r.spotLights = make([]*light.Spot, 0)
 	r.others = make([]core.INode, 0)
+	r.rgraphics = make([]*graphic.Graphic, 0)
+	r.cgraphics = make([]*graphic.Graphic, 0)
 	r.grmatsOpaque = make([]*graphic.GraphicMaterial, 0)
 	r.grmatsTransp = make([]*graphic.GraphicMaterial, 0)
-	r.cgrmats = make([]*graphic.GraphicMaterial, 0)
 	r.panList = make([]gui.IPanel, 0)
 	r.frameBuffers = 2
 	r.sortObjects = true
@@ -189,9 +191,10 @@ func (r *Renderer) renderScene(iscene core.INode, icam camera.ICamera) error {
 	r.pointLights = r.pointLights[0:0]
 	r.spotLights = r.spotLights[0:0]
 	r.others = r.others[0:0]
+	r.rgraphics = r.rgraphics[0:0]
+	r.cgraphics = r.cgraphics[0:0]
 	r.grmatsOpaque = r.grmatsOpaque[0:0]
 	r.grmatsTransp = r.grmatsTransp[0:0]
-	r.cgrmats = r.cgrmats[0:0]
 
 	// Prepare for frustum culling
 	var proj math32.Matrix4
@@ -214,7 +217,6 @@ func (r *Renderer) renderScene(iscene core.INode, icam camera.ICamera) error {
 			if igr.Renderable() {
 
 				gr := igr.GetGraphic()
-				materials := gr.Materials()
 
 				// Frustum culling
 				if igr.Cullable() {
@@ -223,31 +225,15 @@ func (r *Renderer) renderScene(iscene core.INode, icam camera.ICamera) error {
 					bb := geom.BoundingBox()
 					bb.ApplyMatrix4(&mw)
 					if frustum.IntersectsBox(&bb) {
-						// Append all graphic materials of this graphic to list of graphic materials to be rendered
-						for i := 0; i < len(materials); i++ {
-							mat := materials[i].GetMaterial().GetMaterial()
-							if mat.Transparent() {
-								r.grmatsTransp = append(r.grmatsTransp, &materials[i])
-							} else {
-								r.grmatsOpaque = append(r.grmatsOpaque, &materials[i])
-							}
-						}
+						// Append graphic to list of graphics to be rendered
+						r.rgraphics = append(r.rgraphics, gr)
 					} else {
-						// Append all graphic materials of this graphic to list of culled graphic materials
-						for i := 0; i < len(materials); i++ {
-							r.cgrmats = append(r.cgrmats, &materials[i])
-						}
+						// Append graphic to list of culled graphics
+						r.cgraphics = append(r.cgraphics, gr)
 					}
 				} else {
-					// Append all graphic materials of this graphic to list of graphic materials to be rendered
-					for i := 0; i < len(materials); i++ {
-						mat := materials[i].GetMaterial().GetMaterial()
-						if mat.Transparent() {
-							r.grmatsTransp = append(r.grmatsTransp, &materials[i])
-						} else {
-							r.grmatsOpaque = append(r.grmatsOpaque, &materials[i])
-						}
-					}
+					// Append graphic to list of graphics to be rendered
+					r.rgraphics = append(r.rgraphics, gr)
 				}
 			}
 			// Node is not a Graphic
@@ -290,7 +276,24 @@ func (r *Renderer) renderScene(iscene core.INode, icam camera.ICamera) error {
 	r.specs.PointLightsMax = len(r.pointLights)
 	r.specs.SpotLightsMax = len(r.spotLights)
 
-	// Z-sort graphic materials
+	// Pre-calculate MV and MVP matrices and compile lists of opaque and transparent graphic materials
+	for _, gr := range r.rgraphics {
+		// Calculate MV and MVP matrices for all graphics to be rendered
+		gr.CalculateMatrices(r.gs, &r.rinfo)
+
+		// Append all graphic materials of this graphic to list of graphic materials to be rendered
+		materials := gr.Materials()
+		for i := 0; i < len(materials); i++ {
+			mat := materials[i].GetMaterial().GetMaterial()
+			if mat.Transparent() {
+				r.grmatsTransp = append(r.grmatsTransp, &materials[i])
+			} else {
+				r.grmatsOpaque = append(r.grmatsOpaque, &materials[i])
+			}
+		}
+	}
+
+	// Z-sort graphic materials (opaque front-to-back and transparent back-to-front)
 	if r.sortObjects {
 		// Internal function to render a list of graphic materials
 		var zSortGraphicMaterials func(grmats []*graphic.GraphicMaterial, backToFront bool)
@@ -298,18 +301,12 @@ func (r *Renderer) renderScene(iscene core.INode, icam camera.ICamera) error {
 			sort.Slice(grmats, func(i, j int) bool {
 				gr1 := grmats[i].GetGraphic().GetGraphic()
 				gr2 := grmats[j].GetGraphic().GetGraphic()
-				mw1 := gr1.MatrixWorld()
-				mw2 := gr2.MatrixWorld()
-
-				// TODO OPTIMIZATION - this calculation is already generally performed in IGraphic.RenderSetup, should probably cache results in Graphic.
-				var mvm1, mvm2 math32.Matrix4
-				mvm1.MultiplyMatrices(&r.rinfo.ViewMatrix, &mw1)
-				mvm2.MultiplyMatrices(&r.rinfo.ViewMatrix, &mw2)
-
+				mvm1 := gr1.ModelViewMatrix()
+				mvm2 := gr2.ModelViewMatrix()
 				g1pos := gr1.Position()
 				g2pos := gr2.Position()
-				g1pos.ApplyMatrix4(&mvm1)
-				g2pos.ApplyMatrix4(&mvm2)
+				g1pos.ApplyMatrix4(mvm1)
+				g2pos.ApplyMatrix4(mvm2)
 
 				if backToFront {
 					return g1pos.Z < g2pos.Z
@@ -407,7 +404,7 @@ func (r *Renderer) renderGui() error {
 
 	// If no 3D scene was rendered sets Gui panels as renderable for background
 	// User must define the colors
-	if (len(r.grmatsOpaque) == 0) && (len(r.grmatsTransp) == 0) && (len(r.cgrmats) == 0) {
+	if (len(r.rgraphics) == 0) && (len(r.cgraphics) == 0) {
 		r.panelGui.SetRenderable(true)
 		if r.panel3D != nil {
 			r.panel3D.SetRenderable(true)