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

Cookie renderer refactored to use a single render pass #5572

Merged
merged 1 commit into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
154 changes: 104 additions & 50 deletions src/scene/renderer/cookie-renderer.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { DebugHelper } from '../../core/debug.js';
import { Vec4 } from '../../core/math/vec4.js';
import { Mat4 } from '../../core/math/mat4.js';

import { ADDRESS_CLAMP_TO_EDGE, FILTER_NEAREST, PIXELFORMAT_RGBA8 } from '../../platform/graphics/constants.js';
import { ADDRESS_CLAMP_TO_EDGE, CULLFACE_NONE, FILTER_NEAREST, PIXELFORMAT_RGBA8 } from '../../platform/graphics/constants.js';
import { DebugGraphics } from '../../platform/graphics/debug-graphics.js';
import { drawQuadWithShader } from '../graphics/quad-render-utils.js';
import { Texture } from '../../platform/graphics/texture.js';

import { LIGHTTYPE_OMNI } from '../constants.js';
import { LIGHTTYPE_DIRECTIONAL, LIGHTTYPE_OMNI } from '../constants.js';
import { createShaderFromCode } from '../shader-lib/utils.js';
import { LightCamera } from './light-camera.js';
import { BlendState } from '../../platform/graphics/blend-state.js';
import { QuadRender } from '../graphics/quad-render.js';
import { DepthState } from '../../platform/graphics/depth-state.js';
import { RenderPass } from '../../platform/graphics/render-pass.js';

