playcanvas
Version:
PlayCanvas WebGL game engine
232 lines (229 loc) • 11 kB
JavaScript
import { Color } from '../../core/math/color.js';
import { Entity } from '../../framework/entity.js';
import { BlendState } from '../../platform/graphics/blend-state.js';
import { CULLFACE_NONE, FILTER_LINEAR, FILTER_LINEAR_MIPMAP_LINEAR, ADDRESS_CLAMP_TO_EDGE, PIXELFORMAT_SRGBA8, BLENDEQUATION_ADD, BLENDMODE_SRC_ALPHA, BLENDMODE_ONE_MINUS_SRC_ALPHA } from '../../platform/graphics/constants.js';
import { DepthState } from '../../platform/graphics/depth-state.js';
import { RenderTarget } from '../../platform/graphics/render-target.js';
import { Texture } from '../../platform/graphics/texture.js';
import { drawQuadWithShader } from '../../scene/graphics/quad-render-utils.js';
import { QuadRender } from '../../scene/graphics/quad-render.js';
import { StandardMaterialOptions } from '../../scene/materials/standard-material-options.js';
import { StandardMaterial } from '../../scene/materials/standard-material.js';
import { shaderChunks } from '../../scene/shader-lib/chunks/chunks.js';
import { createShaderFromCode } from '../../scene/shader-lib/utils.js';
var shaderOutlineExtendPS = "\n varying vec2 vUv0;\n uniform vec2 uOffset;\n uniform float uSrcMultiplier;\n uniform sampler2D source;\n void main(void)\n {\n vec4 pixel;\n vec4 texel = texture2D(source, vUv0);\n vec4 firstTexel = texel;\n float diff = texel.a * uSrcMultiplier;\n pixel = texture2D(source, vUv0 + uOffset * -2.0);\n texel = max(texel, pixel);\n diff = max(diff, length(firstTexel.rgb - pixel.rgb));\n pixel = texture2D(source, vUv0 + uOffset * -1.0);\n texel = max(texel, pixel);\n diff = max(diff, length(firstTexel.rgb - pixel.rgb));\n pixel = texture2D(source, vUv0 + uOffset * 1.0);\n texel = max(texel, pixel);\n diff = max(diff, length(firstTexel.rgb - pixel.rgb));\n pixel = texture2D(source, vUv0 + uOffset * 2.0);\n texel = max(texel, pixel);\n diff = max(diff, length(firstTexel.rgb - pixel.rgb));\n gl_FragColor = vec4(texel.rgb, min(diff, 1.0));\n }\n";
var _tempFloatArray = new Float32Array(2);
var _tempColor = new Color();
class OutlineRenderer {
destroy() {
var _this_quadRenderer;
this.whiteTex.destroy();
this.whiteTex = null;
this.outlineCameraEntity.destroy();
this.outlineCameraEntity = null;
this.rt.destroyTextureBuffers();
this.rt.destroy();
this.rt = null;
this.tempRt.destroyTextureBuffers();
this.tempRt.destroy();
this.tempRt = null;
this.app.scene.off('postrender', this.postRender);
(_this_quadRenderer = this.quadRenderer) == null ? void 0 : _this_quadRenderer.destroy();
this.quadRenderer = null;
}
getMeshInstances(entity, recursive) {
var meshInstances = [];
var renders = recursive ? entity.findComponents('render') : entity.render ? [
entity.render
] : [];
renders.forEach((render)=>{
if (render.meshInstances) {
meshInstances.push(...render.meshInstances);
}
});
var models = recursive ? entity.findComponents('model') : entity.model ? [
entity.model
] : [];
models.forEach((model)=>{
if (model.meshInstances) {
meshInstances.push(...model.meshInstances);
}
});
return meshInstances;
}
addEntity(entity, color, recursive) {
if (recursive === void 0) recursive = true;
var meshInstances = this.getMeshInstances(entity, recursive);
meshInstances.forEach((meshInstance)=>{
if (meshInstance.material instanceof StandardMaterial) {
var outlineShaderPass = this.outlineShaderPass;
meshInstance.material.onUpdateShader = (options)=>{
if (options.pass === outlineShaderPass) {
var opts = new StandardMaterialOptions();
opts.defines = options.defines;
opts.opacityMap = options.opacityMap;
opts.opacityMapUv = options.opacityMapUv;
opts.opacityMapChannel = options.opacityMapChannel;
opts.opacityMapTransform = options.opacityMapTransform;
opts.opacityVertexColor = options.opacityVertexColor;
opts.opacityVertexColorChannel = options.opacityVertexColorChannel;
opts.litOptions.vertexColors = options.litOptions.vertexColors;
opts.litOptions.alphaTest = options.litOptions.alphaTest;
opts.litOptions.skin = options.litOptions.skin;
opts.litOptions.batch = options.litOptions.batch;
opts.litOptions.useInstancing = options.litOptions.useInstancing;
opts.litOptions.useMorphPosition = options.litOptions.useMorphPosition;
opts.litOptions.useMorphNormal = options.litOptions.useMorphNormal;
opts.litOptions.useMorphTextureBasedInt = options.litOptions.useMorphTextureBasedInt;
return opts;
}
return options;
};
_tempColor.linear(color);
var colArray = new Float32Array([
_tempColor.r,
_tempColor.g,
_tempColor.b
]);
meshInstance.setParameter('material_emissive', colArray, 1 << this.outlineShaderPass);
meshInstance.setParameter('texture_emissiveMap', this.whiteTex, 1 << this.outlineShaderPass);
}
});
this.renderingLayer.addMeshInstances(meshInstances, true);
}
removeEntity(entity, recursive) {
if (recursive === void 0) recursive = true;
var meshInstances = this.getMeshInstances(entity, recursive);
this.renderingLayer.removeMeshInstances(meshInstances);
meshInstances.forEach((meshInstance)=>{
if (meshInstance.material instanceof StandardMaterial) {
meshInstance.material.onUpdateShader = null;
meshInstance.deleteParameter('material_emissive');
}
});
}
removeAllEntities() {
this.renderingLayer.clearMeshInstances();
}
blendOutlines() {
var device = this.app.graphicsDevice;
device.scope.resolve('source').setValue(this.rt.colorBuffer);
device.setDepthState(DepthState.NODEPTH);
device.setCullMode(CULLFACE_NONE);
device.setBlendState(this.blendState);
this.quadRenderer.render();
}
onPostRender() {
var device = this.app.graphicsDevice;
var uOffset = device.scope.resolve('uOffset');
var uColorBuffer = device.scope.resolve('source');
var uSrcMultiplier = device.scope.resolve('uSrcMultiplier');
var { rt, tempRt, shaderExtend } = this;
var { width, height } = rt;
_tempFloatArray[0] = 1.0 / width / 2.0;
_tempFloatArray[1] = 0;
uOffset.setValue(_tempFloatArray);
uColorBuffer.setValue(rt.colorBuffer);
uSrcMultiplier.setValue(0.0);
drawQuadWithShader(device, tempRt, shaderExtend);
_tempFloatArray[0] = 0;
_tempFloatArray[1] = 1.0 / height / 2.0;
uOffset.setValue(_tempFloatArray);
uColorBuffer.setValue(tempRt.colorBuffer);
uSrcMultiplier.setValue(1.0);
drawQuadWithShader(device, rt, shaderExtend);
}
createRenderTarget(name, width, height, depth) {
var texture = new Texture(this.app.graphicsDevice, {
name: name,
width: width,
height: height,
format: PIXELFORMAT_SRGBA8,
mipmaps: false,
addressU: ADDRESS_CLAMP_TO_EDGE,
addressV: ADDRESS_CLAMP_TO_EDGE,
minFilter: FILTER_LINEAR_MIPMAP_LINEAR,
magFilter: FILTER_LINEAR
});
return new RenderTarget({
colorBuffer: texture,
depth: depth,
flipY: this.app.graphicsDevice.isWebGPU
});
}
updateRenderTarget(sceneCamera) {
var _sceneCamera_renderTarget, _sceneCamera_renderTarget1;
var _sceneCamera_renderTarget_width;
var width = (_sceneCamera_renderTarget_width = (_sceneCamera_renderTarget = sceneCamera.renderTarget) == null ? void 0 : _sceneCamera_renderTarget.width) != null ? _sceneCamera_renderTarget_width : this.app.graphicsDevice.width;
var _sceneCamera_renderTarget_height;
var height = (_sceneCamera_renderTarget_height = (_sceneCamera_renderTarget1 = sceneCamera.renderTarget) == null ? void 0 : _sceneCamera_renderTarget1.height) != null ? _sceneCamera_renderTarget_height : this.app.graphicsDevice.height;
var outlineCamera = this.outlineCameraEntity.camera;
if (!outlineCamera.renderTarget || outlineCamera.renderTarget.width !== width || outlineCamera.renderTarget.height !== height) {
this.rt.resize(width, height);
this.tempRt.resize(width, height);
}
}
frameUpdate(sceneCameraEntity, blendLayer, blendLayerTransparent) {
var sceneCamera = sceneCameraEntity.camera;
this.updateRenderTarget(sceneCamera);
var evt = this.app.scene.on('prerender:layer', (cameraComponent, layer, transparent)=>{
if (sceneCamera === cameraComponent && transparent === blendLayerTransparent && layer === blendLayer) {
this.blendOutlines();
evt.off();
}
});
this.outlineCameraEntity.setLocalPosition(sceneCameraEntity.getPosition());
this.outlineCameraEntity.setLocalRotation(sceneCameraEntity.getRotation());
var outlineCamera = this.outlineCameraEntity.camera;
outlineCamera.projection = sceneCamera.projection;
outlineCamera.horizontalFov = sceneCamera.horizontalFov;
outlineCamera.fov = sceneCamera.fov;
outlineCamera.orthoHeight = sceneCamera.orthoHeight;
outlineCamera.nearClip = sceneCamera.nearClip;
outlineCamera.farClip = sceneCamera.farClip;
}
constructor(app, renderingLayer, priority = -1){
this.app = app;
this.renderingLayer = renderingLayer != null ? renderingLayer : app.scene.layers.getLayerByName('Immediate');
this.rt = this.createRenderTarget('OutlineTexture', 1, 1, true);
this.outlineCameraEntity = new Entity('OutlineCamera');
this.outlineCameraEntity.addComponent('camera', {
layers: [
this.renderingLayer.id
],
priority: priority,
clearColor: new Color(0, 0, 0, 0),
renderTarget: this.rt
});
this.outlineShaderPass = this.outlineCameraEntity.camera.setShaderPass('OutlineShaderPass');
this.postRender = (cameraComponent)=>{
if (this.outlineCameraEntity.camera === cameraComponent) {
this.onPostRender();
}
};
app.scene.on('postrender', this.postRender);
this.app.root.addChild(this.outlineCameraEntity);
this.tempRt = this.createRenderTarget('OutlineTempTexture', 1, 1, false);
this.blendState = new BlendState(true, BLENDEQUATION_ADD, BLENDMODE_SRC_ALPHA, BLENDMODE_ONE_MINUS_SRC_ALPHA);
var device = this.app.graphicsDevice;
this.shaderExtend = createShaderFromCode(device, shaderChunks.fullscreenQuadVS, shaderOutlineExtendPS, 'OutlineExtendShader');
this.shaderBlend = createShaderFromCode(device, shaderChunks.fullscreenQuadVS, shaderChunks.outputTex2DPS, 'OutlineBlendShader');
this.quadRenderer = new QuadRender(this.shaderBlend);
this.whiteTex = new Texture(device, {
name: 'OutlineWhiteTexture',
width: 1,
height: 1,
format: PIXELFORMAT_SRGBA8,
mipmaps: false
});
var pixels = this.whiteTex.lock();
pixels.set(new Uint8Array([
255,
255,
255,
255
]));
this.whiteTex.unlock();
}
}
export { OutlineRenderer };