mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-29 09:08:58 +00:00
Document skybox generation process
This commit is contained in:
@@ -0,0 +1 @@
|
||||
**/README.md
|
||||
@@ -0,0 +1,54 @@
|
||||
# Generating Skybox and Image-Based Lighting Assets
|
||||
|
||||
HDRI images like those available at [Poly Haven](https://polyhaven.com/hdris/skies)
|
||||
or [HDRI Haven](https://hdri-haven.com) must be preprocessed before they can be
|
||||
used for skyboxes and image-based lighting in Bevy Engine. Ultimately, three
|
||||
files are required:
|
||||
|
||||
* An `*.hdr` cubemap texture for the skybox
|
||||
* A `*.ktx2` prefiltered diffuse irradiance cubemap for IBL
|
||||
* A `*.ktx2` prefiltered specular cubemap for IBL
|
||||
|
||||
The HDR cubemap texture currently must be generated using Blender. A helper
|
||||
asset with instructions is available [here](https://drive.google.com/file/d/1UG_w9a6XfRmZtcfsSwFTDWPF7KnRa56I/view?usp=sharing)
|
||||
for Blender 3.6.
|
||||
|
||||
The prefiltered IBL cubemaps are generated with two CLI tools:
|
||||
|
||||
1. [glTF IBL Sampler](https://github.com/KhronosGroup/glTF-IBL-Sampler)
|
||||
* Git clone with `--recursive` and build using CMake
|
||||
* Diffuse:
|
||||
```sh
|
||||
./path/to/build-output/cli \
|
||||
-inputPath <input>.hdr \
|
||||
-outCubeMap <output>_diffuse.ktx2 \
|
||||
-distribution Lambertian \
|
||||
-cubeMapResolution 32
|
||||
```
|
||||
* Specular:
|
||||
```sh
|
||||
./path/to/build-output/cli \
|
||||
-inputPath <input>.hdr \
|
||||
-outCubeMap <output>_specular.ktx2 \
|
||||
-distribution GGX \
|
||||
-cubeMapResolution 512
|
||||
```
|
||||
|
||||
2. [bevy_mod_environment_map_tools](https://github.com/DGriffin91/bevy_mod_environment_map_tools)
|
||||
* You can run this directly with `cargo` on the files generated by the
|
||||
previous step:
|
||||
```sh
|
||||
cargo run --release -- \
|
||||
--inputs <input>_diffuse.ktx2,<input>_specular.ktx2 \
|
||||
--outputs <output>_diffuse.zstd.ktx2,<output>_specular.zstd.ktx2
|
||||
```
|
||||
|
||||
The Bevy app expects the filenames to exactly match the patterns given in the
|
||||
examples above:
|
||||
|
||||
* `{name}.hdr`
|
||||
* `{name}_diffuse.zstd.ktx2`
|
||||
* `{name}_specular.zstd.ktx2`
|
||||
|
||||
Once the files are in place, the application's `skybox` module can load all of
|
||||
the associated files for a skybox by passing its `name` to a helper function.
|
||||
@@ -1,4 +1,6 @@
|
||||
import { promises as fs } from "node:fs";
|
||||
import chalk from "chalk";
|
||||
import { glob } from "glob";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import url from "node:url";
|
||||
|
||||
@@ -7,7 +9,72 @@ async function main() {
|
||||
const assetsSrc = path.resolve(__dirname, "../assets");
|
||||
const assetsDest = path.resolve(__dirname, "../../dist/studio/public/assets");
|
||||
|
||||
await fs.cp(assetsSrc, assetsDest, { recursive: true });
|
||||
console.log(`\nCopying assets from ${assetsSrc} to ${assetsDest}`);
|
||||
|
||||
let count = 0;
|
||||
const start = performance.now();
|
||||
|
||||
try {
|
||||
/** @type string[] */
|
||||
let ignore = [];
|
||||
const cpIgnorePath = path.join(assetsSrc, ".cp-ignore");
|
||||
if (fs.existsSync(cpIgnorePath))
|
||||
ignore = (await fs.promises.readFile(cpIgnorePath, "utf-8"))
|
||||
.split(/\r?\n/)
|
||||
.map(line => `assets/${line}`);
|
||||
|
||||
const files = await glob("assets/**/*", {
|
||||
ignore,
|
||||
absolute: true,
|
||||
nodir: true,
|
||||
});
|
||||
count = files.length;
|
||||
|
||||
await Promise.all(files.map(async src => {
|
||||
const relSrc = path.relative(assetsSrc, src);
|
||||
const dest = path.resolve(assetsDest, relSrc);
|
||||
const destDir = path.dirname(dest);
|
||||
|
||||
if (!fs.existsSync(destDir))
|
||||
await fs.promises.mkdir(destDir, { recursive: true });
|
||||
|
||||
await fs.promises.copyFile(src, dest);
|
||||
}));
|
||||
}
|
||||
catch (err) {
|
||||
handleError(err);
|
||||
}
|
||||
|
||||
const elapsed = Math.round(performance.now() - start);
|
||||
const label = chalk.bold.greenBright.inverse(" DONE ");
|
||||
console.log(`${label} Copied ${count} files in ${elapsed} ms`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} err
|
||||
* @returns {never}
|
||||
*/
|
||||
function handleError(err) {
|
||||
const label = chalk.bold.redBright.inverse(" ERROR ");
|
||||
switch (typeof err) {
|
||||
case "number":
|
||||
console.log(`${label} Process exited with code ${err}`);
|
||||
process.exit(err);
|
||||
|
||||
case "string":
|
||||
console.log(`${label} ${err}`);
|
||||
process.exit(1);
|
||||
|
||||
case "object":
|
||||
console.log(`${label} ${err.message}`);
|
||||
if (err.stack && err.stack !== err.message)
|
||||
console.log(chalk.dim(err.stack));
|
||||
process.exit(1);
|
||||
|
||||
default:
|
||||
console.log(`${label} An unknown error occured`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
Reference in New Issue
Block a user