-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial implementation for Area Lights (#16078)
Added initial implementation of Area Lights. Initial light type supported is RectAreaLight and support is only available in Standard and PBR shaders. --------- Co-authored-by: Gary Hsu <[email protected]> Co-authored-by: Popov72 <[email protected]>
- Loading branch information
1 parent
f2a7eab
commit 5484832
Showing
36 changed files
with
1,034 additions
and
88 deletions.
There are no files selected for viewing
This file contains 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,45 @@ | ||
import type { BaseTexture } from "core/Materials/Textures/baseTexture"; | ||
import { Tools } from "core/Misc/tools"; | ||
import type { Tuple } from "core/types"; | ||
|
||
/** | ||
* Linearly transformed cosine textures that are used in the Area Lights shaders. | ||
*/ | ||
export type ILTCTextures = { | ||
/** | ||
* Linearly transformed cosine texture BRDF Approximation. | ||
*/ | ||
LTC1: BaseTexture; | ||
|
||
/** | ||
* Linearly transformed cosine texture Fresnel Approximation. | ||
*/ | ||
LTC2: BaseTexture; | ||
}; | ||
|
||
/** | ||
* Loads LTC texture data from Babylon.js CDN. | ||
* @returns Promise with data for LTC1 and LTC2 textures for area lights. | ||
*/ | ||
export async function DecodeLTCTextureDataAsync(): Promise<Tuple<Uint16Array, 2>> { | ||
const ltc1 = new Uint16Array(64 * 64 * 4); | ||
const ltc2 = new Uint16Array(64 * 64 * 4); | ||
const file = await Tools.LoadFileAsync(Tools.GetAssetUrl("https://assets.babylonjs.com/core/areaLights/areaLightsLTC.bin")); | ||
const ltcEncoded = new Uint16Array(file); | ||
|
||
const pixelCount = ltcEncoded.length / 8; | ||
|
||
for (let pixelIndex = 0; pixelIndex < pixelCount; pixelIndex++) { | ||
ltc1[pixelIndex * 4] = ltcEncoded[pixelIndex * 8]; | ||
ltc1[pixelIndex * 4 + 1] = ltcEncoded[pixelIndex * 8 + 1]; | ||
ltc1[pixelIndex * 4 + 2] = ltcEncoded[pixelIndex * 8 + 2]; | ||
ltc1[pixelIndex * 4 + 3] = ltcEncoded[pixelIndex * 8 + 3]; | ||
|
||
ltc2[pixelIndex * 4] = ltcEncoded[pixelIndex * 8 + 4]; | ||
ltc2[pixelIndex * 4 + 1] = ltcEncoded[pixelIndex * 8 + 5]; | ||
ltc2[pixelIndex * 4 + 2] = ltcEncoded[pixelIndex * 8 + 6]; | ||
ltc2[pixelIndex * 4 + 3] = ltcEncoded[pixelIndex * 8 + 7]; | ||
} | ||
|
||
return [ltc1, ltc2]; | ||
} |
This file contains 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,114 @@ | ||
import type { Vector3 } from "core/Maths/math.vector"; | ||
import { RawTexture } from "core/Materials/Textures/rawTexture"; | ||
import { Texture } from "core/Materials/Textures/texture"; | ||
import { Constants } from "core/Engines/constants"; | ||
import { Light } from "core/Lights/light"; | ||
import type { Effect } from "core/Materials/effect"; | ||
import type { ILTCTextures } from "core/Lights/LTC/ltcTextureTool"; | ||
import { DecodeLTCTextureDataAsync } from "core/Lights/LTC/ltcTextureTool"; | ||
import type { Scene } from "core/scene"; | ||
import { Logger } from "core/Misc/logger"; | ||
|
||
declare module "../scene" { | ||
export interface Scene { | ||
/** | ||
* @internal | ||
*/ | ||
_ltcTextures?: ILTCTextures; | ||
} | ||
} | ||
|
||
function CreateSceneLTCTextures(scene: Scene): void { | ||
const useDelayedTextureLoading = scene.useDelayedTextureLoading; | ||
scene.useDelayedTextureLoading = false; | ||
|
||
const previousState = scene._blockEntityCollection; | ||
scene._blockEntityCollection = false; | ||
|
||
scene._ltcTextures = { | ||
LTC1: RawTexture.CreateRGBATexture(null, 64, 64, scene.getEngine(), false, false, Constants.TEXTURE_LINEAR_LINEAR, Constants.TEXTURETYPE_HALF_FLOAT, 0, false, true), | ||
LTC2: RawTexture.CreateRGBATexture(null, 64, 64, scene.getEngine(), false, false, Constants.TEXTURE_LINEAR_LINEAR, Constants.TEXTURETYPE_HALF_FLOAT, 0, false, true), | ||
}; | ||
|
||
scene._blockEntityCollection = previousState; | ||
|
||
scene._ltcTextures.LTC1.wrapU = Texture.CLAMP_ADDRESSMODE; | ||
scene._ltcTextures.LTC1.wrapV = Texture.CLAMP_ADDRESSMODE; | ||
|
||
scene._ltcTextures.LTC2.wrapU = Texture.CLAMP_ADDRESSMODE; | ||
scene._ltcTextures.LTC2.wrapV = Texture.CLAMP_ADDRESSMODE; | ||
|
||
scene.useDelayedTextureLoading = useDelayedTextureLoading; | ||
|
||
DecodeLTCTextureDataAsync() | ||
.then((textureData) => { | ||
if (scene._ltcTextures) { | ||
const ltc1 = scene._ltcTextures?.LTC1 as RawTexture; | ||
ltc1.update(textureData[0]); | ||
|
||
const ltc2 = scene._ltcTextures?.LTC2 as RawTexture; | ||
ltc2.update(textureData[1]); | ||
|
||
scene.onDisposeObservable.addOnce(() => { | ||
scene._ltcTextures?.LTC1.dispose(); | ||
scene._ltcTextures?.LTC2.dispose(); | ||
}); | ||
} | ||
}) | ||
.catch((error) => { | ||
Logger.Error(`Area Light fail to get LTC textures data. Error: ${error}`); | ||
}); | ||
} | ||
|
||
/** | ||
* Abstract Area Light class that servers as parent for all Area Lights implementations. | ||
* The light is emitted from the area in the -Z direction. | ||
*/ | ||
export abstract class AreaLight extends Light { | ||
/** | ||
* Area Light position. | ||
*/ | ||
public position: Vector3; | ||
|
||
/** | ||
* Creates a area light object. | ||
* Documentation : https://doc.babylonjs.com/features/featuresDeepDive/lights/lights_introduction | ||
* @param name The friendly name of the light | ||
* @param position The position of the area light. | ||
* @param scene The scene the light belongs to | ||
*/ | ||
constructor(name: string, position: Vector3, scene?: Scene) { | ||
super(name, scene); | ||
this.position = position; | ||
|
||
if (!this._scene._ltcTextures) { | ||
CreateSceneLTCTextures(this._scene); | ||
} | ||
} | ||
|
||
public override transferTexturesToEffect(effect: Effect): Light { | ||
if (this._scene._ltcTextures) { | ||
effect.setTexture("areaLightsLTC1Sampler", this._scene._ltcTextures.LTC1); | ||
effect.setTexture("areaLightsLTC2Sampler", this._scene._ltcTextures.LTC2); | ||
} | ||
return this; | ||
} | ||
|
||
/** | ||
* Prepares the list of defines specific to the light type. | ||
* @param defines the list of defines | ||
* @param lightIndex defines the index of the light for the effect | ||
*/ | ||
public prepareLightSpecificDefines(defines: any, lightIndex: number): void { | ||
defines["AREALIGHT" + lightIndex] = true; | ||
defines["AREALIGHTUSED"] = true; | ||
} | ||
|
||
public override _isReady(): boolean { | ||
if (this._scene._ltcTextures) { | ||
return this._scene._ltcTextures.LTC1.isReady() && this._scene._ltcTextures.LTC2.isReady(); | ||
} | ||
|
||
return false; | ||
} | ||
} |
This file contains 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
This file contains 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
This file contains 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
This file contains 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,142 @@ | ||
import { Vector3 } from "../Maths/math.vector"; | ||
import { Node } from "../node"; | ||
import { Light } from "./light"; | ||
import type { Effect } from "core/Materials/effect"; | ||
import { RegisterClass } from "core/Misc/typeStore"; | ||
import { serialize } from "../Misc/decorators"; | ||
import type { Scene } from "core/scene"; | ||
import { AreaLight } from "./areaLight"; | ||
|
||
Node.AddNodeConstructor("Light_Type_4", (name, scene) => { | ||
return () => new RectAreaLight(name, Vector3.Zero(), 1, 1, scene); | ||
}); | ||
|
||
/** | ||
* A rectangular area light defined by an unique point in world space, a width and a height. | ||
* The light is emitted from the rectangular area in the -Z direction. | ||
*/ | ||
export class RectAreaLight extends AreaLight { | ||
private readonly _width: Vector3; | ||
private readonly _height: Vector3; | ||
protected readonly _pointTransformedPosition: Vector3; | ||
protected readonly _pointTransformedWidth: Vector3; | ||
protected readonly _pointTransformedHeight: Vector3; | ||
|
||
/** | ||
* Rect Area Light width. | ||
*/ | ||
@serialize() | ||
public get width(): number { | ||
return this._width.x; | ||
} | ||
/** | ||
* Rect Area Light width. | ||
*/ | ||
public set width(value: number) { | ||
this._width.x = value; | ||
} | ||
|
||
/** | ||
* Rect Area Light height. | ||
*/ | ||
@serialize() | ||
public get height(): number { | ||
return this._height.y; | ||
} | ||
/** | ||
* Rect Area Light height. | ||
*/ | ||
public set height(value: number) { | ||
this._height.y = value; | ||
} | ||
|
||
/** | ||
* Creates a rectangular area light object. | ||
* Documentation : https://doc.babylonjs.com/features/featuresDeepDive/lights/lights_introduction | ||
* @param name The friendly name of the light | ||
* @param position The position of the area light. | ||
* @param width The width of the area light. | ||
* @param height The height of the area light. | ||
* @param scene The scene the light belongs to | ||
*/ | ||
constructor(name: string, position: Vector3, width: number, height: number, scene?: Scene) { | ||
super(name, position, scene); | ||
this._width = new Vector3(width, 0, 0); | ||
this._height = new Vector3(0, height, 0); | ||
this._pointTransformedPosition = Vector3.Zero(); | ||
this._pointTransformedWidth = Vector3.Zero(); | ||
this._pointTransformedHeight = Vector3.Zero(); | ||
} | ||
|
||
/** | ||
* Returns the string "RectAreaLight" | ||
* @returns the class name | ||
*/ | ||
public override getClassName(): string { | ||
return "RectAreaLight"; | ||
} | ||
|
||
/** | ||
* Returns the integer 4. | ||
* @returns The light Type id as a constant defines in Light.LIGHTTYPEID_x | ||
*/ | ||
public override getTypeID(): number { | ||
return Light.LIGHTTYPEID_RECT_AREALIGHT; | ||
} | ||
|
||
protected _buildUniformLayout(): void { | ||
this._uniformBuffer.addUniform("vLightData", 4); | ||
this._uniformBuffer.addUniform("vLightDiffuse", 4); | ||
this._uniformBuffer.addUniform("vLightSpecular", 4); | ||
this._uniformBuffer.addUniform("vLightWidth", 4); | ||
this._uniformBuffer.addUniform("vLightHeight", 4); | ||
this._uniformBuffer.addUniform("shadowsInfo", 3); | ||
this._uniformBuffer.addUniform("depthValues", 2); | ||
this._uniformBuffer.create(); | ||
} | ||
|
||
protected _computeTransformedInformation(): boolean { | ||
if (this.parent && this.parent.getWorldMatrix) { | ||
Vector3.TransformCoordinatesToRef(this.position, this.parent.getWorldMatrix(), this._pointTransformedPosition); | ||
Vector3.TransformNormalToRef(this._width, this.parent.getWorldMatrix(), this._pointTransformedWidth); | ||
Vector3.TransformNormalToRef(this._height, this.parent.getWorldMatrix(), this._pointTransformedHeight); | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Sets the passed Effect "effect" with the PointLight transformed position (or position, if none) and passed name (string). | ||
* @param effect The effect to update | ||
* @param lightIndex The index of the light in the effect to update | ||
* @returns The point light | ||
*/ | ||
public transferToEffect(effect: Effect, lightIndex: string): RectAreaLight { | ||
if (this._computeTransformedInformation()) { | ||
this._uniformBuffer.updateFloat4("vLightData", this._pointTransformedPosition.x, this._pointTransformedPosition.y, this._pointTransformedPosition.z, 0, lightIndex); | ||
this._uniformBuffer.updateFloat4("vLightWidth", this._pointTransformedWidth.x / 2, this._pointTransformedWidth.y / 2, this._pointTransformedWidth.z / 2, 0, lightIndex); | ||
this._uniformBuffer.updateFloat4( | ||
"vLightHeight", | ||
this._pointTransformedHeight.x / 2, | ||
this._pointTransformedHeight.y / 2, | ||
this._pointTransformedHeight.z / 2, | ||
0, | ||
lightIndex | ||
); | ||
} else { | ||
this._uniformBuffer.updateFloat4("vLightData", this.position.x, this.position.y, this.position.z, 0.0, lightIndex); | ||
this._uniformBuffer.updateFloat4("vLightWidth", this._width.x / 2, this._width.y / 2, this._width.z / 2, 0.0, lightIndex); | ||
this._uniformBuffer.updateFloat4("vLightHeight", this._height.x / 2, this._height.y / 2, this._height.z / 2, 0.0, lightIndex); | ||
} | ||
return this; | ||
} | ||
|
||
public transferToNodeMaterialEffect(effect: Effect, lightDataUniformName: string) { | ||
// TO DO: Implement this to add support for NME. | ||
return this; | ||
} | ||
} | ||
|
||
// Register Class Name | ||
RegisterClass("BABYLON.RectAreaLight", RectAreaLight); |
This file contains 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.