Skip to content

Commit 2b0ed11

Browse files
mvaligurskyMartin Valigursky
andauthored
Moved area light LUT functionality to separate file (playcanvas#3160)
Co-authored-by: Martin Valigursky <mvaligursky@snapchat.com>
1 parent bbfdf57 commit 2b0ed11

File tree

3 files changed

+149
-145
lines changed

3 files changed

+149
-145
lines changed

src/framework/application.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
} from '../scene/constants.js';
2525
import { BatchManager } from '../scene/batching/batch-manager.js';
2626
import { ForwardRenderer } from '../scene/renderer/forward-renderer.js';
27+
import { AreaLightLuts } from '../scene/area-light-luts.js';
2728
import { ImmediateData } from '../scene/immediate.js';
2829
import { Layer } from '../scene/layer.js';
2930
import { LayerComposition } from '../scene/layer-composition.js';
@@ -525,6 +526,9 @@ class Application extends EventHandler {
525526
}
526527
});
527528

529+
// placeholder texture for area light LUTs
530+
AreaLightLuts.createPlaceholder(this.graphicsDevice);
531+
528532
this.renderer = new ForwardRenderer(this.graphicsDevice);
529533
this.renderer.scene = this.scene;
530534
this.lightmapper = new Lightmapper(this.graphicsDevice, this.root, this.scene, this.renderer, this.assets);
@@ -1517,9 +1521,9 @@ class Application extends EventHandler {
15171521
*/
15181522
setAreaLightLuts(asset) {
15191523
if (asset) {
1520-
var renderer = this.renderer;
1524+
const device = this.graphicsDevice;
15211525
asset.ready(function (asset) {
1522-
renderer._uploadAreaLightLuts(asset.resource);
1526+
AreaLightLuts.set(device, asset.resource);
15231527
});
15241528
this.assets.load(asset);
15251529
} else {

src/scene/area-light-luts.js

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { math } from '../math/math.js';
2+
import { Texture } from '../graphics/texture.js';
3+
4+
import {
5+
ADDRESS_CLAMP_TO_EDGE,
6+
FILTER_LINEAR, FILTER_NEAREST,
7+
PIXELFORMAT_R8_G8_B8_A8, PIXELFORMAT_RGBA16F, PIXELFORMAT_RGBA32F,
8+
TEXTURETYPE_DEFAULT
9+
} from '../graphics/constants.js';
10+
11+
// static class managing LUT tables for the area lights
12+
class AreaLightLuts {
13+
static createTexture(device, format, size) {
14+
const tex = new Texture(device, {
15+
width: size,
16+
height: size,
17+
format: format,
18+
addressU: ADDRESS_CLAMP_TO_EDGE,
19+
addressV: ADDRESS_CLAMP_TO_EDGE,
20+
type: TEXTURETYPE_DEFAULT,
21+
magFilter: FILTER_LINEAR,
22+
minFilter: FILTER_NEAREST,
23+
anisotropy: 1
24+
});
25+
tex.name = 'AreaLightLUT';
26+
return tex;
27+
}
28+
29+
static setUniforms(device, texture1, texture2) {
30+
device.scope.resolve('areaLightsLutTex1').setValue(texture1);
31+
device.scope.resolve('areaLightsLutTex2').setValue(texture2);
32+
}
33+
34+
// placeholder LUT textures for area light
35+
static createPlaceholder(device) {
36+
const texture = AreaLightLuts.createTexture(device, PIXELFORMAT_R8_G8_B8_A8, 2);
37+
38+
const pixels = texture.lock();
39+
pixels.fill(0);
40+
texture.unlock();
41+
42+
AreaLightLuts.setUniforms(device, texture, texture);
43+
}
44+
45+
// creates LUT texture used by area lights
46+
static set(device, resource) {
47+
48+
function buildTexture(device, data, format) {
49+
const texture = AreaLightLuts.createTexture(device, format, 64);
50+
51+
texture.lock().set(data);
52+
texture.unlock();
53+
texture.upload();
54+
55+
return texture;
56+
}
57+
58+
function offsetScale(data, offset, scale) {
59+
60+
const count = data.length;
61+
const ret = new Float32Array(count);
62+
for (let i = 0; i < count; i++) {
63+
const n = i % 4;
64+
ret[i] = (data[i] + offset[n]) * scale[n];
65+
}
66+
return ret;
67+
}
68+
69+
function convertToHalfFloat(data) {
70+
71+
const count = data.length;
72+
const ret = new Uint16Array(count);
73+
const float2Half = math.float2Half;
74+
for (let i = 0; i < count; i++) {
75+
ret[i] = float2Half(data[i]);
76+
}
77+
78+
return ret;
79+
}
80+
81+
function convertToUint(data) {
82+
83+
const count = data.length;
84+
const ret = new Uint8ClampedArray(count);
85+
for (let i = 0; i < count; i++) {
86+
ret[i] = data[i] * 255;
87+
}
88+
89+
return ret;
90+
}
91+
92+
const versions = new Int16Array(resource, 0, 2);
93+
const majorVersion = versions[0];
94+
const minorVersion = versions[1];
95+
96+
if (majorVersion !== 0 || minorVersion !== 1) {
97+
console.warn(`areaLightLuts asset version: ${majorVersion}.${minorVersion} is not supported in current engine version!`);
98+
} else {
99+
100+
const srcData1 = new Float32Array(resource, 4, 16384);
101+
const srcData2 = new Float32Array(resource, 4 + 16384 * 4, 16384);
102+
103+
// pick format for lut texture
104+
let data1, data2;
105+
const format = device.areaLightLutFormat;
106+
if (format === PIXELFORMAT_RGBA32F) {
107+
108+
// float
109+
data1 = srcData1;
110+
data2 = srcData2;
111+
112+
} else if (format === PIXELFORMAT_RGBA16F) {
113+
114+
// half float
115+
data1 = convertToHalfFloat(srcData1);
116+
data2 = convertToHalfFloat(srcData2);
117+
118+
} else {
119+
120+
// low precision format
121+
// offset and scale to avoid clipping and increase precision - this is undone in the shader
122+
const o1 = [0.0, 0.2976, 0.01381, 0.0];
123+
const s1 = [0.999, 3.08737, 1.6546, 0.603249];
124+
125+
const o2 = [-0.306897, 0.0, 0.0, 0.0];
126+
const s2 = [1.442787, 1.0, 1.0, 1.0];
127+
128+
data1 = convertToUint(offsetScale(srcData1, o1, s1));
129+
data2 = convertToUint(offsetScale(srcData2, o2, s2));
130+
}
131+
132+
// create lut textures
133+
const tex1 = buildTexture(device, data1, format);
134+
const tex2 = buildTexture(device, data2, format);
135+
136+
// assign to uniforms
137+
AreaLightLuts.setUniforms(device, tex1, tex2);
138+
}
139+
}
140+
}
141+
142+
export { AreaLightLuts };

src/scene/renderer/forward-renderer.js

Lines changed: 1 addition & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { now } from '../../core/time.js';
22

3-
import { math } from '../../math/math.js';
43
import { Mat3 } from '../../math/mat3.js';
54
import { Mat4 } from '../../math/mat4.js';
65
import { Vec3 } from '../../math/vec3.js';
@@ -9,20 +8,15 @@ import { BoundingBox } from '../../shape/bounding-box.js';
98
import { BoundingSphere } from '../../shape/bounding-sphere.js';
109

1110
import {
12-
ADDRESS_CLAMP_TO_EDGE,
1311
BUFFER_DYNAMIC,
1412
CLEARFLAG_COLOR, CLEARFLAG_DEPTH, CLEARFLAG_STENCIL,
1513
CULLFACE_BACK, CULLFACE_FRONT, CULLFACE_FRONTANDBACK, CULLFACE_NONE,
16-
FILTER_LINEAR, FILTER_NEAREST,
1714
FUNC_ALWAYS, FUNC_LESSEQUAL,
18-
PIXELFORMAT_R8_G8_B8_A8, PIXELFORMAT_RGBA16F, PIXELFORMAT_RGBA32F,
1915
PRIMITIVE_TRIANGLES,
2016
SEMANTIC_ATTR, SEMANTIC_POSITION,
21-
STENCILOP_KEEP,
22-
TEXTURETYPE_DEFAULT
17+
STENCILOP_KEEP
2318
} from '../../graphics/constants.js';
2419
import { IndexBuffer } from '../../graphics/index-buffer.js';
25-
import { Texture } from '../../graphics/texture.js';
2620
import { VertexBuffer } from '../../graphics/vertex-buffer.js';
2721
import { VertexFormat } from '../../graphics/vertex-format.js';
2822

@@ -194,9 +188,6 @@ class ForwardRenderer {
194188
this.ambientColor = new Float32Array(3);
195189

196190
this.cameraParams = new Float32Array(4);
197-
198-
// placeholder texture for area light LUTs
199-
this._createAreaLightPlaceholderLuts();
200191
}
201192

202193
destroy() {
@@ -515,139 +506,6 @@ class ForwardRenderer {
515506
}
516507
}
517508

518-
// placeholder LUT textures for area light
519-
_createAreaLightPlaceholderLuts() {
520-
var placeholderLutTex = new Texture(this.device, {
521-
width: 2,
522-
height: 2,
523-
format: PIXELFORMAT_R8_G8_B8_A8
524-
});
525-
placeholderLutTex.name = 'placeholder';
526-
527-
var pixels = placeholderLutTex.lock();
528-
for (var i = 0; i < 4; i++) {
529-
for (var c = 0; c < 4; c++) {
530-
pixels[i * 4 + c] = 0;
531-
}
532-
}
533-
placeholderLutTex.unlock();
534-
535-
this.device.scope.resolve('areaLightsLutTex1').setValue(placeholderLutTex);
536-
this.device.scope.resolve('areaLightsLutTex2').setValue(placeholderLutTex);
537-
}
538-
539-
// creates LUT texture used by area lights
540-
_uploadAreaLightLuts(resource) {
541-
542-
function createTexture(device, data, format) {
543-
var tex = new Texture(device, {
544-
width: 64,
545-
height: 64,
546-
format: format,
547-
addressU: ADDRESS_CLAMP_TO_EDGE,
548-
addressV: ADDRESS_CLAMP_TO_EDGE,
549-
type: TEXTURETYPE_DEFAULT,
550-
magFilter: FILTER_LINEAR,
551-
minFilter: FILTER_NEAREST,
552-
anisotropy: 1
553-
});
554-
555-
tex.lock().set(data);
556-
tex.unlock();
557-
tex.upload();
558-
559-
return tex;
560-
}
561-
562-
function offsetScale(data, offset, scale) {
563-
564-
var count = data.length;
565-
var ret = new Float32Array(count);
566-
for (var i = 0; i < count; i++) {
567-
var n = i % 4;
568-
ret[i] = (data[i] + offset[n]) * scale[n];
569-
}
570-
return ret;
571-
}
572-
573-
function convertToHalfFloat(data) {
574-
575-
var count = data.length;
576-
var ret = new Uint16Array(count);
577-
var float2Half = math.float2Half;
578-
for (var i = 0; i < count; i++) {
579-
ret[i] = float2Half(data[i]);
580-
}
581-
582-
return ret;
583-
}
584-
585-
function convertToUint(data) {
586-
587-
var count = data.length;
588-
var ret = new Uint8ClampedArray(count);
589-
for (var i = 0; i < count; i++) {
590-
ret[i] = data[i] * 255;
591-
}
592-
593-
return ret;
594-
}
595-
596-
// create lut textures
597-
var versions = new Int16Array(resource, 0, 2);
598-
599-
var luts = {
600-
data1: new Float32Array(resource, 4, 16384),
601-
data2: new Float32Array(resource, 4 + 16384 * 4, 16384),
602-
majorVersion: versions[0],
603-
minorVersion: versions[1]
604-
};
605-
606-
if (luts.majorVersion !== 0 || luts.minorVersion !== 1) {
607-
console.warn(`areaLightLuts asset version: ${luts.majorVersion}.${luts.minorVersion} is not supported in current engine version!`);
608-
} else {
609-
var device = this.device;
610-
var data1, data2;
611-
var format = device.areaLightLutFormat;
612-
613-
// pick format for lut texture
614-
if (format === PIXELFORMAT_RGBA32F) {
615-
616-
// float
617-
data1 = luts.data1;
618-
data2 = luts.data2;
619-
620-
} else if (format === PIXELFORMAT_RGBA16F) {
621-
622-
// half float
623-
data1 = convertToHalfFloat(luts.data1);
624-
data2 = convertToHalfFloat(luts.data2);
625-
626-
} else {
627-
628-
// low precision format
629-
// offset and scale to avoid clipping and increase precision - this is undone in the shader
630-
631-
var o1 = [0.0, 0.2976, 0.01381, 0.0];
632-
var s1 = [0.999, 3.08737, 1.6546, 0.603249];
633-
634-
var o2 = [-0.306897, 0.0, 0.0, 0.0];
635-
var s2 = [1.442787, 1.0, 1.0, 1.0];
636-
637-
data1 = convertToUint(offsetScale(luts.data1, o1, s1));
638-
data2 = convertToUint(offsetScale(luts.data2, o2, s2));
639-
640-
}
641-
642-
var tex1 = createTexture(device, data1, format);
643-
var tex2 = createTexture(device, data2, format);
644-
645-
// assign to scope variables
646-
device.scope.resolve('areaLightsLutTex1').setValue(tex1);
647-
device.scope.resolve('areaLightsLutTex2').setValue(tex2);
648-
}
649-
}
650-
651509
dispatchGlobalLights(scene) {
652510
var i;
653511
this.mainLight = -1;

0 commit comments

Comments
 (0)