Skip to content

Commit

Permalink
Merge pull request BabylonJS#1 from torchesburn/decal-map-uv-seam-fix
Browse files Browse the repository at this point in the history
MeshUVSpaceRenderer UV Seam Fix
  • Loading branch information
torchesburn committed Dec 4, 2023
2 parents 9e01230 + 8cb60dc commit 460893d
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 15 deletions.
213 changes: 199 additions & 14 deletions packages/dev/core/src/Meshes/meshUVSpaceRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
import type { Texture } from "core/Materials/Textures/texture";
import { Texture } from "core/Materials/Textures/texture";
import type { Vector3 } from "core/Maths/math.vector";
import type { Scene } from "core/scene";
import type { AbstractMesh } from "./abstractMesh";
import type { ThinTexture } from "core/Materials/Textures/thinTexture";
import type { BaseTexture } from "core/Materials/Textures/baseTexture";
import type { Nullable } from "core/types";
import { Matrix } from "core/Maths/math.vector";
import { Matrix, Vector2 } from "core/Maths/math.vector";
import { Constants } from "core/Engines/constants";
import { ShaderMaterial } from "core/Materials/shaderMaterial";
import { RenderTargetTexture } from "core/Materials/Textures/renderTargetTexture";
import { Color4 } from "core/Maths/math.color";
import { PBRMaterial} from "core/Materials/PBR/pbrMaterial";
import { StandardMaterial } from "core/Materials/standardMaterial";
import { PostProcess } from "core/PostProcesses/postProcess";

import "../Shaders/meshUVSpaceRenderer.vertex";
import "../Shaders/meshUVSpaceRenderer.fragment";

import "../Shaders/meshUVSpaceRendererMasker.vertex";
import "../Shaders/meshUVSpaceRendererMasker.fragment";

import "../Shaders/meshUVSpaceRendererFinaliser.fragment";
import "../Shaders/meshUVSpaceRendererFinaliser.vertex";


