Difference between revisions of "Mesh/Download Weight"

From Second Life Wiki
Jump to navigation Jump to search
Line 96: Line 96:
cost *= gSavedSettings.getF32("MeshStreamingCostScaler");
cost *= gSavedSettings.getF32("MeshStreamingCostScaler");
return cost;
return cost;
}
</pre>
== Proposed New Implementation ==
The scores generated by the current implementation are too lax on LoD benefits and as a result many mesh regions are exceeding the maximum triangle budget at various ranges.  A new implementation is proposed to address that issue and ensure a reasonable triangle count per frame in mesh heavy regions.
=== Key Changes ===
Instead of using linear distance to weight each LoD's contribution to the score, use area of ring/circle specific to that LoD's zone of influence (the ring in which an observer would see the LoD in question).  Using a simulator as a "total area" (65536m^2), determine how many bytes would be streamed for the mesh vs the region, constructing a weighted avg byte load based on what percentage of the region each LoD covers.
=== Implementation ===
<pre>
F32 LLMeshRepository::getStreamingCost(const LLSD& header, F32 radius)
{
F32 dlowest = llmin(radius/0.06f, 256.f);
F32 dlow = llmin(radius/0.24f, 256.f);
F32 dmid = llmin(radius/1.0f, 256.f);
F32 bytes_lowest = header["lowest_lod"]["size"].asReal()/1024.f;
F32 bytes_low = header["low_lod"]["size"].asReal()/1024.f;
F32 bytes_mid = header["medium_lod"]["size"].asReal()/1024.f;
F32 bytes_high = header["high_lod"]["size"].asReal()/1024.f;
if (bytes_high == 0.f)
{
return 0.f;
}
if (bytes_mid == 0.f)
{
bytes_mid = bytes_high;
}
if (bytes_low == 0.f)
{
bytes_low = bytes_mid;
}
if (bytes_lowest == 0.f)
{
bytes_lowest = bytes_low;
}
F32 max_area = 65536.f;
F32 min_area = 1.f;
F32 high_area = llmin(F_PI*dmid*dmid, max_area);
F32 mid_area = llmin(F_PI*dlow*dlow, max_area);
F32 low_area = llmin(F_PI*dlowest*dlowest, max_area);
F32 lowest_area = max_area;
lowest_area -= low_area;
low_area -= mid_area;
mid_area -= high_area;
high_area = llclamp(high_area, min_area, max_area);
mid_area = llclamp(mid_area, min_area, max_area);
low_area = llclamp(low_area, min_area, max_area);
lowest_area = llclamp(lowest_area, min_area, max_area);
F32 total_area = high_area + mid_area + low_area + lowest_area;
high_area /= total_area;
mid_area /= total_area;
low_area /= total_area;
lowest_area /= total_area;
F32 weighted_avg = bytes_high*high_area +
  bytes_mid*mid_area +
  bytes_low*low_area +
  bytes_lowest*lowest_area;
return weighted_avg * gSavedSettings.getF32("MeshStreamingCostScaler");
}
}
</pre>
</pre>

Revision as of 15:46, 11 March 2011

READ THIS FIRST

This is a preliminary design of an unimplemented cost algorithm. EVERYTHING is subject to change, and certainly will change, during the course of implementation.

Motivation

Previously used methods of LOD enforcement and mesh cost have proved ineffective and difficult to adhere to. Proposed here is an algorithm for determining cost of a mesh asset (in terms of prim parcel cost) that correlates strongly to the actual load of streaming and displaying a mesh in a general way, without making assumptions about triangle/vertex limits and ratios between levels of detail. The artist need not adhere to any arbitrary restrictions with respect to what LODs must be supplied and what the parameters of those LODs are, but providing proper LODs will greatly reduce the cost of an object in terms of parcel limits, effectively allowing regions with efficient content to carry more content, while regions with inefficient content can carry less. This should allow greater control from Linden Lab in terms of acceptable rendering and streaming budgets while also giving artists complete control over how they build.

Concept

The streaming and rendering cost of a mesh is directly related to the number of bytes in a mesh asset LOD slot, and the likelihood that a given LOD will be downloaded and displayed can be computed based on the size of the object. Imagine a set of 3 concentric circles centered on an object where each circle represents the transition boundary between LODs. The streaming/rendering cost of that object can be determined by examining the size of those circles vs the number of bytes in the relevant LODs. Uploading a high LOD only will result in the load of the high lod being applied to the entire 256m, while uploading appropriate LODs will result in the lion's share of 256m being applied to the lowest LOD

Equation

  1. Compute the distance at which each LOD is displayed
  2. Compute the range at which each LOD is relevant
  3. Adjust for missiing LODs
  4. Compute cost based on relevant range and bytes in LOD

LOD Transition Distances

To compute the distance at which each LOD is displayed, take the radius of the object's bounding box (R) and divide by the LOD ratios used in the viewer:

  • Dlowest = distance at which lowest LOD begins to be displayed
  • Dlow = distance at which low LOD begins to be displayed
  • Dmid = distance at which mid LOD begins to be displayed
  • Dhigh = distance at which high LOD begins to be displayed
  • Dlowest = R / 0.06
  • Dlow = R / 0.24
  • Dmid = R / 1.0
  • Dhigh = 0.0

Relevant LOD Ranges

The relevant range of each LOD is the distance between which that LOD becomes visible and the distance at which that LOD is no longer displayed, clamped to a 256m circle.

