-
Couldn't load subscription status.
- Fork 3.6k
Merging textures on export #17339
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+701
−248
Merged
Merging textures on export #17339
Changes from 1 commit
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
affbe4d
WIP merging textures for OpenPBR on export
MiiBond 117537f
umd fix
MiiBond 203b521
Convert TextureMerger into separate function exports
MiiBond 5609cc5
Don't export duplicate aniso textures
MiiBond 60a8d8b
Add internal flag to texture merger functions
MiiBond File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
275 changes: 275 additions & 0 deletions
275
packages/dev/core/src/Materials/Textures/textureMerger.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,275 @@ | ||
| import type { Scene } from "../../scene"; | ||
| // import type { Nullable } from "../../types"; | ||
| import type { IProceduralTextureCreationOptions } from "core/Materials/Textures/Procedurals/proceduralTexture"; | ||
| import { ProceduralTexture } from "./Procedurals/proceduralTexture"; | ||
| import type { BaseTexture } from "./baseTexture"; | ||
| import type { TextureSize } from "./textureCreationOptions"; | ||
| import { ShaderLanguage } from "core/Materials/shaderLanguage"; | ||
| import { Logger } from "../../Misc/logger"; | ||
| import { Constants } from "../../Engines/constants"; | ||
| import type { Texture } from "./texture"; | ||
|
|
||
| /** | ||
| * Configuration for a texture input source | ||
| */ | ||
| export interface ITextureChannelInput { | ||
| /** The texture to use as input */ | ||
| texture: BaseTexture; | ||
| /** Source channel to read from (0=R, 1=G, 2=B, 3=A) */ | ||
| sourceChannel: number; | ||
| } | ||
|
|
||
| /** | ||
| * Configuration for a constant value input source | ||
| */ | ||
| export interface IConstantChannelInput { | ||
| /** Constant value between 0.0 and 1.0 */ | ||
| value: number; | ||
| } | ||
|
|
||
| /** | ||
| * Union type for channel input sources | ||
| */ | ||
| export type ChannelInput = ITextureChannelInput | IConstantChannelInput; | ||
|
|
||
| /** | ||
| * Configuration for texture merging operation | ||
| */ | ||
| export interface ITextureMergeConfiguration { | ||
| /** Configuration for red output channel */ | ||
| red: ChannelInput; | ||
| /** Configuration for green output channel (optional, defaults to 0) */ | ||
| green?: ChannelInput; | ||
| /** Configuration for blue output channel (optional, defaults to 0) */ | ||
| blue?: ChannelInput; | ||
| /** Configuration for alpha output channel (optional, defaults to 1) */ | ||
| alpha?: ChannelInput; | ||
| /** Output texture size. If not specified, uses the largest input texture size */ | ||
| outputSize?: TextureSize; | ||
| /** Whether to generate mipmaps for the output texture */ | ||
| generateMipMaps?: boolean; | ||
| } | ||
|
|
||
| /** | ||
| * Utility class for merging multiple textures into a single texture using ProceduralTexture | ||
| * Each output channel can be sourced from a texture channel or a constant value | ||
| */ | ||
| export class TextureMerger { | ||
MiiBond marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
MiiBond marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| private static _ShaderName = "textureMerger"; | ||
|
|
||
| /** | ||
| * Get the class name of the texture. | ||
| * @returns "ThinTexture" | ||
| */ | ||
| public getClassName(): string { | ||
| return "TextureMerger"; | ||
| } | ||
|
|
||
| /** | ||
| * Check if a channel input is a texture input | ||
| * @param input The channel input to check | ||
| * @returns True if the input is a texture input, false otherwise | ||
| */ | ||
| private static _IsTextureInput(input: ChannelInput): input is ITextureChannelInput { | ||
| return "texture" in input; | ||
| } | ||
|
|
||
| /** | ||
| * Check if a channel input is a constant input | ||
| * @param input The channel input to check | ||
| * @returns True if the input is a constant input, false otherwise | ||
| */ | ||
| private static _IsConstantInput(input: ChannelInput): input is IConstantChannelInput { | ||
| return "value" in input; | ||
| } | ||
|
|
||
| private static _CopyTextureTransform(source: Texture, destination: Texture) { | ||
alexchuber marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| destination.uOffset = source.uOffset; | ||
| destination.vOffset = source.vOffset; | ||
| destination.uScale = source.uScale; | ||
| destination.vScale = source.vScale; | ||
| destination.uAng = source.uAng; | ||
| destination.vAng = source.vAng; | ||
| destination.wAng = source.wAng; | ||
| destination.uRotationCenter = source.uRotationCenter; | ||
| destination.vRotationCenter = source.vRotationCenter; | ||
| } | ||
|
|
||
| /** | ||
| * Merge multiple texture channels into a single texture | ||
| * @param name Name for the resulting texture | ||
| * @param config Merge configuration | ||
| * @param scene Scene to create the texture in | ||
| * @returns The merged texture | ||
| */ | ||
| public static async MergeTexturesAsync(name: string, config: ITextureMergeConfiguration, scene: Scene): Promise<ProceduralTexture> { | ||
| Logger.Log(`TextureMerger.MergeTextures called with: ${name}`); | ||
MiiBond marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| const channels = [config.red, config.green, config.blue, config.alpha]; | ||
| const textureInputs: BaseTexture[] = []; | ||
| const textureInputMap: number[] = []; // Maps channel index to texture input index (-1 for constants) | ||
|
|
||
| // Collect unique textures and validate inputs | ||
| for (let channelIndex = 0; channelIndex < 4; channelIndex++) { | ||
| const channel = channels[channelIndex]; | ||
| if (channel) { | ||
| if (this._IsTextureInput(channel)) { | ||
| // Validate source channel | ||
| if (channel.sourceChannel < 0 || channel.sourceChannel > 3) { | ||
| throw new Error("Source channel must be between 0 and 3 (R, G, B, A)"); | ||
| } | ||
|
|
||
| // Find or add texture to inputs | ||
| let textureIndex = textureInputs.indexOf(channel.texture); | ||
| if (textureIndex === -1) { | ||
| textureIndex = textureInputs.length; | ||
| textureInputs.push(channel.texture); | ||
| } | ||
| textureInputMap[channelIndex] = textureIndex; | ||
| } else if (this._IsConstantInput(channel)) { | ||
| // Validate constant value | ||
| if (channel.value < 0 || channel.value > 1) { | ||
| throw new Error("Constant value must be between 0.0 and 1.0"); | ||
| } | ||
| textureInputMap[channelIndex] = -1; | ||
| } else { | ||
| throw new Error("Invalid channel input configuration"); | ||
| } | ||
| } else { | ||
| textureInputMap[channelIndex] = -1; | ||
| } | ||
| } | ||
|
|
||
| // Determine output size | ||
| let outputSize = config.outputSize; | ||
| if (!outputSize && textureInputs.length > 0) { | ||
| // Use the largest texture size | ||
| let maxSize = 0; | ||
| for (const texture of textureInputs) { | ||
| const size = texture.getSize(); | ||
| const currentSize = Math.max(size.width, size.height); | ||
| if (currentSize > maxSize) { | ||
| maxSize = currentSize; | ||
| outputSize = size.width === size.height ? maxSize : size; | ||
| } | ||
| } | ||
| } | ||
| outputSize = outputSize || 512; // Fallback size | ||
|
|
||
| // Generate shader defines | ||
| const defines: string[] = []; | ||
| const usedTextures = new Set<number>(); | ||
|
|
||
| for (let channelIndex = 0; channelIndex < 4; channelIndex++) { | ||
| const channel = channels[channelIndex]; | ||
| const channelName = ["RED", "GREEN", "BLUE", "ALPHA"][channelIndex]; | ||
|
|
||
| if (channel && this._IsTextureInput(channel)) { | ||
| defines.push(`${channelName}_FROM_TEXTURE`); | ||
| const textureIndex = textureInputMap[channelIndex]; | ||
| usedTextures.add(textureIndex); | ||
| } | ||
| } | ||
|
|
||
| // Add texture defines for used textures | ||
| for (const textureIndex of usedTextures) { | ||
| defines.push(`USE_TEXTURE${textureIndex}`); | ||
| } | ||
|
|
||
| // Create the procedural texture | ||
| const outputTextureOptions: IProceduralTextureCreationOptions = { | ||
| type: Constants.TEXTURETYPE_HALF_FLOAT, | ||
| format: Constants.TEXTUREFORMAT_RGBA, | ||
| samplingMode: Constants.TEXTURE_NEAREST_SAMPLINGMODE, | ||
| generateDepthBuffer: false, | ||
| generateMipMaps: false, | ||
| shaderLanguage: scene.getEngine().isWebGPU ? ShaderLanguage.WGSL : ShaderLanguage.GLSL, | ||
| extraInitializationsAsync: async () => { | ||
| if (scene.getEngine().isWebGPU) { | ||
| await Promise.all([import("../../ShadersWGSL/textureMerger.fragment")]); | ||
| } else { | ||
| await Promise.all([import("../../Shaders/textureMerger.fragment")]); | ||
| } | ||
| }, | ||
| }; | ||
| const proceduralTexture = new ProceduralTexture(name, outputSize, this._ShaderName, scene, outputTextureOptions); | ||
| proceduralTexture.refreshRate = -1; // Do not auto-refresh | ||
|
|
||
| // Set the defines | ||
| proceduralTexture.defines = defines.length > 0 ? "#define " + defines.join("\n#define ") + "\n" : ""; | ||
|
|
||
| // Set up texture inputs | ||
| for (let i = 0; i < textureInputs.length; i++) { | ||
| TextureMerger._CopyTextureTransform(textureInputs[i] as Texture, proceduralTexture); | ||
| proceduralTexture.setTexture(`inputTexture${i}`, textureInputs[i]); | ||
| } | ||
|
|
||
| // Set up channel configuration | ||
| for (let channelIndex = 0; channelIndex < 4; channelIndex++) { | ||
| const channel = channels[channelIndex]; | ||
| const channelName = ["red", "green", "blue", "alpha"][channelIndex]; | ||
|
|
||
| if (channel && this._IsTextureInput(channel)) { | ||
| const textureIndex = textureInputMap[channelIndex]; | ||
| proceduralTexture.setInt(`${channelName}TextureIndex`, textureIndex); | ||
| proceduralTexture.setInt(`${channelName}SourceChannel`, channel.sourceChannel); | ||
| } else { | ||
| // Use constant value (either provided or default) | ||
| let constantValue: number; | ||
| if (channel && this._IsConstantInput(channel)) { | ||
| constantValue = channel.value; | ||
| } else { | ||
| // Use default values: 0 for RGB, 1 for alpha | ||
| constantValue = channelIndex === 3 ? 1.0 : 0.0; | ||
| } | ||
| proceduralTexture.setFloat(`${channelName}ConstantValue`, constantValue); | ||
| } | ||
| } | ||
|
|
||
| return await new Promise<ProceduralTexture>((resolve, reject) => { | ||
| // Compile and render | ||
| proceduralTexture.executeWhenReady(() => { | ||
| try { | ||
| proceduralTexture.render(); | ||
| resolve(proceduralTexture); | ||
| } catch (error) { | ||
| reject(error instanceof Error ? error : new Error(String(error))); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Create a texture input configuration | ||
| * @param texture The texture to read from | ||
| * @param sourceChannel The channel to read (0=R, 1=G, 2=B, 3=A) | ||
| * @returns Texture channel input configuration | ||
| */ | ||
| public static CreateTextureInput(texture: BaseTexture, sourceChannel: number): ITextureChannelInput { | ||
| return { texture, sourceChannel }; | ||
| } | ||
|
|
||
| /** | ||
| * Create a constant value input configuration | ||
| * @param value The constant value (0.0-1.0) | ||
| * @returns Constant channel input configuration | ||
| */ | ||
| public static CreateConstantInput(value: number): IConstantChannelInput { | ||
| return { value }; | ||
| } | ||
|
|
||
| /** | ||
| * Create a simple RGBA channel packing configuration | ||
| * @param red Input for red channel | ||
| * @param green Input for green channel (optional, defaults to 0) | ||
| * @param blue Input for blue channel (optional, defaults to 0) | ||
| * @param alpha Input for alpha channel (optional, defaults to 1) | ||
| * @returns Texture merge configuration | ||
| */ | ||
| public static CreateRGBAConfiguration(red: ChannelInput, green?: ChannelInput, blue?: ChannelInput, alpha?: ChannelInput): ITextureMergeConfiguration { | ||
| return { red, green, blue, alpha }; | ||
| } | ||
| } | ||
|
|
||
| // Debug: Check if class is properly exported | ||
MiiBond marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // Logger.Log("TextureMerger class defined:", typeof TextureMerger); | ||
| // Logger.Log("TextureMerger.MergeTextures:", typeof TextureMerger.MergeTextures); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.