Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add EXT_primitive_voxels #48

Draft
wants to merge 21 commits into
base: 3d-tiles-next
Choose a base branch
from
Draft

Conversation

krupkad
Copy link

@krupkad krupkad commented Dec 20, 2021

This extension allows primitives to use their attribute data to represent voxel data directly, with a specified grid geometry.

Direct link to spec

extensions/2.0/Vendor/EXT_primitive_voxels/README.md Outdated Show resolved Hide resolved
Comment on lines 49 to 51
![Rectangular Voxel Grid](figures/box.png)
![Cylindrical Voxel Grid](figures/cylinder.png)
![Ellipsoid Voxel Grid](figures/sphere.png)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The blender UI and wireframe is distracting. I'm starting to think these diagrams should be rendered as voxels with the native coordinate systems labelled in addition to the cartesian coordinate system.

The images take up too much space and are not aligned. Try using a markdown table with equally sized images.

should be used to position, orient, and scale the voxel grid as needed. The `POSITION` attribute is _not_ required or used by this extension - all positioning
is through node transforms.

The `dimensions` property of the extension specifies the voxel grid dimensions:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a short explanation of how these coordinates map to the memory layout

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally agree, you've described this to me as a "reinterpret cast", but it would be good to describe this in more detail in the spec.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Diagrams and pictures could be helpful here, like showing how the same voxel grid maps to these different shapes.

the count for neighbors before and after the grid in each dimension, e.g. a `leftCount` of 1 and a `rightCount` of 2 in the `y` dimension mean that each
series of values in a given `y`-slice is preceded by one value and followed by two.

The neighbor data must be supplied with the rest of the voxel data - this means if `d1`, `d2`, `d3` are the grid dimensions and `n` is `neighborEdgeCount`, the attribute must supply `(d1 + n)*(d2 + n)*(d3 + n)` elements.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This equation should be reworked now that neighborEdgeCount doesn't exist

@IanLilleyT IanLilleyT marked this pull request as draft December 22, 2021 15:38
Comment on lines 74 to 75
The neighbor data must be supplied with the rest of the voxel data - this means if `dimensions` is `[d1,d2,d3]`, `beforeCount` is `[a1, a2, a3]`, and `afterCount` is `[b1, b2, b3]`,
the attribute must supply `(d1 + a1 + b1)*(d2 + a2 + b2)*(d3 + a3 + b3)` elements.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Inconsistent spacing between [d1,d2,d3], [a1, a2, a3], and [b1, b2, b3]
  • It's confusing that the a variables are for beforeCount and b variables are for afterCount

@@ -65,13 +69,19 @@ The `dimensions` property of the extension specifies the voxel grid dimensions:

Dimensions must be nonzero. Elements are laid out in memory first-axis-contiguous, e.g. for boxes, `x` data is contiguous (up to stride).

The `bounds` property describes how the voxel is clipped before rendering in voxel space. `bounds.minimum` and `bounds.maximum` specify a "rectangular" region of the voxel in the appropriate

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a better word than clipped here since the data is being scrunched into a smaller space, not discarded.

@@ -65,13 +69,19 @@ The `dimensions` property of the extension specifies the voxel grid dimensions:

Dimensions must be nonzero. Elements are laid out in memory first-axis-contiguous, e.g. for boxes, `x` data is contiguous (up to stride).

The `bounds` property describes how the voxel is clipped before rendering in voxel space. `bounds.minimum` and `bounds.maximum` specify a "rectangular" region of the voxel in the appropriate
coordinate systems:
- Boxes: a rectangular region of the voxel, between `(-1, -1, -1)` and `(1, 1, 1)`
Copy link

@IanLilleyT IanLilleyT Jan 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if boxes should omit this property. If we want to keep it around for consistency, there could be a note saying that it's basically pointless and can be handled with node transforms instead.

Copy link

@IanLilleyT IanLilleyT Jan 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Starting to change my mind on this. I think bounds can be useful for the box shape when the voxel glTFs are coming from a tileset. The base transformation can come from the tileset and the node transforms can be set to identity. This will be more consistent with how ellipsoid and cylinder tilesets are set up, where there's no other way to define the shape of individual tiles besides using bounds.

@IanLilleyT
Copy link

IanLilleyT commented Jan 20, 2022

It's possible we could remove bounds and store the shape-space min/max in the position accessor's min/max. The position accessor would not have a buffer view defined because the "positions" are defined implicitly by the shape type, min/max in shape-space, and any additional node or tileset transforms.

I think this would be fine in glTF:

