diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..0d01faca3 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,28 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "msedge", + "request": "launch", + "name": "Documentation (Edge)", + "url": "http://localhost:3000", + "preLaunchTask": "Dev" + }, + { + "type": "chrome", + "request": "launch", + "name": "Documentation (Chrome)", + "url": "http://localhost:3000", + "preLaunchTask": "Dev" + } + ], + "compounds": [ + { + "name": "Default Launch", + "configurations": ["Documentation (Edge)"] + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..92f1266f1 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,31 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Dev", + "type": "shell", + "command": "npm run dev", + "isBackground": true, + "runOptions": { + "instanceLimit": 1 + }, + "presentation": { + "group": "watch" + }, + "problemMatcher": { + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": ".*Starting...", + }, + "endsPattern": { + "regexp": ".*Ready in .*ms", + }, + }, + }, + }, + ], +} \ No newline at end of file diff --git a/configuration/structure.json b/configuration/structure.json index 50bbbf5b4..47f56aa91 100644 --- a/configuration/structure.json +++ b/configuration/structure.json @@ -649,6 +649,11 @@ "friendlyName": "Progressively Load .glTF Files", "children": {}, "content": "features/featuresDeepDive/importers/glTF/progressiveglTFLoad" + }, + "createExtensions": { + "friendlyName": "Create Your Own glTF Extensions", + "children": {}, + "content": "features/featuresDeepDive/importers/glTF/createExtensions" } }, "content": "features/featuresDeepDive/importers/glTF" diff --git a/content/features/featuresDeepDive/importers.md b/content/features/featuresDeepDive/importers.md index 0ff585bbd..807f500af 100644 --- a/content/features/featuresDeepDive/importers.md +++ b/content/features/featuresDeepDive/importers.md @@ -12,8 +12,8 @@ video-content: The built in file type is `.babylon` and Babylon.js can load these without a plugin. All other file types require a plugin as described in this section. -Possible file types are gLTF, obj, stl +Possible file types are glTF, splat, obj, stl. To help you with imported assets there is a manager for them. -**Note:** Since meshes you import can have a _rotationQuaternion_ set before applying a rotation set the _rotationQuaternion_ to _null \ No newline at end of file +**Note:** Since meshes you import can have a `rotationQuaternion` set before applying a rotation set the `rotationQuaternion` to `null`. \ No newline at end of file diff --git a/content/features/featuresDeepDive/importers/assetContainers.md b/content/features/featuresDeepDive/importers/assetContainers.md index 64af3e441..b2a583c3d 100644 --- a/content/features/featuresDeepDive/importers/assetContainers.md +++ b/content/features/featuresDeepDive/importers/assetContainers.md @@ -34,7 +34,7 @@ container.removeAllFromScene(); -This can be used to add/remove all objects in a scene without the need to exit WebVR. +This can be used to add/remove all objects in a scene without the need to exit WebVR. When creating assets manually the moveAllFromScene method can be used to move all assets currently in a scene into an AssetContainer and remove them from the scene for later use. @@ -62,7 +62,7 @@ The return entries object will contain: - skeletons: A list of all the skeletons created by the duplication process - animationGroups: A list of all the animation groups created by the duplication process - + You can also set two parameters to the call to `instantiateModelsToScene`: diff --git a/content/features/featuresDeepDive/importers/createImporters.md b/content/features/featuresDeepDive/importers/createImporters.md index 351c05c79..1c60da653 100644 --- a/content/features/featuresDeepDive/importers/createImporters.md +++ b/content/features/featuresDeepDive/importers/createImporters.md @@ -12,37 +12,97 @@ video-content: By default, babylon.js comes with an importer for .babylon files. -You can also create your own importer by providing a specific object to the `BABYLON.SceneLoader.RegisterPlugin` function. +You can also create your own importer by providing a specific object to the `BABYLON.registerSceneLoaderPlugin` function. -This object must have three properties: +### Plugins -- A list of supported file extensions (`extensions`) -- An `importMesh` function to import specific meshes -- A `load` function to import complete scene -- A `loadAssets` function to import all babylon elements from the file but do not add them to the scene +A file importer should implement the `ISceneLoaderPluginAsync` interface. -Here is a sample importer object: + -```javascript -BABYLON.SceneLoader.RegisterPlugin({ - extensions: ".babylon", - importMesh: function (meshesNames, scene, data, rootUrl, meshes, particleSystems, skeletons) { - return true; - }, - load: function (scene, data, rootUrl) { - return true; +An abbreviated example of a file importer (scene loader plugin) might look something like this: + +```typescript +import { ISceneLoaderPluginAsync } from "@babylonjs/core/Loading/sceneLoader"; + +class MyCustomImporter implements ISceneLoaderPluginAsync { + public readonly name = "myCustomImporter"; + + public readonly extensions = ".myCustomExtension"; + + public async importMeshAsync(...) { + // Load specified meshes into the Scene + } + + public async loadAsync(...) { + // Load all data into the Scene + } + + public async loadAssetContainerAsync(...) { + // Load all data into an AssetContainer + } +} +``` + +A plugin instance can be passed to `BABYLON.registerSceneLoaderPlugin`, but plugin factories are more flexible and full featured, so we recommend using them to create your custom importers. + +### Plugin Factories + +When you register a plugin, you can register a plugin factory rather than a plugin instance. The factory must have a `createPlugin` function that returns an instance of the plugin. The factory is invoked each time a load function is called. The `pluginOptions` property of the loader options are also passed to the factory function. This makes it possible for your file importer to be configurable through options. + +A plugin factory can return the plugin instance either synchronously or asynchronously. We recommend you dynamically import your plugin to avoid loading it until it is needed. For example: + +```typescript +import { registerSceneLoaderPlugin } from "@babylonjs/core/Loading/sceneLoader"; + +registerSceneLoaderPlugin({ + name: "myCustomImporter", + extensions: ".myCustomExtension", + createPlugin: async () => { + const { MyCustomImporter } = await import("./MyCustomImporter"); + return new MyCustomImporter(); }, - loadAssets(scene, data, rootUrl) { - const container = new AssetContainer(scene); - return container; +}); +``` + +To expose options for your custom file importer, you should first augment the `SceneLoaderPluginOptions` interface to add options for your importer. For example, if the name of your importer is `myCustomImporter`, you would add the following to your code: + +```typescript +type MyCustomImporterOptions = { option1?: string, option2?: number }; + +declare module "@babylonjs/core" { + export interface SceneLoaderPluginOptions { + myCustomImporter: MyCustomImporterOptions; + } +} +``` + +Then, when you register your file importer, you can access the options like this: + +```typescript +import { registerSceneLoaderPlugin } from "@babylonjs/core/Loading/sceneLoader"; + +registerSceneLoaderPlugin({ + name: "myCustomImporter", + extensions: ".myCustomExtension", + createPlugin: async (options) => { + const { MyCustomImporter } = await import("./MyCustomImporter"); + return new MyCustomImporter(options["myCustomImporter"]); }, }); ``` -- `meshesNames` is the names of meshes to import -- `scene` is the scene to load data into -- `data` is the string representation of the file to load -- `rootUrl` defines the root URL of your assets -- `meshes` is the list of imported meshes -- `particleSystems` is the list of imported particle systems -- `skeletons` is the list of imported skeletons +Note that in this example, you will need to modify the `MyCustomImporter` class to accept options (`MyCustomImporterOptions`) in its constructor. + +Finally, these options can be passed into one of the scene loader functions like this: + +```typescript +await loadAssetContainerAsync("path/to/model", scene, { + pluginOptions: { + myCustomImporter: { + option1: "hello world", + option2: 42, + }, + }, +}); +``` diff --git a/content/features/featuresDeepDive/importers/glTF.md b/content/features/featuresDeepDive/importers/glTF.md index 315ce2c14..028697da3 100644 --- a/content/features/featuresDeepDive/importers/glTF.md +++ b/content/features/featuresDeepDive/importers/glTF.md @@ -69,12 +69,24 @@ This loader supports only glTF 1.0 and will fail to load glTF 2.0. ``` +## NPM + +When using the Babylon npm packages in your own build, it is preferable to register the glTF file importer via the top level dynamic loader registration function `registerBuiltInLoaders`. See [Loading Any File Type](/features/featuresDeepDive/importers/loadingFileTypes#npm) for more information. + +If you want to import the glTF file importer statically (not recommended), you can do so via: + +```javascript +import "@babylonjs/loaders/glTF/2.0"; +``` + +You can read more about [NPM support](/setup/frameworkPackages/npmSupport) + ## Loading the Scene -Use one of the static function on the `SceneLoader` to load a glTF asset. +Use one of scene loader functions to load a glTF asset. See [Load from any file type](/features/featuresDeepDive/importers/loadingFileTypes). -See an example here: +See an example here: ## API (Version 2) @@ -84,7 +96,9 @@ See the available [properties and methods](/typedoc/classes/babylon.gltffileload ## Extensions -See the available [extensions](/typedoc/modules/babylon.gltf2.loader.extensions) from the API documentation. +See the built in [extensions](/typedoc/modules/babylon.gltf2.loader.extensions) from the API documentation. + +You can also [create your own extensions](/features/featuresDeepDive/importers/glTF/createExtensions). ## API (Version 1) @@ -105,8 +119,3 @@ Set this property to true in order to work with homogeneous coordinates, availab ```javascript BABYLON.GLTFFileLoader.HomogeneousCoordinates = true; ``` - -## Extensions - -[KHR_binary_glTF](https://github.com/KhronosGroup/glTF/tree/master/extensions/1.0/Khronos/KHR_binary_glTF) -[KHR_materials_common](https://github.com/KhronosGroup/glTF/tree/master/extensions/1.0/Khronos/KHR_materials_common) diff --git a/content/features/featuresDeepDive/importers/glTF/createExtensions.md b/content/features/featuresDeepDive/importers/glTF/createExtensions.md new file mode 100644 index 000000000..0db9c8a17 --- /dev/null +++ b/content/features/featuresDeepDive/importers/glTF/createExtensions.md @@ -0,0 +1,90 @@ +--- +title: Create glTF extensions +image: +description: Learn about creating new glTF loader extensions. +keywords: diving deeper, import, importing assets, asset, glTF, extensions +further-reading: ["https://babylonjs.medium.com/extending-the-gltf-loader-in-babylon-js-588e48fb692b"] +video-overview: +video-content: +--- + +## Introduction + +The glTF format includes the concept of extensions. Usually glTF loader extensions map 1:1 with a corresponding glTF format extensions. However, it is possible to create custom glTF loader extensions are unrelated to glTF format extensions and simply perform some additional processing on the loaded glTF data. + +The glTF loader includes support for many glTF format extensions through built-in glTF loader extensions. It is also possible to create your own glTF loader extensions. + +## Extensions + +Extensions are defined by implementing the `IGLTFLoaderExtension` interface (from `@babylonjs/loaders/glTF/2.0`). An abbreviated example would look something like this: + +```typescript +import { IGLTFLoaderExtension } from "@babylonjs/loaders/glTF/2.0"; + +class MyCustomExtension implements IGLTFLoaderExtension { + public readonly name = "myCustomExtension"; + public enabled = true; + public order = 100; + + // Implement any of the optional functions, such as: + public loadSceneAsync(): Nullable> { + // Modify the default behavior when loading scenes. + } +} +``` + +## Extension Factories + +When you register a loader extension, you register an extension factory. The factory is a function that takes the glTF loader and returns an extension instance synchronously or asynchronously. This allows you to dynamically import your extension to avoid loading it until it is needed. A simple example might look something like this: + +```typescript +import { registerGLTFExtension } from "@babylonjs/loaders/glTF/2.0"; + +registerGLTFExtension("myCustomExtension", true, async (loader) => { + const { MyCustomExtension } = await import("./MyCustomExtension"); + return new MyCustomExtension(loader); +}); +``` + + + +## Extension Options + +To expose options for your custom glTF loader extension, you should first augment the `GLTFLoaderExtensionOptions` interface to add options for your extension. For example: + +```typescript +type MyCustomExtensionOptions = { option1?: string, option2?: number }; + +declare module "@babylonjs/loaders" { + export interface GLTFLoaderExtensionOptions { + myCustomExtension: MyCustomImporterOptions; + } +} +``` + +Then, when you register your extension, you can access the options like this: + +```typescript +class MyCustomExtension implements IGLTFLoaderExtension { + constructor (loader: GLTFLoader) { + const options = loader.parent.extensionOptions["myCustomExtension"]; + } +} +``` + +Finally, these options can be passed into one of the scene loader functions like this: + +```typescript +await loadAssetContainerAsync("path/to/model", scene, { + pluginOptions: { + glTF: { + extensionOptions: { + myCustomExtension: { + option1: "hello world", + option2: 42, + }, + }, + }, + }, +}); +``` diff --git a/content/features/featuresDeepDive/importers/incrementalLoading.md b/content/features/featuresDeepDive/importers/incrementalLoading.md index 7e08985f1..78db5701c 100644 --- a/content/features/featuresDeepDive/importers/incrementalLoading.md +++ b/content/features/featuresDeepDive/importers/incrementalLoading.md @@ -16,7 +16,7 @@ These files can be used just like a standard _.babylon_ scene except that they w You have to put the _.babylonmeshdata_ and _.babylongeometrydata_ files in the same folder as the _.incremental.babylon_ file. -You can find a demo of an incremental scene here: +You can find a demo of an incremental scene here: ## Detailed Step by step @@ -45,9 +45,8 @@ For users less experienced with command line tools, here's a more detailed step 8. And you're ready to load it into Babylon! ```javascript -BABYLON.SceneLoader.Append("src/", "my-scene.incremental.babylon", scene, function () { - console.log("My incremental file was loaded! WOHOO!"); -}); +await BABYLON.appendSceneAsync("src/my-scene.incremental.babylon", scene); +console.log("My incremental file was loaded! WOHOO!"); ``` ## Node.js based incremental file converter diff --git a/content/features/featuresDeepDive/importers/loadFromMemory.md b/content/features/featuresDeepDive/importers/loadFromMemory.md index 8715a495b..ab1d24a74 100644 --- a/content/features/featuresDeepDive/importers/loadFromMemory.md +++ b/content/features/featuresDeepDive/importers/loadFromMemory.md @@ -39,7 +39,9 @@ const assetUrl = URL.createObjectURL(assetBlob); Then finally we load the asset into the scene from the url which points to the memory blob. ```javascript -await BABYLON.SceneLoader.AppendAsync(assetUrl, undefined, scene, undefined, ".glb"); +await BABYLON.appendSceneAsync(assetUrl, scene, { + pluginExtension: ".glb" +}); ``` -It's important to note that the Babylon scene loader will use the correct loader based on the file extension of the asset you're trying to load. In this case, since we're loading binary data saved to memory, the scene loader needs to be explicitly told which loader to use. This is why the final argument in the AppendAsync method is ".glb". +It's important to note that the Babylon scene loader will use the correct loader based on the file extension of the asset you're trying to load. In this case, since we're loading binary data saved to memory, the scene loader needs to be explicitly told which loader to use. This is why the final argument in the `appendSceneAsync` method (the options object) specifies the `pluginExtension` as `".glb"`. diff --git a/content/features/featuresDeepDive/importers/loadingFileTypes.md b/content/features/featuresDeepDive/importers/loadingFileTypes.md index 5c9e5a323..36a1711c7 100644 --- a/content/features/featuresDeepDive/importers/loadingFileTypes.md +++ b/content/features/featuresDeepDive/importers/loadingFileTypes.md @@ -37,28 +37,58 @@ Currently plugins can be found for: - [.stl](/features/featuresDeepDive/importers/stl) - .splat +You can also create your own [custom importer](/features/featuresDeepDive/importers/createImporters) for additional file types. + +#### CDN + To quickly add support for all loaders the following script can be added to your page: -#### Production links +##### Production links ```html ``` -#### Preview links (useful to test for changes to loaders) +##### Preview links (useful to test for changes to loaders) ```html ``` -For NPM usage see: https://www.npmjs.com/package/@babylonjs/loaders - Once the plugin is referenced, scene loader functions can be used to load model files. +#### NPM + +When you have a built/bundled app, you can use [@babylonjs/loaders](https://www.npmjs.com/package/@babylonjs/loaders). + +The preferred way to bring in the loaders is via: + +```typescript +import { registerBuiltInLoaders } from "@babylonjs/loaders/dynamic"; +... +registerBuiltInLoaders(); +``` + +This will register all supported loaders, but internally uses dynamic imports to only download/load a specific importer (e.g. glTF, splat, etc.) when a model of that type is first loaded. + + + +You can also register all loaders statically (e.g. they will all be included in your primary bundle). This is not recommended, but can be done via: + + + +```typescript +import "@babylonjs/loaders"; +``` + +If you are using the UMD package, dynamic loading is not supported. Instead, you should use the static import approach. See [`babylonjs-loaders`](https://www.npmjs.com/package/babylonjs-loaders). + +Importers must be registered with one of these approaches before the scene loader functions can be used. + ## loadAssetContainerAsync Loads all babylon assets from the file and does not append them to the scene. Instead, they are returned in an `AssetContainer` object. @@ -156,7 +186,7 @@ const assetContainer = await BABYLON.loadAssetContainerAsync("https://raw.github MSFT_lod: { maxLODsToLoad: 1, }, - } + }, }, }, }); diff --git a/content/features/featuresDeepDive/importers/oBJ.md b/content/features/featuresDeepDive/importers/oBJ.md index 5b8c28c40..f320df37b 100644 --- a/content/features/featuresDeepDive/importers/oBJ.md +++ b/content/features/featuresDeepDive/importers/oBJ.md @@ -21,13 +21,9 @@ You can find it [here](https://cdn.babylonjs.com/loaders/babylon.objFileLoader.j -If you are using UMD imports via NPM, you need to reference with side-effects: +When using the Babylon npm packages in your own build, it is preferable to register the OBJ file importer via the top level dynamic loader registration function `registerBuiltInLoaders`. See [Loading Any File Type](/features/featuresDeepDive/importers/loadingFileTypes#npm) for more information. -```javascript -import "babylonjs-loaders"; -``` - -If you wish to benefit from the tree-shakeable ES6 package, you need to reference: +If you want to import the OBJ file importer statically (not recommended), you can do so via: ```javascript import "@babylonjs/loaders/OBJ/objFileLoader"; diff --git a/content/features/featuresDeepDive/importers/stl.md b/content/features/featuresDeepDive/importers/stl.md index 19716db0b..36dbf2499 100644 --- a/content/features/featuresDeepDive/importers/stl.md +++ b/content/features/featuresDeepDive/importers/stl.md @@ -21,8 +21,15 @@ To use it you just have to reference it after Babylon.js: ``` -Then you can use one of the static functions on the `SceneLoader` to load. -See [how to load from any file type](/features/featuresDeepDive/importers/loadingFileTypes) +When using the Babylon npm packages in your own build, it is preferable to register the STL file importer via the top level dynamic loader registration function `registerBuiltInLoaders`. See [Loading Any File Type](/features/featuresDeepDive/importers/loadingFileTypes#npm) for more information. + +If you want to import the STL file importer statically (not recommended), you can do so via: + +```javascript +import "@babylonjs/loaders/STL/stlFileLoader"; +``` + +You can read more about [NPM support](/setup/frameworkPackages/npmSupport) By default, the STL loader swaps the Y and Z axes. To disable this behavior, set diff --git a/content/features/featuresDeepDive/lights/rsmgi.md b/content/features/featuresDeepDive/lights/rsmgi.md index 174d1ada0..d6cb2a1dc 100644 --- a/content/features/featuresDeepDive/lights/rsmgi.md +++ b/content/features/featuresDeepDive/lights/rsmgi.md @@ -33,7 +33,7 @@ One of the main uses we foresee is in the context of e-commerce, to better ancho The differences are subtle, just look at the shoe support and the backdrop (click to enlarge). - + ## Reflective Shadow Maps (RSM) @@ -147,7 +147,7 @@ The output in this mode can be quite different from when it is deactivated, beca PG for first screenshot: -
PG for second screenshot: +
PG for second screenshot: In the second screenshot, the size of the RSM texture is `29x26`, so the number of samples is `29x26=754`. Due to the low resolution of the RSM texture, moving objects can exhibit a "wobbling" effect in the lighting. This can be improved by increasing the texture resolution, but you'll quickly be limited by GPU power... @@ -166,7 +166,7 @@ These are the main properties to set when configuring a GI RSM. `GIRSM.intensity |-|-| |![radius=0.2](/img/features/rsmgi/radius0_2.jpg!498)|![radius=0.6](/img/features/rsmgi/radius0_6.jpg!498)| - + The intensity and number of samples are the same in both cases. As you can see, because we've increased the radius, the lighting is more evenly distributed across the scene (you can barely see the red light bleeding from the wall towards the pillar). We'd have to increase the number of samples to regain some brightness, which would not come without a certain cost in terms of performance. @@ -182,7 +182,7 @@ For example, here are two images using the `useFullTexture=true` mode, with diff |-|-| |![edgeArtifact=0.004](/img/features/rsmgi/edgeArtifact0_004.jpg!497)|![edgeArtifact=0.42](/img/features/rsmgi/edgeArtifact0_42.jpg!497)| - + As you can see, the artifacts are reduced in the second screenshot (but not completely removed). @@ -194,7 +194,7 @@ As we only take a limited number of samples to calculate the GI contribution for |-|-| |![Banding final picture](/img/features/rsmgi/banding_full.jpg!496)|![Banding GI only](/img/features/rsmgi/banding_gi.jpg!496)| - + You can trade banding for noise by setting `GIRSM.rotateSample = true`. You'll also need to set `GIRSM.noiseFactor` to a value large enough to make the noise pattern small enough: @@ -202,7 +202,7 @@ You can trade banding for noise by setting `GIRSM.rotateSample = true`. You'll a |-|-| |![Noise factor too small](/img/features/rsmgi/noise_too_small.jpg!497)|![Noise factor ok](/img/features/rsmgi/noise_ok.jpg!497)| - + The right value depends on the scale of your scene. Noise is less objectionable than banding and can be more easily treated by a blur pass (see below) than banding. @@ -246,7 +246,7 @@ Values depend on the scale of your scene. For example: |-|-| |![No blur](/img/features/rsmgi/shoe_normal_35.jpg!500)|![Simple blur](/img/features/rsmgi/shoe_normal_80.jpg!500)| - + As you can see, values between 0.15 and 0.35 are correct, but 0.01 is too low (not enough pixels are blurred), and 0.8 is too high (too many pixels are blurred). @@ -258,7 +258,7 @@ As you can see, values between 0.15 and 0.35 are correct, but 0.01 is too low (n |-|-| |![No blur](/img/features/rsmgi/cornell_blurkernel_4.jpg!500)|![Simple blur](/img/features/rsmgi/cornell_blurkernel_14.jpg!500)| - + Try to use the lowest possible value, as higher values mean greater consumption of GPU resources. @@ -311,7 +311,7 @@ For example, here are two screenshots taken with an RSM texture the size of the |![RSM full](/img/features/rsmgi/rsm_size_big.jpg!500)|![RSM small](/img/features/rsmgi/rsm_size_small.jpg!500)| |![RSM full](/img/features/rsmgi/rsm_size_big2.jpg!500)|![RSM small](/img/features/rsmgi/rsm_size_small2.jpg!500)| - + The difference is barely noticeable in the screenshots. However, be aware that you will see differences if certain objects or light move around in the scene (lighting will flicker if the texture size is too small)! So, once again, use the right dimensions to suit your needs. diff --git a/content/features/featuresDeepDive/materials/node_material/availableNodeBlocks.md b/content/features/featuresDeepDive/materials/node_material/availableNodeBlocks.md index cd8a8ca18..64f94e57c 100644 --- a/content/features/featuresDeepDive/materials/node_material/availableNodeBlocks.md +++ b/content/features/featuresDeepDive/materials/node_material/availableNodeBlocks.md @@ -400,6 +400,38 @@ if input a and input b are both a value of zero the block evaluates false, other - true: Float, Vector2, Vector3, Vector4, Color3, Color4 - Outputs: - output: Float +## Loop + +### Loop + +This creates a root node that will loop through a number of iterations (that value can be set through node.iterations or through the iterations input). + + + - Inputs: + - input: Defines the initial value of the stored data + + - iterations: How many iterations for that loop (this will superseed the value defined on node.iterations) + - Outputs + - output: The aggregated value computed by the loop + - index: That value will be changed on each loop to indicate the current index + - loopID: Use this special port to connect StorageRead and StorageWrite entries to this loop + +### StorageRead + +Used to read the current value of the loop stored data. + + - Inputs: + - loopID: Establish the connection with the parent loop + - Outputs + - value: The current value of the loop stored data + +### StorageWrite + +Used to write the new value of the loop stored data. + + - Inputs: + - loopID: Establish the connection with the parent loop + - value: The new value of the loop stored data ## Math: Standard ### Add diff --git a/content/features/featuresDeepDive/materials/node_material/nodeMaterial.md b/content/features/featuresDeepDive/materials/node_material/nodeMaterial.md index af80e307a..82cb1f456 100644 --- a/content/features/featuresDeepDive/materials/node_material/nodeMaterial.md +++ b/content/features/featuresDeepDive/materials/node_material/nodeMaterial.md @@ -327,12 +327,13 @@ You can also use a standalone version of the editor here: @@ -426,6 +427,22 @@ If `ALPHATEST = 0`, the computed value is `-1 + alphaCutOff`. As `alphaCutOff` i You could also have used `Lerp(0, alphaCutOff, ALPHATEST)` as the input for `Discard.cutoff`, but it's likely that the addition + subtraction used above is faster than a `Lerp` on GPUs (would need some benchmarking to be sure), even if it's by a small (negligible) margin. +#### Using loops + +Loops are always a complicated discussion within a shader as they can easily kill the performance. With Babylon v8.0, we are introducing a new node named `Loop`. The goal of that node is to allow the user to aggregate a value through multiple iterations (think, for instance, about a texture blur). + +To do so you will need to drop a `Loop" block and connect an input value. That value will be the initial value of the aggregated variable. Once computed that value will be available on the output port of the block. + +To aggregate the value you will need to wire a `StorageRead` and `StorageWrite` block to the loopID port of the `Loop` block. These 2 blocks will give you access to the running value of the aggregated variable. + +Here is an example where the loop is simply adding a shade of red to a black color 10 times: + + +Of course you can do far more like a texture blur: + + +Please keep in mind that loops have to be used cautiously as they can add a lot of computations to your shaders. + #### Shader Promotion Optimization The Node Material features a default performance optimization where some nodes in the fragment shader are "promoted" to being evaluated with the vertex shader. This optimization ensures the fastest possible shader evaluation possible. There may be times where this optimization does NOT provide the desired outcome when creating a Node Material. You can change this behavior by setting the "Target" property of any given node, to either "Fragment" or "Vertex." By default, this property is set to "Neutral" which will permit the optimization to occur. Setting this property to "Fragment" or "Vertex" will force the system to evaluate that node in that specific part of the shader. diff --git a/content/features/featuresDeepDive/materials/shaders/introToShaders.md b/content/features/featuresDeepDive/materials/shaders/introToShaders.md index cadaaeb3d..5c0fa3e81 100644 --- a/content/features/featuresDeepDive/materials/shaders/introToShaders.md +++ b/content/features/featuresDeepDive/materials/shaders/introToShaders.md @@ -167,7 +167,6 @@ Here are four ways of putting shader code into your scene: 1. Use [BabylonJS Create Your Own Shader (CYOS)](https://www.babylonjs.com/cyos/) and download a zip file 2. Write the Vertex and Fragment Shader Code into `