<jupyter_start><jupyter_text>GLTF 格式教學 開篇   [`Open in observablehq`](https://observablehq.com/@toonnyy8/gltf)  關於 glTF [glTF](https://www.khronos.org/gltf/) 全名為 GL Transmission Format (GL 傳輸格式)，  是由 Khronos 所推出的新 3D 模型儲存格式，  以容易讀取與解析的 JSON 格式作為主體，  希望能讓開發者輕鬆、高效的存取 3D 模型。 glTF 格式實際上有三種儲存形式，分別是：1. **.gltf + .bin + texture**  > 由 JSON 格式的 `.gltf`、二進位格式的 `.bin` 與紋理圖片 `texture` 組合而成，  > 並在 `.gltf` 裡使用 url 去指向 `.bin` & `texture`。2. **.gltf only**  > 將全部資訊都儲存在 `.gltf` 中，  > 原本 `.bin` 與 `texture` 的部分會轉成 base64 存放於 `.gltf`。3. **.glb**  > 將全部資訊轉換成二進位格式儲存在 `.glb`。  關於本教學 本篇教學是某位萌新因心血來潮而開始編寫，  將會用 js 一步步去解讀 glTF，  希望能藉此將 glTF 格式推廣出去。  > ps.  > 實際上是某天看到 glTF 是自己能看懂的 3D 格式就很興奮的跳坑了，  > 然後才悲劇的發現「看懂!=讀懂」，好在官方文檔還蠻完善的，  > 網路上也能找些許的教學，所以開始非常痛苦又漫長的學習之路，  > 期許各位讀者能從這系列教學輕鬆的了解 glTF 並完成自己的載入器。  .glb 格式解析 作為教學的開篇就先簡單的分析 glTF 的二進位格式 glb![圖 1. glb格式 \[1\]](https://raw.githubusercontent.com/CSP-GD/notes/master/practice/file_format/gltf%E6%A0%BC%E5%BC%8F%E8%A7%A3%E6%9E%90/glb/gltfOverview-2.0.0b-binary.png)圖 1. glb格式 \[1\]  載入 glTF_tools<jupyter_code>!wget https://github.com/CSP-GD/notes/raw/master/practice/file_format/gltf%E6%A0%BC%E5%BC%8F%E8%A7%A3%E6%9E%90/gltf-tools.ipynb -O gltf-tools.ipynb
%run ./gltf-tools.ipynb<jupyter_output>--2020-04-07 15:27:07--  https://github.com/CSP-GD/notes/raw/master/practice/file_format/gltf%E6%A0%BC%E5%BC%8F%E8%A7%A3%E6%9E%90/gltf-tools.ipynb
Resolving github.com (github.com)... 172.16.31.10
Connecting to github.com (github.com)|172.16.31.10|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://raw.githubusercontent.com/CSP-GD/notes/master/practice/file_format/gltf%E6%A0%BC%E5%BC%8F%E8%A7%A3%E6%9E%90/gltf-tools.ipynb [following]
--2020-04-07 15:27:08--  https://raw.githubusercontent.com/CSP-GD/notes/master/practice/file_format/gltf%E6%A0%BC%E5%BC%8F%E8%A7%A3%E6%9E%90/gltf-tools.ipynb
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 192.168.3.11, 172.16.17.32, 192.168.3.11, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|192.168.3.11|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 5549 (5.4K) [text/plain]
Saving to: ‘gltf-tools.ipynb’

gltf-tools.ipynb    100%[==============[...]<jupyter_text>先載入範例檔案<jupyter_code>!wget https://github.com/CSP-GD/notes/raw/master/practice/file_format/gltf%E6%A0%BC%E5%BC%8F%E8%A7%A3%E6%9E%90/glb/cube.glb -O cube.glb
glb_file = open('./cube.glb', 'rb')

glb_file<jupyter_output><empty_output><jupyter_text>將檔案內容讀取出來<jupyter_code>glb_bytes = glb_file.read()<jupyter_output><empty_output><jupyter_text>解析 header 0~3 Bytes 為「glTF」的 ASCII 編碼<jupyter_code>glb_bytes[0:4].decode()<jupyter_output><empty_output><jupyter_text>第 4~7 Bytes 應該會顯示 glTF 的版本<jupyter_code>gltf_version = 0

for idx, curr in enumerate(glb_bytes[4:8]):
    gltf_version += curr * 256 ** idx

gltf_version<jupyter_output><empty_output><jupyter_text>第 8~11 Bytes 則代表此檔案的 Byte 數<jupyter_code>print(len(glb_bytes), "Bytes")
gltf_length = 0

for idx, curr in enumerate(glb_bytes[8:12]):
    gltf_length += curr * 256 ** idx

gltf_length<jupyter_output><empty_output><jupyter_text>解析 chunk<jupyter_code>import json

def chunk_parser(chunk_type, chunk_data):
    return {
        "BIN\u0000": lambda:chunk_data,
        "JSON": lambda:json.loads(chunk_data.decode()),
    }[chunk_type]()<jupyter_output><empty_output><jupyter_text>chunk 0 (JSON)<jupyter_code>chunk = { 'type': "", 'length': 0, 'data': "" }<jupyter_output><empty_output><jupyter_text>由 chunk 0 的 第 0~3 Bytes 知道 chunkLength 為多少<jupyter_code>chunk["length"] = 0

for idx, curr in enumerate(glb_bytes[12:12 + 4]):
    chunk["length"] += curr * 256 ** idx

chunk["length"]<jupyter_output><empty_output><jupyter_text>由 chunk 0 的 第 4~7 Bytes 知道 chunkType 為 JSON<jupyter_code>chunk["type"] = glb_bytes[12 + 4:12 + 8].decode()

chunk["type"]<jupyter_output><empty_output><jupyter_text>知道了 chunkType 以及 chunkLength 就可以開始解析 chunkData 了<jupyter_code>chunk["data"] = chunk_parser(
  chunk["type"],
  glb_bytes[12 + 8:12 + 8 + chunk["length"]]
)
glTF_tools.render_JSON(chunk["data"])<jupyter_output><empty_output><jupyter_text>剩下的 chunk 解析的方式都相同，在此就直接使用迴圈處理<jupyter_code>chunks = []
offset = 12
i = 0

while(offset < len(glb_bytes)):
    chunks.append({ "type": "", "length": 0, "data": "" })

    for idx, curr in enumerate(glb_bytes[offset:offset + 4]):
        chunks[i]["length"] += curr * 256 ** idx

    chunks[i]["type"] = glb_bytes[offset + 4:offset + 8].decode()

    chunks[i]["data"] = chunk_parser(
        chunks[i]["type"],
        glb_bytes[offset + 8:offset + 8 + chunks[i]["length"]]
    )
    offset += 8 + chunks[i]["length"]
    i += 1<jupyter_output><empty_output><jupyter_text>最後將 chunks 整理一下<jupyter_code>model = chunks[0]["data"]
buffers = []

for idx in range(len(model['buffers'])):
    buffers.append(chunks[idx + 1]["data"])<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.17508210180623973, 'fraction_numerical': 0.052545155993431854, 'mean_word_length': 5.59404600811908, 'pattern_counts': {'":': 5, '<': 53, '<?xml version=': 0, '>': 62, 'https://': 8, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 6, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '13', 'dir': 'jupyter-structured-clean-dedup', 'id': '519910', 'license': 'MIT', 'n_tokens_mistral': 2601, 'n_tokens_neox': 2543, 'n_words': 315, 'path': 'glb/glb.ipynb', 'repo_name': 'CSP-GD/tutorial-of-gltf-format', 'size': '4879'}