declare module "../scene" {
/**
*
*/
export interface Scene {
/** @internal */
_meshUVSpaceRendererShader: Nullable<ShaderMaterial>;
Expand Down Expand Up @@ -47,6 +60,10 @@ export interface IMeshUVSpaceRendererOptions {
* If you plan to use the texture as a decal map and rotate / offset the texture, you should set this to false
*/
optimizeUVAllocation?: boolean;
/**
* If true, the texture will be blended with the mesh's texture to avoid seams. Default: false
*/
uvEdgeBlending?: boolean;
}

/**
Expand All @@ -58,6 +75,27 @@ export class MeshUVSpaceRenderer {
private _scene: Scene;
private _options: Required<IMeshUVSpaceRendererOptions>;
private _textureCreatedInternally = false;
/**
* Mask Texture for the UV Edge Blending
*/
_maskTexture: any;
/**
* Final Texture for the UV Edge Blending
*/
_finalTexture: any;

/**
* The decal texture
*/
decalTexture: Texture;
/**
* The final material for the UV Edge Blending
*/
public finalMaterial: ShaderMaterial;
/**
* The final post process for the UV Edge Blending
*/
_finalPostProcess: any;

private static _GetShader(scene: Scene): ShaderMaterial {
if (!scene._meshUVSpaceRendererShader) {
Expand Down Expand Up @@ -120,6 +158,7 @@ export class MeshUVSpaceRenderer {
textureType: Constants.TEXTURETYPE_UNSIGNED_BYTE,
generateMipMaps: true,
optimizeUVAllocation: true,
uvEdgeBlending: options?.uvEdgeBlending ?? false,
...options,
};
}
Expand All @@ -129,11 +168,11 @@ export class MeshUVSpaceRenderer {
* @returns true if the texture is ready to be used
*/
public isReady(): boolean {
if (!this.texture) {
if (!this.decalTexture) {
this._createDiffuseRTT();
}

return MeshUVSpaceRenderer._IsRenderTargetTexture(this.texture) ? this.texture.isReadyForRendering() : this.texture.isReady();
return MeshUVSpaceRenderer._IsRenderTargetTexture(this.decalTexture) ? this.decalTexture.isReadyForRendering() : this.decalTexture.isReady();
}

/**
Expand All @@ -144,19 +183,160 @@ export class MeshUVSpaceRenderer {
* @param size The size of the projection
* @param angle The rotation angle around the direction of the projection
*/
public renderTexture(texture: BaseTexture, position: Vector3, normal: Vector3, size: Vector3, angle = 0): void {
if (!this.texture) {
public async renderTexture(texture: BaseTexture, position: Vector3, normal: Vector3, size: Vector3, angle = 0): Promise<void> {
// Create the diffuse render target texture if it doesn't exist
if (!this.decalTexture) {
this._createDiffuseRTT();
}

if (MeshUVSpaceRenderer._IsRenderTargetTexture(this.texture)) {
const matrix = this._createProjectionMatrix(position, normal, size, angle);
const shader = MeshUVSpaceRenderer._GetShader(this._scene);
// // Prepare the shader with the decal texture, mask texture, and necessary uniforms
const shader = MeshUVSpaceRenderer._GetShader(this._scene);
shader.setTexture("textureSampler", texture); // Decal texture

// Calculate and set the projection matrix
const projectionMatrix = this._createProjectionMatrix(position, normal, size, angle);
shader.setMatrix("projMatrix", projectionMatrix);

if (MeshUVSpaceRenderer._IsRenderTargetTexture(this.decalTexture)) {
this.decalTexture.render();
if(this._options.uvEdgeBlending) {
await this._createMaskTexture();
this._createFinalTexture();
} else {
this.texture = this.decalTexture;
}
}
}

private _createMaskTexture(): void {
if (this._maskTexture) {
Promise.resolve();
return;
}
try {
this._scene.clearColor = new Color4(0,0,0,1);
// Create a new render target texture for the mask
this._maskTexture = new RenderTargetTexture(
"maskTexture",
{ width: this._options.width, height: this._options.height },
this._scene,
false, // No mipmaps for the mask texture
true,
Constants.TEXTURETYPE_UNSIGNED_BYTE,
false, undefined, undefined, undefined, undefined, Constants.TEXTUREFORMAT_R
);

// Set up the mask material
const maskMaterial = new ShaderMaterial(
"meshUVSpaceRendererMaskerShader",
this._scene,
{
vertex: "meshUVSpaceRendererMasker",
fragment: "meshUVSpaceRendererMasker",
},
{
attributes: ["position", "uv"],
uniforms: ["worldViewProjection"]
}
);

shader.setTexture("textureSampler", texture);
shader.setMatrix("projMatrix", matrix);
let texture = null;
if (this._mesh.material instanceof PBRMaterial) {
texture = (this._mesh.material as PBRMaterial).albedoTexture;
} else if (this._mesh.material instanceof StandardMaterial) {
texture = (this._mesh.material as StandardMaterial).diffuseTexture;
}

if (texture) {
maskMaterial.setTexture("textureSampler", texture);
} else {
console.error("Material does not have a valid texture property.");
}

maskMaterial.backFaceCulling = false;

this._mesh.material = this._mesh.material as PBRMaterial;
maskMaterial.backFaceCulling = false;

// Render the mesh with the mask material to the mask texture
this._maskTexture.renderList.push(this._mesh);
this._maskTexture.setMaterialForRendering(this._mesh, maskMaterial);

// Ensure the mask texture is updated
this._maskTexture.refreshRate = RenderTargetTexture.REFRESHRATE_RENDER_ONCE;
this._scene.customRenderTargets.push(this._maskTexture);
Promise.resolve();
} catch (error) {
console.error("Error creating mask texture:", error);
}
}

this.texture.render();
private _createFinalTexture(): void {
if (!this.texture) {
this.texture = new RenderTargetTexture(
"finalTexture",
{ width: this._options.width, height: this._options.height },
this._scene,
false,
true,
this._options.textureType
);
}

try {
this._scene.clearColor = new Color4(0, 0, 0, 1);

// Set up the shader material
this.finalMaterial = new ShaderMaterial(
"meshUVSpaceRendererFinaliserShader",
this._scene,
{
vertex: "meshUVSpaceRendererFinaliser",
fragment: "meshUVSpaceRendererFinaliser",
},
{
attributes: ["position", "uv"],
uniforms: ["worldViewProjection", "textureSize"],
samplers: ["textureSampler", "maskTextureSampler"]
}
);

this.finalMaterial.setTexture("textureSampler", this.decalTexture);
this.finalMaterial.setTexture("maskTextureSampler", this._maskTexture);
this.finalMaterial.setVector2("textureSize", new Vector2(this._options.width, this._options.height));
this.finalMaterial.backFaceCulling = false;

// Create the post-process only if it hasn't been created already
if (!this._finalPostProcess) {
this._finalPostProcess = new PostProcess(
"finalTexturePostProcess",
"meshUVSpaceRendererFinaliser",
["textureSize"],
["textureSampler", "maskTextureSampler"],
1.0,
null,
Texture.NEAREST_SAMPLINGMODE,
this._scene.getEngine(),
false,
null,
this._options.textureType
);

this._finalPostProcess.onApply = (effect: { setTexture: (arg0: string, arg1: Texture) => void; setVector2: (arg0: string, arg1: Vector2) => void; }) => {
effect.setTexture("textureSampler", this.decalTexture);
effect.setTexture("maskTextureSampler", this._maskTexture);
effect.setVector2("textureSize", new Vector2(this._options.width, this._options.height));
};
if (MeshUVSpaceRenderer._IsRenderTargetTexture(this.texture)) {
this.texture.addPostProcess(this._finalPostProcess);
}
}

if (MeshUVSpaceRenderer._IsRenderTargetTexture(this.texture)) {
this.texture.render();
}
} catch (error) {
console.error("Error creating final texture:", error);
}
}

Expand All @@ -181,7 +361,7 @@ export class MeshUVSpaceRenderer {
this.texture.dispose();
this._textureCreatedInternally = false;
}
}
}

private _createDiffuseRTT(): void {
this._textureCreatedInternally = true;
Expand All @@ -190,7 +370,12 @@ export class MeshUVSpaceRenderer {

texture.setMaterialForRendering(this._mesh, MeshUVSpaceRenderer._GetShader(this._scene));

this.texture = texture;
this.decalTexture = texture;

// Additional check after assignment
if (!this.decalTexture.isReady()) {
console.error("decalTexture is not ready after creation in _createDiffuseRTT.");
}
}

private _createRenderTargetTexture(width: number, height: number): RenderTargetTexture {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,4 @@ void main(void) {
vDecalTC = decalTC.xy;

gl_Position = vec4(uv * 2.0 - 1.0, normalView.z > 0.0 ? 2. : decalTC.z, 1.0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Fragment shader
precision highp float;

// Varyings
varying vec2 vUV;

// Uniforms
uniform sampler2D textureSampler;
uniform sampler2D maskTextureSampler;
uniform vec2 textureSize;

void main() {
vec4 mask = texture2D(maskTextureSampler, vUV).rgba;

if (mask.r > 0.5) {
gl_FragColor = texture2D(textureSampler, vUV);
} else {
vec2 texelSize = 4.0 / textureSize;

vec2 uv_p01 = vUV + vec2(-1.0, 0.0) * texelSize;
vec2 uv_p21 = vUV + vec2(1.0, 0.0) * texelSize;
vec2 uv_p10 = vUV + vec2(0.0, -1.0) * texelSize;
vec2 uv_p12 = vUV + vec2(0.0, 1.0) * texelSize;

float mask_p01 = texture2D(maskTextureSampler, uv_p01).r;
float mask_p21 = texture2D(maskTextureSampler, uv_p21).r;
float mask_p10 = texture2D(maskTextureSampler, uv_p10).r;
float mask_p12 = texture2D(maskTextureSampler, uv_p12).r;

vec4 col = vec4(0.0, 0.0, 0.0, 0.0);
float total_weight = 0.0;

if (mask_p01 > 0.5) {
col += texture2D(textureSampler, uv_p01);
total_weight += 1.0;
}
if (mask_p21 > 0.5) {
col += texture2D(textureSampler, uv_p21);
total_weight += 1.0;
}
if (mask_p10 > 0.5) {
col += texture2D(textureSampler, uv_p10);
total_weight += 1.0;
}
if (mask_p12 > 0.5) {
col += texture2D(textureSampler, uv_p12);
total_weight += 1.0;
}

if (total_weight > 0.0) {
gl_FragColor = col / total_weight;
} else {
gl_FragColor = col;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Vertex shader
precision highp float;

// Attributes
attribute vec3 position;
attribute vec2 uv;

// Uniforms
uniform mat4 worldViewProjection;

// Varyings
varying vec2 vUV;

void main() {
gl_Position = worldViewProjection * vec4(position, 1.0);
vUV = uv;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
varying vec2 vUV;

void main(void) {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
attribute vec2 uv;

varying vec2 vUV;

void main(void) {
gl_Position = vec4(vec2(uv.x, uv.y) * 2.0 - 1.0, 0., 1.0);
vUV = uv;
}

0 comments on commit 460893d

Please sign in to comment.