builtin-pipeline.ts 81 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045
  1. /*
  2. Copyright (c) 2021-2024 Xiamen Yaji Software Co., Ltd.
  3. https://www.cocos.com/
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated documentation files (the "Software"), to deal
  6. in the Software without restriction, including without limitation the rights to
  7. use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  8. of the Software, and to permit persons to whom the Software is furnished to do so,
  9. subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in
  11. all copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  18. THE SOFTWARE.
  19. */
  20. import {
  21. assert, cclegacy, clamp, geometry, gfx, Layers, Material, pipeline,
  22. PipelineEventProcessor, PipelineEventType, ReflectionProbeManager, renderer,
  23. rendering, sys, Vec2, Vec3, Vec4, warn,
  24. } from 'cc';
  25. import { DEBUG, EDITOR } from 'cc/env';
  26. import {
  27. BloomType,
  28. makePipelineSettings,
  29. PipelineSettings,
  30. } from './builtin-pipeline-types';
  31. const { AABB, Sphere, intersect } = geometry;
  32. const { ClearFlagBit, Color, Format, FormatFeatureBit, LoadOp, StoreOp, TextureType, Viewport } = gfx;
  33. const { scene } = renderer;
  34. const { CameraUsage, CSMLevel, LightType } = scene;
  35. function forwardNeedClearColor(camera: renderer.scene.Camera): boolean {
  36. return !!(camera.clearFlag & (ClearFlagBit.COLOR | (ClearFlagBit.STENCIL << 1)));
  37. }
  38. function getCsmMainLightViewport(
  39. light: renderer.scene.DirectionalLight,
  40. w: number,
  41. h: number,
  42. level: number,
  43. vp: gfx.Viewport,
  44. screenSpaceSignY: number,
  45. ): void {
  46. if (light.shadowFixedArea || light.csmLevel === CSMLevel.LEVEL_1) {
  47. vp.left = 0;
  48. vp.top = 0;
  49. vp.width = Math.trunc(w);
  50. vp.height = Math.trunc(h);
  51. } else {
  52. vp.left = Math.trunc(level % 2 * 0.5 * w);
  53. if (screenSpaceSignY > 0) {
  54. vp.top = Math.trunc((1 - Math.floor(level / 2)) * 0.5 * h);
  55. } else {
  56. vp.top = Math.trunc(Math.floor(level / 2) * 0.5 * h);
  57. }
  58. vp.width = Math.trunc(0.5 * w);
  59. vp.height = Math.trunc(0.5 * h);
  60. }
  61. vp.left = Math.max(0, vp.left);
  62. vp.top = Math.max(0, vp.top);
  63. vp.width = Math.max(1, vp.width);
  64. vp.height = Math.max(1, vp.height);
  65. }
  66. export class PipelineConfigs {
  67. isWeb = false;
  68. isWebGL1 = false;
  69. isWebGPU = false;
  70. isMobile = false;
  71. isHDR = false;
  72. useFloatOutput = false;
  73. toneMappingType = 0; // 0: ACES, 1: None
  74. shadowEnabled = false;
  75. shadowMapFormat = Format.R32F;
  76. shadowMapSize = new Vec2(1, 1);
  77. usePlanarShadow = false;
  78. screenSpaceSignY = 1;
  79. supportDepthSample = false;
  80. mobileMaxSpotLightShadowMaps = 1;
  81. platform = new Vec4(0, 0, 0, 0);
  82. }
  83. function setupPipelineConfigs(
  84. ppl: rendering.BasicPipeline,
  85. configs: PipelineConfigs,
  86. ): void {
  87. const sampleFeature = FormatFeatureBit.SAMPLED_TEXTURE | FormatFeatureBit.LINEAR_FILTER;
  88. const device = ppl.device;
  89. // Platform
  90. configs.isWeb = !sys.isNative;
  91. configs.isWebGL1 = device.gfxAPI === gfx.API.WEBGL;
  92. configs.isWebGPU = device.gfxAPI === gfx.API.WEBGPU;
  93. configs.isMobile = sys.isMobile;
  94. // Rendering
  95. configs.isHDR = ppl.pipelineSceneData.isHDR; // Has tone mapping
  96. configs.useFloatOutput = ppl.getMacroBool('CC_USE_FLOAT_OUTPUT');
  97. configs.toneMappingType = ppl.pipelineSceneData.postSettings.toneMappingType;
  98. // Shadow
  99. const shadowInfo = ppl.pipelineSceneData.shadows;
  100. configs.shadowEnabled = shadowInfo.enabled;
  101. configs.shadowMapFormat = pipeline.supportsR32FloatTexture(ppl.device) ? Format.R32F : Format.RGBA8;
  102. configs.shadowMapSize.set(shadowInfo.size);
  103. configs.usePlanarShadow = shadowInfo.enabled && shadowInfo.type === renderer.scene.ShadowType.Planar;
  104. // Device
  105. configs.screenSpaceSignY = ppl.device.capabilities.screenSpaceSignY;
  106. configs.supportDepthSample = (ppl.device.getFormatFeatures(Format.DEPTH_STENCIL) & sampleFeature) === sampleFeature;
  107. // Constants
  108. const screenSpaceSignY = device.capabilities.screenSpaceSignY;
  109. configs.platform.x = configs.isMobile ? 1.0 : 0.0;
  110. configs.platform.w = (screenSpaceSignY * 0.5 + 0.5) << 1 | (device.capabilities.clipSpaceSignY * 0.5 + 0.5);
  111. }
  112. export interface PipelineSettings2 extends PipelineSettings {
  113. _passes?: rendering.PipelinePassBuilder[];
  114. }
  115. const defaultSettings = makePipelineSettings();
  116. export class CameraConfigs {
  117. settings: PipelineSettings = defaultSettings;
  118. // Window
  119. isMainGameWindow = false;
  120. renderWindowId = 0;
  121. // Camera
  122. colorName = '';
  123. depthStencilName = '';
  124. // Pipeline
  125. enableFullPipeline = false;
  126. enableProfiler = false;
  127. remainingPasses = 0;
  128. // Shading Scale
  129. enableShadingScale = false;
  130. shadingScale = 1.0;
  131. nativeWidth = 1;
  132. nativeHeight = 1;
  133. width = 1; // Scaled width
  134. height = 1; // Scaled height
  135. // Radiance
  136. enableHDR = false;
  137. radianceFormat = gfx.Format.RGBA8;
  138. // Tone Mapping
  139. copyAndTonemapMaterial: Material | null = null;
  140. // Depth
  141. /** @en mutable */
  142. enableStoreSceneDepth = false;
  143. }
  144. const sClearColorTransparentBlack = new Color(0, 0, 0, 0);
  145. function sortPipelinePassBuildersByConfigOrder(passBuilders: rendering.PipelinePassBuilder[]): void {
  146. passBuilders.sort((a, b) => {
  147. return a.getConfigOrder() - b.getConfigOrder();
  148. });
  149. }
  150. function sortPipelinePassBuildersByRenderOrder(passBuilders: rendering.PipelinePassBuilder[]): void {
  151. passBuilders.sort((a, b) => {
  152. return a.getRenderOrder() - b.getRenderOrder();
  153. });
  154. }
  155. function addCopyToScreenPass(
  156. ppl: rendering.BasicPipeline,
  157. pplConfigs: Readonly<PipelineConfigs>,
  158. cameraConfigs: CameraConfigs,
  159. input: string,
  160. ): rendering.BasicRenderPassBuilder {
  161. assert(!!cameraConfigs.copyAndTonemapMaterial);
  162. const pass = ppl.addRenderPass(
  163. cameraConfigs.nativeWidth,
  164. cameraConfigs.nativeHeight,
  165. 'cc-tone-mapping');
  166. pass.addRenderTarget(
  167. cameraConfigs.colorName,
  168. LoadOp.CLEAR, StoreOp.STORE,
  169. sClearColorTransparentBlack);
  170. pass.addTexture(input, 'inputTexture');
  171. pass.addQueue(rendering.QueueHint.OPAQUE)
  172. .addFullscreenQuad(cameraConfigs.copyAndTonemapMaterial, 1);
  173. return pass;
  174. }
  175. export function getPingPongRenderTarget(prevName: string, prefix: string, id: number): string {
  176. if (prevName.startsWith(prefix)) {
  177. return `${prefix}${1 - Number(prevName.charAt(prefix.length))}_${id}`;
  178. } else {
  179. return `${prefix}0_${id}`;
  180. }
  181. }
  182. export interface PipelineContext {
  183. colorName: string;
  184. depthStencilName: string;
  185. }
  186. class ForwardLighting {
  187. // Active lights
  188. private readonly lights: renderer.scene.Light[] = [];
  189. // Active spot lights with shadows (Mutually exclusive with `lights`)
  190. private readonly shadowEnabledSpotLights: renderer.scene.SpotLight[] = [];
  191. // Internal cached resources
  192. private readonly _sphere = Sphere.create(0, 0, 0, 1);
  193. private readonly _boundingBox = new AABB();
  194. private readonly _rangedDirLightBoundingBox = new AABB(0.0, 0.0, 0.0, 0.5, 0.5, 0.5);
  195. // ----------------------------------------------------------------
  196. // Interface
  197. // ----------------------------------------------------------------
  198. public cullLights(scene: renderer.RenderScene, frustum: geometry.Frustum, cameraPos?: Vec3): void {
  199. // TODO(zhouzhenglong): Make light culling native
  200. this.lights.length = 0;
  201. this.shadowEnabledSpotLights.length = 0;
  202. // spot lights
  203. for (const light of scene.spotLights) {
  204. if (light.baked) {
  205. continue;
  206. }
  207. Sphere.set(this._sphere, light.position.x, light.position.y, light.position.z, light.range);
  208. if (intersect.sphereFrustum(this._sphere, frustum)) {
  209. if (light.shadowEnabled) {
  210. this.shadowEnabledSpotLights.push(light);
  211. } else {
  212. this.lights.push(light);
  213. }
  214. }
  215. }
  216. // sphere lights
  217. for (const light of scene.sphereLights) {
  218. if (light.baked) {
  219. continue;
  220. }
  221. Sphere.set(this._sphere, light.position.x, light.position.y, light.position.z, light.range);
  222. if (intersect.sphereFrustum(this._sphere, frustum)) {
  223. this.lights.push(light);
  224. }
  225. }
  226. // point lights
  227. for (const light of scene.pointLights) {
  228. if (light.baked) {
  229. continue;
  230. }
  231. Sphere.set(this._sphere, light.position.x, light.position.y, light.position.z, light.range);
  232. if (intersect.sphereFrustum(this._sphere, frustum)) {
  233. this.lights.push(light);
  234. }
  235. }
  236. // ranged dir lights
  237. for (const light of scene.rangedDirLights) {
  238. AABB.transform(this._boundingBox, this._rangedDirLightBoundingBox, light.node!.getWorldMatrix());
  239. if (intersect.aabbFrustum(this._boundingBox, frustum)) {
  240. this.lights.push(light);
  241. }
  242. }
  243. if (cameraPos) {
  244. this.shadowEnabledSpotLights.sort(
  245. (lhs, rhs) => Vec3.squaredDistance(cameraPos, lhs.position) - Vec3.squaredDistance(cameraPos, rhs.position),
  246. );
  247. }
  248. }
  249. private _addLightQueues(camera: renderer.scene.Camera, pass: rendering.BasicRenderPassBuilder): void {
  250. for (const light of this.lights) {
  251. const queue = pass.addQueue(rendering.QueueHint.BLEND, 'forward-add');
  252. switch (light.type) {
  253. case LightType.SPHERE:
  254. queue.name = 'sphere-light';
  255. break;
  256. case LightType.SPOT:
  257. queue.name = 'spot-light';
  258. break;
  259. case LightType.POINT:
  260. queue.name = 'point-light';
  261. break;
  262. case LightType.RANGED_DIRECTIONAL:
  263. queue.name = 'ranged-directional-light';
  264. break;
  265. default:
  266. queue.name = 'unknown-light';
  267. }
  268. queue.addScene(
  269. camera,
  270. rendering.SceneFlags.BLEND,
  271. light,
  272. );
  273. }
  274. }
  275. public addSpotlightShadowPasses(
  276. ppl: rendering.BasicPipeline,
  277. camera: renderer.scene.Camera,
  278. maxNumShadowMaps: number,
  279. ): void {
  280. let i = 0;
  281. for (const light of this.shadowEnabledSpotLights) {
  282. const shadowMapSize = ppl.pipelineSceneData.shadows.size;
  283. const shadowPass = ppl.addRenderPass(shadowMapSize.x, shadowMapSize.y, 'default');
  284. shadowPass.name = `SpotLightShadowPass${i}`;
  285. shadowPass.addRenderTarget(`SpotShadowMap${i}`, LoadOp.CLEAR, StoreOp.STORE, new Color(1, 1, 1, 1));
  286. shadowPass.addDepthStencil(`SpotShadowDepth${i}`, LoadOp.CLEAR, StoreOp.DISCARD);
  287. shadowPass.addQueue(rendering.QueueHint.NONE, 'shadow-caster')
  288. .addScene(camera, rendering.SceneFlags.OPAQUE | rendering.SceneFlags.MASK | rendering.SceneFlags.SHADOW_CASTER)
  289. .useLightFrustum(light);
  290. ++i;
  291. if (i >= maxNumShadowMaps) {
  292. break;
  293. }
  294. }
  295. }
  296. public addLightQueues(pass: rendering.BasicRenderPassBuilder,
  297. camera: renderer.scene.Camera, maxNumShadowMaps: number): void {
  298. this._addLightQueues(camera, pass);
  299. let i = 0;
  300. for (const light of this.shadowEnabledSpotLights) {
  301. // Add spot-light pass
  302. // Save last RenderPass to the `pass` variable
  303. // TODO(zhouzhenglong): Fix per queue addTexture
  304. pass.addTexture(`SpotShadowMap${i}`, 'cc_spotShadowMap');
  305. const queue = pass.addQueue(rendering.QueueHint.BLEND, 'forward-add');
  306. queue.addScene(camera, rendering.SceneFlags.BLEND, light);
  307. ++i;
  308. if (i >= maxNumShadowMaps) {
  309. break;
  310. }
  311. }
  312. }
  313. // Notice: ForwardLighting cannot handle a lot of lights.
  314. // If there are too many lights, the performance will be very poor.
  315. // If many lights are needed, please implement a forward+ or deferred rendering pipeline.
  316. public addLightPasses(
  317. colorName: string,
  318. depthStencilName: string,
  319. depthStencilStoreOp: gfx.StoreOp,
  320. id: number, // window id
  321. width: number,
  322. height: number,
  323. camera: renderer.scene.Camera,
  324. viewport: gfx.Viewport,
  325. ppl: rendering.BasicPipeline,
  326. pass: rendering.BasicRenderPassBuilder,
  327. ): rendering.BasicRenderPassBuilder {
  328. this._addLightQueues(camera, pass);
  329. let count = 0;
  330. const shadowMapSize = ppl.pipelineSceneData.shadows.size;
  331. for (const light of this.shadowEnabledSpotLights) {
  332. const shadowPass = ppl.addRenderPass(shadowMapSize.x, shadowMapSize.y, 'default');
  333. shadowPass.name = 'SpotlightShadowPass';
  334. // Reuse csm shadow map
  335. shadowPass.addRenderTarget(`ShadowMap${id}`, LoadOp.CLEAR, StoreOp.STORE, new Color(1, 1, 1, 1));
  336. shadowPass.addDepthStencil(`ShadowDepth${id}`, LoadOp.CLEAR, StoreOp.DISCARD);
  337. shadowPass.addQueue(rendering.QueueHint.NONE, 'shadow-caster')
  338. .addScene(camera, rendering.SceneFlags.OPAQUE | rendering.SceneFlags.MASK | rendering.SceneFlags.SHADOW_CASTER)
  339. .useLightFrustum(light);
  340. // Add spot-light pass
  341. // Save last RenderPass to the `pass` variable
  342. ++count;
  343. const storeOp = count === this.shadowEnabledSpotLights.length
  344. ? depthStencilStoreOp
  345. : StoreOp.STORE;
  346. pass = ppl.addRenderPass(width, height, 'default');
  347. pass.name = 'SpotlightWithShadowMap';
  348. pass.setViewport(viewport);
  349. pass.addRenderTarget(colorName, LoadOp.LOAD);
  350. pass.addDepthStencil(depthStencilName, LoadOp.LOAD, storeOp);
  351. pass.addTexture(`ShadowMap${id}`, 'cc_spotShadowMap');
  352. const queue = pass.addQueue(rendering.QueueHint.BLEND, 'forward-add');
  353. queue.addScene(
  354. camera,
  355. rendering.SceneFlags.BLEND,
  356. light,
  357. );
  358. }
  359. return pass;
  360. }
  361. public isMultipleLightPassesNeeded(): boolean {
  362. return this.shadowEnabledSpotLights.length > 0;
  363. }
  364. }
  365. export interface ForwardPassConfigs {
  366. enableMainLightShadowMap: boolean;
  367. enableMainLightPlanarShadowMap: boolean;
  368. enablePlanarReflectionProbe: boolean;
  369. enableMSAA: boolean;
  370. enableSingleForwardPass: boolean;
  371. }
  372. export class BuiltinForwardPassBuilder implements rendering.PipelinePassBuilder {
  373. static ConfigOrder = 100;
  374. static RenderOrder = 100;
  375. getConfigOrder(): number {
  376. return BuiltinForwardPassBuilder.ConfigOrder;
  377. }
  378. getRenderOrder(): number {
  379. return BuiltinForwardPassBuilder.RenderOrder;
  380. }
  381. configCamera(
  382. camera: Readonly<renderer.scene.Camera>,
  383. pipelineConfigs: Readonly<PipelineConfigs>,
  384. cameraConfigs: CameraConfigs & ForwardPassConfigs): void {
  385. // Shadow
  386. cameraConfigs.enableMainLightShadowMap = pipelineConfigs.shadowEnabled
  387. && !pipelineConfigs.usePlanarShadow
  388. && !!camera.scene
  389. && !!camera.scene.mainLight
  390. && camera.scene.mainLight.shadowEnabled;
  391. cameraConfigs.enableMainLightPlanarShadowMap = pipelineConfigs.shadowEnabled
  392. && pipelineConfigs.usePlanarShadow
  393. && !!camera.scene
  394. && !!camera.scene.mainLight
  395. && camera.scene.mainLight.shadowEnabled;
  396. // Reflection Probe
  397. cameraConfigs.enablePlanarReflectionProbe = cameraConfigs.isMainGameWindow
  398. || camera.cameraUsage === CameraUsage.SCENE_VIEW
  399. || camera.cameraUsage === CameraUsage.GAME_VIEW;
  400. // MSAA
  401. cameraConfigs.enableMSAA = cameraConfigs.settings.msaa.enabled
  402. && !cameraConfigs.enableStoreSceneDepth // Cannot store MS depth, resolve depth is also not cross-platform
  403. && !pipelineConfigs.isWeb // TODO(zhouzhenglong): remove this constraint
  404. && !pipelineConfigs.isWebGL1;
  405. // Forward rendering (Depend on MSAA and TBR)
  406. cameraConfigs.enableSingleForwardPass
  407. = pipelineConfigs.isMobile || cameraConfigs.enableMSAA;
  408. ++cameraConfigs.remainingPasses;
  409. }
  410. windowResize(
  411. ppl: rendering.BasicPipeline,
  412. pplConfigs: Readonly<PipelineConfigs>,
  413. cameraConfigs: Readonly<CameraConfigs & ForwardPassConfigs>,
  414. window: renderer.RenderWindow,
  415. camera: renderer.scene.Camera,
  416. nativeWidth: number,
  417. nativeHeight: number): void {
  418. const ResourceFlags = rendering.ResourceFlags;
  419. const ResourceResidency = rendering.ResourceResidency;
  420. const id = window.renderWindowId;
  421. const settings = cameraConfigs.settings;
  422. const width = cameraConfigs.enableShadingScale
  423. ? Math.max(Math.floor(nativeWidth * cameraConfigs.shadingScale), 1)
  424. : nativeWidth;
  425. const height = cameraConfigs.enableShadingScale
  426. ? Math.max(Math.floor(nativeHeight * cameraConfigs.shadingScale), 1)
  427. : nativeHeight;
  428. // MsaaRadiance
  429. if (cameraConfigs.enableMSAA) {
  430. // Notice: We never store multisample results.
  431. // These samples are always resolved and discarded at the end of the render pass.
  432. // So the ResourceResidency should be MEMORYLESS.
  433. if (cameraConfigs.enableHDR) {
  434. ppl.addTexture(`MsaaRadiance${id}`, TextureType.TEX2D, cameraConfigs.radianceFormat, width, height, 1, 1, 1,
  435. settings.msaa.sampleCount, ResourceFlags.COLOR_ATTACHMENT, ResourceResidency.MEMORYLESS);
  436. } else {
  437. ppl.addTexture(`MsaaRadiance${id}`, TextureType.TEX2D, Format.RGBA8, width, height, 1, 1, 1,
  438. settings.msaa.sampleCount, ResourceFlags.COLOR_ATTACHMENT, ResourceResidency.MEMORYLESS);
  439. }
  440. ppl.addTexture(`MsaaDepthStencil${id}`, TextureType.TEX2D, Format.DEPTH_STENCIL, width, height, 1, 1, 1,
  441. settings.msaa.sampleCount, ResourceFlags.DEPTH_STENCIL_ATTACHMENT, ResourceResidency.MEMORYLESS);
  442. }
  443. // Mainlight ShadowMap
  444. ppl.addRenderTarget(
  445. `ShadowMap${id}`,
  446. pplConfigs.shadowMapFormat,
  447. pplConfigs.shadowMapSize.x,
  448. pplConfigs.shadowMapSize.y,
  449. );
  450. ppl.addDepthStencil(
  451. `ShadowDepth${id}`,
  452. Format.DEPTH_STENCIL,
  453. pplConfigs.shadowMapSize.x,
  454. pplConfigs.shadowMapSize.y,
  455. );
  456. // Spot-light shadow maps
  457. if (cameraConfigs.enableSingleForwardPass) {
  458. const count = pplConfigs.mobileMaxSpotLightShadowMaps;
  459. for (let i = 0; i !== count; ++i) {
  460. ppl.addRenderTarget(
  461. `SpotShadowMap${i}`,
  462. pplConfigs.shadowMapFormat,
  463. pplConfigs.shadowMapSize.x,
  464. pplConfigs.shadowMapSize.y,
  465. );
  466. ppl.addDepthStencil(
  467. `SpotShadowDepth${i}`,
  468. Format.DEPTH_STENCIL,
  469. pplConfigs.shadowMapSize.x,
  470. pplConfigs.shadowMapSize.y,
  471. );
  472. }
  473. }
  474. }
  475. setup(
  476. ppl: rendering.BasicPipeline,
  477. pplConfigs: Readonly<PipelineConfigs>,
  478. cameraConfigs: CameraConfigs & ForwardPassConfigs,
  479. camera: renderer.scene.Camera,
  480. context: PipelineContext): rendering.BasicRenderPassBuilder | undefined {
  481. // Add global constants
  482. ppl.setVec4('g_platform', pplConfigs.platform);
  483. const id = camera.window.renderWindowId;
  484. const scene = camera.scene!;
  485. const mainLight = scene.mainLight;
  486. --cameraConfigs.remainingPasses;
  487. assert(cameraConfigs.remainingPasses >= 0);
  488. // Forward Lighting (Light Culling)
  489. this.forwardLighting.cullLights(scene, camera.frustum);
  490. // Main Directional light CSM Shadow Map
  491. if (cameraConfigs.enableMainLightShadowMap) {
  492. assert(!!mainLight);
  493. this._addCascadedShadowMapPass(ppl, pplConfigs, id, mainLight, camera);
  494. }
  495. // Spot light shadow maps (Mobile or MSAA)
  496. if (cameraConfigs.enableSingleForwardPass) {
  497. // Currently, only support 1 spot light with shadow map on mobile platform.
  498. // TODO(zhouzhenglong): Relex this limitation.
  499. this.forwardLighting.addSpotlightShadowPasses(
  500. ppl, camera, pplConfigs.mobileMaxSpotLightShadowMaps);
  501. }
  502. this._tryAddReflectionProbePasses(ppl, cameraConfigs, id, mainLight, camera.scene);
  503. if (cameraConfigs.remainingPasses > 0 || cameraConfigs.enableShadingScale) {
  504. context.colorName = cameraConfigs.enableShadingScale
  505. ? `ScaledRadiance0_${id}`
  506. : `Radiance0_${id}`;
  507. context.depthStencilName = cameraConfigs.enableShadingScale
  508. ? `ScaledSceneDepth_${id}`
  509. : `SceneDepth_${id}`;
  510. } else {
  511. context.colorName = cameraConfigs.colorName;
  512. context.depthStencilName = cameraConfigs.depthStencilName;
  513. }
  514. const pass = this._addForwardRadiancePasses(
  515. ppl, pplConfigs, cameraConfigs, id, camera,
  516. cameraConfigs.width, cameraConfigs.height, mainLight,
  517. context.colorName, context.depthStencilName,
  518. !cameraConfigs.enableMSAA,
  519. cameraConfigs.enableStoreSceneDepth ? StoreOp.STORE : StoreOp.DISCARD);
  520. if (!cameraConfigs.enableStoreSceneDepth) {
  521. context.depthStencilName = '';
  522. }
  523. if (cameraConfigs.remainingPasses === 0 && cameraConfigs.enableShadingScale) {
  524. return addCopyToScreenPass(ppl, pplConfigs, cameraConfigs, context.colorName);
  525. } else {
  526. return pass;
  527. }
  528. }
  529. private _addCascadedShadowMapPass(
  530. ppl: rendering.BasicPipeline,
  531. pplConfigs: Readonly<PipelineConfigs>,
  532. id: number,
  533. light: renderer.scene.DirectionalLight,
  534. camera: renderer.scene.Camera,
  535. ): void {
  536. const QueueHint = rendering.QueueHint;
  537. const SceneFlags = rendering.SceneFlags;
  538. // ----------------------------------------------------------------
  539. // Dynamic states
  540. // ----------------------------------------------------------------
  541. const shadowSize = ppl.pipelineSceneData.shadows.size;
  542. const width = shadowSize.x;
  543. const height = shadowSize.y;
  544. const viewport = this._viewport;
  545. viewport.left = viewport.top = 0;
  546. viewport.width = width;
  547. viewport.height = height;
  548. // ----------------------------------------------------------------
  549. // CSM Shadow Map
  550. // ----------------------------------------------------------------
  551. const pass = ppl.addRenderPass(width, height, 'default');
  552. pass.name = 'CascadedShadowMap';
  553. pass.addRenderTarget(`ShadowMap${id}`, LoadOp.CLEAR, StoreOp.STORE, new Color(1, 1, 1, 1));
  554. pass.addDepthStencil(`ShadowDepth${id}`, LoadOp.CLEAR, StoreOp.DISCARD);
  555. const csmLevel = ppl.pipelineSceneData.csmSupported ? light.csmLevel : 1;
  556. // Add shadow map viewports
  557. for (let level = 0; level !== csmLevel; ++level) {
  558. getCsmMainLightViewport(light, width, height, level, this._viewport, pplConfigs.screenSpaceSignY);
  559. const queue = pass.addQueue(QueueHint.NONE, 'shadow-caster');
  560. if (!pplConfigs.isWebGPU) { // Temporary workaround for WebGPU
  561. queue.setViewport(this._viewport);
  562. }
  563. queue
  564. .addScene(camera, SceneFlags.OPAQUE | SceneFlags.MASK | SceneFlags.SHADOW_CASTER)
  565. .useLightFrustum(light, level);
  566. }
  567. }
  568. private _tryAddReflectionProbePasses(
  569. ppl: rendering.BasicPipeline,
  570. cameraConfigs: Readonly<CameraConfigs & ForwardPassConfigs>,
  571. id: number,
  572. mainLight: renderer.scene.DirectionalLight | null,
  573. scene: renderer.RenderScene | null,
  574. ): void {
  575. const reflectionProbeManager = cclegacy.internal.reflectionProbeManager as ReflectionProbeManager | undefined;
  576. if (!reflectionProbeManager) {
  577. return;
  578. }
  579. const ResourceResidency = rendering.ResourceResidency;
  580. const probes = reflectionProbeManager.getProbes();
  581. const maxProbeCount = 4;
  582. let probeID = 0;
  583. for (const probe of probes) {
  584. if (!probe.needRender) {
  585. continue;
  586. }
  587. const area = probe.renderArea();
  588. const width = Math.max(Math.floor(area.x), 1);
  589. const height = Math.max(Math.floor(area.y), 1);
  590. if (probe.probeType === renderer.scene.ProbeType.PLANAR) {
  591. if (!cameraConfigs.enablePlanarReflectionProbe) {
  592. continue;
  593. }
  594. const window: renderer.RenderWindow = probe.realtimePlanarTexture!.window!;
  595. const colorName = `PlanarProbeRT${probeID}`;
  596. const depthStencilName = `PlanarProbeDS${probeID}`;
  597. // ProbeResource
  598. ppl.addRenderWindow(colorName,
  599. cameraConfigs.radianceFormat, width, height, window);
  600. ppl.addDepthStencil(depthStencilName,
  601. gfx.Format.DEPTH_STENCIL, width, height, ResourceResidency.MEMORYLESS);
  602. // Rendering
  603. const probePass = ppl.addRenderPass(width, height, 'default');
  604. probePass.name = `PlanarReflectionProbe${probeID}`;
  605. this._buildReflectionProbePass(probePass, cameraConfigs, id, probe.camera,
  606. colorName, depthStencilName, mainLight, scene);
  607. } else if (EDITOR) {
  608. for (let faceIdx = 0; faceIdx < probe.bakedCubeTextures.length; faceIdx++) {
  609. probe.updateCameraDir(faceIdx);
  610. const window: renderer.RenderWindow = probe.bakedCubeTextures[faceIdx].window!;
  611. const colorName = `CubeProbeRT${probeID}${faceIdx}`;
  612. const depthStencilName = `CubeProbeDS${probeID}${faceIdx}`;
  613. // ProbeResource
  614. ppl.addRenderWindow(colorName,
  615. cameraConfigs.radianceFormat, width, height, window);
  616. ppl.addDepthStencil(depthStencilName,
  617. gfx.Format.DEPTH_STENCIL, width, height, ResourceResidency.MEMORYLESS);
  618. // Rendering
  619. const probePass = ppl.addRenderPass(width, height, 'default');
  620. probePass.name = `CubeProbe${probeID}${faceIdx}`;
  621. this._buildReflectionProbePass(probePass, cameraConfigs, id, probe.camera,
  622. colorName, depthStencilName, mainLight, scene);
  623. }
  624. probe.needRender = false;
  625. }
  626. ++probeID;
  627. if (probeID === maxProbeCount) {
  628. break;
  629. }
  630. }
  631. }
  632. private _buildReflectionProbePass(
  633. pass: rendering.BasicRenderPassBuilder,
  634. cameraConfigs: Readonly<CameraConfigs & ForwardPassConfigs>,
  635. id: number,
  636. camera: renderer.scene.Camera,
  637. colorName: string,
  638. depthStencilName: string,
  639. mainLight: renderer.scene.DirectionalLight | null,
  640. scene: renderer.RenderScene | null = null,
  641. ): void {
  642. const QueueHint = rendering.QueueHint;
  643. const SceneFlags = rendering.SceneFlags;
  644. // set viewport
  645. const colorStoreOp = cameraConfigs.enableMSAA ? StoreOp.DISCARD : StoreOp.STORE;
  646. // bind output render target
  647. if (forwardNeedClearColor(camera)) {
  648. this._reflectionProbeClearColor.x = camera.clearColor.x;
  649. this._reflectionProbeClearColor.y = camera.clearColor.y;
  650. this._reflectionProbeClearColor.z = camera.clearColor.z;
  651. const clearColor = rendering.packRGBE(this._reflectionProbeClearColor);
  652. this._clearColor.x = clearColor.x;
  653. this._clearColor.y = clearColor.y;
  654. this._clearColor.z = clearColor.z;
  655. this._clearColor.w = clearColor.w;
  656. pass.addRenderTarget(colorName, LoadOp.CLEAR, colorStoreOp, this._clearColor);
  657. } else {
  658. pass.addRenderTarget(colorName, LoadOp.LOAD, colorStoreOp);
  659. }
  660. // bind depth stencil buffer
  661. if (camera.clearFlag & ClearFlagBit.DEPTH_STENCIL) {
  662. pass.addDepthStencil(
  663. depthStencilName,
  664. LoadOp.CLEAR,
  665. StoreOp.DISCARD,
  666. camera.clearDepth,
  667. camera.clearStencil,
  668. camera.clearFlag & ClearFlagBit.DEPTH_STENCIL,
  669. );
  670. } else {
  671. pass.addDepthStencil(depthStencilName, LoadOp.LOAD, StoreOp.DISCARD);
  672. }
  673. // Set shadow map if enabled
  674. if (cameraConfigs.enableMainLightShadowMap) {
  675. pass.addTexture(`ShadowMap${id}`, 'cc_shadowMap');
  676. }
  677. // TODO(zhouzhenglong): Separate OPAQUE and MASK queue
  678. // add opaque and mask queue
  679. pass.addQueue(QueueHint.NONE, 'reflect-map') // Currently we put OPAQUE and MASK into one queue, so QueueHint is NONE
  680. .addScene(camera,
  681. SceneFlags.OPAQUE | SceneFlags.MASK | SceneFlags.REFLECTION_PROBE,
  682. mainLight || undefined,
  683. scene ? scene : undefined);
  684. }
  685. private _addForwardRadiancePasses(
  686. ppl: rendering.BasicPipeline,
  687. pplConfigs: Readonly<PipelineConfigs>,
  688. cameraConfigs: Readonly<CameraConfigs & ForwardPassConfigs>,
  689. id: number,
  690. camera: renderer.scene.Camera,
  691. width: number,
  692. height: number,
  693. mainLight: renderer.scene.DirectionalLight | null,
  694. colorName: string,
  695. depthStencilName: string,
  696. disableMSAA: boolean = false,
  697. depthStencilStoreOp: gfx.StoreOp = StoreOp.DISCARD,
  698. ): rendering.BasicRenderPassBuilder {
  699. const QueueHint = rendering.QueueHint;
  700. const SceneFlags = rendering.SceneFlags;
  701. // ----------------------------------------------------------------
  702. // Dynamic states
  703. // ----------------------------------------------------------------
  704. // Prepare camera clear color
  705. const clearColor = camera.clearColor; // Reduce C++/TS interop
  706. this._clearColor.x = clearColor.x;
  707. this._clearColor.y = clearColor.y;
  708. this._clearColor.z = clearColor.z;
  709. this._clearColor.w = clearColor.w;
  710. // Prepare camera viewport
  711. const viewport = camera.viewport; // Reduce C++/TS interop
  712. this._viewport.left = Math.round(viewport.x * width);
  713. this._viewport.top = Math.round(viewport.y * height);
  714. // Here we must use camera.viewport.width instead of camera.viewport.z, which
  715. // is undefined on native platform. The same as camera.viewport.height.
  716. this._viewport.width = Math.max(Math.round(viewport.width * width), 1);
  717. this._viewport.height = Math.max(Math.round(viewport.height * height), 1);
  718. // MSAA
  719. const enableMSAA = !disableMSAA && cameraConfigs.enableMSAA;
  720. assert(!enableMSAA || cameraConfigs.enableSingleForwardPass);
  721. // ----------------------------------------------------------------
  722. // Forward Lighting (Main Directional Light)
  723. // ----------------------------------------------------------------
  724. const pass = cameraConfigs.enableSingleForwardPass
  725. ? this._addForwardSingleRadiancePass(ppl, pplConfigs, cameraConfigs,
  726. id, camera, enableMSAA, width, height, mainLight,
  727. colorName, depthStencilName, depthStencilStoreOp)
  728. : this._addForwardMultipleRadiancePasses(ppl, cameraConfigs,
  729. id, camera, width, height, mainLight,
  730. colorName, depthStencilName, depthStencilStoreOp);
  731. // Planar Shadow
  732. if (cameraConfigs.enableMainLightPlanarShadowMap) {
  733. this._addPlanarShadowQueue(camera, mainLight, pass);
  734. }
  735. // ----------------------------------------------------------------
  736. // Forward Lighting (Blend)
  737. // ----------------------------------------------------------------
  738. // Add transparent queue
  739. const sceneFlags = SceneFlags.BLEND |
  740. (camera.geometryRenderer
  741. ? SceneFlags.GEOMETRY
  742. : SceneFlags.NONE);
  743. pass
  744. .addQueue(QueueHint.BLEND)
  745. .addScene(camera, sceneFlags, mainLight || undefined);
  746. return pass;
  747. }
  748. private _addForwardSingleRadiancePass(
  749. ppl: rendering.BasicPipeline,
  750. pplConfigs: Readonly<PipelineConfigs>,
  751. cameraConfigs: Readonly<CameraConfigs & ForwardPassConfigs>,
  752. id: number,
  753. camera: renderer.scene.Camera,
  754. enableMSAA: boolean,
  755. width: number,
  756. height: number,
  757. mainLight: renderer.scene.DirectionalLight | null,
  758. colorName: string,
  759. depthStencilName: string,
  760. depthStencilStoreOp: gfx.StoreOp
  761. ): rendering.BasicRenderPassBuilder {
  762. assert(cameraConfigs.enableSingleForwardPass);
  763. // ----------------------------------------------------------------
  764. // Forward Lighting (Main Directional Light)
  765. // ----------------------------------------------------------------
  766. let pass: rendering.BasicRenderPassBuilder;
  767. if (enableMSAA) {
  768. const msaaRadianceName = `MsaaRadiance${id}`;
  769. const msaaDepthStencilName = `MsaaDepthStencil${id}`;
  770. const sampleCount = cameraConfigs.settings.msaa.sampleCount;
  771. const msPass = ppl.addMultisampleRenderPass(width, height, sampleCount, 0, 'default');
  772. msPass.name = 'MsaaForwardPass';
  773. // MSAA always discards depth stencil
  774. this._buildForwardMainLightPass(msPass, cameraConfigs, id, camera,
  775. msaaRadianceName, msaaDepthStencilName, StoreOp.DISCARD, mainLight);
  776. msPass.resolveRenderTarget(msaaRadianceName, colorName);
  777. pass = msPass;
  778. } else {
  779. pass = ppl.addRenderPass(width, height, 'default');
  780. pass.name = 'ForwardPass';
  781. this._buildForwardMainLightPass(pass, cameraConfigs, id, camera,
  782. colorName, depthStencilName, depthStencilStoreOp, mainLight);
  783. }
  784. assert(pass !== undefined);
  785. // Forward Lighting (Additive Lights)
  786. this.forwardLighting.addLightQueues(
  787. pass,
  788. camera,
  789. pplConfigs.mobileMaxSpotLightShadowMaps,
  790. );
  791. return pass;
  792. }
  793. private _addForwardMultipleRadiancePasses(
  794. ppl: rendering.BasicPipeline,
  795. cameraConfigs: Readonly<CameraConfigs & ForwardPassConfigs>,
  796. id: number,
  797. camera: renderer.scene.Camera,
  798. width: number,
  799. height: number,
  800. mainLight: renderer.scene.DirectionalLight | null,
  801. colorName: string,
  802. depthStencilName: string,
  803. depthStencilStoreOp: gfx.StoreOp
  804. ): rendering.BasicRenderPassBuilder {
  805. assert(!cameraConfigs.enableSingleForwardPass);
  806. // Forward Lighting (Main Directional Light)
  807. let pass = ppl.addRenderPass(width, height, 'default');
  808. pass.name = 'ForwardPass';
  809. const firstStoreOp = this.forwardLighting.isMultipleLightPassesNeeded()
  810. ? StoreOp.STORE
  811. : depthStencilStoreOp;
  812. this._buildForwardMainLightPass(pass, cameraConfigs,
  813. id, camera, colorName, depthStencilName, firstStoreOp, mainLight);
  814. // Forward Lighting (Additive Lights)
  815. pass = this.forwardLighting
  816. .addLightPasses(colorName, depthStencilName, depthStencilStoreOp,
  817. id, width, height, camera, this._viewport, ppl, pass);
  818. return pass;
  819. }
  820. private _buildForwardMainLightPass(
  821. pass: rendering.BasicRenderPassBuilder,
  822. cameraConfigs: Readonly<CameraConfigs & ForwardPassConfigs>,
  823. id: number,
  824. camera: renderer.scene.Camera,
  825. colorName: string,
  826. depthStencilName: string,
  827. depthStencilStoreOp: gfx.StoreOp,
  828. mainLight: renderer.scene.DirectionalLight | null,
  829. scene: renderer.RenderScene | null = null,
  830. ): void {
  831. const QueueHint = rendering.QueueHint;
  832. const SceneFlags = rendering.SceneFlags;
  833. // set viewport
  834. pass.setViewport(this._viewport);
  835. const colorStoreOp = cameraConfigs.enableMSAA ? StoreOp.DISCARD : StoreOp.STORE;
  836. // bind output render target
  837. if (forwardNeedClearColor(camera)) {
  838. pass.addRenderTarget(colorName, LoadOp.CLEAR, colorStoreOp, this._clearColor);
  839. } else {
  840. pass.addRenderTarget(colorName, LoadOp.LOAD, colorStoreOp);
  841. }
  842. // bind depth stencil buffer
  843. if (DEBUG) {
  844. if (colorName === cameraConfigs.colorName &&
  845. depthStencilName !== cameraConfigs.depthStencilName) {
  846. warn('Default framebuffer cannot use custom depth stencil buffer');
  847. }
  848. }
  849. if (camera.clearFlag & ClearFlagBit.DEPTH_STENCIL) {
  850. pass.addDepthStencil(
  851. depthStencilName,
  852. LoadOp.CLEAR,
  853. depthStencilStoreOp,
  854. camera.clearDepth,
  855. camera.clearStencil,
  856. camera.clearFlag & ClearFlagBit.DEPTH_STENCIL,
  857. );
  858. } else {
  859. pass.addDepthStencil(depthStencilName, LoadOp.LOAD, depthStencilStoreOp);
  860. }
  861. // Set shadow map if enabled
  862. if (cameraConfigs.enableMainLightShadowMap) {
  863. pass.addTexture(`ShadowMap${id}`, 'cc_shadowMap');
  864. }
  865. // TODO(zhouzhenglong): Separate OPAQUE and MASK queue
  866. // add opaque and mask queue
  867. pass.addQueue(QueueHint.NONE) // Currently we put OPAQUE and MASK into one queue, so QueueHint is NONE
  868. .addScene(camera,
  869. SceneFlags.OPAQUE | SceneFlags.MASK,
  870. mainLight || undefined,
  871. scene ? scene : undefined);
  872. }
  873. private _addPlanarShadowQueue(
  874. camera: renderer.scene.Camera,
  875. mainLight: renderer.scene.DirectionalLight | null,
  876. pass: rendering.BasicRenderPassBuilder,
  877. ) {
  878. const QueueHint = rendering.QueueHint;
  879. const SceneFlags = rendering.SceneFlags;
  880. pass.addQueue(QueueHint.BLEND, 'planar-shadow')
  881. .addScene(
  882. camera,
  883. SceneFlags.SHADOW_CASTER | SceneFlags.PLANAR_SHADOW | SceneFlags.BLEND,
  884. mainLight || undefined,
  885. );
  886. }
  887. private readonly forwardLighting = new ForwardLighting();
  888. private readonly _viewport = new Viewport();
  889. private readonly _clearColor = new Color(0, 0, 0, 1);
  890. private readonly _reflectionProbeClearColor = new Vec3(0, 0, 0);
  891. }
  892. export interface BloomPassConfigs {
  893. enableBloom: boolean;
  894. }
  895. function downSize(size: number, scale: number): number {
  896. return Math.max(Math.floor(size * scale), 1);
  897. }
  898. interface RenderTextureDesc {
  899. name: string;
  900. width: number;
  901. height: number;
  902. }
  903. export class BuiltinBloomPassBuilder implements rendering.PipelinePassBuilder {
  904. getConfigOrder(): number {
  905. return 0;
  906. }
  907. getRenderOrder(): number {
  908. return 200;
  909. }
  910. configCamera(
  911. camera: Readonly<renderer.scene.Camera>,
  912. pipelineConfigs: Readonly<PipelineConfigs>,
  913. cameraConfigs: CameraConfigs & BloomPassConfigs): void {
  914. const { bloom } = cameraConfigs.settings;
  915. const hasValidMaterial = (
  916. bloom.type === BloomType.KawaseDualFilter && !!bloom.kawaseFilterMaterial ||
  917. bloom.type === BloomType.MipmapFilter && !!bloom.mipmapFilterMaterial
  918. );
  919. cameraConfigs.enableBloom = bloom.enabled && hasValidMaterial;
  920. if (cameraConfigs.enableBloom) {
  921. ++cameraConfigs.remainingPasses;
  922. }
  923. }
  924. windowResize(
  925. ppl: rendering.BasicPipeline,
  926. pplConfigs: Readonly<PipelineConfigs>,
  927. cameraConfigs: CameraConfigs & BloomPassConfigs,
  928. window: renderer.RenderWindow): void {
  929. if (!cameraConfigs.enableBloom) {
  930. return;
  931. }
  932. const { width, height, settings: { bloom } } = cameraConfigs;
  933. const id = window.renderWindowId;
  934. const format = cameraConfigs.radianceFormat;
  935. if (bloom.type === BloomType.KawaseDualFilter) {
  936. let bloomWidth = cameraConfigs.width;
  937. let bloomHeight = cameraConfigs.height;
  938. for (let i = 0; i !== bloom.iterations + 1; ++i) {
  939. bloomWidth = Math.max(Math.floor(bloomWidth / 2), 1);
  940. bloomHeight = Math.max(Math.floor(bloomHeight / 2), 1);
  941. ppl.addRenderTarget(`BloomTex${id}_${i}`, format, bloomWidth, bloomHeight);
  942. }
  943. } else if (bloom.type === BloomType.MipmapFilter) {
  944. const iterations = bloom.iterations;
  945. for (let i = 0; i !== iterations + 1; ++i) {
  946. // DownSample
  947. if (i < iterations) {
  948. const scale = Math.pow(0.5, i + 2);
  949. this._bloomDownSampleTexDescs[i] = this.createTexture(
  950. ppl,
  951. `DownSampleColor${id}${i}`,
  952. downSize(width, scale),
  953. downSize(height, scale),
  954. format);
  955. }
  956. // UpSample
  957. if (i < iterations - 1) {
  958. const scale = Math.pow(0.5, iterations - i - 1);
  959. this._bloomUpSampleTexDescs[i] = this.createTexture(
  960. ppl,
  961. `UpSampleColor${id}${i}`,
  962. downSize(width, scale),
  963. downSize(height, scale),
  964. format);
  965. }
  966. }
  967. this._originalColorDesc = this.createTexture(ppl, `OriginalColor${id}`, width, height, format);
  968. this._prefilterTexDesc = this.createTexture(ppl, `PrefilterColor${id}`,
  969. downSize(width, 0.5), downSize(height, 0.5), format);
  970. }
  971. }
  972. private createTexture(
  973. ppl: rendering.BasicPipeline,
  974. name: string, width: number, height: number, format: number): RenderTextureDesc {
  975. const desc = { name, width, height };
  976. ppl.addRenderTarget(desc.name, format, desc.width, desc.height);
  977. return desc;
  978. }
  979. setup(
  980. ppl: rendering.BasicPipeline,
  981. pplConfigs: Readonly<PipelineConfigs>,
  982. cameraConfigs: CameraConfigs & BloomPassConfigs,
  983. camera: renderer.scene.Camera,
  984. context: PipelineContext,
  985. prevRenderPass?: rendering.BasicRenderPassBuilder)
  986. : rendering.BasicRenderPassBuilder | undefined {
  987. if (!cameraConfigs.enableBloom) {
  988. return prevRenderPass;
  989. }
  990. --cameraConfigs.remainingPasses;
  991. assert(cameraConfigs.remainingPasses >= 0);
  992. const bloom = cameraConfigs.settings.bloom;
  993. const id = camera.window.renderWindowId;
  994. switch (bloom.type) {
  995. case BloomType.KawaseDualFilter: {
  996. const material = bloom.kawaseFilterMaterial;
  997. assert(!!material);
  998. return this._addKawaseDualFilterBloomPasses(
  999. ppl, pplConfigs,
  1000. cameraConfigs,
  1001. cameraConfigs.settings,
  1002. material,
  1003. id,
  1004. cameraConfigs.width,
  1005. cameraConfigs.height,
  1006. context.colorName);
  1007. }
  1008. case BloomType.MipmapFilter: {
  1009. const material = bloom.mipmapFilterMaterial;
  1010. assert(!!material);
  1011. return this._addMipmapFilterBloomPasses(
  1012. ppl, pplConfigs,
  1013. cameraConfigs,
  1014. cameraConfigs.settings,
  1015. material,
  1016. id,
  1017. cameraConfigs.width,
  1018. cameraConfigs.height,
  1019. context.colorName);
  1020. }
  1021. default:
  1022. return prevRenderPass;
  1023. }
  1024. }
  1025. private _addKawaseDualFilterBloomPasses(
  1026. ppl: rendering.BasicPipeline,
  1027. pplConfigs: Readonly<PipelineConfigs>,
  1028. cameraConfigs: CameraConfigs & Readonly<BloomPassConfigs>,
  1029. settings: PipelineSettings,
  1030. bloomMaterial: Material,
  1031. id: number,
  1032. width: number,
  1033. height: number,
  1034. radianceName: string,
  1035. ): rendering.BasicRenderPassBuilder {
  1036. const QueueHint = rendering.QueueHint;
  1037. // Based on Kawase Dual Filter Blur. Saves bandwidth on mobile devices.
  1038. // eslint-disable-next-line max-len
  1039. // https://community.arm.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/00-00-00-20-66/siggraph2015_2D00_mmg_2D00_marius_2D00_slides.pdf
  1040. // Size: [prefilter(1/2), downsample(1/4), downsample(1/8), downsample(1/16), ...]
  1041. const iterations = settings.bloom.iterations;
  1042. const sizeCount = iterations + 1;
  1043. this._bloomWidths.length = sizeCount;
  1044. this._bloomHeights.length = sizeCount;
  1045. this._bloomWidths[0] = Math.max(Math.floor(width / 2), 1);
  1046. this._bloomHeights[0] = Math.max(Math.floor(height / 2), 1);
  1047. for (let i = 1; i !== sizeCount; ++i) {
  1048. this._bloomWidths[i] = Math.max(Math.floor(this._bloomWidths[i - 1] / 2), 1);
  1049. this._bloomHeights[i] = Math.max(Math.floor(this._bloomHeights[i - 1] / 2), 1);
  1050. }
  1051. // Bloom texture names
  1052. this._bloomTexNames.length = sizeCount;
  1053. for (let i = 0; i !== sizeCount; ++i) {
  1054. this._bloomTexNames[i] = `BloomTex${id}_${i}`;
  1055. }
  1056. // Setup bloom parameters
  1057. this._bloomParams.x = pplConfigs.useFloatOutput ? 1 : 0;
  1058. this._bloomParams.y = 0; // unused
  1059. this._bloomParams.z = settings.bloom.threshold;
  1060. this._bloomParams.w = settings.bloom.enableAlphaMask ? 1 : 0;
  1061. // Prefilter pass
  1062. const prefilterPass = ppl.addRenderPass(this._bloomWidths[0], this._bloomHeights[0], 'cc-bloom-prefilter');
  1063. prefilterPass.addRenderTarget(
  1064. this._bloomTexNames[0],
  1065. LoadOp.CLEAR,
  1066. StoreOp.STORE,
  1067. this._clearColorTransparentBlack,
  1068. );
  1069. prefilterPass.addTexture(radianceName, 'inputTexture');
  1070. prefilterPass.setVec4('bloomParams', this._bloomParams);
  1071. prefilterPass
  1072. .addQueue(QueueHint.OPAQUE)
  1073. .addFullscreenQuad(bloomMaterial, 0);
  1074. // Downsample passes
  1075. for (let i = 1; i !== sizeCount; ++i) {
  1076. const downPass = ppl.addRenderPass(this._bloomWidths[i], this._bloomHeights[i], 'cc-bloom-downsample');
  1077. downPass.addRenderTarget(this._bloomTexNames[i], LoadOp.CLEAR, StoreOp.STORE, this._clearColorTransparentBlack);
  1078. downPass.addTexture(this._bloomTexNames[i - 1], 'bloomTexture');
  1079. this._bloomTexSize.x = this._bloomWidths[i - 1];
  1080. this._bloomTexSize.y = this._bloomHeights[i - 1];
  1081. downPass.setVec4('bloomTexSize', this._bloomTexSize);
  1082. downPass
  1083. .addQueue(QueueHint.OPAQUE)
  1084. .addFullscreenQuad(bloomMaterial, 1);
  1085. }
  1086. // Upsample passes
  1087. for (let i = iterations; i-- > 0;) {
  1088. const upPass = ppl.addRenderPass(this._bloomWidths[i], this._bloomHeights[i], 'cc-bloom-upsample');
  1089. upPass.addRenderTarget(this._bloomTexNames[i], LoadOp.CLEAR, StoreOp.STORE, this._clearColorTransparentBlack);
  1090. upPass.addTexture(this._bloomTexNames[i + 1], 'bloomTexture');
  1091. this._bloomTexSize.x = this._bloomWidths[i + 1];
  1092. this._bloomTexSize.y = this._bloomHeights[i + 1];
  1093. upPass.setVec4('bloomTexSize', this._bloomTexSize);
  1094. upPass
  1095. .addQueue(QueueHint.OPAQUE)
  1096. .addFullscreenQuad(bloomMaterial, 2);
  1097. }
  1098. // Combine pass
  1099. this._bloomParams.w = settings.bloom.intensity;
  1100. const combinePass = ppl.addRenderPass(width, height, 'cc-bloom-combine');
  1101. combinePass.addRenderTarget(radianceName, LoadOp.LOAD, StoreOp.STORE);
  1102. combinePass.addTexture(this._bloomTexNames[0], 'bloomTexture');
  1103. combinePass.setVec4('bloomParams', this._bloomParams);
  1104. combinePass
  1105. .addQueue(QueueHint.BLEND)
  1106. .addFullscreenQuad(bloomMaterial, 3);
  1107. if (cameraConfigs.remainingPasses === 0) {
  1108. return addCopyToScreenPass(ppl, pplConfigs, cameraConfigs, radianceName);
  1109. } else {
  1110. return combinePass;
  1111. }
  1112. }
  1113. private _addPass(
  1114. ppl: rendering.BasicPipeline,
  1115. width: number,
  1116. height: number,
  1117. layout: string,
  1118. colorName: string,
  1119. material: Material,
  1120. passIndex: number,
  1121. loadOp: gfx.LoadOp = LoadOp.CLEAR,
  1122. clearColor: gfx.Color = sClearColorTransparentBlack,
  1123. queueHint: rendering.QueueHint = rendering.QueueHint.OPAQUE): rendering.BasicRenderPassBuilder {
  1124. const pass = ppl.addRenderPass(width, height, layout);
  1125. pass.addRenderTarget(colorName, loadOp, StoreOp.STORE, clearColor);
  1126. pass.addQueue(queueHint)
  1127. .addFullscreenQuad(material, passIndex);
  1128. return pass;
  1129. }
  1130. private _addMipmapFilterBloomPasses(
  1131. ppl: rendering.BasicPipeline,
  1132. pplConfigs: Readonly<PipelineConfigs>,
  1133. cameraConfigs: CameraConfigs & Readonly<BloomPassConfigs>,
  1134. settings: PipelineSettings,
  1135. bloomMaterial: Material,
  1136. id: number,
  1137. width: number,
  1138. height: number,
  1139. radianceName: string,
  1140. ): rendering.BasicRenderPassBuilder {
  1141. // Setup bloom parameters
  1142. this._bloomParams.x = pplConfigs.useFloatOutput ? 1 : 0;
  1143. this._bloomParams.x = 0; // unused
  1144. this._bloomParams.z = settings.bloom.threshold;
  1145. this._bloomParams.w = settings.bloom.intensity;
  1146. const prefilterInfo = this._prefilterTexDesc;
  1147. // Prefilter pass
  1148. let currSamplePass = this._addPass(
  1149. ppl,
  1150. prefilterInfo.width,
  1151. prefilterInfo.height,
  1152. 'cc-bloom-mipmap-prefilter',
  1153. prefilterInfo.name,
  1154. bloomMaterial,
  1155. 0,
  1156. );
  1157. currSamplePass.addTexture(radianceName, 'mainTexture');
  1158. currSamplePass.setVec4('bloomParams', this._bloomParams);
  1159. const downSampleInfos = this._bloomDownSampleTexDescs;
  1160. // Downsample passes
  1161. for (let i = 0; i < downSampleInfos.length; ++i) {
  1162. const currInfo = downSampleInfos[i];
  1163. const samplerSrc = i === 0 ? prefilterInfo : downSampleInfos[i - 1];
  1164. const samplerSrcName = samplerSrc.name;
  1165. this._bloomTexSize.x = 1 / samplerSrc.width;
  1166. this._bloomTexSize.y = 1 / samplerSrc.height;
  1167. currSamplePass = this._addPass(
  1168. ppl,
  1169. currInfo.width,
  1170. currInfo.height,
  1171. 'cc-bloom-mipmap-downsample',
  1172. currInfo.name,
  1173. bloomMaterial,
  1174. 1,
  1175. );
  1176. currSamplePass.addTexture(samplerSrcName, 'mainTexture');
  1177. currSamplePass.setVec4('bloomParams', this._bloomTexSize);
  1178. }
  1179. const lastIndex = downSampleInfos.length - 1;
  1180. const upSampleInfos = this._bloomUpSampleTexDescs;
  1181. // Upsample passes
  1182. for (let i = 0; i < upSampleInfos.length; i++) {
  1183. const currInfo = upSampleInfos[i];
  1184. const sampleSrc = i === 0 ? downSampleInfos[lastIndex] : upSampleInfos[i - 1];
  1185. const sampleSrcName = sampleSrc.name;
  1186. this._bloomTexSize.x = 1 / sampleSrc.width;
  1187. this._bloomTexSize.y = 1 / sampleSrc.height;
  1188. currSamplePass = this._addPass(
  1189. ppl,
  1190. currInfo.width,
  1191. currInfo.height,
  1192. 'cc-bloom-mipmap-upsample',
  1193. currInfo.name,
  1194. bloomMaterial,
  1195. 2,
  1196. );
  1197. currSamplePass.addTexture(sampleSrcName, 'mainTexture');
  1198. currSamplePass.addTexture(downSampleInfos[lastIndex - 1 - i].name, 'downsampleTexture');
  1199. currSamplePass.setVec4('bloomParams', this._bloomTexSize);
  1200. }
  1201. // Combine pass
  1202. const combinePass = this._addPass(
  1203. ppl,
  1204. width,
  1205. height,
  1206. 'cc-bloom-mipmap-combine',
  1207. radianceName,
  1208. bloomMaterial,
  1209. 3,
  1210. LoadOp.LOAD,
  1211. );
  1212. combinePass.addTexture(upSampleInfos[upSampleInfos.length - 1].name, 'bloomTexture');
  1213. combinePass.setVec4('bloomParams', this._bloomParams);
  1214. if (cameraConfigs.remainingPasses === 0) {
  1215. return addCopyToScreenPass(ppl, pplConfigs, cameraConfigs, radianceName);
  1216. } else {
  1217. return combinePass;
  1218. }
  1219. }
  1220. // Bloom
  1221. private readonly _clearColorTransparentBlack = new Color(0, 0, 0, 0);
  1222. private readonly _bloomParams = new Vec4(0, 0, 0, 0);
  1223. private readonly _bloomTexSize = new Vec4(0, 0, 0, 0);
  1224. private readonly _bloomWidths: Array<number> = [];
  1225. private readonly _bloomHeights: Array<number> = [];
  1226. private readonly _bloomTexNames: Array<string> = [];
  1227. // Mipmap Bloom
  1228. private readonly _bloomUpSampleTexDescs: Array<RenderTextureDesc> = [];
  1229. private readonly _bloomDownSampleTexDescs: Array<RenderTextureDesc> = [];
  1230. private _prefilterTexDesc: RenderTextureDesc = { name: '', width: 0, height: 0 };
  1231. private _originalColorDesc: RenderTextureDesc = { name: '', width: 0, height: 0 };
  1232. }
  1233. export interface ToneMappingPassConfigs {
  1234. enableToneMapping: boolean;
  1235. enableColorGrading: boolean;
  1236. }
  1237. export class BuiltinToneMappingPassBuilder implements rendering.PipelinePassBuilder {
  1238. getConfigOrder(): number {
  1239. return 0;
  1240. }
  1241. getRenderOrder(): number {
  1242. return 300;
  1243. }
  1244. configCamera(
  1245. camera: Readonly<renderer.scene.Camera>,
  1246. pplConfigs: Readonly<PipelineConfigs>,
  1247. cameraConfigs: CameraConfigs & ToneMappingPassConfigs): void {
  1248. const settings = cameraConfigs.settings;
  1249. cameraConfigs.enableColorGrading
  1250. = settings.colorGrading.enabled
  1251. && !!settings.colorGrading.material
  1252. && !!settings.colorGrading.colorGradingMap;
  1253. cameraConfigs.enableToneMapping
  1254. = cameraConfigs.enableHDR // From Half to RGBA8
  1255. || cameraConfigs.enableColorGrading; // Color grading
  1256. if (cameraConfigs.enableToneMapping) {
  1257. ++cameraConfigs.remainingPasses;
  1258. }
  1259. }
  1260. windowResize(
  1261. ppl: rendering.BasicPipeline,
  1262. pplConfigs: Readonly<PipelineConfigs>,
  1263. cameraConfigs: CameraConfigs & ToneMappingPassConfigs): void {
  1264. if (cameraConfigs.enableColorGrading) {
  1265. assert(!!cameraConfigs.settings.colorGrading.material);
  1266. cameraConfigs.settings.colorGrading.material.setProperty(
  1267. 'colorGradingMap',
  1268. cameraConfigs.settings.colorGrading.colorGradingMap);
  1269. }
  1270. }
  1271. setup(
  1272. ppl: rendering.BasicPipeline,
  1273. pplConfigs: Readonly<PipelineConfigs>,
  1274. cameraConfigs: CameraConfigs & ToneMappingPassConfigs,
  1275. camera: renderer.scene.Camera,
  1276. context: PipelineContext,
  1277. prevRenderPass?: rendering.BasicRenderPassBuilder)
  1278. : rendering.BasicRenderPassBuilder | undefined {
  1279. if (!cameraConfigs.enableToneMapping) {
  1280. return prevRenderPass;
  1281. }
  1282. --cameraConfigs.remainingPasses;
  1283. assert(cameraConfigs.remainingPasses >= 0);
  1284. if (cameraConfigs.remainingPasses === 0) {
  1285. return this._addCopyAndTonemapPass(ppl, pplConfigs, cameraConfigs,
  1286. cameraConfigs.width, cameraConfigs.height,
  1287. context.colorName, cameraConfigs.colorName);
  1288. } else {
  1289. const id = cameraConfigs.renderWindowId;
  1290. const ldrColorPrefix = cameraConfigs.enableShadingScale
  1291. ? `ScaledLdrColor`
  1292. : `LdrColor`;
  1293. const ldrColorName = getPingPongRenderTarget(context.colorName, ldrColorPrefix, id);
  1294. const radianceName = context.colorName;
  1295. context.colorName = ldrColorName;
  1296. return this._addCopyAndTonemapPass(ppl, pplConfigs, cameraConfigs,
  1297. cameraConfigs.width, cameraConfigs.height,
  1298. radianceName, ldrColorName);
  1299. }
  1300. }
  1301. private _addCopyAndTonemapPass(
  1302. ppl: rendering.BasicPipeline,
  1303. pplConfigs: Readonly<PipelineConfigs>,
  1304. cameraConfigs: CameraConfigs & ToneMappingPassConfigs,
  1305. width: number,
  1306. height: number,
  1307. radianceName: string,
  1308. colorName: string,
  1309. ): rendering.BasicRenderPassBuilder {
  1310. let pass: rendering.BasicRenderPassBuilder;
  1311. const settings = cameraConfigs.settings;
  1312. if (cameraConfigs.enableColorGrading) {
  1313. assert(!!settings.colorGrading.material);
  1314. assert(!!settings.colorGrading.colorGradingMap);
  1315. const lutTex = settings.colorGrading.colorGradingMap;
  1316. this._colorGradingTexSize.x = lutTex.width;
  1317. this._colorGradingTexSize.y = lutTex.height;
  1318. const isSquareMap = lutTex.width === lutTex.height;
  1319. if (isSquareMap) {
  1320. pass = ppl.addRenderPass(width, height, 'cc-color-grading-8x8');
  1321. } else {
  1322. pass = ppl.addRenderPass(width, height, 'cc-color-grading-nx1');
  1323. }
  1324. pass.addRenderTarget(colorName, LoadOp.CLEAR, StoreOp.STORE, sClearColorTransparentBlack);
  1325. pass.addTexture(radianceName, 'sceneColorMap');
  1326. pass.setVec2('lutTextureSize', this._colorGradingTexSize);
  1327. pass.setFloat('contribute', settings.colorGrading.contribute);
  1328. pass.addQueue(rendering.QueueHint.OPAQUE)
  1329. .addFullscreenQuad(settings.colorGrading.material, isSquareMap ? 1 : 0);
  1330. } else {
  1331. pass = ppl.addRenderPass(width, height, 'cc-tone-mapping');
  1332. pass.addRenderTarget(colorName, LoadOp.CLEAR, StoreOp.STORE, sClearColorTransparentBlack);
  1333. pass.addTexture(radianceName, 'inputTexture');
  1334. if (settings.toneMapping.material) {
  1335. pass.addQueue(rendering.QueueHint.OPAQUE)
  1336. .addFullscreenQuad(settings.toneMapping.material, 0);
  1337. } else {
  1338. assert(!!cameraConfigs.copyAndTonemapMaterial);
  1339. pass.addQueue(rendering.QueueHint.OPAQUE)
  1340. .addFullscreenQuad(cameraConfigs.copyAndTonemapMaterial, 0);
  1341. }
  1342. }
  1343. return pass;
  1344. }
  1345. private readonly _colorGradingTexSize = new Vec2(0, 0);
  1346. }
  1347. export interface FXAAPassConfigs {
  1348. enableFXAA: boolean;
  1349. }
  1350. export class BuiltinFXAAPassBuilder implements rendering.PipelinePassBuilder {
  1351. getConfigOrder(): number {
  1352. return 0;
  1353. }
  1354. getRenderOrder(): number {
  1355. return 400;
  1356. }
  1357. configCamera(
  1358. camera: Readonly<renderer.scene.Camera>,
  1359. pplConfigs: Readonly<PipelineConfigs>,
  1360. cameraConfigs: CameraConfigs & FXAAPassConfigs): void {
  1361. cameraConfigs.enableFXAA
  1362. = cameraConfigs.settings.fxaa.enabled
  1363. && !!cameraConfigs.settings.fxaa.material;
  1364. if (cameraConfigs.enableFXAA) {
  1365. ++cameraConfigs.remainingPasses;
  1366. }
  1367. }
  1368. setup(
  1369. ppl: rendering.BasicPipeline,
  1370. pplConfigs: Readonly<PipelineConfigs>,
  1371. cameraConfigs: CameraConfigs & FXAAPassConfigs,
  1372. camera: renderer.scene.Camera,
  1373. context: PipelineContext,
  1374. prevRenderPass?: rendering.BasicRenderPassBuilder)
  1375. : rendering.BasicRenderPassBuilder | undefined {
  1376. if (!cameraConfigs.enableFXAA) {
  1377. return prevRenderPass;
  1378. }
  1379. --cameraConfigs.remainingPasses;
  1380. assert(cameraConfigs.remainingPasses >= 0);
  1381. const id = cameraConfigs.renderWindowId;
  1382. const ldrColorPrefix = cameraConfigs.enableShadingScale
  1383. ? `ScaledLdrColor`
  1384. : `LdrColor`;
  1385. const ldrColorName = getPingPongRenderTarget(context.colorName, ldrColorPrefix, id);
  1386. assert(!!cameraConfigs.settings.fxaa.material);
  1387. if (cameraConfigs.remainingPasses === 0) {
  1388. if (cameraConfigs.enableShadingScale) {
  1389. this._addFxaaPass(ppl, pplConfigs,
  1390. cameraConfigs.settings.fxaa.material,
  1391. cameraConfigs.width,
  1392. cameraConfigs.height,
  1393. context.colorName,
  1394. ldrColorName);
  1395. return addCopyToScreenPass(ppl, pplConfigs, cameraConfigs, ldrColorName);
  1396. } else {
  1397. assert(cameraConfigs.width === cameraConfigs.nativeWidth);
  1398. assert(cameraConfigs.height === cameraConfigs.nativeHeight);
  1399. return this._addFxaaPass(ppl, pplConfigs,
  1400. cameraConfigs.settings.fxaa.material,
  1401. cameraConfigs.width,
  1402. cameraConfigs.height,
  1403. context.colorName,
  1404. cameraConfigs.colorName);
  1405. }
  1406. } else {
  1407. const inputColorName = context.colorName;
  1408. context.colorName = ldrColorName;
  1409. const lastPass = this._addFxaaPass(ppl, pplConfigs,
  1410. cameraConfigs.settings.fxaa.material,
  1411. cameraConfigs.width,
  1412. cameraConfigs.height,
  1413. inputColorName,
  1414. ldrColorName);
  1415. return lastPass;
  1416. }
  1417. }
  1418. private _addFxaaPass(
  1419. ppl: rendering.BasicPipeline,
  1420. pplConfigs: Readonly<PipelineConfigs>,
  1421. fxaaMaterial: Material,
  1422. width: number,
  1423. height: number,
  1424. ldrColorName: string,
  1425. colorName: string,
  1426. ): rendering.BasicRenderPassBuilder {
  1427. this._fxaaParams.x = width;
  1428. this._fxaaParams.y = height;
  1429. this._fxaaParams.z = 1 / width;
  1430. this._fxaaParams.w = 1 / height;
  1431. const pass = ppl.addRenderPass(width, height, 'cc-fxaa');
  1432. pass.addRenderTarget(colorName, LoadOp.CLEAR, StoreOp.STORE, sClearColorTransparentBlack);
  1433. pass.addTexture(ldrColorName, 'sceneColorMap');
  1434. pass.setVec4('texSize', this._fxaaParams);
  1435. pass.addQueue(rendering.QueueHint.OPAQUE)
  1436. .addFullscreenQuad(fxaaMaterial, 0);
  1437. return pass;
  1438. }
  1439. // FXAA
  1440. private readonly _fxaaParams = new Vec4(0, 0, 0, 0);
  1441. }
  1442. export interface FSRPassConfigs {
  1443. enableFSR: boolean;
  1444. }
  1445. export class BuiltinFsrPassBuilder implements rendering.PipelinePassBuilder {
  1446. getConfigOrder(): number {
  1447. return 0;
  1448. }
  1449. getRenderOrder(): number {
  1450. return 500;
  1451. }
  1452. configCamera(
  1453. camera: Readonly<renderer.scene.Camera>,
  1454. pplConfigs: Readonly<PipelineConfigs>,
  1455. cameraConfigs: CameraConfigs & FSRPassConfigs): void {
  1456. // FSR (Depend on Shading scale)
  1457. cameraConfigs.enableFSR = cameraConfigs.settings.fsr.enabled
  1458. && !!cameraConfigs.settings.fsr.material
  1459. && cameraConfigs.enableShadingScale
  1460. && cameraConfigs.shadingScale < 1.0;
  1461. if (cameraConfigs.enableFSR) {
  1462. ++cameraConfigs.remainingPasses;
  1463. }
  1464. }
  1465. setup(
  1466. ppl: rendering.BasicPipeline,
  1467. pplConfigs: Readonly<PipelineConfigs>,
  1468. cameraConfigs: CameraConfigs & FSRPassConfigs,
  1469. camera: renderer.scene.Camera,
  1470. context: PipelineContext,
  1471. prevRenderPass?: rendering.BasicRenderPassBuilder)
  1472. : rendering.BasicRenderPassBuilder | undefined {
  1473. if (!cameraConfigs.enableFSR) {
  1474. return prevRenderPass;
  1475. }
  1476. --cameraConfigs.remainingPasses;
  1477. const inputColorName = context.colorName;
  1478. const outputColorName
  1479. = cameraConfigs.remainingPasses === 0
  1480. ? cameraConfigs.colorName
  1481. : getPingPongRenderTarget(context.colorName, 'UiColor', cameraConfigs.renderWindowId);
  1482. context.colorName = outputColorName;
  1483. assert(!!cameraConfigs.settings.fsr.material);
  1484. return this._addFsrPass(ppl, pplConfigs, cameraConfigs,
  1485. cameraConfigs.settings,
  1486. cameraConfigs.settings.fsr.material,
  1487. cameraConfigs.renderWindowId,
  1488. cameraConfigs.width,
  1489. cameraConfigs.height,
  1490. inputColorName,
  1491. cameraConfigs.nativeWidth,
  1492. cameraConfigs.nativeHeight,
  1493. outputColorName);
  1494. }
  1495. private _addFsrPass(
  1496. ppl: rendering.BasicPipeline,
  1497. pplConfigs: Readonly<PipelineConfigs>,
  1498. cameraConfigs: CameraConfigs & FSRPassConfigs,
  1499. settings: PipelineSettings,
  1500. fsrMaterial: Material,
  1501. id: number,
  1502. width: number,
  1503. height: number,
  1504. inputColorName: string,
  1505. nativeWidth: number,
  1506. nativeHeight: number,
  1507. outputColorName: string,
  1508. ): rendering.BasicRenderPassBuilder {
  1509. this._fsrTexSize.x = width;
  1510. this._fsrTexSize.y = height;
  1511. this._fsrTexSize.z = nativeWidth;
  1512. this._fsrTexSize.w = nativeHeight;
  1513. this._fsrParams.x = clamp(1.0 - settings.fsr.sharpness, 0.02, 0.98);
  1514. const uiColorPrefix = 'UiColor';
  1515. const fsrColorName = getPingPongRenderTarget(outputColorName, uiColorPrefix, id);
  1516. const easuPass = ppl.addRenderPass(nativeWidth, nativeHeight, 'cc-fsr-easu');
  1517. easuPass.addRenderTarget(fsrColorName, LoadOp.CLEAR, StoreOp.STORE, sClearColorTransparentBlack);
  1518. easuPass.addTexture(inputColorName, 'outputResultMap');
  1519. easuPass.setVec4('fsrTexSize', this._fsrTexSize);
  1520. easuPass
  1521. .addQueue(rendering.QueueHint.OPAQUE)
  1522. .addFullscreenQuad(fsrMaterial, 0);
  1523. const rcasPass = ppl.addRenderPass(nativeWidth, nativeHeight, 'cc-fsr-rcas');
  1524. rcasPass.addRenderTarget(outputColorName, LoadOp.CLEAR, StoreOp.STORE, sClearColorTransparentBlack);
  1525. rcasPass.addTexture(fsrColorName, 'outputResultMap');
  1526. rcasPass.setVec4('fsrTexSize', this._fsrTexSize);
  1527. rcasPass.setVec4('fsrParams', this._fsrParams);
  1528. rcasPass
  1529. .addQueue(rendering.QueueHint.OPAQUE)
  1530. .addFullscreenQuad(fsrMaterial, 1);
  1531. return rcasPass;
  1532. }
  1533. // FSR
  1534. private readonly _fsrParams = new Vec4(0, 0, 0, 0);
  1535. private readonly _fsrTexSize = new Vec4(0, 0, 0, 0);
  1536. }
  1537. export class BuiltinUiPassBuilder implements rendering.PipelinePassBuilder {
  1538. getConfigOrder(): number {
  1539. return 0;
  1540. }
  1541. getRenderOrder(): number {
  1542. return 1000;
  1543. }
  1544. setup(
  1545. ppl: rendering.BasicPipeline,
  1546. pplConfigs: Readonly<PipelineConfigs>,
  1547. cameraConfigs: CameraConfigs & FSRPassConfigs,
  1548. camera: renderer.scene.Camera,
  1549. context: PipelineContext,
  1550. prevRenderPass?: rendering.BasicRenderPassBuilder)
  1551. : rendering.BasicRenderPassBuilder | undefined {
  1552. assert(!!prevRenderPass);
  1553. let flags = rendering.SceneFlags.UI;
  1554. if (cameraConfigs.enableProfiler) {
  1555. flags |= rendering.SceneFlags.PROFILER;
  1556. prevRenderPass.showStatistics = true;
  1557. }
  1558. prevRenderPass
  1559. .addQueue(rendering.QueueHint.BLEND, 'default', 'default')
  1560. .addScene(camera, flags);
  1561. return prevRenderPass;
  1562. }
  1563. }
  1564. if (rendering) {
  1565. const { QueueHint, SceneFlags } = rendering;
  1566. class BuiltinPipelineBuilder implements rendering.PipelineBuilder {
  1567. private readonly _pipelineEvent: PipelineEventProcessor = cclegacy.director.root.pipelineEvent as PipelineEventProcessor;
  1568. private readonly _forwardPass = new BuiltinForwardPassBuilder();
  1569. private readonly _bloomPass = new BuiltinBloomPassBuilder();
  1570. private readonly _toneMappingPass = new BuiltinToneMappingPassBuilder();
  1571. private readonly _fxaaPass = new BuiltinFXAAPassBuilder();
  1572. private readonly _fsrPass = new BuiltinFsrPassBuilder();
  1573. private readonly _uiPass = new BuiltinUiPassBuilder();
  1574. // Internal cached resources
  1575. private readonly _clearColor = new Color(0, 0, 0, 1);
  1576. private readonly _viewport = new Viewport();
  1577. private readonly _configs = new PipelineConfigs();
  1578. private readonly _cameraConfigs = new CameraConfigs();
  1579. // Materials
  1580. private readonly _copyAndTonemapMaterial = new Material();
  1581. // Internal States
  1582. private _initialized = false; // TODO(zhouzhenglong): Make default effect asset loading earlier and remove this flag
  1583. private _passBuilders: rendering.PipelinePassBuilder[] = [];
  1584. private _setupPipelinePreview(
  1585. camera: renderer.scene.Camera,
  1586. cameraConfigs: CameraConfigs) {
  1587. const isEditorView: boolean
  1588. = camera.cameraUsage === CameraUsage.SCENE_VIEW
  1589. || camera.cameraUsage === CameraUsage.PREVIEW;
  1590. if (isEditorView) {
  1591. const editorSettings = rendering.getEditorPipelineSettings() as PipelineSettings | null;
  1592. if (editorSettings) {
  1593. cameraConfigs.settings = editorSettings;
  1594. } else {
  1595. cameraConfigs.settings = defaultSettings;
  1596. }
  1597. } else {
  1598. if (camera.pipelineSettings) {
  1599. cameraConfigs.settings = camera.pipelineSettings as PipelineSettings;
  1600. } else {
  1601. cameraConfigs.settings = defaultSettings;
  1602. }
  1603. }
  1604. }
  1605. private _preparePipelinePasses(cameraConfigs: CameraConfigs): void {
  1606. const passBuilders = this._passBuilders;
  1607. passBuilders.length = 0;
  1608. const settings = cameraConfigs.settings as PipelineSettings2;
  1609. if (settings._passes) {
  1610. for (const pass of settings._passes) {
  1611. passBuilders.push(pass);
  1612. }
  1613. assert(passBuilders.length === settings._passes.length);
  1614. }
  1615. passBuilders.push(this._forwardPass);
  1616. if (settings.bloom.enabled) {
  1617. passBuilders.push(this._bloomPass);
  1618. }
  1619. passBuilders.push(this._toneMappingPass);
  1620. if (settings.fxaa.enabled) {
  1621. passBuilders.push(this._fxaaPass);
  1622. }
  1623. if (settings.fsr.enabled) {
  1624. passBuilders.push(this._fsrPass);
  1625. }
  1626. passBuilders.push(this._uiPass);
  1627. }
  1628. private _setupBuiltinCameraConfigs(
  1629. ppl: rendering.BasicPipeline,
  1630. camera: renderer.scene.Camera,
  1631. pipelineConfigs: PipelineConfigs,
  1632. cameraConfigs: CameraConfigs
  1633. ) {
  1634. const window = camera.window;
  1635. const isMainGameWindow: boolean = camera.cameraUsage === CameraUsage.GAME && !!window.swapchain;
  1636. const isGameView = isMainGameWindow || camera.cameraUsage === CameraUsage.GAME_VIEW;
  1637. // Window
  1638. cameraConfigs.isMainGameWindow = isMainGameWindow;
  1639. cameraConfigs.renderWindowId = window.renderWindowId;
  1640. // Camera
  1641. cameraConfigs.colorName = window.colorName;
  1642. cameraConfigs.depthStencilName = window.depthStencilName;
  1643. // Pipeline
  1644. cameraConfigs.enableFullPipeline = (camera.visibility & (Layers.Enum.DEFAULT)) !== 0;
  1645. cameraConfigs.enableProfiler = ppl.profiler && isGameView;
  1646. cameraConfigs.remainingPasses = 0;
  1647. // Shading scale
  1648. cameraConfigs.shadingScale = cameraConfigs.settings.shadingScale;
  1649. cameraConfigs.enableShadingScale = cameraConfigs.settings.enableShadingScale
  1650. && cameraConfigs.shadingScale !== 1.0;
  1651. cameraConfigs.nativeWidth = Math.max(Math.floor(window.width), 1);
  1652. cameraConfigs.nativeHeight = Math.max(Math.floor(window.height), 1);
  1653. cameraConfigs.width = cameraConfigs.enableShadingScale
  1654. ? Math.max(Math.floor(cameraConfigs.nativeWidth * cameraConfigs.shadingScale), 1)
  1655. : cameraConfigs.nativeWidth;
  1656. cameraConfigs.height = cameraConfigs.enableShadingScale
  1657. ? Math.max(Math.floor(cameraConfigs.nativeHeight * cameraConfigs.shadingScale), 1)
  1658. : cameraConfigs.nativeHeight;
  1659. // Radiance
  1660. cameraConfigs.enableHDR = cameraConfigs.enableFullPipeline
  1661. && pipelineConfigs.useFloatOutput;
  1662. cameraConfigs.radianceFormat = cameraConfigs.enableHDR
  1663. ? gfx.Format.RGBA16F : gfx.Format.RGBA8;
  1664. // Tone Mapping
  1665. cameraConfigs.copyAndTonemapMaterial = this._copyAndTonemapMaterial;
  1666. // Depth
  1667. cameraConfigs.enableStoreSceneDepth = false;
  1668. }
  1669. private _setupCameraConfigs(
  1670. ppl: rendering.BasicPipeline,
  1671. camera: renderer.scene.Camera,
  1672. pipelineConfigs: PipelineConfigs,
  1673. cameraConfigs: CameraConfigs
  1674. ): void {
  1675. this._setupPipelinePreview(camera, cameraConfigs);
  1676. this._preparePipelinePasses(cameraConfigs);
  1677. sortPipelinePassBuildersByConfigOrder(this._passBuilders);
  1678. this._setupBuiltinCameraConfigs(ppl, camera, pipelineConfigs, cameraConfigs);
  1679. for (const builder of this._passBuilders) {
  1680. if (builder.configCamera) {
  1681. builder.configCamera(camera, pipelineConfigs, cameraConfigs);
  1682. }
  1683. }
  1684. }
  1685. // ----------------------------------------------------------------
  1686. // Interface
  1687. // ----------------------------------------------------------------
  1688. windowResize(
  1689. ppl: rendering.BasicPipeline,
  1690. window: renderer.RenderWindow,
  1691. camera: renderer.scene.Camera,
  1692. nativeWidth: number,
  1693. nativeHeight: number,
  1694. ): void {
  1695. setupPipelineConfigs(ppl, this._configs);
  1696. this._setupCameraConfigs(ppl, camera, this._configs, this._cameraConfigs);
  1697. // Render Window (UI)
  1698. const id = window.renderWindowId;
  1699. ppl.addRenderWindow(this._cameraConfigs.colorName,
  1700. Format.RGBA8, nativeWidth, nativeHeight, window,
  1701. this._cameraConfigs.depthStencilName);
  1702. const width = this._cameraConfigs.width;
  1703. const height = this._cameraConfigs.height;
  1704. if (this._cameraConfigs.enableShadingScale) {
  1705. ppl.addDepthStencil(`ScaledSceneDepth_${id}`, Format.DEPTH_STENCIL, width, height);
  1706. ppl.addRenderTarget(`ScaledRadiance0_${id}`, this._cameraConfigs.radianceFormat, width, height);
  1707. ppl.addRenderTarget(`ScaledRadiance1_${id}`, this._cameraConfigs.radianceFormat, width, height);
  1708. ppl.addRenderTarget(`ScaledLdrColor0_${id}`, Format.RGBA8, width, height);
  1709. ppl.addRenderTarget(`ScaledLdrColor1_${id}`, Format.RGBA8, width, height);
  1710. } else {
  1711. ppl.addDepthStencil(`SceneDepth_${id}`, Format.DEPTH_STENCIL, width, height);
  1712. ppl.addRenderTarget(`Radiance0_${id}`, this._cameraConfigs.radianceFormat, width, height);
  1713. ppl.addRenderTarget(`Radiance1_${id}`, this._cameraConfigs.radianceFormat, width, height);
  1714. ppl.addRenderTarget(`LdrColor0_${id}`, Format.RGBA8, width, height);
  1715. ppl.addRenderTarget(`LdrColor1_${id}`, Format.RGBA8, width, height);
  1716. }
  1717. ppl.addRenderTarget(`UiColor0_${id}`, Format.RGBA8, nativeWidth, nativeHeight);
  1718. ppl.addRenderTarget(`UiColor1_${id}`, Format.RGBA8, nativeWidth, nativeHeight);
  1719. for (const builder of this._passBuilders) {
  1720. if (builder.windowResize) {
  1721. builder.windowResize(ppl, this._configs, this._cameraConfigs, window, camera, nativeWidth, nativeHeight);
  1722. }
  1723. }
  1724. }
  1725. setup(cameras: renderer.scene.Camera[], ppl: rendering.BasicPipeline): void {
  1726. // TODO(zhouzhenglong): Make default effect asset loading earlier and remove _initMaterials
  1727. if (this._initMaterials(ppl)) {
  1728. return;
  1729. }
  1730. // Render cameras
  1731. // log(`==================== One Frame ====================`);
  1732. for (const camera of cameras) {
  1733. // Skip invalid camera
  1734. if (!camera.scene || !camera.window) {
  1735. continue;
  1736. }
  1737. // Setup camera configs
  1738. this._setupCameraConfigs(ppl, camera, this._configs, this._cameraConfigs);
  1739. // log(`Setup camera: ${camera.node!.name}, window: ${camera.window.renderWindowId}, isFull: ${this._cameraConfigs.enableFullPipeline}, `
  1740. // + `size: ${camera.window.width}x${camera.window.height}`);
  1741. this._pipelineEvent.emit(PipelineEventType.RENDER_CAMERA_BEGIN, camera);
  1742. // Build pipeline
  1743. if (this._cameraConfigs.enableFullPipeline) {
  1744. this._buildForwardPipeline(ppl, camera, camera.scene, this._passBuilders);
  1745. } else {
  1746. this._buildSimplePipeline(ppl, camera);
  1747. }
  1748. this._pipelineEvent.emit(PipelineEventType.RENDER_CAMERA_END, camera);
  1749. }
  1750. }
  1751. // ----------------------------------------------------------------
  1752. // Pipelines
  1753. // ----------------------------------------------------------------
  1754. private _buildSimplePipeline(
  1755. ppl: rendering.BasicPipeline,
  1756. camera: renderer.scene.Camera,
  1757. ): void {
  1758. const width = Math.max(Math.floor(camera.window.width), 1);
  1759. const height = Math.max(Math.floor(camera.window.height), 1);
  1760. const colorName = this._cameraConfigs.colorName;
  1761. const depthStencilName = this._cameraConfigs.depthStencilName;
  1762. const viewport = camera.viewport; // Reduce C++/TS interop
  1763. this._viewport.left = Math.round(viewport.x * width);
  1764. this._viewport.top = Math.round(viewport.y * height);
  1765. // Here we must use camera.viewport.width instead of camera.viewport.z, which
  1766. // is undefined on native platform. The same as camera.viewport.height.
  1767. this._viewport.width = Math.max(Math.round(viewport.width * width), 1);
  1768. this._viewport.height = Math.max(Math.round(viewport.height * height), 1);
  1769. const clearColor = camera.clearColor; // Reduce C++/TS interop
  1770. this._clearColor.x = clearColor.x;
  1771. this._clearColor.y = clearColor.y;
  1772. this._clearColor.z = clearColor.z;
  1773. this._clearColor.w = clearColor.w;
  1774. const pass = ppl.addRenderPass(width, height, 'default');
  1775. // bind output render target
  1776. if (forwardNeedClearColor(camera)) {
  1777. pass.addRenderTarget(colorName, LoadOp.CLEAR, StoreOp.STORE, this._clearColor);
  1778. } else {
  1779. pass.addRenderTarget(colorName, LoadOp.LOAD, StoreOp.STORE);
  1780. }
  1781. // bind depth stencil buffer
  1782. if (camera.clearFlag & ClearFlagBit.DEPTH_STENCIL) {
  1783. pass.addDepthStencil(
  1784. depthStencilName,
  1785. LoadOp.CLEAR,
  1786. StoreOp.DISCARD,
  1787. camera.clearDepth,
  1788. camera.clearStencil,
  1789. camera.clearFlag & ClearFlagBit.DEPTH_STENCIL,
  1790. );
  1791. } else {
  1792. pass.addDepthStencil(depthStencilName, LoadOp.LOAD, StoreOp.DISCARD);
  1793. }
  1794. pass.setViewport(this._viewport);
  1795. // The opaque queue is used for Reflection probe preview
  1796. pass.addQueue(QueueHint.OPAQUE)
  1797. .addScene(camera, SceneFlags.OPAQUE);
  1798. // The blend queue is used for UI and Gizmos
  1799. let flags = SceneFlags.BLEND | SceneFlags.UI;
  1800. if (this._cameraConfigs.enableProfiler) {
  1801. flags |= SceneFlags.PROFILER;
  1802. pass.showStatistics = true;
  1803. }
  1804. pass.addQueue(QueueHint.BLEND)
  1805. .addScene(camera, flags);
  1806. }
  1807. private _buildForwardPipeline(
  1808. ppl: rendering.BasicPipeline,
  1809. camera: renderer.scene.Camera,
  1810. scene: renderer.RenderScene,
  1811. passBuilders: rendering.PipelinePassBuilder[],
  1812. ): void {
  1813. sortPipelinePassBuildersByRenderOrder(passBuilders);
  1814. const context: PipelineContext = {
  1815. colorName: '',
  1816. depthStencilName: '',
  1817. };
  1818. let lastPass: rendering.BasicRenderPassBuilder | undefined = undefined;
  1819. for (const builder of passBuilders) {
  1820. if (builder.setup) {
  1821. lastPass = builder.setup(ppl, this._configs, this._cameraConfigs,
  1822. camera, context, lastPass);
  1823. }
  1824. }
  1825. assert(this._cameraConfigs.remainingPasses === 0);
  1826. }
  1827. private _initMaterials(ppl: rendering.BasicPipeline): number {
  1828. if (this._initialized) {
  1829. return 0;
  1830. }
  1831. setupPipelineConfigs(ppl, this._configs);
  1832. // When add new effect asset, please add its uuid to the dependentAssets in cc.config.json.
  1833. this._copyAndTonemapMaterial._uuid = `builtin-pipeline-tone-mapping-material`;
  1834. this._copyAndTonemapMaterial.initialize({ effectName: 'pipeline/post-process/tone-mapping' });
  1835. if (this._copyAndTonemapMaterial.effectAsset) {
  1836. this._initialized = true;
  1837. }
  1838. return this._initialized ? 0 : 1;
  1839. }
  1840. }
  1841. rendering.setCustomPipeline('Builtin', new BuiltinPipelineBuilder());
  1842. } // if (rendering)