When neither sparse nor bufferView is defined, min and max properties MAY have any values. This is intended for use cases when binary data is supplied by external means (e.g., via extensions)

@IanLilleyT
Copy link

IanLilleyT commented Jan 20, 2022

I wonder if the cylinder and ellipsoid shapes should be optional and the box shape should be required. The average voxel renderer probably doesn't care about shapes besides box. Would that mean a new extension for each non-box shape type?

@javagl
Copy link

javagl commented Mar 30, 2022

Nitpick level:

  • The inlined example uses before/after, and the last paragraph uses beforeCount/afterCount

Medium level:

  • The schema also uses neighboringEdges (instead of padding), beforeCount/afterCount, and minimum/maximum (instead of min/max)

  • Is it right that "first-axis-contiguous" means (more generally) that the elements are in lexicographic order?

Veeery high-level (not sure whether I misunderstood something here):

There is before, dimensions and after. Not listing all possible alternatives here, but that information could also be represented with something like globalSize, localOffset and localSize, or globalSize, gridMin and gridMax. They are largely equivalent and can trivially be converted into each other, but ... are there any pros/cons for these different representations?
This may also refer to implementations, which I don't have the slightest clue of. But there's some vague pseudocode in my head:

Grid grid = read("input.bin");
cout << grid.size() << endl; // Prints (10,10,10)
cout << grid.get(5,5,5) << endl; // Prints global element (5,5,5)

Grid subGrid = grid.subGrid(offset(2,3,4), size(4,3,2)); // Creates a local VIEW on the global data
cout << subGrid.get(3,2,1) << endl; // Prints local element (3,2,1), which is ALSO global element (5,5,5)
...

Copy link

@ptrgags ptrgags left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IanLilleyT I had some comments about the spec

| ------------- | ------------- | ------------- |
|![Rectangular Voxel Grid](figures/box.png)|![Cylindrical Voxel Grid](figures/cylinder.png)|![Ellipsoid Voxel Grid](figures/sphere.png)|

These grids all define "unit" objects centered at the origin, contained in the bounding box between `(-1, -1, -1)` and `(1, 1, 1)`. Node transforms should be used to position, orient, and scale the voxel grid as needed. The `POSITION` attribute is _not_ required or used by this extension - all positioning is through node transforms.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the sentence about POSITION, which of these two cases is true:

  • A voxel primitive with a POSITION attribute is valid, but the attribute will be ignored
  • the POSITION attribute is forbidden.

The current wording sounds like the former (which I think makes sense), just wanted to check.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, it's the former

The `dimensions` property of the extension specifies the voxel grid dimensions:
- x/y/z for boxes
- r/z/theta for cylinders
- lon/lat/height for ellipsoids
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- lon/lat/height for ellipsoids
- longitude/latitude/height for ellipsoids

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

should be used to position, orient, and scale the voxel grid as needed. The `POSITION` attribute is _not_ required or used by this extension - all positioning
is through node transforms.

The `dimensions` property of the extension specifies the voxel grid dimensions:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally agree, you've described this to me as a "reinterpret cast", but it would be good to describe this in more detail in the spec.


The `dimensions` property of the extension specifies the voxel grid dimensions:
- x/y/z for boxes
- r/z/theta for cylinders
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In glTF, y is up -- is the cylinder z coordinate pointing in the y direction or the z direction? this should be made clear (perhaps with a diagram)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the cylinder coordinate names from r/z/theta to radius/height/angle so I hope it's more clear now. The flat top of the cylinder (bounds.max.y) corresponds to height and is positive Y axis.

👍 to a diagram.

- r/z/theta for cylinders
- lon/lat/height for ellipsoids

Dimensions must be nonzero. Elements are laid out in memory first-axis-contiguous, e.g. for boxes, `x` data is contiguous (up to stride).
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"first-axis-contiguous" is a term I've never heard before. I find this confusing.

