playcanvas
Version:
PlayCanvas WebGL game engine
649 lines (646 loc) • 28.8 kB
JavaScript
import { math } from '../../core/math/math.js';
import { Vec3 } from '../../core/math/vec3.js';
import { Color } from '../../core/math/color.js';
import { LIGHTSHAPE_PUNCTUAL, LIGHTTYPE_OMNI, LIGHTTYPE_SPOT, LIGHTTYPE_DIRECTIONAL, FOG_NONE, FOG_LINEAR, LAYERID_DEPTH } from '../constants.js';
import { Renderer } from './renderer.js';
import { LightCamera } from './light-camera.js';
import { RenderPassForward } from './render-pass-forward.js';
import { RenderPassPostprocessing } from './render-pass-postprocessing.js';
import { BINDGROUP_VIEW } from '../../platform/graphics/constants.js';
const _noLights = [
[],
[],
[]
];
const tmpColor = new Color();
const _drawCallList = {
drawCalls: [],
shaderInstances: [],
isNewMaterial: [],
lightMaskChanged: [],
clear: function() {
this.drawCalls.length = 0;
this.shaderInstances.length = 0;
this.isNewMaterial.length = 0;
this.lightMaskChanged.length = 0;
}
};
function vogelDiskPrecalculationSamples(numSamples) {
const samples = [];
for(let i = 0; i < numSamples; ++i){
const r = Math.sqrt(i + 0.5) / Math.sqrt(numSamples);
samples.push(r);
}
return samples;
}
function vogelSpherePrecalculationSamples(numSamples) {
const samples = [];
for(let i = 0; i < numSamples; i++){
const weight = i / numSamples;
const radius = Math.sqrt(weight * weight);
samples.push(radius);
}
return samples;
}
class ForwardRenderer extends Renderer {
constructor(graphicsDevice, scene){
super(graphicsDevice, scene);
const device = this.device;
this._forwardDrawCalls = 0;
this._materialSwitches = 0;
this._depthMapTime = 0;
this._forwardTime = 0;
this._sortTime = 0;
const scope = device.scope;
this.fogColorId = scope.resolve('fog_color');
this.fogStartId = scope.resolve('fog_start');
this.fogEndId = scope.resolve('fog_end');
this.fogDensityId = scope.resolve('fog_density');
this.ambientId = scope.resolve('light_globalAmbient');
this.skyboxIntensityId = scope.resolve('skyboxIntensity');
this.cubeMapRotationMatrixId = scope.resolve('cubeMapRotationMatrix');
this.pcssDiskSamplesId = scope.resolve('pcssDiskSamples[0]');
this.pcssSphereSamplesId = scope.resolve('pcssSphereSamples[0]');
this.lightColorId = [];
this.lightDir = [];
this.lightDirId = [];
this.lightShadowMapId = [];
this.lightShadowMatrixId = [];
this.lightShadowParamsId = [];
this.lightShadowIntensity = [];
this.lightRadiusId = [];
this.lightPos = [];
this.lightPosId = [];
this.lightWidth = [];
this.lightWidthId = [];
this.lightHeight = [];
this.lightHeightId = [];
this.lightInAngleId = [];
this.lightOutAngleId = [];
this.lightCookieId = [];
this.lightCookieIntId = [];
this.lightCookieMatrixId = [];
this.lightCookieOffsetId = [];
this.lightShadowSearchAreaId = [];
this.lightCameraParamsId = [];
this.lightSoftShadowParamsId = [];
this.shadowMatrixPaletteId = [];
this.shadowCascadeDistancesId = [];
this.shadowCascadeCountId = [];
this.shadowCascadeBlendId = [];
this.screenSizeId = scope.resolve('uScreenSize');
this._screenSize = new Float32Array(4);
this.fogColor = new Float32Array(3);
this.ambientColor = new Float32Array(3);
this.pcssDiskSamples = vogelDiskPrecalculationSamples(16);
this.pcssSphereSamples = vogelSpherePrecalculationSamples(16);
}
destroy() {
super.destroy();
}
dispatchGlobalLights(scene) {
const ambientUniform = this.ambientColor;
tmpColor.linear(scene.ambientLight);
ambientUniform[0] = tmpColor.r;
ambientUniform[1] = tmpColor.g;
ambientUniform[2] = tmpColor.b;
if (scene.physicalUnits) {
for(let i = 0; i < 3; i++){
ambientUniform[i] *= scene.ambientLuminance;
}
}
this.ambientId.setValue(ambientUniform);
this.skyboxIntensityId.setValue(scene.physicalUnits ? scene.skyboxLuminance : scene.skyboxIntensity);
this.cubeMapRotationMatrixId.setValue(scene._skyboxRotationMat3.data);
}
_resolveLight(scope, i) {
const light = `light${i}`;
this.lightColorId[i] = scope.resolve(`${light}_color`);
this.lightDir[i] = new Float32Array(3);
this.lightDirId[i] = scope.resolve(`${light}_direction`);
this.lightShadowMapId[i] = scope.resolve(`${light}_shadowMap`);
this.lightShadowMatrixId[i] = scope.resolve(`${light}_shadowMatrix`);
this.lightShadowParamsId[i] = scope.resolve(`${light}_shadowParams`);
this.lightShadowIntensity[i] = scope.resolve(`${light}_shadowIntensity`);
this.lightShadowSearchAreaId[i] = scope.resolve(`${light}_shadowSearchArea`);
this.lightRadiusId[i] = scope.resolve(`${light}_radius`);
this.lightPos[i] = new Float32Array(3);
this.lightPosId[i] = scope.resolve(`${light}_position`);
this.lightWidth[i] = new Float32Array(3);
this.lightWidthId[i] = scope.resolve(`${light}_halfWidth`);
this.lightHeight[i] = new Float32Array(3);
this.lightHeightId[i] = scope.resolve(`${light}_halfHeight`);
this.lightInAngleId[i] = scope.resolve(`${light}_innerConeAngle`);
this.lightOutAngleId[i] = scope.resolve(`${light}_outerConeAngle`);
this.lightCookieId[i] = scope.resolve(`${light}_cookie`);
this.lightCookieIntId[i] = scope.resolve(`${light}_cookieIntensity`);
this.lightCookieMatrixId[i] = scope.resolve(`${light}_cookieMatrix`);
this.lightCookieOffsetId[i] = scope.resolve(`${light}_cookieOffset`);
this.lightCameraParamsId[i] = scope.resolve(`${light}_cameraParams`);
this.lightSoftShadowParamsId[i] = scope.resolve(`${light}_softShadowParams`);
this.shadowMatrixPaletteId[i] = scope.resolve(`${light}_shadowMatrixPalette[0]`);
this.shadowCascadeDistancesId[i] = scope.resolve(`${light}_shadowCascadeDistances`);
this.shadowCascadeCountId[i] = scope.resolve(`${light}_shadowCascadeCount`);
this.shadowCascadeBlendId[i] = scope.resolve(`${light}_shadowCascadeBlend`);
}
setLTCDirectionalLight(wtm, cnt, dir, campos, far) {
this.lightPos[cnt][0] = campos.x - dir.x * far;
this.lightPos[cnt][1] = campos.y - dir.y * far;
this.lightPos[cnt][2] = campos.z - dir.z * far;
this.lightPosId[cnt].setValue(this.lightPos[cnt]);
const hWidth = wtm.transformVector(new Vec3(-0.5, 0, 0));
this.lightWidth[cnt][0] = hWidth.x * far;
this.lightWidth[cnt][1] = hWidth.y * far;
this.lightWidth[cnt][2] = hWidth.z * far;
this.lightWidthId[cnt].setValue(this.lightWidth[cnt]);
const hHeight = wtm.transformVector(new Vec3(0, 0, 0.5));
this.lightHeight[cnt][0] = hHeight.x * far;
this.lightHeight[cnt][1] = hHeight.y * far;
this.lightHeight[cnt][2] = hHeight.z * far;
this.lightHeightId[cnt].setValue(this.lightHeight[cnt]);
}
dispatchDirectLights(dirs, mask, camera) {
let cnt = 0;
const scope = this.device.scope;
for(let i = 0; i < dirs.length; i++){
if (!(dirs[i].mask & mask)) continue;
const directional = dirs[i];
const wtm = directional._node.getWorldTransform();
if (!this.lightColorId[cnt]) {
this._resolveLight(scope, cnt);
}
this.lightColorId[cnt].setValue(directional._colorLinear);
wtm.getY(directional._direction).mulScalar(-1);
directional._direction.normalize();
this.lightDir[cnt][0] = directional._direction.x;
this.lightDir[cnt][1] = directional._direction.y;
this.lightDir[cnt][2] = directional._direction.z;
this.lightDirId[cnt].setValue(this.lightDir[cnt]);
if (directional.shape !== LIGHTSHAPE_PUNCTUAL) {
this.setLTCDirectionalLight(wtm, cnt, directional._direction, camera._node.getPosition(), camera.farClip);
}
if (directional.castShadows) {
const lightRenderData = directional.getRenderData(camera, 0);
const biases = directional._getUniformBiasValues(lightRenderData);
this.lightShadowMapId[cnt].setValue(lightRenderData.shadowBuffer);
this.lightShadowMatrixId[cnt].setValue(lightRenderData.shadowMatrix.data);
this.shadowMatrixPaletteId[cnt].setValue(directional._shadowMatrixPalette);
this.shadowCascadeDistancesId[cnt].setValue(directional._shadowCascadeDistances);
this.shadowCascadeCountId[cnt].setValue(directional.numCascades);
this.shadowCascadeBlendId[cnt].setValue(1 - directional.cascadeBlend);
this.lightShadowIntensity[cnt].setValue(directional.shadowIntensity);
this.lightSoftShadowParamsId[cnt].setValue(directional._softShadowParams);
const shadowRT = lightRenderData.shadowCamera.renderTarget;
if (shadowRT) {
this.lightShadowSearchAreaId[cnt].setValue(directional.penumbraSize / lightRenderData.shadowCamera.renderTarget.width * lightRenderData.projectionCompensation);
}
const cameraParams = directional._shadowCameraParams;
cameraParams.length = 4;
cameraParams[0] = 0;
cameraParams[1] = lightRenderData.shadowCamera._farClip;
cameraParams[2] = lightRenderData.shadowCamera._nearClip;
cameraParams[3] = 1;
this.lightCameraParamsId[cnt].setValue(cameraParams);
const params = directional._shadowRenderParams;
params.length = 4;
params[0] = directional._shadowResolution;
params[1] = biases.normalBias;
params[2] = biases.bias;
params[3] = 0;
this.lightShadowParamsId[cnt].setValue(params);
}
cnt++;
}
return cnt;
}
setLTCPositionalLight(wtm, cnt) {
const hWidth = wtm.transformVector(new Vec3(-0.5, 0, 0));
this.lightWidth[cnt][0] = hWidth.x;
this.lightWidth[cnt][1] = hWidth.y;
this.lightWidth[cnt][2] = hWidth.z;
this.lightWidthId[cnt].setValue(this.lightWidth[cnt]);
const hHeight = wtm.transformVector(new Vec3(0, 0, 0.5));
this.lightHeight[cnt][0] = hHeight.x;
this.lightHeight[cnt][1] = hHeight.y;
this.lightHeight[cnt][2] = hHeight.z;
this.lightHeightId[cnt].setValue(this.lightHeight[cnt]);
}
dispatchOmniLight(scope, omni, cnt) {
const wtm = omni._node.getWorldTransform();
if (!this.lightColorId[cnt]) {
this._resolveLight(scope, cnt);
}
this.lightRadiusId[cnt].setValue(omni.attenuationEnd);
this.lightColorId[cnt].setValue(omni._colorLinear);
wtm.getTranslation(omni._position);
this.lightPos[cnt][0] = omni._position.x;
this.lightPos[cnt][1] = omni._position.y;
this.lightPos[cnt][2] = omni._position.z;
this.lightPosId[cnt].setValue(this.lightPos[cnt]);
if (omni.shape !== LIGHTSHAPE_PUNCTUAL) {
this.setLTCPositionalLight(wtm, cnt);
}
if (omni.castShadows) {
const lightRenderData = omni.getRenderData(null, 0);
this.lightShadowMapId[cnt].setValue(lightRenderData.shadowBuffer);
const biases = omni._getUniformBiasValues(lightRenderData);
const params = omni._shadowRenderParams;
params.length = 4;
params[0] = omni._shadowResolution;
params[1] = biases.normalBias;
params[2] = biases.bias;
params[3] = 1.0 / omni.attenuationEnd;
this.lightShadowParamsId[cnt].setValue(params);
this.lightShadowIntensity[cnt].setValue(omni.shadowIntensity);
const pixelsPerMeter = omni.penumbraSize / lightRenderData.shadowCamera.renderTarget.width;
this.lightShadowSearchAreaId[cnt].setValue(pixelsPerMeter);
const cameraParams = omni._shadowCameraParams;
cameraParams.length = 4;
cameraParams[0] = 0;
cameraParams[1] = lightRenderData.shadowCamera._farClip;
cameraParams[2] = lightRenderData.shadowCamera._nearClip;
cameraParams[3] = 0;
this.lightCameraParamsId[cnt].setValue(cameraParams);
}
if (omni._cookie) {
this.lightCookieId[cnt].setValue(omni._cookie);
this.lightShadowMatrixId[cnt].setValue(wtm.data);
this.lightCookieIntId[cnt].setValue(omni.cookieIntensity);
}
}
dispatchSpotLight(scope, spot, cnt) {
const wtm = spot._node.getWorldTransform();
if (!this.lightColorId[cnt]) {
this._resolveLight(scope, cnt);
}
this.lightInAngleId[cnt].setValue(spot._innerConeAngleCos);
this.lightOutAngleId[cnt].setValue(spot._outerConeAngleCos);
this.lightRadiusId[cnt].setValue(spot.attenuationEnd);
this.lightColorId[cnt].setValue(spot._colorLinear);
wtm.getTranslation(spot._position);
this.lightPos[cnt][0] = spot._position.x;
this.lightPos[cnt][1] = spot._position.y;
this.lightPos[cnt][2] = spot._position.z;
this.lightPosId[cnt].setValue(this.lightPos[cnt]);
if (spot.shape !== LIGHTSHAPE_PUNCTUAL) {
this.setLTCPositionalLight(wtm, cnt);
}
wtm.getY(spot._direction).mulScalar(-1);
spot._direction.normalize();
this.lightDir[cnt][0] = spot._direction.x;
this.lightDir[cnt][1] = spot._direction.y;
this.lightDir[cnt][2] = spot._direction.z;
this.lightDirId[cnt].setValue(this.lightDir[cnt]);
if (spot.castShadows) {
const lightRenderData = spot.getRenderData(null, 0);
this.lightShadowMapId[cnt].setValue(lightRenderData.shadowBuffer);
this.lightShadowMatrixId[cnt].setValue(lightRenderData.shadowMatrix.data);
const biases = spot._getUniformBiasValues(lightRenderData);
const params = spot._shadowRenderParams;
params.length = 4;
params[0] = spot._shadowResolution;
params[1] = biases.normalBias;
params[2] = biases.bias;
params[3] = 1.0 / spot.attenuationEnd;
this.lightShadowParamsId[cnt].setValue(params);
this.lightShadowIntensity[cnt].setValue(spot.shadowIntensity);
const pixelsPerMeter = spot.penumbraSize / lightRenderData.shadowCamera.renderTarget.width;
const fov = lightRenderData.shadowCamera._fov * math.DEG_TO_RAD;
const fovRatio = 1.0 / Math.tan(fov / 2.0);
this.lightShadowSearchAreaId[cnt].setValue(pixelsPerMeter * fovRatio);
const cameraParams = spot._shadowCameraParams;
cameraParams.length = 4;
cameraParams[0] = 0;
cameraParams[1] = lightRenderData.shadowCamera._farClip;
cameraParams[2] = lightRenderData.shadowCamera._nearClip;
cameraParams[3] = 0;
this.lightCameraParamsId[cnt].setValue(cameraParams);
}
if (spot._cookie) {
if (!spot.castShadows) {
const cookieMatrix = LightCamera.evalSpotCookieMatrix(spot);
this.lightShadowMatrixId[cnt].setValue(cookieMatrix.data);
}
this.lightCookieId[cnt].setValue(spot._cookie);
this.lightCookieIntId[cnt].setValue(spot.cookieIntensity);
if (spot._cookieTransform) {
spot._cookieTransformUniform[0] = spot._cookieTransform.x;
spot._cookieTransformUniform[1] = spot._cookieTransform.y;
spot._cookieTransformUniform[2] = spot._cookieTransform.z;
spot._cookieTransformUniform[3] = spot._cookieTransform.w;
this.lightCookieMatrixId[cnt].setValue(spot._cookieTransformUniform);
spot._cookieOffsetUniform[0] = spot._cookieOffset.x;
spot._cookieOffsetUniform[1] = spot._cookieOffset.y;
this.lightCookieOffsetId[cnt].setValue(spot._cookieOffsetUniform);
}
}
}
dispatchLocalLights(sortedLights, mask, usedDirLights) {
let cnt = usedDirLights;
const scope = this.device.scope;
const omnis = sortedLights[LIGHTTYPE_OMNI];
const numOmnis = omnis.length;
for(let i = 0; i < numOmnis; i++){
const omni = omnis[i];
if (!(omni.mask & mask)) continue;
this.dispatchOmniLight(scope, omni, cnt);
cnt++;
}
const spts = sortedLights[LIGHTTYPE_SPOT];
const numSpts = spts.length;
for(let i = 0; i < numSpts; i++){
const spot = spts[i];
if (!(spot.mask & mask)) continue;
this.dispatchSpotLight(scope, spot, cnt);
cnt++;
}
}
renderForwardPrepareMaterials(camera, renderTarget, drawCalls, sortedLights, layer, pass) {
const fogParams = camera.fogParams ?? this.scene.fog;
const shaderParams = camera.shaderParams;
shaderParams.fog = fogParams.type;
shaderParams.srgbRenderTarget = renderTarget?.isColorBufferSrgb(0) ?? false;
const addCall = (drawCall, shaderInstance, isNewMaterial, lightMaskChanged)=>{
_drawCallList.drawCalls.push(drawCall);
_drawCallList.shaderInstances.push(shaderInstance);
_drawCallList.isNewMaterial.push(isNewMaterial);
_drawCallList.lightMaskChanged.push(lightMaskChanged);
};
_drawCallList.clear();
const device = this.device;
const scene = this.scene;
const clusteredLightingEnabled = scene.clusteredLightingEnabled;
const lightHash = layer?.getLightHash(clusteredLightingEnabled) ?? 0;
let prevMaterial = null, prevObjDefs, prevLightMask;
const drawCallsCount = drawCalls.length;
for(let i = 0; i < drawCallsCount; i++){
const drawCall = drawCalls[i];
const instancingData = drawCall.instancingData;
if (instancingData && instancingData.count <= 0) {
continue;
}
drawCall.ensureMaterial(device);
const material = drawCall.material;
const objDefs = drawCall._shaderDefs;
const lightMask = drawCall.mask;
if (material && material === prevMaterial && objDefs !== prevObjDefs) {
prevMaterial = null;
}
if (material !== prevMaterial) {
this._materialSwitches++;
material._scene = scene;
if (material.dirty) {
material.updateUniforms(device, scene);
material.dirty = false;
}
}
const shaderInstance = drawCall.getShaderInstance(pass, lightHash, scene, shaderParams, this.viewUniformFormat, this.viewBindGroupFormat, sortedLights);
addCall(drawCall, shaderInstance, material !== prevMaterial, !prevMaterial || lightMask !== prevLightMask);
prevMaterial = material;
prevObjDefs = objDefs;
prevLightMask = lightMask;
}
return _drawCallList;
}
renderForwardInternal(camera, preparedCalls, sortedLights, pass, drawCallback, flipFaces, viewBindGroups) {
const device = this.device;
const scene = this.scene;
const passFlag = 1 << pass;
const flipFactor = flipFaces ? -1 : 1;
const clusteredLightingEnabled = scene.clusteredLightingEnabled;
const viewList = camera.xr?.session && camera.xr.views.list.length ? camera.xr.views.list : null;
const preparedCallsCount = preparedCalls.drawCalls.length;
for(let i = 0; i < preparedCallsCount; i++){
const drawCall = preparedCalls.drawCalls[i];
const newMaterial = preparedCalls.isNewMaterial[i];
const lightMaskChanged = preparedCalls.lightMaskChanged[i];
const shaderInstance = preparedCalls.shaderInstances[i];
const material = drawCall.material;
const lightMask = drawCall.mask;
if (shaderInstance.shader.failed) continue;
if (newMaterial) {
const asyncCompile = false;
device.setShader(shaderInstance.shader, asyncCompile);
material.setParameters(device);
if (lightMaskChanged) {
const usedDirLights = this.dispatchDirectLights(sortedLights[LIGHTTYPE_DIRECTIONAL], lightMask, camera);
if (!clusteredLightingEnabled) {
this.dispatchLocalLights(sortedLights, lightMask, usedDirLights);
}
}
this.alphaTestId.setValue(material.alphaTest);
device.setBlendState(material.blendState);
device.setDepthState(material.depthState);
device.setAlphaToCoverage(material.alphaToCoverage);
}
this.setupCullMode(camera._cullFaces, flipFactor, drawCall);
const stencilFront = drawCall.stencilFront ?? material.stencilFront;
const stencilBack = drawCall.stencilBack ?? material.stencilBack;
device.setStencilState(stencilFront, stencilBack);
drawCall.setParameters(device, passFlag);
device.scope.resolve('meshInstanceId').setValue(drawCall.id);
const mesh = drawCall.mesh;
this.setVertexBuffers(device, mesh);
this.setMorphing(device, drawCall.morphInstance);
this.setSkinning(device, drawCall);
const instancingData = drawCall.instancingData;
if (instancingData) {
device.setVertexBuffer(instancingData.vertexBuffer);
}
this.setMeshInstanceMatrices(drawCall, true);
this.setupMeshUniformBuffers(shaderInstance);
const style = drawCall.renderStyle;
const indexBuffer = mesh.indexBuffer[style];
drawCallback?.(drawCall, i);
const indirectData = drawCall.getDrawCommands(camera);
if (viewList) {
for(let v = 0; v < viewList.length; v++){
const view = viewList[v];
device.setViewport(view.viewport.x, view.viewport.y, view.viewport.z, view.viewport.w);
if (device.supportsUniformBuffers) {
const viewBindGroup = viewBindGroups[v];
device.setBindGroup(BINDGROUP_VIEW, viewBindGroup);
} else {
this.setupViewUniforms(view, v);
}
const first = v === 0;
const last = v === viewList.length - 1;
device.draw(mesh.primitive[style], indexBuffer, instancingData?.count, indirectData, first, last);
this._forwardDrawCalls++;
if (drawCall.instancingData) {
this._instancedDrawCalls++;
}
}
} else {
device.draw(mesh.primitive[style], indexBuffer, instancingData?.count, indirectData);
this._forwardDrawCalls++;
if (drawCall.instancingData) {
this._instancedDrawCalls++;
}
}
if (i < preparedCallsCount - 1 && !preparedCalls.isNewMaterial[i + 1]) {
material.setParameters(device, drawCall.parameters);
}
}
}
renderForward(camera, renderTarget, allDrawCalls, sortedLights, pass, drawCallback, layer, flipFaces, viewBindGroups) {
const preparedCalls = this.renderForwardPrepareMaterials(camera, renderTarget, allDrawCalls, sortedLights, layer, pass);
this.renderForwardInternal(camera, preparedCalls, sortedLights, pass, drawCallback, flipFaces, viewBindGroups);
_drawCallList.clear();
}
renderForwardLayer(camera, renderTarget, layer, transparent, shaderPass, viewBindGroups, options = {}) {
const { scene, device } = this;
const clusteredLightingEnabled = scene.clusteredLightingEnabled;
this.setupViewport(camera, renderTarget);
let visible, splitLights;
if (layer) {
layer.sortVisible(camera, transparent);
const culledInstances = layer.getCulledInstances(camera);
visible = transparent ? culledInstances.transparent : culledInstances.opaque;
scene.immediate.onPreRenderLayer(layer, visible, transparent);
if (layer.requiresLightCube) {
this.lightCube.update(scene.ambientLight, layer._lights);
this.constantLightCube.setValue(this.lightCube.colors);
}
splitLights = layer.splitLights;
} else {
visible = options.meshInstances;
splitLights = options.splitLights ?? _noLights;
}
if (clusteredLightingEnabled) {
const lightClusters = options.lightClusters ?? this.worldClustersAllocator.empty;
lightClusters.activate();
if (layer) {
if (!this.clustersDebugRendered && scene.lighting.debugLayer === layer.id) {
this.clustersDebugRendered = true;
}
}
}
scene._activeCamera = camera;
const fogParams = camera.fogParams ?? this.scene.fog;
this.setFogConstants(fogParams);
const viewList = this.setCameraUniforms(camera, renderTarget);
if (device.supportsUniformBuffers) {
this.setupViewUniformBuffers(viewBindGroups, this.viewUniformFormat, this.viewBindGroupFormat, viewList);
}
const clearColor = options.clearColor ?? false;
const clearDepth = options.clearDepth ?? false;
const clearStencil = options.clearStencil ?? false;
if (clearColor || clearDepth || clearStencil) {
this.clear(camera, clearColor, clearDepth, clearStencil);
}
const flipFaces = !!(camera._flipFaces ^ renderTarget?.flipY);
const forwardDrawCalls = this._forwardDrawCalls;
this.renderForward(camera, renderTarget, visible, splitLights, shaderPass, null, layer, flipFaces, viewBindGroups);
if (layer) {
layer._forwardDrawCalls += this._forwardDrawCalls - forwardDrawCalls;
}
}
setFogConstants(fogParams) {
if (fogParams.type !== FOG_NONE) {
tmpColor.linear(fogParams.color);
const fogUniform = this.fogColor;
fogUniform[0] = tmpColor.r;
fogUniform[1] = tmpColor.g;
fogUniform[2] = tmpColor.b;
this.fogColorId.setValue(fogUniform);
if (fogParams.type === FOG_LINEAR) {
this.fogStartId.setValue(fogParams.start);
this.fogEndId.setValue(fogParams.end);
} else {
this.fogDensityId.setValue(fogParams.density);
}
}
}
setSceneConstants() {
const scene = this.scene;
this.dispatchGlobalLights(scene);
const device = this.device;
this._screenSize[0] = device.width;
this._screenSize[1] = device.height;
this._screenSize[2] = 1 / device.width;
this._screenSize[3] = 1 / device.height;
this.screenSizeId.setValue(this._screenSize);
this.pcssDiskSamplesId.setValue(this.pcssDiskSamples);
this.pcssSphereSamplesId.setValue(this.pcssSphereSamples);
}
buildFrameGraph(frameGraph, layerComposition) {
const scene = this.scene;
frameGraph.reset();
if (scene.clusteredLightingEnabled) {
const { shadowsEnabled, cookiesEnabled } = scene.lighting;
this._renderPassUpdateClustered.update(frameGraph, shadowsEnabled, cookiesEnabled, this.lights, this.localLights);
frameGraph.addRenderPass(this._renderPassUpdateClustered);
} else {
this._shadowRendererLocal.buildNonClusteredRenderPasses(frameGraph, this.localLights);
}
let startIndex = 0;
let newStart = true;
let renderTarget = null;
const renderActions = layerComposition._renderActions;
for(let i = startIndex; i < renderActions.length; i++){
const renderAction = renderActions[i];
const { layer, camera } = renderAction;
if (renderAction.useCameraPasses) {
camera.camera.renderPasses.forEach((renderPass)=>{
frameGraph.addRenderPass(renderPass);
});
} else {
const isDepthLayer = layer.id === LAYERID_DEPTH;
const isGrabPass = isDepthLayer && (camera.renderSceneColorMap || camera.renderSceneDepthMap);
if (newStart) {
newStart = false;
startIndex = i;
renderTarget = renderAction.renderTarget;
}
const nextRenderAction = renderActions[i + 1];
const isNextLayerDepth = nextRenderAction ? !nextRenderAction.useCameraPasses && nextRenderAction.layer.id === LAYERID_DEPTH : false;
const isNextLayerGrabPass = isNextLayerDepth && (camera.renderSceneColorMap || camera.renderSceneDepthMap);
const nextNeedDirShadows = nextRenderAction ? nextRenderAction.firstCameraUse && this.cameraDirShadowLights.has(nextRenderAction.camera.camera) : false;
if (!nextRenderAction || nextRenderAction.renderTarget !== renderTarget || nextNeedDirShadows || isNextLayerGrabPass || isGrabPass) {
const isDepthOnly = isDepthLayer && startIndex === i;
if (!isDepthOnly) {
this.addMainRenderPass(frameGraph, layerComposition, renderTarget, startIndex, i);
}
if (isDepthLayer) {
if (camera.renderSceneColorMap) {
const colorGrabPass = camera.camera.renderPassColorGrab;
colorGrabPass.source = camera.renderTarget;
frameGraph.addRenderPass(colorGrabPass);
}
if (camera.renderSceneDepthMap) {
frameGraph.addRenderPass(camera.camera.renderPassDepthGrab);
}
}
if (renderAction.triggerPostprocess && camera?.onPostprocessing) {
const renderPass = new RenderPassPostprocessing(this.device, this, renderAction);
frameGraph.addRenderPass(renderPass);
}
newStart = true;
}
}
}
}
addMainRenderPass(frameGraph, layerComposition, renderTarget, startIndex, endIndex) {
const renderPass = new RenderPassForward(this.device, layerComposition, this.scene, this);
renderPass.init(renderTarget);
const renderActions = layerComposition._renderActions;
for(let i = startIndex; i <= endIndex; i++){
renderPass.addRenderAction(renderActions[i]);
}
frameGraph.addRenderPass(renderPass);
}
update(comp) {
this.frameUpdate();
this.shadowRenderer.frameUpdate();
this.scene._updateSkyMesh();
this.updateLayerComposition(comp);
this.collectLights(comp);
this.beginFrame(comp);
this.setSceneConstants();
this.gsplatDirector?.update(comp);
this.cullComposition(comp);
this.gpuUpdate(this.processingMeshInstances);
}
}
export { ForwardRenderer };