playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
136 lines (116 loc) • 4.38 kB
JavaScript
// --------------- 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);
};