I think you mean column major? (albeit that is a confusing term in 3D. here's an example of row major in 3D) basically "x-major" means it's the first index if you access it array[a][b][c] and therefore the index that changes the slowest. At least I think?

Also see how NumPy describes its arrays as either row- or column- major (the order parameter) for an example with n-dimensional arrays.

Copy link

@IanLilleyT IanLilleyT Mar 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps that sentence can be reworded. It's row-major access where x changes the fastest. If this were a C array (row major) it would be accessed like array[z][y][x]. I wonder if array[x][y][z] is more typical...

Dimensions must be nonzero. Elements are laid out in memory first-axis-contiguous, e.g. for boxes, `x` data is contiguous (up to stride).

The `bounds` property describes which section of the primitive is mapped to the voxel grid. `bounds.min` and `bounds.max` specify a "rectangular" region of the voxel grid in the appropriate coordinate systems:
- Boxes: a rectangular region of the box, between `(-1, -1, -1)` and `(1, 1, 1)`. **This is essentially a no-op - prefer using node transforms for boxes**.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's not going to be used, should it be forbidden for boxes?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

- Cylinders: a slice of the cylinder, between `(0, -1, 0)` and `(1, 1, 2*pi)`
- Ellipsoids: a surface patch with height, between `(-pi, -pi/2, 0)` and `(pi, pi/2, 1)`

The `padding` property specifies how many rows of attribute data in each dimension come from neighboring grids. This is useful in situations where the primitive represents a single tile in a larger grid, and data from neighboring tiles is needed for non-local effects e.g. trilinear interpolation, blurring, antialiasing. `padding.before` and `padding.after` specify the number of rows before and after the grid in each dimension, e.g. a `padding.before` of 1 and a `padding.after` of 2 in the `y` dimension mean that each series of values in a given `y`-slice is preceded by one value and followed by two.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To a glTF user (and not a 3D Tiles user), this is going to sound confusing. what is a "tile"? do you have a concrete example to illustrate this?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'll need a diagram for this. You're right that calling it a tile is confusing since the padding can be used for other purposes. I'll have to rework this paragraph a bit.


The `padding` property specifies how many rows of attribute data in each dimension come from neighboring grids. This is useful in situations where the primitive represents a single tile in a larger grid, and data from neighboring tiles is needed for non-local effects e.g. trilinear interpolation, blurring, antialiasing. `padding.before` and `padding.after` specify the number of rows before and after the grid in each dimension, e.g. a `padding.before` of 1 and a `padding.after` of 2 in the `y` dimension mean that each series of values in a given `y`-slice is preceded by one value and followed by two.

The padding data must be supplied with the rest of the voxel data - this means if `dimensions` is `[d1, d2, d3]`, `beforeCount` is `[b1, b2, b3]`, and `afterCount` is `[a1, a2, a3]`,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I see what you're getting at, but a more concrete example, perhaps with a diagram would be helpful

"primitives": [
{
"attributes": {
"_TEMPERATURE": 1
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spec doesn't say much about the attributes used here. Some questions:

  • Do any of the built-in attributes (POSITION, NORMAL, TEXCOORD_n, etc.) have well-defined behavior in a voxel context? or are they treated the same as custom attributes like _TEMPERATURE here?
  • Just to check, are there any restrictions on types? e.g. if I made a matrix attribute, that would work fine? if so, how does it interact with the matrix padding requirements

- Cylinders: a slice of the cylinder, between `(0, -1, -pi)` and `(1, 1, pi)`
- Ellipsoids: a surface patch with height, between `(-pi, -pi/2, 0)` and `(pi, pi/2, 1)`

The `padding` property specifies how many rows of attribute data in each dimension come from neighboring grids. This is useful in situations where the primitive represents a single tile in a larger grid, and data from neighboring tiles is needed for non-local effects e.g. trilinear interpolation, blurring, antialiasing. `padding.before` and `padding.after` specify the number of rows before and after the grid in each dimension, e.g. a `padding.before` of 1 and a `padding.after` of 2 in the `y` dimension mean that each series of values in a given `y`-slice is preceded by one value and followed by two.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait is it padding or neighboringEdges? the spec and schema seem inconsistent.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed to padding

@IanLilleyT
Copy link

@javagl

The inlined example uses before/after, and the last paragraph uses beforeCount/afterCount
The schema also uses neighboringEdges (instead of padding), beforeCount/afterCount, and minimum/maximum (instead of min/max)

Fixed to padding before / after and bounds min / max.

Is it right that "first-axis-contiguous" means (more generally) that the elements are in lexicographic order?

Not sure. It matches the order that the coordinates are listed per shape.

There is before, dimensions and after. Not listing all possible alternatives here, but that information could also be represented with something like globalSize, localOffset and localSize, or globalSize, gridMin and gridMax. They are largely equivalent and can trivially be converted into each other, but ... are there any pros/cons for these different representations?

Interesting idea - that sounds more straightforward than the before / after padding fields. A 6x6x6 grid with 1 padding in all directions could look like:

"dimensions": {
    "total": [8, 8, 8],
    "min": [1, 1, 1],
    "max": [6, 6, 6] 
}

And if there's no padding it would just be

"dimensions": {
    "total": [6, 6, 6],
}

@j9liu j9liu mentioned this pull request Jun 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants