├── .github
└── workflows
│ ├── build-usd.yml
│ ├── build.yml
│ ├── deploy-release-manual.yml
│ ├── deploy-release-weekly.yml
│ ├── deploy-release.yml
│ ├── run-tests-manual.yml
│ ├── run-tests-usd2403.yml
│ ├── run-tests-usd2405.yml
│ ├── run-tests-usd2408.yml
│ ├── run-tests-usd2411.yml
│ ├── run-tests-usd2502.yml
│ └── run-tests.yml
├── .gitignore
├── .gitmodules
├── CHANGELOG.md
├── CMakeLists.txt
├── LICENSE
├── README.md
├── cmake
├── Finddraco.cmake
├── UsdDefaults.cmake
└── gucConfig.cmake.in
├── docs
├── Ecosystem_Limitations.md
└── Structure_Mapping.md
├── extern
├── CMakeLists.txt
└── stb_image
│ ├── LICENSE
│ └── stb_image.h
├── preview_glTFSampleViewer.png
├── preview_hdStorm.png
└── src
├── CMakeLists.txt
├── guc
├── CMakeLists.txt
├── license.h.in
└── main.c
└── libguc
├── CMakeLists.txt
├── include
└── guc.h
├── plugInfo.json.in
└── src
├── cgltf_util.cpp
├── cgltf_util.h
├── converter.cpp
├── converter.h
├── debugCodes.cpp
├── debugCodes.h
├── fileFormat.cpp
├── fileFormat.h
├── guc.cpp
├── image.cpp
├── image.h
├── materialx.cpp
├── materialx.h
├── mesh.cpp
├── mesh.h
├── naming.cpp
├── naming.h
├── usdpreviewsurface.cpp
└── usdpreviewsurface.h
/.github/workflows/build-usd.yml:
--------------------------------------------------------------------------------
1 | name: Build guc for USD
2 |
3 | on:
4 | workflow_call:
5 | inputs:
6 | usd-version:
7 | required: true
8 | type: string
9 | build-config:
10 | required: true
11 | type: string
12 | extra-cmake-flags:
13 | required: false
14 | type: string
15 | upload-archive:
16 | required: false
17 | type: boolean
18 | default: true
19 | archive-name-suffix:
20 | required: false
21 | type: string
22 |
23 | jobs:
24 | build:
25 | name: Build ${{ matrix.os-family }} ${{ matrix.build-config }}
26 |
27 | strategy:
28 | matrix:
29 | include:
30 | - os-family: Linux
31 | image: ubuntu-20.04
32 | usd-download-url: "https://github.com/pablode/USD/releases/download/v${{ inputs.usd-version }}-ci-release/USD${{ inputs.usd-version }}_Linux_x64.tar.gz"
33 | usd-install-path: /home/runner/work/USD/USD/INSTALL
34 | archive-name: guc_USD${{ inputs.usd-version }}_Linux_x64
35 |
36 | - os-family: MacOS
37 | image: macos-14
38 | usd-download-url: "https://github.com/pablode/USD/releases/download/v${{ inputs.usd-version }}-ci-release/USD${{ inputs.usd-version }}_MacOS_ARM.tar.gz"
39 | usd-install-path: /Users/runner/work/USD/USD/INSTALL
40 | archive-name: guc_USD${{ inputs.usd-version }}_MacOS_ARM
41 |
42 | - os-family: Windows
43 | image: windows-2019
44 | usd-download-url: "https://github.com/pablode/USD/releases/download/v${{ inputs.usd-version }}-ci-release/USD${{ inputs.usd-version }}_Windows_x64.tar.gz"
45 | usd-install-path: C:/INSTALL
46 | archive-name: guc_USD${{ inputs.usd-version }}_Windows_x64
47 |
48 | uses: ./.github/workflows/build.yml
49 | with:
50 | image: ${{ matrix.image }}
51 | build-config: ${{ inputs.build-config }}
52 | usd-download-url: ${{ matrix.usd-download-url }}
53 | usd-install-path: ${{ matrix.usd-install-path }}
54 | upload-archive: ${{ inputs.upload-archive }}
55 | archive-name: ${{ matrix.archive-name }}${{ inputs.archive-name-suffix }}
56 | extra-cmake-flags: ${{ inputs.extra-cmake-flags }}
57 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build guc
2 |
3 | on:
4 | workflow_call:
5 | inputs:
6 | image:
7 | required: true
8 | type: string
9 | build-config:
10 | required: true
11 | type: string
12 | # Must match CI installation path (see USD issue #1025)
13 | usd-download-url:
14 | required: true
15 | type: string
16 | usd-install-path:
17 | required: true
18 | type: string
19 | upload-archive:
20 | required: true
21 | type: boolean
22 | archive-name:
23 | required: true
24 | type: string
25 | extra-cmake-flags:
26 | required: false
27 | type: string
28 |
29 | defaults:
30 | run:
31 | shell: bash
32 |
33 | jobs:
34 | build:
35 | name: Build
36 | runs-on: ${{ inputs.image }}
37 |
38 | steps:
39 | - name: Check out repository
40 | uses: actions/checkout@v3
41 | with:
42 | submodules: recursive
43 |
44 | - name: Fetch USD binaries
45 | run: curl ${{ inputs.usd-download-url }} -L -v -o USD.tar.gz
46 |
47 | - name: Unpack USD binaries
48 | run: mkdir -p ${{ inputs.usd-install-path }} && tar -xvf USD.tar.gz -C ${{ inputs.usd-install-path }}
49 |
50 | - name: Create temporary folders
51 | run: mkdir BUILD INSTALL
52 |
53 | - name: Generate build system files using CMake
54 | working-directory: BUILD
55 | run: cmake .. -Dpxr_DIR=${{ inputs.usd-install-path }} -DCMAKE_BUILD_TYPE=${{ inputs.build-config }} -DGUC_BUILD_USDGLTF=ON ${{ inputs.extra-cmake-flags }}
56 |
57 | - name: Build guc
58 | working-directory: BUILD
59 | run: cmake --build . --config ${{ inputs.build-config }} -j 2
60 |
61 | - name: Install guc
62 | working-directory: BUILD
63 | run: |
64 | cmake --install . --config ${{ inputs.build-config }} --component guc --prefix "$PWD/../INSTALL"
65 | cmake --install . --config ${{ inputs.build-config }} --component libguc --prefix "$PWD/../INSTALL"
66 | cmake --install . --config ${{ inputs.build-config }} --component usdGlTF --prefix "$PWD/../INSTALL/plugin/usd"
67 |
68 | - name: Create archive
69 | if: ${{ inputs.upload-archive }}
70 | working-directory: INSTALL
71 | run: tar -zcvf ${{ inputs.archive-name }}.tar.gz *
72 |
73 | - name: Upload archive
74 | if: ${{ inputs.upload-archive }}
75 | uses: actions/upload-artifact@v4
76 | with:
77 | name: ${{ inputs.archive-name }}
78 | path: INSTALL/${{ inputs.archive-name }}.tar.gz
79 | retention-days: 1
80 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-release-manual.yml:
--------------------------------------------------------------------------------
1 | name: Deploy Release (Manual)
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | version-name:
7 | required: false
8 | type: string
9 | default: 'Version XXX'
10 | release-notes:
11 | required: false
12 | type: string
13 | default: 'TBD'
14 |
15 | jobs:
16 | deploy-release:
17 | name: Deploy Release
18 | uses: ./.github/workflows/deploy-release.yml
19 | with:
20 | version-name: ${{ inputs.version-name }}
21 | release-notes: ${{ inputs.release-notes }}
22 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-release-weekly.yml:
--------------------------------------------------------------------------------
1 | name: Deploy Release (Weekly)
2 |
3 | on:
4 | schedule:
5 | # Run every sunday at 16:00 UTC (6 PM CET)
6 | - cron: '0 16 * * 0'
7 |
8 | jobs:
9 | deploy-release:
10 | name: Deploy Release
11 | uses: ./.github/workflows/deploy-release.yml
12 | with:
13 | version-name: Weekly
14 | release-notes: 'Weekly build of the main branch.'
15 | tag-name: weekly
16 | publish-prerelease: true
17 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-release.yml:
--------------------------------------------------------------------------------
1 | name: Deploy Release
2 |
3 | on:
4 | workflow_call:
5 | inputs:
6 | version-name:
7 | required: true
8 | type: string
9 | release-notes:
10 | required: false
11 | type: string
12 | default: 'TBD'
13 | tag-name:
14 | required: false
15 | type: string
16 | publish-prerelease:
17 | required: false
18 | default: false
19 | type: boolean
20 |
21 | jobs:
22 | build-2502:
23 | name: Build guc for USD v25.02
24 | uses: ./.github/workflows/build-usd.yml
25 | with:
26 | usd-version: 25.02
27 | build-config: Release
28 |
29 | build-2411:
30 | name: Build guc for USD v24.11
31 | uses: ./.github/workflows/build-usd.yml
32 | with:
33 | usd-version: 24.11
34 | build-config: Release
35 |
36 | build-2408:
37 | name: Build guc for USD v24.08
38 | uses: ./.github/workflows/build-usd.yml
39 | with:
40 | usd-version: 24.08
41 | build-config: Release
42 |
43 | build-2405:
44 | name: Build guc for USD v24.05
45 | uses: ./.github/workflows/build-usd.yml
46 | with:
47 | usd-version: 24.05
48 | build-config: Release
49 |
50 | build-2403:
51 | name: Build guc for USD v24.03
52 | uses: ./.github/workflows/build-usd.yml
53 | with:
54 | usd-version: 24.03
55 | build-config: Release
56 |
57 | deploy-release:
58 | name: Deploy Release
59 | needs: [build-2502, build-2411, build-2408, build-2405, build-2403]
60 | runs-on: ubuntu-latest
61 |
62 | steps:
63 | - name: Download artifacts
64 | uses: actions/download-artifact@v4
65 | with:
66 | merge-multiple: true
67 |
68 | - name: Deploy draft release
69 | uses: softprops/action-gh-release@d4e8205d7e959a9107da6396278b2f1f07af0f9b
70 | with:
71 | name: ${{ inputs.version-name }}
72 | body: ${{ inputs.release-notes }}
73 | files: |
74 | *.tar.gz
75 | fail_on_unmatched_files: true
76 | tag_name: ${{ inputs.tag-name }}
77 | prerelease: ${{ inputs.publish-prerelease }}
78 | draft: ${{ ! inputs.publish-prerelease }}
79 | make_latest: false
80 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests-manual.yml:
--------------------------------------------------------------------------------
1 | name: Run Tests (Manual)
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | enable-graphical-tests-mtlx:
7 | description: Enable MaterialX graphical tests
8 | type: boolean
9 | default: false
10 | enable-graphical-tests-preview:
11 | description: Enable preview graphical tests
12 | type: boolean
13 | default: false
14 | enable-usdchecker:
15 | description: Run usdchecker
16 | type: boolean
17 | default: true
18 | usd-version:
19 | description: The version of USD to build against
20 | type: string
21 | default: 24.05
22 | tests-revision:
23 | description: Test repo revision
24 | type: string
25 | default: 'main'
26 | test-filter:
27 | description: Filter (regular expression)
28 | type: string
29 |
30 | jobs:
31 | run-tests:
32 | name: Run Tests
33 | uses: ./.github/workflows/run-tests.yml
34 | with:
35 | enable-graphical-tests-mtlx: ${{ inputs.enable-graphical-tests-mtlx }}
36 | enable-graphical-tests-preview: ${{ inputs.enable-graphical-tests-preview }}
37 | enable-usdchecker: ${{ inputs.enable-usdchecker }}
38 | usd-version: ${{ inputs.usd-version }}
39 | tests-revision: ${{ inputs.tests-revision }}
40 | test-filter: ${{ inputs.test-filter }}
41 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests-usd2403.yml:
--------------------------------------------------------------------------------
1 | name: USD v24.03
2 |
3 | on:
4 | push:
5 | paths-ignore:
6 | - '**.md'
7 | pull_request:
8 | paths-ignore:
9 | - '**.md'
10 |
11 | jobs:
12 | run-tests:
13 | name: Run Tests for USD v24.03
14 | uses: ./.github/workflows/run-tests.yml
15 | with:
16 | usd-version: 24.03
17 | test-filter: '-Draco|_Equal2411|_Above2411'
18 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests-usd2405.yml:
--------------------------------------------------------------------------------
1 | name: USD v24.05
2 |
3 | on:
4 | push:
5 | paths-ignore:
6 | - '**.md'
7 | pull_request:
8 | paths-ignore:
9 | - '**.md'
10 |
11 | jobs:
12 | run-tests:
13 | name: Run Tests for USD v24.05
14 | uses: ./.github/workflows/run-tests.yml
15 | with:
16 | usd-version: 24.05
17 | test-filter: '-Draco|_Equal2411|_Above2411'
18 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests-usd2408.yml:
--------------------------------------------------------------------------------
1 | name: USD v24.08
2 |
3 | on:
4 | push:
5 | paths-ignore:
6 | - '**.md'
7 | pull_request:
8 | paths-ignore:
9 | - '**.md'
10 |
11 | jobs:
12 | run-tests:
13 | name: Run Tests for USD v24.08
14 | uses: ./.github/workflows/run-tests.yml
15 | with:
16 | usd-version: 24.08
17 | test-filter: '-Draco|_Equal2411|_Above2411'
18 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests-usd2411.yml:
--------------------------------------------------------------------------------
1 | name: USD v24.11
2 |
3 | on:
4 | push:
5 | paths-ignore:
6 | - '**.md'
7 | pull_request:
8 | paths-ignore:
9 | - '**.md'
10 |
11 | jobs:
12 | run-tests:
13 | name: Run Tests for USD v24.11
14 | uses: ./.github/workflows/run-tests.yml
15 | with:
16 | usd-version: 24.11
17 | test-filter: '-Draco|_Below2411|_Above2411'
18 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests-usd2502.yml:
--------------------------------------------------------------------------------
1 | name: USD v25.02
2 |
3 | on:
4 | push:
5 | paths-ignore:
6 | - '**.md'
7 | pull_request:
8 | paths-ignore:
9 | - '**.md'
10 |
11 | jobs:
12 | run-tests:
13 | name: Run Tests for USD v25.02
14 | uses: ./.github/workflows/run-tests.yml
15 | with:
16 | usd-version: 25.02
17 | test-filter: '-Draco|_Below2411|_Equal2411'
18 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: Run Tests
2 |
3 | on:
4 | workflow_call:
5 | inputs:
6 | enable-graphical-tests-mtlx:
7 | required: false
8 | type: boolean
9 | enable-graphical-tests-preview:
10 | required: false
11 | type: boolean
12 | enable-usdchecker:
13 | required: false
14 | type: boolean
15 | default: true
16 | usd-version:
17 | required: true
18 | type: string
19 | tests-revision:
20 | required: false
21 | type: string
22 | default: '05e58f260b8b28266b5f9f24d35063e2420f36de'
23 | test-filter:
24 | required: false
25 | type: string
26 |
27 | defaults:
28 | run:
29 | shell: bash
30 |
31 | jobs:
32 | build-debug:
33 | name: Build guc for USD v${{ inputs.usd-version }} (Debug)
34 | uses: ./.github/workflows/build-usd.yml
35 | with:
36 | usd-version: ${{ inputs.usd-version }}
37 | build-config: Debug
38 | extra-cmake-flags: -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DCMAKE_DISABLE_FIND_PACKAGE_OpenImageIO=1
39 | archive-name-suffix: _debug
40 |
41 | build-debug-oiio:
42 | name: Build guc for USD v${{ inputs.usd-version }} (Debug, OpenImageIO)
43 | uses: ./.github/workflows/build-usd.yml
44 | with:
45 | usd-version: ${{ inputs.usd-version }}
46 | build-config: Debug
47 | extra-cmake-flags: -DCMAKE_COMPILE_WARNING_AS_ERROR=ON
48 | upload-archive: false
49 |
50 | build-release:
51 | name: Build guc for USD v${{ inputs.usd-version }} (Release)
52 | uses: ./.github/workflows/build-usd.yml
53 | with:
54 | usd-version: ${{ inputs.usd-version }}
55 | build-config: Release
56 | extra-cmake-flags: -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DCMAKE_DISABLE_FIND_PACKAGE_OpenImageIO=1
57 |
58 | smoke-test:
59 | name: Smoke Test ${{ matrix.os-family }} (Release)
60 | runs-on: ${{ matrix.image }}
61 | needs: build-release
62 |
63 | strategy:
64 | fail-fast: false
65 | matrix:
66 | include:
67 | - os-family: Linux
68 | image: ubuntu-20.04
69 | usd-download-url: "https://github.com/pablode/USD/releases/download/v${{ inputs.usd-version }}-ci-release/USD${{ inputs.usd-version }}_Linux_x64_Python3.9.tar.gz"
70 | artifact-name: guc_USD${{ inputs.usd-version }}_Linux_x64.tar.gz
71 | executable-name: guc
72 |
73 | - os-family: Windows
74 | image: windows-2019
75 | usd-download-url: "https://github.com/pablode/USD/releases/download/v${{ inputs.usd-version }}-ci-release/USD${{ inputs.usd-version }}_Windows_x64_Python3.9.tar.gz"
76 | artifact-name: guc_USD${{ inputs.usd-version }}_Windows_x64.tar.gz
77 | executable-name: guc.exe
78 |
79 | steps:
80 | - name: Fetch USD binaries
81 | run: curl ${{ matrix.usd-download-url }} -L -v -o USD.tar.gz
82 |
83 | - name: Install USD binaries
84 | run: mkdir -p USD_INSTALL && tar -xvf USD.tar.gz -C $PWD/USD_INSTALL
85 |
86 | - name: Download guc artifacts
87 | id: download-guc
88 | uses: actions/download-artifact@v4
89 | with:
90 | merge-multiple: true
91 |
92 | - name: Install guc
93 | run: mkdir -p GUC_INSTALL && tar -xvf ${{ matrix.artifact-name }} -C $PWD/GUC_INSTALL
94 |
95 | - name: Set up environment variables (Linux)
96 | if: matrix.os-family == 'Linux'
97 | run: echo "LD_LIBRARY_PATH=$PWD/USD_INSTALL/lib" >> $GITHUB_ENV
98 |
99 | - name: Set up environment variables (Windows)
100 | if: matrix.os-family == 'Windows'
101 | run: |
102 | # We need to use real Windows paths, instead of MSYS2's auto-converted Unix paths
103 | echo "$(pwd -W)/USD_INSTALL/lib" >> $GITHUB_PATH
104 | echo "$(pwd -W)/USD_INSTALL/bin" >> $GITHUB_PATH
105 |
106 | - name: Set up Python
107 | uses: actions/setup-python@v5
108 | with:
109 | python-version: '3.9.13'
110 |
111 | - name: Fetch example glTF file
112 | run: mkdir test && curl "https://github.com/KhronosGroup/glTF-Sample-Models/raw/db9ff67c1116cfe28eb36320916bccd8c4127cc1/2.0/ToyCar/glTF-Binary/ToyCar.glb" -L -v -o test/asset.glb
113 |
114 | - name: Run test
115 | working-directory: test
116 | run: ../GUC_INSTALL/bin/${{ matrix.executable-name }} asset.glb asset.usda --emit-mtlx
117 |
118 | - name: Print output info
119 | working-directory: test
120 | run: |
121 | ls -la .
122 | cat asset.mtlx
123 | cat asset.usda
124 |
125 | conversion-tests:
126 | name: Conversion Tests (Debug)
127 | runs-on: ubuntu-20.04
128 | needs: build-debug
129 |
130 | env:
131 | USD_DOWNLOAD_URL: "https://github.com/pablode/USD/releases/download/v${{ inputs.usd-version }}-ci-release/USD${{ inputs.usd-version }}_Linux_x64_Python3.9.tar.gz"
132 | GUC_ARTIFACT_NAME: guc_USD${{ inputs.usd-version }}_Linux_x64_debug.tar.gz
133 | USD_INSTALL_PATH: /home/runner/work/USD/USD/INSTALL
134 |
135 | steps:
136 | - name: Fetch USD binaries
137 | run: curl ${{ env.USD_DOWNLOAD_URL }} -L -v -o USD.tar.gz
138 |
139 | - name: Install USD binaries
140 | run: mkdir -p ${{ env.USD_INSTALL_PATH }} && tar -xvf USD.tar.gz -C ${{ env.USD_INSTALL_PATH }}
141 |
142 | - name: Download guc artifacts
143 | id: download-guc
144 | uses: actions/download-artifact@v4
145 | with:
146 | merge-multiple: true
147 |
148 | # Install guc to the USD installation so that we can use the usdGlTF Sdf plugin
149 | - name: Install guc
150 | run: tar -xvf ${{ env.GUC_ARTIFACT_NAME }} -C ${{ env.USD_INSTALL_PATH }}
151 |
152 | - name: Install packages
153 | run: |
154 | # Use latest stable mesa because Storm requires OpenGL 4.6
155 | sudo add-apt-repository ppa:kisak/kisak-mesa
156 | sudo apt update
157 | sudo apt upgrade
158 | sudo apt-get install mesa-utils xvfb qt5-default
159 |
160 | - name: Set up environment variables
161 | run: |
162 | # Test options
163 | if [[ -z "${{ inputs.enable-graphical-tests-mtlx }}" ]] || [[ "${{ inputs.enable-graphical-tests-mtlx }}" = "false" ]]; then
164 | echo "GT_DISABLE_GRAPHICAL_MTLX=1" >> $GITHUB_ENV
165 | fi
166 | if [[ -z "${{ inputs.enable-graphical-tests-preview }}" ]] || [[ "${{ inputs.enable-graphical-tests-preview }}" = "false" ]]; then
167 | echo "GT_DISABLE_GRAPHICAL_PREVIEW=1" >> $GITHUB_ENV
168 | fi
169 | if [[ -z "${{ inputs.enable-usdchecker }}" ]] || [[ "${{ inputs.enable-usdchecker }}" = "false" ]]; then
170 | echo "GT_DISABLE_USDCHECKER=1" >> $GITHUB_ENV
171 | fi
172 | # Executables and libraries
173 | echo "${{ env.USD_INSTALL_PATH }}/bin" >> $GITHUB_PATH
174 | echo "LD_LIBRARY_PATH=${{ env.USD_INSTALL_PATH }}/lib" >> $GITHUB_ENV
175 | echo "PYTHONPATH=${{ env.USD_INSTALL_PATH }}/lib/python" >> $GITHUB_ENV
176 | # Software rendering
177 | echo "DISPLAY=:1" >> $GITHUB_ENV
178 | echo "LIBGL_ALWAYS_SOFTWARE=1" >> $GITHUB_ENV
179 | echo "GALLIUM_DRIVER=llvmpipe" >> $GITHUB_ENV
180 | echo "MESA_NO_ERROR=1" >> $GITHUB_ENV
181 | # guc debug output
182 | echo "TF_DEBUG=GUC" >> $GITHUB_ENV
183 |
184 | - name: Set up Python
185 | uses: actions/setup-python@v5
186 | with:
187 | python-version: '3.9.13'
188 |
189 | # usdrecord needs python (may change soon!)
190 | - name: Install Python packages
191 | run: pip3 install --user PySide2 PyOpenGL
192 |
193 | - name: Start virtual framebuffer
194 | run: |
195 | Xvfb :1 -screen 0 1280x960x24 &
196 |
197 | - name: Test glxinfo
198 | run: glxinfo
199 |
200 | - name: Print guc help
201 | run: guc -h || [ $? -eq 1 ]
202 |
203 | - name: Check out tests repository
204 | uses: actions/checkout@v3
205 | with:
206 | repository: pablode/guc-tests
207 | submodules: recursive
208 | ref: ${{ inputs.tests-revision }}
209 |
210 | - name: Run tests
211 | run: bash ./run_tests.sh "${{ inputs.test-filter }}"
212 |
213 | - name: Create test output archive
214 | if: success() || failure()
215 | working-directory: tests/output
216 | run: tar -zcvf ../../test-output.tar.gz *
217 |
218 | - name: Upload archive
219 | if: success() || failure()
220 | uses: actions/upload-artifact@v4
221 | with:
222 | path: test-output.tar.gz
223 | if-no-files-found: error
224 | name: test-output-${{ github.sha }}
225 | retention-days: 7
226 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io/api/c,c++,vim,emacs,clion,cmake,intellij,macos
2 |
3 | ### Custom ###
4 | .idea
5 |
6 | ### C ###
7 | # Prerequisites
8 | *.d
9 |
10 | # Object files
11 | *.o
12 | *.ko
13 | *.obj
14 | *.elf
15 |
16 | # Linker output
17 | *.ilk
18 | *.map
19 | *.exp
20 |
21 | # Precompiled Headers
22 | *.gch
23 | *.pch
24 |
25 | # Libraries
26 | *.lib
27 | *.a
28 | *.la
29 | *.lo
30 |
31 | # Shared objects (inc. Windows DLLs)
32 | *.dll
33 | *.so
34 | *.so.*
35 | *.dylib
36 |
37 | # Executables
38 | *.exe
39 | *.out
40 | *.app
41 | *.i*86
42 | *.x86_64
43 | *.hex
44 |
45 | # Debug files
46 | *.dSYM/
47 | *.su
48 | *.idb
49 | *.pdb
50 |
51 | # Kernel Module Compile Results
52 | *.mod*
53 | *.cmd
54 | .tmp_versions/
55 | modules.order
56 | Module.symvers
57 | Mkfile.old
58 | dkms.conf
59 |
60 | ### C++ ###
61 | # Prerequisites
62 |
63 | # Compiled Object files
64 | *.slo
65 |
66 | # Precompiled Headers
67 |
68 | # Compiled Dynamic libraries
69 |
70 | # Fortran module files
71 | *.mod
72 | *.smod
73 |
74 | # Compiled Static libraries
75 | *.lai
76 |
77 | # Executables
78 |
79 | ### CLion ###
80 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
81 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
82 |
83 | # User-specific stuff:
84 | .idea/**/workspace.xml
85 | .idea/**/tasks.xml
86 | .idea/dictionaries
87 |
88 | # Sensitive or high-churn files:
89 | .idea/**/dataSources/
90 | .idea/**/dataSources.ids
91 | .idea/**/dataSources.xml
92 | .idea/**/dataSources.local.xml
93 | .idea/**/sqlDataSources.xml
94 | .idea/**/dynamic.xml
95 | .idea/**/uiDesigner.xml
96 |
97 | # Gradle:
98 | .idea/**/gradle.xml
99 | .idea/**/libraries
100 |
101 | # CMake
102 | cmake-build-debug/
103 |
104 | # Mongo Explorer plugin:
105 | .idea/**/mongoSettings.xml
106 |
107 | ## File-based project format:
108 | *.iws
109 |
110 | ## Plugin-specific files:
111 |
112 | # IntelliJ
113 | /out/
114 |
115 | # mpeltonen/sbt-idea plugin
116 | .idea_modules/
117 |
118 | # JIRA plugin
119 | atlassian-ide-plugin.xml
120 |
121 | # Cursive Clojure plugin
122 | .idea/replstate.xml
123 |
124 | # Ruby plugin and RubyMine
125 | /.rakeTasks
126 |
127 | # Crashlytics plugin (for Android Studio and IntelliJ)
128 | com_crashlytics_export_strings.xml
129 | crashlytics.properties
130 | crashlytics-build.properties
131 | fabric.properties
132 |
133 | ### CLion Patch ###
134 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
135 |
136 | # *.iml
137 | # modules.xml
138 | # .idea/misc.xml
139 | # *.ipr
140 |
141 | # Sonarlint plugin
142 | .idea/sonarlint
143 |
144 | ### CMake ###
145 | CMakeCache.txt
146 | CMakeFiles
147 | CMakeScripts
148 | Testing
149 | Makefile
150 | cmake_install.cmake
151 | install_manifest.txt
152 | compile_commands.json
153 | CTestTestfile.cmake
154 | build
155 |
156 | ### Emacs ###
157 | # -*- mode: gitignore; -*-
158 | *~
159 | \#*\#
160 | /.emacs.desktop
161 | /.emacs.desktop.lock
162 | *.elc
163 | auto-save-list
164 | tramp
165 | .\#*
166 |
167 | # Org-mode
168 | .org-id-locations
169 | *_archive
170 |
171 | # flymake-mode
172 | *_flymake.*
173 |
174 | # eshell files
175 | /eshell/history
176 | /eshell/lastdir
177 |
178 | # elpa packages
179 | /elpa/
180 |
181 | # reftex files
182 | *.rel
183 |
184 | # AUCTeX auto folder
185 | /auto/
186 |
187 | # cask packages
188 | .cask/
189 | dist/
190 |
191 | # Flycheck
192 | flycheck_*.el
193 |
194 | # server auth directory
195 | /server/
196 |
197 | # projectiles files
198 | .projectile
199 | projectile-bookmarks.eld
200 |
201 | # directory configuration
202 | .dir-locals.el
203 |
204 | # saveplace
205 | places
206 |
207 | # url cache
208 | url/cache/
209 |
210 | # cedet
211 | ede-projects.el
212 |
213 | # smex
214 | smex-items
215 |
216 | # company-statistics
217 | company-statistics-cache.el
218 |
219 | # anaconda-mode
220 | anaconda-mode/
221 |
222 | ### Intellij ###
223 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
224 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
225 |
226 | # User-specific stuff:
227 |
228 | # Sensitive or high-churn files:
229 |
230 | # Gradle:
231 |
232 | # CMake
233 |
234 | # Mongo Explorer plugin:
235 |
236 | ## File-based project format:
237 |
238 | ## Plugin-specific files:
239 |
240 | # IntelliJ
241 |
242 | # mpeltonen/sbt-idea plugin
243 |
244 | # JIRA plugin
245 |
246 | # Cursive Clojure plugin
247 |
248 | # Ruby plugin and RubyMine
249 |
250 | # Crashlytics plugin (for Android Studio and IntelliJ)
251 |
252 | ### Intellij Patch ###
253 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
254 |
255 | # *.iml
256 | # modules.xml
257 | # .idea/misc.xml
258 | # *.ipr
259 |
260 | # Sonarlint plugin
261 |
262 | ### macOS ###
263 | *.DS_Store
264 | .AppleDouble
265 | .LSOverride
266 |
267 | # Icon must end with two \r
268 | Icon
269 |
270 | # Thumbnails
271 | ._*
272 |
273 | # Files that might appear in the root of a volume
274 | .DocumentRevisions-V100
275 | .fseventsd
276 | .Spotlight-V100
277 | .TemporaryItems
278 | .Trashes
279 | .VolumeIcon.icns
280 | .com.apple.timemachine.donotpresent
281 |
282 | # Directories potentially created on remote AFP share
283 | .AppleDB
284 | .AppleDesktop
285 | Network Trash Folder
286 | Temporary Items
287 | .apdisk
288 |
289 | ### Vim ###
290 | # swap
291 | .sw[a-p]
292 | .*.sw[a-p]
293 | # session
294 | Session.vim
295 | # temporary
296 | .netrwhist
297 | # auto-generated tag files
298 | tags
299 |
300 | # End of https://www.gitignore.io/api/c,c++,vim,emacs,clion,cmake,intellij,macos
301 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "extern/cgltf"]
2 | path = extern/cgltf
3 | url = https://github.com/jkuhlmann/cgltf
4 | [submodule "extern/MikkTSpace"]
5 | path = extern/MikkTSpace
6 | url = https://github.com/mmikk/MikkTSpace
7 | [submodule "extern/cargs"]
8 | path = extern/cargs
9 | url = https://github.com/likle/cargs.git
10 | [submodule "extern/meshoptimizer"]
11 | path = extern/meshoptimizer
12 | url = https://github.com/zeux/meshoptimizer.git
13 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | # Changelog
3 |
4 | ## Version 0.4 - 2024-07-15
5 |
6 | Added
7 | * Asset resolver support (thanks, [@expenses](https://github.com/expenses)!)
8 | * Command line option to print licenses
9 | * Display name attribute for most glTF objects
10 | * Dynamic _emitMtlx_ Sdf file format argument
11 | * Explicit documentation of GUC_BUILD_USDGLTF to README (thanks, [@expenses](https://github.com/expenses)!)
12 | * Help (-h / --help) command-line option
13 | * Support for [KHR_materials_unlit](https://github.com/KhronosGroup/glTF/blob/082d5a98f479c37dff18767982d1431fc6c014fd/extensions/2.0/Khronos/KHR_materials_unlit/README.md) extension
14 | * Third-party licenses to LICENSE file
15 |
16 | Changed
17 | * Default input values are not authored anymore in MaterialX networks
18 | * Default material binding is now used when MaterialX is disabled (thanks, [@lanxinger](https://github.com/lanxinger)!)
19 | * Fallback tangents are now only generated if MaterialX is enabled
20 | * Improved prim naming heuristics
21 | * MaterialX 1.38.6 is now required
22 | * Prebuilt binaries do not require a specific Python version anymore
23 | * Primvar naming does not emit suffix for index 0 anymore
24 | * Removed dependency on OpenImageIO (stb_image is used as a fallback)
25 | * Removed explicit color space transformations
26 | * Removed glTF PBR implementation options
27 | * Renamed Sdf plugin id from 'glTF' to 'gltf' to conform with other projects
28 | * Reorder command line arguments to conform with UNIX conventions
29 |
30 | Fixed
31 | * False-positive MSVC warning caused by Boost macros
32 | * Incorrect UsdPreviewSurface Sdf value types
33 | * Incorrect warning about greyscale texture alpha channel
34 | * Material name not being preserved if it starts with an underscore
35 | * Missing '.glb' extension in Sdf plugin definition
36 | * Missing UsdLux extents
37 | * Typo in Sdf plugin that could prevent file reading
38 | * UsdLuxShapingAPI not being applied
39 |
40 | ## Version 0.3 - 2023-07-26
41 |
42 | Added
43 | * Binaries for USD v23.08 with MaterialX 1.38.7
44 | * Metainfo that UsdGlTF plugin does not support writing
45 | * Support for [KHR_materials_emissive_strength](https://github.com/KhronosGroup/glTF/blob/d3382c30eca18312bd9cc0b36d6a9ae60e1f1bae/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md) extension
46 | * Support for [KHR_materials_iridescence](https://github.com/KhronosGroup/glTF/tree/d3382c30eca18312bd9cc0b36d6a9ae60e1f1bae/extensions/2.0/Khronos/KHR_materials_iridescence/README.md) extension
47 | * Support for [KHR_materials_variants](https://github.com/KhronosGroup/glTF/blob/d3382c30eca18312bd9cc0b36d6a9ae60e1f1bae/extensions/2.0/Khronos/KHR_materials_variants/README.md) extension
48 | * Support for [KHR_texture_transform](https://github.com/KhronosGroup/glTF/blob/d3382c30eca18312bd9cc0b36d6a9ae60e1f1bae/extensions/2.0/Khronos/KHR_texture_transform/README.md) extension
49 | * Warnings about unsupported optional extensions
50 |
51 | Changed
52 | * Disabled file glTF PBR implementation option on USD v23.08+ due to an internal crash
53 | * Renamed 'tangentSigns' primvar to 'bitangentSigns'
54 | * Updated [Ecosystem Limitations](docs/Ecosystem_Limitations.md) document with latest USD/MaterialX versions
55 |
56 | Fixed
57 | * Normalmap UsdUVTexture:scale[3] not matching USD complianceChecker
58 |
59 | ## Version 0.2 - 2023-01-09
60 |
61 | Added
62 | * CMake config file installation for libguc
63 | * Custom data for generated mesh attributes
64 | * Display color estimation from material properties
65 | * Documentation on [Ecosystem Limitations](docs/Ecosystem_Limitations.md) and [Structure Mapping](docs/Structure_Mapping.md)
66 | * Extent computation
67 | * GitHub Actions builds
68 | * Omittance of MaterialX multiplication nodes with factor 1
69 | * Option to export MaterialX glTF PBR as file
70 | * Sdf file format plugin
71 | * Support for glTF libraries (no scenes)
72 | * Test suite with graphical tests
73 | * Transmission as alpha-as-coverage to UsdPreviewSurfaces
74 | * UsdModelAPI metadata
75 | * USDZ export
76 | * Version information to header, executable and converted assets
77 | * Workaround for glTF assets referencing the wrong texture channel for alpha
78 |
79 | Changed
80 | * CMake 3.15 is now required
81 | * MikkTSpace is now linked statically
82 | * Refined output USD file structure
83 | * Renamed `guc_params` struct to `guc_options`
84 | * Updated USD version to 22.11
85 | * Vertex colors are now stored as dedicated primvars (not as displayColors)
86 | * Vertex opacities are not generated for opaque materials
87 |
88 | Fixed
89 | * Bug with base64 output buffer size estimation
90 | * Erroneous C11 compiler requirement
91 | * Fallback tangent generation
92 | * Flat normals and tangents being attempted to generate for line and point topologies
93 | * Flat normal generation not unindexing primvars
94 | * Handling of MaterialX C++ exceptions
95 | * Inactive scenes being visible
96 | * Incorrect MaterialX glTF PBR default values
97 | * Link errors with Boost and TBB (thanks, [@ix-dcourtois](https://github.com/ix-dcourtois)!)
98 | * Location of MaterialX and OIIO libraries (thanks, [@ix-dcourtois](https://github.com/ix-dcourtois)!)
99 | * MaterialX exception for very small or large float values
100 | * MaterialX inputs having multiple data source attributes
101 | * Missing MaterialX vertex color multiplication in case of missing base color texture
102 | * Mtlx-as-UsdShade error with USD 22.11
103 | * Multiple compiler warnings
104 | * Normal map tangent handling
105 | * Position independent code not being enabled for static library builds
106 | * Shared library symbols not being exported on Windows (thanks, [@ix-dcourtois](https://github.com/ix-dcourtois)!)
107 | * USD MaterialBindingAPI schema not being applied
108 |
109 | ## Version 0.1 - 2022-05-17
110 |
111 | Initial release
112 |
113 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
2 |
3 | project(guc VERSION 0.5)
4 |
5 | set(CMAKE_CXX_STANDARD 17)
6 | set(CMAKE_CXX_STANDARD_REQUIRED ON)
7 | set(CMAKE_CXX_EXTENSIONS OFF)
8 |
9 | set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
10 |
11 | # Find USD.
12 | find_package(pxr CONFIG REQUIRED)
13 |
14 | # Find MaterialX library provided by the USD installation.
15 | # Since we use UsdMtlx, using a custom MaterialX version leads to conflicts.
16 | find_package(MaterialX 1.38.6 REQUIRED HINTS ${pxr_DIR})
17 |
18 | # We need to open PNG and JPEG files in order to read the number of channels
19 | # for shading node creation. OIIO can be provided by the USD installation,
20 | # otherwise we fall back to stb_image.
21 | find_package(OpenImageIO HINTS ${pxr_DIR})
22 |
23 | # Optionally support draco. See Finddraco.cmake for more information.
24 | set(draco_ROOT ${pxr_DIR})
25 | find_package(draco)
26 |
27 | option(GUC_BUILD_EXECUTABLE "Build the guc executable." ON)
28 | option(GUC_BUILD_USDGLTF "Build the Sdf file format plugin." OFF)
29 |
30 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
31 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
32 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
33 |
34 | foreach(CONFIG_TYPE ${CMAKE_CONFIGURATION_TYPES})
35 | string(TOUPPER ${CONFIG_TYPE} CONFIG_TYPE)
36 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${CONFIG_TYPE} ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY})
37 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${CONFIG_TYPE} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
38 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${CONFIG_TYPE} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
39 | endforeach()
40 |
41 | if(MSVC)
42 | # Disable "macro expansion producing 'defined' has undefined behavior" warning
43 | # introduced by indirect inclusion of Windows headers in guc and MikkTSpace.
44 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /wd5105")
45 | # Disable "not enough arguments for function-like macro invocation" warning caused
46 | # by the transitive inclusion of boost headers and non-conformant MSVC preprocessor
47 | # behaviour. See USD's msvcdefaults.cmake file for a detailed comment.
48 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4003")
49 | endif()
50 |
51 | add_subdirectory(extern)
52 |
53 | include(cmake/UsdDefaults.cmake)
54 | set(CMAKE_CXX_FLAGS "${GUC_CXX_FLAGS} ${CMAKE_CXX_FLAGS}")
55 | add_definitions(${GUC_CXX_DEFINITIONS})
56 |
57 | add_subdirectory(src)
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## guc
2 |
3 | 
4 | 
5 |
6 | guc is a glTF to [Universal Scene Description](https://github.com/PixarAnimationStudios/USD) (USD) converter.
7 |
8 | Unlike...
9 | - [gltf2usd](https://github.com/kcoley/gltf2usd), it aims to be more than a PoC
10 | - [usd_from_gltf](https://github.com/google/usd_from_gltf), it is not AR Quick Look centric
11 | - [Apple's USDZ Tools](https://developer.apple.com/augmented-reality/tools/), it is open-source and freely available
12 |
13 | guc furthermore supports near-lossless material translation via the [MaterialX](https://github.com/AcademySoftwareFoundation/MaterialX) standard.
14 |
15 | All glTF features except animation and skinning are implemented and get continuously tested in guc's [test suite](https://github.com/pablode/guc-tests).
16 |
17 |
18 |
19 |
20 |
21 |
22 | Wayfair's Iridescent Dish with Olives (CC BY ) converted to USD+MaterialX with guc and rendered in hdStorm (left).
23 | The same model in Khronos's glTF Sample Viewer (right).
24 |
25 |
26 | ### Build
27 |
28 | You need USD v24.03+ (e.g. v25.02 ) with MaterialX support enabled.
29 |
30 | Do a recursive clone of the repository and set up a build folder:
31 | ```
32 | git clone https://github.com/pablode/guc --recursive
33 | mkdir guc/build && cd guc/build
34 | ```
35 |
36 | Pass following parameters in the CMake generation phase:
37 | ```
38 | cmake .. -Wno-dev -DCMAKE_BUILD_TYPE=Release
39 | ```
40 |
41 | Build the executable:
42 | ```
43 | cmake --build . -j8 --config Release
44 | ```
45 |
46 | > Note: set `BUILD_SHARED_LIBS` for shared builds, and `CMAKE_MSVC_RUNTIME_LIBRARY` to USD's MSVC ABI.
47 |
48 | ### Usage
49 |
50 | ```
51 | guc 0.5 - glTF to USD converter
52 |
53 | Usage: guc [options] [--]
54 |
55 | Options:
56 | -m, --emit-mtlx Emit MaterialX materials in addition to UsdPreviewSurfaces
57 | -u, --mtlx-as-usdshade Convert and inline MaterialX materials into the USD layer using UsdMtlx
58 | -v, --default-material-variant= Index of the material variant that is selected by default
59 | -l, --licenses Print the license of guc and third-party libraries
60 | -h, --help Show the command help
61 | ```
62 |
63 | Both glTF and GLB file types are valid input. USDA, USDC and USDZ formats can be written.
64 |
65 | An example asset conversion is described in the [Structure Mapping](docs/Structure_Mapping.md) document.
66 |
67 | ### Extension support
68 |
69 | Name | Status
70 | ------------------------------------|----------
71 | EXT_meshopt_compression | ✅ Complete
72 | KHR_draco_mesh_compression | ✅ Complete
73 | KHR_lights_punctual | ✅ Partial 1
74 | KHR_materials_clearcoat | ✅ Complete
75 | KHR_materials_emissive_strength | ✅ Complete
76 | KHR_materials_ior | ✅ Complete
77 | KHR_materials_iridescence | ✅ Complete
78 | KHR_materials_sheen | ✅ Complete
79 | KHR_materials_specular | ✅ Complete
80 | KHR_materials_transmission | ✅ Complete
81 | KHR_materials_unlit | ✅ Complete
82 | KHR_materials_variants | ✅ Complete
83 | KHR_materials_volume | ✅ Partial 2
84 | KHR_mesh_quantization | ✅ Complete
85 | KHR_texture_transform | ✅ Complete
86 |
87 | \[1\] Spotlight cone falloff is ignored.
88 | \[2\] Thickness is not supported by the MaterialX glTF PBR implementation.
89 |
90 | ### Sdf plugin
91 |
92 | The _usdGlTF_ library implements USD's Sdf file format interface. Enable the `GUC_BUILD_USDGLTF` CMake option before building and install it as follows:
93 | ```
94 | cmake --install . --component usdGlTF --config Release --prefix /plugin/usd
95 | ```
96 |
97 | glTF files can now be referenced as layers and opened with USD tooling.
98 | The _emitMtlx_ dynamic Sdf file format argument controls MaterialX material emission.
99 |
100 | ### License
101 |
102 | ```
103 |
104 | Copyright 2024 Pablo Delgado Krämer
105 |
106 | Licensed under the Apache License, Version 2.0 (the "License");
107 | you may not use this file except in compliance with the License.
108 | You may obtain a copy of the License at
109 |
110 | http://www.apache.org/licenses/LICENSE-2.0
111 |
112 | Unless required by applicable law or agreed to in writing, software
113 | distributed under the License is distributed on an "AS IS" BASIS,
114 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
115 | See the License for the specific language governing permissions and
116 | limitations under the License.
117 |
118 | ```
119 |
--------------------------------------------------------------------------------
/cmake/Finddraco.cmake:
--------------------------------------------------------------------------------
1 | #
2 | # Finds draco and populates following variables:
3 | # DRACO_FOUND (draco_FOUND)
4 | # DRACO_INCLUDE_DIR
5 | # DRACO_LIBRARIES
6 | #
7 | # The reason why we ship our custom Find* file is that the draco version
8 | # that USD uses, 1.3.6, has a broken CMake config file.
9 | #
10 | # This also caused trouble for USD, which is why it comes with a custom
11 | # Find* script similar to this one. However, the script has two issues:
12 | # 1) It specifies full file names, causing 'libdraco.1.dylib' to not
13 | # be found on macOS.
14 | # 2) It only finds the main draco lib, but not the decoder-specific
15 | # lib which we need in guc.
16 | #
17 |
18 | find_path(DRACO_INCLUDE_DIR NAMES "draco/core/draco_version.h")
19 |
20 | find_library(DRACO_LIBRARY NAMES draco PATH_SUFFIXES lib)
21 | find_library(DRACO_DEC_LIBRARY NAMES dracodec PATH_SUFFIXES lib)
22 |
23 | set(DRACO_LIBRARIES ${DRACO_LIBRARY} ${DRACO_DEC_LIBRARY})
24 |
25 | include(FindPackageHandleStandardArgs)
26 | find_package_handle_standard_args(draco
27 | REQUIRED_VARS
28 | DRACO_INCLUDE_DIR
29 | DRACO_DEC_LIBRARY
30 | DRACO_LIBRARY
31 | DRACO_LIBRARIES
32 | )
33 |
--------------------------------------------------------------------------------
/cmake/UsdDefaults.cmake:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2016 Pixar
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "Apache License")
5 | # with the following modification; you may not use this file except in
6 | # compliance with the Apache License and the following modification to it:
7 | # Section 6. Trademarks. is deleted and replaced with:
8 | #
9 | # 6. Trademarks. This License does not grant permission to use the trade
10 | # names, trademarks, service marks, or product names of the Licensor
11 | # and its affiliates, except as required to comply with Section 4(c) of
12 | # the License and to reproduce the content of the NOTICE file.
13 | #
14 | # You may obtain a copy of the Apache License at
15 | #
16 | # http://www.apache.org/licenses/LICENSE-2.0
17 | #
18 | # Unless required by applicable law or agreed to in writing, software
19 | # distributed under the Apache License with the above modification is
20 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 | # KIND, either express or implied. See the Apache License for the specific
22 | # language governing permissions and limitations under the Apache License.
23 | #
24 |
25 | # This files includes most compiler and linker flags that USD uses. We need
26 | # them to be able to include the headers without warnings, and to fix some issues.
27 |
28 | # Helper functions to avoid duplicate code.
29 | function(_add_define definition)
30 | list(APPEND _GUC_CXX_DEFINITIONS "-D${definition}")
31 | set(_GUC_CXX_DEFINITIONS ${_GUC_CXX_DEFINITIONS} PARENT_SCOPE)
32 | endfunction()
33 |
34 | function(_disable_warning flag)
35 | if(MSVC)
36 | list(APPEND _GUC_CXX_WARNING_FLAGS "/wd${flag}")
37 | else()
38 | list(APPEND _GUC_CXX_WARNING_FLAGS "-Wno-${flag}")
39 | endif()
40 | set(_GUC_CXX_WARNING_FLAGS ${_GUC_CXX_WARNING_FLAGS} PARENT_SCOPE)
41 | endfunction()
42 |
43 | # Compiler-specific defines and warnings.
44 | if(MSVC)
45 | # Enable exception handling.
46 | set(_GUC_CXX_FLAGS "${_GUC_CXX_FLAGS} /EHsc")
47 |
48 | # Standards compliant.
49 | set(_GUC_CXX_FLAGS "${_GUC_CXX_FLAGS} /Zc:rvalueCast /Zc:strictStrings")
50 |
51 | # The /Zc:inline option strips out the "arch_ctor_" symbols used for
52 | # library initialization by ARCH_CONSTRUCTOR starting in Visual Studio 2019,
53 | # causing release builds to fail. Disable the option for this and later
54 | # versions.
55 | #
56 | # For more details, see:
57 | # https://developercommunity.visualstudio.com/content/problem/914943/zcinline-removes-extern-symbols-inside-anonymous-n.html
58 | if(MSVC_VERSION GREATER_EQUAL 1920)
59 | set(_GUC_CXX_FLAGS "${_GUC_CXX_FLAGS} /Zc:inline-")
60 | else()
61 | set(_GUC_CXX_FLAGS "${_GUC_CXX_FLAGS} /Zc:inline")
62 | endif()
63 |
64 | # Turn on all but informational warnings.
65 | set(_GUC_CXX_FLAGS "${_GUC_CXX_FLAGS} /W3")
66 |
67 | # truncation from 'double' to 'float' due to matrix and vector classes in `Gf`
68 | _disable_warning("4244")
69 | _disable_warning("4305")
70 |
71 | # conversion from size_t to int. While we don't want this enabled
72 | # it's in the Python headers. So all the Python wrap code is affected.
73 | _disable_warning("4267")
74 |
75 | # no definition for inline function
76 | # this affects Glf only
77 | _disable_warning("4506")
78 |
79 | # 'typedef ': ignored on left of '' when no variable is declared
80 | # XXX: figure out why we need this
81 | _disable_warning("4091")
82 |
83 | # qualifier applied to function type has no meaning; ignored
84 | # tbb/parallel_for_each.h
85 | _disable_warning("4180")
86 |
87 | # '<<': result of 32-bit shift implicitly converted to 64 bits
88 | # tbb/enumerable_thread_specific.h
89 | _disable_warning("4334")
90 |
91 | # Disable warning C4996 regarding fopen(), strcpy(), etc.
92 | _add_define("_CRT_SECURE_NO_WARNINGS")
93 |
94 | # Disable warning C4996 regarding unchecked iterators for std::transform,
95 | # std::copy, std::equal, et al.
96 | _add_define("_SCL_SECURE_NO_WARNINGS")
97 |
98 | # Make sure WinDef.h does not define min and max macros which
99 | # will conflict with std::min() and std::max().
100 | _add_define("NOMINMAX")
101 |
102 | # Needed to prevent YY files trying to include unistd.h
103 | # (which doesn't exist on Windows)
104 | _add_define("YY_NO_UNISTD_H")
105 |
106 | # Exclude headers from unnecessary Windows APIs to improve build
107 | # times and avoid annoying conflicts with macros defined in those
108 | # headers.
109 | _add_define("WIN32_LEAN_AND_MEAN")
110 |
111 | # These files require /bigobj compiler flag
112 | # Vt/arrayPyBuffer.cpp
113 | # Usd/crateFile.cpp
114 | # Usd/stage.cpp
115 | # Until we can set the flag on a per file basis, we'll have to enable it
116 | # for all translation units.
117 | set(_GUC_CXX_FLAGS "${_GUC_CXX_FLAGS} /bigobj")
118 |
119 | # Enable PDB generation.
120 | set(_GUC_CXX_FLAGS "${_GUC_CXX_FLAGS} /Zi")
121 |
122 | # Enable multiprocessor builds.
123 | set(_GUC_CXX_FLAGS "${_GUC_CXX_FLAGS} /MP")
124 | set(_GUC_CXX_FLAGS "${_GUC_CXX_FLAGS} /Gm-")
125 |
126 | # Disable Boost and TBB auto-linking, as this causes errors when building
127 | # debug guc against their release versions. No direct linking is involved, anyway.
128 | _add_define("BOOST_ALL_NO_LIB")
129 | _add_define("__TBB_NO_IMPLICIT_LINKAGE")
130 | else()
131 | # Enable all warnings.
132 | set(_GUC_CXX_FLAGS "${_GUC_CXX_FLAGS} -Wall -Wformat-security")
133 |
134 | # We use hash_map, suppress deprecation warning.
135 | _disable_warning("deprecated")
136 | _disable_warning("deprecated-declarations")
137 | endif()
138 |
139 | # General defines.
140 | if(CMAKE_BUILD_TYPE STREQUAL "Debug")
141 | _add_define(BUILD_OPTLEVEL_DEV)
142 | endif()
143 |
144 | # Convert lists to strings.
145 | set(_GUC_CXX_FLAGS ${_GUC_CXX_FLAGS} ${_GUC_CXX_WARNING_FLAGS})
146 | string(REPLACE ";" " " GUC_CXX_FLAGS "${_GUC_CXX_FLAGS}")
147 | string(REPLACE ";" " " GUC_CXX_DEFINITIONS "${_GUC_CXX_DEFINITIONS}")
148 |
--------------------------------------------------------------------------------
/cmake/gucConfig.cmake.in:
--------------------------------------------------------------------------------
1 | include(CMakeFindDependencyMacro)
2 |
3 | find_package(pxr CONFIG REQUIRED)
4 | find_package(MaterialX REQUIRED HINTS ${pxr_DIR})
5 | find_package(OpenImageIO REQUIRED HINTS ${pxr_DIR})
6 |
7 | include("${CMAKE_CURRENT_LIST_DIR}/@CMAKE_PROJECT_NAME@Targets.cmake")
8 | check_required_components(@CMAKE_PROJECT_NAME@)
9 |
--------------------------------------------------------------------------------
/docs/Ecosystem_Limitations.md:
--------------------------------------------------------------------------------
1 | # Ecosystem Limitations
2 |
3 | This document describes past and current OpenUSD/MaterialX/Hydra/Storm issues.
4 |
5 | > [!IMPORTANT]
6 | > Last updated with USD v25.02 and MaterialX 1.38.10
7 |
8 | ## MaterialX
9 |
10 | #### Limitations of the MaterialX shading model and the glTF PBR implementation
11 | As described in the [initial PR](https://github.com/AcademySoftwareFoundation/MaterialX/pull/861), certain properties of the glTF shading model do not map to MaterialX:
12 | * _thickness_: whether the material is thin-walled (thickness = 0) or volumetric (thickness > 0). Currently, only the volumetric case is implemented, and the input is ignored. (Issue [#1936](https://github.com/AcademySoftwareFoundation/MaterialX/issues/1936))
13 | * _occlusion_: this concept does not exist in MaterialX. SSAO or ray traced ambient occlusion may be used instead.
14 |
15 | #### Hardcoded `` node handedness
16 | glTF tangents have four components, with the last component denoting the handedness of the calculated bitangent.
17 | For normal mapping, we would ideally want to use MaterialX's `` node, however this node makes a hard assumption on the handedness of the bitangent.
18 | A _bitangent_ input has been proposed (Issue [#945](https://github.com/AcademySoftwareFoundation/MaterialX/issues/945)), but as a fallback, guc injects a flattened, patched version of this node.
19 |
20 | > [!NOTE]
21 | > Resolved in MaterialX 1.39
22 |
23 |
24 | ## MaterialX in USD
25 |
26 | #### Implicit vs. explicit primvar reading
27 |
28 | MaterialX shading networks require geometric properties (normals, texture coordinates, tangents). These are stored as USD primvars, and the renderer needs to provide them accordingly.
29 |
30 | As an *implicit* mapping relies on non-standardized heuristics that do not cover multiple texture sets, vertex colors or tangents, guc always uses *explicit* reading of these primvars using `` nodes.
31 | However, this functionality may not be supported by some render delegates.
32 |
33 | #### Image color spaces are not copied from Hydra shading networks to MaterialX documents
34 |
35 | The HdMtlx USD library provides functions which aid Hydra render delegates in supporting shading networks via MaterialX. The `HdMtlxCreateMtlxDocumentFromHdNetwork` function is essential in that, as it translates Hydra shading networks to MaterialX documents. These documents are then processed by the renderer, and potentially used for code generation using a MaterialXGenShader backend.
36 |
37 | One outstanding problem with the translation process is that color spaces are not copied from the Hydra network to the MaterialX document. This means that, for example, normal maps may be assumed to be encoded in the sRGB color space, leading to incorrect shading. (Issue [#1523](https://github.com/PixarAnimationStudios/USD/issues/1523))
38 |
39 | This issue affects a number of render delegates, including Pixar's [HdStorm](https://github.com/PixarAnimationStudios/USD/blob/0c7b9a95f155c221ff7df9270a39a52e3b23af8b/pxr/imaging/hdSt/materialXFilter.cpp#L877).
40 |
41 | > [!NOTE]
42 | > Resolved in OpenUSD 23.11
43 |
44 |
45 | ## MaterialX in HdStorm
46 |
47 | #### Greyscale images are incorrectly handled
48 | The behaviour does not match MaterialX. It has been logged as [MaterialX #2257](https://github.com/AcademySoftwareFoundation/MaterialX/issues/2257) for clarification.
49 |
50 | #### Texture-coordinate related regression in USD v24.03+
51 | Issue [#3048](https://github.com/PixarAnimationStudios/OpenUSD/issues/3048) currently affects [multiple cases](https://github.com/pablode/guc-tests/commit/695f6dcc58c5b08b1d7b689dc1dc0c2d4305f154?diff=split&w=0) of the guc test suite.
52 |
53 | > [!NOTE]
54 | > Resolved in OpenUSD 24.11
55 |
56 | #### 'edf' shader compilation error on Metal
57 | Issue [#3049](https://github.com/PixarAnimationStudios/OpenUSD/issues/3049).
58 |
59 | > [!NOTE]
60 | > Resolved in OpenUSD 24.08
61 |
62 | #### One and two channel sRGB texture formats are unsupported
63 | Converted assets may use such textures, but HdStorm is not able to render them. ([Source code](https://github.com/PixarAnimationStudios/USD/blob/3abc46452b1271df7650e9948fef9f0ce602e3b2/pxr/imaging/hdSt/textureUtils.cpp#L341-L345))
64 |
65 | > [!NOTE]
66 | > Resolved in OpenUSD 24.08
67 |
68 | #### Incorrect automatic sRGB image detection and decoding
69 | In addition to the HdMtlx color space issue above, a heuristic defined by the [UsdPreviewSurface spec](https://graphics.pixar.com/usd/release/spec_usdpreviewsurface.html#texture-reader) is used to determine whether an image is to be interpreted as sRGB or not. This heuristic can not be disabled and incorrectly classifies normal maps and occlusion-roughness-metalness textures commonly used with glTF. (Issue [#1878](https://github.com/PixarAnimationStudios/USD/issues/1878))
70 |
71 | > [!NOTE]
72 | > Resolved in OpenUSD 23.11
73 |
74 | #### Hardcoded transparent geometry detection
75 | HdStorm is a rasterizer and therefore handles translucent geometry differently than solid geometry. In order to detect whether a geometry's material is translucent or not, a heuristic is used. However, it is not yet adjusted to the MaterialX glTF PBR. (Issue [#1882](https://github.com/PixarAnimationStudios/USD/issues/1882))
76 |
77 | > [!NOTE]
78 | > Resolved in OpenUSD 24.08
79 |
80 | #### Materials with 1x1 images are not rendered correctly
81 | Issue [#2140](https://github.com/PixarAnimationStudios/USD/issues/2140).
82 |
83 | > [!NOTE]
84 | > Resolved in MaterialX 1.38.8 / OpenUSD 24.05
85 |
86 | #### EDF shader compilation error on Metal
87 | Issue [#3049](https://github.com/PixarAnimationStudios/OpenUSD/issues/3049).
88 |
89 | > [!NOTE]
90 | > Resolved in OpenUSD 24.08
91 |
92 | #### Tangent frame is not aligned to the UV space
93 | This causes normal maps to render incorrectly, for instance for the Open Chess Set. (Issue [#2255](https://github.com/PixarAnimationStudios/OpenUSD/issues/2255))
94 |
95 | > [!NOTE]
96 | > Resolved in OpenUSD 24.03
97 |
98 | ## Apple RealityKit and SceneKit compatibility
99 |
100 | Apple's RealityKit (used for AR Quick Look) and SceneKit renderers only [support a subset](https://developer.apple.com/documentation/realitykit/validating-usd-files) of USD's features. guc makes no compatibility efforts, and converted assets may not be displayed correctly.
101 |
--------------------------------------------------------------------------------
/docs/Structure_Mapping.md:
--------------------------------------------------------------------------------
1 | # Structure Mapping
2 |
3 | guc's conversion process produces self-containing components, which can then be
4 | composed as part of a larger assembly.
5 | A consumer may modify the components as needed, however artist-friendliness
6 | (layering, shading network complexity) can not be ensured due to the lack of usage annotations
7 | and the need to support the space of legal glTF constructions.
8 | The converted assets are hence intended to be used "as-is", for instance as leaf
9 | nodes in a USD-based pipeline, and not as intermediate products in a content-authoring pipeline.
10 |
11 |
12 | ## Case Study: A golden triangle
13 |
14 | In the following example, we're going to see how guc translates the golden triangle from
15 | [Chapter 11](https://github.khronos.org/glTF-Tutorials/gltfTutorial/gltfTutorial_011_SimpleMaterial.html) of Khronos's glTF tutorial.
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | ```json
25 | {
26 | "asset" : { "version" : "2.0" },
27 | "scene": 0,
28 | "scenes" : [ { "nodes" : [ 0 ] } ],
29 | "nodes" : [ { "mesh" : 0 } ],
30 | "meshes" : [
31 | {
32 | "primitives" : [ {
33 | "attributes" : {
34 | "POSITION" : 1
35 | },
36 | "indices" : 0,
37 | "material" : 0
38 | } ]
39 | }
40 | ],
41 | "materials" : [
42 | {
43 | "pbrMetallicRoughness": {
44 | "baseColorFactor": [ 1.000, 0.766, 0.336, 1.0 ],
45 | "metallicFactor": 0.5,
46 | "roughnessFactor": 0.1
47 | }
48 | }
49 | ],
50 | "..."
51 | }
52 | ```
53 |
54 |
55 |
56 |
57 |
58 |
59 | A render of the golden glTF triangle (left), and its corresponding JSON (right).
60 |
61 |
62 | ### Asset and Scenes
63 |
64 | Upon conversion, guc creates a root prim, `/Asset`, under which scenes and materials reside.
65 | Scenes contain Xform, mesh, camera and light prims.
66 |
67 | For the "golden triangle" example above, following high-level scene structure is generated:
68 | ```
69 | #usda 1.0
70 | (
71 | defaultPrim = "Asset"
72 | doc = "Converted from glTF with guc 0.2"
73 | metersPerUnit = 1
74 | upAxis = "Y"
75 | )
76 |
77 | def Xform "Asset" (
78 | customData = {
79 | string version = "2.0"
80 | }
81 | kind = "component"
82 | )
83 | {
84 | def Xform "Scenes"
85 | {
86 | def Xform "scene"
87 | {
88 | # ...
89 | ```
90 |
91 | Only the default scene is authored as visible, and if no scenes exists, the glTF file is a _library_.
92 | guc then generates an asset-level `/Nodes` prim.
93 |
94 | ### Nodes
95 |
96 | Nodes are glTF graph elements with an optional transformation, which can either contain a number of nodes, or a reference to a mesh, camera, or light. Nodes are translated to Xforms under the scene prim. Meshes, cameras and lights are "instanced" across scenes through references, but instancing of node trees does not exist.
97 |
98 | ### Meshes
99 |
100 | The glTF buffer, bufferView and accessor concepts are reduced to a single prim,
101 | in the case of the example above, following mesh prim:
102 |
103 | ```
104 | def Mesh "submesh" (
105 | prepend apiSchemas = ["MaterialBindingAPI"]
106 | )
107 | {
108 | float3[] extent = [(0, 0, 0), (1, 1, 0)]
109 | int[] faceVertexCounts = [3]
110 | int[] faceVertexIndices = [0, 1, 2]
111 | rel material:binding =
112 | rel material:binding:preview =
113 | normal3f[] normals = [(0, 0, 1), (0, 0, 1), (0, 0, 1)] (
114 | customData = {
115 | dictionary guc = {
116 | bool generated = 1
117 | }
118 | }
119 | interpolation = "vertex"
120 | )
121 | point3f[] points = [(0, 0, 0), (1, 0, 0), (0, 1, 0)]
122 | color3f[] primvars:displayColor = [(1, 0.766, 0.336)] (
123 | customData = {
124 | dictionary guc = {
125 | bool generated = 1
126 | }
127 | }
128 | interpolation = "constant"
129 | )
130 | uniform token subdivisionScheme = "none"
131 | }
132 | ```
133 |
134 | Depending on material parameters and available accessors, guc creates following primvars:
135 |
136 | Name | Description
137 | --- | ---
138 | color[N] | Vertex color set N
139 | displayColor | Display color (constant or per-vertex)
140 | displayOpacity | Display opacity (constant or per-vertex)
141 | opacity[N] | Vertex opacity set N
142 | st[N] | Texture coordinate set N
143 | tangents | Three-component tangent vectors
144 | bitangentSigns | Bitangent handedness
145 |
146 | Additionally, a material binding relationship is always authored on the prim and its
147 | overrides, potentially binding a default material.
148 |
149 | ### Materials
150 |
151 | guc authors UsdPreviewSurface and MaterialX material collections on an asset-level `/Materials` prim.
152 | The exact path is motivated by the behaviour of UsdMtlx, which implicitly creates prim scopes.
153 |
154 | The generated UsdPreviewSurface material for the example above looks like this:
155 | ```
156 | def Material "mat"
157 | {
158 | token outputs:surface.connect =
159 |
160 | def Shader "node"
161 | {
162 | uniform token info:id = "UsdPreviewSurface"
163 | float3 inputs:diffuseColor = (1, 0.766, 0.336)
164 | float inputs:metallic = 0.5
165 | float inputs:roughness = 0.1
166 | token outputs:surface
167 | }
168 | }
169 | ```
170 |
171 | While the generated MaterialX document is approximately the following:
172 | ```xml
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 | ```
186 |
187 |
188 | ## MaterialX material translation
189 |
190 | guc makes use of MaterialX's [glTF PBR implementation](https://github.com/AcademySoftwareFoundation/MaterialX/blob/f66296b25e095aad135834aa8e59c13282adcdd7/libraries/bxdf/gltf_pbr.mtlx), with the shading logic being maintained and developed by MaterialX contributors.
191 |
192 | guc's leftover responsibility is to instantiate the `` node and to populate its inputs.
193 |
194 | For example, if [Chapter 3.9.2](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#metallic-roughness-material)
195 | of the glTF specification defines the base color to consist of a texture value, a factor and the vertex color,
196 | the following node graph is generated:
197 | ```mermaid
198 | flowchart RL;
199 | A["<!-- baseColorFactor --> <constant> "];
200 | B["<!-- COLOR_0 --> <geompropvalue> "];
201 | C["<multiply> "];
202 |
203 | D["<!-- baseColorTexture --> <image> "];
204 | E["<!-- channel extraction --> "];
205 | F["<!-- opt. colorspace transformation --> "];
206 | G["<multiply> "];
207 |
208 | H["<gltf_pbr> <!-- base_color input --> </gltf_pbr> "];
209 |
210 | A-->C
211 | B-->C
212 | C-->G
213 |
214 | D-->E
215 | E-->F
216 | F-->G
217 |
218 | G-->H
219 |
220 | classDef default fill:#fff,stroke:#999
221 | ```
222 | Depending on conversion options, the graph may contain additional color space transformation nodes.
223 |
224 | ### Explicit tangents
225 | Normal mapped assets require tangents generated by the MikkTSpace algorithm.
226 | These are four-component vectors, with the last component specifiying the handedness of the bitangent.
227 |
228 | guc constructs the tangent space from two authored primvars, _tangents_ and _bitangentSigns_, which are
229 | read directly into a custom normal map node implementation, accounting for a limitation
230 | of MaterialX's `` node (see [Ecosystem Limitations](Ecosystem_Limitations.md)).
231 |
232 |
233 | ## Future improvements
234 |
235 | There are a number of improvements that can be made to the mapping process.
236 |
237 | For instance, the MaterialX graph complexity can be reduced with the introduction of helper nodes.
238 | Resulting USD assets could make use of payloads, and in general, incremental changes
239 | motivated by the [USD Asset WG Structure Guidelines](https://github.com/pablode/usd-wg-assets/blob/main/docs/asset-structure-guidelines.md) are to be expected.
240 |
241 | Lastly, user feedback is welcome in ensuring that assets conform to best practices.
242 |
--------------------------------------------------------------------------------
/extern/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # cgltf
2 | add_library(cgltf INTERFACE)
3 | set_target_properties(cgltf PROPERTIES LINKER_LANGUAGE C)
4 | target_include_directories(cgltf INTERFACE cgltf)
5 |
6 | # MikkTSpace
7 | add_library(MikkTSpace STATIC MikkTSpace/mikktspace.c MikkTSpace/mikktspace.h)
8 | set_target_properties(MikkTSpace PROPERTIES LINKER_LANGUAGE C)
9 | target_include_directories(MikkTSpace
10 | PUBLIC $
11 | PUBLIC $
12 | )
13 |
14 | # stb_image
15 | add_library(stb_image INTERFACE)
16 | set_target_properties(stb_image PROPERTIES LINKER_LANGUAGE C)
17 | target_include_directories(stb_image INTERFACE stb_image)
18 |
19 | # cargs
20 | add_subdirectory(cargs)
21 |
22 | # meshopt
23 | add_subdirectory(meshoptimizer)
24 |
--------------------------------------------------------------------------------
/extern/stb_image/LICENSE:
--------------------------------------------------------------------------------
1 | This software is available under 2 licenses -- choose whichever you prefer.
2 | ------------------------------------------------------------------------------
3 | ALTERNATIVE A - MIT License
4 | Copyright (c) 2017 Sean Barrett
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9 | of the Software, and to permit persons to whom the Software is furnished to do
10 | so, subject to the following conditions:
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 | ------------------------------------------------------------------------------
21 | ALTERNATIVE B - Public Domain (www.unlicense.org)
22 | This is free and unencumbered software released into the public domain.
23 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
24 | software, either in source code form or as a compiled binary, for any purpose,
25 | commercial or non-commercial, and by any means.
26 | In jurisdictions that recognize copyright laws, the author or authors of this
27 | software dedicate any and all copyright interest in the software to the public
28 | domain. We make this dedication for the benefit of the public at large and to
29 | the detriment of our heirs and successors. We intend this dedication to be an
30 | overt act of relinquishment in perpetuity of all present and future rights to
31 | this software under copyright law.
32 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
36 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
37 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
38 |
--------------------------------------------------------------------------------
/preview_glTFSampleViewer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablode/guc/d12dc042ee5e48f9e5a947225475495691d7b452/preview_glTFSampleViewer.png
--------------------------------------------------------------------------------
/preview_hdStorm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablode/guc/d12dc042ee5e48f9e5a947225475495691d7b452/preview_hdStorm.png
--------------------------------------------------------------------------------
/src/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | add_subdirectory(libguc)
2 |
3 | if(GUC_BUILD_EXECUTABLE)
4 | add_subdirectory(guc)
5 | endif()
6 |
--------------------------------------------------------------------------------
/src/guc/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | add_executable(guc main.c)
2 |
3 | set_target_properties(
4 | guc
5 | PROPERTIES
6 | LINKER_LANGUAGE C
7 | C_STANDARD 11
8 | C_STANDARD_REQUIRED ON
9 | C_EXTENSIONS OFF
10 | )
11 |
12 | target_link_libraries(
13 | guc
14 | PRIVATE
15 | libguc
16 | cargs
17 | )
18 |
19 | set_target_properties(
20 | guc
21 | PROPERTIES
22 | # Search libguc in executable dir on MacOS
23 | BUILD_RPATH_USE_ORIGIN ON
24 | )
25 |
26 | # Generate C header file with a string that contains the license text
27 | file(READ "${PROJECT_SOURCE_DIR}/LICENSE" LICENSE_TEXT)
28 | string(REGEX REPLACE "\"" "\\\\\"" LICENSE_TEXT "${LICENSE_TEXT}")
29 | # Instead of concatenating a single string with backslashes, we concatenate
30 | # multiple strings in order to prevent MSVC from throwing C2026: "string too big".
31 | # This is why we manually escape quotation marks and do not use configure_file's
32 | # ESCAPE_QUOTES argument.
33 | string(REGEX REPLACE "\n" "\\\\n\" \\\\\\n \"" LICENSE_TEXT "${LICENSE_TEXT}")
34 | configure_file(license.h.in license.h @ONLY)
35 |
36 | target_include_directories(
37 | guc
38 | PRIVATE
39 | # Allow discovery of generated license.h in build dir
40 | ${CMAKE_CURRENT_BINARY_DIR}
41 | )
42 |
43 | install(
44 | TARGETS guc
45 | COMPONENT guc
46 | )
47 |
--------------------------------------------------------------------------------
/src/guc/license.h.in:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2024 Pablo Delgado Krämer
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | #pragma once
18 |
19 | const char* license_text = "@LICENSE_TEXT@";
20 |
--------------------------------------------------------------------------------
/src/guc/main.c:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Pablo Delgado Krämer
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | #include
18 | #include
19 | #include
20 | #include
21 |
22 | #include
23 |
24 | #include
25 |
26 | #include "license.h"
27 |
28 | static struct cag_option cmd_options[] = {
29 | {
30 | .identifier = 'm',
31 | .access_letters = "m",
32 | .access_name = "emit-mtlx",
33 | .value_name = NULL,
34 | .description = "Emit MaterialX materials in addition to UsdPreviewSurfaces"
35 | },
36 | {
37 | .identifier = 'u',
38 | .access_letters = "u",
39 | .access_name = "mtlx-as-usdshade",
40 | .value_name = NULL,
41 | .description = "Convert and inline MaterialX materials into the USD layer using UsdMtlx"
42 | },
43 | {
44 | .identifier = 'v',
45 | .access_letters = "v",
46 | .access_name = "default-material-variant",
47 | .value_name = "",
48 | .description = "Index of the material variant that is selected by default"
49 | },
50 | {
51 | .identifier = 'l',
52 | .access_letters = "l",
53 | .access_name = "licenses",
54 | .value_name = NULL,
55 | .description = "Print the license of guc and third-party libraries"
56 | },
57 | {
58 | .identifier = 'h',
59 | .access_letters = "h",
60 | .access_name = "help",
61 | .value_name = NULL,
62 | .description = "Show the command help"
63 | }
64 | };
65 |
66 | int main(int argc, char* argv[])
67 | {
68 | struct guc_options options = {
69 | .emit_mtlx = false,
70 | .mtlx_as_usdshade = false,
71 | .default_material_variant = 0
72 | };
73 |
74 | cag_option_context context;
75 | cag_option_init(&context, cmd_options, CAG_ARRAY_SIZE(cmd_options), argc, argv);
76 | while (cag_option_fetch(&context))
77 | {
78 | switch (cag_option_get_identifier(&context))
79 | {
80 | case 'm':
81 | options.emit_mtlx = true;
82 | break;
83 | case 'u':
84 | options.mtlx_as_usdshade = true;
85 | break;
86 | case 'v': {
87 | const char* value = cag_option_get_value(&context);
88 | options.default_material_variant = atoi(value); // fall back to 0 on error
89 | break;
90 | }
91 | case 'l': {
92 | printf("%s\n", license_text);
93 | return EXIT_SUCCESS;
94 | }
95 | case 'h': {
96 | printf("guc %s - glTF to USD converter\n\n", GUC_VERSION_STRING);
97 | printf("Usage: guc [options] [--] \n\n");
98 | printf("Options:\n");
99 | cag_option_print(cmd_options, CAG_ARRAY_SIZE(cmd_options), stdout);
100 | return EXIT_SUCCESS;
101 | }
102 | case '?': {
103 | cag_option_print_error(&context, stderr);
104 | return EXIT_FAILURE;
105 | }
106 | }
107 | }
108 |
109 | int param_index = cag_option_get_index(&context);
110 |
111 | if (param_index < argc && !strcmp(argv[param_index], "--"))
112 | {
113 | param_index++;
114 | }
115 |
116 | if (param_index >= argc)
117 | {
118 | fprintf(stderr, "Missing positional argument .\n");
119 | return EXIT_FAILURE;
120 | }
121 | if ((param_index + 1) >= argc)
122 | {
123 | fprintf(stderr, "Missing positional argument .\n");
124 | return EXIT_FAILURE;
125 | }
126 | if ((argc - param_index) != 2)
127 | {
128 | fprintf(stderr, "Too many positional arguments.\n");
129 | return EXIT_FAILURE;
130 | }
131 |
132 | const char* gltf_path = argv[param_index];
133 | const char* usd_path = argv[param_index + 1];
134 |
135 | bool result = guc_convert(gltf_path, usd_path, &options);
136 |
137 | return result ? EXIT_SUCCESS : EXIT_FAILURE;
138 | }
139 |
--------------------------------------------------------------------------------
/src/libguc/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | #
2 | # In this CMakeLists.txt file, two targets are defined:
3 | # 1. libguc, a library with a USD-independent C-API
4 | # 2. usdGlTF, the USD SDF plugin that gets installed as a DLL only
5 | #
6 | # Both projects share most source files and dependencies. They differ in some
7 | # additionally included file, their build config, installation properties and
8 | # whether a header include dir is installed, or not.
9 | #
10 |
11 | set(LIBGUC_SHARED_SRCS
12 | src/cgltf_util.h
13 | src/cgltf_util.cpp
14 | src/usdpreviewsurface.h
15 | src/usdpreviewsurface.cpp
16 | src/materialx.h
17 | src/materialx.cpp
18 | src/mesh.h
19 | src/mesh.cpp
20 | src/naming.h
21 | src/naming.cpp
22 | src/converter.h
23 | src/converter.cpp
24 | src/debugCodes.h
25 | src/debugCodes.cpp
26 | src/image.h
27 | src/image.cpp
28 | )
29 |
30 | set(LIBGUC_PUBLIC_HEADERS
31 | include/guc.h
32 | )
33 |
34 | set(LIBGUC_DEFINES
35 | GUC_VERSION_MAJOR=${CMAKE_PROJECT_VERSION_MAJOR}
36 | GUC_VERSION_MINOR=${CMAKE_PROJECT_VERSION_MINOR}
37 | GUC_VERSION_PATCH=${CMAKE_PROJECT_VERSION_PATCH}
38 | GUC_VERSION_STRING="${CMAKE_PROJECT_VERSION}"
39 | )
40 |
41 | if(TARGET usd_ms)
42 | set(LIBGUC_USD_LIBS usd_ms)
43 | else()
44 | set(LIBGUC_USD_LIBS usd usdGeom usdLux usdShade usdUtils usdMtlx)
45 | endif()
46 |
47 | set(LIBGUC_SHARED_LIBRARIES
48 | # Header-only library without link dependencies --
49 | # we can get away with not installing the target.
50 | $
51 | MikkTSpace
52 | MaterialXCore
53 | MaterialXFormat
54 | meshoptimizer
55 | ${LIBGUC_USD_LIBS}
56 | )
57 |
58 | if(OpenImageIO_FOUND)
59 | list(APPEND LIBGUC_DEFINES "GUC_USE_OIIO")
60 | list(APPEND LIBGUC_SHARED_LIBRARIES OpenImageIO::OpenImageIO)
61 | else()
62 | list(APPEND LIBGUC_SHARED_LIBRARIES $)
63 | endif()
64 |
65 | if(draco_FOUND)
66 | list(APPEND LIBGUC_DEFINES "GUC_USE_DRACO")
67 | list(APPEND LIBGUC_SHARED_LIBRARIES ${DRACO_LIBRARIES})
68 | endif()
69 |
70 | #
71 | # libguc
72 | #
73 | add_library(
74 | libguc
75 | ${LIBGUC_SHARED_SRCS}
76 | ${LIBGUC_PUBLIC_HEADERS}
77 | src/guc.cpp
78 | )
79 |
80 | target_link_libraries(libguc PRIVATE ${LIBGUC_SHARED_LIBRARIES})
81 |
82 | target_include_directories(libguc
83 | PUBLIC
84 | $
85 | $
86 | )
87 |
88 | if(draco_FOUND)
89 | target_include_directories(libguc PRIVATE ${DRACO_INCLUDE_DIRS})
90 | endif()
91 |
92 | set_target_properties(
93 | libguc
94 | PROPERTIES
95 | PUBLIC_HEADER "${LIBGUC_PUBLIC_HEADERS}"
96 | # We don't want 'liblibguc' on Linux/Unix
97 | PREFIX ""
98 | # fPIC for static linking because the USD libraries we use are built with it
99 | POSITION_INDEPENDENT_CODE ON
100 | )
101 |
102 | if(WIN32 AND BUILD_SHARED_LIBS)
103 | set_target_properties(libguc PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
104 | endif()
105 |
106 | target_compile_definitions(
107 | libguc
108 | PUBLIC
109 | ${LIBGUC_DEFINES}
110 | PRIVATE
111 | # Workaround for https://github.com/PixarAnimationStudios/USD/issues/1471#issuecomment-799813477
112 | "$<$,$>:TBB_USE_DEBUG>"
113 | )
114 |
115 | # Installation and cmake config file
116 | install(TARGETS libguc MikkTSpace EXPORT gucTargets COMPONENT libguc)
117 |
118 | include(CMakePackageConfigHelpers)
119 | write_basic_package_version_file(
120 | gucConfigVersion.cmake
121 | VERSION ${PACKAGE_VERSION}
122 | COMPATIBILITY AnyNewerVersion
123 | )
124 | install(EXPORT gucTargets
125 | FILE gucTargets.cmake
126 | DESTINATION lib/cmake/guc
127 | COMPONENT libguc
128 | )
129 |
130 | configure_file("${PROJECT_SOURCE_DIR}/cmake/gucConfig.cmake.in" gucConfig.cmake @ONLY)
131 | install(
132 | FILES "${CMAKE_CURRENT_BINARY_DIR}/gucConfig.cmake"
133 | "${CMAKE_CURRENT_BINARY_DIR}/gucConfigVersion.cmake"
134 | DESTINATION lib/cmake/guc
135 | COMPONENT libguc
136 | )
137 |
138 | #
139 | # usdGlTF
140 | #
141 | if(GUC_BUILD_USDGLTF)
142 | add_library(
143 | usdGlTF SHARED
144 | ${LIBGUC_SHARED_SRCS}
145 | src/fileFormat.h
146 | src/fileFormat.cpp
147 | )
148 |
149 | target_link_libraries(
150 | usdGlTF
151 | PRIVATE
152 | ${LIBGUC_SHARED_LIBRARIES}
153 | )
154 |
155 | if(TARGET usd_ms)
156 | set(LIBGUC_SDF_LIB usd_ms)
157 | else()
158 | set(LIBGUC_SDF_LIB sdf)
159 | endif()
160 |
161 | target_link_libraries(
162 | usdGlTF
163 | PUBLIC
164 | ${LIBGUC_SDF_LIB}
165 | )
166 |
167 | target_include_directories(usdGlTF PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
168 |
169 | if(draco_FOUND)
170 | target_include_directories(usdGlTF PRIVATE ${DRACO_INCLUDE_DIRS})
171 | endif()
172 |
173 | target_compile_definitions(
174 | usdGlTF
175 | PRIVATE
176 | ${LIBGUC_DEFINES}
177 | # Workaround for https://github.com/PixarAnimationStudios/USD/issues/1471#issuecomment-799813477
178 | "$<$,$>:TBB_USE_DEBUG>"
179 | )
180 |
181 | # We don't want 'liblibguc' on Linux/Unix
182 | set_target_properties(usdGlTF PROPERTIES PREFIX "")
183 |
184 | # Is installed to "plugins/usd" dir; remove the default "lib" folder prefix.
185 | install(TARGETS usdGlTF COMPONENT usdGlTF DESTINATION ".")
186 |
187 | set(PLUGINFO_PATH "${CMAKE_CURRENT_BINARY_DIR}/plugInfo.json")
188 | set(PLUG_INFO_LIBRARY_PATH "../usdGlTF${CMAKE_SHARED_LIBRARY_SUFFIX}")
189 | configure_file(plugInfo.json.in "${PLUGINFO_PATH}" @ONLY)
190 |
191 | install(
192 | FILES "${PLUGINFO_PATH}"
193 | DESTINATION "usdGlTF/resources"
194 | COMPONENT usdGlTF
195 | )
196 | endif()
197 |
--------------------------------------------------------------------------------
/src/libguc/include/guc.h:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Pablo Delgado Krämer
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | #pragma once
18 |
19 | #ifdef __cplusplus
20 | extern "C" {
21 | #else
22 | #include
23 | #endif
24 |
25 | struct guc_options
26 | {
27 | // Generate and reference a MaterialX document containing an accurate translation
28 | // of the glTF materials. The document is serialized to a file if UsdShade inlining
29 | // is not active.
30 | bool emit_mtlx;
31 |
32 | // Parse the generated MaterialX document with UsdMtlx to a UsdShade representation
33 | // and inline it into the USD file. Note that information will be discarded as not
34 | // all MaterialX concepts can be encoded in UsdShade:
35 | // https://graphics.pixar.com/usd/release/api/usd_mtlx_page_front.html
36 | // Files generated without this option may be better supported by future USD
37 | // versions.
38 | bool mtlx_as_usdshade;
39 |
40 | // If the asset supports the KHR_materials_variants extension, select the material
41 | // variant at the given index by default.
42 | int default_material_variant;
43 | };
44 |
45 | bool guc_convert(const char* gltf_path,
46 | const char* usd_path,
47 | const struct guc_options* options);
48 |
49 | #ifdef __cplusplus
50 | }
51 | #endif
52 |
--------------------------------------------------------------------------------
/src/libguc/plugInfo.json.in:
--------------------------------------------------------------------------------
1 | {
2 | "Plugins": [
3 | {
4 | "Info": {
5 | "Types": {
6 | "UsdGlTFFileFormat": {
7 | "bases": [
8 | "SdfFileFormat"
9 | ],
10 | "displayName": "glTF File Format",
11 | "extensions": [
12 | "gltf", "glb"
13 | ],
14 | "supportsWriting": false,
15 | "formatId": "gltf",
16 | "primary": true,
17 | "target": "usd"
18 | }
19 | },
20 | "SdfMetadata": {
21 | "emitMtlx": {
22 | "type": "bool",
23 | "default": "false",
24 | "displayGroup": "Core",
25 | "appliesTo": [ "layers" ],
26 | "documentation:": "Emit MaterialX materials in addition to UsdPreviewSurfaces."
27 | }
28 | }
29 | },
30 | "LibraryPath": "@PLUG_INFO_LIBRARY_PATH@",
31 | "Name": "usdGlTF",
32 | "ResourcePath": "resources",
33 | "Root": "..",
34 | "Type": "library"
35 | }
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/src/libguc/src/cgltf_util.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Pablo Delgado Krämer
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | #include "cgltf_util.h"
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 |
25 | #define CGLTF_IMPLEMENTATION
26 | #include
27 |
28 | #include
29 |
30 | #ifdef GUC_USE_DRACO
31 | #include
32 | #include
33 | #endif
34 |
35 | #include
36 | #include
37 |
38 | #include "debugCodes.h"
39 |
40 | using namespace PXR_NS;
41 |
42 | namespace detail
43 | {
44 | constexpr static const char* GLTF_EXT_MESHOPT_COMPRESSION_EXTENSION_NAME = "EXT_meshopt_compression";
45 | #ifdef GUC_USE_DRACO
46 | constexpr static const char* GLTF_KHR_DRACO_MESH_COMPRESSION_EXTENSION_NAME = "KHR_draco_mesh_compression";
47 | #endif
48 |
49 | bool extensionSupported(const char* name)
50 | {
51 | return strcmp(name, GLTF_EXT_MESHOPT_COMPRESSION_EXTENSION_NAME) == 0 ||
52 | #ifdef GUC_USE_DRACO
53 | strcmp(name, GLTF_KHR_DRACO_MESH_COMPRESSION_EXTENSION_NAME) == 0 ||
54 | #endif
55 | strcmp(name, "KHR_lights_punctual") == 0 ||
56 | strcmp(name, "KHR_materials_clearcoat") == 0 ||
57 | strcmp(name, "KHR_materials_emissive_strength") == 0 ||
58 | strcmp(name, "KHR_materials_ior") == 0 ||
59 | strcmp(name, "KHR_materials_iridescence") == 0 ||
60 | strcmp(name, "KHR_materials_pbrSpecularGlossiness") == 0 ||
61 | strcmp(name, "KHR_materials_sheen") == 0 ||
62 | strcmp(name, "KHR_materials_specular") == 0 ||
63 | strcmp(name, "KHR_materials_transmission") == 0 ||
64 | strcmp(name, "KHR_materials_unlit") == 0 ||
65 | strcmp(name, "KHR_materials_variants") == 0 ||
66 | strcmp(name, "KHR_materials_volume") == 0 ||
67 | strcmp(name, "KHR_mesh_quantization") == 0 ||
68 | strcmp(name, "KHR_texture_transform") == 0;
69 | }
70 |
71 | struct BufferHolder
72 | {
73 | std::unordered_map> map;
74 | };
75 |
76 | cgltf_result readFile(const cgltf_memory_options* memory_options,
77 | const cgltf_file_options* file_options,
78 | const char* path,
79 | cgltf_size* size,
80 | void** data)
81 | {
82 | TF_DEBUG(GUC).Msg("reading file %s\n", path);
83 |
84 | ArResolver& resolver = ArGetResolver();
85 | std::string identifier = resolver.CreateIdentifier(path);
86 | TF_DEBUG(GUC).Msg("normalized path to %s\n", identifier.c_str());
87 |
88 | ArResolvedPath resolvedPath = resolver.Resolve(identifier);
89 | if (!resolvedPath)
90 | {
91 | TF_RUNTIME_ERROR("unable to resolve %s", path);
92 | return cgltf_result_file_not_found;
93 | }
94 |
95 | std::string resolvedPathStr = resolvedPath.GetPathString();
96 | TF_DEBUG(GUC).Msg("resolved path to %s\n", resolvedPathStr.c_str());
97 |
98 | std::shared_ptr asset = resolver.OpenAsset(ArResolvedPath(path));
99 | if (!asset)
100 | {
101 | TF_RUNTIME_ERROR("unable to open asset %s", resolvedPathStr.c_str());
102 | return cgltf_result_file_not_found;
103 | }
104 |
105 | std::shared_ptr buffer = asset->GetBuffer();
106 | if (!buffer)
107 | {
108 | TF_RUNTIME_ERROR("unable to open buffer for %s", resolvedPathStr.c_str());
109 | return cgltf_result_io_error;
110 | }
111 |
112 | const char* bufferPtr = buffer.get();
113 | (*size) = asset->GetSize();
114 | (*data) = (void*) bufferPtr;
115 |
116 | auto bufferHolder = (BufferHolder*) file_options->user_data;
117 | bufferHolder->map[bufferPtr] = buffer;
118 |
119 | return cgltf_result_success;
120 | }
121 |
122 | void releaseFile(const cgltf_memory_options* memory_options,
123 | const cgltf_file_options* file_options,
124 | void* data)
125 | {
126 | auto bufferPtr = (const char*) data;
127 | auto bufferHolder = (BufferHolder*) file_options->user_data;
128 | bufferHolder->map.erase(bufferPtr);
129 | }
130 |
131 | #ifdef GUC_USE_DRACO
132 | template
133 | bool convertDracoVertexAttributes(const draco::PointAttribute* attribute,
134 | size_t vertexCount,
135 | uint8_t* result)
136 | {
137 | uint32_t elemSize = sizeof(T) * attribute->num_components();
138 |
139 | const static uint32_t MAX_COMPONENT_COUNT = 4;
140 |
141 | T elems[MAX_COMPONENT_COUNT];
142 | for (size_t i = 0; i < vertexCount; i++)
143 | {
144 | draco::PointIndex pointIndex(i);
145 | draco::AttributeValueIndex valueIndex = attribute->mapped_index(pointIndex);
146 |
147 | if (!attribute->ConvertValue(valueIndex, attribute->num_components(), elems))
148 | {
149 | return false;
150 | }
151 |
152 | uint32_t elemOffset = i * elemSize;
153 | memcpy(&result[elemOffset], &elems[0], elemSize);
154 | }
155 | return true;
156 | }
157 |
158 | bool convertDracoVertexAttributes(cgltf_component_type componentType,
159 | const draco::PointAttribute* attribute,
160 | size_t vertexCount,
161 | uint8_t* result)
162 | {
163 | bool success = false;
164 | switch (componentType)
165 | {
166 | case cgltf_component_type_r_8:
167 | success = convertDracoVertexAttributes(attribute, vertexCount, result);
168 | case cgltf_component_type_r_8u:
169 | success = convertDracoVertexAttributes(attribute, vertexCount, result);
170 | case cgltf_component_type_r_16:
171 | success = convertDracoVertexAttributes(attribute, vertexCount, result);
172 | case cgltf_component_type_r_16u:
173 | success = convertDracoVertexAttributes(attribute, vertexCount, result);
174 | case cgltf_component_type_r_32u:
175 | success = convertDracoVertexAttributes(attribute, vertexCount, result);
176 | case cgltf_component_type_r_32f:
177 | success = convertDracoVertexAttributes(attribute, vertexCount, result);
178 | break;
179 | default:
180 | break;
181 | }
182 | return success;
183 | }
184 |
185 | bool decompressDraco(cgltf_data* data)
186 | {
187 | using EncodedAttributes = std::map ;
188 |
189 | std::map attributeBuffersToDecompress;
190 |
191 | //
192 | // Phase 1: Collect draco buffers and attributes to decode. Multiple prims can reference the same buffer
193 | // with a disjunct or overlapping set of attributes, which is why we use the map/set construct.
194 | // We also overwrite the primitive indices data source which we fill in the second phase.
195 | //
196 | for (size_t i = 0; i < data->meshes_count; ++i)
197 | {
198 | const cgltf_mesh& mesh = data->meshes[i];
199 |
200 | for (size_t j = 0; j < mesh.primitives_count; j++)
201 | {
202 | const cgltf_primitive& primitive = mesh.primitives[j];
203 | if (!primitive.has_draco_mesh_compression)
204 | {
205 | continue;
206 | }
207 |
208 | const cgltf_draco_mesh_compression* draco = &primitive.draco_mesh_compression;
209 | if (attributeBuffersToDecompress.count(draco) == 0)
210 | {
211 | attributeBuffersToDecompress[draco] = {};
212 | }
213 |
214 | EncodedAttributes& mapEnty = attributeBuffersToDecompress[draco];
215 |
216 | // Spec: "the attributes defined in the extension must be a subset of the attributes of the primitive."
217 | for (size_t i = 0; i < draco->attributes_count; i++)
218 | {
219 | const cgltf_attribute* dracoAttr = &draco->attributes[i];
220 |
221 | cgltf_attribute* srcAttr = nullptr;
222 | for (size_t j = 0; j < primitive.attributes_count; j++)
223 | {
224 | cgltf_attribute* attr = &primitive.attributes[i];
225 |
226 | if (strcmp(attr->name, dracoAttr->name) == 0)
227 | {
228 | srcAttr = attr;
229 | break;
230 | }
231 | }
232 | TF_AXIOM(srcAttr); // ensured by validation
233 |
234 | auto dracoUid = int(cgltf_accessor_index(data, dracoAttr->data));
235 | mapEnty[dracoUid] = srcAttr->data;
236 | }
237 |
238 | primitive.indices->offset = 0;
239 | primitive.indices->stride = sizeof(uint32_t);
240 | primitive.indices->buffer_view = draco->buffer_view;
241 | }
242 | }
243 |
244 | //
245 | // Phase 2: In this second phase, we decode attributes from the draco buffers. We can't allocate new
246 | // accessors, buffers and buffer views because this invalidates all of cgltf's pointer references.
247 | // Instead, we allocate the .data field of the draco buffer view, which takes precedence over the
248 | // encoded buffer, and use it as the decoded data source.
249 | //
250 | for (auto it = attributeBuffersToDecompress.begin(); it != attributeBuffersToDecompress.end(); ++it)
251 | {
252 | // Decompress into draco mesh
253 | const cgltf_draco_mesh_compression* draco = it->first;
254 | cgltf_buffer_view* bufferView = draco->buffer_view;
255 | cgltf_buffer* buffer = bufferView->buffer;
256 |
257 | const char* bufferData = &((const char*) buffer->data)[bufferView->offset];
258 |
259 | draco::DecoderBuffer decoderBuffer;
260 | decoderBuffer.Init(bufferData, bufferView->size);
261 |
262 | auto geomType = draco::Decoder::GetEncodedGeometryType(&decoderBuffer);
263 | if (!geomType.ok() || geomType.value() != draco::TRIANGULAR_MESH)
264 | {
265 | TF_RUNTIME_ERROR("unsupported Draco geometry type");
266 | return false;
267 | }
268 |
269 | draco::Decoder decoder;
270 | auto decodeResult = decoder.DecodeMeshFromBuffer(&decoderBuffer);
271 | if (!decodeResult.ok())
272 | {
273 | TF_RUNTIME_ERROR("Draco failed to decode mesh from buffer");
274 | return false;
275 | }
276 |
277 | const std::unique_ptr& mesh = decodeResult.value();
278 |
279 | // Allocate decoded buffer
280 | const EncodedAttributes& attrsToDecode = it->second;
281 |
282 | uint32_t vertexCount = mesh->num_points();
283 | uint32_t faceCount = mesh->num_faces();
284 | uint32_t indexCount = faceCount * 3;
285 |
286 | uint32_t indicesSize = indexCount * sizeof(uint32_t);
287 |
288 | size_t attributesSize = 0;
289 | for (const auto& c : attrsToDecode)
290 | {
291 | cgltf_accessor* srcAccessor = c.second;
292 |
293 | attributesSize += vertexCount * srcAccessor->stride;
294 | }
295 |
296 | bufferView->data = malloc(indicesSize + attributesSize);
297 |
298 | // Write decoded data
299 | auto baseIndex = draco::FaceIndex(0);
300 | TF_VERIFY(sizeof(mesh->face(baseIndex)[0]) == 4);
301 | memcpy(bufferView->data, &mesh->face(baseIndex)[0], indicesSize);
302 |
303 | size_t attributeOffset = indicesSize;
304 | for (const auto& c : attrsToDecode)
305 | {
306 | int dracoUid = c.first;
307 |
308 | const draco::PointAttribute* dracoAttr = mesh->GetAttributeByUniqueId(dracoUid);
309 | if (!dracoAttr)
310 | {
311 | TF_RUNTIME_ERROR("invalid Draco attribute uid");
312 | return false;
313 | }
314 |
315 | cgltf_accessor* srcAccessor = c.second;
316 |
317 | TF_VERIFY(srcAccessor->count == vertexCount);
318 | if (!convertDracoVertexAttributes(srcAccessor->component_type,
319 | dracoAttr,
320 | vertexCount,
321 | &((uint8_t*) bufferView->data)[attributeOffset]))
322 | {
323 | TF_RUNTIME_ERROR("failed to decode Draco attribute");
324 | return false;
325 | }
326 |
327 | srcAccessor->buffer_view = draco->buffer_view;
328 | srcAccessor->offset = attributeOffset;
329 |
330 | attributeOffset += vertexCount * srcAccessor->stride;
331 | }
332 | }
333 |
334 | return true;
335 | }
336 | #endif
337 |
338 | // Based on https://github.com/jkuhlmann/cgltf/pull/129
339 | cgltf_result decompressMeshopt(cgltf_data* data)
340 | {
341 | for (size_t i = 0; i < data->buffer_views_count; ++i)
342 | {
343 | cgltf_buffer_view& bufferView = data->buffer_views[i];
344 |
345 | if (!bufferView.has_meshopt_compression)
346 | {
347 | continue;
348 | }
349 |
350 | const cgltf_meshopt_compression& mc = bufferView.meshopt_compression;
351 |
352 | const unsigned char* source = (const unsigned char*) mc.buffer->data;
353 | if (!source)
354 | {
355 | return cgltf_result_invalid_gltf;
356 | }
357 |
358 | source += mc.offset;
359 |
360 | void* result = malloc(mc.count * mc.stride);
361 | if (!result)
362 | {
363 | return cgltf_result_out_of_memory;
364 | }
365 |
366 | int errorCode = -1;
367 |
368 | switch (mc.mode)
369 | {
370 | default:
371 | case cgltf_meshopt_compression_mode_invalid:
372 | break;
373 |
374 | case cgltf_meshopt_compression_mode_attributes:
375 | errorCode = meshopt_decodeVertexBuffer(result, mc.count, mc.stride, source, mc.size);
376 | break;
377 |
378 | case cgltf_meshopt_compression_mode_triangles:
379 | errorCode = meshopt_decodeIndexBuffer(result, mc.count, mc.stride, source, mc.size);
380 | break;
381 |
382 | case cgltf_meshopt_compression_mode_indices:
383 | errorCode = meshopt_decodeIndexSequence(result, mc.count, mc.stride, source, mc.size);
384 | break;
385 | }
386 |
387 | if (errorCode != 0)
388 | {
389 | free(result);
390 | return cgltf_result_io_error;
391 | }
392 |
393 | switch (mc.filter)
394 | {
395 | default:
396 | case cgltf_meshopt_compression_filter_none:
397 | break;
398 |
399 | case cgltf_meshopt_compression_filter_octahedral:
400 | meshopt_decodeFilterOct(result, mc.count, mc.stride);
401 | break;
402 |
403 | case cgltf_meshopt_compression_filter_quaternion:
404 | meshopt_decodeFilterQuat(result, mc.count, mc.stride);
405 | break;
406 |
407 | case cgltf_meshopt_compression_filter_exponential:
408 | meshopt_decodeFilterExp(result, mc.count, mc.stride);
409 | break;
410 | }
411 |
412 | bufferView.data = result;
413 | }
414 |
415 | return cgltf_result_success;
416 | }
417 | }
418 |
419 | namespace guc
420 | {
421 | bool load_gltf(const char* gltfPath, cgltf_data** data)
422 | {
423 | detail::BufferHolder* bufferHolder = new detail::BufferHolder;
424 |
425 | cgltf_file_options fileOptions = {};
426 | fileOptions.read = detail::readFile;
427 | fileOptions.release = detail::releaseFile;
428 | fileOptions.user_data = bufferHolder;
429 |
430 | cgltf_options options = {};
431 | options.file = fileOptions;
432 |
433 | cgltf_result result = cgltf_parse_file(&options, gltfPath, data);
434 |
435 | if (result != cgltf_result_success)
436 | {
437 | TF_RUNTIME_ERROR("unable to parse glTF file: %s", cgltf_error_string(result));
438 | delete bufferHolder;
439 | return false;
440 | }
441 |
442 | result = cgltf_load_buffers(&options, *data, gltfPath);
443 |
444 | if (result != cgltf_result_success)
445 | {
446 | TF_RUNTIME_ERROR("unable to load glTF buffers: %s", cgltf_error_string(result));
447 | free_gltf(*data);
448 | return false;
449 | }
450 |
451 | result = cgltf_validate(*data);
452 |
453 | if (result != cgltf_result_success)
454 | {
455 | TF_RUNTIME_ERROR("unable to validate glTF: %s", cgltf_error_string(result));
456 | free_gltf(*data);
457 | return false;
458 | }
459 |
460 | bool meshoptCompressionRequired = false;
461 | #ifdef GUC_USE_DRACO
462 | bool dracoMeshCompressionRequired = false;
463 | #endif
464 |
465 | for (size_t i = 0; i < (*data)->extensions_required_count; i++)
466 | {
467 | const char* ext = (*data)->extensions_required[i];
468 | TF_DEBUG(GUC).Msg("extension required: %s\n", ext);
469 |
470 | if (strcmp(ext, detail::GLTF_EXT_MESHOPT_COMPRESSION_EXTENSION_NAME) == 0)
471 | {
472 | meshoptCompressionRequired = true;
473 | }
474 |
475 | #ifdef GUC_USE_DRACO
476 | if (strcmp(ext, detail::GLTF_KHR_DRACO_MESH_COMPRESSION_EXTENSION_NAME) == 0)
477 | {
478 | dracoMeshCompressionRequired = true;
479 | }
480 | #endif
481 |
482 | if (detail::extensionSupported(ext))
483 | {
484 | continue;
485 | }
486 |
487 | TF_RUNTIME_ERROR("extension %s not supported", ext);
488 | free_gltf(*data);
489 | return false;
490 | }
491 |
492 | result = detail::decompressMeshopt(*data);
493 |
494 | if (result != cgltf_result_success)
495 | {
496 | const char* errStr = "unable to decode meshoptimizer data: %s";
497 |
498 | if (meshoptCompressionRequired)
499 | {
500 | TF_RUNTIME_ERROR(errStr, guc::cgltf_error_string(result));
501 | free_gltf(*data);
502 | return false;
503 | }
504 |
505 | TF_WARN(errStr, guc::cgltf_error_string(result));
506 | }
507 |
508 | #ifdef GUC_USE_DRACO
509 | if (!detail::decompressDraco(*data))
510 | {
511 | const char* errStr = "unable to decode Draco data";
512 |
513 | if (dracoMeshCompressionRequired)
514 | {
515 | TF_RUNTIME_ERROR("%s", errStr);
516 | free_gltf(*data);
517 | return false;
518 | }
519 |
520 | TF_WARN("%s", errStr);
521 | }
522 | #endif
523 |
524 | for (size_t i = 0; i < (*data)->extensions_used_count; i++)
525 | {
526 | const char* ext = (*data)->extensions_used[i];
527 | TF_DEBUG(GUC).Msg("extension used: %s\n", ext);
528 |
529 | if (detail::extensionSupported(ext))
530 | {
531 | continue;
532 | }
533 |
534 | TF_WARN("optional extension %s not suppported", ext);
535 | }
536 |
537 | return true;
538 | }
539 |
540 | void free_gltf(cgltf_data* data)
541 | {
542 | auto bufferHolder = (detail::BufferHolder*) data->file.user_data;
543 | cgltf_free(data); // releases buffers in buffer holder
544 | delete bufferHolder;
545 | }
546 |
547 | const char* cgltf_error_string(cgltf_result result)
548 | {
549 | TF_VERIFY(result != cgltf_result_success);
550 | TF_VERIFY(result != cgltf_result_invalid_options);
551 | switch (result)
552 | {
553 | case cgltf_result_legacy_gltf:
554 | return "legacy glTF not supported";
555 | case cgltf_result_data_too_short:
556 | case cgltf_result_invalid_json:
557 | case cgltf_result_invalid_gltf:
558 | return "malformed glTF";
559 | case cgltf_result_unknown_format:
560 | return "unknown format";
561 | case cgltf_result_file_not_found:
562 | return "file not found";
563 | case cgltf_result_io_error:
564 | return "io error";
565 | case cgltf_result_out_of_memory:
566 | return "out of memory";
567 | default:
568 | return "unknown";
569 | }
570 | }
571 |
572 | const cgltf_accessor* cgltf_find_accessor(const cgltf_primitive* primitive,
573 | const char* name)
574 | {
575 | for (size_t j = 0; j < primitive->attributes_count; j++)
576 | {
577 | const cgltf_attribute* attribute = &primitive->attributes[j];
578 |
579 | if (strcmp(attribute->name, name) == 0)
580 | {
581 | return attribute->data;
582 | }
583 | }
584 |
585 | return nullptr;
586 | }
587 |
588 | bool cgltf_transform_required(const cgltf_texture_transform& transform)
589 | {
590 | return !GfIsClose(transform.offset[0], 0.0f, 1e-5f) ||
591 | !GfIsClose(transform.offset[1], 0.0f, 1e-5f) ||
592 | !GfIsClose(transform.rotation, 0.0f, 1e-5f) ||
593 | !GfIsClose(transform.scale[0], 1.0f, 1e-5f) ||
594 | !GfIsClose(transform.scale[1], 1.0f, 1e-5f);
595 | }
596 | }
597 |
--------------------------------------------------------------------------------
/src/libguc/src/cgltf_util.h:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Pablo Delgado Krämer
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | #pragma once
18 |
19 | #include
20 |
21 | namespace guc
22 | {
23 | bool load_gltf(const char* gltfPath, cgltf_data** data);
24 |
25 | void free_gltf(cgltf_data* data);
26 |
27 | const char* cgltf_error_string(cgltf_result result);
28 |
29 | const cgltf_accessor* cgltf_find_accessor(const cgltf_primitive* primitive,
30 | const char* name);
31 |
32 | bool cgltf_transform_required(const cgltf_texture_transform& transform);
33 | }
34 |
--------------------------------------------------------------------------------
/src/libguc/src/converter.h:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Pablo Delgado Krämer
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | #pragma once
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 |
25 | #include
26 | #include
27 | #include
28 |
29 | #include "materialx.h"
30 | #include "usdpreviewsurface.h"
31 |
32 | namespace fs = std::filesystem;
33 | using namespace PXR_NS;
34 |
35 | namespace guc
36 | {
37 | class Converter
38 | {
39 | public:
40 | struct Params
41 | {
42 | fs::path srcDir;
43 | fs::path dstDir;
44 | fs::path mtlxFileName;
45 | bool copyExistingFiles;
46 | bool genRelativePaths;
47 | bool emitMtlx;
48 | bool mtlxAsUsdShade;
49 | int defaultMaterialVariant;
50 | };
51 |
52 | public:
53 | Converter(const cgltf_data* data, UsdStageRefPtr stage, const Params& params);
54 |
55 | public:
56 | struct FileExport
57 | {
58 | std::string filePath;
59 | std::string refPath;
60 | };
61 | using FileExports = std::vector;
62 |
63 | void convert(FileExports& fileExports);
64 |
65 | private:
66 | void createMaterials(FileExports& fileExports, bool createDefaultMaterial);
67 | void createNodesRecursively(const cgltf_node* nodeData, SdfPath path);
68 | void createOrOverCamera(const cgltf_camera* cameraData, SdfPath path);
69 | void createOrOverLight(const cgltf_light* lightData, SdfPath path);
70 | void createOrOverMesh(const cgltf_mesh* meshData, SdfPath path);
71 | void createMaterialBinding(UsdPrim& prim, const std::string& materialName);
72 | bool createPrimitive(const cgltf_primitive* primitiveData, SdfPath path, UsdPrim& prim);
73 |
74 | private:
75 | bool overridePrimInPathMap(void* dataPtr, const SdfPath& path, UsdPrim& prim);
76 | bool isValidTexture(const cgltf_texture_view& textureView);
77 |
78 | private:
79 | const cgltf_data* m_data;
80 | UsdStageRefPtr m_stage;
81 | const Params& m_params;
82 |
83 | private:
84 | ImageMetadataMap m_imgMetadata;
85 | MaterialX::DocumentPtr m_mtlxDoc;
86 | MaterialXMaterialConverter m_mtlxConverter;
87 | UsdPreviewSurfaceMaterialConverter m_usdPreviewSurfaceConverter;
88 | std::unordered_map m_uniquePaths;
89 | std::vector m_materialNames;
90 | };
91 | }
92 |
--------------------------------------------------------------------------------
/src/libguc/src/debugCodes.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Pablo Delgado Krämer
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | #include "debugCodes.h"
18 |
19 | #include
20 | #include
21 |
22 | PXR_NAMESPACE_OPEN_SCOPE
23 |
24 | TF_REGISTRY_FUNCTION(TfDebug)
25 | {
26 | TF_DEBUG_ENVIRONMENT_SYMBOL(GUC, "GUC debug logging");
27 | }
28 |
29 | PXR_NAMESPACE_CLOSE_SCOPE
30 |
--------------------------------------------------------------------------------
/src/libguc/src/debugCodes.h:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Pablo Delgado Krämer
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | #pragma once
18 |
19 | #include
20 |
21 | PXR_NAMESPACE_OPEN_SCOPE
22 |
23 | TF_DEBUG_CODES(GUC)
24 |
25 | PXR_NAMESPACE_CLOSE_SCOPE
26 |
--------------------------------------------------------------------------------
/src/libguc/src/fileFormat.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Pablo Delgado Krämer
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | #include "fileFormat.h"
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 |
29 | #include
30 |
31 | #include "cgltf_util.h"
32 | #include "converter.h"
33 | #include "debugCodes.h"
34 |
35 | using namespace guc;
36 | namespace fs = std::filesystem;
37 |
38 | PXR_NAMESPACE_OPEN_SCOPE
39 |
40 | TF_DEFINE_PUBLIC_TOKENS(
41 | UsdGlTFFileFormatTokens,
42 | USDGLTF_FILE_FORMAT_TOKENS
43 | );
44 |
45 | TF_DEFINE_PRIVATE_TOKENS(
46 | _tokens,
47 | (gltf)
48 | (glb)
49 | (emitMtlx)
50 | );
51 |
52 | TF_REGISTRY_FUNCTION(TfType)
53 | {
54 | SDF_DEFINE_FILE_FORMAT(UsdGlTFFileFormat, SdfFileFormat);
55 | }
56 |
57 | // glTF files can contain embedded images. In order to support them in our Sdf file
58 | // format plugin, we create a temporary directory for each glTF file, write the images
59 | // to it, and reference them. Afterwards, this directory gets deleted, however I was
60 | // unable to use the Sdf file format destructor for this purpose, as it does not seem
61 | // to get called. Instead, we instantiate an object with a static lifetime.
62 | class UsdGlTFTmpDirHolder
63 | {
64 | private:
65 | std::vector m_dirPaths;
66 |
67 | public:
68 | std::string makeDir()
69 | {
70 | std::string dir = ArchMakeTmpSubdir(ArchGetTmpDir(), "usdGlTF");
71 | TF_DEBUG(GUC).Msg("created temp dir %s\n", dir.c_str());
72 | m_dirPaths.push_back(dir);
73 | return dir;
74 | }
75 | ~UsdGlTFTmpDirHolder()
76 | {
77 | for (const std::string& dir : m_dirPaths)
78 | {
79 | TF_DEBUG(GUC).Msg("deleting temp dir %s\n", dir.c_str());
80 | fs::remove_all(fs::path(dir));
81 | }
82 | }
83 | };
84 |
85 | static UsdGlTFTmpDirHolder s_tmpDirHolder;
86 |
87 | UsdGlTFFileFormat::UsdGlTFFileFormat()
88 | : SdfFileFormat(
89 | UsdGlTFFileFormatTokens->Id,
90 | UsdGlTFFileFormatTokens->Version,
91 | UsdGlTFFileFormatTokens->Target,
92 | /* extensions: */{ _tokens->gltf, _tokens->glb })
93 | {
94 | }
95 |
96 | UsdGlTFFileFormat::~UsdGlTFFileFormat()
97 | {
98 | }
99 |
100 | SdfAbstractDataRefPtr UsdGlTFFileFormat::InitData(const FileFormatArguments& args) const
101 | {
102 | UsdGlTFDataRefPtr data(new UsdGlTFData());
103 |
104 | auto emitMtlxIt = args.find(_tokens->emitMtlx.GetText());
105 | if (emitMtlxIt != args.end())
106 | {
107 | data->emitMtlx = TfUnstringify(emitMtlxIt->second);
108 | }
109 |
110 | return data;
111 | }
112 |
113 | bool UsdGlTFFileFormat::CanRead(const std::string& filePath) const
114 | {
115 | // FIXME: implement? In my tests, this is not even called.
116 | return true;
117 | }
118 |
119 | bool UsdGlTFFileFormat::Read(SdfLayer* layer,
120 | const std::string& resolvedPath,
121 | bool metadataOnly) const
122 | {
123 | fs::path srcDir = fs::path(resolvedPath).parent_path();
124 |
125 | // In case we're accessing an image file in a sibling or parent folder, ArResolver needs
126 | // to know the root glTF directory in order to be able to resolve relative file paths.
127 | ArDefaultResolverContext ctx({srcDir.string()});
128 | ArResolverContextBinder binder(ctx);
129 |
130 | cgltf_data* gltf_data = nullptr;
131 | if (!load_gltf(resolvedPath.c_str(), &gltf_data))
132 | {
133 | TF_RUNTIME_ERROR("unable to load glTF file %s", resolvedPath.c_str());
134 | return false;
135 | }
136 |
137 | SdfAbstractDataRefPtr layerData = InitData(layer->GetFileFormatArguments());
138 | UsdGlTFDataConstPtr data = TfDynamic_cast(layerData);
139 |
140 | Converter::Params params = {};
141 | params.srcDir = srcDir;
142 | params.dstDir = s_tmpDirHolder.makeDir();
143 | params.mtlxFileName = ""; // Not needed because of Mtlx-as-UsdShade option
144 | params.copyExistingFiles = false;
145 | params.genRelativePaths = false;
146 | params.emitMtlx = data->emitMtlx;
147 | params.mtlxAsUsdShade = true;
148 | params.defaultMaterialVariant = 0;
149 |
150 | SdfLayerRefPtr tmpLayer = SdfLayer::CreateAnonymous(".usdc");
151 | UsdStageRefPtr stage = UsdStage::Open(tmpLayer);
152 |
153 | Converter converter(gltf_data, stage, params);
154 |
155 | Converter::FileExports fileExports; // only used for USDZ
156 | converter.convert(fileExports);
157 |
158 | free_gltf(gltf_data);
159 |
160 | layer->TransferContent(tmpLayer);
161 |
162 | return true;
163 | }
164 |
165 | bool UsdGlTFFileFormat::ReadFromString(SdfLayer* layer,
166 | const std::string& str) const
167 | {
168 | // glTF files often reference other files (e.g. a .bin payload or images).
169 | // Hence, without a file location, most glTF files can not be loaded correctly.
170 | // TODO: but we could still try and return false on failure
171 | return false;
172 | }
173 |
174 | bool UsdGlTFFileFormat::WriteToString(const SdfLayer& layer,
175 | std::string* str,
176 | const std::string& comment) const
177 | {
178 | // Not supported, and never will be. Write USDC instead.
179 | SdfFileFormatConstPtr usdcFormat = SdfFileFormat::FindById(UsdUsdcFileFormatTokens->Id);
180 | return usdcFormat->WriteToString(layer, str, comment);
181 | }
182 |
183 | bool UsdGlTFFileFormat::WriteToStream(const SdfSpecHandle &spec,
184 | std::ostream& out,
185 | size_t indent) const
186 | {
187 | // Not supported, and never will be. Write USDC instead.
188 | SdfFileFormatConstPtr usdcFormat = SdfFileFormat::FindById(UsdUsdcFileFormatTokens->Id);
189 | return usdcFormat->WriteToStream(spec, out, indent);
190 | }
191 |
192 | void UsdGlTFFileFormat::ComposeFieldsForFileFormatArguments(const std::string& assetPath,
193 | const PcpDynamicFileFormatContext& context,
194 | FileFormatArguments* args,
195 | VtValue *dependencyContextData) const
196 | {
197 | VtValue emitMtlxValue;
198 | if (context.ComposeValue(_tokens->emitMtlx, &emitMtlxValue))
199 | {
200 | (*args)[_tokens->emitMtlx] = TfStringify(emitMtlxValue);
201 | }
202 | }
203 |
204 | PXR_NAMESPACE_CLOSE_SCOPE
205 |
--------------------------------------------------------------------------------
/src/libguc/src/fileFormat.h:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Pablo Delgado Krämer
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | #pragma once
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | PXR_NAMESPACE_OPEN_SCOPE
28 |
29 | #define USDGLTF_FILE_FORMAT_TOKENS \
30 | ((Id, "gltf")) \
31 | ((Version, GUC_VERSION_STRING)) \
32 | ((Target, "usd"))
33 |
34 | TF_DECLARE_PUBLIC_TOKENS(UsdGlTFFileFormatTokens, USDGLTF_FILE_FORMAT_TOKENS);
35 |
36 | TF_DECLARE_WEAK_AND_REF_PTRS(UsdGlTFFileFormat);
37 | TF_DECLARE_WEAK_AND_REF_PTRS(UsdGlTFData);
38 |
39 | class UsdGlTFFileFormat : public SdfFileFormat, public PcpDynamicFileFormatInterface
40 | {
41 | protected:
42 | SDF_FILE_FORMAT_FACTORY_ACCESS;
43 |
44 | UsdGlTFFileFormat();
45 |
46 | virtual ~UsdGlTFFileFormat();
47 |
48 | public:
49 | SdfAbstractDataRefPtr InitData(const FileFormatArguments& args) const override;
50 |
51 | bool CanRead(const std::string &file) const override;
52 |
53 | bool Read(SdfLayer* layer,
54 | const std::string& resolvedPath,
55 | bool metadataOnly) const override;
56 |
57 | bool ReadFromString(SdfLayer* layer,
58 | const std::string& str) const override;
59 |
60 | bool WriteToString(const SdfLayer& layer,
61 | std::string* str,
62 | const std::string& comment = std::string())
63 | const override;
64 |
65 | bool WriteToStream(const SdfSpecHandle &spec,
66 | std::ostream& out,
67 | size_t indent) const override;
68 |
69 | public:
70 | void ComposeFieldsForFileFormatArguments(const std::string& assetPath,
71 | const PcpDynamicFileFormatContext& context,
72 | FileFormatArguments* args,
73 | VtValue *dependencyContextData) const override;
74 | };
75 |
76 | class UsdGlTFData : public SdfData
77 | {
78 | public:
79 | bool emitMtlx = false;
80 | };
81 |
82 | PXR_NAMESPACE_CLOSE_SCOPE
83 |
84 |
--------------------------------------------------------------------------------
/src/libguc/src/guc.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Pablo Delgado Krämer
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | #include "guc.h"
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | #include
28 |
29 | #include "debugCodes.h"
30 | #include "cgltf_util.h"
31 | #include "converter.h"
32 |
33 | using namespace guc;
34 | namespace fs = std::filesystem;
35 |
36 | bool convertToUsd(fs::path src_dir,
37 | const cgltf_data* gltf_data,
38 | fs::path usd_path,
39 | bool copyExistingFiles,
40 | const guc_options* options,
41 | Converter::FileExports& fileExports)
42 | {
43 | UsdStageRefPtr stage = UsdStage::CreateNew(usd_path.string());
44 | if (!stage)
45 | {
46 | TF_RUNTIME_ERROR("unable to open stage at %s", usd_path.string().c_str());
47 | return false;
48 | }
49 |
50 | Converter::Params params = {};
51 | params.srcDir = src_dir;
52 | params.dstDir = usd_path.parent_path();
53 | params.mtlxFileName = usd_path.filename().replace_extension(".mtlx");
54 | params.copyExistingFiles = copyExistingFiles;
55 | params.genRelativePaths = true;
56 | params.emitMtlx = options->emit_mtlx;
57 | params.mtlxAsUsdShade = options->mtlx_as_usdshade;
58 | params.defaultMaterialVariant = options->default_material_variant;
59 |
60 | Converter converter(gltf_data, stage, params);
61 |
62 | converter.convert(fileExports);
63 |
64 | TF_DEBUG(GUC).Msg("saving stage to %s\n", usd_path.string().c_str());
65 | stage->Save();
66 |
67 | return true;
68 | }
69 |
70 | bool guc_convert(const char* gltf_path,
71 | const char* usd_path,
72 | const guc_options* options)
73 | {
74 | fs::path src_dir = fs::path(gltf_path).parent_path();
75 |
76 | // We unconditionally use USD's asset resolver which needs to be able to resolve
77 | // relative file paths.
78 | ArDefaultResolverContext ctx({src_dir.string()});
79 | ArResolverContextBinder binder(ctx);
80 |
81 | // The path we write USDA/USDC files to. If the user wants a USDZ file, we first
82 | // write these files to a temporary location, zip them, and copy the ZIP file to
83 | // the destination directory.
84 | fs::path final_usd_path = usd_path;
85 | fs::path base_usd_path = usd_path;
86 | fs::path dst_dir = base_usd_path.parent_path();
87 |
88 | bool export_usdz = base_usd_path.extension() == ".usdz";
89 |
90 | if (export_usdz)
91 | {
92 | dst_dir = ArchMakeTmpSubdir(ArchGetTmpDir(), "guc");
93 | TF_DEBUG(GUC).Msg("using temp dir %s\n", dst_dir.string().c_str());
94 |
95 | if (dst_dir.empty())
96 | {
97 | TF_RUNTIME_ERROR("unable to create temporary directory for USDZ contents");
98 | return false;
99 | }
100 |
101 | auto new_usd_filename = base_usd_path.replace_extension(".usdc").filename();
102 | base_usd_path = dst_dir / new_usd_filename;
103 | TF_DEBUG(GUC).Msg("temporary USD path: %s\n", base_usd_path.string().c_str());
104 | }
105 |
106 | cgltf_data* gltf_data = nullptr;
107 | if (!load_gltf(gltf_path, &gltf_data))
108 | {
109 | TF_RUNTIME_ERROR("unable to load glTF file %s", gltf_path);
110 | return false;
111 | }
112 |
113 | bool copyExistingFiles = !export_usdz; // Add source files directly to archive in case of USDZ
114 |
115 | Converter::FileExports fileExports;
116 | bool result = convertToUsd(src_dir, gltf_data, base_usd_path, copyExistingFiles, options, fileExports);
117 |
118 | free_gltf(gltf_data);
119 |
120 | if (!result)
121 | {
122 | return false;
123 | }
124 |
125 | // In case of USDZ, we have now written the USDC file and all image files to a
126 | // temporary directory. Next, we invoke Pixar's USDZ API in order to zip them.
127 | if (export_usdz)
128 | {
129 | auto usdz_dst_dir = fs::absolute(final_usd_path).parent_path();
130 | if (!fs::exists(usdz_dst_dir) && !fs::create_directories(usdz_dst_dir))
131 | {
132 | TF_RUNTIME_ERROR("unable to create destination directory");
133 | return false;
134 | }
135 |
136 | TF_DEBUG(GUC).Msg("creating USDZ archive %s\n", final_usd_path.string().c_str());
137 | UsdZipFileWriter writer = UsdZipFileWriter::CreateNew(final_usd_path.string());
138 |
139 | TF_DEBUG(GUC).Msg("adding %s to USDZ archive at ./%s\n", base_usd_path.string().c_str(), base_usd_path.filename().string().c_str());
140 | if (writer.AddFile(base_usd_path.string(), base_usd_path.filename().string()) == "")
141 | {
142 | TF_RUNTIME_ERROR("unable to usdzip %s to %s", base_usd_path.string().c_str(), base_usd_path.filename().string().c_str());
143 | return false; // Fatal error
144 | }
145 | for (const auto& fileExport : fileExports)
146 | {
147 | // We need to pass explicit source paths to UsdZipFileWriter because it uses the lower-level file
148 | // API instead of USD's ArResolver: https://github.com/PixarAnimationStudios/OpenUSD/issues/2374
149 | std::string srcPath = (src_dir / fileExport.filePath).string();
150 | std::string dstPathInUsdz = fileExport.refPath;
151 |
152 | TF_DEBUG(GUC).Msg("adding %s to USDZ archive at ./%s\n", srcPath.c_str(), dstPathInUsdz.c_str());
153 | if (writer.AddFile(srcPath, dstPathInUsdz) == "")
154 | {
155 | TF_RUNTIME_ERROR("unable to usdzip %s to %s", srcPath.c_str(), dstPathInUsdz.c_str());
156 | // (non-fatal error)
157 | }
158 | }
159 |
160 | if (!writer.Save())
161 | {
162 | TF_RUNTIME_ERROR("unable to save USDZ file");
163 | return false;
164 | }
165 |
166 | TF_DEBUG(GUC).Msg("removing temporary directory %s\n", dst_dir.string().c_str());
167 | fs::remove_all(dst_dir);
168 | }
169 |
170 | return true;
171 | }
172 |
--------------------------------------------------------------------------------
/src/libguc/src/image.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Pablo Delgado Krämer
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | #include "image.h"
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 |
25 | #ifdef GUC_USE_OIIO
26 | #include
27 | #include
28 | #else
29 | #define STB_IMAGE_IMPLEMENTATION
30 | #include
31 | #endif
32 |
33 | #include
34 | #include
35 |
36 | #include "cgltf_util.h"
37 | #include "debugCodes.h"
38 | #include "naming.h"
39 |
40 | namespace fs = std::filesystem;
41 |
42 | namespace detail
43 | {
44 | using namespace guc;
45 |
46 | bool readImageDataFromBufferView(const cgltf_buffer_view* bufferView,
47 | size_t& dstSize,
48 | std::shared_ptr& dstData)
49 | {
50 | const char* srcData = (const char*) bufferView->buffer->data;
51 |
52 | if (bufferView->data)
53 | {
54 | TF_WARN("buffer view contains unsupported extension data");
55 | }
56 |
57 | if (!srcData)
58 | {
59 | TF_RUNTIME_ERROR("unable to read buffer view; data is NULL");
60 | return false;
61 | }
62 |
63 | srcData += bufferView->offset;
64 |
65 | dstSize = bufferView->size;
66 | dstData = std::shared_ptr(srcData, [](const char* ptr) {
67 | // Do not free the memory - it's owned by cgltf_data which is
68 | // externally managed and exceeds the lifetime of this shared_ptr.
69 | });
70 | return true;
71 | }
72 |
73 | bool readImageDataFromBase64(const char* base64Str,
74 | size_t& size,
75 | std::shared_ptr& data)
76 | {
77 | size = strlen(base64Str);
78 |
79 | size_t padding = 0;
80 | if (size >= 2 && base64Str[size - 2] == '=')
81 | {
82 | padding = 2;
83 | }
84 | else if (size >= 1 && base64Str[size - 1] == '=')
85 | {
86 | padding = 1;
87 | }
88 |
89 | size = (size / 4) * 3 - padding;
90 |
91 | if (size == 0)
92 | {
93 | TF_WARN("base64 string has no payload");
94 | return false;
95 | }
96 |
97 | void* dataPtr;
98 | cgltf_options options = {};
99 | cgltf_result result = cgltf_load_buffer_base64(&options, size, base64Str, &dataPtr);
100 | if (result != cgltf_result_success)
101 | {
102 | TF_RUNTIME_ERROR("unable to read base64-encoded data");
103 | return false;
104 | }
105 |
106 | data = std::shared_ptr((const char*) dataPtr, [](const char* ptr) {
107 | free((void*) ptr);
108 | });
109 | return true;
110 | }
111 |
112 | bool readImageFromFile(const char* path,
113 | size_t& size,
114 | std::shared_ptr& data)
115 | {
116 | TF_DEBUG(GUC).Msg("reading image %s\n", path);
117 |
118 | ArResolver& resolver = ArGetResolver();
119 | std::string identifier = resolver.CreateIdentifier(path);
120 | TF_DEBUG(GUC).Msg("normalized path to %s\n", identifier.c_str());
121 |
122 | ArResolvedPath resolvedPath = resolver.Resolve(identifier);
123 | if (!resolvedPath)
124 | {
125 | TF_RUNTIME_ERROR("unable to resolve %s", path);
126 | return false;
127 | }
128 |
129 | std::string resolvedPathStr = resolvedPath.GetPathString();
130 | TF_DEBUG(GUC).Msg("resolved path to %s\n", resolvedPathStr.c_str());
131 |
132 | std::shared_ptr asset = resolver.OpenAsset(resolvedPath);
133 | if (!asset)
134 | {
135 | TF_RUNTIME_ERROR("unable to open asset %s", resolvedPathStr.c_str());
136 | return false;
137 | }
138 |
139 | std::shared_ptr buffer = asset->GetBuffer();
140 | if (!buffer)
141 | {
142 | TF_RUNTIME_ERROR("unable to open buffer for %s", resolvedPathStr.c_str());
143 | return false;
144 | }
145 |
146 | size = asset->GetSize();
147 | data = asset->GetBuffer();
148 | return true;
149 | }
150 |
151 | bool writeImageData(const char* filePath, size_t size, const std::shared_ptr& data)
152 | {
153 | FILE* file = ArchOpenFile(filePath, "wb");
154 | if (!file)
155 | {
156 | TF_RUNTIME_ERROR("unable to open file for writing: %s", filePath);
157 | return false;
158 | }
159 |
160 | int64_t result = ArchPWrite(file, data.get(), size, 0);
161 |
162 | ArchCloseFile(ArchFileNo(file));
163 |
164 | if (result == -1)
165 | {
166 | TF_RUNTIME_ERROR("unable to read from file %s", filePath);
167 | return false;
168 | }
169 | return true;
170 | }
171 |
172 | bool readExtensionFromDataSignature(size_t size, const std::shared_ptr& data, std::string& extension)
173 | {
174 | const static std::array JPEG_HEADER = { 0xFF, 0xD8, 0xFF };
175 | const static std::array PNG_HEADER = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
176 |
177 | if (size < JPEG_HEADER.size())
178 | {
179 | return false;
180 | }
181 | else if (memcmp(data.get(), JPEG_HEADER.data(), JPEG_HEADER.size()) == 0)
182 | {
183 | extension = ".jpg";
184 | return true;
185 | }
186 | else if (size < PNG_HEADER.size())
187 | {
188 | return false;
189 | }
190 | else if (memcmp(data.get(), PNG_HEADER.data(), PNG_HEADER.size()) == 0)
191 | {
192 | extension = ".png";
193 | return true;
194 | }
195 |
196 | return false;
197 | }
198 |
199 | bool decodeImageMetadata(const std::shared_ptr& buffer,
200 | size_t bufferSize,
201 | const char* path, // only a hint
202 | int& channelCount)
203 | {
204 | #ifdef GUC_USE_OIIO
205 | OIIO::Filesystem::IOMemReader memReader((void*) buffer.get(), bufferSize);
206 |
207 | auto image = OIIO::ImageInput::open(path, nullptr, &memReader);
208 | if (image)
209 | {
210 | TF_VERIFY(image->supports("ioproxy"));
211 |
212 | const OIIO::ImageSpec& spec = image->spec();
213 | channelCount = spec.nchannels;
214 | image->close();
215 | return true;
216 | }
217 | else
218 | {
219 | std::string errStr = OIIO::geterror();
220 | TF_RUNTIME_ERROR("OpenImageIO %s", errStr.c_str());
221 | }
222 | #else
223 | int width, height;
224 | int ok = stbi_info_from_memory((const stbi_uc*) buffer.get(), bufferSize, &width, &height, &channelCount);
225 | if (ok)
226 | {
227 | return true;
228 | }
229 | #endif
230 | TF_RUNTIME_ERROR("unable to open file for reading: %s", path);
231 | return false;
232 | }
233 |
234 | bool readImageMetadata(const char* path, int& channelCount)
235 | {
236 | size_t size;
237 | std::shared_ptr data;
238 | if (!readImageFromFile(path, size, data))
239 | {
240 | return false;
241 | }
242 |
243 | return decodeImageMetadata(data, size, path, channelCount);
244 | }
245 |
246 | std::optional processImage(const cgltf_image* image,
247 | const fs::path& srcDir,
248 | const fs::path& dstDir,
249 | bool copyExistingFiles,
250 | bool genRelativePaths,
251 | std::unordered_set& generatedFileNames)
252 | {
253 | size_t size = 0;
254 | std::shared_ptr data;
255 | std::string srcFilePath;
256 |
257 | const char* uri = image->uri;
258 | if (uri && strncmp(uri, "data:", 5) == 0)
259 | {
260 | const char* comma = strchr(uri, ',');
261 | if (comma && (comma - uri < 7 || strncmp(comma - 7, ";base64", 7) != 0))
262 | {
263 | return std::nullopt;
264 | }
265 |
266 | if (!readImageDataFromBase64(comma + 1, size, data))
267 | {
268 | return std::nullopt;
269 | }
270 | }
271 | else if (uri)
272 | {
273 | srcFilePath = std::string(uri);
274 | cgltf_decode_uri(srcFilePath.data());
275 |
276 | if (!readImageFromFile(srcFilePath.c_str(), size, data))
277 | {
278 | return std::nullopt;
279 | }
280 | }
281 | else if (image->buffer_view)
282 | {
283 | if (!readImageDataFromBufferView(image->buffer_view, size, data))
284 | {
285 | return std::nullopt;
286 | }
287 | }
288 | else
289 | {
290 | TF_WARN("no image source; probably defined by unsupported extension");
291 | return std::nullopt;
292 | }
293 |
294 | std::string fileExt;
295 | if (!readExtensionFromDataSignature(size, data, fileExt))
296 | {
297 | // Doesn't matter what the mime type or path extension is if the image can not be read
298 | const char* hint = image->name;
299 | if (!srcFilePath.empty())
300 | {
301 | hint = srcFilePath.c_str();
302 | }
303 | if (!hint || !strcmp(hint, ""))
304 | {
305 | hint = "embedded";
306 | }
307 | TF_RUNTIME_ERROR("unable to determine image data type (hint: %s)", hint);
308 | return std::nullopt;
309 | }
310 |
311 | bool genNewFileName = srcFilePath.empty() || genRelativePaths;
312 | bool writeNewFile = srcFilePath.empty() || copyExistingFiles;
313 |
314 | std::string dstRefPath = srcFilePath;
315 | if (genNewFileName)
316 | {
317 | std::string srcFileName = fs::path(srcFilePath).filename().string();
318 | std::string dstFileName = makeUniqueImageFileName(image->name, srcFileName, fileExt, generatedFileNames);
319 |
320 | generatedFileNames.insert(dstFileName);
321 |
322 | dstRefPath = dstFileName;
323 | }
324 |
325 | std::string dstFilePath = srcFilePath;
326 | if (writeNewFile)
327 | {
328 | TF_VERIFY(genNewFileName); // Makes no sense to write a file to its source path
329 |
330 | std::string writeFilePath = (dstDir / fs::path(dstRefPath)).string();
331 | TF_DEBUG(GUC).Msg("writing img %s\n", writeFilePath.c_str());
332 | if (!writeImageData(writeFilePath.c_str(), size, data))
333 | {
334 | return std::nullopt;
335 | }
336 |
337 | dstFilePath = writeFilePath;
338 |
339 | if (!genRelativePaths)
340 | {
341 | dstRefPath = writeFilePath;
342 | }
343 | }
344 |
345 | ImageMetadata metadata;
346 | metadata.filePath = dstFilePath;
347 | metadata.refPath = dstRefPath;
348 |
349 | // Read the metadata required for MaterialX shading network creation
350 | if (!readImageMetadata(dstFilePath.c_str(), metadata.channelCount))
351 | {
352 | TF_RUNTIME_ERROR("unable to read metadata of image %s", dstFilePath.c_str());
353 | return std::nullopt;
354 | }
355 |
356 | return metadata;
357 | }
358 | }
359 |
360 | namespace guc
361 | {
362 | void processImages(const cgltf_image* images,
363 | size_t imageCount,
364 | const fs::path& srcDir,
365 | const fs::path& dstDir,
366 | bool copyExistingFiles,
367 | bool genRelativePaths,
368 | ImageMetadataMap& metadata)
369 | {
370 | std::unordered_set generatedFileNames;
371 |
372 | for (size_t i = 0; i < imageCount; i++)
373 | {
374 | const cgltf_image* image = &images[i];
375 |
376 | auto meta = detail::processImage(image, srcDir, dstDir, copyExistingFiles, genRelativePaths, generatedFileNames);
377 |
378 | if (meta.has_value())
379 | {
380 | metadata[image] = meta.value();
381 | }
382 | }
383 |
384 | TF_DEBUG(GUC).Msg("processed %d images\n", int(metadata.size()));
385 | }
386 | }
387 |
--------------------------------------------------------------------------------
/src/libguc/src/image.h:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Pablo Delgado Krämer
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | #pragma once
18 |
19 | #include
20 |
21 | #include
22 | #include
23 | #include
24 |
25 | namespace fs = std::filesystem;
26 |
27 | namespace guc
28 | {
29 | struct ImageMetadata
30 | {
31 | std::string filePath;
32 | std::string refPath;
33 | int channelCount; // Needed to determine the type of MaterialX nodes
34 | };
35 |
36 | using ImageMetadataMap = std::unordered_map;
37 |
38 | void processImages(const cgltf_image* images,
39 | size_t imageCount,
40 | const fs::path& srcDir,
41 | const fs::path& dstDir,
42 | bool copyExistingFiles,
43 | bool genRelativePaths,
44 | ImageMetadataMap& metadata);
45 | }
46 |
--------------------------------------------------------------------------------
/src/libguc/src/materialx.h:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Pablo Delgado Krämer
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | #pragma once
18 |
19 | #include
20 |
21 | #include
22 |
23 | #include "image.h"
24 |
25 | namespace mx = MaterialX;
26 |
27 | namespace guc
28 | {
29 | class MaterialXMaterialConverter
30 | {
31 | public:
32 | MaterialXMaterialConverter(mx::DocumentPtr doc,
33 | const ImageMetadataMap& imageMetadataMap);
34 |
35 | void convert(const cgltf_material* material, const std::string& materialName);
36 |
37 | private:
38 | mx::DocumentPtr m_doc;
39 | const ImageMetadataMap& m_imageMetadataMap;
40 | std::string m_defaultColorSetName;
41 | std::string m_defaultOpacitySetName;
42 |
43 | private:
44 | void createUnlitSurfaceNodes(const cgltf_material* material,
45 | const std::string& materialName);
46 |
47 | void createGltfPbrNodes(const cgltf_material* material,
48 | const std::string& materialName);
49 |
50 | using ShaderNodeCreationCallback =
51 | std::function;
52 |
53 | void createMaterialNodes(const cgltf_material* material,
54 | const std::string& materialName,
55 | const std::string& shaderNodeType,
56 | ShaderNodeCreationCallback callback);
57 |
58 | private:
59 | void addGltfPbrInputs(const cgltf_material* material,
60 | mx::NodeGraphPtr nodeGraph,
61 | mx::NodePtr shaderNode);
62 |
63 | void addDiffuseTextureInput(mx::NodeGraphPtr nodeGraph,
64 | mx::NodePtr shaderNode,
65 | const std::string& inputName,
66 | const cgltf_texture_view* textureView,
67 | const mx::Color3& factor);
68 |
69 | void addAlphaTextureInput(mx::NodeGraphPtr nodeGraph,
70 | mx::NodePtr shaderNode,
71 | const std::string& inputName,
72 | const cgltf_texture_view* textureView,
73 | float factor);
74 |
75 | void addNormalTextureInput(mx::NodeGraphPtr nodeGraph,
76 | mx::NodePtr shaderNode,
77 | const std::string& inputName,
78 | const cgltf_texture_view& textureView);
79 |
80 | void addOcclusionTextureInput(mx::NodeGraphPtr nodeGraph,
81 | mx::NodePtr shaderNode,
82 | const cgltf_texture_view& textureView);
83 |
84 | void addIridescenceThicknessInput(mx::NodeGraphPtr nodeGraph,
85 | mx::NodePtr shaderNode,
86 | const cgltf_iridescence* iridescence);
87 |
88 | private:
89 | void addSrgbTextureInput(mx::NodeGraphPtr nodeGraph,
90 | mx::NodePtr shaderNode,
91 | const std::string& inputName,
92 | const cgltf_texture_view& textureView,
93 | mx::Color3 factor,
94 | mx::Color3 factorDefault);
95 |
96 | void addFloatTextureInput(mx::NodeGraphPtr nodeGraph,
97 | mx::NodePtr shaderNode,
98 | const std::string& inputName,
99 | const cgltf_texture_view& textureView,
100 | int channelIndex,
101 | float factor,
102 | float factorDefault);
103 |
104 | private:
105 | // These two functions not only set up the image nodes with the correct value
106 | // types and sampling properties, but also resolve mismatches between the desired and
107 | // given component types. Resolution is handled according to this table:
108 | //
109 | // texture type
110 | // (#channels)
111 | // +---------------+---------------+--------------------+
112 | // desired | | | color3 |
113 | // type | | float | (/vector3) |
114 | // +---------------+---------------+--------------------+
115 | // | | | img + |
116 | // | greyscale (1) | img | convert_color3 |
117 | // +---------------+---------------+--------------------+
118 | // | | | img + |
119 | // | greyscale + | img + | extract_float(0) + |
120 | // | alpha (2) | extract_float | convert_color3 |
121 | // +---------------+---------------+--------------------+
122 | // | | img + | |
123 | // | RGB (3) | extract_float | img |
124 | // +---------------+---------------+--------------------+
125 | // | | img + | img + |
126 | // | RGBA (4) | extract_float | convert_color3 |
127 | // +---------------+---------------+--------------------+
128 | //
129 | mx::NodePtr addFloatTextureNodes(mx::NodeGraphPtr nodeGraph,
130 | const cgltf_texture_view& textureView,
131 | std::string& filePath,
132 | int channelIndex);
133 |
134 | mx::NodePtr addFloat3TextureNodes(mx::NodeGraphPtr nodeGraph,
135 | const cgltf_texture_view& textureView,
136 | std::string& filePath,
137 | bool color,
138 | mx::ValuePtr defaultValue);
139 |
140 | mx::NodePtr addTextureTransformNode(mx::NodeGraphPtr nodeGraph,
141 | mx::NodePtr texcoordNode,
142 | const cgltf_texture_transform& transform);
143 |
144 | mx::NodePtr addTextureNode(mx::NodeGraphPtr nodeGraph,
145 | const std::string& filePath,
146 | const std::string& textureType,
147 | bool isSrgb,
148 | const cgltf_texture_view& textureView,
149 | mx::ValuePtr defaultValue);
150 |
151 | mx::NodePtr makeGeompropValueNode(mx::NodeGraphPtr nodeGraph,
152 | const std::string& geompropName,
153 | const std::string& geompropValueTypeName,
154 | mx::ValuePtr defaultValue = nullptr);
155 |
156 | void connectNodeGraphNodeToShaderInput(mx::NodeGraphPtr nodeGraph, mx::InputPtr input, mx::NodePtr node);
157 |
158 | private:
159 | bool getTextureMetadata(const cgltf_texture_view& textureView, ImageMetadata& metadata) const;
160 | bool getTextureFilePath(const cgltf_texture_view& textureView, std::string& filePath) const;
161 | int getTextureChannelCount(const cgltf_texture_view& textureView) const;
162 |
163 | std::string getTextureValueType(const cgltf_texture_view& textureView, bool color) const;
164 | };
165 | }
166 |
--------------------------------------------------------------------------------
/src/libguc/src/mesh.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Pablo Delgado Krämer
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | #include "mesh.h"
18 |
19 | #include
20 | #include
21 | #include
22 |
23 | #include
24 |
25 | #include "debugCodes.h"
26 |
27 | namespace guc
28 | {
29 | bool createGeometryRepresentation(const cgltf_primitive* prim,
30 | const VtIntArray& inIndices,
31 | VtIntArray& outIndices,
32 | VtIntArray& faceVertexCounts)
33 | {
34 | const char* INDICES_MISMATCH_ERROR_MSG = "indices count does not match primitive type";
35 |
36 | switch (prim->type)
37 | {
38 | case cgltf_primitive_type_points: {
39 | faceVertexCounts = VtIntArray(inIndices.size(), 1);
40 | outIndices = inIndices;
41 | break;
42 | }
43 | case cgltf_primitive_type_lines: {
44 | if ((inIndices.size() % 2) != 0)
45 | {
46 | TF_RUNTIME_ERROR("%s", INDICES_MISMATCH_ERROR_MSG);
47 | return false;
48 | }
49 | faceVertexCounts = VtIntArray(inIndices.size() / 2, 2);
50 | outIndices = inIndices;
51 | break;
52 | }
53 | case cgltf_primitive_type_triangles: {
54 | if ((inIndices.size() % 3) != 0)
55 | {
56 | TF_RUNTIME_ERROR("%s", INDICES_MISMATCH_ERROR_MSG);
57 | return false;
58 | }
59 | faceVertexCounts = VtIntArray(inIndices.size() / 3, 3);
60 | outIndices = inIndices;
61 | break;
62 | }
63 | case cgltf_primitive_type_line_strip: {
64 | if (inIndices.size() < 2)
65 | {
66 | TF_RUNTIME_ERROR("%s", INDICES_MISMATCH_ERROR_MSG);
67 | return false;
68 | }
69 | faceVertexCounts = VtIntArray(inIndices.size() - 1, 2);
70 | outIndices.resize(faceVertexCounts.size() * 2);
71 | for (size_t i = 0; i < faceVertexCounts.size(); i++)
72 | {
73 | outIndices[i * 2 + 0] = inIndices[i + 0];
74 | outIndices[i * 2 + 1] = inIndices[i + 1];
75 | }
76 | break;
77 | }
78 | case cgltf_primitive_type_line_loop: {
79 | if (inIndices.size() < 2)
80 | {
81 | TF_RUNTIME_ERROR("%s", INDICES_MISMATCH_ERROR_MSG);
82 | return false;
83 | }
84 | faceVertexCounts = VtIntArray(inIndices.size(), 2);
85 | outIndices.resize(inIndices.size() * 2);
86 | size_t i;
87 | for (i = 0; i < inIndices.size() - 1; i++)
88 | {
89 | outIndices[i * 2 + 0] = inIndices[i + 0];
90 | outIndices[i * 2 + 1] = inIndices[i + 1];
91 | }
92 | outIndices[i + 0] = inIndices[inIndices.size() - 1];
93 | outIndices[i + 1] = inIndices[0];
94 | break;
95 | }
96 | case cgltf_primitive_type_triangle_strip: {
97 | if (inIndices.size() < 3)
98 | {
99 | TF_RUNTIME_ERROR("%s", INDICES_MISMATCH_ERROR_MSG);
100 | return false;
101 | }
102 | faceVertexCounts = VtIntArray(inIndices.size() - 2, 3);
103 | outIndices.resize(faceVertexCounts.size() * 3);
104 | bool forward = true;
105 | for (size_t i = 0; i < faceVertexCounts.size(); i++)
106 | {
107 | int i0 = i + 0;
108 | int i1 = forward ? (i + 1) : (i + 2);
109 | int i2 = forward ? (i + 2) : (i + 1);
110 | outIndices[i * 3 + 0] = inIndices[i0];
111 | outIndices[i * 3 + 1] = inIndices[i1];
112 | outIndices[i * 3 + 2] = inIndices[i2];
113 | forward = !forward;
114 | }
115 | break;
116 | }
117 | case cgltf_primitive_type_triangle_fan: {
118 | if (inIndices.size() < 3)
119 | {
120 | TF_RUNTIME_ERROR("%s", INDICES_MISMATCH_ERROR_MSG);
121 | return false;
122 | }
123 | faceVertexCounts = VtIntArray(inIndices.size() - 2, 3);
124 | outIndices.resize(faceVertexCounts.size() * 3);
125 | for (size_t i = 0; i < faceVertexCounts.size(); i++)
126 | {
127 | outIndices[i * 3 + 0] = inIndices[0];
128 | outIndices[i * 3 + 1] = inIndices[i + 1];
129 | outIndices[i * 3 + 2] = inIndices[i + 2];
130 | }
131 | break;
132 | }
133 | default:
134 | TF_CODING_ERROR("unhandled primitive type %d", int(prim->type));
135 | return false;
136 | }
137 | return true;
138 | }
139 |
140 | void createFlatNormals(const VtIntArray& indices,
141 | const VtVec3fArray& positions,
142 | VtVec3fArray& normals)
143 | {
144 | TF_VERIFY((indices.size() % 3) == 0);
145 | normals.resize(positions.size());
146 |
147 | for (size_t i = 0; i < indices.size(); i += 3)
148 | {
149 | int i0 = indices[i + 0];
150 | int i1 = indices[i + 1];
151 | int i2 = indices[i + 2];
152 |
153 | const GfVec3f& p0 = positions[i0];
154 | const GfVec3f& p1 = positions[i1];
155 | const GfVec3f& p2 = positions[i2];
156 |
157 | GfVec3f e1 = (p1 - p0);
158 | GfVec3f e2 = (p2 - p0);
159 | e1.Normalize();
160 | e2.Normalize();
161 |
162 | GfVec3f n = GfCross(e1, e2);
163 | n.Normalize();
164 |
165 | normals[i0] = n;
166 | normals[i1] = n;
167 | normals[i2] = n;
168 | }
169 | }
170 |
171 | bool createTangents(const VtIntArray& indices,
172 | const VtVec3fArray& positions,
173 | const VtVec3fArray& normals,
174 | const VtVec2fArray& texcoords,
175 | VtFloatArray& unindexedSigns,
176 | VtVec3fArray& unindexedTangents)
177 | {
178 | TF_VERIFY(!texcoords.empty());
179 |
180 | int vertexCount = indices.size();
181 | unindexedTangents.resize(vertexCount);
182 | unindexedSigns.resize(vertexCount);
183 |
184 | struct UserData {
185 | const VtIntArray& indices;
186 | const VtVec3fArray& positions;
187 | const VtVec3fArray& normals;
188 | const VtVec2fArray& texcoords;
189 | VtFloatArray& unindexedSigns;
190 | VtVec3fArray& unindexedTangents;
191 | } userData = {
192 | indices, positions, normals, texcoords, unindexedSigns, unindexedTangents
193 | };
194 |
195 | auto getNumFacesFunc = [](const SMikkTSpaceContext* pContext) {
196 | UserData* userData = (UserData*) pContext->m_pUserData;
197 | return (int) userData->indices.size() / 3;
198 | };
199 |
200 | auto getNumVerticesOfFaceFunc = [](const SMikkTSpaceContext* pContext, const int iFace) {
201 | return 3;
202 | };
203 |
204 | auto getPositionFunc = [](const SMikkTSpaceContext* pContext, float fvPosOut[], const int iFace, const int iVert) {
205 | UserData* userData = (UserData*) pContext->m_pUserData;
206 | int vertexIndex = userData->indices[iFace * 3 + iVert];
207 | const GfVec3f& position = userData->positions[vertexIndex];
208 | fvPosOut[0] = position[0];
209 | fvPosOut[1] = position[1];
210 | fvPosOut[2] = position[2];
211 | };
212 |
213 | auto getNormalFunc = [](const SMikkTSpaceContext* pContext, float fvNormOut[], const int iFace, const int iVert) {
214 | UserData* userData = (UserData*) pContext->m_pUserData;
215 | int vertexIndex = userData->indices[iFace * 3 + iVert];
216 | const GfVec3f& normal = userData->normals[vertexIndex];
217 | fvNormOut[0] = normal[0];
218 | fvNormOut[1] = normal[1];
219 | fvNormOut[2] = normal[2];
220 | };
221 |
222 | auto getTexCoordFunc = [](const SMikkTSpaceContext* pContext, float fvTexcOut[], const int iFace, const int iVert) {
223 | UserData* userData = (UserData*) pContext->m_pUserData;
224 | int vertexIndex = userData->indices[iFace * 3 + iVert];
225 | const GfVec2f& texcoord = userData->texcoords[vertexIndex];
226 | fvTexcOut[0] = texcoord[0];
227 | fvTexcOut[1] = texcoord[1];
228 | };
229 |
230 | auto setTSpaceBasicFunc = [](const SMikkTSpaceContext* pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert) {
231 | UserData* userData = (UserData*) pContext->m_pUserData;
232 | int newVertexIndex = iFace * 3 + iVert;
233 | userData->unindexedTangents[newVertexIndex] = GfVec3f(fvTangent[0], fvTangent[1], fvTangent[2]);
234 | userData->unindexedSigns[newVertexIndex] = fSign;
235 | };
236 |
237 | SMikkTSpaceInterface interface;
238 | interface.m_getNumFaces = getNumFacesFunc;
239 | interface.m_getNumVerticesOfFace= getNumVerticesOfFaceFunc;
240 | interface.m_getPosition = getPositionFunc;
241 | interface.m_getNormal = getNormalFunc;
242 | interface.m_getTexCoord = getTexCoordFunc;
243 | interface.m_setTSpaceBasic = setTSpaceBasicFunc;
244 | interface.m_setTSpace = nullptr;
245 |
246 | SMikkTSpaceContext context;
247 | context.m_pInterface = &interface;
248 | context.m_pUserData = &userData;
249 |
250 | return genTangSpaceDefault(&context);
251 | }
252 | }
253 |
--------------------------------------------------------------------------------
/src/libguc/src/mesh.h:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Pablo Delgado Krämer
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | #pragma once
18 |
19 | #include
20 |
21 | #include
22 |
23 | using namespace PXR_NS;
24 |
25 | namespace guc
26 | {
27 | bool createGeometryRepresentation(const cgltf_primitive* prim,
28 | const VtIntArray& inIndices,
29 | VtIntArray& outIndices,
30 | VtIntArray& faceVertexCounts);
31 |
32 | void createFlatNormals(const VtIntArray& indices,
33 | const VtVec3fArray& positions,
34 | VtVec3fArray& normals);
35 |
36 | bool createTangents(const VtIntArray& indices,
37 | const VtVec3fArray& positions,
38 | const VtVec3fArray& normals,
39 | const VtVec2fArray& texcoords,
40 | VtFloatArray& signs,
41 | VtVec3fArray& tangents);
42 | }
43 |
--------------------------------------------------------------------------------
/src/libguc/src/naming.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Pablo Delgado Krämer
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | #include "naming.h"
18 |
19 | #include
20 | #include
21 | #include
22 |
23 | #include
24 |
25 | namespace fs = std::filesystem;
26 | namespace mx = MaterialX;
27 |
28 | namespace guc
29 | {
30 | static const SdfPath s_entryPaths[] = {
31 | SdfPath{ "/Asset" },
32 | SdfPath{ "/Asset/Scenes" },
33 | SdfPath{ "/Asset/Nodes" },
34 | SdfPath{ "/Asset/Materials" },
35 | SdfPath{ "/Asset/Materials/UsdPreviewSurface" },
36 | SdfPath{ "/Asset/Materials/MaterialX" },
37 | SdfPath{ "/Asset/Meshes" },
38 | SdfPath{ "/Asset/Cameras" },
39 | SdfPath{ "/Asset/Lights" }
40 | };
41 | static_assert(sizeof(s_entryPaths) / sizeof(s_entryPaths[0]) == size_t(EntryPathType::ENUM_SIZE));
42 |
43 | const SdfPath& getEntryPath(EntryPathType type)
44 | {
45 | return s_entryPaths[size_t(type)];
46 | }
47 |
48 | const char* getMaterialVariantSetName()
49 | {
50 | return "shadingVariant";
51 | }
52 |
53 | std::string normalizeVariantName(const std::string& name)
54 | {
55 | return TfMakeValidIdentifier(name);
56 | }
57 |
58 | std::string makeStSetName(int index)
59 | {
60 | std::string name = UsdUtilsGetPrimaryUVSetName(); // likely to be "st"
61 | if (index == 0)
62 | {
63 | return name;
64 | }
65 | return name + std::to_string(index);
66 | }
67 |
68 | std::string makeColorSetName(int index)
69 | {
70 | // The primvar name for colors is not standardized. I have chosen 'color' for it,
71 | // and give reasons against the other suggestions discussed in this forum thread:
72 | // https://groups.google.com/g/usd-interest/c/VOkh0aj-8bU/m/zxrMQ-pJAgAJ
73 | //
74 | // 'colorSet': Maya seems to use this primvar name, however if there's a colorSet,
75 | // there should also be a texCoordSet / stSet.
76 | // 'vertexColor': includes the interpolation mode, of which USD has a few. We don't
77 | // use "vertexTangents" etc., although we emit per-vertex tangents.
78 | //
79 | // Furthermore, 'color' maps directly to the COLOR_ glTF attribute name and goes well
80 | // with the already existing 'displayColor' primvar. It's just not for the 'display'
81 | // purpose, but rather part of the actual data used for shading.
82 | std::string name = "color";
83 | if (index == 0)
84 | {
85 | return name;
86 | }
87 | return name + std::to_string(index);
88 | }
89 |
90 | std::string makeOpacitySetName(int index)
91 | {
92 | std::string name = "opacity";
93 | if (index == 0)
94 | {
95 | return name;
96 | }
97 | return name + std::to_string(index);
98 | }
99 |
100 | const static std::unordered_set MTLX_TYPE_NAME_SET = {
101 | /* Basic data types */
102 | "integer", "boolean", "float", "color3", "color4", "vector2", "vector3",
103 | "vector4", "matrix33", "matrix44", "string", "filename", "geomname", "integerarray",
104 | "floatarray", "color3array", "color4array", "vector2array", "vector3array",
105 | "vector4array", "stringarray", "geomnamearray",
106 | /* Custom data types */
107 | "color", "shader", "material"
108 | };
109 |
110 | const char* DEFAULT_MATERIAL_NAME = "mat";
111 |
112 | std::string makeUniqueMaterialName(std::string baseName,
113 | const std::unordered_set& existingNames)
114 | {
115 | if (baseName.empty())
116 | {
117 | baseName = DEFAULT_MATERIAL_NAME;
118 | }
119 | else
120 | {
121 | baseName = mx::createValidName(baseName);
122 | baseName = TfMakeValidIdentifier(baseName);
123 |
124 | // Remove underscore prefix to prevent compile errors in HdStorm
125 | if (baseName[0] == '_')
126 | {
127 | baseName = DEFAULT_MATERIAL_NAME + baseName;
128 | }
129 | }
130 |
131 | std::string name = baseName;
132 |
133 | int i = 1;
134 | while (existingNames.find(name) != existingNames.end() ||
135 | MTLX_TYPE_NAME_SET.find(name) != MTLX_TYPE_NAME_SET.end())
136 | {
137 | name = baseName + "_" + std::to_string(i);
138 | i++;
139 | }
140 |
141 | return name;
142 | }
143 |
144 | const char* DEFAULT_IMAGE_FILENAME = "img";
145 |
146 | std::string makeUniqueImageFileName(const char* nameHint,
147 | const std::string& fileName,
148 | const std::string& fileExt,
149 | const std::unordered_set& existingNames)
150 | {
151 | std::string baseName = fileName;
152 |
153 | if (baseName.empty() && nameHint)
154 | {
155 | baseName = TfMakeValidIdentifier(nameHint);
156 | }
157 |
158 | baseName = fs::path(baseName).replace_extension("").string(); // remove ext if already in img name
159 |
160 | if (baseName.empty())
161 | {
162 | baseName = DEFAULT_IMAGE_FILENAME;
163 | }
164 |
165 | auto finalName = baseName + fileExt;
166 |
167 | int i = 1;
168 | while (existingNames.find(finalName) != existingNames.end())
169 | {
170 | finalName = baseName + "_" + std::to_string(i) + fileExt;
171 | i++;
172 | }
173 |
174 | return finalName;
175 | }
176 |
177 | SdfPath makeUniqueStageSubpath(UsdStageRefPtr stage,
178 | const SdfPath& root,
179 | const std::string& baseName,
180 | const std::string& delimiter)
181 | {
182 | SdfPath finalPath = root.AppendElementString(TfMakeValidIdentifier(baseName));
183 |
184 | std::string basePath = finalPath.GetAsString();
185 | // FIXME: evaluate performance impact of GetObjectAtPath compared to simple hashmap
186 | for (int i = 1; stage->GetObjectAtPath(finalPath); i++)
187 | {
188 | auto newPathStr = basePath + delimiter + std::to_string(i);
189 | TF_VERIFY(SdfPath::IsValidPathString(newPathStr));
190 | finalPath = SdfPath(newPathStr);
191 | }
192 |
193 | return finalPath;
194 | }
195 |
196 | SdfPath makeMtlxMaterialPath(const std::string& materialName)
197 | {
198 | return getEntryPath(EntryPathType::MaterialXMaterials).AppendElementString("Materials").AppendElementString(materialName);
199 | }
200 |
201 | SdfPath makeUsdPreviewSurfaceMaterialPath(const std::string& materialName)
202 | {
203 | return getEntryPath(EntryPathType::PreviewMaterials).AppendElementString("Materials").AppendElementString(materialName);
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/src/libguc/src/naming.h:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Pablo Delgado Krämer
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | #pragma once
18 |
19 | #include
20 |
21 | #include
22 |
23 | using namespace PXR_NS;
24 |
25 | namespace guc
26 | {
27 | enum class EntryPathType
28 | {
29 | Root = 0,
30 | Scenes,
31 | Nodes,
32 | Materials,
33 | PreviewMaterials,
34 | MaterialXMaterials,
35 | Meshes,
36 | Cameras,
37 | Lights,
38 | ENUM_SIZE
39 | };
40 | const SdfPath& getEntryPath(EntryPathType type);
41 |
42 | const char* getMaterialVariantSetName();
43 | std::string normalizeVariantName(const std::string& name);
44 |
45 | std::string makeStSetName(int index);
46 | std::string makeColorSetName(int index);
47 | std::string makeOpacitySetName(int index);
48 |
49 | std::string makeUniqueMaterialName(std::string baseName,
50 | const std::unordered_set& existingNames);
51 |
52 | std::string makeUniqueImageFileName(const char* nameHint,
53 | const std::string& fileName,
54 | const std::string& fileExt,
55 | const std::unordered_set& existingNames);
56 |
57 | SdfPath makeUniqueStageSubpath(UsdStageRefPtr stage,
58 | const SdfPath& root,
59 | const std::string& baseName,
60 | const std::string& delimiter = "_");
61 |
62 | SdfPath makeMtlxMaterialPath(const std::string& materialName);
63 |
64 | SdfPath makeUsdPreviewSurfaceMaterialPath(const std::string& materialName);
65 | }
66 |
--------------------------------------------------------------------------------
/src/libguc/src/usdpreviewsurface.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Pablo Delgado Krämer
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | #include "usdpreviewsurface.h"
18 |
19 | #include
20 | #include
21 | #include
22 |
23 | #include
24 |
25 | #include "naming.h"
26 | #include "debugCodes.h"
27 | #include "cgltf_util.h"
28 |
29 | namespace fs = std::filesystem;
30 |
31 | TF_DEFINE_PRIVATE_TOKENS(
32 | _tokens,
33 | // Shading node IDs
34 | (UsdPreviewSurface)
35 | (UsdUVTexture)
36 | (UsdPrimvarReader_float2)
37 | (UsdTransform2d)
38 | // UsdPreviewSurface inputs
39 | (emissiveColor)
40 | (occlusion)
41 | (normal)
42 | (opacityThreshold)
43 | (diffuseColor)
44 | (opacity)
45 | (metallic)
46 | (roughness)
47 | (clearcoat)
48 | (clearcoatRoughness)
49 | (ior)
50 | (specularColor)
51 | (useSpecularWorkflow)
52 | // UsdUVTexture inputs
53 | (st)
54 | (file)
55 | (scale)
56 | (bias)
57 | (fallback)
58 | (wrapS)
59 | (wrapT)
60 | (sourceColorSpace)
61 | // UsdUVTexture outputs
62 | (r)
63 | (g)
64 | (b)
65 | (a)
66 | (rgb)
67 | // UsdUVTexture wrap modes
68 | (clamp)
69 | (mirror)
70 | (repeat)
71 | // UsdUVTexture color spaces
72 | (raw)
73 | (sRGB)
74 | // UsdPrimvarReader_float2 input and output
75 | (varname)
76 | (result)
77 | // UsdTransform2d inputs
78 | (in)
79 | (rotation)
80 | (translation)
81 | );
82 |
83 | namespace detail
84 | {
85 | void setChannelInputValues(const UsdShadeInput& input, GfVec4f value, const TfToken& channels)
86 | {
87 | if (channels == _tokens->rgb) {
88 | input.Set(GfVec3f(value[0], value[1], value[2]));
89 | }
90 | else if (channels == _tokens->r) {
91 | input.Set(value[0]);
92 | }
93 | else if (channels == _tokens->g) {
94 | input.Set(value[1]);
95 | }
96 | else if (channels == _tokens->b) {
97 | input.Set(value[2]);
98 | }
99 | else if (channels == _tokens->a) {
100 | input.Set(value[3]);
101 | }
102 | else {
103 | TF_CODING_ERROR("unhandled input channel");
104 | }
105 | }
106 |
107 | TfToken convertWrapMode(int wrapMode)
108 | {
109 | switch (wrapMode)
110 | {
111 | case 33071 /* CLAMP_TO_EDGE */:
112 | return _tokens->clamp;
113 | case 33648 /* MIRRORED_REPEAT */:
114 | return _tokens->mirror;
115 | default:
116 | TF_CODING_ERROR("invalid wrap mode");
117 | case 0:
118 | // use glTF default
119 | case 10497 /* REPEAT */:
120 | return _tokens->repeat;
121 | }
122 | }
123 |
124 | void connectTextureInputOutput(UsdShadeInput& input, UsdShadeShader& node, const TfToken& channels)
125 | {
126 | auto valueType = channels == _tokens->rgb ? SdfValueTypeNames->Float3 : SdfValueTypeNames->Float;
127 | auto output = node.CreateOutput(channels, valueType);
128 | input.ConnectToSource(output);
129 | }
130 | }
131 |
132 | namespace guc
133 | {
134 | UsdPreviewSurfaceMaterialConverter::UsdPreviewSurfaceMaterialConverter(UsdStageRefPtr stage,
135 | const ImageMetadataMap& imageMetadataMap)
136 | : m_stage(stage)
137 | , m_imageMetadataMap(imageMetadataMap)
138 | {
139 | }
140 |
141 | void UsdPreviewSurfaceMaterialConverter::convert(const cgltf_material* material, const SdfPath& path)
142 | {
143 | auto shadeMaterial = UsdShadeMaterial::Define(m_stage, path);
144 | auto surfaceOutput = shadeMaterial.CreateSurfaceOutput(UsdShadeTokens->universalRenderContext);
145 |
146 | // FIXME: the first node will be called 'node' while MaterialX's first node is 'node1'
147 | const char* nodeNameNumberDelimiter = ""; // mimic MaterialX nodename generation with no delimiter between "node" and number
148 | auto shaderPath = makeUniqueStageSubpath(m_stage, path, "node", nodeNameNumberDelimiter);
149 | auto shader = UsdShadeShader::Define(m_stage, shaderPath);
150 | shader.CreateIdAttr(VtValue(_tokens->UsdPreviewSurface));
151 | auto shaderOutput = shader.CreateOutput(UsdShadeTokens->surface, SdfValueTypeNames->Token);
152 | surfaceOutput.ConnectToSource(shaderOutput);
153 |
154 | auto setAlphaTextureInput = [&](const cgltf_pbr_metallic_roughness* pbrMetallicRoughness)
155 | {
156 | if (material->alpha_mode == cgltf_alpha_mode_opaque)
157 | {
158 | // Do nothing.
159 | return;
160 | }
161 |
162 | GfVec4f opacityFallback(1.0f); // image fallback value is 0.0, but opacity default should be 1.0
163 |
164 | auto opacityInput = shader.CreateInput(_tokens->opacity, SdfValueTypeNames->Float);
165 | setFloatTextureInput(path, opacityInput, pbrMetallicRoughness->base_color_texture, _tokens->a, GfVec4f(pbrMetallicRoughness->base_color_factor[3]), &opacityFallback);
166 |
167 | if (material->alpha_mode == cgltf_alpha_mode_mask)
168 | {
169 | auto opacityThresholdInput = shader.CreateInput(_tokens->opacityThreshold, SdfValueTypeNames->Float);
170 | opacityThresholdInput.Set(material->alpha_cutoff);
171 | }
172 | };
173 |
174 | auto emissiveColorInput = shader.CreateInput(_tokens->emissiveColor, SdfValueTypeNames->Color3f);
175 | auto emissiveFactor = GfVec4f(material->emissive_factor);
176 |
177 | if (material->unlit) // Not really 'unlit', but there's no better way
178 | {
179 | if (material->has_pbr_metallic_roughness)
180 | {
181 | const cgltf_pbr_metallic_roughness* pbrMetallicRoughness = &material->pbr_metallic_roughness;
182 |
183 | GfVec4f unlitColorFallback(1.0f); // not specified
184 |
185 | setSrgbTextureInput(path, emissiveColorInput, pbrMetallicRoughness->base_color_texture, GfVec4f(pbrMetallicRoughness->base_color_factor), &unlitColorFallback);
186 | setAlphaTextureInput(pbrMetallicRoughness);
187 | }
188 | else
189 | {
190 | emissiveColorInput.Set(GfVec3f(1.0f));
191 | }
192 | return;
193 | }
194 |
195 | if (material->has_emissive_strength)
196 | {
197 | const cgltf_emissive_strength* emissiveStrength = &material->emissive_strength;
198 | emissiveFactor *= emissiveStrength->emissive_strength;
199 | }
200 | setSrgbTextureInput(path, emissiveColorInput, material->emissive_texture, emissiveFactor);
201 |
202 | auto occlusionInput = shader.CreateInput(_tokens->occlusion, SdfValueTypeNames->Float);
203 | setOcclusionTextureInput(path, occlusionInput, material->occlusion_texture);
204 |
205 | auto normalInput = shader.CreateInput(_tokens->normal, SdfValueTypeNames->Normal3f);
206 | setNormalTextureInput(path, normalInput, material->normal_texture);
207 |
208 | // We need to set these values regardless of whether pbrMetallicRoughness is present or not, because UsdPreviewSurface's
209 | // default values differ (and we want to come as close as possible to the MaterialX look, although the shading model differs).
210 | auto diffuseColorInput = shader.CreateInput(_tokens->diffuseColor, SdfValueTypeNames->Color3f);
211 | auto metallicInput = shader.CreateInput(_tokens->metallic, SdfValueTypeNames->Float);
212 | auto roughnessInput = shader.CreateInput(_tokens->roughness, SdfValueTypeNames->Float);
213 |
214 | if (material->has_pbr_metallic_roughness)
215 | {
216 | const cgltf_pbr_metallic_roughness* pbrMetallicRoughness = &material->pbr_metallic_roughness;
217 |
218 | GfVec4f diffuseColorFallback(1.0f); // same as glTF spec sec. 5.22.2: "When undefined, the texture MUST be sampled as having 1.0 in all components."
219 | setSrgbTextureInput(path, diffuseColorInput, pbrMetallicRoughness->base_color_texture, GfVec4f(pbrMetallicRoughness->base_color_factor), &diffuseColorFallback);
220 | GfVec4f metallicRoughnessFallback(1.0f); // same as glTF spec sec. 5.22.5: "When undefined, the texture MUST be sampled as having 1.0 in G and B components."
221 | setFloatTextureInput(path, metallicInput, pbrMetallicRoughness->metallic_roughness_texture, _tokens->b, GfVec4f(pbrMetallicRoughness->metallic_factor), &metallicRoughnessFallback);
222 | setFloatTextureInput(path, roughnessInput, pbrMetallicRoughness->metallic_roughness_texture, _tokens->g, GfVec4f(pbrMetallicRoughness->roughness_factor), &metallicRoughnessFallback);
223 | setAlphaTextureInput(pbrMetallicRoughness);
224 | }
225 | else
226 | {
227 | diffuseColorInput.Set(GfVec3f(1.0f)); // 0.18 in UsdPreviewSurface spec
228 | metallicInput.Set(1.0f); // 0.0 in UsdPreviewSurface spec
229 | roughnessInput.Set(1.0f); // 0.5 in UsdPreviewSurface spec
230 | }
231 |
232 | if (material->has_clearcoat)
233 | {
234 | const cgltf_clearcoat* clearcoat = &material->clearcoat;
235 |
236 | // see glTF clearcoat extension spec: "If the clearcoatTexture or clearcoatRoughnessTexture is not given, respective texture components are assumed to have a value of 1.0."
237 | GfVec4f clearcoatFallback(1.0f);
238 | GfVec4f clearcoatRoughnessFallback(1.0f);
239 |
240 | auto clearcoatInput = shader.CreateInput(_tokens->clearcoat, SdfValueTypeNames->Float);
241 | setFloatTextureInput(path, clearcoatInput, clearcoat->clearcoat_texture, _tokens->r, GfVec4f(clearcoat->clearcoat_factor), &clearcoatFallback);
242 |
243 | auto clearcoatRoughnessInput = shader.CreateInput(_tokens->clearcoatRoughness, SdfValueTypeNames->Float);
244 | setFloatTextureInput(path, clearcoatRoughnessInput, clearcoat->clearcoat_roughness_texture, _tokens->g, GfVec4f(clearcoat->clearcoat_roughness_factor), &clearcoatRoughnessFallback);
245 | }
246 |
247 | if (material->has_ior)
248 | {
249 | auto iorInput = shader.CreateInput(_tokens->ior, SdfValueTypeNames->Float);
250 |
251 | const cgltf_ior* ior = &material->ior;
252 | iorInput.Set(ior->ior);
253 | }
254 |
255 | if (material->has_specular)
256 | {
257 | const cgltf_specular* specular = &material->specular;
258 |
259 | GfVec4f specularColorFallback(1.0f); // use default from glTF specular ext spec
260 |
261 | auto specularColorInput = shader.CreateInput(_tokens->specularColor, SdfValueTypeNames->Color3f);
262 | setSrgbTextureInput(path, specularColorInput, specular->specular_color_texture, GfVec4f(specular->specular_color_factor), &specularColorFallback);
263 |
264 | auto useSpecularWorkflowInput = shader.CreateInput(_tokens->useSpecularWorkflow, SdfValueTypeNames->Int);
265 | useSpecularWorkflowInput.Set(1);
266 | }
267 |
268 | // Transmissive materials are rendered translucently (but prefer explicit alpha)
269 | if (material->has_transmission && material->alpha_mode == cgltf_alpha_mode_opaque && material->has_pbr_metallic_roughness)
270 | {
271 | const cgltf_transmission* transmission = &material->transmission;
272 | const cgltf_pbr_metallic_roughness* pbrMetallicRoughness = &material->pbr_metallic_roughness;
273 | const cgltf_float* baseColorFactor = pbrMetallicRoughness->base_color_factor;
274 |
275 | // Hand-crafted heuristic based on HSV 'saturation' value. range: [0.25, 1.0]
276 | float baseColorMaxValue = std::max(baseColorFactor[0], std::max(baseColorFactor[1], baseColorFactor[2]));
277 | float baseColorMinValue = std::min(baseColorFactor[0], std::min(baseColorFactor[1], baseColorFactor[2]));
278 | float baseColorRange = baseColorMaxValue - baseColorMinValue;
279 | float saturation = (baseColorMaxValue <= std::numeric_limits::min()) ? 0.0f : (baseColorRange / baseColorMaxValue);
280 | float opacity = 0.25f + 0.75f * saturation * (1.0f - transmission->transmission_factor);
281 |
282 | auto opacityInput = shader.CreateInput(_tokens->opacity, SdfValueTypeNames->Float); // not set because of OPAQUE mode check
283 | opacityInput.Set(opacity);
284 | }
285 | }
286 |
287 | void UsdPreviewSurfaceMaterialConverter::setNormalTextureInput(const SdfPath& basePath,
288 | UsdShadeInput& shaderInput,
289 | const cgltf_texture_view& textureView)
290 | {
291 | // glTF spec 2.0 3.9.3: transform [0, 1] value range to [-1, 1].
292 | // We also scale the normal although this does not guarantee that the resulting vector is normalized.
293 | float xyScale = 2.0f * textureView.scale;
294 | float xyBias = -1.0f * textureView.scale;
295 | GfVec4f scale(xyScale, xyScale, 2.0f, 1.0f);
296 | GfVec4f bias(xyBias, xyBias, -1.0f, 0.0f);
297 | GfVec4f fallback(0.5f, 0.5f, 1.0f, 0.0f); // glTF fallback normal
298 |
299 | UsdShadeShader textureNode;
300 | if (!addTextureNode(basePath, textureView, _tokens->raw, &scale, &bias, &fallback, textureNode))
301 | {
302 | return;
303 | }
304 |
305 | detail::connectTextureInputOutput(shaderInput, textureNode, _tokens->rgb);
306 | }
307 |
308 | void UsdPreviewSurfaceMaterialConverter::setOcclusionTextureInput(const SdfPath& basePath,
309 | UsdShadeInput& shaderInput,
310 | const cgltf_texture_view& textureView)
311 | {
312 | // glTF spec 2.0 3.9.3.
313 | // if 'strength' attribute is present, it affects occlusion as follows:
314 | // 1.0 + strength * (occlusionTexture - 1.0)
315 | //
316 | // we multiply that out since we only have scale and bias (value * scale + bias) to
317 | // occlusionTexture * strength + (1.0 - strength)
318 | // ---------------- ++++++++ ~~~~~~~~~~~~~~~~
319 | // (value) * scale + bias
320 | GfVec4f scale(textureView.scale);
321 | GfVec4f bias(1.0f - textureView.scale);
322 | GfVec4f fallback(1.0f); // image fallback value is 0.0, but default occlusion value should be 1.0
323 |
324 | UsdShadeShader textureNode;
325 | if (!addTextureNode(basePath, textureView, _tokens->raw, &scale, &bias, &fallback, textureNode))
326 | {
327 | return;
328 | }
329 |
330 | detail::connectTextureInputOutput(shaderInput, textureNode, _tokens->r);
331 | }
332 |
333 | void UsdPreviewSurfaceMaterialConverter::setSrgbTextureInput(const SdfPath& basePath,
334 | UsdShadeInput& shaderInput,
335 | const cgltf_texture_view& textureView,
336 | const GfVec4f& factor,
337 | const GfVec4f* fallback)
338 | {
339 | setTextureInput(basePath, shaderInput, textureView, _tokens->rgb, _tokens->sRGB, &factor, nullptr, fallback);
340 | }
341 |
342 | void UsdPreviewSurfaceMaterialConverter::setFloatTextureInput(const SdfPath& basePath,
343 | UsdShadeInput& shaderInput,
344 | const cgltf_texture_view& textureView,
345 | const TfToken& channel,
346 | const GfVec4f& factor,
347 | const GfVec4f* fallback)
348 | {
349 | setTextureInput(basePath, shaderInput, textureView, channel, _tokens->raw, &factor, nullptr, fallback);
350 | }
351 |
352 | void UsdPreviewSurfaceMaterialConverter::setTextureInput(const SdfPath& basePath,
353 | UsdShadeInput& shaderInput,
354 | const cgltf_texture_view& textureView,
355 | const TfToken& channels,
356 | const TfToken& colorSpace,
357 | const GfVec4f* scale,
358 | const GfVec4f* bias,
359 | const GfVec4f* fallback)
360 | {
361 | UsdShadeShader textureNode;
362 | if (addTextureNode(basePath, textureView, colorSpace, scale, bias, fallback, textureNode))
363 | {
364 | // "If a two-channel texture is fed into a UsdUVTexture, the r, g, and b components of the rgb output will
365 | // repeat the first channel's value, while the single a output will be set to the second channel's value."
366 | int channelCount = getTextureChannelCount(textureView);
367 | bool remapChannelToAlpha = (channelCount == 2 && channels == _tokens->g);
368 |
369 | detail::connectTextureInputOutput(shaderInput, textureNode, remapChannelToAlpha ? _tokens->a : channels);
370 | }
371 | else if (scale)
372 | {
373 | detail::setChannelInputValues(shaderInput, *scale, channels);
374 | }
375 | }
376 |
377 | void UsdPreviewSurfaceMaterialConverter::addTextureTransformNode(const SdfPath& basePath,
378 | const cgltf_texture_transform& transform,
379 | int stIndex,
380 | UsdShadeInput& textureStInput)
381 | {
382 | auto nodePath = makeUniqueStageSubpath(m_stage, basePath, "node", "");
383 |
384 | UsdShadeShader node = UsdShadeShader::Define(m_stage, nodePath);
385 | node.CreateIdAttr(VtValue(_tokens->UsdTransform2d));
386 |
387 | auto rotationInput = node.CreateInput(_tokens->rotation, SdfValueTypeNames->Float);
388 | rotationInput.Set(float(GfRadiansToDegrees(transform.rotation)));
389 |
390 | auto scaleInput = node.CreateInput(_tokens->scale, SdfValueTypeNames->Float2);
391 | scaleInput.Set(GfVec2f(transform.scale[0], transform.scale[1]));
392 |
393 | // Modify offset to account for glTF-USD differences in rotation pivot and y-origin
394 | GfVec2f offset(transform.offset[0], -transform.offset[1]);
395 | offset[0] += sinf(transform.rotation) * transform.scale[1];
396 | offset[1] += 1.0f - (cosf(transform.rotation) * transform.scale[1]);
397 |
398 | auto translationInput = node.CreateInput(_tokens->translation, SdfValueTypeNames->Float2);
399 | translationInput.Set(offset);
400 |
401 | auto untransformedInput = node.CreateInput(_tokens->in, SdfValueTypeNames->Float2);
402 | setStPrimvarInput(untransformedInput, basePath, stIndex);
403 |
404 | auto transformedOutput = node.CreateOutput(_tokens->result, SdfValueTypeNames->Float2);
405 | textureStInput.ConnectToSource(transformedOutput);
406 | }
407 |
408 | bool UsdPreviewSurfaceMaterialConverter::addTextureNode(const SdfPath& basePath,
409 | const cgltf_texture_view& textureView,
410 | const TfToken& colorSpace,
411 | const GfVec4f* scale,
412 | const GfVec4f* bias,
413 | const GfVec4f* fallback,
414 | UsdShadeShader& node)
415 | {
416 | std::string filePath;
417 | if (!getTextureFilePath(textureView, filePath))
418 | {
419 | return false;
420 | }
421 |
422 | auto nodePath = makeUniqueStageSubpath(m_stage, basePath, "node", "");
423 | node = UsdShadeShader::Define(m_stage, nodePath);
424 | node.CreateIdAttr(VtValue(_tokens->UsdUVTexture));
425 |
426 | auto fileInput = node.CreateInput(_tokens->file, SdfValueTypeNames->Asset);
427 | fileInput.Set(SdfAssetPath(filePath));
428 |
429 | if (scale)
430 | {
431 | auto scaleInput = node.CreateInput(_tokens->scale, SdfValueTypeNames->Float4);
432 | scaleInput.Set(*scale);
433 | }
434 |
435 | if (bias)
436 | {
437 | auto biasInput = node.CreateInput(_tokens->bias, SdfValueTypeNames->Float4);
438 | biasInput.Set(*bias);
439 | }
440 |
441 | if (fallback)
442 | {
443 | auto fallbackInput = node.CreateInput(_tokens->fallback, SdfValueTypeNames->Float4);
444 | fallbackInput.Set(*fallback);
445 | }
446 |
447 | auto sourceColorSpaceInput = node.CreateInput(_tokens->sourceColorSpace, SdfValueTypeNames->Token);
448 | sourceColorSpaceInput.Set(colorSpace);
449 |
450 | const cgltf_sampler* sampler = textureView.texture->sampler;
451 |
452 | // glTF spec sec. 5.29.1. texture sampler: "When undefined, a sampler with repeat wrapping [..] SHOULD be used."
453 | auto wrapSInput = node.CreateInput(_tokens->wrapS, SdfValueTypeNames->Token);
454 | wrapSInput.Set(sampler ? detail::convertWrapMode(sampler->wrap_s) : _tokens->repeat);
455 |
456 | auto wrapTInput = node.CreateInput(_tokens->wrapT, SdfValueTypeNames->Token);
457 | wrapTInput.Set(sampler ? detail::convertWrapMode(sampler->wrap_t) : _tokens->repeat);
458 |
459 | // Tex coords come from either a primvar or the output of a UsdTransform2d node
460 | auto stInput = node.CreateInput(_tokens->st, SdfValueTypeNames->Float2);
461 |
462 | const cgltf_texture_transform& transform = textureView.transform;
463 | int stIndex = (textureView.has_transform && transform.has_texcoord) ? transform.texcoord : textureView.texcoord;
464 |
465 | if (textureView.has_transform && cgltf_transform_required(transform))
466 | {
467 | addTextureTransformNode(basePath, transform, stIndex, stInput);
468 | }
469 | else
470 | {
471 | setStPrimvarInput(stInput, basePath, stIndex);
472 | }
473 |
474 | return true;
475 | }
476 |
477 | void UsdPreviewSurfaceMaterialConverter::setStPrimvarInput(UsdShadeInput& input,
478 | const SdfPath& nodeBasePath,
479 | int stIndex)
480 | {
481 | auto nodePath = makeUniqueStageSubpath(m_stage, nodeBasePath, "node", "");
482 | auto node = UsdShadeShader::Define(m_stage, nodePath);
483 | node.CreateIdAttr(VtValue(_tokens->UsdPrimvarReader_float2));
484 |
485 | auto varnameInput = node.CreateInput(_tokens->varname, SdfValueTypeNames->String);
486 | varnameInput.Set(makeStSetName(stIndex));
487 |
488 | auto output = node.CreateOutput(_tokens->result, SdfValueTypeNames->Float2);
489 | input.ConnectToSource(output);
490 | }
491 |
492 | bool UsdPreviewSurfaceMaterialConverter::getTextureMetadata(const cgltf_texture_view& textureView, ImageMetadata& metadata) const
493 | {
494 | const cgltf_texture* texture = textureView.texture;
495 | if (!texture)
496 | {
497 | return false;
498 | }
499 |
500 | const cgltf_image* image = texture->image;
501 | if (!image)
502 | {
503 | return false;
504 | }
505 |
506 | auto iter = m_imageMetadataMap.find(image);
507 | if (iter == m_imageMetadataMap.end())
508 | {
509 | return false;
510 | }
511 |
512 | metadata = iter->second;
513 | return true;
514 | }
515 |
516 | bool UsdPreviewSurfaceMaterialConverter::getTextureFilePath(const cgltf_texture_view& textureView, std::string& fileName) const
517 | {
518 | ImageMetadata metadata;
519 | if (!getTextureMetadata(textureView, metadata))
520 | {
521 | return false;
522 | }
523 | fileName = metadata.refPath;
524 | return true;
525 | }
526 |
527 | int UsdPreviewSurfaceMaterialConverter::getTextureChannelCount(const cgltf_texture_view& textureView) const
528 | {
529 | ImageMetadata metadata;
530 | TF_VERIFY(getTextureMetadata(textureView, metadata));
531 | return metadata.channelCount;
532 | }
533 | }
534 |
--------------------------------------------------------------------------------
/src/libguc/src/usdpreviewsurface.h:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2022 Pablo Delgado Krämer
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | #pragma once
18 |
19 | #include
20 |
21 | #include
22 | #include
23 |
24 | #include "image.h"
25 |
26 | using namespace PXR_NS;
27 |
28 | namespace guc
29 | {
30 | class UsdPreviewSurfaceMaterialConverter
31 | {
32 | public:
33 | UsdPreviewSurfaceMaterialConverter(UsdStageRefPtr stage,
34 | const ImageMetadataMap& imageMetadataMap);
35 |
36 | void convert(const cgltf_material* material, const SdfPath& path);
37 |
38 | private:
39 | UsdStageRefPtr m_stage;
40 | const ImageMetadataMap& m_imageMetadataMap;
41 |
42 | private:
43 | void setNormalTextureInput(const SdfPath& basePath,
44 | UsdShadeInput& shaderInput,
45 | const cgltf_texture_view& textureView);
46 |
47 | void setOcclusionTextureInput(const SdfPath& basePath,
48 | UsdShadeInput& shaderInput,
49 | const cgltf_texture_view& textureView);
50 |
51 | void setSrgbTextureInput(const SdfPath& basePath,
52 | UsdShadeInput& shaderInput,
53 | const cgltf_texture_view& textureView,
54 | const GfVec4f& factor,
55 | const GfVec4f* fallback = nullptr);
56 |
57 | void setFloatTextureInput(const SdfPath& basePath,
58 | UsdShadeInput& shaderInput,
59 | const cgltf_texture_view& textureView,
60 | const TfToken& channel,
61 | const GfVec4f& factor,
62 | const GfVec4f* fallback = nullptr);
63 |
64 | private:
65 | void setTextureInput(const SdfPath& basePath,
66 | UsdShadeInput& shaderInput,
67 | const cgltf_texture_view& textureView,
68 | const TfToken& channels,
69 | const TfToken& colorSpace,
70 | const GfVec4f* scale,
71 | const GfVec4f* bias,
72 | const GfVec4f* fallback);
73 |
74 | void addTextureTransformNode(const SdfPath& basePath,
75 | const cgltf_texture_transform& transform,
76 | int stIndex,
77 | UsdShadeInput& textureStInput);
78 |
79 | bool addTextureNode(const SdfPath& basePath,
80 | const cgltf_texture_view& textureView,
81 | const TfToken& colorSpace,
82 | const GfVec4f* scale,
83 | const GfVec4f* bias,
84 | const GfVec4f* fallback,
85 | UsdShadeShader& node);
86 |
87 | void setStPrimvarInput(UsdShadeInput& input, const SdfPath& nodeBasePath, int stIndex);
88 |
89 | private:
90 | bool getTextureMetadata(const cgltf_texture_view& textureView, ImageMetadata& metadata) const;
91 | bool getTextureFilePath(const cgltf_texture_view& textureView, std::string& filePath) const;
92 | int getTextureChannelCount(const cgltf_texture_view& textureView) const;
93 | };
94 | }
95 |
--------------------------------------------------------------------------------