From 7ca834e3ef4215d72530972a1cacb35cd7c4dac6 Mon Sep 17 00:00:00 2001 From: sunag Date: Tue, 16 Aug 2022 21:53:08 -0300 Subject: [PATCH] Nodes: MaterialX Noise Functions Lib (#24504) * add includes for constructor argument * add support to array for function call * add function interface * convert to nodeObjects function call parameters and includes * add fn * add materialx noise functions example * adjust scale to better visual --- examples/files.json | 1 + examples/jsm/nodes/core/CodeNode.js | 6 +- examples/jsm/nodes/core/FunctionCallNode.js | 25 +- examples/jsm/nodes/core/FunctionNode.js | 4 +- examples/jsm/nodes/materialx/Disclaimer.md | 199 ++++++ .../nodes/materialx/functions/lib/mx_hsv.js | 56 ++ .../nodes/materialx/functions/lib/mx_noise.js | 607 ++++++++++++++++++ .../jsm/nodes/parsers/GLSLNodeFunction.js | 27 +- .../shadernode/ShaderNodeBaseElements.js | 8 +- .../webgl_nodes_materialx_noise.jpg | Bin 0 -> 37486 bytes examples/webgl_nodes_materialx_noise.html | 214 ++++++ 11 files changed, 1128 insertions(+), 19 deletions(-) create mode 100644 examples/jsm/nodes/materialx/Disclaimer.md create mode 100644 examples/jsm/nodes/materialx/functions/lib/mx_hsv.js create mode 100644 examples/jsm/nodes/materialx/functions/lib/mx_noise.js create mode 100644 examples/screenshots/webgl_nodes_materialx_noise.jpg create mode 100644 examples/webgl_nodes_materialx_noise.html diff --git a/examples/files.json b/examples/files.json index 84feb7a1c232b2..b6efb2368c062a 100644 --- a/examples/files.json +++ b/examples/files.json @@ -235,6 +235,7 @@ "webgl_nodes_materials_instance_uniform", "webgl_nodes_materials_physical_clearcoat", "webgl_nodes_materials_standard", + "webgl_nodes_materialx_noise", "webgl_nodes_playground", "webgl_nodes_points" ], diff --git a/examples/jsm/nodes/core/CodeNode.js b/examples/jsm/nodes/core/CodeNode.js index 0dfb8938463913..bb571a76ac7526 100644 --- a/examples/jsm/nodes/core/CodeNode.js +++ b/examples/jsm/nodes/core/CodeNode.js @@ -2,15 +2,15 @@ import Node from './Node.js'; class CodeNode extends Node { - constructor( code = '', nodeType = 'code' ) { + constructor( code = '', includes = [] ) { - super( nodeType ); + super( 'code' ); this.isCodeNode = true; this.code = code; - this._includes = []; + this._includes = includes; } diff --git a/examples/jsm/nodes/core/FunctionCallNode.js b/examples/jsm/nodes/core/FunctionCallNode.js index e5609168ad7e8d..1d257f9d4b4e89 100644 --- a/examples/jsm/nodes/core/FunctionCallNode.js +++ b/examples/jsm/nodes/core/FunctionCallNode.js @@ -40,17 +40,32 @@ class FunctionCallNode extends TempNode { const inputs = functionNode.getInputs( builder ); const parameters = this.parameters; - for ( const inputNode of inputs ) { + if ( Array.isArray( parameters ) ) { - const node = parameters[ inputNode.name ]; + for ( let i = 0; i < parameters.length; i ++ ) { - if ( node !== undefined ) { + const inputNode = inputs[ i ]; + const node = parameters[ i ]; params.push( node.build( builder, inputNode.type ) ); - } else { + } + + } else { + + for ( const inputNode of inputs ) { + + const node = parameters[ inputNode.name ]; + + if ( node !== undefined ) { + + params.push( node.build( builder, inputNode.type ) ); + + } else { + + throw new Error( `FunctionCallNode: Input '${inputNode.name}' not found in FunctionNode.` ); - throw new Error( `FunctionCallNode: Input '${inputNode.name}' not found in FunctionNode.` ); + } } diff --git a/examples/jsm/nodes/core/FunctionNode.js b/examples/jsm/nodes/core/FunctionNode.js index 175835bf1315c0..a7806ad88a8471 100644 --- a/examples/jsm/nodes/core/FunctionNode.js +++ b/examples/jsm/nodes/core/FunctionNode.js @@ -3,9 +3,9 @@ import FunctionCallNode from './FunctionCallNode.js'; class FunctionNode extends CodeNode { - constructor( code = '' ) { + constructor( code = '', includes = [] ) { - super( code ); + super( code, includes ); this.keywords = {}; diff --git a/examples/jsm/nodes/materialx/Disclaimer.md b/examples/jsm/nodes/materialx/Disclaimer.md new file mode 100644 index 00000000000000..2badec8cfdad74 --- /dev/null +++ b/examples/jsm/nodes/materialx/Disclaimer.md @@ -0,0 +1,199 @@ +## MaterialX + +MaterialX is a project of the +[Academy Software Foundation](https://www.aswf.io/) and relies on the ASWF +governance policies, supported by the Linux Foundation. + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +``` +------------------------------------------------------------------------- + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS +``` \ No newline at end of file diff --git a/examples/jsm/nodes/materialx/functions/lib/mx_hsv.js b/examples/jsm/nodes/materialx/functions/lib/mx_hsv.js new file mode 100644 index 00000000000000..fa30c833639d3d --- /dev/null +++ b/examples/jsm/nodes/materialx/functions/lib/mx_hsv.js @@ -0,0 +1,56 @@ +import { fn } from '../../../Nodes.js'; + +// Original shader code from: +// https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/stdlib/genglsl/lib/mx_hsv.glsl + +export const mx_hsvtorgb = fn( `vec3 mx_hsvtorgb(vec3 hsv) +{ + // Reference for this technique: Foley & van Dam + float h = hsv.x; float s = hsv.y; float v = hsv.z; + if (s < 0.0001f) { + return vec3 (v, v, v); + } else { + h = 6.0f * (h - floor(h)); // expand to [0..6) + int hi = int(trunc(h)); + float f = h - float(hi); + float p = v * (1.0f-s); + float q = v * (1.0f-s*f); + float t = v * (1.0f-s*(1.0f-f)); + if (hi == 0) + return vec3 (v, t, p); + else if (hi == 1) + return vec3 (q, v, p); + else if (hi == 2) + return vec3 (p, v, t); + else if (hi == 3) + return vec3 (p, q, v); + else if (hi == 4) + return vec3 (t, p, v); + return vec3 (v, p, q); + } +}` ); + +export const mx_rgbtohsv = fn( `vec3 mx_rgbtohsv(vec3 c) +{ + // See Foley & van Dam + float r = c.x; float g = c.y; float b = c.z; + float mincomp = min (r, min(g, b)); + float maxcomp = max (r, max(g, b)); + float delta = maxcomp - mincomp; // chroma + float h, s, v; + v = maxcomp; + if (maxcomp > 0.0f) + s = delta / maxcomp; + else s = 0.0f; + if (s <= 0.0f) + h = 0.0f; + else { + if (r >= maxcomp) h = (g-b) / delta; + else if (g >= maxcomp) h = 2.0f + (b-r) / delta; + else h = 4.0f + (r-g) / delta; + h *= (1.0f/6.0f); + if (h < 0.0f) + h += 1.0f; + } + return vec3(h, s, v); +}` ); diff --git a/examples/jsm/nodes/materialx/functions/lib/mx_noise.js b/examples/jsm/nodes/materialx/functions/lib/mx_noise.js new file mode 100644 index 00000000000000..7d495f9e79b91f --- /dev/null +++ b/examples/jsm/nodes/materialx/functions/lib/mx_noise.js @@ -0,0 +1,607 @@ +import { code, fn } from '../../../Nodes.js'; + +// Original shader code from: +// https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/stdlib/genglsl/lib/mx_noise.glsl + +export const mx_noise = code( `float mx_select(bool b, float t, float f) +{ + return b ? t : f; +} + +float mx_negate_if(float val, bool b) +{ + return b ? -val : val; +} + +int mx_floor(float x) +{ + return int(floor(x)); +} + +// return mx_floor as well as the fractional remainder +float mx_floorfrac(float x, out int i) +{ + i = mx_floor(x); + return x - float(i); +} + +float mx_bilerp(float v0, float v1, float v2, float v3, float s, float t) +{ + float s1 = 1.0 - s; + return (1.0 - t) * (v0*s1 + v1*s) + t * (v2*s1 + v3*s); +} +vec3 mx_bilerp(vec3 v0, vec3 v1, vec3 v2, vec3 v3, float s, float t) +{ + float s1 = 1.0 - s; + return (1.0 - t) * (v0*s1 + v1*s) + t * (v2*s1 + v3*s); +} +float mx_trilerp(float v0, float v1, float v2, float v3, float v4, float v5, float v6, float v7, float s, float t, float r) +{ + float s1 = 1.0 - s; + float t1 = 1.0 - t; + float r1 = 1.0 - r; + return (r1*(t1*(v0*s1 + v1*s) + t*(v2*s1 + v3*s)) + + r*(t1*(v4*s1 + v5*s) + t*(v6*s1 + v7*s))); +} +vec3 mx_trilerp(vec3 v0, vec3 v1, vec3 v2, vec3 v3, vec3 v4, vec3 v5, vec3 v6, vec3 v7, float s, float t, float r) +{ + float s1 = 1.0 - s; + float t1 = 1.0 - t; + float r1 = 1.0 - r; + return (r1*(t1*(v0*s1 + v1*s) + t*(v2*s1 + v3*s)) + + r*(t1*(v4*s1 + v5*s) + t*(v6*s1 + v7*s))); +} + +// 2 and 3 dimensional gradient functions - perform a dot product against a +// randomly chosen vector. Note that the gradient vector is not normalized, but +// this only affects the overal "scale" of the result, so we simply account for +// the scale by multiplying in the corresponding "perlin" function. +float mx_gradient_float(uint hash, float x, float y) +{ + // 8 possible directions (+-1,+-2) and (+-2,+-1) + uint h = hash & 7u; + float u = mx_select(h<4u, x, y); + float v = 2.0 * mx_select(h<4u, y, x); + // compute the dot product with (x,y). + return mx_negate_if(u, bool(h&1u)) + mx_negate_if(v, bool(h&2u)); +} +float mx_gradient_float(uint hash, float x, float y, float z) +{ + // use vectors pointing to the edges of the cube + uint h = hash & 15u; + float u = mx_select(h<8u, x, y); + float v = mx_select(h<4u, y, mx_select((h==12u)||(h==14u), x, z)); + return mx_negate_if(u, bool(h&1u)) + mx_negate_if(v, bool(h&2u)); +} +vec3 mx_gradient_vec3(uvec3 hash, float x, float y) +{ + return vec3(mx_gradient_float(hash.x, x, y), mx_gradient_float(hash.y, x, y), mx_gradient_float(hash.z, x, y)); +} +vec3 mx_gradient_vec3(uvec3 hash, float x, float y, float z) +{ + return vec3(mx_gradient_float(hash.x, x, y, z), mx_gradient_float(hash.y, x, y, z), mx_gradient_float(hash.z, x, y, z)); +} +// Scaling factors to normalize the result of gradients above. +// These factors were experimentally calculated to be: +// 2D: 0.6616 +// 3D: 0.9820 +float mx_gradient_scale2d(float v) { return 0.6616 * v; } +float mx_gradient_scale3d(float v) { return 0.9820 * v; } +vec3 mx_gradient_scale2d(vec3 v) { return 0.6616 * v; } +vec3 mx_gradient_scale3d(vec3 v) { return 0.9820 * v; } + +/// Bitwise circular rotation left by k bits (for 32 bit unsigned integers) +uint mx_rotl32(uint x, int k) +{ + return (x<>(32-k)); +} + +void mx_bjmix(inout uint a, inout uint b, inout uint c) +{ + a -= c; a ^= mx_rotl32(c, 4); c += b; + b -= a; b ^= mx_rotl32(a, 6); a += c; + c -= b; c ^= mx_rotl32(b, 8); b += a; + a -= c; a ^= mx_rotl32(c,16); c += b; + b -= a; b ^= mx_rotl32(a,19); a += c; + c -= b; c ^= mx_rotl32(b, 4); b += a; +} + +// Mix up and combine the bits of a, b, and c (doesn't change them, but +// returns a hash of those three original values). +uint mx_bjfinal(uint a, uint b, uint c) +{ + c ^= b; c -= mx_rotl32(b,14); + a ^= c; a -= mx_rotl32(c,11); + b ^= a; b -= mx_rotl32(a,25); + c ^= b; c -= mx_rotl32(b,16); + a ^= c; a -= mx_rotl32(c,4); + b ^= a; b -= mx_rotl32(a,14); + c ^= b; c -= mx_rotl32(b,24); + return c; +} + +// Convert a 32 bit integer into a floating point number in [0,1] +float mx_bits_to_01(uint bits) +{ + return float(bits) / float(uint(0xffffffff)); +} + +float mx_fade(float t) +{ + return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); +} + +uint mx_hash_int(int x) +{ + uint len = 1u; + uint seed = uint(0xdeadbeef) + (len << 2u) + 13u; + return mx_bjfinal(seed+uint(x), seed, seed); +} + +uint mx_hash_int(int x, int y) +{ + uint len = 2u; + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (len << 2u) + 13u; + a += uint(x); + b += uint(y); + return mx_bjfinal(a, b, c); +} + +uint mx_hash_int(int x, int y, int z) +{ + uint len = 3u; + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (len << 2u) + 13u; + a += uint(x); + b += uint(y); + c += uint(z); + return mx_bjfinal(a, b, c); +} + +uint mx_hash_int(int x, int y, int z, int xx) +{ + uint len = 4u; + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (len << 2u) + 13u; + a += uint(x); + b += uint(y); + c += uint(z); + mx_bjmix(a, b, c); + a += uint(xx); + return mx_bjfinal(a, b, c); +} + +uint mx_hash_int(int x, int y, int z, int xx, int yy) +{ + uint len = 5u; + uint a, b, c; + a = b = c = uint(0xdeadbeef) + (len << 2u) + 13u; + a += uint(x); + b += uint(y); + c += uint(z); + mx_bjmix(a, b, c); + a += uint(xx); + b += uint(yy); + return mx_bjfinal(a, b, c); +} + +uvec3 mx_hash_vec3(int x, int y) +{ + uint h = mx_hash_int(x, y); + // we only need the low-order bits to be random, so split out + // the 32 bit result into 3 parts for each channel + uvec3 result; + result.x = (h ) & 0xFFu; + result.y = (h >> 8 ) & 0xFFu; + result.z = (h >> 16) & 0xFFu; + return result; +} + +uvec3 mx_hash_vec3(int x, int y, int z) +{ + uint h = mx_hash_int(x, y, z); + // we only need the low-order bits to be random, so split out + // the 32 bit result into 3 parts for each channel + uvec3 result; + result.x = (h ) & 0xFFu; + result.y = (h >> 8 ) & 0xFFu; + result.z = (h >> 16) & 0xFFu; + return result; +} + +float mx_perlin_noise_float(vec2 p) +{ + int X, Y; + float fx = mx_floorfrac(p.x, X); + float fy = mx_floorfrac(p.y, Y); + float u = mx_fade(fx); + float v = mx_fade(fy); + float result = mx_bilerp( + mx_gradient_float(mx_hash_int(X , Y ), fx , fy ), + mx_gradient_float(mx_hash_int(X+1, Y ), fx-1.0, fy ), + mx_gradient_float(mx_hash_int(X , Y+1), fx , fy-1.0), + mx_gradient_float(mx_hash_int(X+1, Y+1), fx-1.0, fy-1.0), + u, v); + return mx_gradient_scale2d(result); +} + +float mx_perlin_noise_float(vec3 p) +{ + int X, Y, Z; + float fx = mx_floorfrac(p.x, X); + float fy = mx_floorfrac(p.y, Y); + float fz = mx_floorfrac(p.z, Z); + float u = mx_fade(fx); + float v = mx_fade(fy); + float w = mx_fade(fz); + float result = mx_trilerp( + mx_gradient_float(mx_hash_int(X , Y , Z ), fx , fy , fz ), + mx_gradient_float(mx_hash_int(X+1, Y , Z ), fx-1.0, fy , fz ), + mx_gradient_float(mx_hash_int(X , Y+1, Z ), fx , fy-1.0, fz ), + mx_gradient_float(mx_hash_int(X+1, Y+1, Z ), fx-1.0, fy-1.0, fz ), + mx_gradient_float(mx_hash_int(X , Y , Z+1), fx , fy , fz-1.0), + mx_gradient_float(mx_hash_int(X+1, Y , Z+1), fx-1.0, fy , fz-1.0), + mx_gradient_float(mx_hash_int(X , Y+1, Z+1), fx , fy-1.0, fz-1.0), + mx_gradient_float(mx_hash_int(X+1, Y+1, Z+1), fx-1.0, fy-1.0, fz-1.0), + u, v, w); + return mx_gradient_scale3d(result); +} + +vec3 mx_perlin_noise_vec3(vec2 p) +{ + int X, Y; + float fx = mx_floorfrac(p.x, X); + float fy = mx_floorfrac(p.y, Y); + float u = mx_fade(fx); + float v = mx_fade(fy); + vec3 result = mx_bilerp( + mx_gradient_vec3(mx_hash_vec3(X , Y ), fx , fy ), + mx_gradient_vec3(mx_hash_vec3(X+1, Y ), fx-1.0, fy ), + mx_gradient_vec3(mx_hash_vec3(X , Y+1), fx , fy-1.0), + mx_gradient_vec3(mx_hash_vec3(X+1, Y+1), fx-1.0, fy-1.0), + u, v); + return mx_gradient_scale2d(result); +} + +vec3 mx_perlin_noise_vec3(vec3 p) +{ + int X, Y, Z; + float fx = mx_floorfrac(p.x, X); + float fy = mx_floorfrac(p.y, Y); + float fz = mx_floorfrac(p.z, Z); + float u = mx_fade(fx); + float v = mx_fade(fy); + float w = mx_fade(fz); + vec3 result = mx_trilerp( + mx_gradient_vec3(mx_hash_vec3(X , Y , Z ), fx , fy , fz ), + mx_gradient_vec3(mx_hash_vec3(X+1, Y , Z ), fx-1.0, fy , fz ), + mx_gradient_vec3(mx_hash_vec3(X , Y+1, Z ), fx , fy-1.0, fz ), + mx_gradient_vec3(mx_hash_vec3(X+1, Y+1, Z ), fx-1.0, fy-1.0, fz ), + mx_gradient_vec3(mx_hash_vec3(X , Y , Z+1), fx , fy , fz-1.0), + mx_gradient_vec3(mx_hash_vec3(X+1, Y , Z+1), fx-1.0, fy , fz-1.0), + mx_gradient_vec3(mx_hash_vec3(X , Y+1, Z+1), fx , fy-1.0, fz-1.0), + mx_gradient_vec3(mx_hash_vec3(X+1, Y+1, Z+1), fx-1.0, fy-1.0, fz-1.0), + u, v, w); + return mx_gradient_scale3d(result); +} + +float mx_cell_noise_float(float p) +{ + int ix = mx_floor(p); + return mx_bits_to_01(mx_hash_int(ix)); +} + +float mx_cell_noise_float(vec2 p) +{ + int ix = mx_floor(p.x); + int iy = mx_floor(p.y); + return mx_bits_to_01(mx_hash_int(ix, iy)); +} + +float mx_cell_noise_float(vec3 p) +{ + int ix = mx_floor(p.x); + int iy = mx_floor(p.y); + int iz = mx_floor(p.z); + return mx_bits_to_01(mx_hash_int(ix, iy, iz)); +} + +float mx_cell_noise_float(vec4 p) +{ + int ix = mx_floor(p.x); + int iy = mx_floor(p.y); + int iz = mx_floor(p.z); + int iw = mx_floor(p.w); + return mx_bits_to_01(mx_hash_int(ix, iy, iz, iw)); +} + +vec3 mx_cell_noise_vec3(float p) +{ + int ix = mx_floor(p); + return vec3( + mx_bits_to_01(mx_hash_int(ix, 0)), + mx_bits_to_01(mx_hash_int(ix, 1)), + mx_bits_to_01(mx_hash_int(ix, 2)) + ); +} + +vec3 mx_cell_noise_vec3(vec2 p) +{ + int ix = mx_floor(p.x); + int iy = mx_floor(p.y); + return vec3( + mx_bits_to_01(mx_hash_int(ix, iy, 0)), + mx_bits_to_01(mx_hash_int(ix, iy, 1)), + mx_bits_to_01(mx_hash_int(ix, iy, 2)) + ); +} + +vec3 mx_cell_noise_vec3(vec3 p) +{ + int ix = mx_floor(p.x); + int iy = mx_floor(p.y); + int iz = mx_floor(p.z); + return vec3( + mx_bits_to_01(mx_hash_int(ix, iy, iz, 0)), + mx_bits_to_01(mx_hash_int(ix, iy, iz, 1)), + mx_bits_to_01(mx_hash_int(ix, iy, iz, 2)) + ); +} + +vec3 mx_cell_noise_vec3(vec4 p) +{ + int ix = mx_floor(p.x); + int iy = mx_floor(p.y); + int iz = mx_floor(p.z); + int iw = mx_floor(p.w); + return vec3( + mx_bits_to_01(mx_hash_int(ix, iy, iz, iw, 0)), + mx_bits_to_01(mx_hash_int(ix, iy, iz, iw, 1)), + mx_bits_to_01(mx_hash_int(ix, iy, iz, iw, 2)) + ); +} + +float mx_fractal_noise_float(vec3 p, int octaves, float lacunarity, float diminish) +{ + float result = 0.0; + float amplitude = 1.0; + for (int i = 0; i < octaves; ++i) + { + result += amplitude * mx_perlin_noise_float(p); + amplitude *= diminish; + p *= lacunarity; + } + return result; +} + +vec3 mx_fractal_noise_vec3(vec3 p, int octaves, float lacunarity, float diminish) +{ + vec3 result = vec3(0.0); + float amplitude = 1.0; + for (int i = 0; i < octaves; ++i) + { + result += amplitude * mx_perlin_noise_vec3(p); + amplitude *= diminish; + p *= lacunarity; + } + return result; +} + +vec2 mx_fractal_noise_vec2(vec3 p, int octaves, float lacunarity, float diminish) +{ + return vec2(mx_fractal_noise_float(p, octaves, lacunarity, diminish), + mx_fractal_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish)); +} + +vec4 mx_fractal_noise_vec4(vec3 p, int octaves, float lacunarity, float diminish) +{ + vec3 c = mx_fractal_noise_vec3(p, octaves, lacunarity, diminish); + float f = mx_fractal_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish); + return vec4(c, f); +} + +float mx_worley_distance(vec2 p, int x, int y, int xoff, int yoff, float jitter, int metric) +{ + vec3 tmp = mx_cell_noise_vec3(vec2(x+xoff, y+yoff)); + vec2 off = vec2(tmp.x, tmp.y); + + off -= 0.5f; + off *= jitter; + off += 0.5f; + + vec2 cellpos = vec2(float(x), float(y)) + off; + vec2 diff = cellpos - p; + if (metric == 2) + return abs(diff.x) + abs(diff.y); // Manhattan distance + if (metric == 3) + return max(abs(diff.x), abs(diff.y)); // Chebyshev distance + // Either Euclidian or Distance^2 + return dot(diff, diff); +} + +float mx_worley_distance(vec3 p, int x, int y, int z, int xoff, int yoff, int zoff, float jitter, int metric) +{ + vec3 off = mx_cell_noise_vec3(vec3(x+xoff, y+yoff, z+zoff)); + + off -= 0.5f; + off *= jitter; + off += 0.5f; + + vec3 cellpos = vec3(float(x), float(y), float(z)) + off; + vec3 diff = cellpos - p; + if (metric == 2) + return abs(diff.x) + abs(diff.y) + abs(diff.z); // Manhattan distance + if (metric == 3) + return max(max(abs(diff.x), abs(diff.y)), abs(diff.z)); // Chebyshev distance + // Either Euclidian or Distance^2 + return dot(diff, diff); +} + +float mx_worley_noise_float(vec2 p, float jitter, int metric) +{ + int X, Y; + vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); + float sqdist = 1e6f; // Some big number for jitter > 1 (not all GPUs may be IEEE) + for (int x = -1; x <= 1; ++x) + { + for (int y = -1; y <= 1; ++y) + { + float dist = mx_worley_distance(localpos, x, y, X, Y, jitter, metric); + sqdist = min(sqdist, dist); + } + } + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; +} + +vec2 mx_worley_noise_vec2(vec2 p, float jitter, int metric) +{ + int X, Y; + vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); + vec2 sqdist = vec2(1e6f, 1e6f); + for (int x = -1; x <= 1; ++x) + { + for (int y = -1; y <= 1; ++y) + { + float dist = mx_worley_distance(localpos, x, y, X, Y, jitter, metric); + if (dist < sqdist.x) + { + sqdist.y = sqdist.x; + sqdist.x = dist; + } + else if (dist < sqdist.y) + { + sqdist.y = dist; + } + } + } + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; +} + +vec3 mx_worley_noise_vec3(vec2 p, float jitter, int metric) +{ + int X, Y; + vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); + vec3 sqdist = vec3(1e6f, 1e6f, 1e6f); + for (int x = -1; x <= 1; ++x) + { + for (int y = -1; y <= 1; ++y) + { + float dist = mx_worley_distance(localpos, x, y, X, Y, jitter, metric); + if (dist < sqdist.x) + { + sqdist.z = sqdist.y; + sqdist.y = sqdist.x; + sqdist.x = dist; + } + else if (dist < sqdist.y) + { + sqdist.z = sqdist.y; + sqdist.y = dist; + } + else if (dist < sqdist.z) + { + sqdist.z = dist; + } + } + } + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; +} + +float mx_worley_noise_float(vec3 p, float jitter, int metric) +{ + int X, Y, Z; + vec3 localpos = vec3(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y), mx_floorfrac(p.z, Z)); + float sqdist = 1e6f; + for (int x = -1; x <= 1; ++x) + { + for (int y = -1; y <= 1; ++y) + { + for (int z = -1; z <= 1; ++z) + { + float dist = mx_worley_distance(localpos, x, y, z, X, Y, Z, jitter, metric); + sqdist = min(sqdist, dist); + } + } + } + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; +} + +vec2 mx_worley_noise_vec2(vec3 p, float jitter, int metric) +{ + int X, Y, Z; + vec3 localpos = vec3(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y), mx_floorfrac(p.z, Z)); + vec2 sqdist = vec2(1e6f, 1e6f); + for (int x = -1; x <= 1; ++x) + { + for (int y = -1; y <= 1; ++y) + { + for (int z = -1; z <= 1; ++z) + { + float dist = mx_worley_distance(localpos, x, y, z, X, Y, Z, jitter, metric); + if (dist < sqdist.x) + { + sqdist.y = sqdist.x; + sqdist.x = dist; + } + else if (dist < sqdist.y) + { + sqdist.y = dist; + } + } + } + } + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; +} + +vec3 mx_worley_noise_vec3(vec3 p, float jitter, int metric) +{ + int X, Y, Z; + vec3 localpos = vec3(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y), mx_floorfrac(p.z, Z)); + vec3 sqdist = vec3(1e6f, 1e6f, 1e6f); + for (int x = -1; x <= 1; ++x) + { + for (int y = -1; y <= 1; ++y) + { + for (int z = -1; z <= 1; ++z) + { + float dist = mx_worley_distance(localpos, x, y, z, X, Y, Z, jitter, metric); + if (dist < sqdist.x) + { + sqdist.z = sqdist.y; + sqdist.y = sqdist.x; + sqdist.x = dist; + } + else if (dist < sqdist.y) + { + sqdist.z = sqdist.y; + sqdist.y = dist; + } + else if (dist < sqdist.z) + { + sqdist.z = dist; + } + } + } + } + if (metric == 0) + sqdist = sqrt(sqdist); + return sqdist; +}` ); + +const includes = [ mx_noise ]; + +export const mx_perlin_noise_float = fn( 'float mx_perlin_noise_float( vec3 p )', includes ); +export const mx_cell_noise_float = fn( 'float mx_cell_noise_float( vec3 p )', includes ); +export const mx_worley_noise_float = fn( 'float mx_worley_noise_float( vec3 p, float jitter, int metric )', includes ); +export const mx_fractal_noise_float = fn( 'float mx_fractal_noise_float( vec3 p, int octaves, float lacunarity, float diminish )', includes ); diff --git a/examples/jsm/nodes/parsers/GLSLNodeFunction.js b/examples/jsm/nodes/parsers/GLSLNodeFunction.js index 917b9609e9b8f4..a3b3901f2e2651 100644 --- a/examples/jsm/nodes/parsers/GLSLNodeFunction.js +++ b/examples/jsm/nodes/parsers/GLSLNodeFunction.js @@ -117,18 +117,33 @@ class GLSLNodeFunction extends NodeFunction { getCode( name = this.name ) { - const headerCode = this.headerCode; - const presicion = this.presicion; + let code; - let declarationCode = `${ this.type } ${ name } ( ${ this.inputsCode.trim() } )`; + const blockCode = this.blockCode; - if ( presicion !== '' ) { + if ( blockCode !== '' ) { - declarationCode = `${ presicion } ${ declarationCode }`; + const { type, inputsCode, headerCode, presicion } = this; + + let declarationCode = `${ type } ${ name } ( ${ inputsCode.trim() } )`; + + if ( presicion !== '' ) { + + declarationCode = `${ presicion } ${ declarationCode }`; + + } + + code = headerCode + declarationCode + blockCode; + + } else { + + // interface function + + code = ''; } - return headerCode + declarationCode + this.blockCode; + return code; } diff --git a/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js b/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js index 5d8191b2769b06..c7a3a5ca24a901 100644 --- a/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js +++ b/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js @@ -87,12 +87,12 @@ export const bmat4 = new ConvertType( 'bmat4' ); // @TODO: ArrayUniformNode -export const func = ( code ) => { +export const func = ( code, includes ) => { - const node = nodeObject( new FunctionNode( code ) ); + const node = nodeObject( new FunctionNode( code, includes ) ); const call = node.call.bind( node ); - node.call = ( params ) => nodeObject( call( params ) ); + node.call = ( ...params ) => nodeObject( call( params.length > 1 || params[ 0 ]?.isNode === true ? nodeArray( params ) : nodeObjects( params[ 0 ] ) ) ); return node; @@ -109,6 +109,8 @@ export const uniform = ( nodeOrType ) => { }; +export const fn = ( code, includes ) => func( code, includes ).call; + export const attribute = ( name, nodeType ) => nodeObject( new AttributeNode( name, nodeType ) ); export const property = ( name, nodeOrType ) => nodeObject( new PropertyNode( name, getConstNodeType( nodeOrType ) ) ); diff --git a/examples/screenshots/webgl_nodes_materialx_noise.jpg b/examples/screenshots/webgl_nodes_materialx_noise.jpg new file mode 100644 index 0000000000000000000000000000000000000000..99ee561e76257606660a833c044c0b46782750c5 GIT binary patch literal 37486 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

uPt4?Hod$W|nQ7A?n?8CN{;Jj2IQe*XY1f>>mH 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 literal 0 HcmV?d00001 diff --git a/examples/webgl_nodes_materialx_noise.html b/examples/webgl_nodes_materialx_noise.html new file mode 100644 index 00000000000000..c3729e36056808 --- /dev/null +++ b/examples/webgl_nodes_materialx_noise.html @@ -0,0 +1,214 @@ + + + + three.js webgl - materials - materialx nodes + + + + + +

+ three.js webgl - MaterialX - Noise +
+ + + + + + + + + +