outlineRenderer.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. import { VertexBuffer } from "Meshes/buffer";
  2. import { SubMesh } from "Meshes/subMesh";
  3. import { _InstancesBatch } from "Meshes/mesh";
  4. import { AbstractMesh } from "Meshes/abstractMesh";
  5. import { Scene } from "scene";
  6. import { Engine } from "Engines/engine";
  7. import { Constants } from "Engines/constants";
  8. import { ISceneComponent, SceneComponentConstants } from "sceneComponent";
  9. import { Effect } from "Materials/effect";
  10. import { Material } from "Materials/material";
  11. import "Shaders/outline.fragment";
  12. import "Shaders/outline.vertex";
  13. declare module "scene" {
  14. export interface Scene {
  15. /** @hidden */
  16. _outlineRenderer: OutlineRenderer;
  17. /**
  18. * Gets the outline renderer associated with the scene
  19. * @returns a OutlineRenderer
  20. */
  21. getOutlineRenderer(): OutlineRenderer;
  22. }
  23. }
  24. /**
  25. * Gets the outline renderer associated with the scene
  26. * @returns a OutlineRenderer
  27. */
  28. Scene.prototype.getOutlineRenderer = function(): OutlineRenderer {
  29. if (!this._outlineRenderer) {
  30. this._outlineRenderer = new OutlineRenderer(this);
  31. }
  32. return this._outlineRenderer;
  33. };
  34. declare module "Meshes/AbstractMesh" {
  35. export interface AbstractMesh {
  36. /** @hidden (Backing field) */
  37. _renderOutline: boolean;
  38. /**
  39. * Gets or sets a boolean indicating if the outline must be rendered as well
  40. * @see https://www.babylonjs-playground.com/#10WJ5S#3
  41. */
  42. renderOutline: boolean;
  43. /** @hidden (Backing field) */
  44. _renderOverlay: boolean;
  45. /**
  46. * Gets or sets a boolean indicating if the overlay must be rendered as well
  47. * @see https://www.babylonjs-playground.com/#10WJ5S#2
  48. */
  49. renderOverlay: boolean;
  50. }
  51. }
  52. Object.defineProperty(AbstractMesh.prototype, "renderOutline", {
  53. get: function(this: AbstractMesh) {
  54. return this._renderOutline;
  55. },
  56. set: function(this: AbstractMesh, value: boolean) {
  57. if (value) {
  58. // Lazy Load the component.
  59. this.getScene().getOutlineRenderer();
  60. }
  61. this._renderOutline = value;
  62. },
  63. enumerable: true,
  64. configurable: true
  65. });
  66. Object.defineProperty(AbstractMesh.prototype, "renderOverlay", {
  67. get: function(this: AbstractMesh) {
  68. return this._renderOverlay;
  69. },
  70. set: function(this: AbstractMesh, value: boolean) {
  71. if (value) {
  72. // Lazy Load the component.
  73. this.getScene().getOutlineRenderer();
  74. }
  75. this._renderOverlay = value;
  76. },
  77. enumerable: true,
  78. configurable: true
  79. });
  80. /**
  81. * This class is responsible to draw bothe outline/overlay of meshes.
  82. * It should not be used directly but through the available method on mesh.
  83. */
  84. export class OutlineRenderer implements ISceneComponent {
  85. /**
  86. * The name of the component. Each component must have a unique name.
  87. */
  88. public name = SceneComponentConstants.NAME_OUTLINERENDERER;
  89. /**
  90. * The scene the component belongs to.
  91. */
  92. public scene: Scene;
  93. /**
  94. * Defines a zOffset to prevent zFighting between the overlay and the mesh.
  95. */
  96. public zOffset = 1;
  97. private _engine: Engine;
  98. private _effect: Effect;
  99. private _cachedDefines: string;
  100. private _savedDepthWrite: boolean;
  101. /**
  102. * Instantiates a new outline renderer. (There could be only one per scene).
  103. * @param scene Defines the scene it belongs to
  104. */
  105. constructor(scene: Scene) {
  106. this.scene = scene;
  107. this._engine = scene.getEngine();
  108. this.scene._addComponent(this);
  109. }
  110. /**
  111. * Register the component to one instance of a scene.
  112. */
  113. public register(): void {
  114. this.scene._beforeRenderingMeshStage.registerStep(SceneComponentConstants.STEP_BEFORERENDERINGMESH_OUTLINE, this, this._beforeRenderingMesh);
  115. this.scene._afterRenderingMeshStage.registerStep(SceneComponentConstants.STEP_AFTERRENDERINGMESH_OUTLINE, this, this._afterRenderingMesh);
  116. }
  117. /**
  118. * Rebuilds the elements related to this component in case of
  119. * context lost for instance.
  120. */
  121. public rebuild(): void {
  122. // Nothing to do here.
  123. }
  124. /**
  125. * Disposes the component and the associated ressources.
  126. */
  127. public dispose(): void {
  128. // Nothing to do here.
  129. }
  130. /**
  131. * Renders the outline in the canvas.
  132. * @param subMesh Defines the sumesh to render
  133. * @param batch Defines the batch of meshes in case of instances
  134. * @param useOverlay Defines if the rendering is for the overlay or the outline
  135. */
  136. public render(subMesh: SubMesh, batch: _InstancesBatch, useOverlay: boolean = false): void {
  137. var scene = this.scene;
  138. var engine = scene.getEngine();
  139. var hardwareInstancedRendering = (engine.getCaps().instancedArrays) && (batch.visibleInstances[subMesh._id] !== null) && (batch.visibleInstances[subMesh._id] !== undefined);
  140. if (!this.isReady(subMesh, hardwareInstancedRendering)) {
  141. return;
  142. }
  143. var mesh = subMesh.getRenderingMesh();
  144. var material = subMesh.getMaterial();
  145. if (!material || !scene.activeCamera) {
  146. return;
  147. }
  148. engine.enableEffect(this._effect);
  149. // Logarithmic depth
  150. if ((<any> material).useLogarithmicDepth)
  151. {
  152. this._effect.setFloat("logarithmicDepthConstant", 2.0 / (Math.log(scene.activeCamera.maxZ + 1.0) / Math.LN2));
  153. }
  154. this._effect.setFloat("offset", useOverlay ? 0 : mesh.outlineWidth);
  155. this._effect.setColor4("color", useOverlay ? mesh.overlayColor : mesh.outlineColor, useOverlay ? mesh.overlayAlpha : material.alpha);
  156. this._effect.setMatrix("viewProjection", scene.getTransformMatrix());
  157. // Bones
  158. if (mesh.useBones && mesh.computeBonesUsingShaders && mesh.skeleton) {
  159. this._effect.setMatrices("mBones", mesh.skeleton.getTransformMatrices(mesh));
  160. }
  161. mesh._bind(subMesh, this._effect, Material.TriangleFillMode);
  162. // Alpha test
  163. if (material && material.needAlphaTesting()) {
  164. var alphaTexture = material.getAlphaTestTexture();
  165. if (alphaTexture) {
  166. this._effect.setTexture("diffuseSampler", alphaTexture);
  167. this._effect.setMatrix("diffuseMatrix", alphaTexture.getTextureMatrix());
  168. }
  169. }
  170. engine.setZOffset(-this.zOffset);
  171. mesh._processRendering(subMesh, this._effect, Material.TriangleFillMode, batch, hardwareInstancedRendering,
  172. (isInstance, world) => { this._effect.setMatrix("world", world); });
  173. engine.setZOffset(0);
  174. }
  175. /**
  176. * Returns whether or not the outline renderer is ready for a given submesh.
  177. * All the dependencies e.g. submeshes, texture, effect... mus be ready
  178. * @param subMesh Defines the submesh to check readyness for
  179. * @param useInstances Defines wheter wee are trying to render instances or not
  180. * @returns true if ready otherwise false
  181. */
  182. public isReady(subMesh: SubMesh, useInstances: boolean): boolean {
  183. var defines = [];
  184. var attribs = [VertexBuffer.PositionKind, VertexBuffer.NormalKind];
  185. var mesh = subMesh.getMesh();
  186. var material = subMesh.getMaterial();
  187. if (material) {
  188. // Alpha test
  189. if (material.needAlphaTesting())
  190. {
  191. defines.push("#define ALPHATEST");
  192. if (mesh.isVerticesDataPresent(VertexBuffer.UVKind)) {
  193. attribs.push(VertexBuffer.UVKind);
  194. defines.push("#define UV1");
  195. }
  196. if (mesh.isVerticesDataPresent(VertexBuffer.UV2Kind)) {
  197. attribs.push(VertexBuffer.UV2Kind);
  198. defines.push("#define UV2");
  199. }
  200. }
  201. //Logarithmic depth
  202. if ((<any> material).useLogarithmicDepth)
  203. {
  204. defines.push("#define LOGARITHMICDEPTH");
  205. }
  206. }
  207. // Bones
  208. if (mesh.useBones && mesh.computeBonesUsingShaders) {
  209. attribs.push(VertexBuffer.MatricesIndicesKind);
  210. attribs.push(VertexBuffer.MatricesWeightsKind);
  211. if (mesh.numBoneInfluencers > 4) {
  212. attribs.push(VertexBuffer.MatricesIndicesExtraKind);
  213. attribs.push(VertexBuffer.MatricesWeightsExtraKind);
  214. }
  215. defines.push("#define NUM_BONE_INFLUENCERS " + mesh.numBoneInfluencers);
  216. defines.push("#define BonesPerMesh " + (mesh.skeleton ? mesh.skeleton.bones.length + 1 : 0));
  217. } else {
  218. defines.push("#define NUM_BONE_INFLUENCERS 0");
  219. }
  220. // Instances
  221. if (useInstances) {
  222. defines.push("#define INSTANCES");
  223. attribs.push("world0");
  224. attribs.push("world1");
  225. attribs.push("world2");
  226. attribs.push("world3");
  227. }
  228. // Get correct effect
  229. var join = defines.join("\n");
  230. if (this._cachedDefines !== join) {
  231. this._cachedDefines = join;
  232. this._effect = this.scene.getEngine().createEffect("outline",
  233. attribs,
  234. ["world", "mBones", "viewProjection", "diffuseMatrix", "offset", "color", "logarithmicDepthConstant"],
  235. ["diffuseSampler"], join);
  236. }
  237. return this._effect.isReady();
  238. }
  239. private _beforeRenderingMesh(mesh: AbstractMesh, subMesh: SubMesh, batch: _InstancesBatch): void {
  240. // Outline - step 1
  241. this._savedDepthWrite = this._engine.getDepthWrite();
  242. if (mesh.renderOutline) {
  243. this._engine.setDepthWrite(false);
  244. this.render(subMesh, batch);
  245. this._engine.setDepthWrite(this._savedDepthWrite);
  246. }
  247. }
  248. private _afterRenderingMesh(mesh: AbstractMesh, subMesh: SubMesh, batch: _InstancesBatch): void {
  249. // Outline - step 2
  250. if (mesh.renderOutline && this._savedDepthWrite) {
  251. this._engine.setDepthWrite(true);
  252. this._engine.setColorWrite(false);
  253. this.render(subMesh, batch);
  254. this._engine.setColorWrite(true);
  255. }
  256. // Overlay
  257. if (mesh.renderOverlay) {
  258. var currentMode = this._engine.getAlphaMode();
  259. this._engine.setAlphaMode(Constants.ALPHA_COMBINE);
  260. this.render(subMesh, batch, true);
  261. this._engine.setAlphaMode(currentMode);
  262. }
  263. }
  264. }