UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

139 lines (120 loc) 4.47 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. */ function OutlineEffect(graphicsDevice, thickness) { pc.PostEffect.call(this, graphicsDevice); var fshader = [ `#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);', '}' ].join('\n'); 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); } OutlineEffect.prototype = Object.create(pc.PostEffect.prototype); OutlineEffect.prototype.constructor = OutlineEffect; Object.assign(OutlineEffect.prototype, { render: function (inputTarget, outputTarget, rect) { var device = this.device; var 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); };