three-stdlib
Version:
stand-alone library of threejs examples
278 lines (277 loc) • 11.4 kB
JavaScript
import { Color, ShaderMaterial, UniformsUtils, UniformsLib, BackSide } from "three";
import { version } from "../_polyfill/constants.js";
class OutlineEffect {
constructor(renderer, parameters = {}) {
this.enabled = true;
const defaultThickness = parameters.defaultThickness !== void 0 ? parameters.defaultThickness : 3e-3;
const defaultColor = new Color().fromArray(
parameters.defaultColor !== void 0 ? parameters.defaultColor : [0, 0, 0]
);
const defaultAlpha = parameters.defaultAlpha !== void 0 ? parameters.defaultAlpha : 1;
const defaultKeepAlive = parameters.defaultKeepAlive !== void 0 ? parameters.defaultKeepAlive : false;
const cache = {};
const removeThresholdCount = 60;
const originalMaterials = {};
const originalOnBeforeRenders = {};
const uniformsOutline = {
outlineThickness: { value: defaultThickness },
outlineColor: { value: defaultColor },
outlineAlpha: { value: defaultAlpha }
};
const vertexShader = [
"#include <common>",
"#include <uv_pars_vertex>",
"#include <displacementmap_pars_vertex>",
"#include <fog_pars_vertex>",
"#include <morphtarget_pars_vertex>",
"#include <skinning_pars_vertex>",
"#include <logdepthbuf_pars_vertex>",
"#include <clipping_planes_pars_vertex>",
"uniform float outlineThickness;",
"vec4 calculateOutline( vec4 pos, vec3 normal, vec4 skinned ) {",
" float thickness = outlineThickness;",
" const float ratio = 1.0;",
// TODO: support outline thickness ratio for each vertex
" vec4 pos2 = projectionMatrix * modelViewMatrix * vec4( skinned.xyz + normal, 1.0 );",
// NOTE: subtract pos2 from pos because BackSide objectNormal is negative
" vec4 norm = normalize( pos - pos2 );",
" return pos + norm * thickness * pos.w * ratio;",
"}",
"void main() {",
" #include <uv_vertex>",
" #include <beginnormal_vertex>",
" #include <morphnormal_vertex>",
" #include <skinbase_vertex>",
" #include <skinnormal_vertex>",
" #include <begin_vertex>",
" #include <morphtarget_vertex>",
" #include <skinning_vertex>",
" #include <displacementmap_vertex>",
" #include <project_vertex>",
" vec3 outlineNormal = - objectNormal;",
// the outline material is always rendered with BackSide
" gl_Position = calculateOutline( gl_Position, outlineNormal, vec4( transformed, 1.0 ) );",
" #include <logdepthbuf_vertex>",
" #include <clipping_planes_vertex>",
" #include <fog_vertex>",
"}"
].join("\n");
const fragmentShader = [
"#include <common>",
"#include <fog_pars_fragment>",
"#include <logdepthbuf_pars_fragment>",
"#include <clipping_planes_pars_fragment>",
"uniform vec3 outlineColor;",
"uniform float outlineAlpha;",
"void main() {",
" #include <clipping_planes_fragment>",
" #include <logdepthbuf_fragment>",
" gl_FragColor = vec4( outlineColor, outlineAlpha );",
" #include <tonemapping_fragment>",
` #include <${version >= 154 ? "colorspace_fragment" : "encodings_fragment"}>`,
" #include <fog_fragment>",
" #include <premultiplied_alpha_fragment>",
"}"
].join("\n");
function createMaterial() {
return new ShaderMaterial({
type: "OutlineEffect",
uniforms: UniformsUtils.merge([UniformsLib["fog"], UniformsLib["displacementmap"], uniformsOutline]),
vertexShader,
fragmentShader,
side: BackSide
});
}
function getOutlineMaterialFromCache(originalMaterial) {
let data = cache[originalMaterial.uuid];
if (data === void 0) {
data = {
material: createMaterial(),
used: true,
keepAlive: defaultKeepAlive,
count: 0
};
cache[originalMaterial.uuid] = data;
}
data.used = true;
return data.material;
}
function getOutlineMaterial(originalMaterial) {
const outlineMaterial = getOutlineMaterialFromCache(originalMaterial);
originalMaterials[outlineMaterial.uuid] = originalMaterial;
updateOutlineMaterial(outlineMaterial, originalMaterial);
return outlineMaterial;
}
function isCompatible(object) {
const geometry = object.geometry;
const hasNormals = geometry !== void 0 && geometry.attributes.normal !== void 0;
return object.isMesh === true && object.material !== void 0 && hasNormals === true;
}
function setOutlineMaterial(object) {
if (isCompatible(object) === false)
return;
if (Array.isArray(object.material)) {
for (let i = 0, il = object.material.length; i < il; i++) {
object.material[i] = getOutlineMaterial(object.material[i]);
}
} else {
object.material = getOutlineMaterial(object.material);
}
originalOnBeforeRenders[object.uuid] = object.onBeforeRender;
object.onBeforeRender = onBeforeRender;
}
function restoreOriginalMaterial(object) {
if (isCompatible(object) === false)
return;
if (Array.isArray(object.material)) {
for (let i = 0, il = object.material.length; i < il; i++) {
object.material[i] = originalMaterials[object.material[i].uuid];
}
} else {
object.material = originalMaterials[object.material.uuid];
}
object.onBeforeRender = originalOnBeforeRenders[object.uuid];
}
function onBeforeRender(renderer2, scene, camera, geometry, material) {
const originalMaterial = originalMaterials[material.uuid];
if (originalMaterial === void 0)
return;
updateUniforms(material, originalMaterial);
}
function updateUniforms(material, originalMaterial) {
const outlineParameters = originalMaterial.userData.outlineParameters;
material.uniforms.outlineAlpha.value = originalMaterial.opacity;
if (outlineParameters !== void 0) {
if (outlineParameters.thickness !== void 0)
material.uniforms.outlineThickness.value = outlineParameters.thickness;
if (outlineParameters.color !== void 0)
material.uniforms.outlineColor.value.fromArray(outlineParameters.color);
if (outlineParameters.alpha !== void 0)
material.uniforms.outlineAlpha.value = outlineParameters.alpha;
}
if (originalMaterial.displacementMap) {
material.uniforms.displacementMap.value = originalMaterial.displacementMap;
material.uniforms.displacementScale.value = originalMaterial.displacementScale;
material.uniforms.displacementBias.value = originalMaterial.displacementBias;
}
}
function updateOutlineMaterial(material, originalMaterial) {
if (material.name === "invisible")
return;
const outlineParameters = originalMaterial.userData.outlineParameters;
material.fog = originalMaterial.fog;
material.toneMapped = originalMaterial.toneMapped;
material.premultipliedAlpha = originalMaterial.premultipliedAlpha;
material.displacementMap = originalMaterial.displacementMap;
if (outlineParameters !== void 0) {
if (originalMaterial.visible === false) {
material.visible = false;
} else {
material.visible = outlineParameters.visible !== void 0 ? outlineParameters.visible : true;
}
material.transparent = outlineParameters.alpha !== void 0 && outlineParameters.alpha < 1 ? true : originalMaterial.transparent;
if (outlineParameters.keepAlive !== void 0)
cache[originalMaterial.uuid].keepAlive = outlineParameters.keepAlive;
} else {
material.transparent = originalMaterial.transparent;
material.visible = originalMaterial.visible;
}
if (originalMaterial.wireframe === true || originalMaterial.depthTest === false)
material.visible = false;
if (originalMaterial.clippingPlanes) {
material.clipping = true;
material.clippingPlanes = originalMaterial.clippingPlanes;
material.clipIntersection = originalMaterial.clipIntersection;
material.clipShadows = originalMaterial.clipShadows;
}
material.version = originalMaterial.version;
}
function cleanupCache() {
let keys;
keys = Object.keys(originalMaterials);
for (let i = 0, il = keys.length; i < il; i++) {
originalMaterials[keys[i]] = void 0;
}
keys = Object.keys(originalOnBeforeRenders);
for (let i = 0, il = keys.length; i < il; i++) {
originalOnBeforeRenders[keys[i]] = void 0;
}
keys = Object.keys(cache);
for (let i = 0, il = keys.length; i < il; i++) {
const key = keys[i];
if (cache[key].used === false) {
cache[key].count++;
if (cache[key].keepAlive === false && cache[key].count > removeThresholdCount) {
delete cache[key];
}
} else {
cache[key].used = false;
cache[key].count = 0;
}
}
}
this.render = function(scene, camera) {
if (this.enabled === false) {
renderer.render(scene, camera);
return;
}
const currentAutoClear = renderer.autoClear;
renderer.autoClear = this.autoClear;
renderer.render(scene, camera);
renderer.autoClear = currentAutoClear;
this.renderOutline(scene, camera);
};
this.renderOutline = function(scene, camera) {
const currentAutoClear = renderer.autoClear;
const currentSceneAutoUpdate = scene.matrixWorldAutoUpdate;
const currentSceneBackground = scene.background;
const currentShadowMapEnabled = renderer.shadowMap.enabled;
scene.matrixWorldAutoUpdate = false;
scene.background = null;
renderer.autoClear = false;
renderer.shadowMap.enabled = false;
scene.traverse(setOutlineMaterial);
renderer.render(scene, camera);
scene.traverse(restoreOriginalMaterial);
cleanupCache();
scene.matrixWorldAutoUpdate = currentSceneAutoUpdate;
scene.background = currentSceneBackground;
renderer.autoClear = currentAutoClear;
renderer.shadowMap.enabled = currentShadowMapEnabled;
};
this.autoClear = renderer.autoClear;
this.domElement = renderer.domElement;
this.shadowMap = renderer.shadowMap;
this.clear = function(color, depth, stencil) {
renderer.clear(color, depth, stencil);
};
this.getPixelRatio = function() {
return renderer.getPixelRatio();
};
this.setPixelRatio = function(value) {
renderer.setPixelRatio(value);
};
this.getSize = function(target) {
return renderer.getSize(target);
};
this.setSize = function(width, height, updateStyle) {
renderer.setSize(width, height, updateStyle);
};
this.setViewport = function(x, y, width, height) {
renderer.setViewport(x, y, width, height);
};
this.setScissor = function(x, y, width, height) {
renderer.setScissor(x, y, width, height);
};
this.setScissorTest = function(boolean) {
renderer.setScissorTest(boolean);
};
this.setRenderTarget = function(renderTarget) {
renderer.setRenderTarget(renderTarget);
};
}
}
export {
OutlineEffect
};
//# sourceMappingURL=OutlineEffect.js.map