├── .gitignore ├── LICENSE ├── Readme.md ├── build ├── aframe-outline.js └── aframe-outline.min.js ├── index.html ├── index.js ├── package.json └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Takahiro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # A-Frame Outline component 2 | 3 | aframe-outline is two-pass Outline effect component for A-Frame. 4 | 5 | ![screenshot](./screenshot.png "screenshot") 6 | 7 | ## Demo 8 | 9 | [Demo](https://rawgit.com/takahirox/aframe-outline/v1.1.1/index.html) 10 | 11 | ## Properties 12 | 13 | ### aframe-outline 14 | 15 | | Properties | type | Default Value | Description | 16 | | ----------- | ------ | ------------- | ----------- | 17 | | thickness | number | 0.003 | Outline thickness. | 18 | | color | color | '#000' | Outline color. | 19 | | alpha | number | 1.0 | Outline alpha. | 20 | 21 | ## Browser 22 | 23 | ### How to use 24 | 25 | To apply outline effect in a scene, add `outline` attribute in `` like ``. 26 | 27 | ```html 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ``` 45 | 46 | ## NPM 47 | 48 | ### How to install 49 | 50 | ``` 51 | $ npm install aframe-outline 52 | ``` 53 | 54 | ### How to build 55 | 56 | ``` 57 | $ npm install 58 | $ npm run all 59 | ``` 60 | 61 | ### How to load 62 | 63 | ``` 64 | require('aframe'); 65 | require('aframe-outline'); 66 | ``` 67 | -------------------------------------------------------------------------------- /build/aframe-outline.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | /******/ 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | /******/ 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) { 10 | /******/ return installedModules[moduleId].exports; 11 | /******/ } 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ i: moduleId, 15 | /******/ l: false, 16 | /******/ exports: {} 17 | /******/ }; 18 | /******/ 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | /******/ 22 | /******/ // Flag the module as loaded 23 | /******/ module.l = true; 24 | /******/ 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | /******/ 29 | /******/ 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | /******/ 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | /******/ 36 | /******/ // define getter function for harmony exports 37 | /******/ __webpack_require__.d = function(exports, name, getter) { 38 | /******/ if(!__webpack_require__.o(exports, name)) { 39 | /******/ Object.defineProperty(exports, name, { 40 | /******/ configurable: false, 41 | /******/ enumerable: true, 42 | /******/ get: getter 43 | /******/ }); 44 | /******/ } 45 | /******/ }; 46 | /******/ 47 | /******/ // getDefaultExport function for compatibility with non-harmony modules 48 | /******/ __webpack_require__.n = function(module) { 49 | /******/ var getter = module && module.__esModule ? 50 | /******/ function getDefault() { return module['default']; } : 51 | /******/ function getModuleExports() { return module; }; 52 | /******/ __webpack_require__.d(getter, 'a', getter); 53 | /******/ return getter; 54 | /******/ }; 55 | /******/ 56 | /******/ // Object.prototype.hasOwnProperty.call 57 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 58 | /******/ 59 | /******/ // __webpack_public_path__ 60 | /******/ __webpack_require__.p = ""; 61 | /******/ 62 | /******/ // Load entry module and return exports 63 | /******/ return __webpack_require__(__webpack_require__.s = 0); 64 | /******/ }) 65 | /************************************************************************/ 66 | /******/ ([ 67 | /* 0 */ 68 | /***/ (function(module, exports, __webpack_require__) { 69 | 70 | /** 71 | * @author Takahiro / https://github.com/takahirox 72 | */ 73 | 74 | if (typeof AFRAME === 'undefined') { 75 | throw new Error('Component attempted to register before' + 76 | 'AFRAME was available.'); 77 | } 78 | 79 | __webpack_require__(1); 80 | 81 | AFRAME.registerComponent('outline', { 82 | schema: { 83 | thickness: {type:'number', default: 0.003}, 84 | color: {type:'color', default: '#000'}, 85 | alpha: {type:'number', default: 1.0} 86 | }, 87 | 88 | init: function () { 89 | this.effect = null; 90 | }, 91 | 92 | update: function () { 93 | this.setupOutlineEffect(); 94 | }, 95 | 96 | remove: function () { 97 | this.disableOutlineEffect(); 98 | }, 99 | 100 | setupOutlineEffect: function () { 101 | if (this.effect !== null) { 102 | this.effect.enabled = true; 103 | return; 104 | } 105 | 106 | var data = this.data; 107 | var el = this.el; 108 | var sceneEl = el.sceneEl; 109 | var renderer = sceneEl.renderer; 110 | var effect = sceneEl.effect; 111 | 112 | if (renderer === undefined || effect === undefined) { return; } 113 | 114 | var outlineEffect = new THREE.OutlineEffect(renderer, { 115 | defaultThickness: data.thickness, 116 | defaultColor: new THREE.Color(data.color), 117 | defaultAlpha: data.alpha 118 | }); 119 | 120 | // override scene effect 121 | // this's very sensitive to sceneEl impl 122 | var keys = Object.keys(renderer); 123 | for (var i = 0, il = keys.length; i < il; i++) { 124 | var key = keys[i]; 125 | if (outlineEffect[key] === undefined) { 126 | outlineEffect[key] = typeof renderer[key] === 'function' 127 | ? renderer[key].bind(renderer) 128 | : renderer[key]; 129 | } 130 | } 131 | this.effect = outlineEffect; 132 | sceneEl.effect = new THREE.VREffect(outlineEffect); 133 | }, 134 | 135 | disableOutlineEffect: function () { 136 | if (this.effect === null) { return; } 137 | 138 | this.effect.enabled = false; 139 | } 140 | }); 141 | 142 | 143 | /***/ }), 144 | /* 1 */ 145 | /***/ (function(module, exports) { 146 | 147 | /** 148 | * @author takahirox / http://github.com/takahirox/ 149 | * 150 | * Reference: https://en.wikipedia.org/wiki/Cel_shading 151 | * 152 | * // How to set default outline parameters 153 | * new THREE.OutlineEffect( renderer, { 154 | * defaultThickNess: 0.01, 155 | * defaultColor: new THREE.Color( 0x888888 ), 156 | * defaultAlpha: 0.8, 157 | * defaultKeepAlive: true // keeps outline material in cache even if material is removed from scene 158 | * } ); 159 | * 160 | * // How to set outline parameters for each material 161 | * material.outlineParameters = { 162 | * thickNess: 0.01, 163 | * color: new THREE.Color( 0x888888 ), 164 | * alpha: 0.8, 165 | * visible: true, 166 | * keepAlive: true 167 | * }; 168 | * 169 | * TODO 170 | * - support shader material without objectNormal in its vertexShader 171 | */ 172 | 173 | THREE.OutlineEffect = function ( renderer, parameters ) { 174 | 175 | parameters = parameters || {}; 176 | 177 | this.enabled = true; 178 | 179 | var defaultThickness = parameters.defaultThickness !== undefined ? parameters.defaultThickness : 0.003; 180 | var defaultColor = parameters.defaultColor !== undefined ? parameters.defaultColor : new THREE.Color( 0x000000 ); 181 | var defaultAlpha = parameters.defaultAlpha !== undefined ? parameters.defaultAlpha : 1.0; 182 | var defaultKeepAlive = parameters.defaultKeepAlive !== undefined ? parameters.defaultKeepAlive : false; 183 | 184 | // object.material.uuid -> outlineMaterial or 185 | // object.material[ n ].uuid -> outlineMaterial 186 | // save at the outline material creation and release 187 | // if it's unused removeThresholdCount frames 188 | // unless keepAlive is true. 189 | var cache = {}; 190 | 191 | var removeThresholdCount = 60; 192 | 193 | // outlineMaterial.uuid -> object.material or 194 | // outlineMaterial.uuid -> object.material[ n ] 195 | // save before render and release after render. 196 | var originalMaterials = {}; 197 | 198 | // object.uuid -> originalOnBeforeRender 199 | // save before render and release after render. 200 | var originalOnBeforeRenders = {}; 201 | 202 | //this.cache = cache; // for debug 203 | 204 | // copied from WebGLPrograms and removed some materials 205 | var shaderIDs = { 206 | MeshBasicMaterial: 'basic', 207 | MeshLambertMaterial: 'lambert', 208 | MeshPhongMaterial: 'phong', 209 | MeshToonMaterial: 'phong', 210 | MeshStandardMaterial: 'physical', 211 | MeshPhysicalMaterial: 'physical' 212 | }; 213 | 214 | var uniformsChunk = { 215 | outlineThickness: { type: "f", value: defaultThickness }, 216 | outlineColor: { type: "c", value: defaultColor }, 217 | outlineAlpha: { type: "f", value: defaultAlpha } 218 | }; 219 | 220 | var vertexShaderChunk = [ 221 | 222 | "#include ", 223 | 224 | "uniform float outlineThickness;", 225 | 226 | "vec4 calculateOutline( vec4 pos, vec3 objectNormal, vec4 skinned ) {", 227 | 228 | " float thickness = outlineThickness;", 229 | " const float ratio = 1.0;", // TODO: support outline thickness ratio for each vertex 230 | " vec4 pos2 = projectionMatrix * modelViewMatrix * vec4( skinned.xyz + objectNormal, 1.0 );", 231 | // NOTE: subtract pos2 from pos because BackSide objectNormal is negative 232 | " vec4 norm = normalize( pos - pos2 );", 233 | " return pos + norm * thickness * pos.w * ratio;", 234 | 235 | "}" 236 | 237 | ].join( "\n" ); 238 | 239 | var vertexShaderChunk2 = [ 240 | 241 | "#if ! defined( LAMBERT ) && ! defined( PHONG ) && ! defined( TOON ) && ! defined( PHYSICAL )", 242 | " #ifndef USE_ENVMAP", 243 | " vec3 objectNormal = normalize( normal );", 244 | " #endif", 245 | "#endif", 246 | 247 | "#ifdef FLIP_SIDED", 248 | " objectNormal = -objectNormal;", 249 | "#endif", 250 | 251 | "#ifdef DECLARE_TRANSFORMED", 252 | " vec3 transformed = vec3( position );", 253 | "#endif", 254 | 255 | "gl_Position = calculateOutline( gl_Position, objectNormal, vec4( transformed, 1.0 ) );", 256 | 257 | "#include " 258 | 259 | ].join( "\n" ); 260 | 261 | var fragmentShader = [ 262 | 263 | "#include ", 264 | "#include ", 265 | 266 | "uniform vec3 outlineColor;", 267 | "uniform float outlineAlpha;", 268 | 269 | "void main() {", 270 | 271 | " gl_FragColor = vec4( outlineColor, outlineAlpha );", 272 | 273 | " #include ", 274 | 275 | "}" 276 | 277 | ].join( "\n" ); 278 | 279 | function createInvisibleMaterial() { 280 | 281 | return new THREE.ShaderMaterial( { name: 'invisible', visible: false } ); 282 | 283 | } 284 | 285 | function createMaterial( originalMaterial ) { 286 | 287 | var shaderID = shaderIDs[ originalMaterial.type ]; 288 | var originalUniforms, originalVertexShader; 289 | var outlineParameters = originalMaterial.outlineParameters; 290 | 291 | if ( shaderID !== undefined ) { 292 | 293 | var shader = THREE.ShaderLib[ shaderID ]; 294 | originalUniforms = shader.uniforms; 295 | originalVertexShader = shader.vertexShader; 296 | 297 | } else if ( originalMaterial.isRawShaderMaterial === true ) { 298 | 299 | originalUniforms = originalMaterial.uniforms; 300 | originalVertexShader = originalMaterial.vertexShader; 301 | 302 | if ( ! /attribute\s+vec3\s+position\s*;/.test( originalVertexShader ) || 303 | ! /attribute\s+vec3\s+normal\s*;/.test( originalVertexShader ) ) { 304 | 305 | console.warn( 'THREE.OutlineEffect requires both vec3 position and normal attributes in vertex shader, ' + 306 | 'does not draw outline for ' + originalMaterial.name + '(uuid:' + originalMaterial.uuid + ') material.' ); 307 | 308 | return createInvisibleMaterial(); 309 | 310 | } 311 | 312 | } else if ( originalMaterial.isShaderMaterial === true ) { 313 | 314 | originalUniforms = originalMaterial.uniforms; 315 | originalVertexShader = originalMaterial.vertexShader; 316 | 317 | } else { 318 | 319 | return createInvisibleMaterial(); 320 | 321 | } 322 | 323 | var uniforms = Object.assign( {}, originalUniforms, uniformsChunk ); 324 | 325 | var vertexShader = originalVertexShader 326 | // put vertexShaderChunk right before "void main() {...}" 327 | .replace( /void\s+main\s*\(\s*\)/, vertexShaderChunk + '\nvoid main()' ) 328 | // put vertexShaderChunk2 the end of "void main() {...}" 329 | // Note: here assums originalVertexShader ends with "}" of "void main() {...}" 330 | .replace( /\}\s*$/, vertexShaderChunk2 + '\n}' ) 331 | // remove any light related lines 332 | // Note: here is very sensitive to originalVertexShader 333 | // TODO: consider safer way 334 | .replace( /#include\s+<[\w_]*light[\w_]*>/g, '' ); 335 | 336 | var defines = {}; 337 | 338 | if ( ! /vec3\s+transformed\s*=/.test( originalVertexShader ) && 339 | ! /#include\s+/.test( originalVertexShader ) ) defines.DECLARE_TRANSFORMED = true; 340 | 341 | return new THREE.ShaderMaterial( { 342 | defines: defines, 343 | uniforms: uniforms, 344 | vertexShader: vertexShader, 345 | fragmentShader: fragmentShader, 346 | side: THREE.BackSide, 347 | //wireframe: true, 348 | skinning: false, 349 | morphTargets: false, 350 | morphNormals: false, 351 | fog: false 352 | } ); 353 | 354 | } 355 | 356 | function getOutlineMaterialFromCache( originalMaterial ) { 357 | 358 | var data = cache[ originalMaterial.uuid ]; 359 | 360 | if ( data === undefined ) { 361 | 362 | data = { 363 | material: createMaterial( originalMaterial ), 364 | used: true, 365 | keepAlive: defaultKeepAlive, 366 | count: 0 367 | }; 368 | 369 | cache[ originalMaterial.uuid ] = data; 370 | 371 | } 372 | 373 | data.used = true; 374 | 375 | return data.material; 376 | 377 | } 378 | 379 | function getOutlineMaterial( originalMaterial ) { 380 | 381 | var outlineMaterial = getOutlineMaterialFromCache( originalMaterial ); 382 | 383 | originalMaterials[ outlineMaterial.uuid ] = originalMaterial; 384 | 385 | updateOutlineMaterial( outlineMaterial, originalMaterial ); 386 | 387 | return outlineMaterial; 388 | 389 | } 390 | 391 | function setOutlineMaterial( object ) { 392 | 393 | if ( object.material === undefined ) return; 394 | 395 | if ( Array.isArray( object.material ) ) { 396 | 397 | for ( var i = 0, il = object.material.length; i < il; i ++ ) { 398 | 399 | object.material[ i ] = getOutlineMaterial( object.material[ i ] ); 400 | 401 | } 402 | 403 | } else { 404 | 405 | object.material = getOutlineMaterial( object.material ); 406 | 407 | } 408 | 409 | originalOnBeforeRenders[ object.uuid ] = object.onBeforeRender; 410 | object.onBeforeRender = onBeforeRender; 411 | 412 | } 413 | 414 | function restoreOriginalMaterial( object ) { 415 | 416 | if ( object.material === undefined ) return; 417 | 418 | if ( Array.isArray( object.material ) ) { 419 | 420 | for ( var i = 0, il = object.material.length; i < il; i ++ ) { 421 | 422 | object.material[ i ] = originalMaterials[ object.material[ i ].uuid ]; 423 | 424 | } 425 | 426 | } else { 427 | 428 | object.material = originalMaterials[ object.material.uuid ]; 429 | 430 | } 431 | 432 | object.onBeforeRender = originalOnBeforeRenders[ object.uuid ]; 433 | 434 | } 435 | 436 | function onBeforeRender( renderer, scene, camera, geometry, material, group ) { 437 | 438 | var originalMaterial = originalMaterials[ material.uuid ]; 439 | 440 | // just in case 441 | if ( originalMaterial === undefined ) return; 442 | 443 | updateUniforms( material, originalMaterial ); 444 | 445 | } 446 | 447 | function updateUniforms( material, originalMaterial ) { 448 | 449 | var outlineParameters = originalMaterial.outlineParameters; 450 | 451 | material.uniforms.outlineAlpha.value = originalMaterial.opacity; 452 | 453 | if ( outlineParameters !== undefined ) { 454 | 455 | if ( outlineParameters.thickness !== undefined ) material.uniforms.outlineThickness.value = outlineParameters.thickness; 456 | if ( outlineParameters.color !== undefined ) material.uniforms.outlineColor.value.copy( outlineParameters.color ); 457 | if ( outlineParameters.alpha !== undefined ) material.uniforms.outlineAlpha.value = outlineParameters.alpha; 458 | 459 | } 460 | 461 | } 462 | 463 | function updateOutlineMaterial( material, originalMaterial ) { 464 | 465 | if ( material.name === 'invisible' ) return; 466 | 467 | var outlineParameters = originalMaterial.outlineParameters; 468 | 469 | material.skinning = originalMaterial.skinning; 470 | material.morphTargets = originalMaterial.morphTargets; 471 | material.morphNormals = originalMaterial.morphNormals; 472 | material.fog = originalMaterial.fog; 473 | 474 | if ( outlineParameters !== undefined ) { 475 | 476 | if ( originalMaterial.visible === false ) { 477 | 478 | material.visible = false; 479 | 480 | } else { 481 | 482 | material.visible = ( outlineParameters.visible !== undefined ) ? outlineParameters.visible : true; 483 | 484 | } 485 | 486 | material.transparent = ( outlineParameters.alpha !== undefined && outlineParameters.alpha < 1.0 ) ? true : originalMaterial.transparent; 487 | 488 | if ( outlineParameters.keepAlive !== undefined ) cache[ originalMaterial.uuid ].keepAlive = outlineParameters.keepAlive; 489 | 490 | } else { 491 | 492 | material.transparent = originalMaterial.transparent; 493 | material.visible = originalMaterial.visible; 494 | 495 | } 496 | 497 | if ( originalMaterial.wireframe === true || originalMaterial.depthTest === false ) material.visible = false; 498 | 499 | } 500 | 501 | function cleanupCache() { 502 | 503 | var keys; 504 | 505 | // clear originialMaterials 506 | keys = Object.keys( originalMaterials ); 507 | 508 | for ( var i = 0, il = keys.length; i < il; i ++ ) { 509 | 510 | originalMaterials[ keys[ i ] ] = undefined; 511 | 512 | } 513 | 514 | // clear originalOnBeforeRenders 515 | keys = Object.keys( originalOnBeforeRenders ); 516 | 517 | for ( var i = 0, il = keys.length; i < il; i ++ ) { 518 | 519 | originalOnBeforeRenders[ keys[ i ] ] = undefined; 520 | 521 | } 522 | 523 | // remove unused outlineMaterial from cache 524 | keys = Object.keys( cache ); 525 | 526 | for ( var i = 0, il = keys.length; i < il; i ++ ) { 527 | 528 | var key = keys[ i ]; 529 | 530 | if ( cache[ key ].used === false ) { 531 | 532 | cache[ key ].count++; 533 | 534 | if ( cache[ key ].keepAlive === false && cache[ key ].count > removeThresholdCount ) { 535 | 536 | delete cache[ key ]; 537 | 538 | } 539 | 540 | } else { 541 | 542 | cache[ key ].used = false; 543 | cache[ key ].count = 0; 544 | 545 | } 546 | 547 | } 548 | 549 | } 550 | 551 | this.render = function ( scene, camera, renderTarget, forceClear ) { 552 | 553 | if ( this.enabled === false ) { 554 | 555 | renderer.render( scene, camera, renderTarget, forceClear ); 556 | return; 557 | 558 | } 559 | 560 | var currentAutoClear = renderer.autoClear; 561 | renderer.autoClear = this.autoClear; 562 | 563 | // 1. render normally 564 | renderer.render( scene, camera, renderTarget, forceClear ); 565 | 566 | // 2. render outline 567 | var currentSceneAutoUpdate = scene.autoUpdate; 568 | var currentSceneBackground = scene.background; 569 | var currentShadowMapEnabled = renderer.shadowMap.enabled; 570 | 571 | scene.autoUpdate = false; 572 | scene.background = null; 573 | renderer.autoClear = false; 574 | renderer.shadowMap.enabled = false; 575 | 576 | scene.traverse( setOutlineMaterial ); 577 | 578 | renderer.render( scene, camera, renderTarget ); 579 | 580 | scene.traverse( restoreOriginalMaterial ); 581 | 582 | cleanupCache(); 583 | 584 | scene.autoUpdate = currentSceneAutoUpdate; 585 | scene.background = currentSceneBackground; 586 | renderer.autoClear = currentAutoClear; 587 | renderer.shadowMap.enabled = currentShadowMapEnabled; 588 | 589 | }; 590 | 591 | /* 592 | * See #9918 593 | * 594 | * The following property copies and wrapper methods enable 595 | * THREE.OutlineEffect to be called from other *Effect, like 596 | * 597 | * effect = new THREE.VREffect( new THREE.OutlineEffect( renderer ) ); 598 | * 599 | * function render () { 600 | * 601 | * effect.render( scene, camera ); 602 | * 603 | * } 604 | */ 605 | this.autoClear = renderer.autoClear; 606 | this.domElement = renderer.domElement; 607 | this.shadowMap = renderer.shadowMap; 608 | 609 | this.clear = function ( color, depth, stencil ) { 610 | 611 | renderer.clear( color, depth, stencil ); 612 | 613 | }; 614 | 615 | this.getPixelRatio = function () { 616 | 617 | return renderer.getPixelRatio(); 618 | 619 | }; 620 | 621 | this.setPixelRatio = function ( value ) { 622 | 623 | renderer.setPixelRatio( value ); 624 | 625 | }; 626 | 627 | this.getSize = function () { 628 | 629 | return renderer.getSize(); 630 | 631 | }; 632 | 633 | this.setSize = function ( width, height, updateStyle ) { 634 | 635 | renderer.setSize( width, height, updateStyle ); 636 | 637 | }; 638 | 639 | this.setViewport = function ( x, y, width, height ) { 640 | 641 | renderer.setViewport( x, y, width, height ); 642 | 643 | }; 644 | 645 | this.setScissor = function ( x, y, width, height ) { 646 | 647 | renderer.setScissor( x, y, width, height ); 648 | 649 | }; 650 | 651 | this.setScissorTest = function ( boolean ) { 652 | 653 | renderer.setScissorTest( boolean ); 654 | 655 | }; 656 | 657 | this.setRenderTarget = function ( renderTarget ) { 658 | 659 | renderer.setRenderTarget( renderTarget ); 660 | 661 | }; 662 | 663 | }; 664 | 665 | 666 | /***/ }) 667 | /******/ ]); -------------------------------------------------------------------------------- /build/aframe-outline.min.js: -------------------------------------------------------------------------------- 1 | !function(e){function t(n){if(i[n])return i[n].exports;var a=i[n]={i:n,l:!1,exports:{}};return e[n].call(a.exports,a,a.exports,t),a.l=!0,a.exports}var i={};t.m=e,t.c=i,t.d=function(e,i,n){t.o(e,i)||Object.defineProperty(e,i,{configurable:!1,enumerable:!0,get:n})},t.n=function(e){var i=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(i,"a",i),i},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=0)}([function(e,t,i){if("undefined"==typeof AFRAME)throw new Error("Component attempted to register beforeAFRAME was available.");i(1),AFRAME.registerComponent("outline",{schema:{thickness:{type:"number",default:.003},color:{type:"color",default:"#000"},alpha:{type:"number",default:1}},init:function(){this.effect=null},update:function(){this.setupOutlineEffect()},remove:function(){this.disableOutlineEffect()},setupOutlineEffect:function(){if(null!==this.effect)return void(this.effect.enabled=!0);var e=this.data,t=this.el,i=t.sceneEl,n=i.renderer,a=i.effect;if(void 0!==n&&void 0!==a){for(var r=new THREE.OutlineEffect(n,{defaultThickness:e.thickness,defaultColor:new THREE.Color(e.color),defaultAlpha:e.alpha}),o=Object.keys(n),l=0,s=o.length;l/g,""),s={};return/vec3\s+transformed\s*=/.test(n)||/#include\s+/.test(n)||(s.DECLARE_TRANSFORMED=!0),new THREE.ShaderMaterial({defines:s,uniforms:o,vertexShader:l,fragmentShader:T,side:THREE.BackSide,skinning:!1,morphTargets:!1,morphNormals:!1,fog:!1})}function a(e){var t=m[e.uuid];return void 0===t&&(t={material:n(e),used:!0,keepAlive:p,count:0},m[e.uuid]=t),t.used=!0,t.material}function r(e){var t=a(e);return g[t.uuid]=e,f(t,e),t}function o(e){if(void 0!==e.material){if(Array.isArray(e.material))for(var t=0,i=e.material.length;tb&&delete m[n]):(m[n].used=!1,m[n].count=0)}}t=t||{},this.enabled=!0;var c=void 0!==t.defaultThickness?t.defaultThickness:.003,v=void 0!==t.defaultColor?t.defaultColor:new THREE.Color(0),h=void 0!==t.defaultAlpha?t.defaultAlpha:1,p=void 0!==t.defaultKeepAlive&&t.defaultKeepAlive,m={},b=60,g={},E={},A={MeshBasicMaterial:"basic",MeshLambertMaterial:"lambert",MeshPhongMaterial:"phong",MeshToonMaterial:"phong",MeshStandardMaterial:"physical",MeshPhysicalMaterial:"physical"},k={outlineThickness:{type:"f",value:c},outlineColor:{type:"c",value:v},outlineAlpha:{type:"f",value:h}},M=["#include ","uniform float outlineThickness;","vec4 calculateOutline( vec4 pos, vec3 objectNormal, vec4 skinned ) {","\tfloat thickness = outlineThickness;","\tconst float ratio = 1.0;","\tvec4 pos2 = projectionMatrix * modelViewMatrix * vec4( skinned.xyz + objectNormal, 1.0 );","\tvec4 norm = normalize( pos - pos2 );","\treturn pos + norm * thickness * pos.w * ratio;","}"].join("\n"),R=["#if ! defined( LAMBERT ) && ! defined( PHONG ) && ! defined( TOON ) && ! defined( PHYSICAL )","\t#ifndef USE_ENVMAP","\t\tvec3 objectNormal = normalize( normal );","\t#endif","#endif","#ifdef FLIP_SIDED","\tobjectNormal = -objectNormal;","#endif","#ifdef DECLARE_TRANSFORMED","\tvec3 transformed = vec3( position );","#endif","gl_Position = calculateOutline( gl_Position, objectNormal, vec4( transformed, 1.0 ) );","#include "].join("\n"),T=["#include ","#include ","uniform vec3 outlineColor;","uniform float outlineAlpha;","void main() {","\tgl_FragColor = vec4( outlineColor, outlineAlpha );","\t#include ","}"].join("\n");this.render=function(t,i,n,a){if(!1===this.enabled)return void e.render(t,i,n,a);var r=e.autoClear;e.autoClear=this.autoClear,e.render(t,i,n,a);var s=t.autoUpdate,u=t.background,f=e.shadowMap.enabled;t.autoUpdate=!1,t.background=null,e.autoClear=!1,e.shadowMap.enabled=!1,t.traverse(o),e.render(t,i,n),t.traverse(l),d(),t.autoUpdate=s,t.background=u,e.autoClear=r,e.shadowMap.enabled=f},this.autoClear=e.autoClear,this.domElement=e.domElement,this.shadowMap=e.shadowMap,this.clear=function(t,i,n){e.clear(t,i,n)},this.getPixelRatio=function(){return e.getPixelRatio()},this.setPixelRatio=function(t){e.setPixelRatio(t)},this.getSize=function(){return e.getSize()},this.setSize=function(t,i,n){e.setSize(t,i,n)},this.setViewport=function(t,i,n,a){e.setViewport(t,i,n,a)},this.setScissor=function(t,i,n,a){e.setScissor(t,i,n,a)},this.setScissorTest=function(t){e.setScissorTest(t)},this.setRenderTarget=function(t){e.setRenderTarget(t)}}}]); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | A-Frame Outline component 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Takahiro / https://github.com/takahirox 3 | */ 4 | 5 | if (typeof AFRAME === 'undefined') { 6 | throw new Error('Component attempted to register before' + 7 | 'AFRAME was available.'); 8 | } 9 | 10 | require('three/examples/js/effects/OutlineEffect'); 11 | 12 | AFRAME.registerComponent('outline', { 13 | schema: { 14 | thickness: {type:'number', default: 0.003}, 15 | color: {type:'color', default: '#000'}, 16 | alpha: {type:'number', default: 1.0} 17 | }, 18 | 19 | init: function () { 20 | this.effect = null; 21 | }, 22 | 23 | update: function () { 24 | this.setupOutlineEffect(); 25 | }, 26 | 27 | remove: function () { 28 | this.disableOutlineEffect(); 29 | }, 30 | 31 | setupOutlineEffect: function () { 32 | if (this.effect !== null) { 33 | this.effect.enabled = true; 34 | return; 35 | } 36 | 37 | var data = this.data; 38 | var el = this.el; 39 | var sceneEl = el.sceneEl; 40 | var renderer = sceneEl.renderer; 41 | var effect = sceneEl.effect; 42 | 43 | if (renderer === undefined || effect === undefined) { return; } 44 | 45 | var outlineEffect = new THREE.OutlineEffect(renderer, { 46 | defaultThickness: data.thickness, 47 | defaultColor: new THREE.Color(data.color), 48 | defaultAlpha: data.alpha 49 | }); 50 | 51 | // override scene effect 52 | // this's very sensitive to sceneEl impl 53 | var keys = Object.keys(renderer); 54 | for (var i = 0, il = keys.length; i < il; i++) { 55 | var key = keys[i]; 56 | if (outlineEffect[key] === undefined) { 57 | outlineEffect[key] = typeof renderer[key] === 'function' 58 | ? renderer[key].bind(renderer) 59 | : renderer[key]; 60 | } 61 | } 62 | this.effect = outlineEffect; 63 | sceneEl.effect = new THREE.VREffect(outlineEffect); 64 | }, 65 | 66 | disableOutlineEffect: function () { 67 | if (this.effect === null) { return; } 68 | 69 | this.effect.enabled = false; 70 | } 71 | }); 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aframe-outline", 3 | "version": "1.1.1", 4 | "description": "Two-pass Outline effect component for A-Frame", 5 | "main": "index.js", 6 | "scripts": { 7 | "all": "npm run build && npm run build-uglify", 8 | "build": "webpack index.js build/aframe-outline.js", 9 | "build-uglify": "webpack -p index.js build/aframe-outline.min.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/takahirox/aframe-outline.git" 14 | }, 15 | "keywords": [ 16 | "aframe", 17 | "aframe-vr", 18 | "vr", 19 | "three", 20 | "threejs", 21 | "mozvr", 22 | "webvr" 23 | ], 24 | "author": "Takahiro (https://github.com/takahirox)", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/takahirox/aframe-outline/issues" 28 | }, 29 | "homepage": "https://github.com/takahirox/aframe-outline#readme", 30 | "devDependencies": { 31 | "aframe": "^0.7.1", 32 | "three": "^0.87.1", 33 | "webpack": "^3.8.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takahirox/aframe-outline/1b339d26a01eaa8d42809b69090e8991a804ed37/screenshot.png --------------------------------------------------------------------------------