UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

136 lines (116 loc) 4.38 kB
// --------------- POST EFFECT DEFINITION --------------- // /** * @class * @name OutlineEffect * @classdesc Applies an outline effect on input render target. * @description Creates new instance of the post effect. * @augments PostEffect * @param {GraphicsDevice} graphicsDevice - The graphics device of the application. * @param {number} thickness - The thickness for the outline effect passed here to be used as a constant in shader. * @property {Texture} texture The outline texture to use. * @property {Color} color The outline color. */ class OutlineEffect extends pc.PostEffect { constructor(graphicsDevice, thickness) { super(graphicsDevice); const fshader = /* glsl */` #define THICKNESS ${thickness ? thickness.toFixed(0) : 1} uniform float uWidth; uniform float uHeight; uniform vec4 uOutlineCol; uniform sampler2D uColorBuffer; uniform sampler2D uOutlineTex; varying vec2 vUv0; void main(void) { vec4 texel1 = texture2D(uColorBuffer, vUv0); float sample0 = texture2D(uOutlineTex, vUv0).a; float outline = 0.0; if (sample0==0.0) { for (int x=-THICKNESS;x<=THICKNESS;x++) { for (int y=-THICKNESS;y<=THICKNESS;y++) { float tex=texture2DLod(uOutlineTex, vUv0 + vec2(float(x)/uWidth, float(y)/uHeight), 0.0).a; if (tex>0.0) { outline=1.0; } } } } gl_FragColor = mix(texel1, uOutlineCol, outline * uOutlineCol.a); } `; this.shader = pc.ShaderUtils.createShader(graphicsDevice, { uniqueName: 'OutlineShader', attributes: { aPosition: pc.SEMANTIC_POSITION }, vertexGLSL: pc.PostEffect.quadVertexShader, fragmentGLSL: fshader }); // Uniforms this.color = new pc.Color(1, 1, 1, 1); this.texture = new pc.Texture(graphicsDevice); this.texture.name = 'pe-outline'; this._colorData = new Float32Array(4); } render(inputTarget, outputTarget, rect) { const device = this.device; const scope = device.scope; this._colorData[0] = this.color.r; this._colorData[1] = this.color.g; this._colorData[2] = this.color.b; this._colorData[3] = this.color.a; scope.resolve('uWidth').setValue(inputTarget.width); scope.resolve('uHeight').setValue(inputTarget.height); scope.resolve('uOutlineCol').setValue(this._colorData); scope.resolve('uColorBuffer').setValue(inputTarget.colorBuffer); scope.resolve('uOutlineTex').setValue(this.texture); this.drawQuad(outputTarget, this.shader, rect); } } // ----------------- SCRIPT DEFINITION ------------------ // var Outline = pc.createScript('outline'); Outline.attributes.add('color', { type: 'rgb', default: [0.5, 0.5, 0.5], title: 'Color' }); Outline.attributes.add('thickness', { type: 'number', default: 1.0, min: 1.0, max: 10, precision: 0, title: 'Thickness', description: 'Note: Changing the thickness requires reloading the effect.' }); Outline.attributes.add('texture', { type: 'asset', assetType: 'texture', title: 'Texture' }); Outline.prototype.initialize = function () { this.effect = new OutlineEffect(this.app.graphicsDevice, this.thickness); this.effect.color = this.color; this.effect.texture = this.texture.resource; var queue = this.entity.camera.postEffects; queue.addEffect(this.effect); this.on('state', function (enabled) { if (enabled) { queue.addEffect(this.effect); } else { queue.removeEffect(this.effect); } }); this.on('destroy', function () { queue.removeEffect(this.effect); }); this.on('attr:color', function (value) { this.effect.color = value; }, this); this.on('attr:texture', function (value) { this.effect.texture = value ? value.resource : null; }, this); };