Open ptrgags opened 3 years ago
Yesterday I discussed some details about textures with @lilleyse, here are some notes from that:
BOOLEAN
will be stored in the texture as a UINT8, but interpreted as a bool
in the shader, not a uint
. RG
and BA
channels of a texel). However, there will be a flag to control this, as in some cases this is undesirable (e.g. interpolating values)rowsPerFeature = ceil(featureCount / maximumTextureWidth)
actualTextureWidth = ceil(featureCount / rowsPerFeature)
At a high-level, the algorithm will have the following phases:
For this first iteration, let's keep this simple using the rules I mentioned in the description. To recap:
defaultValue
properties are constants. They will be inlined in the shader code.constant: 0, divisor 1
), use attributesIn theory, we might want to fallback between textures <-> attributes
, but I'll hold off on this for this first iteration.
This step is very simple, it simply rejects the following types as "not representable" - any other issues like lack of floating point texture support will be caught in the next step.
STRING
/ARRAY[STRING]
- not allowed because it is variable length at each vertexARRAY[*, 5+]
(any array of 5 or more components) - textures and attributes are limited to 4 channels/components, so we are not supporting any more than this.ARRAY
s - not allowed because it is variable length at each vertexThis is the most involved phase of the algorithm. Essentially we want to go from a list of property types to a list of (packedType, channelCount: int, packingSteps: PackingFunction[])
. This process varies depending on the destination (constant/uniform/attribute/texture), as WebGL has different rules for what types are allowed.
Packing functions are any steps that are needed to do to prepare the values for packing. They will be applied in order when packing, and the inverse will be performed in the shader to unpack the values.
Some packing types require a lossy conversions. We might want to log an error or throw an error when this happens.
Several types have similar packing rules, so here are some rules for converting these into a smaller set of types. These operations are added as packing rules. The following tables summarize these rules.
Notes:
x
is the number of bits (8, 16, 32, or 64) and N
is the number of components (1-4). ARRAY
types will ultimately be implemented as a scalar/vector type for constants/uniforms/attributes, or as RGBA channels of a texture as necessary. However, for describing the type conversions, it's easiest to describe if everything is an array.Type | Converted Type | Packing Function | Lossy |
---|---|---|---|
AnyScalarType |
ARRAY[AnyScalarType, 1] |
promoteScalarToArray |
No |
ARRAY[INT(8/16), N] |
ARRAY[INT32, N] |
promoteToInt |
No |
ARRAY[INT64, N] |
ARRAY[FLOAT32, N] |
convertInt64ToF32 |
Yes |
ARRAY[UINT(8/16), N] |
ARRAY[UINT32, N] |
promoteToUint |
No |
ARRAY[UINT64, N] |
ARRAY[FLOAT32, N] |
convertU64ToF32 |
Yes |
ARRAY[FLOAT64, N] |
ARRAY[FLOAT32, N] |
convertF64ToF32 |
Yes |
At the end, only these families of types will remain: ARRAY[INT32, N]
, ARRAY[UINT32, N]
, ARRAY[FLOAT32, N]
, ARRAY[BOOLEAN, N]
. They are translated to GLSL as following:
Type | GPU Types |
---|---|
ARRAY[INT32, N] |
int/ivec2/ivec3/ivec4 |
ARRAY[UINT32, N] |
uint/uvec2/uvec3/uvec4 |
ARRAY[FLOAT32, N] |
float/vec2/vec3/vec4 |
ARRAY[BOOLEAN, N] |
bool/bvec2/bvec3/bvec4 |
Type | Converted Type | Packing Function | Lossy |
---|---|---|---|
AnyScalarType |
ARRAY[AnyScalarType, 1] |
promoteScalarToArray |
No |
ARRAY[(U)INT(8/16), N] |
ARRAY[FLOAT32, N] |
convert(U)IntToF32 |
No |
ARRAY[(U)INT(32/64), N] |
ARRAY[FLOAT32, N] |
convert(U)IntToF32Lossy |
Yes |
ARRAY[BOOLEAN, N] |
ARRAY[FLOAT32, N] |
reinterpretBooleanAsF32 |
No |
ARRAY[FLOAT64, N] |
ARRAY[FLOAT32, N] |
convertF64ToF32 |
Yes |
At the end, only the ARRAY[FLOAT32, N]
family of types will remain. They are translated as float/vec2/vec3/vec4
in GLSL
Type | Converted Type | Packing Function | Lossy |
---|---|---|---|
AnyScalarType |
ARRAY[AnyScalarType, 1] |
promoteScalarToArray |
No |
ARRAY[(U)INT64, N] |
ARRAY[FLOAT32, N] |
convert(U)Int64ToF32 |
Yes |
ARRAY[INTx, N] |
ARRAY[UINTx, N] |
reinterpretSignedAsUnsigned |
No |
ARRAY[BOOLEAN, N] |
ARRAY[UINT8, N] |
reinterpretBooleanAsU8 |
No |
ARRAY[FLOAT64, N] |
ARRAY[FLOAT32, N] |
convertF64ToF32 |
Yes |
At the end, only these families of types will remain: ARRAY[UINT(8/16/32), N]
, ARRAY[FLOAT32, N]
. The packed type is a little more involved, as it depends on whether float textures are supported via the OES_texture_float
extension. Some types have a
fallback when this is not available, others will throw errors. In some cases, more packing functions are needed.
Type | With OES_texture_float |
OES_texture_float unavailable |
Packing Functions |
---|---|---|---|
ARRAY[FLOAT32, 1] |
FLOAT32 texture, 1 channel | UINT8 texture, 4 channels | packFloatAsRGBA (without float textures) |
ARRAY[FLOAT32, N] |
FLOAT32 texture, N channels | Unsupported | None |
ARRAY[UINT8, N] |
UINT8 texture, N channels | UINT8 texture, N channels | None |
ARRAY[UINT16, 1] |
FLOAT32 texture, 1 channel | UINT8 texture, 2 channels | packUint16AsFloat32 or packUint16As2Channels |
ARRAY[UINT16, 2] |
FLOAT32 texture, 2 channels | UINT8 texture, 4 channels | packUint16AsFloat32 or packUint16As2Channels |
ARRAY[UINT16, N] |
FLOAT32 texture, N channels | Unsupported | packUint16AsFloat32 |
ARRAY[UINT32, 1] |
FLOAT32 texture, 1 channel (lossy) | UINT8 texture, 4 channels (not lossy) | packUint32AsFloat32 or packUint32AsRGBA |
ARRAY[UINT32, N] |
FLOAT32 texture, N channels (lossy) | Unsupported | packUint32AsFloat32 |
Note: in what follows, when I say "group properties" I am not referring to group metadata from 3DTILES_metadata
, but grouping properties together by size for space efficiency.
The next step is to group properties together into a single texel/vector to conserve space.
Note: this step is optional, it should be controlled by a boolean flag. It's nice for memory efficiency, but will not be useful when interpolation is needed.
There are only 5 partitions of 4:
4
3 + 1
2 + 2
2 + 1 + 1
1 + 1 + 1 + 1
We can use this fact to pair up components to pack memory more densely:
FLOAT32
textures and properties packed as UINT8
textures. The following steps will apply to each type of texture separatelyFor example, if I had (property, channels) = (A, 1), (B, 2), (C, 2), (D, 4), (E, 3), (F, 3), (G, 1), (H, 3)
, the algorithm would work like this:
After binning:
1: A, G
2: B, C
3: E, F, H
4: D
After handling 4-components:
output = [D]
After handling 3-components
output = [D, [E, A], [F, G], H]
(note that there's nothing to pair with H so the texel will have an unused 4th component)
After handling 3-components
output = [D, [E, A], [F, G], H, [B, C]]
After handling 1-components
output = [D, [E, A], [F, G], H, [B, C]] (no changes needed)
For uniforms, each group of properties becomes a single uniform.
For attributes, each group of properties becomes a single attribute.
For textures, it's a little more involved. Each group of properties becomes a single texel, but there are a couple different ways these texels can be arranged:
propertyHeight = ceil(featureCount / textureWidth)
, then texels are accessed by row = propertyIndex * propertyHeight + floor(featureId / textureWidth)
column = featureId % textureWidth`.
where propertyIndex
would be computed for each property
index = propertyOffset + featureId
row = floor(index // textureWidth)
column = index % textureWidth
Where propertyOffset is computed for each property.
I think Option 2 is nicer for its simplicity and better memory efficiency for multiple feature tables.
NOTE: In the above, assume textures are the maximum size and 4 channels. The next step will handle shrinking this layout to fit the content tightly, this is to be done at the end.
To finish the layout, we want to avoid wasting memory, so reduce dimensions of the data to fit the data as tight as possible. This involves:
N
texels in use, this can be done with the formula:rows = ceil(N / maximumTextureWidth)
columns = ceil(N / rows)
For example, say maximumTextureWidth = 10
and N = 11
, we have:
Original texture use: 10 x 2, 9 pixels wasted:
1111111111
100000000
rows = ceil(11/10) = 2
columns = ceil(11 / 2) = 6
Result: 6x2, only 1 texel wasted:
111111
111110
Crop the number of channels if not all 4 are needed.
Texture example: Suppose there is a texture with a single property that requires 2 components (such as an ARRAY[UINT8, 2]
), use a LUMINOSITY_ALPHA
texture rather than a RGBA
texture
Attribute/uniform example: Suppose we only need 2 components (e.g. 2 UINT8
properties packed tightly into a single attribute), use a vec2
, not a vec4
Oh one clarification: when it comes to grouping properties by size, this needs to be done per-type. So for example, when it comes to textures, the FLOAT32
properties are grouped together, while the UINT32
ones are handled separately.
Learned this while reviewing https://github.com/CesiumGS/cesium/pull/9595 - WebGL 1 does not have uint
because it uses GLSL 100. WebGL 2 supports uint
because it uses GLSL 300.
Requested in https://github.com/CesiumGS/cesium/issues/11450.
Requested in #11450.
thank you for your reply. now i am trying to shake every building differently . i once thought the metadata seem a good choice to distinct them ,can you give me some advice on how i can distinct each building .
https://sandcastle.cesium.com/?src=Custom%20Shaders%203D%20Tiles.html&label=3D%20Tiles%20Next
in this example,a batch building share the same featureId
One of the upcoming parts of our 3D Tiles Next effort is to pack metadata (specifically, feature tables) for use on the GPU. This will be necessary for both custom shaders (see https://github.com/CesiumGS/cesium/issues/9518) and GPU feature styling.
Packing Overview
The goal for this subsystem is to take the metadata from the CPU, pack it into GPU memory (textures, attributes and uniforms), and then unpack it in the shader.
Only properties used in the shader code will be uploaded to the GPU. @lilleyse’s
model-loading
branch will have a way to determine this.Once uploaded and no longer needed on the CPU, try to free the CPU resources. We should include some options for controlling this.
We also want to make any texture management general-purpose, as the refactored Model.js will use other types of textures (feature textures, feature ID textures).
Datatype Compatibility
Not every data type is GPU-compatible. For example, STRING and variable length ARRAY are not easily representable on the GPU. Also, 64-bit types are not directly representable, but a fallback would be to convert them to 32-bit types.
Furthermore, WebGL 1 only supports 8-bit integer or 32-bit float (with
OES_texture_float
) textures. For larger integer types, multiple image channels or multiple pixels will have to be used.Supported Types
LUMINANCE
,ALPHA
)OES_texture_float
is available)LUMINANCE
,LUMINANCE_ALPHA
,RGB
orRGBA
depending on size)Supported with Fallbacks
LUMINANCE_ALPHA
)RGBA
)PointCloud.js
does.RGBA
) whenOES_texture_float
is not available (see https://github.com/CesiumGS/cesium/blob/master/Source/Scene/createElevationBandMaterial.js#L483-L501)Not supported
Other Notes:
vec3
? Or would this add too much complexity?Encoding Considerations
There are some special cases where values need additional encoding:
Choosing a GPU layout
The main unknown right now is how to choose an optimal GPU layout. The calling code will provide a list of properties and information about what GPU resources are available. The layout algorithm needs to take this information and determine what textures/vertex attributes/uniforms to use to store the metadata.
One possibility is to divide the properties into three categories:
constant: 0, divisor: 1
) are good candidates for storing in attributesdefaultValue
can be inlined into the shader code to avoid using GPU resources.However, determining the exact layout is more involved. Here are some complicating factors:
Inputs:
Output:
This layout can be used by the caller to set the
Property
struct in the shader, as well as determine where/how to upload data to the GPU.Stretch Goal: Filtering
One detail that would be nice to have is to allow a method to let the user filter properties. This has a number of benefits:
Potential downsides:
To Do:
czm_
builtin functions (or snippets appended to a shader) for unpacking metadata in GLSLModel.js
refactor