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

Textures update for glTF 2.0 #860

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 74 additions & 23 deletions specification/2.0/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Copyright (C) 2013-2017 The Khronos Group Inc. All Rights Reserved. glTF is a tr
* [Texture Data](#texture-data)
* [Textures](#textures)
* [Images](#images)
* [Image Formats](#image-formats)
* [Samplers](#samplers)
* [Materials](#materials)
* [Metallic-Roughness Material](#metallic-roughness-material)
Expand Down Expand Up @@ -831,57 +832,108 @@ A skin is instanced within a node using a combination of the node's `mesh` and `

## Texture Data

**TODO: describe separation of concerns: textures / images / samplers**
glTF separates texture access into three distinct types of objects: Textures, Images, and Samplers.

### Textures

**TODO: update to latest revisions**

All textures are stored in the asset's `textures` array. A texture is defined by an image file, denoted by the `source` property; `format` and `internalFormat` specifiers, corresponding to the GL texture format types; a `target` type for the sampler; a sampler identifier (`sampler`), and a `type` property defining the internal data format. Refer to the GL definition of `texImage2D()` for more details.
All textures are stored in the asset's `textures` array. A texture is defined by an image resource, denoted by the `image` property; a `target` type for the sampler; and a sampler index (`sampler`).
When `sampler` is omitted, a sampler with all-default properties should be used.

```json
{
"textures": [
{
"format": 6408,
"internalFormat": 6408,
"sampler": 0,
"source": 2,
"target": 3553,
"type": 5121
"image": 2,
"sampler": 0
}
]
}
```

> **Implementation Note** glTF 2.0 supports only 2D texture targets. Support for other targets is expected in future revisions.

### Images

**TODO: update to latest revisions**
Images referred by textures are stored in the `images` array of the asset.

Each `image` defines an `image.sources` set of image sources. Type and format of image source are defined by `mimeType` and `internalFormat` specifiers.

An image source can refer to image data either by array of URIs (external or base64-encoded), or by array of Buffer Views.

> **Implementation Note** glTF 2.0 requires these arrays to contain no more than one element. This restriction could be lifted in future to support more texture targets.

First image pixel corresponds to the lower left corner of the image.

> **Implementation Note**: With WebGL API, the first pixel transferred from the `TexImageSource` (i.e., HTML Image object) to the WebGL implementation corresponds to the upper left corner of the source. To achieve correct rendering, WebGL runtimes must flip Y axis by enabling `UNPACK_FLIP_Y_WEBGL` flag. This behavior was changed from glTF 1.0.
Copy link
Member

Choose a reason for hiding this comment

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

Once this is merged, please update #605 with changes like this, especially this one since it is breaking but the syntax doesn't change.


Images referred to by textures are stored in the `images` array of the asset. Each image contains a URI to an external file (or a reference to a bufferView) in one of the supported images formats. Image data may also be stored within the glTF file as base64-encoded data and referenced via data URI. For example:
Any colorspace information (such as ICC profiles, intents, etc) from PNG or JPEG containers must be ignored.

> **Implementation Note**: This increases portability of an asset, since not all image decoding libraries fully support custom color conversions. To achieve correct rendering, WebGL runtimes must disable such conversions by setting `UNPACK_COLORSPACE_CONVERSION_WEBGL` flag to `NONE`.

In the following example asset provides two versions of the same image: in sRGB and in linear colorspaces; so clients with hardware sRGB filtering support (or with enough fragment shader processing power) benefit from reduced banding, whilst low-power clients still get correct texel values by using linear-space image data directly.
Copy link
Member

Choose a reason for hiding this comment

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

Sounds like a conformant renderer is free to select any object in the sources array. Can the spec be more explicit about this?

Copy link
Member Author

Choose a reason for hiding this comment

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

sources are different representations of the same image. So choice is ultimately up to engine (based on mimeType and internalformat).

Copy link
Member

Choose a reason for hiding this comment

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

Agreed, let's just be super explicit about it in the spec.


```json
{
"images": [
{
"uri": "duckCM.png"
},
{
"bufferView": 14,
"mimeType": "image/jpeg"
"sources": [
{
"internalformat": 32856,
"mimeType": "image/png",
"uris": ["duck_diffuse_linear.png"]
},
{
"internalformat": 35907,
"mimeType": "image/png",
"uris": ["duck_diffuse_srgb.png"]
}
]
}
]
}
```
> **Implementation Note**: With WebGL API, the first pixel transferred from the `TexImageSource` (i.e., HTML Image object) to the WebGL implementation corresponds to the upper left corner of the source. Non-WebGL runtimes may need to flip Y axis to achieve correct texture rendering.

### Samplers
#### Image Formats

glTF 2.0 allows a limited set of possible `internalformat` values based on OpenGL ES 3.0 enums.
Copy link
Member

Choose a reason for hiding this comment

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

Isn't it possible that a glTF asset will contain a texture format that a WebGL 1.0 client will not be able to render?


Each `internalformat` value defines exactly one matching pair of `type` and `format` parameters of OpenGL ES 2.0/3.0 texture specification. See table below for details:

| `source.internalformat` | OpenGL ES 2.0 Format & Internal Format | OpenGL ES 3.0 Format | OpenGL ES 3.0 Internal Format | OpenGL ES 2.0/3.0 Type |
|-------------------------|----------------------------------------|----------------------|-------------------------------|---------------------------|
| ALPHA (`6406`) | ALPHA | ALPHA | ALPHA | UNSIGNED_BYTE |
| LUMINANCE (`6409`) | LUMINANCE | LUMINANCE | LUMINANCE | UNSIGNED_BYTE |
| LUMINANCE_ALPHA (`6410`) | LUMINANCE_ALPHA | LUMINANCE_ALPHA | LUMINANCE_ALPHA | UNSIGNED_BYTE |
| RGB8 (`32849`) | RGB | RGB | RGB8 | UNSIGNED_BYTE |
| RGBA4 (`32854`) | RGBA | RGBA | RGBA4 | UNSIGNED_SHORT_4_4_4_4 |
| RGB5_A1 (`32855`) | RGBA | RGBA | RGB5_A1 | UNSIGNED_SHORT_5_5_5_1 |
| RGBA8 (`32856`) | RGBA | RGBA | RGBA8 | UNSIGNED_BYTE |
| SRGB8 (`35905`) | SRGB_EXT | RGB | SRGB8 | UNSIGNED_BYTE |
Copy link
Member

Choose a reason for hiding this comment

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

Does this require an extension? https://www.khronos.org/registry/webgl/extensions/EXT_sRGB/

The spec should clarify this.

Copy link
Member Author

Choose a reason for hiding this comment

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

The best WebGL 1.0 runtime strategy is to use that extension, yes. Without it, the only correct option is to disable hardware filtering and perform it manually in GLSL (after manual colorspace conversion).

Supplying alternative image source with linear data could help such incapable clients. Quality will suffer from banding, obviously.

| SRGB8_ALPHA8 (`35907`) | SRGB_ALPHA_EXT | RGBA | SRGB8_ALPHA8 | UNSIGNED_BYTE |
| RGB565 (`36194`) | RGB | RGB | RGB565 | UNSIGNED_SHORT_5_6_5 |

These formats are usually supported by non-OpenGL APIs as well. See the following table for matches with Vulkan, D3D11/12, and Metal:

| `source.internalformat` | Vulkan | D3D11/12 | Metal |
|-------------------------|------------------------------------|-------------------------------------------|-----------------------------------------|
| ALPHA (`6406`) | _expand to VK_FORMAT_R8G8B8A8_UNORM_ | DXGI_FORMAT_A8_UNORM | MTLPixelFormatA8Unorm |
| LUMINANCE (`6409`) | _expand to VK_FORMAT_R8G8B8_UNORM_ | _expand to DXGI_FORMAT_R8G8B8A8_UNORM_ | _expand to MTLPixelFormatRGBA8Unorm_ |
| LUMINANCE_ALPHA (`6410`) | _expand to VK_FORMAT_R8G8B8A8_UNORM_ | _expand to DXGI_FORMAT_R8G8B8A8_UNORM_ | _expand to MTLPixelFormatRGBA8Unorm_ |
| RGB8 (`32849`) | VK_FORMAT_R8G8B8_UNORM | _expand to DXGI_FORMAT_R8G8B8A8_UNORM_ | _expand to MTLPixelFormatRGBA8Unorm_ |
| RGBA4 (`32854`) | VK_FORMAT_R4G4B4A4_UNORM_PACK16 | _repack to DXGI_FORMAT_B4G4R4A4_UNORM_ | MTLPixelFormatABGR4Unorm |
| RGB5_A1 (`32855`) | VK_FORMAT_R5G5B5A1_UNORM_PACK16 | _repack to DXGI_FORMAT_B5G5R5A1_UNORM_ | MTLPixelFormatA1BGR5Unorm |
| RGBA8 (`32856`) | VK_FORMAT_R8G8B8A8_UNORM | DXGI_FORMAT_R8G8B8A8_UNORM | MTLPixelFormatRGBA8Unorm |
| SRGB8 (`35905`) | VK_FORMAT_R8G8B8_SRGB | _expand to DXGI_FORMAT_R8G8B8A8_UNORM_SRGB_ | _expand to MTLPixelFormatRGBA8Unorm_sRGB_ |
| SRGB8_ALPHA8 (`35907`) | VK_FORMAT_R8G8B8A8_SRGB | DXGI_FORMAT_R8G8B8A8_UNORM_SRGB | MTLPixelFormatRGBA8Unorm_sRGB |
| RGB565 (`36194`) | VK_FORMAT_R5G6B5_UNORM_PACK16 | DXGI_FORMAT_B5G6R5_UNORM | MTLPixelFormatB5G6R5Unorm |

For formats without direct matches, engines could utilize channel swizzling instead of expanding.

**TODO: update to latest revisions**
### Samplers

Samplers are stored in the `samplers` array of the asset. Each sampler specifies filter and wrapping options corresponding to the GL types. The following example defines a sampler with linear mag filtering, linear mipmap min filtering, and repeat wrapping in S and T.


```json
{
"samplers": [
Expand All @@ -897,10 +949,9 @@ Samplers are stored in the `samplers` array of the asset. Each sampler specifies

> **Mipmapping Implementation Note**: When a sampler's minification filter (`minFilter`) uses mipmapping (`NEAREST_MIPMAP_NEAREST`, `NEAREST_MIPMAP_LINEAR`, `LINEAR_MIPMAP_NEAREST`, or `LINEAR_MIPMAP_LINEAR`), any texture referencing the sampler needs to have mipmaps, e.g., by calling GL's `generateMipmap()` function.


> **Non-Power-Of-Two Texture Implementation Note**: glTF does not guarantee that a texture's dimensions are a power-of-two. At runtime, if a texture's width or height is not a power-of-two, the texture needs to be resized so its dimensions are powers-of-two if the `sampler` the texture references
> * Has a wrapping mode (either `wrapS` or `wrapT`) equal to `REPEAT` or `MIRRORED_REPEAT`, or
> * Has a minification filter (`minFilter`) that uses mipmapping (`NEAREST_MIPMAP_NEAREST`, `NEAREST_MIPMAP_LINEAR`, `LINEAR_MIPMAP_NEAREST`, or `LINEAR_MIPMAP_LINEAR`).
> * has a wrapping mode (either `wrapS` or `wrapT`) equal to `REPEAT` or `MIRRORED_REPEAT`, or
> * has a minification filter (`minFilter`) that uses mipmapping (`NEAREST_MIPMAP_NEAREST`, `NEAREST_MIPMAP_LINEAR`, `LINEAR_MIPMAP_NEAREST`, or `LINEAR_MIPMAP_LINEAR`).

## Materials

Expand Down
30 changes: 9 additions & 21 deletions specification/2.0/schema/image.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,20 @@
"$schema" : "http://json-schema.org/draft-04/schema",
"title" : "image",
"type" : "object",
"description" : "Image data used to create a texture. Image can be referenced by URI or `bufferView` index. `mimeType` is required in the latter case.",
"description" : "Image data used to create a texture. Image can have different representations, defined in `sources` array.",
"allOf" : [ { "$ref" : "glTFChildOfRootProperty.schema.json" } ],
"properties" : {
"uri" : {
"type" : "string",
"description" : "The uri of the image.",
"format" : "uriref",
"gltf_detailedDescription" : "The uri of the image. Relative paths are relative to the .gltf file. Instead of referencing an external file, the uri can also be a data-uri. The image format must be jpg, png, bmp, or gif.",
"gltf_uriType" : "image"
},
"mimeType" : {
"type" : "string",
"description" : "The image's MIME type.",
"minLength" : 1
},
"bufferView" : {
"allOf" : [ { "$ref" : "glTFid.schema.json" } ],
"description" : "The index of the bufferView that contains the image. Use this instead of the image's uri property.",
"minLength" : 1
"sources" : {
"type" : "array",
"description" : "An array of image sources.",
"items" : {
"$ref" : "image.source.schema.json"
},
"minItems" : 1
},
"name" : {},
"extensions" : {},
"extras" : {}
},
"additionalProperties" : false,
"dependencies" : {
"bufferView" : ["mimeType"]
}
"additionalProperties" : false
}
48 changes: 48 additions & 0 deletions specification/2.0/schema/image.source.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"$schema" : "http://json-schema.org/draft-04/schema",
"title" : "source",
"type" : "object",
"description" : "Image source used to create a texture. Image source can be defined by an array of URI or `bufferView`s.",
"allOf" : [ { "$ref" : "glTFProperty.schema.json" } ],
"properties" : {
"internalFormat": {
"type" : "integer",
"description" : "The texture's internal format.",
"enum" : [6406, 6409, 6410, 32849, 32854, 32855, 32856, 35905, 35907, 36194],
"gltf_enumNames" : ["GL_ALPHA", "GL_LUMINANCE", "GL_LUMINANCE_ALPHA", "GL_RGB8", "GL_RGBA4", "GL_RGB5_A1", "GL_RGBA8", "GL_SRGB8", "GL_SRGB8_ALPHA8", "GL_RGB565"],
"default" : 32856,
"gltf_detailedDescription" : "The texture's internal format. Valid values correspond to GL enums: `6406` (ALPHA), `6409` (LUMINANCE), `6410` (LUMINANCE_ALPHA), `32849` (RGB8), `32854` (RGBA4), `32855` (RGB5_A1), `32856` (RGBA8), `35905` (SRGB8), `35907` (SRGB8_ALPHA8), `36194` (RGB565)",
"gltf_webgl" : "`texImage2D()` internalFormat parameter"
},
"mimeType" : {
"type" : "string",
"description" : "The image's MIME type.",
"enum" : ["image/jpeg", "image/png"]
},
"uris" : {
"type" : "array",
"description" : "The array with uris of the images.",
"items" : {
"type" : "string",
"format" : "uriref"
},
"minItems" : 1,
"maxItems" : 1,
"gltf_detailedDescription" : "The array with uris of the images. Relative paths are relative to the .gltf file. Instead of referencing an external file, the uri can also be a data-uri. The image format must match mimeType.",
"gltf_uriType" : "image"
},
"bufferViews" : {
"type" : "array",
"description" : "The array with indices of the bufferViews that contains the images. Use this instead of the `uris` property.",
"items" : {
"allOf" : [ { "$ref" : "glTFid.schema.json" } ]
},
"minItems" : 1,
"maxItems" : 1
},
"extensions" : {},
"extras" : {}
},
"additionalProperties" : false,
"required" : ["mimeType"]
}
31 changes: 2 additions & 29 deletions specification/2.0/schema/texture.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,11 @@
"description" : "A texture and its sampler.",
"allOf" : [ { "$ref" : "glTFChildOfRootProperty.schema.json" } ],
"properties" : {
"format": {
"type" : "integer",
"description" : "The texture's format.",
"enum" : [6406, 6407, 6408, 6409, 6410],
"gltf_enumNames" : ["ALPHA", "RGB", "RGBA", "LUMINANCE", "LUMINANCE_ALPHA"],
"default" : 6408,
"gltf_detailedDescription" : "The texture's format. Valid values correspond to WebGL enums: `6406` (ALPHA), `6407` (RGB), `6408` (RGBA), `6409` (LUMINANCE), and `6410` (LUMINANCE_ALPHA).",
"gltf_webgl" : "`texImage2D()` format parameter"
},
"internalFormat": {
"type" : "integer",
"description" : "The texture's internal format.",
"enum" : [6406, 6407, 6408, 6409, 6410],
"gltf_enumNames" : ["ALPHA", "RGB", "RGBA", "LUMINANCE", "LUMINANCE_ALPHA"],
"default" : 6408,
"gltf_detailedDescription" : "The texture's internal format. Valid values correspond to WebGL enums: `6406` (ALPHA), `6407` (RGB), `6408` (RGBA), `6409` (LUMINANCE), and `6410` (LUMINANCE_ALPHA). Defaults to same value as format.",
"gltf_webgl" : "`texImage2D()` internalFormat parameter"
},
"sampler" : {
"allOf" : [ { "$ref" : "glTFid.schema.json" } ],
"description" : "The index of the sampler used by this texture."
},
"source" : {
"image" : {
"allOf" : [ { "$ref" : "glTFid.schema.json" } ],
"description" : "The index of the image used by this texture."
},
Expand All @@ -40,20 +22,11 @@
"gltf_detailedDescription" : "The target that the WebGL texture should be bound to. Valid values correspond to WebGL enums: `3553` (TEXTURE_2D).",
"gltf_webgl" : "`bindTexture()`"
},
"type": {
"type" : "integer",
"description" : "Texel datatype.",
"enum" : [5121, 33635, 32819, 32820],
"gltf_enumNames" : ["UNSIGNED_BYTE", "UNSIGNED_SHORT_5_6_5", "UNSIGNED_SHORT_4_4_4_4", "UNSIGNED_SHORT_5_5_5_1"],
"default" : 5121,
"gltf_detailedDescription" : "Texel datatype. Valid values correspond to WebGL enums: `5121` (UNSIGNED_BYTE), `33635` (UNSIGNED_SHORT_5_6_5), `32819` (UNSIGNED_SHORT_4_4_4_4), and `32820` (UNSIGNED_SHORT_5_5_5_1).",
"gltf_webgl" : "`texImage2D()` type parameter"
},
"name" : {},
"extensions" : {},
"extras" : {}
},
"additionalProperties" : false,
"gltf_webgl" : "`createTexture()`, `deleteTexture()`, `bindTexture()`, `texImage2D()`, and `texParameterf()`",
"required": ["sampler", "source"]
"required": ["image"]
}