auto-git:
[add] src/rendering/terrain-layer-material.ts [add] tests/domain/terrains.test.ts [change] src/app/App.tsx [change] src/core/terrain-brush.ts [change] src/document/migrate-scene-document.ts [change] src/document/scene-document-validation.ts [change] src/document/scene-document.ts [change] src/document/terrains.ts [change] src/geometry/terrain-brush.ts [change] src/geometry/terrain-mesh.ts [change] src/runtime-three/rapier-collision-world.ts [change] src/runtime-three/runtime-host.ts [change] src/runtime-three/runtime-scene-build.ts [change] src/viewport-three/ViewportCanvas.tsx [change] src/viewport-three/ViewportPanel.tsx [change] src/viewport-three/viewport-host.ts [change] tests/domain/build-runtime-scene.test.ts [change] tests/domain/rapier-collision-world.test.ts [change] tests/domain/terrain.command.test.ts [change] tests/domain/water-material.test.ts [change] tests/geometry/terrain-brush.test.ts [change] tests/geometry/terrain-mesh.test.ts [change] tests/serialization/scene-document-json.test.ts [change] tests/unit/terrain-foundation.integration.test.tsx [change] tests/unit/viewport-canvas.test.tsx
This commit is contained in:
144
src/rendering/terrain-layer-material.ts
Normal file
144
src/rendering/terrain-layer-material.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import {
|
||||
ClampToEdgeWrapping,
|
||||
Color,
|
||||
DataTexture,
|
||||
LinearFilter,
|
||||
Material,
|
||||
MeshBasicMaterial,
|
||||
MeshStandardMaterial,
|
||||
RGBAFormat,
|
||||
SRGBColorSpace,
|
||||
UnsignedByteType,
|
||||
type Texture
|
||||
} from "three";
|
||||
|
||||
import type { MaterialDef } from "../materials/starter-material-library";
|
||||
|
||||
interface TerrainLayerBlendMaterialOptions {
|
||||
layerTextures: readonly [Texture, Texture, Texture, Texture];
|
||||
emissiveHex?: number;
|
||||
emissiveIntensity?: number;
|
||||
wireframe?: boolean;
|
||||
}
|
||||
|
||||
let fallbackTerrainLayerTexture: Texture | null = null;
|
||||
|
||||
function createFallbackTerrainLayerTexture(): Texture {
|
||||
const texture = new DataTexture(
|
||||
new Uint8Array([174, 167, 154, 255]),
|
||||
1,
|
||||
1,
|
||||
RGBAFormat,
|
||||
UnsignedByteType
|
||||
);
|
||||
texture.colorSpace = SRGBColorSpace;
|
||||
texture.minFilter = LinearFilter;
|
||||
texture.magFilter = LinearFilter;
|
||||
texture.wrapS = ClampToEdgeWrapping;
|
||||
texture.wrapT = ClampToEdgeWrapping;
|
||||
texture.needsUpdate = true;
|
||||
return texture;
|
||||
}
|
||||
|
||||
export function getFallbackTerrainLayerTexture(): Texture {
|
||||
if (fallbackTerrainLayerTexture === null) {
|
||||
fallbackTerrainLayerTexture = createFallbackTerrainLayerTexture();
|
||||
}
|
||||
|
||||
return fallbackTerrainLayerTexture;
|
||||
}
|
||||
|
||||
export function getTerrainLayerTexture(
|
||||
material: MaterialDef | null,
|
||||
textureLookup: (material: MaterialDef) => Texture
|
||||
): Texture {
|
||||
return material === null
|
||||
? getFallbackTerrainLayerTexture()
|
||||
: textureLookup(material);
|
||||
}
|
||||
|
||||
export function getTerrainLayerPreviewColor(material: MaterialDef | null): number {
|
||||
return material === null
|
||||
? new Color("#aea79a").getHex()
|
||||
: new Color(material.swatchColorHex).getHex();
|
||||
}
|
||||
|
||||
export function createTerrainLayerBlendMaterial(
|
||||
options: TerrainLayerBlendMaterialOptions
|
||||
): Material {
|
||||
if (options.wireframe === true) {
|
||||
return new MeshBasicMaterial({
|
||||
color: 0xf2ece2,
|
||||
wireframe: true
|
||||
});
|
||||
}
|
||||
|
||||
const material = new MeshStandardMaterial({
|
||||
color: 0xffffff,
|
||||
map: options.layerTextures[0],
|
||||
emissive: options.emissiveHex ?? 0x000000,
|
||||
emissiveIntensity: options.emissiveIntensity ?? 0,
|
||||
roughness: 1,
|
||||
metalness: 0
|
||||
});
|
||||
|
||||
material.onBeforeCompile = (shader) => {
|
||||
shader.uniforms.terrainLayerMap0 = { value: options.layerTextures[0] };
|
||||
shader.uniforms.terrainLayerMap1 = { value: options.layerTextures[1] };
|
||||
shader.uniforms.terrainLayerMap2 = { value: options.layerTextures[2] };
|
||||
shader.uniforms.terrainLayerMap3 = { value: options.layerTextures[3] };
|
||||
|
||||
shader.vertexShader = shader.vertexShader
|
||||
.replace(
|
||||
"#include <common>",
|
||||
`#include <common>
|
||||
attribute vec4 terrainLayerWeights;
|
||||
varying vec4 vTerrainLayerWeights;
|
||||
varying vec2 vTerrainUv;`
|
||||
)
|
||||
.replace(
|
||||
"#include <uv_vertex>",
|
||||
`#include <uv_vertex>
|
||||
vTerrainUv = uv;
|
||||
vTerrainLayerWeights = terrainLayerWeights;`
|
||||
);
|
||||
|
||||
shader.fragmentShader = shader.fragmentShader
|
||||
.replace(
|
||||
"#include <common>",
|
||||
`#include <common>
|
||||
varying vec4 vTerrainLayerWeights;
|
||||
varying vec2 vTerrainUv;
|
||||
uniform sampler2D terrainLayerMap0;
|
||||
uniform sampler2D terrainLayerMap1;
|
||||
uniform sampler2D terrainLayerMap2;
|
||||
uniform sampler2D terrainLayerMap3;`
|
||||
)
|
||||
.replace(
|
||||
"#include <map_fragment>",
|
||||
`vec4 terrainWeights = max(vTerrainLayerWeights, 0.0);
|
||||
float terrainWeightSum =
|
||||
terrainWeights.x +
|
||||
terrainWeights.y +
|
||||
terrainWeights.z +
|
||||
terrainWeights.w;
|
||||
|
||||
if (terrainWeightSum <= 0.0) {
|
||||
terrainWeights = vec4(1.0, 0.0, 0.0, 0.0);
|
||||
} else {
|
||||
terrainWeights /= terrainWeightSum;
|
||||
}
|
||||
|
||||
vec4 terrainLayerColor =
|
||||
texture2D(terrainLayerMap0, vTerrainUv) * terrainWeights.x +
|
||||
texture2D(terrainLayerMap1, vTerrainUv) * terrainWeights.y +
|
||||
texture2D(terrainLayerMap2, vTerrainUv) * terrainWeights.z +
|
||||
texture2D(terrainLayerMap3, vTerrainUv) * terrainWeights.w;
|
||||
diffuseColor *= terrainLayerColor;`
|
||||
);
|
||||
};
|
||||
|
||||
material.customProgramCacheKey = () => "terrain-layer-blend-v1";
|
||||
material.needsUpdate = true;
|
||||
return material;
|
||||
}
|
||||
Reference in New Issue
Block a user