UNPKG

@babylonjs/core

Version:

Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.

287 lines (286 loc) 13.6 kB
import { Matrix } from "../Maths/math.vector.js"; import { ShaderMaterial } from "../Materials/shaderMaterial.js"; import { RenderTargetTexture } from "../Materials/Textures/renderTargetTexture.js"; import { Color4 } from "../Maths/math.color.js"; import { PostProcess } from "../PostProcesses/postProcess.js"; /** * Class used to render in the mesh UV space * @since 5.49.1 */ export class MeshUVSpaceRenderer { static _GetShader(scene, shaderLanguage) { if (!scene._meshUVSpaceRendererShader) { const shader = new ShaderMaterial("meshUVSpaceRendererShader", scene, { vertex: "meshUVSpaceRenderer", fragment: "meshUVSpaceRenderer", }, { attributes: ["position", "normal", "uv"], uniforms: ["world", "projMatrix"], samplers: ["textureSampler"], needAlphaBlending: true, shaderLanguage: shaderLanguage, }); shader.backFaceCulling = false; shader.alphaMode = 2; scene.onDisposeObservable.add(() => { scene._meshUVSpaceRendererShader?.dispose(); scene._meshUVSpaceRendererShader = null; }); scene._meshUVSpaceRendererShader = shader; } return scene._meshUVSpaceRendererShader; } static _GetMaskShader(scene, shaderLanguage) { if (!scene._meshUVSpaceRendererMaskShader) { const shader = new ShaderMaterial("meshUVSpaceRendererMaskShader", scene, { vertex: "meshUVSpaceRendererMasker", fragment: "meshUVSpaceRendererMasker", }, { attributes: ["position", "uv"], uniforms: ["worldViewProjection"], shaderLanguage: shaderLanguage, }); shader.backFaceCulling = false; shader.alphaMode = 2; scene.onDisposeObservable.add(() => { scene._meshUVSpaceRendererMaskShader?.dispose(); scene._meshUVSpaceRendererMaskShader = null; }); scene._meshUVSpaceRendererMaskShader = shader; } return scene._meshUVSpaceRendererMaskShader; } static _IsRenderTargetTexture(texture) { return texture.renderList !== undefined; } /** * Gets the shader language used in this material. */ get shaderLanguage() { return this._shaderLanguage; } /** * Creates a new MeshUVSpaceRenderer * @param mesh The mesh used for the source UV space * @param scene The scene the mesh belongs to * @param options The options to use when creating the texture */ constructor(mesh, scene, options) { this._textureCreatedInternally = false; this._configureUserCreatedTexture = true; this._maskTexture = null; this._finalPostProcess = null; this._shadersLoaded = false; this._isDisposed = false; /** * Clear color of the texture */ this.clearColor = new Color4(0, 0, 0, 0); /** * Target texture used for rendering * If you don't set the property, a RenderTargetTexture will be created internally given the options provided to the constructor. * If you provide a RenderTargetTexture, it will be used directly. */ this.texture = null; /** Shader language used by the material */ this._shaderLanguage = 0 /* ShaderLanguage.GLSL */; this._mesh = mesh; this._scene = scene; this._options = { width: 1024, height: 1024, textureType: 0, generateMipMaps: true, optimizeUVAllocation: true, uvEdgeBlending: false, ...options, }; this._initShaderSourceAsync(); } async _initShaderSourceAsync() { const engine = this._scene.getEngine(); if (engine.isWebGPU) { this._shaderLanguage = 1 /* ShaderLanguage.WGSL */; await Promise.all([ import("../ShadersWGSL/meshUVSpaceRenderer.vertex.js"), import("../ShadersWGSL/meshUVSpaceRenderer.fragment.js"), import("../ShadersWGSL/meshUVSpaceRendererMasker.vertex.js"), import("../ShadersWGSL/meshUVSpaceRendererMasker.fragment.js"), import("../ShadersWGSL/meshUVSpaceRendererFinaliser.vertex.js"), import("../ShadersWGSL/meshUVSpaceRendererFinaliser.fragment.js"), ]); } else { await Promise.all([ import("../Shaders/meshUVSpaceRenderer.vertex.js"), import("../Shaders/meshUVSpaceRenderer.fragment.js"), import("../Shaders/meshUVSpaceRendererMasker.vertex.js"), import("../Shaders/meshUVSpaceRendererMasker.fragment.js"), import("../Shaders/meshUVSpaceRendererFinaliser.vertex.js"), import("../Shaders/meshUVSpaceRendererFinaliser.fragment.js"), ]); } if (this._isDisposed) { return; } this._shadersLoaded = true; } /** * Checks if the texture is ready to be used * @returns true if the texture is ready to be used */ isReady() { if (!this._shadersLoaded) { return false; } if (!this.texture) { this._createDiffuseRTT(); } else if (this._configureUserCreatedTexture) { this._configureUserCreatedRTT(); } // this.texture is guaranteed to be non-null here as it is created in _createDiffuseRTT above const textureIsReady = MeshUVSpaceRenderer._IsRenderTargetTexture(this.texture) ? this.texture.isReadyForRendering() : this.texture.isReady(); const maskIsReady = this._maskTexture?.isReadyForRendering() ?? true; const postProcessIsReady = this._finalPostProcess?.isReady() ?? true; return textureIsReady && maskIsReady && postProcessIsReady; } /** * Projects and renders a texture in the mesh UV space * @param texture The texture * @param position The position of the center of projection (world space coordinates) * @param normal The direction of the projection (world space coordinates) * @param size The size of the projection * @param angle The rotation angle around the direction of the projection (default: 0) * @param checkIsReady If true, it will check if the texture is ready before rendering (default: true). If the texture is not ready, a new attempt will be scheduled in 16ms */ renderTexture(texture, position, normal, size, angle = 0, checkIsReady = true) { if (checkIsReady && !this.isReady()) { setTimeout(() => { this.renderTexture(texture, position, normal, size, angle, checkIsReady); }, 16); return; } if (!this.texture) { this._createDiffuseRTT(); } else if (this._configureUserCreatedTexture) { this._configureUserCreatedRTT(); } // this.texture is guaranteed to be non-null here as it is created in _createDiffuseRTT above if (MeshUVSpaceRenderer._IsRenderTargetTexture(this.texture)) { const matrix = this._createProjectionMatrix(position, normal, size, angle); const shader = MeshUVSpaceRenderer._GetShader(this._scene, this._shaderLanguage); shader.setTexture("textureSampler", texture); shader.setMatrix("projMatrix", matrix); this.texture.render(); // We needed the texture only once for the render() call above, so we can remove it from the shader. // It's important to do that, because this texture could be disposed by the user, meaning that shader.isReady() would return false as part of the this.texture.isReadyForRendering() call of isReady() shader.removeTexture("textureSampler"); } } /** * Clears the texture map */ clear() { if (this.texture && MeshUVSpaceRenderer._IsRenderTargetTexture(this.texture) && this.texture.renderTarget) { const engine = this._scene.getEngine(); engine.bindFramebuffer(this.texture.renderTarget); engine.clear(this.clearColor, true, true, true); engine.unBindFramebuffer(this.texture.renderTarget); } if (this._finalPostProcess?.inputTexture) { const engine = this._scene.getEngine(); engine.bindFramebuffer(this._finalPostProcess?.inputTexture); engine.clear(this.clearColor, true, true, true); engine.unBindFramebuffer(this._finalPostProcess?.inputTexture); } } /** * Disposes of the resources */ dispose() { if (this._textureCreatedInternally) { this.texture?.dispose(); this._textureCreatedInternally = false; } this._configureUserCreatedTexture = true; this._maskTexture?.dispose(); this._maskTexture = null; this._finalPostProcess?.dispose(); this._finalPostProcess = null; this._isDisposed = true; } _configureUserCreatedRTT() { this._configureUserCreatedTexture = false; if (this.texture && MeshUVSpaceRenderer._IsRenderTargetTexture(this.texture)) { this.texture.setMaterialForRendering(this._mesh, MeshUVSpaceRenderer._GetShader(this._scene, this._shaderLanguage)); this.texture.onClearObservable.add(() => { }); this.texture.renderList = [this._mesh]; if (this._options.uvEdgeBlending) { this._createMaskTexture(); this._createPostProcess(); this.texture.addPostProcess(this._finalPostProcess); } } } _createDiffuseRTT() { this._textureCreatedInternally = true; const texture = this._createRenderTargetTexture(this._options.width, this._options.height); texture.setMaterialForRendering(this._mesh, MeshUVSpaceRenderer._GetShader(this._scene, this._shaderLanguage)); this.texture = texture; this._configureUserCreatedTexture = false; if (this._options.uvEdgeBlending) { this._createMaskTexture(); this._createPostProcess(); texture.addPostProcess(this._finalPostProcess); } } _createMaskTexture() { if (this._maskTexture) { return; } this._maskTexture = new RenderTargetTexture(this._mesh.name + "_maskTexture", { width: this._options.width, height: this._options.height }, this._scene, false, // No mipmaps for the mask texture true, 0, false, 2, undefined, undefined, undefined, 6); this._maskTexture.clearColor = new Color4(0, 0, 0, 0); // Render the mesh with the mask material to the mask texture this._maskTexture.renderList.push(this._mesh); this._maskTexture.setMaterialForRendering(this._mesh, MeshUVSpaceRenderer._GetMaskShader(this._scene, this._shaderLanguage)); // Ensure the mask texture is updated this._maskTexture.refreshRate = RenderTargetTexture.REFRESHRATE_RENDER_ONCE; this._scene.customRenderTargets.push(this._maskTexture); } _createPostProcess() { if (this._finalPostProcess) { return; } this._finalPostProcess = new PostProcess(this._mesh.name + "_fixSeamsPostProcess", "meshUVSpaceRendererFinaliser", ["textureSize"], ["textureSampler", "maskTextureSampler"], 1.0, null, 1, this._scene.getEngine(), false, null, this._options.textureType, undefined, undefined, undefined, undefined, this._shaderLanguage); this._finalPostProcess.onApplyObservable.add((effect) => { effect.setTexture("maskTextureSampler", this._maskTexture); effect.setFloat2("textureSize", this._options.width, this._options.height); }); } _createRenderTargetTexture(width, height) { const rtt = new RenderTargetTexture(this._mesh.name + "_uvspaceTexture", { width, height }, this._scene, this._options.generateMipMaps, true, this._options.textureType, false, this._options.generateMipMaps ? 3 : 2, false, false, false, 5); rtt.renderParticles = false; rtt.optimizeUVAllocation = !!this._options.optimizeUVAllocation; rtt.onClearObservable.addOnce(() => { this._scene.getEngine().clear(this.clearColor, true, true, true); rtt.onClearObservable.add(() => { }); // this disables clearing the texture for the next frames }); rtt.renderList = [this._mesh]; return rtt; } _createProjectionMatrix(position, normal, size, angle = 0) { const yaw = -Math.atan2(normal.z, normal.x) - Math.PI / 2; const len = Math.sqrt(normal.x * normal.x + normal.z * normal.z); const pitch = Math.atan2(normal.y, len); const p = position.add(normal.scale(size.z * 0.5)); const projWorldMatrix = Matrix.RotationYawPitchRoll(yaw, pitch, angle).multiply(Matrix.Translation(p.x, p.y, p.z)); const inverseProjWorldMatrix = Matrix.Invert(projWorldMatrix); const projMatrix = Matrix.FromArray([2 / size.x, 0, 0, 0, 0, 2 / size.y, 0, 0, 0, 0, 1 / size.z, 0, 0, 0, 0, 1]); const screenMatrix = Matrix.FromArray([0.5, 0, 0, 0, 0, 0.5, 0, 0, 0, 0, 1, 0, 0.5, 0.5, 0.0, 1]); return inverseProjWorldMatrix.multiply(projMatrix).multiply(screenMatrix); } } //# sourceMappingURL=meshUVSpaceRenderer.js.map