diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 987aaa4839..c9750eb355 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,22 +23,15 @@ jobs: compiler: gcc compiler_version: "9" python: 3.7 - cmake_config: -DMATERIALX_BUILD_SHARED_LIBS=ON + cmake_config: -DMATERIALX_BUILD_SHARED_LIBS=ON -DMATERIALX_BUILD_MONOLITHIC=ON - - name: Linux_GCC_12_Python311 - os: ubuntu-22.04 + - name: Linux_GCC_13_Python311 + os: ubuntu-24.04 compiler: gcc - compiler_version: "12" + compiler_version: "13" python: 3.11 build_javascript: ON - - name: Linux_GCC_12_Python311_Static_Monolithic - os: ubuntu-22.04 - compiler: gcc - compiler_version: "12" - python: 3.11 - cmake_config: -DMATERIALX_BUILD_SHARED_LIBS=ON -DMATERIALX_BUILD_MONOLITHIC=ON - - name: Linux_GCC_14_Python312 os: ubuntu-24.04 compiler: gcc @@ -48,7 +41,7 @@ jobs: cmake_config: -DCMAKE_EXPORT_COMPILE_COMMANDS=ON - name: Linux_GCC_CoverageAnalysis - os: ubuntu-22.04 + os: ubuntu-24.04 compiler: gcc compiler_version: "None" python: None @@ -70,12 +63,12 @@ jobs: test_render: ON clang_format: ON - - name: MacOS_Xcode_13_Python37 + - name: MacOS_Xcode_13_Python39 os: macos-12 compiler: xcode compiler_version: "13.1" cmake_config: -DMATERIALX_BUILD_SHARED_LIBS=ON - python: 3.7 + python: 3.9 - name: MacOS_Xcode_14_Python311 os: macos-14 @@ -86,14 +79,14 @@ jobs: - name: MacOS_Xcode_15_Python312 os: macos-14 compiler: xcode - compiler_version: "15.0" + compiler_version: "15.4" python: 3.12 test_shaders: ON - name: MacOS_Xcode_DynamicAnalysis os: macos-14 compiler: xcode - compiler_version: "15.0" + compiler_version: "15.4" python: None dynamic_analysis: ON cmake_config: -DMATERIALX_DYNAMIC_ANALYSIS=ON @@ -101,7 +94,7 @@ jobs: - name: iOS_Xcode_15 os: macos-14 compiler: xcode - compiler_version: "15.0" + compiler_version: "15.4" python: None cmake_config: -DMATERIALX_BUILD_IOS=ON -DCMAKE_OSX_SYSROOT=`xcrun --sdk iphoneos --show-sdk-path` -DCMAKE_OSX_ARCHITECTURES=arm64 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..6ddb62f2b4 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,43 @@ +name: release + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + + release: + name: Release Signing + runs-on: ubuntu-latest + env: + RELEASE_TAG: ${{ github.ref_name }} + permissions: + contents: write + id-token: write + repository-projects: write + + steps: + - name: Sync Repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Create Archive Name + run: echo "MATERIALX_ARCHIVE=MaterialX-${RELEASE_TAG//v}" >> $GITHUB_ENV + + - name: Generate Archives + run: | + git archive --prefix ${MATERIALX_ARCHIVE}/ --output ${MATERIALX_ARCHIVE}.zip ${RELEASE_TAG} + git archive --prefix ${MATERIALX_ARCHIVE}/ --output ${MATERIALX_ARCHIVE}.tar.gz ${RELEASE_TAG} + + - name: Sign and Upload Archives + uses: sigstore/gh-action-sigstore-python@v3.0.0 + with: + inputs: | + ${{ env.MATERIALX_ARCHIVE }}.zip + ${{ env.MATERIALX_ARCHIVE }}.tar.gz + upload-signing-artifacts: true + release-signing-artifacts: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5cebfc0499..7c3ea19e54 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,33 +1,214 @@ # Contributing to MaterialX -Thank you for your interest in contributing to MaterialX! This document explains our contribution process and procedures. +Thank you for your interest in contributing to MaterialX! This document +explains our contribution process and procedures. ## Community and Discussion There are two primary ways to connect with the MaterialX community: -* The MaterialX channel of the [Academy Software Foundation Slack](http://academysoftwarefdn.slack.com/). This platform is appropriate for general questions, feature requests, and discussion of the MaterialX project as a whole. You can request an invitation to join the Academy Software Foundation Slack at https://slack.aswf.io/. -* The [Issues](https://github.com/AcademySoftwareFoundation/MaterialX/issues) panel of the MaterialX GitHub, which is used to report and discuss bugs and build issues. +- The MaterialX channel of the +[Academy Software Foundation Slack](http://academysoftwarefdn.slack.com/). +This platform is appropriate for general questions, feature requests, and +discussion of the MaterialX project as a whole. +You can request an invitation to join the Academy Software Foundation Slack +at https://www.aswf.io/get-involved/. +- The [Issues](https://github.com/AcademySoftwareFoundation/MaterialX/issues) +panel of the MaterialX GitHub, which is used both to track bugs and to discuss +feature requests. -## Contributor License Agreements +### How to Ask for Help -To contribute to MaterialX, you must sign a Contributor License Agreement through the *EasyCLA* system, which is integrated with GitHub as a pull request check. +If you have trouble installing, building, or using the library, but there's +not yet reason to suspect you've encountered a genuine bug, start by posting +a question to the MaterialX channel of the +[Academy Software Foundation Slack](http://academysoftwarefdn.slack.com/). +This is the place for questions such has "How do I...". -Prior to submitting a pull request, you can sign the form through [this link](https://contributor.easycla.lfx.linuxfoundation.org/#/cla/project/68fa91fe-51fe-41ac-a21d-e0a0bf688a53/user/564e571e-12d7-4857-abd4-898939accdd7). If you submit a pull request before the form is signed, the EasyCLA check will fail with a red *NOT COVERED* message, and you'll have another opportunity to sign the form through the provided link. +### How to Report a Bug -* If you are an individual writing the code on your own time and you're sure you are the sole owner of any intellectual property you contribute, you can sign the CLA as an Individual Contributor. -* If you are writing the code as part of your job, or if your employer retains ownership to intellectual property you create, then your company's legal affairs representatives should sign a Corporate Contributor License Agreement. If your company already has a signed CCLA on file, ask your local CLA manager to add you to your company's approved list. +MaterialX uses +[GitHub Issues](https://github.com/AcademySoftwareFoundation/MaterialX/issues) +for reporting and tracking bugs, enhancements, and feature requests. -The MaterialX CLAs are the standard forms used by Linux Foundation projects and [recommended by the ASWF TAC](https://github.com/AcademySoftwareFoundation/tac/blob/main/process/contributing.md#contributor-license-agreement-cla). +If you are submitting a bug report, please be sure to note which version of +MaterialX you are using, on what platform (OS/version, which compiler you used, +and any special build flags or other unusual environmental issues). Please +give a specific account of the following, with enough detail that others can +reproduce the problem: -## Coding Conventions +- What you tried. +- What happened. +- What you expected to happen instead. -The coding style of the MaterialX project is defined by a [clang-format](.clang-format) file in the repository, which is supported by Clang versions 13 and newer. +### How to Report a Security Vulnerability -When adding new source files to the repository, use the provided clang-format file to automatically align the code to MaterialX conventions. When modifying existing code, follow the surrounding formatting conventions so that new or modified code blends in with the current code. +If you think you've found a potential vulnerability in MaterialX, please refer +to [SECURITY.md](SECURITY.md) to responsibly disclose it. -## Unit Tests +### How to Contribute a Bug Fix or Change -Each MaterialX module has a companion folder within the [MaterialXTest](source/MaterialXTest) module, containing a set of unit tests that validate its functionality. +To contribute code to the project, you'll need: -When contributing new code to MaterialX, make sure to include appropriate unit tests in MaterialXTest to validate the expected behavior of the new code. +- Basic knowledge of Git. +- A fork of the MaterialX repository on GitHub. +- An understanding of the project's development workflow. +- Legal authorization, that is, you need to have signed a contributor + License Agreement. See below for details. + +## Legal Requirements + +MaterialX is a project of the Academy Software Foundation and follows the +open source software best practice policies of the Linux Foundation. + +### License + +MaterialX is licensed under the [Apache 2.0](LICENSE.md) license. +Contributions to the project should abide by that standard license. + +### Contributor License Agreements + +To contribute to MaterialX, you must sign a Contributor License Agreement +through the *EasyCLA* system, which is integrated with GitHub as a pull +request check. + +Prior to submitting a pull request, you can sign the form through +[this link](https://contributor.easycla.lfx.linuxfoundation.org/#/cla/project/68fa91fe-51fe-41ac-a21d-e0a0bf688a53/user/564e571e-12d7-4857-abd4-898939accdd7). +If you submit a pull request before the form is signed, the EasyCLA check +will fail with a red *NOT COVERED* message, and you'll have another +opportunity to sign the form through the provided link. + +- If you are an individual writing the code on your own time and you're sure +you are the sole owner of any intellectual property you contribute, you can +sign the CLA as an Individual Contributor. +- If you are writing the code as part of your job, or if your employer +retains ownership to intellectual property you create, then your company's +legal affairs representatives should sign a Corporate Contributor License +Agreement. If your company already has a signed CCLA on file, ask your +local CLA manager to add you to your company's approved list. + +The MaterialX CLAs are the standard forms used by Linux Foundation projects +and [recommended by the ASWF TAC](https://github.com/AcademySoftwareFoundation/tac/blob/main/process/contributing.md#contributor-license-agreement-cla). + +## Development Workflow + +### Git Basics + +Working with MaterialX requires a basic understanding of Git and GitHub +terminology. If you’re unfamiliar with these concepts, please look at the +[GitHub Glossary](https://help.github.com/articles/github-glossary/) or +browse [GitHub Help](https://help.github.com/). + +To contribute, you need a GitHub account. This is needed in order to push +changes to the upstream repository, via a pull request. + +You will also need [Git](https://git-scm.com/doc) or a Git client such +as [Git Fork](https://git-fork.com/) or +[GitHub Desktop](https://desktop.github.com/download/) installed +on your local development machine. + +### Repository Structure and Commit Policy + +Development work in the MaterialX project is usually done directly +on the `main` branch. This branch represents the cutting edge of the +project and the majority of new work is proposed, tested, reviewed, +and merged there. + +After sufficient work is done on the `main` branch and the MaterialX +leadership determines that a release is due, they will mark a release with +the current version tag and increment the development version for future +work. This basic repository structure keeps maintenance low, while remaining +simple to understand. + +The `main` branch may include untested features and is not generally stable +enough for release. To retrieve a stable version of the source code, use any +of the +[official releases](https://github.com/AcademySoftwareFoundation/MaterialX/releases) +of the project. + +### Use the Fork, Luke. + +In a typical workflow, you should *fork* the MaterialX repository to +your account. This creates a copy of the repository under your user +namespace and serves as the “home base” for your development branches, +from which you will submit *pull requests* to the upstream +repository to be merged. + +Once your Git environment is operational, the next step is to locally +*clone* your forked MaterialX repository, and add a *remote* +pointing to the upstream MaterialX repository. These topics are +covered in the GitHub documentation +[Cloning a repository](https://help.github.com/articles/cloning-a-repository/) +and +[Configuring a remote for a fork](https://help.github.com/articles/configuring-a-remote-for-a-fork/). + +### Pull Requests + +Contributions should be submitted as GitHub pull requests. See +[Creating a pull request](https://help.github.com/articles/creating-a-pull-request/) +if you're unfamiliar with this concept. + +The development cycle for a code change should follow this protocol: + +1. Create a topic branch in your local repository, assigning the branch a +brief name that describes the feature or fix that you're working on. +2. Make changes, compile, and test thoroughly. Code style should match existing +style and conventions, and changes should be focused on the topic the pull +request will be addressing. Make unrelated changes in a separate topic branch +with a separate pull request. +3. Push commits to your fork. +4. Create a GitHub pull request from your topic branch. +5. Pull requests will be reviewed by project maintainers and contributors, +who may discuss, offer constructive feedback, request changes, or approve +the work. +6. Upon receiving the required number of approvals (as outlined in +[Required Approvals](#code-review-and-required-approvals)), a maintainer +may merge changes into the `main` branch. + +### Code Review and Required Approvals + +Modifications of the contents of the MaterialX repository are made on a +collaborative basis. Anyone with a GitHub account may propose a modification +via pull request and it will be considered by the project maintainers. + +Pull requests must meet a minimum number of maintainer approvals prior to +being merged. Rather than having a hard rule for all PRs, the requirement +is based on the complexity and risk of the proposed changes, factoring in +the length of time the PR has been open to discussion. The following +guidelines outline the project's established approval rules for merging: + +- Minor changes that don't modify current behaviors or are straightforward +fixes to existing features can be approved and merged by a single maintainer +of the repository. +- Moderate changes that modify current behaviors or introduce new features +should be approved by *two* maintainers before merging. Unless the change is +an emergency fix, the author should give the community at least 48 hours to +review the proposed change. +- Major new features and core design decisions should be discussed at length +in the ASWF Slack or in TSC meetings before any PR is submitted, in order to +solicit feedback, build consensus, and alert all stakeholders to be on the +lookout for the eventual PR when it appears. + +### Coding Conventions + +The coding style of the MaterialX project is defined by a +[clang-format](.clang-format) file in the repository, which is supported by +Clang versions 13 and newer. + +When adding new source files to the repository, use the provided clang-format +file to automatically align the code to MaterialX conventions. When modifying +existing code, follow the surrounding formatting conventions so that new or +modified code blends in with the current code. + +### Unit Tests + +Each MaterialX module has a companion folder within the +[MaterialXTest](source/MaterialXTest) module, containing a set of unit tests +that validate its functionality. When contributing new code to MaterialX, make +sure to include appropriate unit tests in MaterialXTest to validate the +expected behavior of the new code. + +The MaterialX test suite can be run locally via MaterialXTest before submitting +a pull request. Upon receiving a pull request, the GitHub CI process will +automatically run MaterialXTest across all platforms, and a successful result +is required before merging a change. diff --git a/documents/Specification/MaterialX.PBRSpec.md b/documents/Specification/MaterialX.PBRSpec.md index 2e314d479d..67fecd80b3 100644 --- a/documents/Specification/MaterialX.PBRSpec.md +++ b/documents/Specification/MaterialX.PBRSpec.md @@ -243,6 +243,21 @@ The PBS nodes also make use of the following standard MaterialX types: * `normal` (vector3): Normal vector of the surface. Defaults to world space normal. * `mode` (uniform string): Selects between `conty_kulla` and `zeltner` sheen models. Defaults to `conty_kulla`. + + +* **`chiang_hair_bsdf`**: Constructs a hair BSDF based on the Chiang hair shading model[^Chiang2016]. This node does not support vertical layering. + * `tint_R` (color3): Color multiplier for the first R-lobe. Defaults to (1.0, 1.0, 1.0). + * `tint_TT` (color3): Color multiplier for the first TT-lobe. Defaults to (1.0, 1.0, 1.0). + * `tint_TRT` (color3): Color multiplier for the first TRT-lobe. Defaults to (1.0, 1.0, 1.0). + * `ior` (float): Index of refraction. Defaults to 1.55 being the value for keratin. + * `roughness_R` (vector2): Longitudinal and azimuthal roughness (ν, s) for the first R-lobe, range [0.0, ∞). With (0, 0) specifying pure specular scattering. Defaults to (0.1, 0.1). + * `roughness_TT` (vector2): Longitudinal and azimuthal roughness (ν, s) for the first TT-lobe, range [0.0, ∞). With (0, 0) specifying pure specular scattering. Defaults to (0.05, 0.05). + * `roughness_TRT` (vector2): Longitudinal and azimuthal roughness (ν, s) for the first TRT-lobe, range [0.0, ∞). With (0, 0) specifying pure specular scattering. Defaults to (0.2, 0.2). + * `cuticle_angle` (float): Cuticle angle in radians, Values above 0.5 tilt the scales towards the root of the fiber, range [0.0, 1.0]. With 0.5 specifying no tilt. Defaults to 0.5. + * `absorption_coefficient` (vector3): Absorption coefficient normalized to the hair fiber diameter. Defaults to (0.0, 0.0, 0.0). + * `normal` (vector3): Normal vector of the surface. Defaults to world space normal. + * `curve_direction` (vector3): Direction of the hair geometry. Defaults to world space tangent. + ## EDF Nodes @@ -374,6 +389,27 @@ Note that the standard library includes definitions for [**`displacement`**](./M * `ior` (**output**, vector3): Computed index of refraction. * `extinction` (**output**, vector3): Computed extinction coefficient. + + +* **`chiang_hair_roughness`**: Converts the artistic parameterization hair roughness to roughness for R, TT and TRT lobes, as described in [^Chiang2016]. Output type `multioutput`, `roughness_R`, `roughness_TT` and `roughness_TRT`, `vector2` type. + * `longitudinal` (float): Longitudinal roughness, range [0.0, 1.0]. Defaults to 0.1. + * `azimuthal` (float): Azimuthal roughness, range [0.0, 1.0]. Defaults to 0.2. + * `scale_TT` (float): Roughness scale for TT lobe. Defaults to 0.5[^Marschner2003]. + * `scale_TRT` (float): Roughness scale for TRT lobe. Defaults to 2.0[^Marschner2003]. + + + +* **`deon_hair_absorption_from_melanin`** : Converts the hair melanin parameterization to absorption coefficient based on pigments eumelanin and pheomelanin using the mapping method described in [^d'Eon2011]. The default of `eumelanin_color` and `pheomelanin_color` are `lin_rec709` color converted from the constants[^d'Eon2011] via `exp(-c)`. They may be transformed to scene-linear rendering color space. `Output type `vector3`. + * `melanin_concentration` (float): Amount of melanin affected to the output, range [0.0, 1.0]. Defaults to 0.25. + * `melanin_redness` (float): Amount of redness affected to the output, range [0.0, 1.0]. Defaults to 0.5. + * `eumelanin_color` (color3): Eumelanin color. Defaults to (0.657704, 0.498077, 0.254107) + * `pheomelanin_color` (color3): Pheomelanin color. Defaults to (0.829444, 0.67032, 0.349938) + + + +* **`chiang_hair_absorption_from_color`** : Coverts the hair scattering color to absorption coefficient using the mapping method described in [^Chiang2016]. Output type `vector3`. + * `color` (color3): Scattering color. Defaults to (1.0, 1.0, 1.0). + * `azimuthal_roughness` (float): Azimuthal roughness, range [0.0, 1.0]. Defaults to 0.2. # Shading Model Examples @@ -381,6 +417,14 @@ Note that the standard library includes definitions for [**`displacement`**](./M This section contains examples of shading model implementations using the MaterialX PBS library. For all examples, the shading model is defined via a <nodedef> interface plus a nodegraph implementation. The resulting nodes can be used as shaders by a MaterialX material definition. +## Disney Principled BSDF + +This shading model was presented by Brent Burley from Walt Disney Animation Studios in 2012[^Burley2012], with additional refinements presented in 2015[^Burley2015]. + +A MaterialX definition and nodegraph implementation of the Disney Principled BSDF can be found here: +[https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/bxdf/disney_principled.mtlx](https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/bxdf/disney_principled.mtlx) + + ## Autodesk Standard Surface This is a surface shading model used in Autodesk products created by the Solid Angle team for the Arnold renderer. It is an über shader built from ten different BSDF layers[^Georgiev2019]. @@ -430,16 +474,25 @@ The MaterialX PBS Library includes a number of nodegraphs that can be used to ap [^Burley2012]: Brent Burley, **Physically-Based Shading at Disney**, , 2012 +[^Burley2015]: Brent Burley, **Extending the Disney BRDF to a BSDF with Integrated Subsurface Scattering**, , 2015 + +[^Chiang2016]: Matt Jen-Yuan Chiang et al., **A Practical and Controllable Hair and Fur Model for Production +Path Tracing**, , 2016 + [^Christensen2015]: Per H. Christensen, Brent Burley, **Approximate Reflectance Profiles for Efficient Subsurface Scattering**, 2015 [^Conty2017]: Alejandro Conty, Christopher Kulla, **Production Friendly Microfacet Sheen BRDF**, , 2017 +[^d'Eon2011]: Eugene d'Eon et al., **An Energy-Conserving Hair Reflectance Model**, , 2011 + [^Georgiev2019]: Iliyan Georgiev et al., **Autodesk Standard Surface**, , 2019. [^Gulbrandsen2014]: Ole Gulbrandsen, **Artist Friendly Metallic Fresnel**, , 2014 [^Hoffman2023]: Naty Hoffman, **Generalization of Adobe's Fresnel Model**, 2023 +[^Marschner2003]: Stephen R. Marschner et al., **Light Scattering from Human Hair Fibers**, , 2003 + [^Oren1994]: Michael Oren, Shree K. Nayar, **Generalization of Lambert’s Reflectance Model**, , 1994 [^Pharr2023]: Matt Pharr et al., **Physically Based Rendering: From Theory To Implementation**, , 2023 diff --git a/documents/Specification/MaterialX.Proposals.md b/documents/Specification/MaterialX.Proposals.md index 97b37787ba..b96c688f25 100644 --- a/documents/Specification/MaterialX.Proposals.md +++ b/documents/Specification/MaterialX.Proposals.md @@ -6,7 +6,7 @@ MaterialX Proposals v1.39 # MaterialX: Proposed Additions and Changes **Proposals for Version 1.39** -July 26, 2024 +September 15, 2024 # Introduction @@ -207,6 +207,7 @@ We have a standard 3d fractal noise, but a 2d variant would be useful as well. * `geomprop` (uniform string): the geometric property to be referenced. * `default` (same type as the geomprop's value): a value to return if the specified `geomprop` is not defined on the current geometry. +Note: when <geompropvalueuniform> is added, the text in the first paragraph of the Specification about Node Inputs should be revised to include "<geompropvalueuniform>" as an example of "or any other node whose output is explicitly declared to be uniform". ### Global Nodes diff --git a/documents/Specification/MaterialX.Specification.md b/documents/Specification/MaterialX.Specification.md index 60e62d9ce2..070587340c 100644 --- a/documents/Specification/MaterialX.Specification.md +++ b/documents/Specification/MaterialX.Specification.md @@ -8,7 +8,7 @@ MaterialX Specification v1.39 **Version 1.39** Doug Smythe - Industrial Light & Magic Jonathan Stone - Lucasfilm Advanced Development Group -July 26, 2024 +September 15, 2024 # Introduction @@ -368,9 +368,9 @@ By default, MaterialX supports the following color spaces as defined in ACES 1.2 * `srgb_displayp3` * `lin_displayp3` -The working color space of a MaterialX document is defined by the `colorspace` attribute of its root <materialx> element, and it is strongly recommended that all <materialx> elements define a specific `colorspace` if they wish to use a color-managed workflow rather than relying on a default colorspace setting from an external configuration file. +The working color space of a MaterialX document is defined by the `colorspace` attribute of its root <materialx> element, and it is strongly recommended that all <materialx> elements define a specific `colorspace` if they wish to use a color-managed workflow rather than relying on a default color space setting from an external configuration file. If a MaterialX document is xi:included into another MaterialX document, it will inherit the working color space setting of the parent document, unless it itself declares a specific working color space. -The color space of individual color image files and values may be defined via a `colorspace` attribute in an input which defines a filename or value. Color images and values in spaces other than the working color space are expected to be transformed by the application into the working space before computations are performed. In the example below, an image file has been defined in the “srgb_texture” color space, while its default value has been defined in “lin_rec709”; both should be transformed to the application’s working color space before being applied to any computations. +The color space of individual color image files and values may be defined via a `colorspace` attribute in an input which defines a filename or value. Other elements, such as <nodegraph> or a node instance, are allowed to define a `colorspace` attribute that will apply to elements within their scope; color values in inputs and files that do not explicitly provide a `colorspace` attribute will be treated as if they are in the color space of the nearest enclosing scope which does define a `colorspace` attribute. Color images and values in spaces other than the working color space are expected to be transformed by the application into the working space before computations are performed. In the example below, an image file has been defined in the “srgb_texture” color space, while its default value has been defined in “lin_rec709”; both should be transformed to the application’s working color space before being applied to any computations. ```xml @@ -381,7 +381,7 @@ The color space of individual color image files and values may be defined via a ``` -MaterialX reserves the color space name "none" to mean no color space conversion should be applied to the images and color values within their scope. +MaterialX reserves the color space name "none" to mean no color space conversion should be applied to the images and color values within their scope, regardless of any differences between stated color spaces at the local scope and document or application working color space settings. @@ -603,7 +603,7 @@ MaterialX defines a number of Standard Nodes which all implementations should su ## Inputs -Node elements contain zero or more <input> elements defining the name, type, and value or connection for each node input. Input elements can assign an explicit uniform value by providing a `value` attribute, make a connection to the output of another node by providing a `nodename` attribute, or make a connection to the output of a nodegraph by providing a `nodegraph` attribute. An optional `output` attribute may also be provided for <input> elements, allowing the input to connect to a specific, named output of the referenced upstream node or nodegraph. If the referenced node/nodegraph has multiple outputs, `output` is required; if it has only one output, the `output` attribute of the <input> is ignored. Input elements may be defined to only accept uniform values, in which case the input may provide a `value` or a `nodename` connection to the output of a [<constant> node](#node-constant) (possibly through one or more no-op [<dot> nodes](#node-dot)) or any other node whose output is explicitly declared to be "uniform" (such as [<geompropvalueuniform>](#node-geompropvalueuniform)), but may not provide a `nodename` or `nodegraph` connection to any arbitrary node output or to any nodegraph output. String- and filename-type inputs are required to be "uniform", as are any array-typed inputs. Input elements may be connected to an external parameter interface in the node definition, allowing them to be assigned values from materials or node instantiations; this includes "uniform" and string/filename-type inputs, however, the same connectability restrictions listed above apply to the inputs of the material or node instance. Inputs may only be connected to node/nodegraph outputs or nodedef interface inputs of the same type, though it is permissible for a `string`-type output to be connected to a `filename`-type input (but not the other way around). +Node elements contain zero or more <input> elements defining the name, type, and value or connection for each node input. Input elements can assign an explicit uniform value by providing a `value` attribute, make a connection to the output of another node by providing a `nodename` attribute, or make a connection to the output of a nodegraph by providing a `nodegraph` attribute. An optional `output` attribute may also be provided for <input> elements, allowing the input to connect to a specific, named output of the referenced upstream node or nodegraph. If the referenced node/nodegraph has multiple outputs, `output` is required; if it has only one output, the `output` attribute of the <input> is ignored. Input elements may be defined to only accept uniform values, in which case the input may provide a `value` or a `nodename` connection to the output of a [<constant> node](#node-constant) (possibly through one or more no-op [<dot> nodes](#node-dot)) or any other node whose output is explicitly declared to be "uniform", but may not provide a `nodename` or `nodegraph` connection to any arbitrary node output or to any nodegraph output. String- and filename-type inputs are required to be "uniform", as are any array-typed inputs. Input elements may be connected to an external parameter interface in the node definition, allowing them to be assigned values from materials or node instantiations; this includes "uniform" and string/filename-type inputs, however, the same connectability restrictions listed above apply to the inputs of the material or node instance. Inputs may only be connected to node/nodegraph outputs or nodedef interface inputs of the same type, though it is permissible for a `string`-type output to be connected to a `filename`-type input (but not the other way around). A float/vectorN input of a node, or a "filename"-type input referring to an image file containing float or vectorN values, may specify a unit for its value by providing a `unit` attribute, and that unit must be one associated with the `unittype` for that input in the nodedef, if specified; please see the [Units](#units) section above for details on declaring units and unittypes. If the nodedef for a node (see the [Custom Nodes](#custom-nodes) section below) does not declare a `unittype` for an input, the node may do so; it is not permissible to provide a `unit` for a node input without a compatible `unittype` being defined on either the node or applicable nodedef. @@ -1095,13 +1095,7 @@ Standard Geometric nodes: * `geomprop` (uniform string): the geometric property to be referenced. * `default` (same type as the geomprop's value): a value to return if the specified `geomprop` is not defined on the current geometry. - - -* **`geompropvalueuniform`**: the value of the specified uniform geometric property (defined using <geompropdef>) of the currently-bound geometry. This node's type must match that of the referenced geomprop. - * `geomprop` (uniform string): the geometric property to be referenced. - * `default` (same type as the geomprop's value): a value to return if the specified `geomprop` is not defined on the current geometry. - -Additionally, the `geomcolor`, `geompropvalue` and `geompropvalueuniform` nodes for color3/color4-type properties can take a `colorspace` attribute to declare what colorspace the color property value is in; the default is "none" for no colorspace declaration (and hence no colorspace conversion). +Additionally, the `geomcolor` and `geompropvalue` nodes for color3/color4-type properties can take a `colorspace` attribute to declare what colorspace the color property value is in; the default is "none" for no colorspace declaration (and hence no colorspace conversion). @@ -1438,7 +1432,7 @@ Math nodes have one or two spatially-varying inputs, and are used to perform a m -* **`dot`**: a no-op, passes its input through to its output unchanged. Users can use dot nodes to shape edge connection paths or provide documentation checkpoints in node graph layout UI's. Dot nodes may also pass uniform values from <constant>, <tokenvalue> or other nodes with uniform="true" outputs to uniform <input>s and <token>s. +* **`dot`**: a no-op, passes its input through to its output unchanged. Users can use dot nodes to shape edge connection paths or provide documentation checkpoints in node graph layout UI's. Dot nodes may also pass uniform values from <constant> or other nodes with uniform="true" outputs to uniform <input>s and <token>s. * `in` (any type): the nodename to be connected to the Dot node's "in" input. Unlike inputs on other node types, the <dot> node's input is specifically disallowed to provide a `channels` attribute: input data can only be passed through unmodified. @@ -2103,7 +2097,7 @@ Attributes for NodeDef Input elements: * `defaultgeomprop` (string, optional): for vector2 or vector3 inputs, the name of an intrinsic geometric property that provides the default value for this input, must be one of "position", "normal", "tangent", "bitangent" or "texcoord" or vector3-type custom geometric property for vector3 inputs, or "texcoord" or vector2-type custom geometric property for vector2 inputs. For standard geometric properties, this is effectively the same as declaring a default connection of the input to a Geometric Node with default input values. May not be specified on uniform inputs. * `enum` (stringarray, optional): a comma-separated non-exclusive list of string value descriptors that the input couldmayis allowed to take: for string- and stringarray-type inputs, these are the actual values (or values per array index for stringarrays); for other types, these are the "enum" labels e.g. as shown in the application user interface for each of the actual underlying values specified by `enumvalues`. The enum list can be thought of as a list of commonly used values or UI labels for the input rather than a strict list, and MaterialX itself does not enforce that a specified input enum value is actually in this list, with the exception that if the input is a "string" (or "stringarray") type and an enum list is provided, then the value(s) must in fact be one of the enum stringarray values. * `enumvalues` (typearray, optional): for non-string/stringarray types, a comma-separated list of values of the same base type as the <input>, representing the values that would be used if the corresponding `enum` string was chosen in the UI. MaterialX itself does not enforce that a specified input value is actually in this list. Note that implementations are allowed to redefine `enumvalues` (but not `enum`) for specific targets: see the [Custom Node Definition Using Implementation Elements](#custom-node-definition-using-implementation-elements) section below. -* `colorspace` (string, optional): for color3- or color4-type inputs, the expected colorspace for this input. Nodedef inputs do not typically specify a colorspace; the most common use case is to specify `colorspace="none"` for inputs that are color-like but which should not be affected by colorspace conversions. +* `colorspace` (string, optional): for color3- or color4-type inputs, the colorspace of the default value for this input. * `unittype` (string, optional): the type of unit for this input, e.g. "distance", which must be defined by a <unittypedef>. Default is to not specify a unittype. Only float-, vectorN- and filename-type inputs may specify a `unittype`. * `unit` (string, optional): the specific unit for this input. Nodedef inputs do not typically specify a unit; if it does, that would indicate that the implementation of that node expects values to be specified in that unit, and that any invocation of that node using a different unit should be converted to the nodedef-specified unit for that input rather than to the application's scene unit. The most common instance of this is for angular values, where a nodedef might specify that it expects values to be given in degrees. * `uiname` (string, optional): an alternative name for this input as it appears in the UI. If `uiname` is not provided, then `name` is the presumed UI name for the input. @@ -2387,7 +2381,7 @@ Custom nodes that output data types with a "shader" semantic are referred to in The attributes for <nodedef> elements as they pertain to the declaration of shaders are: * `name` (string, required): a user-chosen name for this shader node definition element. -* `node` (string, required): the name of the shader node being defined, which typically matches the name of an associated shader function such as “blinn_phong”, “Disney_BRDF_2012”, “volumecloud_vol”. Just as for custom nodes, this shading program may be defined precisely through an <implementation> or <nodegraph>, or left to the application to locate by name using any shader definition method that it chooses. +* `node` (string, required): the name of the shader node being defined, which typically matches the name of an associated shader function such as “blinn_phong”, “disney_principled”, “volumecloud_vol”. Just as for custom nodes, this shading program may be defined precisely through an <implementation> or <nodegraph>, or left to the application to locate by name using any shader definition method that it chooses. The child <output> element within the <nodedef> defines the "data type" of the output for this shader, which must have been defined with a "shader" semantic; see the [Custom Data Types](#custom-data-types) section above and discussion below for details. @@ -2707,8 +2701,6 @@ Example uses for variants include defining a number of allowable colors and text Variants and variantsets are not intrinsically associated with any particular material; they merely state a number of values for a number of named inputs/tokens. However, variantsets may state that they are associated with specific shader-semantic nodes and/or <nodedef> declarations by providing stringarray-type `node` and/or `nodedef` attributes: ```xml - - ... ... ``` diff --git a/javascript/MaterialXView/source/helper.js b/javascript/MaterialXView/source/helper.js index cd3f144f06..3f3be46c79 100644 --- a/javascript/MaterialXView/source/helper.js +++ b/javascript/MaterialXView/source/helper.js @@ -89,7 +89,7 @@ function fromMatrix(matrix, dimension) */ function toThreeUniform(type, value, name, uniforms, textureLoader, searchPath, flipY) { - let outValue; + let outValue = null; switch (type) { case 'float': @@ -117,18 +117,63 @@ function toThreeUniform(type, value, name, uniforms, textureLoader, searchPath, case 'filename': if (value) { - let fullPath = searchPath + IMAGE_PATH_SEPARATOR + value; - const texture = textureLoader.load(fullPath); - // Set address & filtering mode - if (texture) - setTextureParameters(texture, name, uniforms, flipY); - outValue = texture; + // Cache / reuse texture to avoid reload overhead. + // Note: that data blobs and embedded data textures are not cached as they are transient data. + let checkCache = true; + let texturePath = searchPath + IMAGE_PATH_SEPARATOR + value; + if (value.startsWith('blob:')) + { + texturePath = value; + console.log('Load blob URL:', texturePath); + checkCache = false; + } + else if (value.startsWith('http')) + { + texturePath = value; + console.log('Load HTTP URL:', texturePath); + } + else if (value.startsWith('data:')) + { + texturePath = value; + checkCache = false; + console.log('Load data URL:', texturePath); + } + const cachedTexture = checkCache && THREE.Cache.get(texturePath); + if (cachedTexture) + { + // Get texture from cache + outValue = cachedTexture; + console.log('Use cached texture: ', texturePath, outValue); + } + else + { + outValue = textureLoader.load( + texturePath, + function (texture) { + console.log('Load new texture: ' + texturePath, texture); + outValue = texture; + + // Add texture to ThreeJS cache + if (checkCache) + THREE.Cache.add(texturePath, texture); + }, + undefined, + function (error) { + console.error('Error loading texture: ', error); + }); + + // Set address & filtering mode + if (outValue) + setTextureParameters(outValue, name, uniforms, flipY); + } } break; case 'samplerCube': case 'string': - default: break; + default: + console.log('Value type not supported: ' + type); + outValue = null; } return outValue; diff --git a/javascript/MaterialXView/source/viewer.js b/javascript/MaterialXView/source/viewer.js index 57e155c87b..febe34c809 100644 --- a/javascript/MaterialXView/source/viewer.js +++ b/javascript/MaterialXView/source/viewer.js @@ -221,9 +221,14 @@ export class Scene orbitControls.update(); } - setUpdateTransforms() + setUpdateTransforms(val=true) { - this.#_updateTransforms = true; + this.#_updateTransforms = val; + } + + getUpdateTransforms() + { + return this.#_updateTransforms; } updateTransforms() @@ -235,7 +240,7 @@ export class Scene { return; } - this.#_updateTransforms = false; + this.setUpdateTransforms(false); const scene = this.getScene(); const camera = this.getCamera(); @@ -622,7 +627,12 @@ export class Material // Load material if (mtlxMaterial) - await mx.readFromXmlString(doc, mtlxMaterial, searchPath); + try { + await mx.readFromXmlString(doc, mtlxMaterial, searchPath); + } + catch (error) { + console.log('Error loading material file: ', error); + } else Material.createFallbackMaterial(doc); diff --git a/libraries/bxdf/disney_brdf_2015.mtlx b/libraries/bxdf/disney_brdf_2015.mtlx deleted file mode 100644 index 916c7d55af..0000000000 --- a/libraries/bxdf/disney_brdf_2015.mtlx +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/libraries/bxdf/disney_principled.mtlx b/libraries/bxdf/disney_principled.mtlx new file mode 100644 index 0000000000..72394cb84f --- /dev/null +++ b/libraries/bxdf/disney_principled.mtlx @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libraries/pbrlib/genglsl/lib/mx_microfacet_sheen.glsl b/libraries/pbrlib/genglsl/lib/mx_microfacet_sheen.glsl index a5c3ade7a8..6301105dd1 100644 --- a/libraries/pbrlib/genglsl/lib/mx_microfacet_sheen.glsl +++ b/libraries/pbrlib/genglsl/lib/mx_microfacet_sheen.glsl @@ -123,7 +123,7 @@ mat3 mx_orthonormal_basis_ltc(vec3 V, vec3 N, float NdotV) float lenSqr = dot(X, X); if (lenSqr > 0.0) { - X *= inversesqrt(lenSqr); + X *= mx_inversesqrt(lenSqr); vec3 Y = cross(N, X); return mat3(X, Y, N); } @@ -173,7 +173,7 @@ vec3 mx_zeltner_sheen_importance_sample(vec2 Xi, vec3 V, vec3 N, float roughness vec3 w = vec3(wo.x/aInv - wo.z*bInv/aInv, wo.y / aInv, wo.z); float lenSqr = dot(w, w); - w *= inversesqrt(lenSqr); + w *= mx_inversesqrt(lenSqr); // D(w) = Do(wo) . ||M.wo||^3 / |M| // = Do(wo / ||M.wo||) . ||M.wo||^4 / |M| diff --git a/libraries/pbrlib/genglsl/mx_hair_bsdf.glsl b/libraries/pbrlib/genglsl/mx_hair_bsdf.glsl new file mode 100644 index 0000000000..e3a9ac20d5 --- /dev/null +++ b/libraries/pbrlib/genglsl/mx_hair_bsdf.glsl @@ -0,0 +1,343 @@ +#include "lib/mx_microfacet_specular.glsl" + +// https://eugenedeon.com/pdfs/egsrhair.pdf +void mx_deon_hair_absorption_from_melanin( + float melanin_concentration, + float melanin_redness, + // constants converted to color via exp(-c). the defaults are lin_rec709 colors, they may be + // transformed to scene-linear rendering color space. + vec3 eumelanin_color, // default: (0.657704, 0.498077, 0.254106) == exp(-(0.419, 0.697, 1.37)) + vec3 pheomelanin_color, // default: (0.829443, 0.670320, 0.349937) == exp(-(0.187, 0.4, 1.05)) + out vec3 absorption) +{ + float melanin = -log(max(1.0 - melanin_concentration, 0.0001)); + float eumelanin = melanin * (1.0 - melanin_redness); + float pheomelanin = melanin * melanin_redness; + absorption = max( + eumelanin * -log(eumelanin_color) + pheomelanin * -log(pheomelanin_color), + vec3(0.0) + ); +} + +// https://media.disneyanimation.com/uploads/production/publication_asset/152/asset/eurographics2016Fur_Smaller.pdf +void mx_chiang_hair_absorption_from_color(vec3 color, float betaN, out vec3 absorption) +{ + float b2 = betaN* betaN; + float b4 = b2 * b2; + float b_fac = + 5.969 - + (0.215 * betaN) + + (2.532 * b2) - + (10.73 * b2 * betaN) + + (5.574 * b4) + + (0.245 * b4 * betaN); + vec3 sigma = log(min(max(color, 0.001), vec3(1.0))) / b_fac; + absorption = sigma * sigma; +} + +void mx_chiang_hair_roughness( + float longitudinal, + float azimuthal, + float scale_TT, // empirical roughenss scale from Marschner et al. (2003). + float scale_TRT, // default: scale_TT = 0.5, scale_TRT = 2.0 + out vec2 roughness_R, + out vec2 roughness_TT, + out vec2 roughness_TRT +) +{ + float lr = clamp(longitudinal, 0.001, 1.0); + float ar = clamp(azimuthal, 0.001, 1.0); + + // longitudinal variance + float v = 0.726 * lr + 0.812 * lr * lr + 3.7 * pow(lr, 20); + v = v * v; + + float s = 0.265 * ar + 1.194 * ar * ar + 5.372 * pow(ar, 22); + + roughness_R = vec2(v, s); + roughness_TT = vec2(v * scale_TT * scale_TT, s); + roughness_TRT = vec2(v * scale_TRT * scale_TRT, s); +} + +float mx_hair_transform_sin_cos(float x) +{ + return sqrt(max(1.0 - x * x, 0.0)); +} + +float mx_hair_I0(float x) +{ + float v = 1.0; + float n = 1.0; + float d = 1.0; + float f = 1.0; + float x2 = x * x; + for (int i = 0; i < 9 ; ++i) + { + d *= 4.0 * (f * f); + n *= x2; + v += n / d; + f += 1.0; + } + return v; +} + +float mx_hair_log_I0(float x) +{ + if (x > 12.0) + return x + 0.5 * (-log(2.0 * M_PI) + log(1.0 / x) + 1.0 / (8.0 * x)); + else + return log(mx_hair_I0(x)); +} + +float mx_hair_logistic(float x, float s) +{ + if (x > 0.0) + x = -x; + float f = exp(x / s); + return f / (s * (1.0 + f) * (1.0 + f)); +} + +float mx_hair_logistic_cdf(float x, float s) +{ + return 1.0 / (1.0 + exp(-x / s)); +} + +float mx_hair_trimmed_logistic(float x, float s, float a, float b) +{ + // the constant can be found in Chiang et al. (2016) Appendix A, eq. (12) + s *= 0.626657; // sqrt(M_PI/8) + return mx_hair_logistic(x, s) / (mx_hair_logistic_cdf(b, s) - mx_hair_logistic_cdf(a, s)); +} + +float mx_hair_phi(int p, float gammaO, float gammaT) +{ + return 2.0 * p * gammaT - 2.0 * gammaO + p * M_PI; +} + +float mx_hair_longitudinal_scattering( // Mp + float sinThetaI, + float cosThetaI, + float sinThetaO, + float cosThetaO, + float v +) +{ + float inv_v = 1.0 / v; + float a = cosThetaO * cosThetaI * inv_v; + float b = sinThetaO * sinThetaI * inv_v; + if (v < 0.1) + return exp(mx_hair_log_I0(a) - b - inv_v + 0.6931 + log(0.5 * inv_v)); + else + return ((exp(-b) * mx_hair_I0(a)) / (2.0 * v * sinh(inv_v))); +} + +float mx_hair_azimuthal_scattering( // Np + float phi, + int p, + float s, + float gammaO, + float gammaT +) +{ + if (p >= 3) + return float(0.5 / M_PI); + + float dphi = phi - mx_hair_phi(p, gammaO, gammaT); + if (isinf(dphi)) + return float(0.5 / M_PI); + + while (dphi > M_PI) dphi -= (2.0 * M_PI); + while (dphi < (-M_PI)) dphi += (2.0 * M_PI); + + return mx_hair_trimmed_logistic(dphi, s, -M_PI, M_PI); +} + +void mx_hair_alpha_angles( + float alpha, + float sinThetaI, + float cosThetaI, + out vec2 angles[4] +) +{ + // 0:R, 1:TT, 2:TRT, 3:TRRT+ + for (int i = 0; i <= 3; ++i) + { + if (alpha == 0.0 || i == 3) + angles[i] = vec2(sinThetaI, cosThetaI); + else + { + float m = 2.0 - float(i) * 3.0; + float sa = sin(m * alpha); + float ca = cos(m * alpha); + angles[i].x = sinThetaI * ca + cosThetaI * sa; + angles[i].y = cosThetaI * ca - sinThetaI * sa; + } + } +} + +void mx_hair_attenuation(float f, vec3 T, out vec3 Ap[4]) // Ap +{ + // 0:R, 1:TT, 2:TRT, 3:TRRT+ + Ap[0] = vec3(f); + Ap[1] = (1.0 - f) * (1.0 - f) * T; + Ap[2] = Ap[1] * T * f; + Ap[3] = Ap[2] * T * f / (vec3(1.0) - T * f); +} + +vec3 mx_chiang_hair_bsdf( + vec3 L, + vec3 V, + vec3 tint_R, + vec3 tint_TT, + vec3 tint_TRT, + float ior, + vec2 roughness_R, + vec2 roughness_TT, + vec2 roughness_TRT, + float cuticle_angle, + vec3 absorption_coefficient, + vec3 N, + vec3 X +) +{ + N = mx_forward_facing_normal(N, V); + X = normalize(X - dot(X, N) * N); + vec3 Y = cross(N, X); + + float sinThetaO = dot(V, X); + float sinThetaI = dot(L, X); + float cosThetaO = mx_hair_transform_sin_cos(sinThetaO); + float cosThetaI = mx_hair_transform_sin_cos(sinThetaI); + + float y1 = dot(L, N); + float x1 = dot(L, Y); + float y2 = dot(V, N); + float x2 = dot(V, Y); + float phi = atan(y1 * x2 - y2 * x1, x1 * x2 + y1 * y2); + + vec3 k1_p = normalize(V - X * dot(V, X)); + float cosGammaO = dot(N, k1_p); + float sinGammaO = mx_hair_transform_sin_cos(cosGammaO); + if (dot(k1_p, Y) > 0.0) + sinGammaO = -sinGammaO; + float gammaO = asin(sinGammaO); + + float sinThetaT = sinThetaO / ior; + float cosThetaT = mx_hair_transform_sin_cos(sinThetaT); + float etaP = sqrt(max(ior * ior - sinThetaO * sinThetaO, 0.0)) / max(cosThetaO, 1e-8); + float sinGammaT = max(min(sinGammaO / etaP, 1.0), -1.0); + float cosGammaT = sqrt(1.0 - sinGammaT * sinGammaT); + float gammaT = asin(sinGammaT); + + // attenuation + vec3 Ap[4]; + float fresnel = mx_fresnel_dielectric(cosThetaO * cosGammaO, ior); + vec3 T = exp(-absorption_coefficient * (2.0 * cosGammaT / cosThetaT)); + mx_hair_attenuation(fresnel, T, Ap); + + // parameters for each lobe + vec2 angles[4]; + float alpha = cuticle_angle * M_PI - (M_PI / 2.0); // remap [0, 1] to [-PI/2, PI/2] + mx_hair_alpha_angles(alpha, sinThetaI, cosThetaI, angles); + + vec3 tint[4] = vec3[](tint_R, tint_TT, tint_TRT, tint_TRT); + + roughness_R = clamp(roughness_R, 0.001, 1.0); + roughness_TT = clamp(roughness_TT, 0.001, 1.0); + roughness_TRT = clamp(roughness_TRT, 0.001, 1.0); + vec2 vs[4] = vec2[](roughness_R, roughness_TT, roughness_TRT, roughness_TRT); + + // R, TT, TRT, TRRT+ + vec3 F = vec3(0.0); + for (int i = 0; i <= 3; ++i) + { + if (all(lessThanEqual(tint[i], vec3(0.0)))) + continue; + + float Mp = mx_hair_longitudinal_scattering(angles[i].x, angles[i].y, sinThetaO, cosThetaO, vs[i].x); + float Np = (i == 3) ? (1.0 / 2.0 * M_PI) : mx_hair_azimuthal_scattering(phi, i, vs[i].y, gammaO, gammaT); + F += Mp * Np * tint[i] * Ap[i]; + } + + return F; +} + +void mx_chiang_hair_bsdf_reflection( + vec3 L, + vec3 V, + vec3 P, + float occlusion, + vec3 tint_R, + vec3 tint_TT, + vec3 tint_TRT, + float ior, + vec2 roughness_R, + vec2 roughness_TT, + vec2 roughness_TRT, + float cuticle_angle, + vec3 absorption_coefficient, + vec3 N, + vec3 X, + inout BSDF bsdf +) +{ + vec3 F = mx_chiang_hair_bsdf( + L, + V, + tint_R, + tint_TT, + tint_TRT, + ior, + roughness_R, + roughness_TT, + roughness_TRT, + cuticle_angle, + absorption_coefficient, + N, + X + ); + + bsdf.throughput = vec3(0.0); + bsdf.response = F * occlusion * M_PI_INV; +} + +void mx_chiang_hair_bsdf_indirect( + vec3 V, + vec3 tint_R, + vec3 tint_TT, + vec3 tint_TRT, + float ior, + vec2 roughness_R, + vec2 roughness_TT, + vec2 roughness_TRT, + float cuticle_angle, + vec3 absorption_coefficient, + vec3 N, + vec3 X, + inout BSDF bsdf +) +{ + // this indirect lighing is *very* rough approximation + + N = mx_forward_facing_normal(N, V); + + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + FresnelData fd = mx_init_fresnel_dielectric(ior, 0.0, 1.0); + vec3 F = mx_compute_fresnel(NdotV, fd); + + vec2 roughness = (roughness_R + roughness_TT + roughness_TRT) / vec2(3.0); // ? + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + // use ggx because the environment map for FIS is preintegrated with ggx + float F0 = mx_ior_to_f0(ior); + vec3 comp = mx_ggx_energy_compensation(NdotV, avgAlpha, F); + vec3 dirAlbedo = mx_ggx_dir_albedo(NdotV, avgAlpha, F0, 1.0) * comp; + + vec3 Li = mx_environment_radiance(N, V, X, safeAlpha, 0, fd); + vec3 tint = (tint_R + tint_TT + tint_TRT) / vec3(3.0); // ? + + bsdf.throughput = vec3(0.0); + bsdf.response = Li * comp * tint; +} diff --git a/libraries/pbrlib/genglsl/pbrlib_genglsl_impl.mtlx b/libraries/pbrlib/genglsl/pbrlib_genglsl_impl.mtlx index 2a23886640..1c495cb4bd 100644 --- a/libraries/pbrlib/genglsl/pbrlib_genglsl_impl.mtlx +++ b/libraries/pbrlib/genglsl/pbrlib_genglsl_impl.mtlx @@ -25,6 +25,9 @@ + + + @@ -74,4 +77,13 @@ + + + + + + + + + diff --git a/libraries/pbrlib/pbrlib_defs.mtlx b/libraries/pbrlib/pbrlib_defs.mtlx index 2e71f80611..0bb75716e6 100644 --- a/libraries/pbrlib/pbrlib_defs.mtlx +++ b/libraries/pbrlib/pbrlib_defs.mtlx @@ -136,6 +136,25 @@ + + + + + + + + + + + + + + + + @@ -404,4 +423,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libraries/stdlib/genglsl/lib/mx_math.glsl b/libraries/stdlib/genglsl/lib/mx_math.glsl index 781a24a6e7..cff7835a4c 100644 --- a/libraries/stdlib/genglsl/lib/mx_math.glsl +++ b/libraries/stdlib/genglsl/lib/mx_math.glsl @@ -22,3 +22,8 @@ vec3 mx_srgb_encode(vec3 color) vec3 powSeg = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055; return mix(linSeg, powSeg, isAbove); } + +float mx_inversesqrt(float x) +{ + return inversesqrt(x); +} diff --git a/libraries/stdlib/genglsl/mx_smoothstep_float.glsl b/libraries/stdlib/genglsl/mx_smoothstep_float.glsl index 1bca2e4d9b..0b317bdf33 100644 --- a/libraries/stdlib/genglsl/mx_smoothstep_float.glsl +++ b/libraries/stdlib/genglsl/mx_smoothstep_float.glsl @@ -1,9 +1,9 @@ void mx_smoothstep_float(float val, float low, float high, out float result) { - if (val <= low) - result = 0.0; - else if (val >= high) + if (val >= high) result = 1.0; + else if (val <= low) + result = 0.0; else result = smoothstep(low, high, val); } diff --git a/libraries/stdlib/genmsl/lib/mx_math.metal b/libraries/stdlib/genmsl/lib/mx_math.metal index 7c4851ea31..46a8c5d57e 100644 --- a/libraries/stdlib/genmsl/lib/mx_math.metal +++ b/libraries/stdlib/genmsl/lib/mx_math.metal @@ -21,6 +21,11 @@ T1 mx_mod(T1 x, T2 y) return x - y * floor(x/y); } +float mx_inversesqrt(float x) +{ + return ::rsqrt(x); +} + #ifdef __DECL_GL_MATH_FUNCTIONS__ float radians(float degree) { return (degree * M_PI_F / 180.0f); } @@ -97,9 +102,6 @@ T atan(T y_over_x) { return ::atan(y_over_x); } template T atan(T y, T x) { return ::atan2(y, x); } -template -T inversesqrt(T x) { return ::rsqrt(x); } - #define lessThan(a, b) ((a) < (b)) #define lessThanEqual(a, b) ((a) <= (b)) #define greaterThan(a, b) ((a) > (b)) diff --git a/resources/Materials/Examples/DisneyPrincipled/disney_principled_carpaint.mtlx b/resources/Materials/Examples/DisneyPrincipled/disney_principled_carpaint.mtlx new file mode 100644 index 0000000000..7c5a0ae94f --- /dev/null +++ b/resources/Materials/Examples/DisneyPrincipled/disney_principled_carpaint.mtlx @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/libraries/bxdf/disney_brdf_2012.mtlx b/resources/Materials/Examples/DisneyPrincipled/disney_principled_default.mtlx similarity index 60% rename from libraries/bxdf/disney_brdf_2012.mtlx rename to resources/Materials/Examples/DisneyPrincipled/disney_principled_default.mtlx index d65e7dbc55..717d769870 100644 --- a/libraries/bxdf/disney_brdf_2012.mtlx +++ b/resources/Materials/Examples/DisneyPrincipled/disney_principled_default.mtlx @@ -1,18 +1,21 @@ - + - - - + - - - + + + + + + + + diff --git a/resources/Materials/Examples/DisneyPrincipled/disney_principled_glass.mtlx b/resources/Materials/Examples/DisneyPrincipled/disney_principled_glass.mtlx new file mode 100644 index 0000000000..dfb1fab67d --- /dev/null +++ b/resources/Materials/Examples/DisneyPrincipled/disney_principled_glass.mtlx @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/resources/Materials/Examples/DisneyPrincipled/disney_principled_gold.mtlx b/resources/Materials/Examples/DisneyPrincipled/disney_principled_gold.mtlx new file mode 100644 index 0000000000..f0dde69049 --- /dev/null +++ b/resources/Materials/Examples/DisneyPrincipled/disney_principled_gold.mtlx @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/resources/Materials/Examples/DisneyPrincipled/disney_principled_plastic.mtlx b/resources/Materials/Examples/DisneyPrincipled/disney_principled_plastic.mtlx new file mode 100644 index 0000000000..7b2561b5ec --- /dev/null +++ b/resources/Materials/Examples/DisneyPrincipled/disney_principled_plastic.mtlx @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/resources/Materials/Examples/SimpleHair/simple_hair_default.mtlx b/resources/Materials/Examples/SimpleHair/simple_hair_default.mtlx new file mode 100644 index 0000000000..056c79b554 --- /dev/null +++ b/resources/Materials/Examples/SimpleHair/simple_hair_default.mtlx @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/Materials/TestSuite/pbrlib/bsdf/hair_bsdf.mtlx b/resources/Materials/TestSuite/pbrlib/bsdf/hair_bsdf.mtlx new file mode 100644 index 0000000000..8d95ffe63f --- /dev/null +++ b/resources/Materials/TestSuite/pbrlib/bsdf/hair_bsdf.mtlx @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/Materials/TestSuite/pbrlib/surfaceshader/hair_surfaceshader.mtlx b/resources/Materials/TestSuite/pbrlib/surfaceshader/hair_surfaceshader.mtlx new file mode 100644 index 0000000000..056c79b554 --- /dev/null +++ b/resources/Materials/TestSuite/pbrlib/surfaceshader/hair_surfaceshader.mtlx @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/MaterialXCore/Value.cpp b/source/MaterialXCore/Value.cpp index 3e8c1f4462..68da4b5b9d 100644 --- a/source/MaterialXCore/Value.cpp +++ b/source/MaterialXCore/Value.cpp @@ -193,6 +193,60 @@ template T fromValueString(const string& value) return data; } +StringVec parseStructValueString(const string& value) +{ + static const char SEPARATOR = ';'; + static const char OPEN_BRACE = '{'; + static const char CLOSE_BRACE = '}'; + + if (value.empty()) + return StringVec(); + + // Validate the string is correctly formatted - must be at least 2 characters long and start and end with braces + if (value.size() < 2 || (value[0] != OPEN_BRACE || value[value.size()-1] != CLOSE_BRACE)) + { + return StringVec(); + } + + StringVec split; + + // Strip off the surrounding braces + string substring = value.substr(1, value.size()-2); + + // Sequentially examine each character to parse the list initializer. + string part = ""; + int braceDepth = 0; + for (const char c : substring) + { + if (c == OPEN_BRACE) + { + // We've already trimmed the starting brace, so any additional braces indicate members that are themselves list initializers. + // We will just return this as a string of the list initializer. + braceDepth += 1; + } + if (braceDepth > 0 && c == CLOSE_BRACE) + { + braceDepth -= 1; + } + + if (braceDepth == 0 && c == SEPARATOR) + { + // When we hit a separator we store the currently accumulated part, and clear to start collecting the next. + split.emplace_back(part); + part = ""; + } + else + { + part += c; + } + } + + if (!part.empty()) + split.emplace_back(part); + + return split; +} + // // TypedValue methods // diff --git a/source/MaterialXCore/Value.h b/source/MaterialXCore/Value.h index 1bd29b1d2b..764a0ccc01 100644 --- a/source/MaterialXCore/Value.h +++ b/source/MaterialXCore/Value.h @@ -216,6 +216,11 @@ template MX_CORE_API string toValueString(const T& data); /// @throws ExceptionTypeError if the conversion cannot be performed. template MX_CORE_API T fromValueString(const string& value); +/// Tokenize the string representation of a struct value i.e, "{1;2;3}" into a +/// vector of substrings. +/// Note: "{1;2;{3;4;5}}" will be split in to ["1", "2", "{3;4;5}"] +MX_CORE_API StringVec parseStructValueString(const string& value); + /// Forward declaration of specific template instantiations. /// Base types MX_CORE_EXTERN_TEMPLATE(TypedValue); diff --git a/source/MaterialXTest/MaterialXCore/Value.cpp b/source/MaterialXTest/MaterialXCore/Value.cpp index 40d6b120e7..31c333d211 100644 --- a/source/MaterialXTest/MaterialXCore/Value.cpp +++ b/source/MaterialXTest/MaterialXCore/Value.cpp @@ -78,6 +78,15 @@ TEST_CASE("Value strings", "[value]") REQUIRE_THROWS_AS(mx::fromValueString("text"), mx::ExceptionTypeError); REQUIRE_THROWS_AS(mx::fromValueString("1"), mx::ExceptionTypeError); REQUIRE_THROWS_AS(mx::fromValueString("1"), mx::ExceptionTypeError); + + // Parse value strings using structure syntax features. + REQUIRE(mx::parseStructValueString("{{1;2;3};4}") == (std::vector{"{1;2;3}","4"})); + REQUIRE(mx::parseStructValueString("{1;2;3;4}") == (std::vector{"1","2","3","4"})); + REQUIRE(mx::parseStructValueString("{1;{2;3};4}") == (std::vector{"1","{2;3}","4"})); + REQUIRE(mx::parseStructValueString("{1;{2;3;4}}") == (std::vector{"1","{2;3;4}"})); + REQUIRE(mx::parseStructValueString("{1;{2;{3;4}}}") == (std::vector{"1","{2;{3;4}}"})); + REQUIRE(mx::parseStructValueString("{1;2;{3};4}") == (std::vector{"1","2","{3}","4"})); + REQUIRE(mx::parseStructValueString("{1;2;{3};4}") == (std::vector{"1","2","{3}","4"})); } TEST_CASE("Typed values", "[value]") diff --git a/source/MaterialXTest/MaterialXGenMdl/GenMdl.cpp b/source/MaterialXTest/MaterialXGenMdl/GenMdl.cpp index db75a5ddf6..12a1f33868 100644 --- a/source/MaterialXTest/MaterialXGenMdl/GenMdl.cpp +++ b/source/MaterialXTest/MaterialXGenMdl/GenMdl.cpp @@ -91,9 +91,14 @@ TEST_CASE("GenShader: MDL Implementation Check", "[genmdl]") mx::StringSet generatorSkipNodeTypes; generatorSkipNodeTypes.insert("light"); + mx::StringSet generatorSkipNodeDefs; + generatorSkipNodeDefs.insert("ND_chiang_hair_roughness"); + generatorSkipNodeDefs.insert("ND_chiang_hair_absorption_from_color"); + generatorSkipNodeDefs.insert("ND_deon_hair_absorption_from_melanin"); + generatorSkipNodeDefs.insert("ND_chiang_hair_bsdf"); - GenShaderUtil::checkImplementations(context, generatorSkipNodeTypes, generatorSkipNodeDefs, 31); + GenShaderUtil::checkImplementations(context, generatorSkipNodeTypes, generatorSkipNodeDefs, 35); } diff --git a/source/MaterialXTest/MaterialXGenMdl/GenMdl.h b/source/MaterialXTest/MaterialXGenMdl/GenMdl.h index f40254c5f4..1e617f73fb 100644 --- a/source/MaterialXTest/MaterialXGenMdl/GenMdl.h +++ b/source/MaterialXTest/MaterialXGenMdl/GenMdl.h @@ -49,6 +49,10 @@ class MdlShaderGeneratorTester : public GenShaderUtil::ShaderGeneratorTester // df_cuda will currently hang on rendering one of the shaders in this file _skipFiles.insert("heighttonormal_in_nodegraph.mtlx"); } + + _skipFiles.insert("hair_bsdf.mtlx"); + _skipFiles.insert("hair_surfaceshader.mtlx"); + ShaderGeneratorTester::addSkipFiles(); } diff --git a/source/MaterialXTest/MaterialXGenMsl/GenMsl.cpp b/source/MaterialXTest/MaterialXGenMsl/GenMsl.cpp index b0309ea44d..414bc3dd5d 100644 --- a/source/MaterialXTest/MaterialXGenMsl/GenMsl.cpp +++ b/source/MaterialXTest/MaterialXGenMsl/GenMsl.cpp @@ -84,7 +84,12 @@ TEST_CASE("GenShader: MSL Implementation Check", "[genmsl]") mx::StringSet generatorSkipNodeTypes; mx::StringSet generatorSkipNodeDefs; - GenShaderUtil::checkImplementations(context, generatorSkipNodeTypes, generatorSkipNodeDefs, 30); + generatorSkipNodeDefs.insert("ND_chiang_hair_roughness"); + generatorSkipNodeDefs.insert("ND_chiang_hair_absorption_from_color"); + generatorSkipNodeDefs.insert("ND_deon_hair_absorption_from_melanin"); + generatorSkipNodeDefs.insert("ND_chiang_hair_bsdf"); + + GenShaderUtil::checkImplementations(context, generatorSkipNodeTypes, generatorSkipNodeDefs, 34); } TEST_CASE("GenShader: MSL Unique Names", "[genmsl]") diff --git a/source/MaterialXTest/MaterialXGenMsl/GenMsl.h b/source/MaterialXTest/MaterialXGenMsl/GenMsl.h index 78cb7a8f4e..692c283fda 100644 --- a/source/MaterialXTest/MaterialXGenMsl/GenMsl.h +++ b/source/MaterialXTest/MaterialXGenMsl/GenMsl.h @@ -41,6 +41,12 @@ class MslShaderGeneratorTester : public GenShaderUtil::ShaderGeneratorTester ParentClass::addSkipNodeDefs(); } + void addSkipFiles() override + { + _skipFiles.insert("hair_bsdf.mtlx"); + _skipFiles.insert("hair_surfaceshader.mtlx"); + } + void setupDependentLibraries() override { ParentClass::setupDependentLibraries(); diff --git a/source/MaterialXTest/MaterialXGenOsl/GenOsl.cpp b/source/MaterialXTest/MaterialXGenOsl/GenOsl.cpp index f31c3d2a9b..c89f9ab19d 100644 --- a/source/MaterialXTest/MaterialXGenOsl/GenOsl.cpp +++ b/source/MaterialXTest/MaterialXGenOsl/GenOsl.cpp @@ -87,9 +87,14 @@ TEST_CASE("GenShader: OSL Implementation Check", "[genosl]") mx::StringSet generatorSkipNodeTypes; generatorSkipNodeTypes.insert("light"); + mx::StringSet generatorSkipNodeDefs; + generatorSkipNodeDefs.insert("ND_chiang_hair_roughness"); + generatorSkipNodeDefs.insert("ND_chiang_hair_absorption_from_color"); + generatorSkipNodeDefs.insert("ND_deon_hair_absorption_from_melanin"); + generatorSkipNodeDefs.insert("ND_chiang_hair_bsdf"); - GenShaderUtil::checkImplementations(context, generatorSkipNodeTypes, generatorSkipNodeDefs, 31); + GenShaderUtil::checkImplementations(context, generatorSkipNodeTypes, generatorSkipNodeDefs, 35); } TEST_CASE("GenShader: OSL Unique Names", "[genosl]") diff --git a/source/MaterialXTest/MaterialXGenOsl/GenOsl.h b/source/MaterialXTest/MaterialXGenOsl/GenOsl.h index a4ccdc2003..f02f750d26 100644 --- a/source/MaterialXTest/MaterialXGenOsl/GenOsl.h +++ b/source/MaterialXTest/MaterialXGenOsl/GenOsl.h @@ -42,6 +42,13 @@ class OslShaderGeneratorTester : public GenShaderUtil::ShaderGeneratorTester { _skipLibraryFiles.insert( "pbrlib_genosl_arnold_impl.mtlx" ); } + + void addSkipFiles() override + { + _skipFiles.insert("hair_bsdf.mtlx"); + _skipFiles.insert("hair_surfaceshader.mtlx"); + } + // Ignore light shaders in the document for OSL void findLights(mx::DocumentPtr /*doc*/, std::vector& lights) override {