UNPKG

@animech-public/playcanvas

Version:
2 lines (1 loc) 14.7 kB
import{now as e}from"../../core/time.js";import{Color as t}from"../../core/math/color.js";import{math as s}from"../../core/math/math.js";import{Vec3 as r}from"../../core/math/vec3.js";import{BoundingBox as a}from"../../core/shape/bounding-box.js";import{PIXELFORMAT_RGBA8 as i,TEXTURETYPE_RGBM as n,CHUNKAPI_1_65 as h,CULLFACE_NONE as l,TEXTURETYPE_DEFAULT as o,FILTER_NEAREST as c,ADDRESS_CLAMP_TO_EDGE as m,FILTER_LINEAR as d}from"../../platform/graphics/constants.js";import{RenderTarget as p}from"../../platform/graphics/render-target.js";import{drawQuadWithShader as g}from"../../scene/graphics/quad-render-utils.js";import{Texture as u}from"../../platform/graphics/texture.js";import{MeshInstance as f}from"../../scene/mesh-instance.js";import{LightingParams as b}from"../../scene/lighting/lighting-params.js";import{WorldClusters as w}from"../../scene/lighting/world-clusters.js";import{shaderChunks as k}from"../../scene/shader-lib/chunks/chunks.js";import{shaderChunksLightmapper as T}from"../../scene/shader-lib/chunks/chunks-lightmapper.js";import{PROJECTION_ORTHOGRAPHIC as S,MASK_AFFECT_LIGHTMAPPED as L,BAKE_COLORDIR as M,MASK_BAKE as x,MASK_AFFECT_DYNAMIC as C,LIGHTTYPE_DIRECTIONAL as _,SHADOWUPDATE_REALTIME as P,SHADOWUPDATE_THISFRAME as B,FOG_NONE as y,LIGHTTYPE_SPOT as R,PROJECTION_PERSPECTIVE as j,LIGHTTYPE_OMNI as A,SHADER_FORWARDHDR as E,SHADERDEF_LM as D,SHADERDEF_DIRLM as F,SHADERDEF_LMAMBIENT as v}from"../../scene/constants.js";import{Camera as O}from"../../scene/camera.js";import{GraphNode as z}from"../../scene/graph-node.js";import{StandardMaterial as I}from"../../scene/materials/standard-material.js";import{BakeLightSimple as N}from"./bake-light-simple.js";import{BakeLightAmbient as U}from"./bake-light-ambient.js";import{BakeMeshNode as V}from"./bake-mesh-node.js";import{LightmapCache as $}from"../../scene/graphics/lightmap-cache.js";import{LightmapFilters as G}from"./lightmap-filters.js";import{BlendState as W}from"../../platform/graphics/blend-state.js";import{DepthState as q}from"../../platform/graphics/depth-state.js";import{RenderPassLightmapper as H}from"./render-pass-lightmapper.js";const Y=new r;class J{constructor(e,s,r,a,i){this.device=e,this.root=s,this.scene=r,this.renderer=a,this.assets=i,this.shadowMapCache=a.shadowMapCache,this._tempSet=new Set,this._initCalled=!1,this.passMaterials=[],this.ambientAOMaterial=null,this.fog="",this.ambientLight=new t,this.renderTargets=new Map,this.stats={renderPasses:0,lightmapCount:0,totalRenderTime:0,forwardTime:0,fboTime:0,shadowMapTime:0,compileTime:0,shadersLinked:0}}destroy(){var e;$.decRef(this.blackTex),this.blackTex=null,$.destroy(),this.device=null,this.root=null,this.scene=null,this.renderer=null,this.assets=null,null==(e=this.camera)||e.destroy(),this.camera=null}initBake(e){if(!this._initCalled){this._initCalled=!0,this.lightmapFilters=new G(e),this.constantBakeDir=e.scope.resolve("bakeDir"),this.materials=[],this.blackTex=new u(this.device,{width:4,height:4,format:i,type:n,name:"lightmapBlack"}),$.incRef(this.blackTex);const t=new O;t.clearColor.set(0,0,0,0),t.clearColorBuffer=!0,t.clearDepthBuffer=!1,t.clearStencilBuffer=!1,t.frustumCulling=!1,t.projection=S,t.aspectRatio=1,t.node=new z,this.camera=t}if(this.scene.clusteredLightingEnabled){const t=new b(e.supportsAreaLights,e.maxTextureSize,(()=>{}));this.lightingParams=t;const s=this.scene.lighting;t.shadowsEnabled=s.shadowsEnabled,t.shadowAtlasResolution=s.shadowAtlasResolution,t.cookiesEnabled=s.cookiesEnabled,t.cookieAtlasResolution=s.cookieAtlasResolution,t.areaLightsEnabled=s.areaLightsEnabled,t.cells=new r(3,3,3),t.maxLightsPerCell=4,this.worldClusters=new w(e),this.worldClusters.name="ClusterLightmapper"}}finishBake(e){function t(e){$.decRef(e.colorBuffer),e.destroy()}this.materials=[],this.renderTargets.forEach((e=>{t(e)})),this.renderTargets.clear(),e.forEach((e=>{e.renderTargets.forEach((e=>{t(e)})),e.renderTargets.length=0})),this.ambientAOMaterial=null,this.worldClusters&&(this.worldClusters.destroy(),this.worldClusters=null)}createMaterialForPass(e,s,r,a){const n=new I;n.name=`lmMaterial-pass:${r}-ambient:${a}`,n.chunks.APIVersion=h;if(n.chunks.transformVS="#define UV1LAYOUT\n"+k.transformVS,0===r){let e=T.bakeLmEndPS;a?e=`\n\t\t\t\t\t\t\t\t\t\tdDiffuseLight = ((dDiffuseLight - 0.5) * max(${s.ambientBakeOcclusionContrast.toFixed(1)} + 1.0, 0.0)) + 0.5;\n\t\t\t\t\t\t\t\t\t\tdDiffuseLight += vec3(${s.ambientBakeOcclusionBrightness.toFixed(1)});\n\t\t\t\t\t\t\t\t\t\tdDiffuseLight = saturate(dDiffuseLight);\n\t\t\t\t\t\t\t\t\t\tdDiffuseLight *= dAmbientLight;\n\t\t\t\t\t\t\t\t${e}`:(n.ambient=new t(0,0,0),n.ambientTint=!0),n.chunks.basePS=k.basePS+(s.lightmapPixelFormat===i?"\n#define LIGHTMAP_RGBM\n":""),n.chunks.endPS=e,n.lightMap=this.blackTex}else n.chunks.basePS=`${k.basePS}\nuniform sampler2D texture_dirLightMap;\nuniform float bakeDir;\n`,n.chunks.endPS=T.bakeDirLmEndPS;return n.chunks.outputAlphaPS="\n",n.chunks.outputAlphaOpaquePS="\n",n.chunks.outputAlphaPremulPS="\n",n.cull=l,n.forceUv1=!0,n.update(),n}createMaterials(e,t,s){for(let r=0;r<s;r++)this.passMaterials[r]||(this.passMaterials[r]=this.createMaterialForPass(e,t,r,!1));this.ambientAOMaterial||(this.ambientAOMaterial=this.createMaterialForPass(e,t,0,!0),this.ambientAOMaterial.onUpdateShader=function(e){return e.litOptions.lightMapWithoutAmbient=!0,e.litOptions.separateAmbient=!0,e})}createTexture(e,t){return new u(this.device,{width:e,height:e,format:this.scene.lightmapPixelFormat,mipmaps:!1,type:this.scene.lightmapPixelFormat===i?n:o,minFilter:c,magFilter:c,addressU:m,addressV:m,name:t})}collectModels(e,t,s){var r,a,i;if(!e.enabled)return;let n;if(null!=(r=e.model)&&r.model&&null!=(a=e.model)&&a.enabled&&(s&&s.push(new V(e)),e.model.lightmapped&&t&&(n=e.model.model.meshInstances)),null!=(i=e.render)&&i.enabled&&(s&&s.push(new V(e)),e.render.lightmapped&&t&&(n=e.render.meshInstances)),n){let s=!0;for(let e=0;e<n.length;e++)if(!n[e].mesh.vertexBuffer.format.hasUv1){s=!1;break}if(s){const s=[];for(let r=0;r<n.length;r++){const a=n[r].mesh;this._tempSet.has(a)?t.push(new V(e,[n[r]])):s.push(n[r]),this._tempSet.add(a)}this._tempSet.clear(),s.length>0&&t.push(new V(e,s))}}for(let r=0;r<e._children.length;r++)this.collectModels(e._children[r],t,s)}prepareShadowCasters(e){const t=[];for(let s=0;s<e.length;s++){const r=e[s].component;if(r.castShadows=r.castShadowsLightmap,r.castShadowsLightmap){const r=e[s].meshInstances;for(let e=0;e<r.length;e++)r[e].visibleThisFrame=!0,t.push(r[e])}}return t}updateTransforms(e){for(let t=0;t<e.length;t++){const s=e[t].meshInstances;for(let e=0;e<s.length;e++)s[e].node.getWorldTransform()}}calculateLightmapSize(e){let t;const r=this.scene.lightmapSizeMultiplier||16,a=Y;let i,n;e.model?(n=e.model.lightmapSizeMultiplier,e.model.asset?(t=this.assets.get(e.model.asset).data,t.area&&(i=t.area)):e.model._area&&(t=e.model,t._area&&(i=t._area))):e.render&&(n=e.render.lightmapSizeMultiplier,"asset"!==e.render.type&&e.render._area&&(t=e.render,t._area&&(i=t._area)));const h={x:1,y:1,z:1,uv:1};i&&(h.x=i.x,h.y=i.y,h.z=i.z,h.uv=i.uv);const l=n||1;h.x*=l,h.y*=l,h.z*=l;const o=e.render||e.model,c=this.computeNodeBounds(o.meshInstances);a.copy(c.halfExtents);let m=h.x*a.y*a.z+h.y*a.x*a.z+h.z*a.x*a.y;m/=h.uv,m=Math.sqrt(m);return Math.min(s.nextPowerOfTwo(m*r),this.scene.lightmapMaxResolution||2048)}setLightmapping(e,t,s,r){for(let a=0;a<e.length;a++){const i=e[a],n=i.meshInstances;for(let e=0;e<n.length;e++){const a=n[e];if(a.setLightmapped(t),t){r&&(a._shaderDefs|=r),a.mask=L;for(let e=0;e<s;e++){const t=i.renderTargets[e].colorBuffer;t.minFilter=d,t.magFilter=d,a.setRealtimeLightmap(f.lightmapParamNames[e],t)}}}}}bake(t,s=M){const r=this.device,a=e();this.scene._updateSkyMesh(),this.stats.renderPasses=0,this.stats.shadowMapTime=0,this.stats.forwardTime=0;const i=r._shaderStats.linked,n=r._renderTargetCreationTime,h=r._shaderStats.compileTime,l=[],o=[];if(t){for(let e=0;e<t.length;e++)this.collectModels(t[e],l,null);this.collectModels(this.root,null,o)}else this.collectModels(this.root,l,o);if(l.length>0){this.renderer.shadowRenderer.frameUpdate();const e=s===M?2:1;this.setLightmapping(l,!1,e),this.initBake(r),this.bakeInternal(e,l,o);let t=D;s===M&&(t|=F),this.scene.ambientBake&&(t|=v),this.setLightmapping(l,!0,e,t),this.finishBake(l)}const c=e();this.stats.totalRenderTime=c-a,this.stats.shadersLinked=r._shaderStats.linked-i,this.stats.compileTime=r._shaderStats.compileTime-h,this.stats.fboTime=r._renderTargetCreationTime-n,this.stats.lightmapCount=l.length}allocateTextures(e,t){for(let s=0;s<e.length;s++){const r=e[s],a=this.calculateLightmapSize(r.node);for(let e=0;e<t;e++){const t=this.createTexture(a,`lightmapper_lightmap_${s}`);$.incRef(t),r.renderTargets[e]=new p({colorBuffer:t,depth:!1})}if(!this.renderTargets.has(a)){const e=this.createTexture(a,`lightmapper_temp_lightmap_${a}`);$.incRef(e),this.renderTargets.set(a,new p({colorBuffer:e,depth:!1}))}}}prepareLightsToBake(e,t){if(this.scene.ambientBake){const e=new U(this);t.push(e)}const s=this.renderer.lights;for(let r=0;r<s.length;r++){const a=s[r],i=new N(this,a);e.push(i),a.enabled&&a.mask&x&&(a.mask=x|L|C,a.shadowUpdateMode=a.type===_?P:B,t.push(i))}t.sort()}restoreLights(e){for(let t=0;t<e.length;t++)e[t].restore()}setupScene(){this.fog=this.scene.fog,this.ambientLight.copy(this.scene.ambientLight),this.scene.fog=y,this.scene.ambientBake||this.scene.ambientLight.set(0,0,0),this.renderer.setSceneConstants()}restoreScene(){this.scene.fog=this.fog,this.scene.ambientLight.copy(this.ambientLight)}computeNodeBounds(e){const t=new a;if(e.length>0){t.copy(e[0].aabb);for(let s=1;s<e.length;s++)t.add(e[s].aabb)}return t}computeNodesBounds(e){for(let t=0;t<e.length;t++){const s=e[t].meshInstances;e[t].bounds=this.computeNodeBounds(s)}}computeBounds(e){const t=new a;for(let s=0;s<e.length;s++){t.copy(e[0].aabb);for(let s=1;s<e.length;s++)t.add(e[s].aabb)}return t}backupMaterials(e){for(let t=0;t<e.length;t++)this.materials[t]=e[t].material}restoreMaterials(e){for(let t=0;t<e.length;t++)e[t].material=this.materials[t]}lightCameraPrepare(e,t){const s=t.light;let r;if(s.type===R){r=s.getRenderData(null,0).shadowCamera,r._node.setPosition(s._node.getPosition()),r._node.setRotation(s._node.getRotation()),r._node.rotateLocal(-90,0,0),r.projection=j,r.nearClip=s.attenuationEnd/1e3,r.farClip=s.attenuationEnd,r.aspectRatio=1,r.fov=2*s._outerConeAngle,this.renderer.updateCameraFrustum(r)}return r}lightCameraPrepareAndCull(e,t,s,r){const a=e.light;let i=!0;if(a.type===_){Y.copy(r.center),Y.y+=r.halfExtents.y,this.camera.node.setPosition(Y),this.camera.node.setEulerAngles(-90,0,0),this.camera.nearClip=0,this.camera.farClip=2*r.halfExtents.y;const e=Math.max(r.halfExtents.x,r.halfExtents.z);this.camera.orthoHeight=e}else e.lightBounds.intersects(t.bounds)||(i=!1);if(a.type===R){let e=!1;const r=t.meshInstances;for(let t=0;t<r.length;t++)if(r[t]._isVisible(s)){e=!0;break}e||(i=!1)}return i}setupLightArray(e,t){e[_].length=0,e[A].length=0,e[R].length=0,e[t.type][0]=t,t.visibleThisFrame=!0}renderShadowMap(e,t,s,r){const a=r.light,i=this.scene.clusteredLightingEnabled,n=a.castShadows&&(!i||this.scene.lighting.shadowsEnabled);if(!t&&n)if(a.shadowMap||i||(a.shadowMap=this.shadowMapCache.get(this.device,a)),a.type===_){this.renderer._shadowRendererDirectional.cull(a,e,this.camera,s);const t=this.renderer._shadowRendererDirectional.getLightRenderPass(a,this.camera);null==t||t.render()}else{if(this.device.isWebGPU)return!0;this.renderer._shadowRendererLocal.cull(a,e,s);const t=!1;this.renderer.shadowRenderer.render(a,this.camera,t)}return!0}postprocessTextures(e,t,s){const r=this.lightmapFilters.shaderDilate,a=this.scene.lightmapFilterEnabled;a&&this.lightmapFilters.prepareDenoise(this.scene.lightmapFilterRange,this.scene.lightmapFilterSmoothness),e.setBlendState(W.NOBLEND),e.setDepthState(q.NODEPTH),e.setStencilState(null,null);for(let i=0;i<t.length;i++){const n=t[i];for(let t=0;t<s;t++){const s=n.renderTargets[t],i=s.colorBuffer,h=this.renderTargets.get(i.width),l=h.colorBuffer;this.lightmapFilters.prepare(i.width,i.height);for(let n=0;n<1;n++){this.lightmapFilters.setSourceTexture(i);g(e,h,a&&0===t&&0===n?this.lightmapFilters.shaderDenoise:r),this.lightmapFilters.setSourceTexture(l),g(e,s,r)}}}}bakeInternal(e,t,s){const r=this.scene,a=r.layers,i=this.device,n=r.clusteredLightingEnabled;this.createMaterials(i,r,e),this.setupScene(),a._update(),this.computeNodesBounds(t),this.allocateTextures(t,e),this.renderer.collectLights(a);const h=[],l=[];this.prepareLightsToBake(h,l),this.updateTransforms(s);const o=this.prepareShadowCasters(s);this.renderer.updateCpuSkinMatrices(o),this.renderer.gpuUpdate(o);const c=this.computeBounds(o);let m,d,p,g;for(m=0;m<t.length;m++){for(p=t[m].meshInstances,d=0;d<p.length;d++)g=p[d],g.setLightmapped(!1),g.mask=x,g.setRealtimeLightmap(f.lightmapParamNames[0],this.blackTex),g.setRealtimeLightmap(f.lightmapParamNames[1],this.blackTex)}for(d=0;d<l.length;d++)l[d].light.enabled=!1;const u=[[],[],[]];let b,w,k=!1;for(m=0;m<l.length;m++){const s=l[m],h=s instanceof U,T=s.light.type===_;let S=s.numVirtualLights;e>1&&S>1&&s.light.bakeDir&&(S=1);for(let l=0;l<S;l++){S>1&&s.prepareVirtualLight(l,S),s.startBake();let m=!1;const L=this.lightCameraPrepare(i,s);for(w=0;w<t.length;w++){const M=t[w];p=M.meshInstances;if(!this.lightCameraPrepareAndCull(s,M,L,c))continue;this.setupLightArray(u,s.light);const x=T?[]:[s.light];for(n&&this.renderer.lightTextureAtlas.update(x,this.lightingParams),m=this.renderShadowMap(a,m,o,s),n&&this.worldClusters.update(x,this.scene.gammaCorrection,this.lightingParams),this.backupMaterials(p),b=0;b<e&&!(b>0&&l>0)&&!(h&&b>0);b++){const e=M.renderTargets[b],t=M.renderTargets[b].colorBuffer.width,a=this.renderTargets.get(t),o=a.colorBuffer;0===b?k=r.updateShaders:k&&(r.updateShaders=!0);let c=this.passMaterials[b];if(h){l+1===S&&0===b&&(c=this.ambientAOMaterial)}for(d=0;d<p.length;d++)p[d].material=c;if(this.renderer.updateShaders(p),1===b&&this.constantBakeDir.setValue(s.light.bakeDir?1:0),i.isWebGPU){const e=new H(i,this.renderer,this.camera,n?this.worldClusters:null,p,u);e.init(a),e.render(),e.destroy()}else this.renderer.setCamera(this.camera,a,!0),n&&this.worldClusters.activate(),this.renderer._forwardTime=0,this.renderer._shadowMapTime=0,this.renderer.renderForward(this.camera,p,u,E),i.updateEnd();for(M.renderTargets[b]=a,this.renderTargets.set(t,e),d=0;d<p.length;d++)g=p[d],g.setRealtimeLightmap(f.lightmapParamNames[b],o),g._shaderDefs|=D}this.restoreMaterials(p)}s.endBake(this.shadowMapCache)}}for(this.postprocessTextures(i,t,e),w=0;w<s.length;w++)s[w].restore();this.restoreLights(h),this.restoreScene(),n||this.shadowMapCache.clear()}}export{J as Lightmapper};