Add world background renderer and update advanced rendering composer
This commit is contained in:
@@ -110,7 +110,8 @@ export function createAdvancedRenderingComposer(
|
|||||||
renderer: WebGLRenderer,
|
renderer: WebGLRenderer,
|
||||||
scene: Scene,
|
scene: Scene,
|
||||||
camera: PerspectiveCamera,
|
camera: PerspectiveCamera,
|
||||||
settings: AdvancedRenderingSettings
|
settings: AdvancedRenderingSettings,
|
||||||
|
backgroundScene: Scene | null = null
|
||||||
): EffectComposer {
|
): EffectComposer {
|
||||||
// The scene is always rendered into the composer's offscreen targets first,
|
// The scene is always rendered into the composer's offscreen targets first,
|
||||||
// so those targets need depth for correct visibility even when no effect samples it.
|
// so those targets need depth for correct visibility even when no effect samples it.
|
||||||
@@ -121,7 +122,14 @@ export function createAdvancedRenderingComposer(
|
|||||||
frameBufferType: renderer.capabilities.isWebGL2 ? HalfFloatType : UnsignedByteType
|
frameBufferType: renderer.capabilities.isWebGL2 ? HalfFloatType : UnsignedByteType
|
||||||
});
|
});
|
||||||
|
|
||||||
composer.addPass(new RenderPass(scene, camera));
|
if (backgroundScene !== null) {
|
||||||
|
composer.addPass(new RenderPass(backgroundScene, camera));
|
||||||
|
const mainRenderPass = new RenderPass(scene, camera);
|
||||||
|
mainRenderPass.clear = false;
|
||||||
|
composer.addPass(mainRenderPass);
|
||||||
|
} else {
|
||||||
|
composer.addPass(new RenderPass(scene, camera));
|
||||||
|
}
|
||||||
|
|
||||||
const effects: Array<BloomEffect | DepthOfFieldEffect | ToneMappingEffect | SMAAEffect> = [];
|
const effects: Array<BloomEffect | DepthOfFieldEffect | ToneMappingEffect | SMAAEffect> = [];
|
||||||
|
|
||||||
|
|||||||
259
src/rendering/world-background-renderer.ts
Normal file
259
src/rendering/world-background-renderer.ts
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
import {
|
||||||
|
BackSide,
|
||||||
|
Color,
|
||||||
|
Group,
|
||||||
|
Mesh,
|
||||||
|
MeshBasicMaterial,
|
||||||
|
PerspectiveCamera,
|
||||||
|
Scene,
|
||||||
|
ShaderMaterial,
|
||||||
|
SphereGeometry,
|
||||||
|
Texture
|
||||||
|
} from "three";
|
||||||
|
|
||||||
|
import type { WorldBackgroundSettings } from "../document/world-settings";
|
||||||
|
|
||||||
|
const BACKGROUND_SPHERE_RADIUS = 320;
|
||||||
|
const BACKGROUND_SPHERE_WIDTH_SEGMENTS = 48;
|
||||||
|
const BACKGROUND_SPHERE_HEIGHT_SEGMENTS = 24;
|
||||||
|
const DEFAULT_IMAGE_BACKGROUND_FALLBACK_COLOR = "#0d1116";
|
||||||
|
const NIGHT_BACKGROUND_EPSILON = 1e-4;
|
||||||
|
|
||||||
|
function clamp(value: number, min: number, max: number) {
|
||||||
|
return Math.min(Math.max(value, min), max);
|
||||||
|
}
|
||||||
|
|
||||||
|
function lerp(left: number, right: number, amount: number) {
|
||||||
|
return left + (right - left) * amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveGradientColors(background: WorldBackgroundSettings) {
|
||||||
|
if (background.mode === "solid") {
|
||||||
|
return {
|
||||||
|
topColorHex: background.colorHex,
|
||||||
|
bottomColorHex: background.colorHex
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (background.mode === "verticalGradient") {
|
||||||
|
return {
|
||||||
|
topColorHex: background.topColorHex,
|
||||||
|
bottomColorHex: background.bottomColorHex
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
topColorHex: DEFAULT_IMAGE_BACKGROUND_FALLBACK_COLOR,
|
||||||
|
bottomColorHex: DEFAULT_IMAGE_BACKGROUND_FALLBACK_COLOR
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorldBackgroundOverlayState {
|
||||||
|
texture: Texture | null;
|
||||||
|
opacity: number;
|
||||||
|
environmentIntensity: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorldEnvironmentState {
|
||||||
|
texture: Texture | null;
|
||||||
|
intensity: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveWorldEnvironmentState(
|
||||||
|
background: WorldBackgroundSettings,
|
||||||
|
backgroundTexture: Texture | null,
|
||||||
|
overlay: WorldBackgroundOverlayState | null
|
||||||
|
): WorldEnvironmentState {
|
||||||
|
const baseTexture = background.mode === "image" ? backgroundTexture : null;
|
||||||
|
const baseIntensity =
|
||||||
|
background.mode === "image" ? background.environmentIntensity : 0;
|
||||||
|
const overlayTexture = overlay?.texture ?? null;
|
||||||
|
const overlayOpacity = clamp(overlay?.opacity ?? 0, 0, 1);
|
||||||
|
const overlayIntensity = overlay?.environmentIntensity ?? 0;
|
||||||
|
|
||||||
|
if (
|
||||||
|
baseTexture !== null &&
|
||||||
|
overlayTexture !== null &&
|
||||||
|
overlayOpacity > NIGHT_BACKGROUND_EPSILON &&
|
||||||
|
overlayOpacity < 1 - NIGHT_BACKGROUND_EPSILON
|
||||||
|
) {
|
||||||
|
if (overlayOpacity < 0.5) {
|
||||||
|
return {
|
||||||
|
texture: baseTexture,
|
||||||
|
intensity: lerp(baseIntensity, 0, overlayOpacity * 2)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
texture: overlayTexture,
|
||||||
|
intensity: lerp(0, overlayIntensity, (overlayOpacity - 0.5) * 2)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overlayTexture !== null && overlayOpacity > NIGHT_BACKGROUND_EPSILON) {
|
||||||
|
if (baseTexture === null) {
|
||||||
|
return {
|
||||||
|
texture: overlayTexture,
|
||||||
|
intensity: overlayIntensity * overlayOpacity
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overlayOpacity >= 1 - NIGHT_BACKGROUND_EPSILON) {
|
||||||
|
return {
|
||||||
|
texture: overlayTexture,
|
||||||
|
intensity: overlayIntensity
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baseTexture !== null) {
|
||||||
|
return {
|
||||||
|
texture: baseTexture,
|
||||||
|
intensity: baseIntensity
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overlayTexture !== null && overlayOpacity > NIGHT_BACKGROUND_EPSILON) {
|
||||||
|
return {
|
||||||
|
texture: overlayTexture,
|
||||||
|
intensity: overlayIntensity * overlayOpacity
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
texture: null,
|
||||||
|
intensity: 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const GRADIENT_VERTEX_SHADER = `
|
||||||
|
varying vec3 vWorldPosition;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 worldPosition = modelMatrix * vec4(position, 1.0);
|
||||||
|
vWorldPosition = worldPosition.xyz;
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const GRADIENT_FRAGMENT_SHADER = `
|
||||||
|
uniform vec3 uTopColor;
|
||||||
|
uniform vec3 uBottomColor;
|
||||||
|
varying vec3 vWorldPosition;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec3 direction = normalize(vWorldPosition - cameraPosition);
|
||||||
|
float gradientAmount = clamp(direction.y * 0.5 + 0.5, 0.0, 1.0);
|
||||||
|
vec3 color = mix(uBottomColor, uTopColor, gradientAmount);
|
||||||
|
gl_FragColor = vec4(color, 1.0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export class WorldBackgroundRenderer {
|
||||||
|
readonly scene = new Scene();
|
||||||
|
|
||||||
|
private readonly anchor = new Group();
|
||||||
|
private readonly geometry = new SphereGeometry(
|
||||||
|
BACKGROUND_SPHERE_RADIUS,
|
||||||
|
BACKGROUND_SPHERE_WIDTH_SEGMENTS,
|
||||||
|
BACKGROUND_SPHERE_HEIGHT_SEGMENTS
|
||||||
|
);
|
||||||
|
private readonly gradientMaterial = new ShaderMaterial({
|
||||||
|
uniforms: {
|
||||||
|
uTopColor: {
|
||||||
|
value: new Color(DEFAULT_IMAGE_BACKGROUND_FALLBACK_COLOR)
|
||||||
|
},
|
||||||
|
uBottomColor: {
|
||||||
|
value: new Color(DEFAULT_IMAGE_BACKGROUND_FALLBACK_COLOR)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
vertexShader: GRADIENT_VERTEX_SHADER,
|
||||||
|
fragmentShader: GRADIENT_FRAGMENT_SHADER,
|
||||||
|
side: BackSide,
|
||||||
|
depthTest: false,
|
||||||
|
depthWrite: false,
|
||||||
|
fog: false
|
||||||
|
});
|
||||||
|
private readonly imageMaterial = new MeshBasicMaterial({
|
||||||
|
color: 0xffffff,
|
||||||
|
side: BackSide,
|
||||||
|
depthTest: false,
|
||||||
|
depthWrite: false,
|
||||||
|
fog: false
|
||||||
|
});
|
||||||
|
private readonly overlayMaterial = new MeshBasicMaterial({
|
||||||
|
color: 0xffffff,
|
||||||
|
side: BackSide,
|
||||||
|
depthTest: false,
|
||||||
|
depthWrite: false,
|
||||||
|
fog: false,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0
|
||||||
|
});
|
||||||
|
private readonly gradientMesh = new Mesh(this.geometry, this.gradientMaterial);
|
||||||
|
private readonly imageMesh = new Mesh(this.geometry, this.imageMaterial);
|
||||||
|
private readonly overlayMesh = new Mesh(this.geometry, this.overlayMaterial);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.gradientMesh.renderOrder = -1002;
|
||||||
|
this.imageMesh.renderOrder = -1001;
|
||||||
|
this.overlayMesh.renderOrder = -1000;
|
||||||
|
|
||||||
|
for (const mesh of [this.gradientMesh, this.imageMesh, this.overlayMesh]) {
|
||||||
|
mesh.frustumCulled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.anchor.add(this.gradientMesh);
|
||||||
|
this.anchor.add(this.imageMesh);
|
||||||
|
this.anchor.add(this.overlayMesh);
|
||||||
|
this.scene.add(this.anchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(
|
||||||
|
background: WorldBackgroundSettings,
|
||||||
|
backgroundTexture: Texture | null,
|
||||||
|
overlay: WorldBackgroundOverlayState | null
|
||||||
|
) {
|
||||||
|
const gradientColors = resolveGradientColors(background);
|
||||||
|
this.gradientMaterial.uniforms.uTopColor.value.set(
|
||||||
|
gradientColors.topColorHex
|
||||||
|
);
|
||||||
|
this.gradientMaterial.uniforms.uBottomColor.value.set(
|
||||||
|
gradientColors.bottomColorHex
|
||||||
|
);
|
||||||
|
|
||||||
|
const showImageBackground =
|
||||||
|
background.mode === "image" && backgroundTexture !== null;
|
||||||
|
|
||||||
|
if (this.imageMaterial.map !== backgroundTexture) {
|
||||||
|
this.imageMaterial.map = backgroundTexture;
|
||||||
|
this.imageMaterial.needsUpdate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.gradientMesh.visible = !showImageBackground;
|
||||||
|
this.imageMesh.visible = showImageBackground;
|
||||||
|
|
||||||
|
const overlayTexture = overlay?.texture ?? null;
|
||||||
|
const overlayOpacity =
|
||||||
|
overlayTexture === null ? 0 : clamp(overlay?.opacity ?? 0, 0, 1);
|
||||||
|
|
||||||
|
if (this.overlayMaterial.map !== overlayTexture) {
|
||||||
|
this.overlayMaterial.map = overlayTexture;
|
||||||
|
this.overlayMaterial.needsUpdate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.overlayMaterial.opacity = overlayOpacity;
|
||||||
|
this.overlayMesh.visible = overlayOpacity > NIGHT_BACKGROUND_EPSILON;
|
||||||
|
}
|
||||||
|
|
||||||
|
syncToCamera(camera: PerspectiveCamera) {
|
||||||
|
this.anchor.position.copy(camera.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this.geometry.dispose();
|
||||||
|
this.gradientMaterial.dispose();
|
||||||
|
this.imageMaterial.dispose();
|
||||||
|
this.overlayMaterial.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user