cione-comp-lib
Version:
`$ yarn add cione-comp-lib` 或者 `$ npm i cione-comp-lib -S`
206 lines (205 loc) • 8.19 kB
JavaScript
import { Object3D, OrthographicCamera, MeshDepthMaterial, ShaderMaterial, Box3, Vector3, PlaneGeometry, MeshBasicMaterial, BackSide, Mesh, RGBAFormat, WebGLRenderTarget } from "../../../../_three@0.150.1@three/build/three.module.mjs";
import { HorizontalBlurShader } from "../../../../_three@0.150.1@three/examples/jsm/shaders/HorizontalBlurShader.mjs";
import { VerticalBlurShader } from "../../../../_three@0.150.1@three/examples/jsm/shaders/VerticalBlurShader.mjs";
import { lerp } from "../../../../_three@0.150.1@three/src/math/MathUtils.mjs";
/* @license
* Copyright 2022 Google LLC. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const LOG_MAX_RESOLUTION = 9;
const LOG_MIN_RESOLUTION = 6;
const ANIMATION_SCALING = 2;
const DEFAULT_HARD_INTENSITY = 0.3;
class Shadow extends Object3D {
constructor(scene, softness, side) {
super();
this.camera = new OrthographicCamera();
this.renderTarget = null;
this.renderTargetBlur = null;
this.depthMaterial = new MeshDepthMaterial();
this.horizontalBlurMaterial = new ShaderMaterial(HorizontalBlurShader);
this.verticalBlurMaterial = new ShaderMaterial(VerticalBlurShader);
this.intensity = 0;
this.softness = 1;
this.boundingBox = new Box3();
this.size = new Vector3();
this.maxDimension = 0;
this.isAnimated = false;
this.needsUpdate = false;
const { camera } = this;
camera.rotation.x = Math.PI / 2;
camera.left = -0.5;
camera.right = 0.5;
camera.bottom = -0.5;
camera.top = 0.5;
this.add(camera);
const plane = new PlaneGeometry();
const shadowMaterial = new MeshBasicMaterial({
opacity: 1,
transparent: true,
side: BackSide
});
this.floor = new Mesh(plane, shadowMaterial);
this.floor.userData.shadow = true;
camera.add(this.floor);
this.blurPlane = new Mesh(plane);
this.blurPlane.visible = false;
camera.add(this.blurPlane);
scene.target.add(this);
this.depthMaterial.onBeforeCompile = function(shader) {
shader.fragmentShader = shader.fragmentShader.replace("gl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );", "gl_FragColor = vec4( vec3( 0.0 ), ( 1.0 - fragCoordZ ) * opacity );");
};
this.horizontalBlurMaterial.depthTest = false;
this.verticalBlurMaterial.depthTest = false;
this.setScene(scene, softness, side);
}
setScene(scene, softness, side) {
const { boundingBox, size, rotation, position } = this;
this.isAnimated = scene.animationNames.length > 0;
this.boundingBox.copy(scene.boundingBox);
this.size.copy(scene.size);
this.maxDimension = Math.max(size.x, size.y, size.z) * (this.isAnimated ? ANIMATION_SCALING : 1);
this.boundingBox.getCenter(position);
if (side === "back") {
const { min, max } = boundingBox;
[min.y, min.z] = [min.z, min.y];
[max.y, max.z] = [max.z, max.y];
[size.y, size.z] = [size.z, size.y];
rotation.x = Math.PI / 2;
rotation.y = Math.PI;
} else {
rotation.x = 0;
rotation.y = 0;
}
if (this.isAnimated) {
const minY = boundingBox.min.y;
const maxY = boundingBox.max.y;
size.y = this.maxDimension;
boundingBox.expandByVector(size.subScalar(this.maxDimension).multiplyScalar(-0.5));
boundingBox.min.y = minY;
boundingBox.max.y = maxY;
size.set(this.maxDimension, maxY - minY, this.maxDimension);
}
if (side === "bottom") {
position.y = boundingBox.min.y;
} else {
position.z = boundingBox.min.y;
}
this.setSoftness(softness);
}
setSoftness(softness) {
this.softness = softness;
const { size, camera } = this;
const scaleY = this.isAnimated ? ANIMATION_SCALING : 1;
const resolution = scaleY * Math.pow(2, LOG_MAX_RESOLUTION - softness * (LOG_MAX_RESOLUTION - LOG_MIN_RESOLUTION));
this.setMapSize(resolution);
const softFar = size.y / 2;
const hardFar = size.y * scaleY;
camera.near = 0;
camera.far = lerp(hardFar, softFar, softness);
this.depthMaterial.opacity = 1 / softness;
camera.updateProjectionMatrix();
this.setIntensity(this.intensity);
this.setOffset(0);
}
setMapSize(maxMapSize) {
const { size } = this;
if (this.isAnimated) {
maxMapSize *= ANIMATION_SCALING;
}
const baseWidth = Math.floor(size.x > size.z ? maxMapSize : maxMapSize * size.x / size.z);
const baseHeight = Math.floor(size.x > size.z ? maxMapSize * size.z / size.x : maxMapSize);
const TAP_WIDTH = 10;
const width = TAP_WIDTH + baseWidth;
const height = TAP_WIDTH + baseHeight;
if (this.renderTarget != null && (this.renderTarget.width !== width || this.renderTarget.height !== height)) {
this.renderTarget.dispose();
this.renderTarget = null;
this.renderTargetBlur.dispose();
this.renderTargetBlur = null;
}
if (this.renderTarget == null) {
const params = { format: RGBAFormat };
this.renderTarget = new WebGLRenderTarget(width, height, params);
this.renderTargetBlur = new WebGLRenderTarget(width, height, params);
this.floor.material.map = this.renderTarget.texture;
}
this.camera.scale.set(size.x * (1 + TAP_WIDTH / baseWidth), size.z * (1 + TAP_WIDTH / baseHeight), 1);
this.needsUpdate = true;
}
setIntensity(intensity) {
this.intensity = intensity;
if (intensity > 0) {
this.visible = true;
this.floor.visible = true;
this.floor.material.opacity = intensity * lerp(DEFAULT_HARD_INTENSITY, 1, this.softness * this.softness);
} else {
this.visible = false;
this.floor.visible = false;
}
}
getIntensity() {
return this.intensity;
}
setOffset(offset) {
this.floor.position.z = -offset + 1e-3 * this.maxDimension;
}
render(renderer, scene) {
scene.overrideMaterial = this.depthMaterial;
const initialClearAlpha = renderer.getClearAlpha();
renderer.setClearAlpha(0);
this.floor.visible = false;
const xrEnabled = renderer.xr.enabled;
renderer.xr.enabled = false;
const oldRenderTarget = renderer.getRenderTarget();
renderer.setRenderTarget(this.renderTarget);
renderer.render(scene, this.camera);
scene.overrideMaterial = null;
this.floor.visible = true;
this.blurShadow(renderer);
renderer.xr.enabled = xrEnabled;
renderer.setRenderTarget(oldRenderTarget);
renderer.setClearAlpha(initialClearAlpha);
}
blurShadow(renderer) {
const { camera, horizontalBlurMaterial, verticalBlurMaterial, renderTarget, renderTargetBlur, blurPlane } = this;
blurPlane.visible = true;
blurPlane.material = horizontalBlurMaterial;
horizontalBlurMaterial.uniforms.h.value = 1 / this.renderTarget.width;
horizontalBlurMaterial.uniforms.tDiffuse.value = this.renderTarget.texture;
renderer.setRenderTarget(renderTargetBlur);
renderer.render(blurPlane, camera);
blurPlane.material = verticalBlurMaterial;
verticalBlurMaterial.uniforms.v.value = 1 / this.renderTarget.height;
verticalBlurMaterial.uniforms.tDiffuse.value = this.renderTargetBlur.texture;
renderer.setRenderTarget(renderTarget);
renderer.render(blurPlane, camera);
blurPlane.visible = false;
}
dispose() {
if (this.renderTarget != null) {
this.renderTarget.dispose();
}
if (this.renderTargetBlur != null) {
this.renderTargetBlur.dispose();
}
this.depthMaterial.dispose();
this.horizontalBlurMaterial.dispose();
this.verticalBlurMaterial.dispose();
this.floor.material.dispose();
this.floor.geometry.dispose();
this.blurPlane.geometry.dispose();
this.removeFromParent();
}
}
export { Shadow };