const textureBlitVertexShader = `
attribute vec2 vertex_position;
Expand Down Expand Up @@ -41,42 +44,46 @@ const textureCubeBlitFragmentShader = `
}`;

const _viewport = new Vec4();
const _filteredLights = [];

// helper class used by clustered lighting system to render cookies into the texture atlas, similarly to shadow renderer
class CookieRenderer {
/** @type {QuadRender|null} */
_quadRenderer2D = null;

/** @type {QuadRender|null} */
_quadRendererCube = null;

constructor(device, lightTextureAtlas) {
this.device = device;
this.lightTextureAtlas = lightTextureAtlas;

this.blitShader2d = null;
this.blitShaderCube = null;
this.blitTextureId = null;
this.invViewProjId = null;
this.blitTextureId = this.device.scope.resolve('blitTexture');
this.invViewProjId = this.device.scope.resolve('invViewProj');
}

destroy() {
}

getShader(shader, fragment) {

if (!this[shader])
this[shader] = createShaderFromCode(this.device, textureBlitVertexShader, fragment, `cookie_renderer_${shader}`);

if (!this.blitTextureId)
this.blitTextureId = this.device.scope.resolve('blitTexture');

if (!this.invViewProjId)
this.invViewProjId = this.device.scope.resolve('invViewProj');
this._quadRenderer2D?.destroy();
this._quadRenderer2D = null;

return this[shader];
this._quadRendererCube?.destroy();
this._quadRendererCube = null;
}

get shader2d() {
return this.getShader('blitShader2d', textureBlitFragmentShader);
get quadRenderer2D() {
if (!this._quadRenderer2D) {
const shader = createShaderFromCode(this.device, textureBlitVertexShader, textureBlitFragmentShader, `cookieRenderer2d`);
this._quadRenderer2D = new QuadRender(shader);
}
return this._quadRenderer2D;
}

get shaderCube() {
return this.getShader('blitShaderCube', textureCubeBlitFragmentShader);
get quadRendererCube() {
if (!this._quadRendererCube) {
const shader = createShaderFromCode(this.device, textureBlitVertexShader, textureCubeBlitFragmentShader, `cookieRendererCube`);
this._quadRendererCube = new QuadRender(shader);
}
return this._quadRendererCube;
}

static createTexture(device, resolution) {
Expand Down Expand Up @@ -113,51 +120,98 @@ class CookieRenderer {
}
}

render(light, renderTarget) {
filter(lights) {

for (let i = 0; i < lights.length; i++) {
const light = lights[i];

if (light.enabled && light.cookie && light.visibleThisFrame) {
// skip directional lights
if (light._type === LIGHTTYPE_DIRECTIONAL)
continue;

DebugGraphics.pushGpuMarker(this.device, `COOKIE ${light._node.name}`);
// skip clustered cookies with no assigned atlas slot
if (!light.atlasViewportAllocated)
continue;

const faceCount = light.numShadowFaces;
const shader = faceCount > 1 ? this.shaderCube : this.shader2d;
const device = this.device;
// only render cookie when the slot is reassigned (assuming the cookie texture is static)
if (!light.atlasSlotUpdated)
continue;

if (faceCount > 1) {
this.initInvViewProjMatrices();
if (light.enabled && light.cookie && light.visibleThisFrame) {
_filteredLights.push(light);
}
}
}

render(renderTarget, lights) {

// source texture
this.blitTextureId.setValue(light.cookie);
// pick lights we need to update the cookies for
this.filter(lights);
if (_filteredLights.length <= 0)
return;

// prepare a single render pass to render all quads to the render target
const device = this.device;
const renderPass = new RenderPass(device, () => {

// render state
device.setBlendState(BlendState.NOBLEND);
device.setCullMode(CULLFACE_NONE);
device.setDepthState(DepthState.NODEPTH);
device.setStencilState(null, null);

for (let i = 0; i < _filteredLights.length; i++) {
const light = _filteredLights[i];

// render it to a viewport of the target
for (let face = 0; face < faceCount; face++) {
DebugGraphics.pushGpuMarker(this.device, `COOKIE ${light._node.name}`);

_viewport.copy(light.atlasViewport);
const faceCount = light.numShadowFaces;
const quad = faceCount > 1 ? this.quadRendererCube : this.quadRenderer2D;

if (faceCount > 1) {
this.initInvViewProjMatrices();
}

// source texture
this.blitTextureId.setValue(light.cookie);

// render it to a viewport of the target
for (let face = 0; face < faceCount; face++) {

_viewport.copy(light.atlasViewport);

if (faceCount > 1) {

// for cubemap, render to one of the 3x3 sub-areas
const smallSize = _viewport.z / 3;
const offset = this.lightTextureAtlas.cubeSlotsOffsets[face];
_viewport.x += smallSize * offset.x;
_viewport.y += smallSize * offset.y;
_viewport.z = smallSize;
_viewport.w = smallSize;
// for cubemap, render to one of the 3x3 sub-areas
const smallSize = _viewport.z / 3;
const offset = this.lightTextureAtlas.cubeSlotsOffsets[face];
_viewport.x += smallSize * offset.x;
_viewport.y += smallSize * offset.y;
_viewport.z = smallSize;
_viewport.w = smallSize;

// cubemap face projection uniform
this.invViewProjId.setValue(CookieRenderer._invViewProjMatrices[face].data);
// cubemap face projection uniform
this.invViewProjId.setValue(CookieRenderer._invViewProjMatrices[face].data);
}

_viewport.mulScalar(renderTarget.colorBuffer.width);

quad.render(_viewport);
}

_viewport.mulScalar(renderTarget.colorBuffer.width);
drawQuadWithShader(device, renderTarget, shader, _viewport);
DebugGraphics.popGpuMarker(device);
}
});

DebugGraphics.popGpuMarker(this.device);
}
DebugHelper.setName(renderPass, `RenderPass-CookieRenderer`);
renderPass.init(renderTarget);
renderPass.colorOps.clear = false;
renderPass.depthStencilOps.clearDepth = false;

// render the pass
renderPass.render();

_filteredLights.length = 0;
}
}

Expand Down
3 changes: 1 addition & 2 deletions src/scene/renderer/forward-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -801,8 +801,7 @@ class ForwardRenderer extends Renderer {
const renderPass = new RenderPass(this.device, () => {
// render cookies for all local visible lights
if (this.scene.lighting.cookiesEnabled) {
this.renderCookies(layerComposition._splitLights[LIGHTTYPE_SPOT]);
this.renderCookies(layerComposition._splitLights[LIGHTTYPE_OMNI]);
this.renderCookies(layerComposition._lights);
}
});
renderPass.requiresCubemaps = false;
Expand Down
15 changes: 1 addition & 14 deletions src/scene/renderer/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1055,21 +1055,8 @@ class Renderer {
}

renderCookies(lights) {

const cookieRenderTarget = this.lightTextureAtlas.cookieRenderTarget;
for (let i = 0; i < lights.length; i++) {
const light = lights[i];

// skip clustered cookies with no assigned atlas slot
if (!light.atlasViewportAllocated)
continue;

// only render cookie when the slot is reassigned (assuming the cookie texture is static)
if (!light.atlasSlotUpdated)
continue;

this._cookieRenderer.render(light, cookieRenderTarget);
}
this._cookieRenderer.render(cookieRenderTarget, lights);
}

/**
Expand Down