Example:
For an object with a bounding box R of 10m,
The Dhigh LOD will be displayed while the camera is within 0m to 10m from the object's center.
The Dmid LOD will be displayed while the camera is within 10m to 41.67m (10/0.24) from the object's center.
The Dlow LOD will be displayed while the camera is within 41.67m to 166.37m from the object's center.
The Dlowest LOD will be displayed while the camera is within 166.37m to 256m from the object's center.

Adjusting for missing LODs

if any lod is missing, substitute bytes in next highest available LOD. That is, if BYTES_IN_MID is zero, substitute BYTES_IN_HIGH for BYTES_IN_MID, and so on

Computing Cost

Streaming Cost = (MAX(256-Dlowest, 1.0)/32 * KBYTES_IN_LOWEST + MAX(Dlowest-Dlow, 1.0)/32 * KBYTES_IN_LOW + MAX(Dlow - Dmid, 1.0)/32 * KBYTES_IN_MID + MAX(Dmid, 1.0)/32 * KBYTES_IN_HIGH) * COST_SCALER

Cost Scaler

Cost scaler is currently 0.125.

Implementation

F32 getStreamingCost(const LLSD& header, F32 radius)
{
	F32 dlowest = llmin(radius/0.06f, 256.f);
	F32 dlow = llmin(radius/0.24f, 256.f);
	F32 dmid = llmin(radius/1.0f, 256.f);
	F32 dhigh = 0.f;


	F32 bytes_lowest = header["lowest_lod"]["size"].asReal()/1024.f;
	F32 bytes_low = header["low_lod"]["size"].asReal()/1024.f;
	F32 bytes_mid = header["medium_lod"]["size"].asReal()/1024.f;
	F32 bytes_high = header["high_lod"]["size"].asReal()/1024.f;

	if (bytes_high == 0.f)
	{
		return 0.f;
	}

	if (bytes_mid == 0.f)
	{
		bytes_mid = bytes_high;
	}

	if (bytes_low == 0.f)
	{
		bytes_low = bytes_mid;
	}

	if (bytes_lowest == 0.f)
	{
		bytes_lowest = bytes_low;
	}

	F32 cost = 0.f;
	cost += llmax(256.f-dlowest, 1.f)/32.f*bytes_lowest;
	cost += llmax(dlowest-dlow, 1.f)/32.f*bytes_low;
	cost += llmax(dlow-dmid, 1.f)/32.f*bytes_mid;
	cost += llmax(dmid-dhigh, 1.f)/32.f*bytes_high;

	cost *= gSavedSettings.getF32("MeshStreamingCostScaler");
	return cost;
}

Proposed New Implementation

The scores generated by the current implementation are too lax on LoD benefits and as a result many mesh regions are exceeding the maximum triangle budget at various ranges. A new implementation is proposed to address that issue and ensure a reasonable triangle count per frame in mesh heavy regions.

Key Changes

Instead of using linear distance to weight each LoD's contribution to the score, use area of ring/circle specific to that LoD's zone of influence (the ring in which an observer would see the LoD in question). Using a simulator as a "total area" (65536m^2), determine how many bytes would be streamed for the mesh vs the region, constructing a weighted avg byte load based on what percentage of the region each LoD covers.


Implementation

F32 LLMeshRepository::getStreamingCost(const LLSD& header, F32 radius)
{
	F32 dlowest = llmin(radius/0.06f, 256.f);
	F32 dlow = llmin(radius/0.24f, 256.f);
	F32 dmid = llmin(radius/1.0f, 256.f);
	
	F32 bytes_lowest = header["lowest_lod"]["size"].asReal()/1024.f;
	F32 bytes_low = header["low_lod"]["size"].asReal()/1024.f;
	F32 bytes_mid = header["medium_lod"]["size"].asReal()/1024.f;
	F32 bytes_high = header["high_lod"]["size"].asReal()/1024.f;

	if (bytes_high == 0.f)
	{
		return 0.f;
	}

	if (bytes_mid == 0.f)
	{
		bytes_mid = bytes_high;
	}

	if (bytes_low == 0.f)
	{
		bytes_low = bytes_mid;
	}

	if (bytes_lowest == 0.f)
	{
		bytes_lowest = bytes_low;
	}

	F32 max_area = 65536.f;
	F32 min_area = 1.f;

	F32 high_area = llmin(F_PI*dmid*dmid, max_area);
	F32 mid_area = llmin(F_PI*dlow*dlow, max_area);
	F32 low_area = llmin(F_PI*dlowest*dlowest, max_area);
	F32 lowest_area = max_area;

	lowest_area -= low_area;
	low_area -= mid_area;
	mid_area -= high_area;

	high_area = llclamp(high_area, min_area, max_area);
	mid_area = llclamp(mid_area, min_area, max_area);
	low_area = llclamp(low_area, min_area, max_area);
	lowest_area = llclamp(lowest_area, min_area, max_area);

	F32 total_area = high_area + mid_area + low_area + lowest_area;
	high_area /= total_area;
	mid_area /= total_area;
	low_area /= total_area;
	lowest_area /= total_area;

	F32 weighted_avg = bytes_high*high_area +
					   bytes_mid*mid_area +
					   bytes_low*low_area +
					  bytes_lowest*lowest_area;

	return weighted_avg * gSavedSettings.getF32("MeshStreamingCostScaler");
}

Issues

  • Providing identical models for every LOD results in a cost identical to providing a single LOD, but results in 4x the bandwidth usage.
  • Changing the scale of an object changes its cost, which can be confusing.
  • For viewers with a view distance greater than 256m, the clamping to 256m is unrealistic.
  • Some validating of LODs is still necessary.
    • The highest LOD must be specified
    • Each LOD must have the same number of faces as the highest LOD

Related Articles