diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5b2354da24..fecdbf7a52 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -203,7 +203,7 @@ jobs: run: | python MaterialXTest/main.py python MaterialXTest/genshader.py - python Scripts/mxformat.py ../resources/Materials/TestSuite/stdlib/upgrade --yes --upgradeVersion True + python Scripts/mxformat.py ../resources/Materials/TestSuite/stdlib/upgrade --yes --upgrade python Scripts/mxvalidate.py ../resources/Materials/Examples/StandardSurface/standard_surface_marble_solid.mtlx --stdlib --verbose python Scripts/mxdoc.py --docType md ../libraries/pbrlib/pbrlib_defs.mtlx python Scripts/mxdoc.py --docType html ../libraries/bxdf/standard_surface.mtlx diff --git a/libraries/lights/genmsl/lights_genmsl_impl.mtlx b/libraries/lights/genmsl/lights_genmsl_impl.mtlx index 1b1782638c..90b4917a8a 100644 --- a/libraries/lights/genmsl/lights_genmsl_impl.mtlx +++ b/libraries/lights/genmsl/lights_genmsl_impl.mtlx @@ -1,5 +1,5 @@ - + diff --git a/libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl b/libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl index d66f3a8cfb..575991e28d 100644 --- a/libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl +++ b/libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl @@ -23,6 +23,7 @@ vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distributio // Compute derived properties. float NdotV = clamp(V.z, M_FLOAT_EPS, 1.0); float avgAlpha = mx_average_alpha(alpha); + float G1V = mx_ggx_smith_G1(NdotV, avgAlpha); // Integrate outgoing radiance using filtered importance sampling. // http://cgg.mff.cuni.cz/~jaroslav/papers/2008-egsr-fis/2008-egsr-fis-final-embedded.pdf @@ -33,18 +34,16 @@ vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distributio vec2 Xi = mx_spherical_fibonacci(i, envRadianceSamples); // Compute the half vector and incoming light direction. - vec3 H = mx_ggx_importance_sample_NDF(Xi, alpha); + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, alpha); vec3 L = fd.refraction ? mx_refraction_solid_sphere(-V, H, fd.ior.x) : -reflect(V, H); // Compute dot products for this sample. - float NdotH = clamp(H.z, M_FLOAT_EPS, 1.0); float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); float VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); - float LdotH = VdotH; // Sample the environment light from the given direction. vec3 Lw = tangentToWorld * L; - float pdf = mx_ggx_PDF(H, LdotH, alpha); + float pdf = mx_ggx_NDF(H, alpha) * G1V / (4.0 * NdotV); float lod = mx_latlong_compute_lod(Lw, pdf, float($envRadianceMips - 1), envRadianceSamples); vec3 sampleColor = mx_latlong_map_lookup(Lw, $envMatrix, lod, $envRadiance); @@ -61,13 +60,15 @@ vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distributio // From https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf // incidentLight = sampleColor * NdotL // microfacetSpecular = D * F * G / (4 * NdotL * NdotV) - // pdf = D * NdotH / (4 * VdotH) + // pdf = D * G1V / (4 * NdotV); // radiance = incidentLight * microfacetSpecular / pdf - radiance += sampleColor * FG * VdotH / (NdotV * NdotH); + radiance += sampleColor * FG; } - // Normalize and return the final radiance. - radiance /= float(envRadianceSamples); + // Apply the global component of the geometric term and normalize. + radiance /= G1V * float(envRadianceSamples); + + // Return the final radiance. return radiance; } diff --git a/libraries/pbrlib/genglsl/lib/mx_microfacet_specular.glsl b/libraries/pbrlib/genglsl/lib/mx_microfacet_specular.glsl index cd165c0615..63aba17869 100644 --- a/libraries/pbrlib/genglsl/lib/mx_microfacet_specular.glsl +++ b/libraries/pbrlib/genglsl/lib/mx_microfacet_specular.glsl @@ -61,48 +61,22 @@ float mx_ggx_NDF(vec3 H, vec2 alpha) return 1.0 / (M_PI * alpha.x * alpha.y * mx_square(denom)); } -// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf -// Appendix B.1 Equation 3 -float mx_ggx_PDF(vec3 H, float LdotH, vec2 alpha) -{ - float NdotH = H.z; - return mx_ggx_NDF(H, alpha) * NdotH / (4.0 * LdotH); -} - -// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf -// Appendix B.2 Equation 15 -vec3 mx_ggx_importance_sample_NDF(vec2 Xi, vec2 alpha) -{ - float phi = 2.0 * M_PI * Xi.x; - float tanTheta = sqrt(Xi.y / (1.0 - Xi.y)); - vec3 H = vec3(tanTheta * alpha.x * cos(phi), - tanTheta * alpha.y * sin(phi), - 1.0); - return normalize(H); -} - -// http://jcgt.org/published/0007/04/01/paper.pdf -// Appendix A Listing 1 +// https://ggx-research.github.io/publication/2023/06/09/publication-ggx.html vec3 mx_ggx_importance_sample_VNDF(vec2 Xi, vec3 V, vec2 alpha) { // Transform the view direction to the hemisphere configuration. V = normalize(vec3(V.xy * alpha, V.z)); - // Construct an orthonormal basis from the view direction. - float len = length(V.xy); - vec3 T1 = (len > 0.0) ? vec3(-V.y, V.x, 0.0) / len : vec3(1.0, 0.0, 0.0); - vec3 T2 = cross(V, T1); - - // Parameterization of the projected area. - float r = sqrt(Xi.y); + // Sample a spherical cap in (-V.z, 1]. float phi = 2.0 * M_PI * Xi.x; - float t1 = r * cos(phi); - float t2 = r * sin(phi); - float s = 0.5 * (1.0 + V.z); - t2 = (1.0 - s) * sqrt(1.0 - mx_square(t1)) + s * t2; - - // Reprojection onto hemisphere. - vec3 H = t1 * T1 + t2 * T2 + sqrt(max(0.0, 1.0 - mx_square(t1) - mx_square(t2))) * V; + float z = (1.0 - Xi.y) * (1.0 + V.z) - V.z; + float sinTheta = sqrt(clamp(1.0 - z * z, 0.0, 1.0)); + float x = sinTheta * cos(phi); + float y = sinTheta * sin(phi); + vec3 c = vec3(x, y, z); + + // Compute the microfacet normal. + vec3 H = c + V; // Transform the microfacet normal back to the ellipsoid configuration. H = normalize(vec3(H.xy * alpha, max(H.z, 0.0))); diff --git a/libraries/pbrlib/pbrlib_defs.mtlx b/libraries/pbrlib/pbrlib_defs.mtlx index 0a931cdc7d..e63625a45d 100644 --- a/libraries/pbrlib/pbrlib_defs.mtlx +++ b/libraries/pbrlib/pbrlib_defs.mtlx @@ -76,8 +76,8 @@ --> - - + + diff --git a/libraries/stdlib/stdlib_defs.mtlx b/libraries/stdlib/stdlib_defs.mtlx index 4b9a6c1d34..e3b4344ff2 100644 --- a/libraries/stdlib/stdlib_defs.mtlx +++ b/libraries/stdlib/stdlib_defs.mtlx @@ -2280,7 +2280,7 @@ - + diff --git a/python/Scripts/mxformat.py b/python/Scripts/mxformat.py index 81ba3f72ea..8f6ad731e1 100644 --- a/python/Scripts/mxformat.py +++ b/python/Scripts/mxformat.py @@ -1,49 +1,38 @@ #!/usr/bin/env python ''' -Format MaterialX document content by reanding and writing files. Optionally -upgrade the document version. +Reformat a folder of MaterialX documents in place, optionally upgrading +the documents to the latest version of the standard. ''' import sys, os, argparse import MaterialX as mx -def getFiles(rootPath): - filelist = [] - for subdir, dirs, files in os.walk(rootPath): - for file in files: - if file.endswith('mtlx'): - filelist.append(os.path.join(subdir, file)) - return filelist - def main(): - parser = argparse.ArgumentParser(description="Format document by reading the file and writing it back out. Optionally update to the latest version.") + parser = argparse.ArgumentParser(description="Reformat a folder of MaterialX documents in place.") parser.add_argument("--yes", dest="yes", action="store_true", help="Proceed without asking for confirmation from the user.") - parser.add_argument('--checkForChanges', dest='checkForChanges', type=mx.stringToBoolean, default=True, help='Check if a file has changed. Default is True') - parser.add_argument('--upgradeVersion', dest='upgradeVersion', type=mx.stringToBoolean, default=False, help='Upgrade the document version. Default is False') + parser.add_argument('--upgrade', dest='upgrade', action="store_true", help='Upgrade documents to the latest version of the standard.') parser.add_argument(dest="inputFolder", help="An input folder to scan for MaterialX documents.") opts = parser.parse_args() - fileList = [] - rootPath = opts.inputFolder - if os.path.isdir(rootPath): - fileList = getFiles(rootPath) - else: - fileList.append(rootPath) - - # Preserve version, comments and newlines - readOptions = mx.XmlReadOptions() - readOptions.readComments = True - readOptions.readNewlines = True - readOptions.upgradeVersion = opts.upgradeVersion - validDocs = dict() - for filename in fileList: - doc = mx.createDocument() - try: - mx.readFromXmlFile(doc, filename, mx.FileSearchPath(), readOptions) - validDocs[filename] = doc - except mx.Exception: - pass + for root, dirs, files in os.walk(opts.inputFolder): + for file in files: + if file.endswith('.mtlx'): + filename = os.path.join(root, file) + doc = mx.createDocument() + try: + readOptions = mx.XmlReadOptions() + readOptions.readComments = True + readOptions.readNewlines = True + readOptions.upgradeVersion = opts.upgrade + try: + mx.readFromXmlFile(doc, filename, mx.FileSearchPath(), readOptions) + except Exception as err: + print('Skipping "' + file + '" due to exception: ' + str(err)) + continue + validDocs[filename] = doc + except mx.Exception: + pass if not validDocs: print('No MaterialX documents were found in "%s"' % (opts.inputFolder)) @@ -54,31 +43,21 @@ def main(): mxVersion = mx.getVersionIntegers() if not opts.yes: - question = 'Would you like to update all %i documents in place (y/n)?' % len(validDocs) - if opts.upgradeVersion: - question = 'Would you like to update all %i documents to MaterialX v%i.%i in place (y/n)?' % (len(validDocs), mxVersion[0], mxVersion[1]) + if opts.upgrade: + question = 'Would you like to upgrade all %i documents to MaterialX v%i.%i in place (y/n)?' % (len(validDocs), mxVersion[0], mxVersion[1]) + else: + question = 'Would you like to reformat all %i documents in place (y/n)?' % len(validDocs) answer = input(question) if answer != 'y' and answer != 'Y': return - writeCount = 0 for (filename, doc) in validDocs.items(): - writeFile = True - if opts.checkForChanges: - origString = mx.readFile(filename) - docString = mx.writeToXmlString(doc) - if origString == docString: - writeFile = False - - if writeFile: - writeCount = writeCount + 1 - print('- Updated file %s.' % filename) - mx.writeToXmlFile(doc, filename) + mx.writeToXmlFile(doc, filename) - if opts.upgradeVersion: - print('Updated %i documents to MaterialX v%i.%i' % (writeCount, mxVersion[0], mxVersion[1])) + if opts.upgrade: + print('Upgraded %i documents to MaterialX v%i.%i' % (len(validDocs), mxVersion[0], mxVersion[1])) else: - print('Updated %i documents ' % writeCount) + print('Reformatted %i documents ' % len(validDocs)) if __name__ == '__main__': main() diff --git a/resources/Lights/goegap_split.mtlx b/resources/Lights/goegap_split.mtlx index 026d07b45f..e046df5bf4 100644 --- a/resources/Lights/goegap_split.mtlx +++ b/resources/Lights/goegap_split.mtlx @@ -1,5 +1,5 @@ - + diff --git a/resources/Lights/san_giuseppe_bridge_split.mtlx b/resources/Lights/san_giuseppe_bridge_split.mtlx index e7975ae900..570c7b4f70 100644 --- a/resources/Lights/san_giuseppe_bridge_split.mtlx +++ b/resources/Lights/san_giuseppe_bridge_split.mtlx @@ -1,5 +1,5 @@ - + diff --git a/resources/Lights/table_mountain_split.mtlx b/resources/Lights/table_mountain_split.mtlx index 634f9facb3..b16050465f 100644 --- a/resources/Lights/table_mountain_split.mtlx +++ b/resources/Lights/table_mountain_split.mtlx @@ -1,5 +1,5 @@ - + diff --git a/resources/Materials/TestSuite/stdlib/upgrade/1_36_to_1_37.mtlx b/resources/Materials/TestSuite/stdlib/upgrade/1_36_to_1_37.mtlx index 0a0b3fd720..84db1c6101 100644 --- a/resources/Materials/TestSuite/stdlib/upgrade/1_36_to_1_37.mtlx +++ b/resources/Materials/TestSuite/stdlib/upgrade/1_36_to_1_37.mtlx @@ -1,10 +1,10 @@ - - + --> diff --git a/source/PyMaterialX/PyMaterialXRender/PyModule.cpp b/source/PyMaterialX/PyMaterialXRender/PyModule.cpp index 9c6a191108..1c60142346 100644 --- a/source/PyMaterialX/PyMaterialXRender/PyModule.cpp +++ b/source/PyMaterialX/PyMaterialXRender/PyModule.cpp @@ -19,6 +19,7 @@ void bindPyOiioImageLoader(py::module& mod); void bindPyTinyObjLoader(py::module& mod); void bindPyCamera(py::module& mod); void bindPyShaderRenderer(py::module& mod); +void bindPyCgltfLoader(py::module& mod); PYBIND11_MODULE(PyMaterialXRender, mod) { @@ -36,4 +37,5 @@ PYBIND11_MODULE(PyMaterialXRender, mod) bindPyTinyObjLoader(mod); bindPyCamera(mod); bindPyShaderRenderer(mod); + bindPyCgltfLoader(mod); }