Skip to content
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

Partial support for Lightmapper on WebGPU #5953

Merged
merged 5 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion examples/src/examples/graphics/lights-baked-a-o.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -417,5 +417,4 @@ export class LightsBakedAOExample {
static CATEGORY = 'Graphics';
static controls = controls;
static example = example;
static WEBGPU_ENABLED = false; // house is just gray
}
1 change: 0 additions & 1 deletion examples/src/examples/graphics/lights-baked.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -152,5 +152,4 @@ async function example({ canvas, deviceType, glslangPath, twgslPath }) {
export class LightsBakedExample {
static CATEGORY = 'Graphics';
static example = example;
static WEBGPU_ENABLED = false; // house is black
}
49 changes: 25 additions & 24 deletions src/framework/lightmapper/lightmapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import {
FOG_NONE,
LIGHTTYPE_DIRECTIONAL, LIGHTTYPE_OMNI, LIGHTTYPE_SPOT,
PROJECTION_ORTHOGRAPHIC, PROJECTION_PERSPECTIVE,
SHADER_FORWARDHDR,
SHADERDEF_DIRLM, SHADERDEF_LM, SHADERDEF_LMAMBIENT,
MASK_BAKE, MASK_AFFECT_LIGHTMAPPED, MASK_AFFECT_DYNAMIC,
SHADOWUPDATE_REALTIME, SHADOWUPDATE_THISFRAME
Expand All @@ -46,6 +45,7 @@ import { LightmapCache } from '../../scene/graphics/lightmap-cache.js';
import { LightmapFilters } from './lightmap-filters.js';
import { BlendState } from '../../platform/graphics/blend-state.js';
import { DepthState } from '../../platform/graphics/depth-state.js';
import { RenderPassLightmapper } from './render-pass-lightmapper.js';

const MAX_LIGHTMAP_SIZE = 2048;

Expand Down Expand Up @@ -228,7 +228,8 @@ class Lightmapper {
const material = new StandardMaterial();
material.name = `lmMaterial-pass:${pass}-ambient:${addAmbient}`;
material.chunks.APIVersion = CHUNKAPI_1_65;
material.chunks.transformVS = '#define UV1LAYOUT\n' + shaderChunks.transformVS; // draw UV1
const transformDefines = '#define UV1LAYOUT\n' + (device.isWebGPU ? '#define UV1LAYOUT_FLIP\n' : '');
material.chunks.transformVS = transformDefines + shaderChunks.transformVS; // draw into UV1 texture space

if (pass === PASS_COLOR) {
let bakeLmEndChunk = shaderChunksLightmapper.bakeLmEndPS; // encode to RGBM
Expand Down Expand Up @@ -514,11 +515,6 @@ class Lightmapper {
bake(nodes, mode = BAKE_COLORDIR) {

const device = this.device;
if (device.isWebGPU) {
Debug.warnOnce('Lightmapper is not supported on WebGPU, skipping.');
return;
}

const startTime = now();

// update skybox
Expand Down Expand Up @@ -854,12 +850,24 @@ class Lightmapper {

if (light.type === LIGHTTYPE_DIRECTIONAL) {
this.renderer._shadowRendererDirectional.cull(light, comp, this.camera, casters);

const shadowPass = this.renderer._shadowRendererDirectional.getLightRenderPass(light, this.camera);
shadowPass?.render();

} else {

// TODO: lightmapper on WebGPU does not yet support spot and omni shadows
if (this.device.isWebGPU) {
Debug.warnOnce('Lightmapper on WebGPU does not yet support spot and omni shadows.');
return true;
}

this.renderer._shadowRendererLocal.cull(light, comp, casters);
}

const insideRenderPass = false;
this.renderer.shadowRenderer.render(light, this.camera, insideRenderPass);
// TODO: this needs to used render passes to work on WebGPU
const insideRenderPass = false;
this.renderer.shadowRenderer.render(light, this.camera, insideRenderPass);
}
}

return true;
Expand Down Expand Up @@ -1078,24 +1086,17 @@ class Lightmapper {
// update shader
this.renderer.updateShaders(rcv);

// ping-ponging output
this.renderer.setCamera(this.camera, tempRT, true);

// render receivers to the tempRT
if (pass === PASS_DIR) {
this.constantBakeDir.setValue(bakeLight.light.bakeDir ? 1 : 0);
}

// prepare clustered lighting
if (clusteredLightingEnabled) {
this.worldClusters.activate();
}

this.renderer._forwardTime = 0;
this.renderer._shadowMapTime = 0;

this.renderer.renderForward(this.camera, rcv, lightArray, SHADER_FORWARDHDR);

device.updateEnd();
const renderPass = new RenderPassLightmapper(device, this.renderer, this.camera,
clusteredLightingEnabled ? this.worldClusters : null,
rcv, lightArray);
renderPass.init(tempRT);
renderPass.render();
renderPass.destroy();

// #if _PROFILER
this.stats.shadowMapTime += this.renderer._shadowMapTime;
Expand Down
55 changes: 55 additions & 0 deletions src/framework/lightmapper/render-pass-lightmapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { DebugGraphics } from '../../platform/graphics/debug-graphics.js';
import { RenderPass } from '../../platform/graphics/render-pass.js';
import { SHADER_FORWARDHDR } from '../../scene/constants.js';

/**
* A render pass implementing rendering of mesh instance receivers for light-mapper.
*
* @ignore
*/
class RenderPassLightmapper extends RenderPass {
/** @type {import('../../platform/graphics/bind-group.js').BindGroup[]} */
viewBindGroups = [];

constructor(device, renderer, camera, worldClusters, receivers, lightArray) {
super(device);
this.renderer = renderer;
this.camera = camera;
this.worldClusters = worldClusters;
this.receivers = receivers;
this.lightArray = lightArray;
}

destroy() {
this.viewBindGroups.forEach((bg) => {
bg.defaultUniformBuffer.destroy();
bg.destroy();
});
this.viewBindGroups.length = 0;
}

execute() {
const device = this.device;
DebugGraphics.pushGpuMarker(device, 'Lightmapper');

const { renderer, camera, receivers, renderTarget, worldClusters, lightArray } = this;

// prepare clustered lighting
if (worldClusters) {
worldClusters.activate();
}

renderer.setCameraUniforms(camera, renderTarget);
if (device.supportsUniformBuffers) {
renderer.setupViewUniformBuffers(this.viewBindGroups, renderer.viewUniformFormat, renderer.viewBindGroupFormat, 1);
}

renderer._forwardTime = 0;
renderer._shadowMapTime = 0;
renderer.renderForward(camera, receivers, lightArray, SHADER_FORWARDHDR);

DebugGraphics.popGpuMarker(device);
}
}

export { RenderPassLightmapper };
3 changes: 3 additions & 0 deletions src/scene/shader-lib/chunks/common/vert/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ vec4 getPosition() {
vec4 screenPos;
#ifdef UV1LAYOUT
screenPos = vec4(vertex_texCoord1.xy * 2.0 - 1.0, 0.5, 1);
#ifdef UV1LAYOUT_FLIP
screenPos.y *= -1.0;
#endif
#else
#ifdef SCREENSPACE
screenPos = posW;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ uniform float kernel[MSIZE];

void main(void) {

vec4 pixelRgbm = texture2D(source, vUv0);
vec4 pixelRgbm = texture2DLodEXT(source, vUv0, 0.0);

// lightmap specific optimization - skip pixels that were not baked
// this also allows dilate filter that work on the output of this to work correctly, as it depends on .a being zero
Expand All @@ -72,7 +72,7 @@ void main(void) {

// sample the pixel with offset
vec2 coord = vUv0 + vec2(float(i), float(j)) * pixelOffset;
vec4 rgbm = texture2D(source, coord);
vec4 rgbm = texture2DLodEXT(source, coord, 0.0);

// lightmap - only use baked pixels
if (rgbm.a > 0.0) {
Expand Down
18 changes: 9 additions & 9 deletions src/scene/shader-lib/chunks/lightmapper/frag/dilate.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ uniform sampler2D source;
uniform vec2 pixelOffset;

void main(void) {
vec4 c = texture2D(source, vUv0);
c = c.a>0.0? c : texture2D(source, vUv0 - pixelOffset);
c = c.a>0.0? c : texture2D(source, vUv0 + vec2(0, -pixelOffset.y));
c = c.a>0.0? c : texture2D(source, vUv0 + vec2(pixelOffset.x, -pixelOffset.y));
c = c.a>0.0? c : texture2D(source, vUv0 + vec2(-pixelOffset.x, 0));
c = c.a>0.0? c : texture2D(source, vUv0 + vec2(pixelOffset.x, 0));
c = c.a>0.0? c : texture2D(source, vUv0 + vec2(-pixelOffset.x, pixelOffset.y));
c = c.a>0.0? c : texture2D(source, vUv0 + vec2(0, pixelOffset.y));
c = c.a>0.0? c : texture2D(source, vUv0 + pixelOffset);
vec4 c = texture2DLodEXT(source, vUv0, 0.0);
c = c.a>0.0? c : texture2DLodEXT(source, vUv0 - pixelOffset, 0.0);
c = c.a>0.0? c : texture2DLodEXT(source, vUv0 + vec2(0, -pixelOffset.y), 0.0);
c = c.a>0.0? c : texture2DLodEXT(source, vUv0 + vec2(pixelOffset.x, -pixelOffset.y), 0.0);
c = c.a>0.0? c : texture2DLodEXT(source, vUv0 + vec2(-pixelOffset.x, 0), 0.0);
c = c.a>0.0? c : texture2DLodEXT(source, vUv0 + vec2(pixelOffset.x, 0), 0.0);
c = c.a>0.0? c : texture2DLodEXT(source, vUv0 + vec2(-pixelOffset.x, pixelOffset.y), 0.0);
c = c.a>0.0? c : texture2DLodEXT(source, vUv0 + vec2(0, pixelOffset.y), 0.0);
c = c.a>0.0? c : texture2DLodEXT(source, vUv0 + pixelOffset, 0.0);
gl_FragColor = c;
}
`;