Skip to content

Commit

Permalink
documentation + formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
BarthPaleologue committed Nov 12, 2023
1 parent 59ab43e commit 176dfe3
Show file tree
Hide file tree
Showing 22 changed files with 377 additions and 285 deletions.
25 changes: 19 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,48 @@ A procedural asset scattering system built with BabylonJS. It can render million

## Online demo

Main demo with procedural terrain, lod, collisions, butterflies: [here](https://barthpaleologue.github.io/AssetScattering/)
Main demo with procedural terrain, lod, collisions, butterflies and trees: [here](https://barthpaleologue.github.io/AssetScattering/)

Large flat grass field with lod and butterflied: [here](https://barthpaleologue.github.io/AssetScattering/field.html)
Large flat grass field with lod and butterflies: [here](https://barthpaleologue.github.io/AssetScattering/field.html)

Minimal example of a dense patch of grass: [here](https://barthpaleologue.github.io/AssetScattering/minimal.html)

If you can't run the demo, there is a video on YouTube [here](https://www.youtube.com/watch?v=0I5Kd784K6A).

## How to use

The files for the asset scattering are located in the folder `src/ts/instancing`.

There are 2 types of patches: `InstancePatch` and `ThinInstancePatch`. They both use the same interface `IPatch` so they are interchangeable.
There are 3 types of patches: `InstancePatch`, `ThinInstancePatch` and `HierarchyInstancePatch`. They all use the same interface `IPatch` so they are interchangeable.

The first thing to do is to create a matrix buffer for the patch which will contain all positions, scaling and rotations of the instances. There is a utility function in each patch class to create a simple square patch (look at `src/ts/minimal.ts` to see it in action).

When the buffer is created, it is just a matter of passing it to the patch constructor and then calling `createInstances()` on the instance with the mesh you want to instantiate.

### Which one to use?
### Which patch to use?

If you want raw speed, prefer `ThinInstancePatch`. It is faster because it uses a single draw call for all the instances. However, you give up the ability to have collisions on your instances.

On the other hand, `InstancePatch` is slower, but you get a list of `InstancedMesh` that you can tweak individually. This is useful if you want to have collisions on your instances.

`HierarchyInstancePatch` is a special case of `InstancePatch` that allows to instantiate hierarchies of meshes. It can be useful for complex objects or GLTF models.

### Manage LoD

You can have LoD with your patches by using a `PatchManager`. Simply add your patches to the manager, and the different LoD levels of your base mesh and the manager will take care of the rest as long as you call its `update` method.
This is completely optional, but you can have LoD with your patches by using a `PatchManager`.

The Manager takes an array of meshes describing the different LoD levels of your base mesh and a function that defines the LoD as a function of the distance to the camera.
Then, you simply add your patches to the manager and call `update()` on the manager every frame. The manager will take care of updating the LoD of your patches.

This may result in frame drops if your patches have a lot of thin instances, this might be solved in the future but this is not a priority for now.

### Collisions

To enable collisions on an `InstancePatch`, simply set `checkCollisions=true` on your base mesh. Then moving objects with `moveWithCollisions` will respect the collisions.

## Resources

The grass rendering is based on [this video]() from Simon Dev. It is itself based on [this gdc conference]().
The grass rendering is based on [this video](https://www.youtube.com/watch?v=bp7REZBV4P4) from Simon Dev. It is itself based on [this gdc conference](https://www.youtube.com/watch?v=Ibe1JBF5i5Y).

The wind sound effect is from [this video](https://www.youtube.com/watch?v=a3aFMAalCpk).

Expand Down
8 changes: 4 additions & 4 deletions src/ts/butterfly/butterfly.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {VertexData} from "@babylonjs/core/Meshes/mesh.vertexData";
import {Mesh} from "@babylonjs/core/Meshes/mesh";
import {Scene} from "@babylonjs/core/scene";
import { VertexData } from "@babylonjs/core/Meshes/mesh.vertexData";
import { Mesh } from "@babylonjs/core/Meshes/mesh";
import { Scene } from "@babylonjs/core/scene";

export function createButterfly(scene: Scene) {
const positions = new Float32Array(6 * 3);
Expand Down Expand Up @@ -86,4 +86,4 @@ export function createButterfly(scene: Scene) {
mesh.bakeCurrentTransformIntoVertices();

return mesh;
}
}
16 changes: 8 additions & 8 deletions src/ts/butterfly/butterflyMaterial.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {Scene} from "@babylonjs/core/scene";
import {Effect} from "@babylonjs/core/Materials/effect";
import {ShaderMaterial} from "@babylonjs/core/Materials/shaderMaterial";
import { Scene } from "@babylonjs/core/scene";
import { Effect } from "@babylonjs/core/Materials/effect";
import { ShaderMaterial } from "@babylonjs/core/Materials/shaderMaterial";
import butterflyFragment from "../../shaders/butterflyFragment.glsl";
import butterflyVertex from "../../shaders/butterflyVertex.glsl";

import butterflyTexture from "../../assets/butterfly.png";
import {Texture} from "@babylonjs/core/Materials/Textures/texture";
import {TransformNode} from "@babylonjs/core/Meshes/transformNode";
import {DirectionalLight} from "@babylonjs/core/Lights/directionalLight";
import { Texture } from "@babylonjs/core/Materials/Textures/texture";
import { TransformNode } from "@babylonjs/core/Meshes/transformNode";
import { DirectionalLight } from "@babylonjs/core/Lights/directionalLight";

export function createButterflyMaterial(player: TransformNode, light: DirectionalLight, scene: Scene) {
const shaderName = "butterflyMaterial";
Expand All @@ -18,7 +18,7 @@ export function createButterflyMaterial(player: TransformNode, light: Directiona
attributes: ["position", "normal", "uv"],
uniforms: ["world", "worldView", "worldViewProjection", "view", "projection", "viewProjection", "time", "lightDirection", "playerPosition"],
defines: ["#define INSTANCES"],
samplers: ["butterflyTexture"],
samplers: ["butterflyTexture"]
});

butterflyMaterial.setTexture("butterflyTexture", new Texture(butterflyTexture, scene));
Expand All @@ -33,4 +33,4 @@ export function createButterflyMaterial(player: TransformNode, light: Directiona
});

return butterflyMaterial;
}
}
74 changes: 33 additions & 41 deletions src/ts/flatfield.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,38 @@
import {Scene} from "@babylonjs/core/scene";
import {Vector3} from "@babylonjs/core/Maths/math.vector";
import { Scene } from "@babylonjs/core/scene";
import { Vector3 } from "@babylonjs/core/Maths/math.vector";
import "@babylonjs/core/Loading/loadingScreen";

import {ActionManager, ExecuteCodeAction} from "@babylonjs/core/Actions";

import {Tools} from "@babylonjs/core/Misc/tools";
import {DirectionalLight} from "@babylonjs/core/Lights/directionalLight";
import {HemisphericLight} from "@babylonjs/core/Lights/hemisphericLight";
import { Tools } from "@babylonjs/core/Misc/tools";
import { DirectionalLight } from "@babylonjs/core/Lights/directionalLight";
import { HemisphericLight } from "@babylonjs/core/Lights/hemisphericLight";

import "../styles/index.scss";
import {createGrassBlade} from "./grass/grassBlade";
import {createGrassMaterial} from "./grass/grassMaterial";
import { createGrassBlade } from "./grass/grassBlade";
import { createGrassMaterial } from "./grass/grassMaterial";

import {createSkybox} from "./utils/skybox";
import {UI} from "./utils/ui";
import {createCharacterController} from "./utils/character";
import {ThinInstancePatch} from "./instancing/thinInstancePatch";
import {ArcRotateCamera} from "@babylonjs/core/Cameras/arcRotateCamera";
import { createSkybox } from "./utils/skybox";
import { UI } from "./utils/ui";
import { createCharacterController } from "./utils/character";
import { ThinInstancePatch } from "./instancing/thinInstancePatch";
import { ArcRotateCamera } from "@babylonjs/core/Cameras/arcRotateCamera";

import windSound from "../assets/wind.mp3";

import "@babylonjs/core/Collisions/collisionCoordinator"
import "@babylonjs/core/Collisions/collisionCoordinator";
import "@babylonjs/core/Audio/audioSceneComponent";
import "@babylonjs/core/Audio/audioEngine";
import {Sound} from "@babylonjs/core/Audio/sound";
import {Engine} from "@babylonjs/core/Engines/engine";
import { Sound } from "@babylonjs/core/Audio/sound";
import { Engine } from "@babylonjs/core/Engines/engine";

import "@babylonjs/core/Physics/physicsEngineComponent";
import {PatchManager} from "./instancing/patchManager";
import {MeshBuilder} from "@babylonjs/core/Meshes/meshBuilder";
import {StandardMaterial} from "@babylonjs/core/Materials/standardMaterial";
import { PatchManager } from "./instancing/patchManager";
import { MeshBuilder } from "@babylonjs/core/Meshes/meshBuilder";
import { StandardMaterial } from "@babylonjs/core/Materials/standardMaterial";
import HavokPhysics from "@babylonjs/havok";
import {HavokPlugin} from "@babylonjs/core/Physics/v2/Plugins/havokPlugin";
import {IPatch} from "./instancing/iPatch";
import {createButterfly} from "./butterfly/butterfly";
import {createButterflyMaterial} from "./butterfly/butterflyMaterial";
import { HavokPlugin } from "@babylonjs/core/Physics/v2/Plugins/havokPlugin";
import { IPatch } from "./instancing/iPatch";
import { createButterfly } from "./butterfly/butterfly";
import { createButterflyMaterial } from "./butterfly/butterflyMaterial";

// Init babylonjs
const canvas = document.getElementById("renderer") as HTMLCanvasElement;
Expand All @@ -51,7 +49,7 @@ const havokPlugin = new HavokPlugin(true, havokInstance);
const scene = new Scene(engine);
scene.enablePhysics(new Vector3(0, -9.81, 0), havokPlugin);

const camera = new ArcRotateCamera("camera", -3.14 * 3 / 4, 1.4, 6, Vector3.Zero(), scene);
const camera = new ArcRotateCamera("camera", (-3.14 * 3) / 4, 1.4, 6, Vector3.Zero(), scene);
camera.minZ = 0.1;
camera.attachControl();

Expand All @@ -64,16 +62,7 @@ new Sound("wind", windSound, scene, null, {
autoplay: true
});

const inputMap: Map<string, boolean> = new Map<string, boolean>();
scene.actionManager = new ActionManager(scene);
scene.actionManager.registerAction(new ExecuteCodeAction(ActionManager.OnKeyDownTrigger, (e) => {
inputMap.set(e.sourceEvent.key, e.sourceEvent.type == "keydown");
}));
scene.actionManager.registerAction(new ExecuteCodeAction(ActionManager.OnKeyUpTrigger, (e) => {
inputMap.set(e.sourceEvent.key, e.sourceEvent.type == "keydown");
}));

const character = await createCharacterController(scene, camera, inputMap);
const character = await createCharacterController(scene, camera);

// Interesting part starts here
const highQualityGrassBlade = createGrassBlade(scene, 4);
Expand All @@ -97,10 +86,14 @@ const grassManager = new PatchManager([lowQualityGrassBlade, highQualityGrassBla
grassManager.addPatches(PatchManager.circleInit(fieldRadius, patchSize, patchResolution));
grassManager.initInstances();

const ground = MeshBuilder.CreateGround("ground", {
width: patchSize * fieldRadius * 2,
height: patchSize * fieldRadius * 2
}, scene);
const ground = MeshBuilder.CreateGround(
"ground",
{
width: patchSize * fieldRadius * 2,
height: patchSize * fieldRadius * 2
},
scene
);
const groundMaterial = new StandardMaterial("groundMaterial", scene);
groundMaterial.diffuseColor.set(0.02, 0.1, 0.01);
groundMaterial.specularColor.scaleInPlace(0);
Expand All @@ -121,7 +114,7 @@ const ui = new UI(scene);
document.addEventListener("keypress", (e) => {
if (e.key === "p") {
// take screenshot
Tools.CreateScreenshot(engine, camera, {precision: 1});
Tools.CreateScreenshot(engine, camera, { precision: 1 });
}
});

Expand All @@ -141,4 +134,3 @@ window.addEventListener("resize", () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});

15 changes: 9 additions & 6 deletions src/ts/grass/grassBlade.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import {Mesh} from "@babylonjs/core/Meshes/mesh";
import {VertexData} from "@babylonjs/core/Meshes/mesh.vertexData";
import {Scene} from "@babylonjs/core/scene";
import {Vector3} from "@babylonjs/core/Maths/math.vector";
import { Mesh } from "@babylonjs/core/Meshes/mesh";
import { VertexData } from "@babylonjs/core/Meshes/mesh.vertexData";
import { Scene } from "@babylonjs/core/scene";
import { Vector3 } from "@babylonjs/core/Maths/math.vector";

// rotation using https://www.wikiwand.com/en/Rodrigues%27_rotation_formula
function rotateAround(vector: Vector3, axis: Vector3, theta: number) {
// Please note that unit vector are required, i did not divided by the norms
return vector.scale(Math.cos(theta)).addInPlace(Vector3.Cross(axis, vector).scaleInPlace(Math.sin(theta))).addInPlace(axis.scale(Vector3.Dot(axis, vector) * (1.0 - Math.cos(theta))));
return vector
.scale(Math.cos(theta))
.addInPlace(Vector3.Cross(axis, vector).scaleInPlace(Math.sin(theta)))
.addInPlace(axis.scale(Vector3.Dot(axis, vector) * (1.0 - Math.cos(theta))));
}

export function createGrassBlade(scene: Scene, nbStacks: number) {
Expand Down Expand Up @@ -80,4 +83,4 @@ export function createGrassBlade(scene: Scene, nbStacks: number) {
vertexData.applyToMesh(grassBlade);

return grassBlade;
}
}
14 changes: 7 additions & 7 deletions src/ts/grass/grassMaterial.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { Effect } from "@babylonjs/core/Materials/effect";
import { ShaderMaterial } from "@babylonjs/core/Materials/shaderMaterial";
import {Scene} from "@babylonjs/core/scene";
import { Scene } from "@babylonjs/core/scene";

import grassFragment from "../../shaders/grassFragment.glsl";
import grassVertex from "../../shaders/grassVertex.glsl";
import {TransformNode} from "@babylonjs/core/Meshes/transformNode";
import {DirectionalLight} from "@babylonjs/core/Lights/directionalLight";
import {Texture} from "@babylonjs/core/Materials/Textures/texture";
import { TransformNode } from "@babylonjs/core/Meshes/transformNode";
import { DirectionalLight } from "@babylonjs/core/Lights/directionalLight";
import { Texture } from "@babylonjs/core/Materials/Textures/texture";
import perlinNoise from "../../assets/perlin.png";
import {Vector3} from "@babylonjs/core/Maths/math.vector";
import { Vector3 } from "@babylonjs/core/Maths/math.vector";

export function createGrassMaterial(light: DirectionalLight, scene: Scene, player?:TransformNode) {
export function createGrassMaterial(light: DirectionalLight, scene: Scene, player?: TransformNode) {
const shaderName = "grassMaterial";
Effect.ShadersStore[`${shaderName}FragmentShader`] = grassFragment;
Effect.ShadersStore[`${shaderName}VertexShader`] = grassVertex;
Expand Down Expand Up @@ -40,4 +40,4 @@ export function createGrassMaterial(light: DirectionalLight, scene: Scene, playe
});

return grassMaterial;
}
}
Loading

0 comments on commit 176dfe3

Please sign in to comment.