Feat: Implement wind simulation for instanced foliage rendering
This commit is contained in:
@@ -114,14 +114,128 @@ function stableStringify(value: unknown): string {
|
|||||||
return JSON.stringify(String(value));
|
return JSON.stringify(String(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
function cloneMaterial(material: Material): Material {
|
function resolveFoliageWindDirectionVector(
|
||||||
return material.clone();
|
settings: FoliageQualitySettings
|
||||||
|
): Vector2 {
|
||||||
|
const radians = (settings.windDirectionDegrees * Math.PI) / 180;
|
||||||
|
|
||||||
|
return new Vector2(Math.cos(radians), Math.sin(radians)).normalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
function cloneMaterialSet(material: Material | Material[]): Material | Material[] {
|
function createFoliageWindUniformSet(
|
||||||
|
settings: FoliageQualitySettings,
|
||||||
|
time: number
|
||||||
|
): FoliageWindUniformSet {
|
||||||
|
return {
|
||||||
|
time: { value: time },
|
||||||
|
strength: { value: settings.windStrength },
|
||||||
|
speed: { value: settings.windSpeed },
|
||||||
|
direction: { value: resolveFoliageWindDirectionVector(settings) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeFoliageWindUniformValues(
|
||||||
|
uniforms: FoliageWindUniformSet,
|
||||||
|
settings: FoliageQualitySettings,
|
||||||
|
time: number
|
||||||
|
) {
|
||||||
|
uniforms.time.value = time;
|
||||||
|
uniforms.strength.value = settings.windStrength;
|
||||||
|
uniforms.speed.value = settings.windSpeed;
|
||||||
|
uniforms.direction.value.copy(resolveFoliageWindDirectionVector(settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
function patchFoliageWindMaterial(
|
||||||
|
material: MeshStandardMaterial,
|
||||||
|
options: FoliageWindMaterialOptions
|
||||||
|
) {
|
||||||
|
const previousOnBeforeCompile = material.onBeforeCompile.bind(material);
|
||||||
|
const previousCustomProgramCacheKey =
|
||||||
|
material.customProgramCacheKey.bind(material);
|
||||||
|
|
||||||
|
material.onBeforeCompile = (
|
||||||
|
shader: FoliageWindCompileShader,
|
||||||
|
renderer: FoliageWindCompileRenderer
|
||||||
|
) => {
|
||||||
|
previousOnBeforeCompile(shader, renderer);
|
||||||
|
|
||||||
|
const uniforms = createFoliageWindUniformSet(
|
||||||
|
options.getSettings(),
|
||||||
|
options.getTime()
|
||||||
|
);
|
||||||
|
|
||||||
|
shader.uniforms.foliageWindTime = uniforms.time;
|
||||||
|
shader.uniforms.foliageWindStrength = uniforms.strength;
|
||||||
|
shader.uniforms.foliageWindSpeed = uniforms.speed;
|
||||||
|
shader.uniforms.foliageWindDirection = uniforms.direction;
|
||||||
|
shader.vertexShader = shader.vertexShader
|
||||||
|
.replace(
|
||||||
|
"#include <common>",
|
||||||
|
`#include <common>
|
||||||
|
attribute vec2 ${FOLIAGE_WIND_ATTRIBUTE_NAME};
|
||||||
|
uniform float foliageWindTime;
|
||||||
|
uniform float foliageWindStrength;
|
||||||
|
uniform float foliageWindSpeed;
|
||||||
|
uniform vec2 foliageWindDirection;`
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
"#include <project_vertex>",
|
||||||
|
`vec4 mvPosition = vec4( transformed, 1.0 );
|
||||||
|
#ifdef USE_BATCHING
|
||||||
|
mvPosition = batchingMatrix * mvPosition;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_INSTANCING
|
||||||
|
mvPosition = instanceMatrix * mvPosition;
|
||||||
|
#endif
|
||||||
|
float foliageWindHeightMask = clamp(position.y, 0.0, 1.0);
|
||||||
|
foliageWindHeightMask *= foliageWindHeightMask;
|
||||||
|
float foliageWindInstanceStrength = max(${FOLIAGE_WIND_ATTRIBUTE_NAME}.y, 0.0);
|
||||||
|
float foliageWindAmount =
|
||||||
|
foliageWindStrength * foliageWindInstanceStrength * foliageWindHeightMask;
|
||||||
|
vec2 foliageWindSideDirection = vec2(-foliageWindDirection.y, foliageWindDirection.x);
|
||||||
|
float foliageWindPhase =
|
||||||
|
dot(mvPosition.xz, foliageWindDirection * 0.22) +
|
||||||
|
${FOLIAGE_WIND_ATTRIBUTE_NAME}.x +
|
||||||
|
foliageWindTime * foliageWindSpeed;
|
||||||
|
float foliageWindPrimary = sin(foliageWindPhase);
|
||||||
|
float foliageWindSecondary = sin(foliageWindPhase * 1.73 + foliageWindTime * foliageWindSpeed * 0.37);
|
||||||
|
mvPosition.xz += foliageWindDirection * foliageWindPrimary * foliageWindAmount * 0.18;
|
||||||
|
mvPosition.xz += foliageWindSideDirection * foliageWindSecondary * foliageWindAmount * 0.045;
|
||||||
|
mvPosition.y += foliageWindSecondary * foliageWindAmount * 0.025;
|
||||||
|
mvPosition = modelViewMatrix * mvPosition;
|
||||||
|
gl_Position = projectionMatrix * mvPosition;`
|
||||||
|
);
|
||||||
|
|
||||||
|
options.registerUniforms(uniforms);
|
||||||
|
};
|
||||||
|
material.customProgramCacheKey = () =>
|
||||||
|
`${previousCustomProgramCacheKey()}|${FOLIAGE_WIND_SHADER_KEY}`;
|
||||||
|
material.needsUpdate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloneMaterial(
|
||||||
|
material: Material,
|
||||||
|
windOptions?: FoliageWindMaterialOptions
|
||||||
|
): Material {
|
||||||
|
const clonedMaterial = material.clone();
|
||||||
|
|
||||||
|
if (
|
||||||
|
windOptions !== undefined &&
|
||||||
|
clonedMaterial instanceof MeshStandardMaterial
|
||||||
|
) {
|
||||||
|
patchFoliageWindMaterial(clonedMaterial, windOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return clonedMaterial;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloneMaterialSet(
|
||||||
|
material: Material | Material[],
|
||||||
|
windOptions?: FoliageWindMaterialOptions
|
||||||
|
): Material | Material[] {
|
||||||
return Array.isArray(material)
|
return Array.isArray(material)
|
||||||
? material.map((entry) => cloneMaterial(entry))
|
? material.map((entry) => cloneMaterial(entry, windOptions))
|
||||||
: cloneMaterial(material);
|
: cloneMaterial(material, windOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
function disposeMaterial(material: Material | Material[]) {
|
function disposeMaterial(material: Material | Material[]) {
|
||||||
@@ -268,18 +382,35 @@ function createFoliageRenderResourceSignature(options: {
|
|||||||
quality: {
|
quality: {
|
||||||
enabled: options.quality.enabled,
|
enabled: options.quality.enabled,
|
||||||
densityMultiplier: options.quality.densityMultiplier,
|
densityMultiplier: options.quality.densityMultiplier,
|
||||||
shadows: options.quality.shadows
|
shadows: options.quality.shadows,
|
||||||
|
windEnabled: options.quality.windEnabled
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createFoliageWindAttribute(
|
||||||
|
instances: FoliageRenderBatch["instances"]
|
||||||
|
): InstancedBufferAttribute {
|
||||||
|
const values = new Float32Array(instances.length * 2);
|
||||||
|
|
||||||
|
for (let index = 0; index < instances.length; index += 1) {
|
||||||
|
const instance = instances[index]!;
|
||||||
|
|
||||||
|
values[index * 2] = instance.windPhase;
|
||||||
|
values[index * 2 + 1] = Math.max(0, instance.windStrength);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new InstancedBufferAttribute(values, 2);
|
||||||
|
}
|
||||||
|
|
||||||
function createInstancedMeshForSource(
|
function createInstancedMeshForSource(
|
||||||
batch: FoliageRenderBatch,
|
batch: FoliageRenderBatch,
|
||||||
sourceMesh: FoliageTemplateSourceMesh
|
sourceMesh: FoliageTemplateSourceMesh,
|
||||||
|
windOptions?: FoliageWindMaterialOptions
|
||||||
): InstancedMesh {
|
): InstancedMesh {
|
||||||
const mesh = new InstancedMesh(
|
const mesh = new InstancedMesh(
|
||||||
sourceMesh.geometry.clone(),
|
sourceMesh.geometry.clone(),
|
||||||
cloneMaterialSet(sourceMesh.material),
|
cloneMaterialSet(sourceMesh.material, windOptions),
|
||||||
batch.instances.length
|
batch.instances.length
|
||||||
);
|
);
|
||||||
const color = new Color();
|
const color = new Color();
|
||||||
@@ -313,6 +444,13 @@ function createInstancedMeshForSource(
|
|||||||
|
|
||||||
mesh.instanceMatrix.needsUpdate = true;
|
mesh.instanceMatrix.needsUpdate = true;
|
||||||
|
|
||||||
|
if (windOptions !== undefined) {
|
||||||
|
mesh.geometry.setAttribute(
|
||||||
|
FOLIAGE_WIND_ATTRIBUTE_NAME,
|
||||||
|
createFoliageWindAttribute(batch.instances)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (mesh.instanceColor !== null) {
|
if (mesh.instanceColor !== null) {
|
||||||
mesh.instanceColor.needsUpdate = true;
|
mesh.instanceColor.needsUpdate = true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user