A billboard is a primitive which always faces the camera in every frame.
Billboards can be used for special effects or some other trickery which requires the triangles to always facing the camera no matter where it is. The engine groups billboards into sets for efficiency, so you should never create a billboard on it's own (it's ok to have a set of one if you need it).

Billboards have their geometry generated every frame depending on where the camera is. It is most beneficial for all billboards in a set to be identically sized since the engine can take advantage of this and save some calculations - useful when you have sets of hundreds of billboards as is possible with special effects. You can deviate from this if you wish (example: a smoke effect would probably have smoke puffs expanding as they rise, so each billboard will legitimately have it's own size) but be aware the extra overhead this brings and try to avoid it if you can.

Billboards are just the mechanism for rendering a range of effects such as particles. It is other classes which use billboards to create their individual effects, so the methods here are quite generic.

Exemple #1
0
		protected void GenerateVertices( Vector3[] offsets, Billboard bb )
		{
			int color = Root.Instance.ConvertColor( bb.Color );
			// Texcoords
			Debug.Assert( bb.UseTexcoordRect || bb.TexcoordIndex < this.textureCoords.Count );
			RectangleF r = bb.UseTexcoordRect ? bb.TexcoordRect : this.textureCoords[ bb.TexcoordIndex ];

			if ( this.pointRendering )
			{
				unsafe
				{
					float* posPtr = (float*)this.lockPtr.ToPointer();
					int* colPtr = (int*)posPtr;

					// Single vertex per billboard, ignore offsets
					// position
					posPtr[ this.ptrOffset++ ] = bb.Position.x;
					posPtr[ this.ptrOffset++ ] = bb.Position.y;
					posPtr[ this.ptrOffset++ ] = bb.Position.z;
					colPtr[ this.ptrOffset++ ] = color;
					// No texture coords in point rendering
				}
			}
			else if ( this.allDefaultRotation || bb.Rotation == 0 )
			{
				unsafe
				{
					float* posPtr = (float*)this.lockPtr.ToPointer();
					int* colPtr = (int*)posPtr;
					float* texPtr = (float*)posPtr;

					// Left-top
					// Positions
					posPtr[ this.ptrOffset++ ] = offsets[ 0 ].x + bb.Position.x;
					posPtr[ this.ptrOffset++ ] = offsets[ 0 ].y + bb.Position.y;
					posPtr[ this.ptrOffset++ ] = offsets[ 0 ].z + bb.Position.z;
					// Color
					colPtr[ this.ptrOffset++ ] = color;
					// Texture coords
					texPtr[ this.ptrOffset++ ] = r.Left;
					texPtr[ this.ptrOffset++ ] = r.Top;

					// Right-top
					// Positions
					posPtr[ this.ptrOffset++ ] = offsets[ 1 ].x + bb.Position.x;
					posPtr[ this.ptrOffset++ ] = offsets[ 1 ].y + bb.Position.y;
					posPtr[ this.ptrOffset++ ] = offsets[ 1 ].z + bb.Position.z;
					// Color
					colPtr[ this.ptrOffset++ ] = color;
					// Texture coords
					texPtr[ this.ptrOffset++ ] = r.Right;
					texPtr[ this.ptrOffset++ ] = r.Top;

					// Left-bottom
					// Positions
					posPtr[ this.ptrOffset++ ] = offsets[ 2 ].x + bb.Position.x;
					posPtr[ this.ptrOffset++ ] = offsets[ 2 ].y + bb.Position.y;
					posPtr[ this.ptrOffset++ ] = offsets[ 2 ].z + bb.Position.z;
					// Color
					colPtr[ this.ptrOffset++ ] = color;
					// Texture coords
					texPtr[ this.ptrOffset++ ] = r.Left;
					texPtr[ this.ptrOffset++ ] = r.Bottom;

					// Right-bottom
					// Positions
					posPtr[ this.ptrOffset++ ] = offsets[ 3 ].x + bb.Position.x;
					posPtr[ this.ptrOffset++ ] = offsets[ 3 ].y + bb.Position.y;
					posPtr[ this.ptrOffset++ ] = offsets[ 3 ].z + bb.Position.z;
					// Color
					colPtr[ this.ptrOffset++ ] = color;
					// Texture coords
					texPtr[ this.ptrOffset++ ] = r.Right;
					texPtr[ this.ptrOffset++ ] = r.Bottom;
				}
			}
			else if ( this.rotationType == BillboardRotationType.Vertex )
			{
				// TODO: Cache axis when billboard type is BillboardType.Point or
				//       BillboardType.PerpendicularCommon
				Vector3 axis = ( offsets[ 3 ] - offsets[ 0 ] ).Cross( offsets[ 2 ] - offsets[ 1 ] );
				axis.Normalize();

				Quaternion rotation = Quaternion.FromAngleAxis( bb.rotationInRadians, axis );
				Vector3 pt;

				unsafe
				{
					float* posPtr = (float*)this.lockPtr.ToPointer();
					int* colPtr = (int*)posPtr;
					float* texPtr = (float*)posPtr;

					// Left-top
					// Positions
					pt = rotation * offsets[ 0 ];
					posPtr[ this.ptrOffset++ ] = offsets[ 0 ].x + bb.Position.x;
					posPtr[ this.ptrOffset++ ] = offsets[ 0 ].y + bb.Position.y;
					posPtr[ this.ptrOffset++ ] = offsets[ 0 ].z + bb.Position.z;
					// Color
					colPtr[ this.ptrOffset++ ] = color;
					// Texture coords
					texPtr[ this.ptrOffset++ ] = r.Left;
					texPtr[ this.ptrOffset++ ] = r.Top;

					// Right-top
					// Positions
					pt = rotation * offsets[ 1 ];
					posPtr[ this.ptrOffset++ ] = pt.x + bb.Position.x;
					posPtr[ this.ptrOffset++ ] = pt.y + bb.Position.y;
					posPtr[ this.ptrOffset++ ] = pt.z + bb.Position.z;
					// Color
					colPtr[ this.ptrOffset++ ] = color;
					// Texture coords
					texPtr[ this.ptrOffset++ ] = r.Right;
					texPtr[ this.ptrOffset++ ] = r.Top;

					// Left-bottom
					// Positions
					pt = rotation * offsets[ 2 ];
					posPtr[ this.ptrOffset++ ] = pt.x + bb.Position.x;
					posPtr[ this.ptrOffset++ ] = pt.y + bb.Position.y;
					posPtr[ this.ptrOffset++ ] = pt.z + bb.Position.z;
					// Color
					colPtr[ this.ptrOffset++ ] = color;
					// Texture coords
					texPtr[ this.ptrOffset++ ] = r.Left;
					texPtr[ this.ptrOffset++ ] = r.Bottom;

					// Right-bottom
					// Positions
					pt = rotation * offsets[ 3 ];
					posPtr[ this.ptrOffset++ ] = pt.x + bb.Position.x;
					posPtr[ this.ptrOffset++ ] = pt.y + bb.Position.y;
					posPtr[ this.ptrOffset++ ] = pt.z + bb.Position.z;
					// Color
					colPtr[ this.ptrOffset++ ] = color;
					// Texture coords
					texPtr[ this.ptrOffset++ ] = r.Right;
					texPtr[ this.ptrOffset++ ] = r.Bottom;
				}
			}
			else
			{
				float cos_rot = Utility.Cos( bb.rotationInRadians );
				float sin_rot = Utility.Sin( bb.rotationInRadians );

				float width = ( r.Right - r.Left ) / 2;
				float height = ( r.Bottom - r.Top ) / 2;
				float mid_u = r.Left + width;
				float mid_v = r.Top + height;

				float cos_rot_w = cos_rot * width;
				float cos_rot_h = cos_rot * height;
				float sin_rot_w = sin_rot * width;
				float sin_rot_h = sin_rot * height;

				unsafe
				{
					float* posPtr = (float*)this.lockPtr.ToPointer();
					int* colPtr = (int*)posPtr;
					float* texPtr = (float*)posPtr;

					// Left-top
					// Positions
					posPtr[ this.ptrOffset++ ] = offsets[ 0 ].x + bb.Position.x;
					posPtr[ this.ptrOffset++ ] = offsets[ 0 ].y + bb.Position.y;
					posPtr[ this.ptrOffset++ ] = offsets[ 0 ].z + bb.Position.z;
					// Color
					colPtr[ this.ptrOffset++ ] = color;
					// Texture coords
					texPtr[ this.ptrOffset++ ] = mid_u - cos_rot_w + sin_rot_h;
					texPtr[ this.ptrOffset++ ] = mid_v - sin_rot_w - cos_rot_h;

					// Right-top
					// Positions
					posPtr[ this.ptrOffset++ ] = offsets[ 1 ].x + bb.Position.x;
					posPtr[ this.ptrOffset++ ] = offsets[ 1 ].y + bb.Position.y;
					posPtr[ this.ptrOffset++ ] = offsets[ 1 ].z + bb.Position.z;
					// Color
					colPtr[ this.ptrOffset++ ] = color;
					// Texture coords
					texPtr[ this.ptrOffset++ ] = mid_u + cos_rot_w + sin_rot_h;
					texPtr[ this.ptrOffset++ ] = mid_v + sin_rot_w - cos_rot_h;

					// Left-bottom
					// Positions
					posPtr[ this.ptrOffset++ ] = offsets[ 2 ].x + bb.Position.x;
					posPtr[ this.ptrOffset++ ] = offsets[ 2 ].y + bb.Position.y;
					posPtr[ this.ptrOffset++ ] = offsets[ 2 ].z + bb.Position.z;
					// Color
					colPtr[ this.ptrOffset++ ] = color;
					// Texture coords
					texPtr[ this.ptrOffset++ ] = mid_u - cos_rot_w - sin_rot_h;
					texPtr[ this.ptrOffset++ ] = mid_v - sin_rot_w + cos_rot_h;

					// Right-bottom
					// Positions
					posPtr[ this.ptrOffset++ ] = offsets[ 3 ].x + bb.Position.x;
					posPtr[ this.ptrOffset++ ] = offsets[ 3 ].y + bb.Position.y;
					posPtr[ this.ptrOffset++ ] = offsets[ 3 ].z + bb.Position.z;
					// Color
					colPtr[ this.ptrOffset++ ] = color;
					// Texture coords
					texPtr[ this.ptrOffset++ ] = mid_u + cos_rot_w - sin_rot_h;
					texPtr[ this.ptrOffset++ ] = mid_v + sin_rot_w + cos_rot_h;
				}
			}
		}
Exemple #2
0
		/// <summary>
		///		Generates billboard corners.
		///	 </summary>
		/// <remarks>Billboard param only required for type OrientedSelf</remarks>
		protected virtual void GenerateBillboardAxes( ref Vector3 x, ref Vector3 y, Billboard bb )
		{
			// If we're using accurate facing, recalculate camera direction per BB
			if ( this.accurateFacing &&
				 ( this.billboardType == BillboardType.Point ||
				   this.billboardType == BillboardType.OrientedCommon ||
				   this.billboardType == BillboardType.OrientedSelf ) )
			{
				// cam -> bb direction
				this.camDir = bb.Position - this.camPos;
				this.camDir.Normalize();
			}

			switch ( this.billboardType )
			{
				case BillboardType.Point:
					if ( this.accurateFacing )
					{
						// Point billboards will have 'up' based on but not equal to cameras
						y = this.camQ * Vector3.UnitY;
						x = this.camDir.Cross( y );
						x.Normalize();
						y = x.Cross( this.camDir ); // both normalised already
					}
					else
					{
						// Get camera axes for X and Y (depth is irrelevant)
						x = this.camQ * Vector3.UnitX;
						y = this.camQ * Vector3.UnitY;
					}
					break;

				case BillboardType.OrientedCommon:
					// Y-axis is common direction
					// X-axis is cross with camera direction
					y = this.commonDirection;
					x = this.camDir.Cross( y );
					x.Normalize();
					break;

				case BillboardType.OrientedSelf:
					// Y-axis is direction
					// X-axis is cross with camera direction
					// Scale direction first
					y = bb.Direction;
					x = this.camDir.Cross( y );
					x.Normalize();
					break;

				case BillboardType.PerpendicularCommon:
					// X-axis is up-vector cross common direction
					// Y-axis is common direction cross X-axis
					x = this.commonUpVector.Cross( this.commonDirection );
					y = this.commonDirection.Cross( x );
					break;

				case BillboardType.PerpendicularSelf:
					// X-axis is up-vector cross own direction
					// Y-axis is own direction cross X-axis
					x = this.commonUpVector.Cross( bb.Direction );
					x.Normalize();
					y = bb.Direction.Cross( x ); // both should be normalised
					break;
			}

#if NOT
		// Default behavior is that billboards are in local node space
		// so orientation of camera (in world space) must be reverse-transformed
		// into node space to generate the axes
			Quaternion invTransform = parentNode.DerivedOrientation.Inverse();
			Quaternion camQ = Quaternion.Zero;

			switch (billboardType) {
				case BillboardType.Point:
					// Get camera world axes for X and Y (depth is irrelevant)
					camQ = camera.DerivedOrientation;
						// Convert into billboard local space
						camQ = invTransform * camQ;
					x = camQ * Vector3.UnitX;
					y = camQ * Vector3.UnitY;
					break;
				case BillboardType.OrientedCommon:
					// Y-axis is common direction
					// X-axis is cross with camera direction
					y = commonDirection;
					y.Normalize();
						// Convert into billboard local space
						camQ = invTransform * camQ;
					x = camQ * camera.DerivedDirection.Cross(y);
					x.Normalize();
					break;
				case BillboardType.OrientedSelf:
					// Y-axis is direction
					// X-axis is cross with camera direction
					y = billboard.Direction;
						// Convert into billboard local space
						camQ = invTransform * camQ;
					x = camQ * camera.DerivedDirection.Cross(y);
					x.Normalize();
					break;
				case BillboardType.PerpendicularCommon:
					// X-axis is common direction cross common up vector
					// Y-axis is coplanar with common direction and common up vector
					x = commonDirection.Cross(commonUpVector);
					x.Normalize();
					y = x.Cross(commonDirection);
					y.Normalize();
					break;
				case BillboardType.PerpendicularSelf:
					// X-axis is direction cross common up vector
					// Y-axis is coplanar with direction and common up vector
					x = billboard.Direction.Cross(commonUpVector);
					x.Normalize();
					y = x.Cross(billboard.Direction);
					y.Normalize();
					break;
			}
#endif
		}
Exemple #3
0
		/// <summary>
		///		Determines whether the supplied billboard is visible in the camera or not.
		///	 </summary>
		/// <param name="camera"></param>
		/// <param name="billboard"></param>
		/// <returns></returns>
		protected bool IsBillboardVisible( Camera camera, Billboard billboard )
		{
			// if not culling each one, return true always
			if ( !this.cullIndividual )
			{
				return true;
			}

			// get the world matrix of this billboard set
			this.GetWorldTransforms( this.world );

			// get the center of the bounding sphere
			this.sphere.Center = this.world[ 0 ] * billboard.Position;

			// calculate the radius of the bounding sphere for the billboard
			if ( billboard.HasOwnDimensions )
			{
				this.sphere.Radius = Utility.Max( billboard.Width, billboard.Height );
			}
			else
			{
				this.sphere.Radius = Utility.Max( this.defaultParticleWidth, this.defaultParticleHeight );
			}

			// finally, see if the sphere is visible in the camera
			return camera.IsObjectVisible( this.sphere );
		}
Exemple #4
0
		internal void InjectBillboard( Billboard bb )
		{
			// Skip if not visible (NB always true if not bounds checking individual billboards)
			if ( !this.IsBillboardVisible( this.currentCamera, bb ) )
			{
				return;
			}

			if ( !this.pointRendering &&
				 ( this.billboardType == BillboardType.OrientedSelf ||
				   this.billboardType == BillboardType.PerpendicularSelf ||
				   ( this.accurateFacing && this.billboardType != BillboardType.PerpendicularCommon ) ) )
			{
				// Have to generate axes & offsets per billboard
				this.GenerateBillboardAxes( ref this.camX, ref this.camY, bb );
			}

			// If they're all the same size or we're point rendering
			if ( this.allDefaultSize || this.pointRendering )
			{
				/* No per-billboard checking, just blast through.
				   Saves us an if clause every billboard which may
				   make a difference.
				*/

				if ( !this.pointRendering &&
					 ( this.billboardType == BillboardType.OrientedSelf ||
					   this.billboardType == BillboardType.PerpendicularSelf ||
					   ( this.accurateFacing && this.billboardType != BillboardType.PerpendicularCommon ) ) )
				{
					this.GenerateVertexOffsets( this.leftOff,
												this.rightOff,
												this.topOff,
												this.bottomOff,
												this.defaultParticleWidth,
												this.defaultParticleHeight,
												ref this.camX,
												ref this.camY,
												this.vOffset );
				}
				this.GenerateVertices( this.vOffset, bb );
			}
			else // not all default size and not point rendering
			{
				Vector3[] vOwnOffset = new Vector3[ 4 ];
				// If it has own dimensions, or self-oriented, gen offsets
				if ( this.billboardType == BillboardType.OrientedSelf ||
					 this.billboardType == BillboardType.PerpendicularSelf ||
					 bb.HasOwnDimensions ||
					 ( this.accurateFacing && this.billboardType != BillboardType.PerpendicularCommon ) )
				{
					// Generate using own dimensions
					this.GenerateVertexOffsets( this.leftOff,
												this.rightOff,
												this.topOff,
												this.bottomOff,
												bb.Width,
												bb.Height,
												ref this.camX,
												ref this.camY,
												vOwnOffset );
					// Create vertex data
					this.GenerateVertices( vOwnOffset, bb );
				}
				else // Use default dimension, already computed before the loop, for faster creation
				{
					this.GenerateVertices( this.vOffset, bb );
				}
			}
			// Increment visibles
			this.numVisibleBillboards++;
		}
Exemple #5
0
		protected void RemoveBillboard( Billboard bill )
		{
			int index = this.activeBillboards.IndexOf( bill );
			Debug.Assert( index >= 0, "Billboard is not in the active list" );
			RemoveBillboard( index );
		}
        public override void UpdateRenderQueue(RenderQueue queue, 
                                               List<Particle> currentParticles, 
                                               bool cullIndividually)
        {
            billboardSet.CullIndividual = cullIndividually;

            // Update billboard set geometry
            billboardSet.BeginBillboards();
            Billboard bb = new Billboard();
            foreach (Particle p in currentParticles) {
                bb.Position = p.Position;
                if (billboardSet.BillboardType == BillboardType.OrientedSelf ||
                    billboardSet.BillboardType == BillboardType.PerpendicularSelf)
                {
                    // Normalise direction vector
                    bb.Direction = p.Direction;
                    bb.Direction.Normalize();
                }
                bb.Color = p.Color;
                bb.rotationInRadians = p.rotationInRadians;
                bb.HasOwnDimensions = p.HasOwnDimensions;
                if (bb.HasOwnDimensions)
                {
                    bb.width = p.Width;
                    bb.height = p.Height;
                }
                billboardSet.InjectBillboard(bb);
            }

            billboardSet.EndBillboards();

            // Update the queue
            billboardSet.UpdateRenderQueue(queue);
        }