I suspect there would be some very efficient ways to implement a floating origin system with the ECS ( keeping track of absolute positions as a double3 and keeping the character / camera / rendering around position 0 ).
But I wonder if it could be constantly done (per frame) or if I’d need to reset the positions when the camera/player reaches a distance of 2000.
If you don’t need need per frame, then don’t.
Probably you would think closer to approaching of 10k.
In my opinion 2k is far too early and unnecessary.
I did my own floating origin in XNA a long time ago, and it was actually just using custom view/projection matrices and
shaders - there was no such thing as moving transforms, no extra calculations, just another way to render stuff.
The concept of a camera does not exist on the GPU, at low level it doesn’t matter if the world moves or if the player moves. So a pure ECS rendering could work like that.
HDRP is camera-relative rendering, so the camera remains at 0,0,0 at all times. This allows you to utilise ECS to shift everything periodically. I would expect this to not impact your framerate at all.
You would not necessarily need doubles with this approach. As mentioned above, 10k is when things start breaking down numerically, assuming real world sizes and 1 unit = 1 meter.
Obviously if your scales differ then you would adjust that estimate.
Just putting it here for now as for reference (if don’t mind), nothing conclusive in the context for the need, at least not for me. But I will search more on Relative Rendering.
NotRobSheridan edited this page 22 days ago · 1 revision
Camera-Relative Rendering in the HDRP
What is it?
The purpose of camera-relative rendering is to make rendering of distant objects (with large world space coordinates) more robust and numerically stable.
How does it work?
It accomplishes the task by translating objects and lights by the negated world space camera position (into the so-called camera-relative world space) prior to performing any other geometric transformations. The world space camera position is then subsequently set to 0, and all relevant matrices are modified accordingly.
Therefore, in the shader, expect view and view-projection matrices (and their inverses) to be camera-relative, along with light (e.g. LightData.positionWS) and surface (e.g. PositionInputs.positionWS) positions. Expect most world space positions you encounter in HDRP shaders to be camera-relative. Note that _WorldSpaceCameraPos is never camera-relative, as it’s used for coordinate space conversion.
How can I enable it?
It is enabled by default in ShaderConfig.cs. If you change the value in the file, make sure to Generate Shader Includes in order to update ShaderConfig.cs.hlsl.
How do I switch between coordinate spaces?
Use GetAbsolutePositionWS() and GetCameraRelativePositionWS() defined in ShaderVariablesFunctions.hlsl.
Examples
If camera-relative rendering is enabled:
*GetAbsolutePositionWS(PositionInputs.positionWS) *returns the non-camera-relative world space position.
GetAbsolutePositionWS(float3(0, 0, 0)) returns the world space position of the camera equal to _WorldSpaceCameraPos.
Relative rendering isn’t all you want with floating origin style setups. You also want the precision for physics etc, which you don’t get with relative rendering. Typically it starts getting obvious with things you move on top of (vehicles etc) first - stuttering/sliding in the movement
Currently ECS hasn’t got native physics support, but it’s apparently on the cards for an indeterminable date, so that is a pretty big limitation of using ECS right now - many systems are still yet to come.
This was something I was concerned about. Therefore, I attempted to investigate a bit more, what the relative rendering is about. But indeed, I couldn’t find anything concrete, which could help with floating origin case.
For which, I don’t mind wait for ECS physics support. Therefore, it leads me to thinking for now, typical tricks and solutions can be applied, as in Classic OOP, to achieve floating origin results.
I have found a bit more breakdown under the 1000 meter mark. This is of course when moving at orbital speeds. I update the entire world back to (0,0,0) when the player gets 1km from the origin.
Since the original Wiki gives a great quick fix I want to share my kinda sloppy but working code here for anyone who wants to use it. (works in 5.6)
I am using this code in Orbital Dogfight VR. If you have a Rift please give a whirl. Its Free on Itch here:
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Morgan Garrard Mod Origin Transform
/// </summary>
public class OriginTransform : MonoBehaviour {
public static List<OriginTransform> OriginTransforms;
public Transform _ParentTransform;
public bool hasParticles = false;
public bool hasLineRender = false;
private bool onList = false;
public ParticleSystem[] particles;
public LineRenderer[] lineRenderers;
private void Awake()
{
_ParentTransform = transform;
if (hasParticles)
{
particles = gameObject.GetComponentsInChildren<ParticleSystem>();
}
if (hasLineRender)
{
lineRenderers = gameObject.GetComponentsInChildren<LineRenderer>();
}
}
private void OnEnable()
{
if (OriginTransforms == null)
{
OriginTransforms = new List<OriginTransform>();
}
if (!onList)
{
OriginTransforms.Add(this);
onList = true;
}
}
}
using UnityEngine;
/// <summary>
/// Morgan Garrard Mod Origin Controller
/// </summary>
public class OriginController : MonoBehaviour {
public GameObject controllerPos;
public float threshold = 950.0f;
public int ListCount;
ParticleSystem.Particle[] parts = null;
public Transform[] activeTransforms;
void Start () {
controllerPos = Camera.main.gameObject;
}
private void GetActiveTransforms()
{
int count = OriginTransform.OriginTransforms.Count;
activeTransforms = new Transform[count];
ListCount = count;
for (int i = 0; i < count; i++)
{
activeTransforms[i] = OriginTransform.OriginTransforms[i].gameObject.transform;
}
}
private void MoveToOrigin()
{
int count = OriginTransform.OriginTransforms.Count;
for (int i = 0; i < count; i++)
{
Transform t = OriginTransform.OriginTransforms[i]._ParentTransform;
if (!t.gameObject.activeInHierarchy)
{
//Debug.Log("DID NOT MOVE INACTIVE" + t.gameObject.name.ToString());
continue;
}
//Debug.Log("MOVING TRANSFORM" + t.gameObject.name.ToString());
t.position -= controllerPos.transform.position;
if (OriginTransform.OriginTransforms[i].hasLineRender)
{
foreach (LineRenderer item in OriginTransform.OriginTransforms[i].lineRenderers)
{
Vector3[] points = new Vector3[item.positionCount];
for (int l = 0; l < points.Length; l++)
{
points[l] -= controllerPos.transform.position;
}
item.SetPositions(points);
}
}
if (OriginTransform.OriginTransforms[i].hasParticles)
{
foreach (ParticleSystem item in OriginTransform.OriginTransforms[i].particles)
{
MoveParticles(item);
}
}
}
}
private void MoveParticles(ParticleSystem sys)
{
if (sys.main.simulationSpace != ParticleSystemSimulationSpace.World)
return;
int particlesNeeded = sys.main.maxParticles;
if (particlesNeeded <= 0)
return;
bool wasPaused = sys.isPaused;
bool wasPlaying = sys.isPlaying;
if (!wasPaused)
sys.Pause();
// ensure a sufficiently large array in which to store the particles
if (parts == null || parts.Length < particlesNeeded)
{
parts = new ParticleSystem.Particle[particlesNeeded];
}
// now get the particles
int num = sys.GetParticles(parts);
for (int p = 0; p < num; p++)
{
parts[p].position -= controllerPos.transform.position;
}
sys.SetParticles(parts, num);
if (wasPlaying)
sys.Play();
}
private void LateUpdate () {
if (controllerPos.transform.position.magnitude > threshold)
{
MoveToOrigin();
}
}
}
Iirc Kerbal Space Program deals with this by also having “floating velocity” or whatever you’d call it. You also shift velocity to be (0, 0, 0) at the origin. It’ll also greatly help reduce the amount of time you have to shift the origin. (It’s probably also the source of the “kraken” issues they had earlier on, where bigger / more complicated crafts had a chance to randomly explode)
Yeah I love Kerbal… Orbital Dogfight borrows heavily from KSP but I have not yet mimicked their floating velocity.
The two scripts above are intended to replace http://wiki.unity3d.com/index.php/Floating_Origin with more functionality to include LineRenderers and with no “Gameobject.Find()” which causes a decent amount of overhead if you need to update position speedily.
I have a floating origin system that I like a lot. I have a system that takes user input and records the camera position in my 64-bit space. The camera is actually 4-5 different cameras; the first rendering from 1 to 1000 meters at regular size, the next from 1000 to 1000000 meters at 0.001 scale, and so on.
Then I have a system that tracks which objects should be rendered by each camera.
Then I have a system that generates the matrices based on the camera position and object position.
public sealed class RenderSystem : ComponentSystem {
private ComponentGroup _componentGroup;
protected override void OnCreateManager () {
_componentGroup = GetComponentGroup(ComponentType.ReadOnly<Position>(),
ComponentType.ReadOnly<Rotation>(),
ComponentType.ReadOnly<VisibleLayerIndicies>(),
ComponentType.ReadOnly<LayeredLods>());
}
protected override void OnUpdate () {
DebugUi.RenderedObjects = 0;
_componentGroup.ResetFilter();
NativeArray<ArchetypeChunk> chunks = _componentGroup.CreateArchetypeChunkArray(Allocator.TempJob);
foreach (ArchetypeChunk chunk in chunks) {
LayeredLods layeredLods = chunk.GetSharedComponentData(GetArchetypeChunkSharedComponentType<LayeredLods>(),
World.Active.EntityManager);
_componentGroup.SetFilter(layeredLods);
Profiler.BeginSample("Get Chunk Components");
NativeArray<Position> chunkPositions =
chunk.GetNativeArray(GetArchetypeChunkComponentType<Position>(true));
NativeArray<Rotation> chunkRotations =
chunk.GetNativeArray(GetArchetypeChunkComponentType<Rotation>(true));
NativeArray<VisibleLayerIndicies> chunkVisibleLayerIndicies =
chunk.GetNativeArray(GetArchetypeChunkComponentType<VisibleLayerIndicies>(true));
Profiler.EndSample();
int layerIterations = math.min(layeredLods.Layers.Length, CameraRig.FarClipDistanceLayers.Length);
int chunkSize = chunk.Count;
for (int i = 0; i < chunkSize; i++) {
if (chunkVisibleLayerIndicies[i].Value == Ints.NoBitsSet) {
continue;
}
for (int layerIndex = layerIterations; layerIndex >= 0; layerIndex--) {
// Debug.Log($"Visible layers: {chunkVisibleLayerIndicies[i].Value.ToStringBinary()}");
if (chunkVisibleLayerIndicies[i].Value.IsBitSet(layerIndex)) {
Profiler.BeginSample("Render Calculations");
int layer = CameraRig.FarClipDistanceLayers[layerIndex].Layer;
float layerScale = math.pow(CameraRig.WorldCameraFactor, -layerIndex);
double3 relativePosition = (chunkPositions[i].Value - CameraRig.WorldCameraPosition) * layerScale;
float3 layerScaleVector = new float3(layerScale);
float4x4 matrix = float4x4.TRS(relativePosition.toFloat3(),
chunkRotations[i].Value.toNormalizedQuaternion(),
layerScaleVector);
Profiler.EndSample();
Profiler.BeginSample("Render Call");
// TODO add GPU batching? (or is Unity batching these for me already? - see the frame debugger)
Graphics.DrawMesh(layeredLods.Layers[layerIndex].Mesh,
matrix,
layeredLods.Layers[layerIndex].Material,
layer);
Profiler.EndSample();
DebugUi.RenderedObjects++;
}
}
}
}
chunks.Dispose();
}
}
The floating origin itself isn’t that hard, it is just ObjectPosition - WorldCameraPosition. Rendering everything nicely is more of a challenge.
It would really be nice to see the project setup for this.
Does it matter if you’re using HDRP or can you use the old standard shaders with this? (I ask because of that camera-relative rendering thing).
This is all still a bit of voodoo for me, but I think I get the gist. I definitely would like to see that blog post explaining the prerequisites and limitations of a system like this. For example, I wonder about the scale factor of the distant cameras and why you chose the particular scale parameters you did. I also wonder about how you’d handle physics in a world that matched your camera view size – especially when using ECS. Does this script actually manage to move the physics objects/colliders themselves, or does it only change where in the world the objects are rendered (so it can prevent vert/animation flickering)?