outlineRenderer.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  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. * Stencil value used to avoid outline being seen within the mesh when the mesh is transparent
  87. */
  88. private static _StencilReference = 0x04;
  89. /**
  90. * The name of the component. Each component must have a unique name.
  91. */
  92. public name = SceneComponentConstants.NAME_OUTLINERENDERER;
  93. /**
  94. * The scene the component belongs to.
  95. */
  96. public scene: Scene;
  97. /**
  98. * Defines a zOffset to prevent zFighting between the overlay and the mesh.
  99. */
  100. public zOffset = 1;
  101. private _engine: Engine;
  102. private _effect: Effect;
  103. private _cachedDefines: string;
  104. private _savedDepthWrite: boolean;
  105. /**
  106. * Instantiates a new outline renderer. (There could be only one per scene).
  107. * @param scene Defines the scene it belongs to
  108. */
  109. constructor(scene: Scene) {
  110. this.scene = scene;
  111. this._engine = scene.getEngine();
  112. this.scene._addComponent(this);
  113. }
  114. /**
  115. * Register the component to one instance of a scene.
  116. */
  117. public register(): void {
  118. this.scene._beforeRenderingMeshStage.registerStep(SceneComponentConstants.STEP_BEFORERENDERINGMESH_OUTLINE, this, this._beforeRenderingMesh);
  119. this.scene._afterRenderingMeshStage.registerStep(SceneComponentConstants.STEP_AFTERRENDERINGMESH_OUTLINE, this, this._afterRenderingMesh);
  120. }
  121. /**
  122. * Rebuilds the elements related to this component in case of
  123. * context lost for instance.
  124. */
  125. public rebuild(): void {
  126. // Nothing to do here.
  127. }
  128. /**
  129. * Disposes the component and the associated ressources.
  130. */
  131. public dispose(): void {
  132. // Nothing to do here.
  133. }
  134. /**
  135. * Renders the outline in the canvas.
  136. * @param subMesh Defines the sumesh to render
  137. * @param batch Defines the batch of meshes in case of instances
  138. * @param useOverlay Defines if the rendering is for the overlay or the outline
  139. */
  140. public render(subMesh: SubMesh, batch: _InstancesBatch, useOverlay: boolean = false): void {
  141. var scene = this.scene;
  142. var engine = scene.getEngine();
  143. var hardwareInstancedRendering = (engine.getCaps().instancedArrays) && (batch.visibleInstances[subMesh._id] !== null) && (batch.visibleInstances[subMesh._id] !== undefined);
  144. if (!this.isReady(subMesh, hardwareInstancedRendering)) {
  145. return;
  146. }
  147. var mesh = subMesh.getRenderingMesh();
  148. var material = subMesh.getMaterial();
  149. if (!material || !scene.activeCamera) {
  150. return;
  151. }
  152. engine.enableEffect(this._effect);
  153. // Logarithmic depth
  154. if ((<any>material).useLogarithmicDepth) {
  155. this._effect.setFloat("logarithmicDepthConstant", 2.0 / (Math.log(scene.activeCamera.maxZ + 1.0) / Math.LN2));
  156. }
  157. this._effect.setFloat("offset", useOverlay ? 0 : mesh.outlineWidth);
  158. this._effect.setColor4("color", useOverlay ? mesh.overlayColor : mesh.outlineColor, useOverlay ? mesh.overlayAlpha : material.alpha);
  159. this._effect.setMatrix("viewProjection", scene.getTransformMatrix());
  160. // Bones
  161. if (mesh.useBones && mesh.computeBonesUsingShaders && mesh.skeleton) {
  162. this._effect.setMatrices("mBones", mesh.skeleton.getTransformMatrices(mesh));
  163. }
  164. mesh._bind(subMesh, this._effect, Material.TriangleFillMode);
  165. // Alpha test
  166. if (material && material.needAlphaTesting()) {
  167. var alphaTexture = material.getAlphaTestTexture();
  168. if (alphaTexture) {
  169. this._effect.setTexture("diffuseSampler", alphaTexture);
  170. this._effect.setMatrix("diffuseMatrix", alphaTexture.getTextureMatrix());
  171. }
  172. }
  173. engine.setZOffset(-this.zOffset);
  174. mesh._processRendering(subMesh, this._effect, Material.TriangleFillMode, batch, hardwareInstancedRendering,
  175. (isInstance, world) => { this._effect.setMatrix("world", world); });
  176. engine.setZOffset(0);
  177. }
  178. /**
  179. * Returns whether or not the outline renderer is ready for a given submesh.
  180. * All the dependencies e.g. submeshes, texture, effect... mus be ready
  181. * @param subMesh Defines the submesh to check readyness for
  182. * @param useInstances Defines wheter wee are trying to render instances or not
  183. * @returns true if ready otherwise false
  184. */
  185. public isReady(subMesh: SubMesh, useInstances: boolean): boolean {
  186. var defines = [];
  187. var attribs = [VertexBuffer.PositionKind, VertexBuffer.NormalKind];
  188. var mesh = subMesh.getMesh();
  189. var material = subMesh.getMaterial();
  190. if (material) {
  191. // Alpha test
  192. if (material.needAlphaTesting()) {
  193. defines.push("#define ALPHATEST");
  194. if (mesh.isVerticesDataPresent(VertexBuffer.UVKind)) {
  195. attribs.push(VertexBuffer.UVKind);
  196. defines.push("#define UV1");
  197. }
  198. if (mesh.isVerticesDataPresent(VertexBuffer.UV2Kind)) {
  199. attribs.push(VertexBuffer.UV2Kind);
  200. defines.push("#define UV2");
  201. }
  202. }
  203. //Logarithmic depth
  204. if ((<any>material).useLogarithmicDepth) {
  205. defines.push("#define LOGARITHMICDEPTH");
  206. }
  207. }
  208. // Bones
  209. if (mesh.useBones && mesh.computeBonesUsingShaders) {
  210. attribs.push(VertexBuffer.MatricesIndicesKind);
  211. attribs.push(VertexBuffer.MatricesWeightsKind);
  212. if (mesh.numBoneInfluencers > 4) {
  213. attribs.push(VertexBuffer.MatricesIndicesExtraKind);
  214. attribs.push(VertexBuffer.MatricesWeightsExtraKind);
  215. }
  216. defines.push("#define NUM_BONE_INFLUENCERS " + mesh.numBoneInfluencers);
  217. defines.push("#define BonesPerMesh " + (mesh.skeleton ? mesh.skeleton.bones.length + 1 : 0));
  218. } else {
  219. defines.push("#define NUM_BONE_INFLUENCERS 0");
  220. }
  221. // Instances
  222. if (useInstances) {
  223. defines.push("#define INSTANCES");
  224. attribs.push("world0");
  225. attribs.push("world1");
  226. attribs.push("world2");
  227. attribs.push("world3");
  228. }
  229. // Get correct effect
  230. var join = defines.join("\n");
  231. if (this._cachedDefines !== join) {
  232. this._cachedDefines = join;
  233. this._effect = this.scene.getEngine().createEffect("outline",
  234. attribs,
  235. ["world", "mBones", "viewProjection", "diffuseMatrix", "offset", "color", "logarithmicDepthConstant"],
  236. ["diffuseSampler"], join);
  237. }
  238. return this._effect.isReady();
  239. }
  240. private _beforeRenderingMesh(mesh: AbstractMesh, subMesh: SubMesh, batch: _InstancesBatch): void {
  241. // Outline - step 1
  242. this._savedDepthWrite = this._engine.getDepthWrite();
  243. if (mesh.renderOutline) {
  244. var material = subMesh.getMaterial();
  245. if (material && material.needAlphaBlending) {
  246. this._engine.cacheStencilState();
  247. // Draw only to stencil buffer for the original mesh
  248. // The resulting stencil buffer will be used so the outline is not visible inside the mesh when the mesh is transparent
  249. this._engine.setDepthWrite(false);
  250. this._engine.setColorWrite(false);
  251. this._engine.setStencilBuffer(true);
  252. this._engine.setStencilOperationPass(Constants.REPLACE);
  253. this._engine.setStencilFunction(Constants.ALWAYS);
  254. this._engine.setStencilMask(OutlineRenderer._StencilReference);
  255. this._engine.setStencilFunctionReference(OutlineRenderer._StencilReference);
  256. this.render(subMesh, batch, /* This sets offset to 0 */ true);
  257. this._engine.setColorWrite(true);
  258. this._engine.setStencilFunction(Constants.NOTEQUAL);
  259. }
  260. // Draw the outline using the above stencil if needed to avoid drawing within the mesh
  261. this._engine.setDepthWrite(false);
  262. this.render(subMesh, batch);
  263. this._engine.setDepthWrite(this._savedDepthWrite);
  264. if (material && material.needAlphaBlending) {
  265. this._engine.restoreStencilState();
  266. }
  267. }
  268. }
  269. private _afterRenderingMesh(mesh: AbstractMesh, subMesh: SubMesh, batch: _InstancesBatch): void {
  270. // Overlay
  271. if (mesh.renderOverlay) {
  272. var currentMode = this._engine.getAlphaMode();
  273. this._engine.setAlphaMode(Constants.ALPHA_COMBINE);
  274. this.render(subMesh, batch, true);
  275. this._engine.setAlphaMode(currentMode);
  276. }
  277. // Outline - step 2
  278. if (mesh.renderOutline && this._savedDepthWrite) {
  279. this._engine.setDepthWrite(true);
  280. this._engine.setColorWrite(false);
  281. this.render(subMesh, batch);
  282. this._engine.setColorWrite(true);
  283. }
  284. }
  285. }