outlineRenderer.ts 13 KB

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