From ea6270a35fe943559b40d704e9107cd96cd8a9b5 Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Wed, 28 Oct 2026 12:29:52 +0000 Subject: [PATCH 1/8] docs: add the Frame Graph section on frame, render and compute passes Explains physical render and compute passes and the frame graph, and documents the engine's frame passes: the pass classes and their lifecycle, camera passes, RenderPass, RenderPassShaderQuad, RenderPassForward and ComputePass. The CameraFrame custom render passes page is folded into it, with a redirect to Camera Passes. --- .../frame-graph/camera-passes.md | 95 ++++++++++ .../frame-graph/compute-pass.md | 99 ++++++++++ .../frame-graph/frame-passes.md | 114 ++++++++++++ .../advanced-rendering/frame-graph/index.md | 70 ++++++++ .../frame-graph/render-pass-forward.md | 56 ++++++ .../frame-graph/render-pass-shader-quad.md | 169 ++++++++++++++++++ .../frame-graph/render-pass.md | 141 +++++++++++++++ .../posteffects/cameraframe/custom-passes.md | 153 ---------------- .../graphics/posteffects/cameraframe/index.md | 2 +- docusaurus.config.js | 2 + .../posteffects/cameraframe/custom-passes.md | 153 ---------------- .../graphics/posteffects/cameraframe/index.md | 2 +- llms/user-manual/engine/llms.txt | 1 + sidebars.js | 26 ++- 14 files changed, 774 insertions(+), 309 deletions(-) create mode 100644 docs/user-manual/graphics/advanced-rendering/frame-graph/camera-passes.md create mode 100644 docs/user-manual/graphics/advanced-rendering/frame-graph/compute-pass.md create mode 100644 docs/user-manual/graphics/advanced-rendering/frame-graph/frame-passes.md create mode 100644 docs/user-manual/graphics/advanced-rendering/frame-graph/index.md create mode 100644 docs/user-manual/graphics/advanced-rendering/frame-graph/render-pass-forward.md create mode 100644 docs/user-manual/graphics/advanced-rendering/frame-graph/render-pass-shader-quad.md create mode 100644 docs/user-manual/graphics/advanced-rendering/frame-graph/render-pass.md delete mode 100644 docs/user-manual/graphics/posteffects/cameraframe/custom-passes.md delete mode 100644 i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/custom-passes.md diff --git a/docs/user-manual/graphics/advanced-rendering/frame-graph/camera-passes.md b/docs/user-manual/graphics/advanced-rendering/frame-graph/camera-passes.md new file mode 100644 index 00000000000..3c5a9dd3777 --- /dev/null +++ b/docs/user-manual/graphics/advanced-rendering/frame-graph/camera-passes.md @@ -0,0 +1,95 @@ +--- +title: Camera Passes +description: "Adding frame passes to a camera: passes rendering before and after it, replacing its rendering with your own passes, and who owns them." +--- + +Each camera renders its part of the [frame graph](index.md) as a group of [frame passes](frame-passes.md). By default, these are passes the engine creates to render the layers of the camera. You can add passes of your own before and after them, or replace them with your own passes. + +## The passes of a camera + +Each camera renders its passes in three groups, in this order, and all three finish before the next camera starts: + +```mermaid +flowchart LR + CameraBefore[camera.beforePasses] --> Rendering[the camera's rendering:
its default passes, or camera.framePasses] --> CameraAfter[camera.afterPasses] +``` + +- [`beforePasses`](#before-passes) render before the camera renders its scene. +- The camera's rendering is either its default rendering, or the passes in [`framePasses`](#replacing-the-cameras-rendering). +- [`afterPasses`](#after-passes) render after everything the camera renders. + +## Before passes + +The passes in [`CameraComponent#beforePasses`](https://api.playcanvas.com/engine/classes/CameraComponent.html#beforepasses) render after the directional shadow maps of the camera, and before it renders its scene. Use them to render into textures the materials of the scene read, or to run compute shaders whose results the scene uses, in the same frame. + +They cannot include a pass rendering the layers of a camera, such as a [`RenderPassForward`](render-pass-forward.md#limitations). When the frame renders multiple XR views, they render for each view, apart from a compute pass, which executes in the view its [`xrViewIndex`](compute-pass.md#xr-views) selects. + +## After passes + +The passes in [`CameraComponent#afterPasses`](https://api.playcanvas.com/engine/classes/CameraComponent.html#afterpasses) render after everything the camera renders in the frame, its frame passes included. Use them to process what the camera rendered in the same frame, for example with a [compute pass](compute-pass.md) reading its color map. When the frame renders multiple XR views, they render once, after all the views. + +## Replacing the camera's rendering + +The passes in [`CameraComponent#framePasses`](https://api.playcanvas.com/engine/classes/CameraComponent.html#framepasses) render instead of the camera's default rendering, in the order of the array. With them, you decide how the camera renders: which of its layers render into which render targets, and which passes process the result. Usually, one of them is a [`RenderPassForward`](render-pass-forward.md), which renders the layers of the scene with the camera. + +Here, a forward pass renders the scene into a texture, and a [shader quad pass](render-pass-shader-quad.md) draws it to the screen, tinted: + +```javascript +// a texture the size of the back buffer, which the scene renders into +const texture = new Texture(app.graphicsDevice, { + name: 'SceneTexture', + width: 4, + height: 4, + format: PIXELFORMAT_RGBA8, + mipmaps: false, + minFilter: FILTER_LINEAR, + magFilter: FILTER_LINEAR, + addressU: ADDRESS_CLAMP_TO_EDGE, + addressV: ADDRESS_CLAMP_TO_EDGE +}); +const renderTarget = new RenderTarget({ colorBuffer: texture, depth: true }); + +// render the world and the UI layers into the texture, resized to the back buffer each frame +const scenePass = new RenderPassForward(app.graphicsDevice, app.scene, cameraEntity.camera); +scenePass.name = 'ScenePass'; +scenePass.init(renderTarget, { resizeSource: null }); +scenePass.addLayer(worldLayer, false); +scenePass.addLayer(uiLayer, true); + +// draw the texture to the screen, tinted +const tintPass = new TintPass(app.graphicsDevice, texture); +tintPass.init(null); + +cameraEntity.camera.framePasses = [scenePass, tintPass]; +``` + +To return to the camera's default rendering, set `framePasses` to `null`. + +The before and after passes of the camera still render around its frame passes. The before passes render before the first of the frame passes rendering the layers of the camera, and do not render when none of them does. The after passes render after all of them. + +A camera can also render its layers in several forward passes, for example its opaque layers, then a pass of your own reading what they rendered, and then its transparent layers. + +## Adding and removing passes + +Add passes to the arrays, and remove them to stop rendering them. The camera does not destroy its passes, so destroy a pass once it is removed, and then what it renders into: + +```javascript +const pass = new MyPass(app.graphicsDevice); +pass.name = 'MyPass'; +cameraEntity.camera.afterPasses.push(pass); + +// when no longer needed, remove the pass and destroy it +const passes = cameraEntity.camera.afterPasses; +passes.splice(passes.indexOf(pass), 1); +pass.destroy(); +``` + +## Examples + +The camera renders with frame passes: a forward pass renders the world and the UI layers into a texture, which a shader quad pass draws to the screen, tinted. + + + +A compute pass in the after passes of the camera builds a histogram of the colors it rendered. + + diff --git a/docs/user-manual/graphics/advanced-rendering/frame-graph/compute-pass.md b/docs/user-manual/graphics/advanced-rendering/frame-graph/compute-pass.md new file mode 100644 index 00000000000..a803153e6d5 --- /dev/null +++ b/docs/user-manual/graphics/advanced-rendering/frame-graph/compute-pass.md @@ -0,0 +1,99 @@ +--- +title: Compute Passes +description: "ComputePass dispatches compute shaders at a position in the frame: adding it to a camera, sizing the dispatch from the scene maps, reading results back, and XR views." +--- + +A [`ComputePass`](https://api.playcanvas.com/engine/classes/ComputePass.html) is a [frame pass](frame-passes.md) which dispatches a list of [compute](/user-manual/graphics/shaders/compute-shaders/) instances, in order, in a single physical compute pass. It places compute work at a position in the frame, for example after a camera renders, to process its scene depth and color maps in the same frame. + +To dispatch compute instances right away instead, when called, use [`GraphicsDevice#computeDispatch`](https://api.playcanvas.com/engine/classes/GraphicsDevice.html#computedispatch). + +:::warning + +Compute shaders are only supported on **WebGPU**, see [`GraphicsDevice#supportsCompute`](https://api.playcanvas.com/engine/classes/GraphicsDevice.html#supportscompute). On other devices, the pass executes without dispatching anything. + +::: + +## Creating the pass + +Create the pass with the compute instances it dispatches. Its [`computes`](https://api.playcanvas.com/engine/classes/ComputePass.html#computes) array holds them, and you can add instances to it, or remove them, later. Set its name, which labels the dispatch in GPU profiling: + +```javascript +const compute = new Compute(app.graphicsDevice, shader, 'Histogram'); +compute.setParameter('bins', histogramBuffer); + +const pass = new ComputePass(app.graphicsDevice, [compute]); +pass.name = 'HistogramPass'; +``` + +## Adding the pass to a frame + +Add the pass to the [`beforePasses`](https://api.playcanvas.com/engine/classes/CameraComponent.html#beforepasses) or the [`afterPasses`](https://api.playcanvas.com/engine/classes/CameraComponent.html#afterpasses) of a camera, to dispatch it before or after the camera renders, or to the before or after passes of another pass. A camera rendering with its own [`framePasses`](camera-passes.md#replacing-the-cameras-rendering) can also include the pass among them, to dispatch it at its position in the list, for example between a pass rendering the scene into a texture and a pass using what the compute shader wrote. A pass in the after passes of a camera reads what the camera rendered in the same frame: + +```javascript +cameraEntity.camera.afterPasses.push(pass); +``` + +The pass executes at most once in a frame. + +## Reading what the camera rendered + +A compute pass in the after passes of a camera can read the depth and color maps the camera rendered in the same frame. Request the maps on the camera, with [`requestSceneDepthMap`](https://api.playcanvas.com/engine/classes/CameraComponent.html#requestscenedepthmap) and [`requestSceneColorMap`](https://api.playcanvas.com/engine/classes/CameraComponent.html#requestscenecolormap), and attach them to the compute instance, with [`setSceneDepthMap`](https://api.playcanvas.com/engine/classes/Compute.html#setscenedepthmap) and [`setSceneColorMap`](https://api.playcanvas.com/engine/classes/Compute.html#setscenecolormap): + +```javascript +cameraEntity.camera.requestSceneColorMap(true); +compute.setSceneColorMap(cameraEntity.camera.sceneColorMapHandle); +``` + +See [Scene Depth and Color Maps](/user-manual/graphics/shaders/compute-shaders/#scene-depth-and-color-maps) for requesting them on a camera rendering with a `CameraFrame`, and for reading them in the compute shader. + +## The dispatch + +Each compute instance is dispatched with the dispatch size and the parameters set on it. Set them before the frame renders, or in [`onBefore`](https://api.playcanvas.com/engine/classes/FramePass.html#onbefore), right before the dispatch. Use `onBefore` also to prepare the buffers the shaders write, for example to clear them. + +A shader processing each texel of the scene color map of a camera, attached [as above](#reading-what-the-camera-rendered), is dispatched over the size of the color map. Its handle has the size of the map the camera rendered most recently, which differs from the size of the canvas when the camera renders at a render scale, or into a render target of its own. The depth map handle has the same [`width`](https://api.playcanvas.com/engine/classes/SceneDepthMapHandle.html#width) and [`height`](https://api.playcanvas.com/engine/classes/SceneDepthMapHandle.html#height): + +```javascript +pass.onBefore = () => { + // a thread for each texel of the color map the camera just rendered + const { width, height } = cameraEntity.camera.sceneColorMapHandle; + compute.setupDispatchThreads(width, height); + histogramBuffer.clear(); +}; +``` + +## Reading the results back + +Read the results in [`onAfter`](https://api.playcanvas.com/engine/classes/FramePass.html#onafter), as a read requested there is recorded right after the dispatch. The data arrives a few frames later: + +```javascript +pass.onAfter = () => { + histogramBuffer.read().then((data) => { + // the histogram, a few frames after it was computed + }); +}; +``` + +## XR views + +When the frame renders multiple XR views, [`xrViewIndex`](https://api.playcanvas.com/engine/classes/ComputePass.html#xrviewindex) selects the view the pass executes in, at its position in that view. Use `-1` to execute it once, before all the views. As a compute instance is dispatched at most once in a frame, dispatching for several views needs a `ComputePass` for each, with separate compute instances. A pass in the after passes of a camera executes once, after all the views. + +## Owning the pass + +The pass does not destroy its compute instances, and the camera does not destroy the pass. When no longer needed, remove the pass, and destroy it and the compute instances: + +```javascript +const passes = cameraEntity.camera.afterPasses; +passes.splice(passes.indexOf(pass), 1); +pass.destroy(); +compute.destroy(); +``` + +## Examples + +A compute pass after the camera builds a histogram of the colors it rendered, which is read back and drawn on the screen. + + + +A compute pass after the camera reads its scene depth and color maps, to draw a scanner pulse sweeping through the scene. + + diff --git a/docs/user-manual/graphics/advanced-rendering/frame-graph/frame-passes.md b/docs/user-manual/graphics/advanced-rendering/frame-graph/frame-passes.md new file mode 100644 index 00000000000..a2da2d6dc26 --- /dev/null +++ b/docs/user-manual/graphics/advanced-rendering/frame-graph/frame-passes.md @@ -0,0 +1,114 @@ +--- +title: Frame Passes +description: "The engine's frame passes, the logical passes of the frame graph: the pass classes, their lifecycle, nesting, adding them to a camera, and who owns them." +--- + +In PlayCanvas, each logical pass of the [frame graph](index.md) is a **frame pass**: an object of the engine, which records the work of the pass when the frame renders. The engine renders its own frame this way: the shadow maps, the scene of each camera, and the post-processing are all frame passes, and you can add frame passes of your own, to render into textures, to process what a camera renders, or to run compute shaders on it. + +Each frame, the engine collects the frame passes, in order, into the frame graph, and then runs them one after another, starting and ending the physical passes they need. You never start or end a physical pass yourself. + +## The pass classes + +Every frame pass is an instance of `FramePass`, or of a class derived from it. Two classes derived from it cover the two kinds of physical passes: `RenderPass` for rendering, and `ComputePass` for compute shaders: + +```mermaid +classDiagram + FramePass <|-- RenderPass + FramePass <|-- ComputePass +``` + +| Class | Use it for | +| ----- | ---------- | +| [`FramePass`](https://api.playcanvas.com/engine/classes/FramePass.html) | The base class. Work which does not need a physical pass, or a group of other frame passes. | +| [`RenderPass`](render-pass.md) | Rendering into a render target, or into the back buffer. | +| [`ComputePass`](compute-pass.md) | Dispatching compute shaders at a position in the frame. | + +A `RenderPass` is a logical render pass, which records its draw calls into a physical render pass for its render target. Usually, the engine starts a physical render pass for each `RenderPass` and ends it after it. When `RenderPass` objects in a row render into the same render target, they share a single physical render pass instead, see [Frame Graph](index.md#the-frame-graph). Similarly, the engine records the dispatches of a `ComputePass` into a physical compute pass. + +The engine derives classes from `RenderPass` for common kinds of drawing, such as running a shader for every pixel of a render target, or rendering the scene. See [Render Passes](render-pass.md#classes-derived-from-renderpass). + +## The lifecycle of a frame pass + +A frame pass goes through two phases in each frame it is part of. While the frame is built, before anything renders, the engine updates the pass and works out which passes the frame renders. Then the frame renders the passes in order. + +```mermaid +flowchart TB + subgraph Build[Building the frame] + direction LR + Enabled{enabled?} -->|yes| FrameUpdate[frameUpdate] --> Add[its before passes, the pass
and its after passes are added] + Enabled -->|no| Skip([skipped, with its
before and after passes]) + end + subgraph Render[Rendering the frame] + direction LR + Before[before] --> OnBefore[onBefore] --> ExecuteEnabled{executeEnabled?} + ExecuteEnabled -->|yes| Execute[execute] --> After[after] + ExecuteEnabled -->|no| After + After --> OnAfter[onAfter] + end + Build --> Render +``` + +A subclass overrides these methods to do its work: + +- [`frameUpdate`](https://api.playcanvas.com/engine/classes/FramePass.html#frameupdate) updates the pass for the frame, before anything renders: for example to resize its render targets, to set the textures it reads, or to add or remove its before and after passes. +- [`before`](https://api.playcanvas.com/engine/classes/FramePass.html#before) and [`after`](https://api.playcanvas.com/engine/classes/FramePass.html#after) run around the work of the pass. +- [`execute`](https://api.playcanvas.com/engine/classes/FramePass.html#execute) records the work. For a `RenderPass`, it runs inside a physical render pass for its render target. + +The [`onBefore`](https://api.playcanvas.com/engine/classes/FramePass.html#onbefore) and [`onAfter`](https://api.playcanvas.com/engine/classes/FramePass.html#onafter) callbacks let you hook into a pass without writing a subclass: to set the parameters of the pass, or to clear a buffer it writes to, right before it executes, and to read back what it wrote right after: + +```javascript +pass.onBefore = () => { + buffer.clear(); +}; + +pass.onAfter = () => { + buffer.read().then((data) => { + // the results arrive a few frames later + }); +}; +``` + +### Enabled and executeEnabled + +Two properties turn a pass off, at different points of the lifecycle: + +- Setting [`enabled`](https://api.playcanvas.com/engine/classes/FramePass.html#enabled) to false skips the pass when the frame is built, together with its before and after passes, and its `frameUpdate` is not called. Change it before the frame is built: the `frameUpdate` of a pass cannot disable the pass itself. +- Setting [`executeEnabled`](https://api.playcanvas.com/engine/classes/FramePass.html#executeenabled) to false only skips `execute`. The pass is still updated, its before and after passes still render, and `before`, `after`, `onBefore` and `onAfter` are still called. Set it at the latest in `frameUpdate`. + +## Before and after passes + +Every pass has [`beforePasses`](https://api.playcanvas.com/engine/classes/FramePass.html#beforepasses) and [`afterPasses`](https://api.playcanvas.com/engine/classes/FramePass.html#afterpasses), which render right before and right after it, every frame the pass renders: + +```mermaid +flowchart LR + BeforePasses[pass.beforePasses] --> Pass[pass] --> AfterPasses[pass.afterPasses] +``` + +This nests passes, and a plain `FramePass` can hold a group of passes which are enabled and disabled together: + +```javascript +// a group of two blur passes: disabling the group skips both +const blur = new FramePass(app.graphicsDevice); +blur.name = 'Blur'; +blur.beforePasses.push(horizontalBlurPass, verticalBlurPass); + +blur.enabled = false; +``` + +## Adding passes to a camera + +A camera renders [before passes](camera-passes.md#before-passes), then its own rendering, which you can replace with [frame passes](camera-passes.md#replacing-the-cameras-rendering) of your own, and then [after passes](camera-passes.md#after-passes). See [Camera Passes](camera-passes.md). + +## Rendering a pass on its own + +A pass can also render outside of a frame, for example to render into a texture once, by calling [`render`](https://api.playcanvas.com/engine/classes/FramePass.html#render). It runs `before`, `onBefore`, `execute`, `after` and `onAfter`, but the frame does not prepare it: its `frameUpdate` is not called, and its before and after passes do not render. + +```javascript +pass.render(); +``` + +## Owning passes + +Nothing destroys a pass for you. A camera does not destroy the passes in its lists, and a pass does not destroy its before and after passes. The code which creates a pass destroys it, once it is removed from the lists it was added to, by calling [`destroy`](https://api.playcanvas.com/engine/classes/FramePass.html#destroy). A pass is not used after it is destroyed. A pass does not destroy what it was given either, such as its render target or its shader. + +The name of a pass, set with [`name`](https://api.playcanvas.com/engine/classes/FramePass.html#name), identifies it in GPU profiling and graphics debuggers. Set it explicitly, as the default, the name of the class, is shortened in minified builds. diff --git a/docs/user-manual/graphics/advanced-rendering/frame-graph/index.md b/docs/user-manual/graphics/advanced-rendering/frame-graph/index.md new file mode 100644 index 00000000000..7b0fa5e5cc7 --- /dev/null +++ b/docs/user-manual/graphics/advanced-rendering/frame-graph/index.md @@ -0,0 +1,70 @@ +--- +title: Frame Graph +description: "How the GPU renders a frame as a sequence of render and compute passes, and how a frame graph describes them." +--- + +The GPU renders into a [render target](../render-targets.md), a texture or a set of textures, or into the **back buffer**, which is displayed on the screen. Rendering into one of them, from start to end, is called a **physical render pass**. A frame takes several physical render passes, and a **frame graph** describes the steps of the frame which produce them, as **logical passes**. This page explains these concepts, and the pages in this section show how PlayCanvas implements them. + +## Physical render passes + +A render target holds one or more color buffers, and optionally a depth and a stencil buffer, called its **attachments**. The back buffer has the same attachments, provided by the canvas. A physical render pass renders into these attachments in three steps: + +1. **It starts** by clearing the attachments to a value, or by loading what they already contain. +2. **It draws**: all its draw calls render into the attachments. +3. **It ends** by storing what was drawn into the attachments, or by discarding it. + +The graphics API defines the physical render pass: WebGPU calls it a render pass, and in WebGL it is the drawing done into one framebuffer. + +## Physical compute passes + +Compute shaders do not render into a render target. Instead, they run dispatches, which read and write buffers and textures directly. The GPU runs them in **physical compute passes**, which group the dispatches the way a physical render pass groups draw calls. Compute shaders are only supported on WebGPU. + +## A frame of passes + +A **frame** is one image the application renders, typically once for each refresh of the display. A physical render pass renders into a single render target, so a frame needs several of them: to render into a different render target, for example to sample a texture which was just rendered into, one physical render pass ends and another starts. A simple frame renders three physical render passes, each sampling what the one before it rendered: + +```mermaid +flowchart LR + Shadow[Shadow map] -->|samples| Scene[Scene texture] + Scene -->|samples| Post[Back buffer, displayed] +``` + +Each box is one physical render pass, named after what it renders into: the shadow map of a light, a texture holding the scene, and the back buffer, into which the post-processing draws the scene texture. The passes depend on each other, so their order matters: a pass sampling a texture renders after the pass rendering into it. + +## The frame graph + +A **frame graph** describes all the steps of a frame, in the order they execute: what each step renders into, and what it draws or computes. Each step is a **logical pass**. Rather than starting and ending physical passes as it goes, the renderer builds the frame graph for the frame first, and then executes its logical passes in physical passes. Most logical render passes get a physical render pass of their own, but knowing the whole frame in advance lets the renderer start and end only the physical passes the frame needs: + +- **Logical passes share physical render passes.** Consecutive logical passes rendering into the same render target are recorded into a single physical render pass, so the attachments are not stored and loaded again between them. +- **Attachments are stored only when needed.** An attachment a later logical pass loads is stored, and the others can be discarded. + +Here, three logical passes render the frame in two physical render passes: + +```mermaid +flowchart LR + Opaque[Logical pass:
opaque scene] --> ScenePhysical[Physical render pass 1:
into the scene texture] + Transparent[Logical pass:
transparent scene] --> ScenePhysical + Post[Logical pass:
post-processing] --> BackPhysical[Physical render pass 2:
into the back buffer] +``` + +### Multiple cameras + +A scene can have several cameras, for example one rendering a reflection or a minimap into a texture, and the main camera rendering to the screen. The frame graph composes the logical passes of all the cameras into one frame, in the order of their [priorities](https://api.playcanvas.com/engine/classes/CameraComponent.html#priority): the passes of a camera with a smaller priority value come first. A camera rendering into a texture therefore needs a smaller priority than the camera using the texture, so the texture is ready when it is used. The shadow maps a camera needs render before its scene. + +```mermaid +flowchart LR + Reflection[Camera, priority -1:
its passes render
the reflection texture] --> Main[Camera, priority 0:
its passes render the scene,
sampling the reflection,
to the back buffer] +``` + +## Performance + +Starting and ending a physical render pass has a cost, especially on mobile GPUs. They render in small tiles of fast on-chip memory, and copy the attachments between it and the main memory: in when a pass loads them, and out when it stores them. How many physical render passes a frame needs, and what they load and store, therefore matters for performance. Clearing an attachment, rather than loading it, avoids copying it in, and so does sharing a physical render pass between logical passes rendering into the same render target. + +## In PlayCanvas + +Each logical pass of the frame graph is a frame pass, an object of the engine. The engine renders its own frame with frame passes, and you can add your own: + +- [Frame Passes](frame-passes.md) are the logical passes of the frame graph: the pass classes and their lifecycle. +- [Camera Passes](camera-passes.md) render before and after a camera, or replace its rendering. +- [Render Passes](render-pass.md) render into a render target, including running a shader for every pixel of it, and rendering the scene. +- [Compute Passes](compute-pass.md) run compute shaders. diff --git a/docs/user-manual/graphics/advanced-rendering/frame-graph/render-pass-forward.md b/docs/user-manual/graphics/advanced-rendering/frame-graph/render-pass-forward.md new file mode 100644 index 00000000000..f4f9602441a --- /dev/null +++ b/docs/user-manual/graphics/advanced-rendering/frame-graph/render-pass-forward.md @@ -0,0 +1,56 @@ +--- +title: Rendering the Scene +description: "RenderPassForward renders layers of the scene with a camera: creating it, adding layers, what it does each frame, and replacing the camera's rendering with your own passes." +--- + +A [`RenderPassForward`](https://api.playcanvas.com/engine/classes/RenderPassForward.html) is a [`RenderPass`](render-pass.md) which renders layers of the scene with a single camera. It is the pass the engine renders the scene with. Use it in the [`framePasses`](https://api.playcanvas.com/engine/classes/CameraComponent.html#framepasses) of a camera to render the scene into a render target of your own, and to process it with your own passes before it is displayed. + +## Creating the pass + +Create the pass with the graphics device, the scene, and the camera component rendering the layers. Initialize it with the render target to render into, and then add the layers it renders, in order: + +```javascript +const worldLayer = app.scene.layers.getLayerByName('World'); +const uiLayer = app.scene.layers.getLayerById(LAYERID_UI); + +const scenePass = new RenderPassForward(app.graphicsDevice, app.scene, cameraEntity.camera); +scenePass.name = 'ScenePass'; +scenePass.init(renderTarget); + +scenePass.addLayer(worldLayer, false); +scenePass.addLayer(uiLayer, true); +``` + +Each layer has an opaque and a transparent sub-layer. The second argument of [`addLayer`](https://api.playcanvas.com/engine/classes/RenderPassForward.html#addlayer) selects which of them renders, so add a layer twice to render both. The camera needs to render the layers the pass renders, see [`CameraComponent#layers`](https://api.playcanvas.com/engine/classes/CameraComponent.html#layers). + +## What the pass does each frame + +The pass renders its layers the way a camera renders them: + +- It clears the render target as the clear flags of the camera and of the layers request. Pass `false` as the third argument of `addLayer` to not clear for that layer. +- It culls the mesh instances of its layers for the camera, and renders the directional shadows they need. +- It renders the [`beforePasses`](https://api.playcanvas.com/engine/classes/CameraComponent.html#beforepasses) of the camera, when it is the first pass rendering the camera in the frame. +- It fires the `prerender` event of the scene from the first pass rendering the camera in the frame, `postrender` from the last one, and `prerender:layer` and `postrender:layer` around each layer it renders. + +## Gamma correction and tone mapping + +By default, the pass renders with the gamma correction and the tone mapping of the camera. A pass can override them with [`gammaCorrection`](https://api.playcanvas.com/engine/classes/RenderPassForward.html#gammacorrection) and [`toneMapping`](https://api.playcanvas.com/engine/classes/RenderPassForward.html#tonemapping). For example, an HDR pipeline renders the scene linearly into a floating point render target, and tone maps it in a later pass, while a pass rendering the UI to the screen keeps the camera's settings: + +```javascript +scenePass.gammaCorrection = GAMMA_NONE; +scenePass.toneMapping = TONEMAP_LINEAR; +``` + +## Replacing the camera's rendering + +To render a camera with passes of your own, add a `RenderPassForward` to its [`framePasses`](camera-passes.md#replacing-the-cameras-rendering), together with the passes processing what it renders, for example rendering the scene into a texture, and then drawing the texture to the screen with a [shader quad pass](render-pass-shader-quad.md). A camera can also render its layers in several forward passes, for example its opaque layers, then a pass of your own reading what they rendered, and then its transparent layers. See [Camera Passes](camera-passes.md). + +## Limitations + +A `RenderPassForward` cannot be nested under another one: in its before or after passes, or in the `beforePasses` of its camera, which it renders before itself. To render the layers with another camera first, render the passes one after the other, for example as the before passes of a plain `FramePass` in the `framePasses`, or render them with a camera of a lower priority. + +## Example + +A forward pass renders the world and the UI layers into a texture, which a shader quad pass draws to the screen, tinted. + + diff --git a/docs/user-manual/graphics/advanced-rendering/frame-graph/render-pass-shader-quad.md b/docs/user-manual/graphics/advanced-rendering/frame-graph/render-pass-shader-quad.md new file mode 100644 index 00000000000..13fac18e6e4 --- /dev/null +++ b/docs/user-manual/graphics/advanced-rendering/frame-graph/render-pass-shader-quad.md @@ -0,0 +1,169 @@ +--- +title: Shader Quad Passes +description: "RenderPassShaderQuad runs a shader for every pixel of a render target, or of a part of it: writing the shader, setting its parameters, render states, instanced and repeated quads, and drawQuadWithShader." +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +A [`RenderPassShaderQuad`](https://api.playcanvas.com/engine/classes/RenderPassShaderQuad.html) is a [`RenderPass`](render-pass.md) which draws a quad with a shader, so the fragment shader runs once for each pixel the quad covers: by default the whole render target, or the part of it its [viewport](#render-states-and-the-drawn-area) selects. This is the building block of post-processing effects, and of any pass computing a texture on the GPU. + +The pass creates the quad for its [`shader`](https://api.playcanvas.com/engine/classes/RenderPassShaderQuad.html#shader), sets the render states, and draws the quad when it executes. You can use it directly, by setting its shader, or as the base class of a pass which creates the shader and sets its parameters. + +## The shader + +The vertex shader receives the corners of the quad in an attribute using `SEMANTIC_POSITION`. The `quadVS` shader chunk implements it, and passes the uv coordinates of the quad to the fragment shader in the `uv0` varying, so you only write the fragment shader. Write it in both GLSL and WGSL, so the pass works on WebGL2 and WebGPU: + + + + +```glsl +uniform sampler2D sourceTexture; +uniform vec3 tint; +varying vec2 uv0; + +void main() { + vec4 color = texture2D(sourceTexture, uv0); + gl_FragColor = vec4(color.rgb * tint, color.a); +} +``` + + + + +```wgsl +var sourceTexture: texture_2d; +var sourceTextureSampler: sampler; +uniform tint: vec3f; +varying uv0: vec2f; + +@fragment fn fragmentMain(input: FragmentInput) -> FragmentOutput { + var output: FragmentOutput; + let color = textureSample(sourceTexture, sourceTextureSampler, input.uv0); + output.color = vec4f(color.rgb * uniform.tint, color.a); + return output; +} +``` + + + + +Create the shader with [`ShaderUtils.createShader`](https://api.playcanvas.com/engine/classes/ShaderUtils.html#createshader), giving it both fragment shaders: + +```javascript +const shader = ShaderUtils.createShader(device, { + uniqueName: 'TintShader', + attributes: { aPosition: SEMANTIC_POSITION }, + vertexChunk: 'quadVS', + fragmentGLSL: tintGLSL, + fragmentWGSL: tintWGSL +}); +``` + +The pass does not destroy its shader, as shaders are shared. + +## Setting the shader parameters + +The textures and uniforms of the shader are set on the scope of the graphics device. Other passes can set parameters with the same names, so set them right before the quad is drawn: resolve them once, when the pass is created, and set their values in an override of `execute`, before calling `super.execute()`, which draws the quad: + +```javascript +// A pass drawing a texture into its render target, multiplied by a color +class TintPass extends RenderPassShaderQuad { + constructor(device, sourceTexture) { + super(device); + this.name = 'TintPass'; + this.sourceTexture = sourceTexture; + this.tint = new Float32Array([1, 0.5, 0.5]); + this.shader = ShaderUtils.createShader(device, { + uniqueName: 'TintShader', + attributes: { aPosition: SEMANTIC_POSITION }, + vertexChunk: 'quadVS', + fragmentGLSL: tintGLSL, + fragmentWGSL: tintWGSL + }); + + // resolve the shader parameters once + this.sourceTextureId = device.scope.resolve('sourceTexture'); + this.tintId = device.scope.resolve('tint'); + } + + execute() { + // set the parameters right before the quad is drawn + this.sourceTextureId.setValue(this.sourceTexture); + this.tintId.setValue(this.tint); + super.execute(); + } +} + +const pass = new TintPass(app.graphicsDevice, sourceTexture); +pass.init(renderTarget); + +// the quad covers the whole render target, so clear it instead of loading its content +pass.setClearColor(Color.BLACK); +``` + +Add the pass to a camera to draw it every frame, see [Camera Passes](camera-passes.md), or render it once with `pass.render()`. + +## Render states and the drawn area + +By default, the quad covers the whole render target, and is drawn without blending, depth test or face culling, replacing what the render target contains. To change that, set: + +- [`blendState`](https://api.playcanvas.com/engine/classes/RenderPassShaderQuad.html#blendstate) to combine the quad with the content of the render target. +- [`depthState`](https://api.playcanvas.com/engine/classes/RenderPassShaderQuad.html#depthstate), [`stencilFront`](https://api.playcanvas.com/engine/classes/RenderPassShaderQuad.html#stencilfront) and [`stencilBack`](https://api.playcanvas.com/engine/classes/RenderPassShaderQuad.html#stencilback) to test the quad against the depth or the stencil of the render target. +- [`viewport`](https://api.playcanvas.com/engine/classes/RenderPassShaderQuad.html#viewport), and optionally [`scissor`](https://api.playcanvas.com/engine/classes/RenderPassShaderQuad.html#scissor), to draw into a part of the render target only. + +For example, to draw shaders into a tile of a texture atlas, using the pass directly. The atlas is not cleared, so its other tiles keep their content: + +```javascript +const pass = new RenderPassShaderQuad(app.graphicsDevice); +pass.name = 'AtlasTile'; +pass.shader = tileShader; +pass.init(atlasRenderTarget); + +pass.viewport = new Vec4(256, 0, 256, 256); +pass.render(); + +// blend a second shader over the same tile +pass.shader = overlayShader; +pass.blendState = BlendState.ALPHABLEND; +pass.render(); + +pass.destroy(); +``` + +## Drawing several quads + +To draw many quads which differ only by the data each reads for its index, such as sprites, set [`numInstances`](https://api.playcanvas.com/engine/classes/RenderPassShaderQuad.html#numinstances) to draw them in a single instanced draw. Each instance reads its index, `gl_InstanceID` in GLSL and `pcInstanceIndex` in WGSL, to fetch the data of its quad, for example to place it. By default, `numInstances` is `undefined`, which draws a single quad without instancing. Zero draws nothing, while the pass still renders, so it still clears its render target. + +To draw several quads with different parameters, for example one for each light, override `execute`, and for each quad set its parameters, or its viewport, and call `super.execute()`, which draws it with the render states of the pass: + +```javascript +execute() { + for (const light of this.lights) { + this.colorId.setValue(light.color); + this.viewport = light.screenRect; + super.execute(); + } +} +``` + +## Drawing a quad immediately + +For a single draw, for example to generate a texture once, [`drawQuadWithShader`](https://api.playcanvas.com/engine/functions/drawQuadWithShader.html) draws a quad with a shader into a render target right away, without setting up a pass: + +```javascript +device.scope.resolve('scale').setValue(4); +drawQuadWithShader(device, renderTarget, shader); +``` + +Each call creates a pass, renders it and destroys it, so it allocates on every call. It draws with the blend state currently set on the graphics device, and loads the content of the render target without clearing it. To draw a quad every frame, or with its own render states, use a `RenderPassShaderQuad`, created once and reused. + +## Examples + +Two `RenderPassShaderQuad` passes paint light trails into a texture: a fade pass darkens it with a reverse subtract blend state, and a brush pass draws a dot along the path of each brush, setting the viewport of each dot, all in one pass. + + + +A `RenderPassShaderQuad` in a post-processing effect, drawing ink outlines into the scene where its depth or brightness changes abruptly. + + diff --git a/docs/user-manual/graphics/advanced-rendering/frame-graph/render-pass.md b/docs/user-manual/graphics/advanced-rendering/frame-graph/render-pass.md new file mode 100644 index 00000000000..553cc1a7ec5 --- /dev/null +++ b/docs/user-manual/graphics/advanced-rendering/frame-graph/render-pass.md @@ -0,0 +1,141 @@ +--- +title: Render Passes +description: "RenderPass draws into a render target or the back buffer: initializing it, resizing its render target, clearing, storing and mipmaps, and writing a pass." +--- + +A [`RenderPass`](https://api.playcanvas.com/engine/classes/RenderPass.html) is a [frame pass](frame-passes.md) which draws into a [render target](../render-targets.md). When it executes, its draw calls are recorded into a physical render pass for its render target: usually one of its own, which starts right before the pass executes and ends right after, or one it shares with the passes next to it which render into the same render target, see [Passes sharing a render target](#passes-sharing-a-render-target). + +## Classes derived from RenderPass + +`RenderPass` is the base class of the passes which draw. For common kinds of drawing, the engine provides classes derived from it, which you use directly, or as the base class of your own pass: + +```mermaid +classDiagram + RenderPass <|-- RenderPassShaderQuad + RenderPass <|-- RenderPassForward +``` + +| Class | Use it for | +| ----- | ---------- | +| [`RenderPassShaderQuad`](render-pass-shader-quad.md) | Running a shader for every pixel of a render target, or of a part of it, such as a post-processing effect. | +| [`RenderPassForward`](render-pass-forward.md) | Rendering layers of the scene with a camera. | + +Derive from `RenderPass` itself to record draw calls of your own, see [Writing a pass](#writing-a-pass). The rest of this page applies to all render passes, including the derived classes. + +## Setting up the pass + +Once the pass is created, call [`init`](https://api.playcanvas.com/engine/classes/RenderPass.html#init) with the render target to draw into, or with `null` to draw into the back buffer. A pass needs to be initialized before it renders, and before its clears and stores are set: + +```javascript +const pass = new MyPass(app.graphicsDevice); +pass.name = 'MyPass'; +pass.init(renderTarget); +``` + +The pass does not own its render target, and does not destroy it. Call `init` again to switch the pass to a render target with different attachments. A render target with the same layout, the same color buffers, depth and stencil, and number of samples, can also be assigned to [`renderTarget`](https://api.playcanvas.com/engine/classes/RenderPass.html#rendertarget) directly, for example to alternate between two render targets each frame. + +## Resizing the render target + +When `init` is given options, the pass also resizes its render target every frame, to the size of a texture, of a render target, or of the back buffer, scaled by `scaleX` and `scaleY`: + +```javascript +// a render target half the size of the back buffer +pass.init(renderTarget, { + resizeSource: null, + scaleX: 0.5, + scaleY: 0.5 +}); + +// a render target the size of the texture the pass processes +pass.init(renderTarget, { resizeSource: sourceTexture }); +``` + +The options are copied. To change them later, set [`resizeSource`](https://api.playcanvas.com/engine/classes/RenderPass.html#resizesource), [`scaleX`](https://api.playcanvas.com/engine/classes/RenderPass.html#scalex) and [`scaleY`](https://api.playcanvas.com/engine/classes/RenderPass.html#scaley) on the pass: for example, set `resizeSource` when the texture the pass processes is replaced. A `resizeSource` of `undefined`, the default, leaves the size of the render target as it is. + +The render target is resized while the frame is built, in the `frameUpdate` of the pass, and has its new size once the pass is updated. On a frame the size changes, the passes updated before it still see the previous size. + +## Clearing the attachments + +The attachments of a render target are its color buffers, and its depth and stencil buffers. By default, the physical render pass loads their content, and the pass draws over it. To start from a single value instead, clear them: + +```javascript +pass.setClearColor(Color.BLACK); +pass.setClearDepth(1); +pass.setClearStencil(0); +``` + +When the pass covers an attachment completely, clearing it is also faster than loading it on tiled mobile GPUs, which then do not read the previous content from memory. [`setClearColor`](https://api.playcanvas.com/engine/classes/RenderPass.html#setclearcolor) clears all the color buffers, or the one at the index it is given. + +## Storing the attachments + +When the physical render pass ends, the pass keeps what it rendered into the textures of its render target, so they can be sampled, or loaded by a later pass: its color buffers, resolved when the render target is multisampled, and its depth buffer texture. It discards the other attachments: the samples of a multisampled color buffer which is resolved, the stencil, and a depth buffer the render target creates internally. + +When a later pass of the same frame draws into the same render target and loads them, the frame stores them automatically. For a pass rendered on its own, store them with [`setStoreColor`](https://api.playcanvas.com/engine/classes/RenderPass.html#setstorecolor), [`setStoreDepth`](https://api.playcanvas.com/engine/classes/RenderPass.html#setstoredepth) and [`setStoreStencil`](https://api.playcanvas.com/engine/classes/RenderPass.html#setstorestencil). + +The mipmaps of color buffers with mipmaps are generated when the pass ends too. When several passes draw into the same render target, the mipmaps are only needed after the last of them, so turn them off for the others with [`setGenerateMipmaps`](https://api.playcanvas.com/engine/classes/RenderPass.html#setgeneratemipmaps): + +```javascript +firstPass.setGenerateMipmaps(false); +``` + +`init` resets the clears, stores and mipmaps to their defaults, so set them once the pass is initialized. + +## Passes sharing a render target + +In a frame, consecutive passes drawing into the same render target are merged into a single physical render pass, which avoids storing and loading the attachments between them. Something which needs to happen between them keeps them apart: a clear of the later pass, a before or after pass between them, or an `onBefore` or `onAfter` callback. + +## Writing a pass + +A subclass records its draw calls in [`execute`](https://api.playcanvas.com/engine/classes/FramePass.html#execute), which runs while the physical render pass is active. It can override [`postInit`](https://api.playcanvas.com/engine/classes/RenderPass.html#postinit) to set up its clears once the pass is initialized, and [`frameUpdate`](https://api.playcanvas.com/engine/classes/RenderPass.html#frameupdate) to update the pass before each frame renders, calling `super.frameUpdate()` to keep the resize. It releases what it created in [`destroy`](https://api.playcanvas.com/engine/classes/FramePass.html#destroy), calling `super.destroy()`: + +```javascript +// A pass drawing a quad with a shader, covering its whole render target +class MyPass extends RenderPass { + constructor(device, shader) { + super(device); + this.quadRender = new QuadRender(shader); + } + + postInit() { + // the quad covers the whole target, so clear it instead of loading its content + this.setClearColor(Color.BLACK); + } + + execute() { + // the default draw states suit a quad: no blending, and no depth test or write + this.device.setDrawStates(); + this.quadRender.render(); + } + + destroy() { + this.quadRender.destroy(); + super.destroy(); + } +} +``` + +For a quad drawn with a shader, [`RenderPassShaderQuad`](render-pass-shader-quad.md) already does this, and also sets up the render states. + +## Rendering into a texture once + +To render into a texture only when its content needs to change, render the pass on its own with [`render`](https://api.playcanvas.com/engine/classes/RenderPass.html#render), outside of a frame: + +```javascript +const renderTarget = new RenderTarget({ colorBuffer: texture, depth: false }); +const pass = new MyPass(app.graphicsDevice, shader); +pass.name = 'MyPass'; +pass.init(renderTarget); + +// draw into the texture +pass.render(); + +// destroy the pass, and then the render target, which the pass does not own +pass.destroy(); +renderTarget.destroy(); +``` + +## Example + +Two `RenderPass` subclasses, which are not attached to any camera, generate the textures of a material: a pattern, and a normal map from its height. They render on their own from the `prerender` event, every frame while animating, and otherwise only when a setting changes. + + diff --git a/docs/user-manual/graphics/posteffects/cameraframe/custom-passes.md b/docs/user-manual/graphics/posteffects/cameraframe/custom-passes.md deleted file mode 100644 index 0f99d310b76..00000000000 --- a/docs/user-manual/graphics/posteffects/cameraframe/custom-passes.md +++ /dev/null @@ -1,153 +0,0 @@ ---- -title: Custom Render Passes -description: Build custom RenderPass-based post stacks on the camera without CameraFrame for full manual pipeline control. ---- - -The most flexible approach is to implement completely custom render passes that work independently from `CameraFrame`. This gives you full control over the rendering pipeline and allows you to build a custom post-processing stack from scratch. - -## Overview - -This approach does not use `CameraFrame` at all. Instead, you create your own render passes and assign them directly to the camera's `framePasses` array. This is ideal when you need complete control or want to implement a custom post-processing pipeline. - -## Example: Simple Tint Render Pass - -Here's a complete example of a custom render pass that tints the scene: - -```javascript -import { - Color, - PIXELFORMAT_RGBA8, - RenderPassForward, - RenderPassShaderQuad, - RenderTarget, - SEMANTIC_POSITION, - ShaderUtils, - Texture -} from 'playcanvas'; - -class RenderPassTint extends RenderPassShaderQuad { - constructor(device, sourceTexture) { - super(device); - this.sourceTexture = sourceTexture; - this.tint = Color.WHITE.clone(); - - this.shader = this.createShader(); - } - - createShader() { - return ShaderUtils.createShader(this.device, { - uniqueName: 'TintShader', - attributes: { aPosition: SEMANTIC_POSITION }, - vertexChunk: 'quadVS', - - fragmentGLSL: ` - uniform sampler2D sourceTexture; - uniform vec3 tint; - varying vec2 uv0; - - void main() { - vec4 color = texture2D(sourceTexture, uv0); - gl_FragColor = vec4(color.rgb * tint, color.a); - } - `, - - fragmentWGSL: ` - var sourceTexture: texture_2d; - var sourceTextureSampler: sampler; - uniform tint: vec3f; - varying uv0: vec2f; - - @fragment fn fragmentMain(input: FragmentInput) -> FragmentOutput { - var output: FragmentOutput; - let color: vec4f = textureSample(sourceTexture, sourceTextureSampler, uv0); - output.color = vec4f(color.rgb * uniform.tint, color.a); - return output; - } - ` - }); - } - - execute() { - this.device.scope.resolve('sourceTexture').setValue(this.sourceTexture); - this.device.scope.resolve('tint').setValue([this.tint.r, this.tint.g, this.tint.b]); - super.execute(); - } -} -``` - -## Setting Up Custom Render Passes - -To use custom render passes without `CameraFrame`: - -```javascript -// Create your scene render pass (renders the 3D scene) -const scenePass = new RenderPassForward(device, composition, scene, renderer); -scenePass.init(renderTarget); - -// Create your custom post-processing pass -const tintPass = new RenderPassTint(device, renderTarget.colorBuffer); -tintPass.init(camera.renderTarget); - -// Assign passes to the camera -camera.framePasses = [scenePass, tintPass]; -``` - -## Multi-Pass Example - -Here's an example of chaining multiple custom passes: - -```javascript -// Create render targets -const rt1 = new RenderTarget({ - colorBuffer: new Texture(device, { - width: 1920, height: 1080, - format: PIXELFORMAT_RGBA8 - }) -}); - -const rt2 = new RenderTarget({ - colorBuffer: new Texture(device, { - width: 1920, height: 1080, - format: PIXELFORMAT_RGBA8 - }) -}); - -// Create scene pass -const scenePass = new RenderPassForward(device, composition, scene, renderer); -scenePass.init(rt1); - -// RenderPassBlurHorizontal and RenderPassBlurVertical are illustrative custom -// passes you'd define yourself, similar to RenderPassTint above -const blurHPass = new RenderPassBlurHorizontal(device, rt1.colorBuffer); -blurHPass.init(rt2); - -const blurVPass = new RenderPassBlurVertical(device, rt2.colorBuffer); -blurVPass.init(camera.renderTarget); // Final output - -// Set the pass chain -camera.framePasses = [scenePass, blurHPass, blurVPass]; -``` - -## Resources - -- Render Pass Example - Complete demonstration of custom render passes - - - -## Use Cases - -This approach is ideal for: - -- **Complete custom pipelines** - When you need total control over the rendering process -- **Integration with external systems** - Integrating third-party rendering or effects libraries -- **Performance optimization** - Building a minimal pipeline tailored to your specific needs -- **Learning and experimentation** - Understanding how rendering pipelines work -- **Specialized rendering** - Non-standard rendering techniques or research implementations - -## Important Considerations - -- **Manual management** - You're responsible for managing render targets, textures, and memory -- **Memory management** - Ensure proper resource cleanup to avoid memory leaks -- **Cross-platform** - Provide both GLSL and WGSL shaders for WebGL and WebGPU support -- **Resolution handling** - Handle dynamic resolution changes appropriately -- **Layer management** - Properly configure which layers each pass renders diff --git a/docs/user-manual/graphics/posteffects/cameraframe/index.md b/docs/user-manual/graphics/posteffects/cameraframe/index.md index 179f6874325..b7820fa2181 100644 --- a/docs/user-manual/graphics/posteffects/cameraframe/index.md +++ b/docs/user-manual/graphics/posteffects/cameraframe/index.md @@ -126,7 +126,7 @@ Extend the `CameraFrame` by adding custom frame passes. This allows you to integ **Best for:** Multi-pass effects, advanced integrations, processing intermediate results. -### [Custom Render Passes](custom-passes) +### [Custom Render Passes](/user-manual/graphics/advanced-rendering/frame-graph/camera-passes/) Build a complete custom post-processing stack without using `CameraFrame`. This gives you full control over the entire rendering pipeline. diff --git a/docusaurus.config.js b/docusaurus.config.js index 3480685fb4b..99981746218 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -170,6 +170,8 @@ const config = { { from: ['/user-manual/supersplat/timeline/'], to: '/user-manual/supersplat/editor/timeline/' }, { from: ['/user-manual/supersplat/rendering/'], to: '/user-manual/supersplat/editor/rendering/' }, { from: ['/user-manual/supersplat/publishing/'], to: '/user-manual/supersplat/editor/publishing/' }, + // CameraFrame custom render passes moved to the Frame Graph section + { from: ['/user-manual/graphics/posteffects/cameraframe/custom-passes/'], to: '/user-manual/graphics/advanced-rendering/frame-graph/camera-passes/' }, ], createRedirects: (existingPath) => { // Create redirects from old paths prefixed with /en diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/custom-passes.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/custom-passes.md deleted file mode 100644 index ae064198aaf..00000000000 --- a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/custom-passes.md +++ /dev/null @@ -1,153 +0,0 @@ ---- -title: カスタムレンダーパス -description: CameraFrame を使わずにカメラ上で RenderPass ベースのポストスタックを構築し、パイプラインを完全に手動制御します。 ---- - -最も柔軟なアプローチは、`CameraFrame` から独立して動作する完全カスタムのレンダーパスを実装することです。レンダリングパイプラインを完全に制御でき、ゼロからカスタムのポストプロセススタックを構築できます。 - -## 概要 - -このアプローチでは `CameraFrame` は一切使いません。代わりに独自のレンダーパスを作成し、カメラの `framePasses` 配列に直接割り当てます。完全な制御が必要な場合や、カスタムのポストプロセスパイプラインを実装したい場合に適しています。 - -## 例:簡単なティントのレンダーパス - -シーンにティントをかけるカスタムレンダーパスの完全な例です。 - -```javascript -import { - Color, - PIXELFORMAT_RGBA8, - RenderPassForward, - RenderPassShaderQuad, - RenderTarget, - SEMANTIC_POSITION, - ShaderUtils, - Texture -} from 'playcanvas'; - -class RenderPassTint extends RenderPassShaderQuad { - constructor(device, sourceTexture) { - super(device); - this.sourceTexture = sourceTexture; - this.tint = Color.WHITE.clone(); - - this.shader = this.createShader(); - } - - createShader() { - return ShaderUtils.createShader(this.device, { - uniqueName: 'TintShader', - attributes: { aPosition: SEMANTIC_POSITION }, - vertexChunk: 'quadVS', - - fragmentGLSL: ` - uniform sampler2D sourceTexture; - uniform vec3 tint; - varying vec2 uv0; - - void main() { - vec4 color = texture2D(sourceTexture, uv0); - gl_FragColor = vec4(color.rgb * tint, color.a); - } - `, - - fragmentWGSL: ` - var sourceTexture: texture_2d; - var sourceTextureSampler: sampler; - uniform tint: vec3f; - varying uv0: vec2f; - - @fragment fn fragmentMain(input: FragmentInput) -> FragmentOutput { - var output: FragmentOutput; - let color: vec4f = textureSample(sourceTexture, sourceTextureSampler, uv0); - output.color = vec4f(color.rgb * uniform.tint, color.a); - return output; - } - ` - }); - } - - execute() { - this.device.scope.resolve('sourceTexture').setValue(this.sourceTexture); - this.device.scope.resolve('tint').setValue([this.tint.r, this.tint.g, this.tint.b]); - super.execute(); - } -} -``` - -## カスタムレンダーパスの設定 - -`CameraFrame` を使わずにカスタムレンダーパスを使うには: - -```javascript -// Create your scene render pass (renders the 3D scene) -const scenePass = new RenderPassForward(device, composition, scene, renderer); -scenePass.init(renderTarget); - -// Create your custom post-processing pass -const tintPass = new RenderPassTint(device, renderTarget.colorBuffer); -tintPass.init(camera.renderTarget); - -// Assign passes to the camera -camera.framePasses = [scenePass, tintPass]; -``` - -## マルチパスの例 - -複数のカスタムパスを連鎖させる例です。 - -```javascript -// Create render targets -const rt1 = new RenderTarget({ - colorBuffer: new Texture(device, { - width: 1920, height: 1080, - format: PIXELFORMAT_RGBA8 - }) -}); - -const rt2 = new RenderTarget({ - colorBuffer: new Texture(device, { - width: 1920, height: 1080, - format: PIXELFORMAT_RGBA8 - }) -}); - -// Create scene pass -const scenePass = new RenderPassForward(device, composition, scene, renderer); -scenePass.init(rt1); - -// RenderPassBlurHorizontal and RenderPassBlurVertical are illustrative custom -// passes you'd define yourself, similar to RenderPassTint above -const blurHPass = new RenderPassBlurHorizontal(device, rt1.colorBuffer); -blurHPass.init(rt2); - -const blurVPass = new RenderPassBlurVertical(device, rt2.colorBuffer); -blurVPass.init(camera.renderTarget); // Final output - -// Set the pass chain -camera.framePasses = [scenePass, blurHPass, blurVPass]; -``` - -## 参考資料 - -- Render Pass の例 — カスタムレンダーパスの完全なデモ - - - -## 用途 - -このアプローチは次のような場合に適しています。 - -- **完全カスタムのパイプライン** — レンダリング処理全体を細かく制御したいとき -- **外部システムとの連携** — サードパーティのレンダリングやエフェクトライブラリを組み込むとき -- **パフォーマンスの最適化** — 用途に合わせた最小限のパイプラインを構築するとき -- **学習と実験** — レンダリングパイプラインの動作を理解するとき -- **特殊なレンダリング** — 非標準のレンダリング手法や研究実装 - -## 重要な考慮事項 - -- **手動管理** — レンダーターゲット、テクスチャ、メモリは自分で管理する責任があります。 -- **メモリ管理** — メモリリークを避けるため、リソースの適切な解放を行ってください。 -- **クロスプラットフォーム** — WebGL と WebGPU のサポートのため、GLSL と WGSL の両方のシェーダーを用意してください。 -- **解像度の扱い** — 動的な解像度変更に適切に対応してください。 -- **レイヤー管理** — 各パスがレンダリングするレイヤーを正しく設定してください。 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/index.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/index.md index 5b39a7a0ab1..6069968b4fb 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/index.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/index.md @@ -126,7 +126,7 @@ Gaussian Splat でフォグや被写界深度を使用する場合は、[シー **向いている用途:** マルチパスエフェクト、高度な連携、中間結果の処理。 -### [カスタムレンダーパス](custom-passes) +### [カスタムレンダーパス](/user-manual/graphics/advanced-rendering/frame-graph/camera-passes/) `CameraFrame` を使わず、完全なカスタムポストプロセススタックを構築します。レンダリングパイプライン全体を完全に制御できます。 diff --git a/llms/user-manual/engine/llms.txt b/llms/user-manual/engine/llms.txt index f42c23a1980..f39baf9e94b 100644 --- a/llms/user-manual/engine/llms.txt +++ b/llms/user-manual/engine/llms.txt @@ -108,6 +108,7 @@ Use this index to write code against the engine directly. React, Web Components - [Modern Post Processing](/user-manual/graphics/posteffects/cameraframe/): The CameraFrame HDR stack: volumetric fog, bloom, SSAO, TAA, depth of field, color enhancement and grading. - [Layers](/user-manual/graphics/layers/): Controlling render order and visibility with layers and the layer composition. - [Render Targets](/user-manual/graphics/advanced-rendering/render-targets/): Rendering into an offscreen texture and using the result in the scene. +- [Frame Graph](/user-manual/graphics/advanced-rendering/frame-graph/): How a frame renders as logical passes in physical render and compute passes, and the engine's frame passes: adding passes to a camera, render passes, shader quad passes, rendering the scene into a texture, and compute passes. - [Hardware Instancing](/user-manual/graphics/advanced-rendering/hardware-instancing/): Drawing many copies of a mesh at once: instance buffer formats, mesh instance setup and shader chunks. - [Batching](/user-manual/graphics/advanced-rendering/batching/): Merging mesh instances into batch groups to reduce draw calls. - [Particles](/user-manual/graphics/particles/): Particle systems, starting them from scripts, and soft particles. diff --git a/sidebars.js b/sidebars.js index f655aab2635..79b47ceea93 100644 --- a/sidebars.js +++ b/sidebars.js @@ -892,7 +892,6 @@ const sidebars = { 'user-manual/graphics/posteffects/cameraframe/volumetric-fog', 'user-manual/graphics/posteffects/cameraframe/compose-shader', 'user-manual/graphics/posteffects/cameraframe/extending-class', - 'user-manual/graphics/posteffects/cameraframe/custom-passes', ], }, { @@ -931,6 +930,31 @@ const sidebars = { 'user-manual/graphics/advanced-rendering/multi-draw', 'user-manual/graphics/advanced-rendering/render-targets', 'user-manual/graphics/advanced-rendering/multiple-render-targets', + { + type: 'category', + label: 'Frame Graph', + link: { + type: 'doc', + id: 'user-manual/graphics/advanced-rendering/frame-graph/index', + }, + items: [ + 'user-manual/graphics/advanced-rendering/frame-graph/frame-passes', + 'user-manual/graphics/advanced-rendering/frame-graph/camera-passes', + { + type: 'category', + label: 'Render Passes', + link: { + type: 'doc', + id: 'user-manual/graphics/advanced-rendering/frame-graph/render-pass', + }, + items: [ + 'user-manual/graphics/advanced-rendering/frame-graph/render-pass-shader-quad', + 'user-manual/graphics/advanced-rendering/frame-graph/render-pass-forward', + ], + }, + 'user-manual/graphics/advanced-rendering/frame-graph/compute-pass', + ], + }, 'user-manual/graphics/advanced-rendering/dual-source-blending', 'user-manual/graphics/advanced-rendering/indirect-drawing', 'user-manual/graphics/advanced-rendering/transform-feedback', From 5ef0652d92f71ab3defb87bcab082d98b5c48872 Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Wed, 28 Oct 2026 15:29:25 +0000 Subject: [PATCH 2/8] docs: document CameraFrame effects, lens flare and dynamic sky Replaces the compose shader and extending CameraFrame pages with two pages on CameraFrameEffect: Custom Effects covers the composition and its compose slots, the built-in effects with their ids, slots and order, writing, registering and parameterizing an effect, debug views, chunk overrides and effect scripts for the Editor. Effects with Passes covers the stages of the camera frame, the frame resources, creating and rebuilding passes, per-frame values and resolving the scene. Lens Flare and Dynamic Sky get pages of their own. The camera frame overview lists the effects added since, and its Custom Post Processing section points to the new pages and to Camera Passes, in English and Japanese. The removed pages redirect to their replacements, and the sidebar and the Engine LLM index link the new pages. --- .../posteffects/cameraframe/compose-shader.md | 76 ------ .../posteffects/cameraframe/custom-effects.md | 249 ++++++++++++++++++ .../posteffects/cameraframe/dynamic-sky.md | 107 ++++++++ .../posteffects/cameraframe/effect-passes.md | 167 ++++++++++++ .../cameraframe/extending-class.md | 67 ----- .../graphics/posteffects/cameraframe/index.md | 26 +- .../posteffects/cameraframe/lens-flare.md | 97 +++++++ docusaurus.config.js | 3 + .../posteffects/cameraframe/compose-shader.md | 76 ------ .../cameraframe/extending-class.md | 67 ----- .../graphics/posteffects/cameraframe/index.md | 18 +- llms/user-manual/engine/llms.txt | 2 + sidebars.js | 6 +- 13 files changed, 654 insertions(+), 307 deletions(-) delete mode 100644 docs/user-manual/graphics/posteffects/cameraframe/compose-shader.md create mode 100644 docs/user-manual/graphics/posteffects/cameraframe/custom-effects.md create mode 100644 docs/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md create mode 100644 docs/user-manual/graphics/posteffects/cameraframe/effect-passes.md delete mode 100644 docs/user-manual/graphics/posteffects/cameraframe/extending-class.md create mode 100644 docs/user-manual/graphics/posteffects/cameraframe/lens-flare.md delete mode 100644 i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/compose-shader.md delete mode 100644 i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/extending-class.md diff --git a/docs/user-manual/graphics/posteffects/cameraframe/compose-shader.md b/docs/user-manual/graphics/posteffects/cameraframe/compose-shader.md deleted file mode 100644 index 3b228bc77bf..00000000000 --- a/docs/user-manual/graphics/posteffects/cameraframe/compose-shader.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -title: Customizing the Compose Shader -description: Override CameraFrame compose chunks to inject uniforms and pixel effects into the final fullscreen combine pass. ---- - -The simplest way to add custom post-effects is by customizing the final compose pass, where all effects are combined and rendered to the backbuffer. This approach is ideal when you don't need additional render passes and want to modify the final output. - -## Overview - -You can inject custom shader code by overriding shader chunks before creating the `CameraFrame`. The compose pass provides three empty chunks specifically for customization: - -- `composeDeclarationsPS` - Add custom uniform declarations and helper functions -- `composeMainStartPS` - Add code at the start of the main function -- `composeMainEndPS` - Add code at the end of the main function, just before the final output - -## Example: Simple Pixelation Effect - -Here's a complete example showing how to add a pixelation effect: - -```javascript -import { CameraFrame, ShaderChunks, SHADERLANGUAGE_GLSL, SHADERLANGUAGE_WGSL } from 'playcanvas'; - -// Override compose shader chunks before creating CameraFrame -const shaderChunks = ShaderChunks.get(graphicsDevice, SHADERLANGUAGE_GLSL); - -shaderChunks.set('composeDeclarationsPS', ` - uniform float pixelSize; -`); - -shaderChunks.set('composeMainEndPS', ` - // Apply pixelation effect - vec2 pixelatedUV = floor(uv0 / pixelSize) * pixelSize; - color = getLinear(texture2D(sceneTexture, pixelatedUV)); -`); - -// For WebGPU, also set WGSL chunks -const wgslChunks = ShaderChunks.get(graphicsDevice, SHADERLANGUAGE_WGSL); -wgslChunks.set('composeDeclarationsPS', ` - uniform pixelSize: f32; -`); - -wgslChunks.set('composeMainEndPS', ` - let pixelatedUV: vec2f = floor(input.uv0 / uniform.pixelSize) * uniform.pixelSize; - color = getLinear(textureSample(sceneTexture, sceneTextureSampler, pixelatedUV)); -`); - -// Now create the CameraFrame -const cameraFrame = new CameraFrame(app, cameraEntity.camera); -cameraFrame.update(); - -// Set the custom uniform value -app.on('update', () => { - graphicsDevice.scope.resolve('pixelSize').setValue(0.005); -}); -``` - -## Important Notes - -- **Global Application**: Changes to shader chunks are applied globally to all `CameraFrame` instances. -- **WebGPU Support**: Remember to provide both GLSL and WGSL shader chunks for cross-platform compatibility. -- **Timing**: Shader chunks must be set before creating the `CameraFrame` instance. - -## Resources - -- Custom Compose Shader Example - Complete working demonstration - - - -## Use Cases - -This approach is ideal for: - -- Adding simple screen-space effects (vignette, color adjustments, distortion) -- Post-processing that doesn't require additional textures or render passes -- Quick prototyping of visual effects -- Effects that operate on the final composed image diff --git a/docs/user-manual/graphics/posteffects/cameraframe/custom-effects.md b/docs/user-manual/graphics/posteffects/cameraframe/custom-effects.md new file mode 100644 index 00000000000..5e6ae60892c --- /dev/null +++ b/docs/user-manual/graphics/posteffects/cameraframe/custom-effects.md @@ -0,0 +1,249 @@ +--- +title: Custom Effects +description: "Write post-processing effects for CameraFrame with CameraFrameEffect: compose slots, the shader chunk, parameters, registering effects, debug views, and effect scripts for the Editor." +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +A [`CameraFrame`](https://api.playcanvas.com/engine/classes/CameraFrame.html) renders its post-processing with **effects**, objects derived from [`CameraFrameEffect`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html). The built-in effects are effects too: `cameraFrame.bloom`, `cameraFrame.vignette` and the others are instances of effect classes. You can write effects of your own, and register them with a camera frame next to the built-in ones. + +Most effects contribute a shader chunk to the **composition**: a single pass, drawing the final image, in which all these chunks run, one after another. Contributing to it never costs an additional full-screen pass. An effect needing work the composition cannot do in a single sample per pixel, such as a blur at a lower resolution, owns passes as well, see [Effects with Passes](effect-passes.md). + +## Compose slots + +The composition is a shader which runs once for each pixel of the output. It reads the color the scene rendered at the pixel, and converts it to the color written to the screen. The scene renders linear HDR colors, proportional to the light and of any brightness. Tone mapping maps them to the range a display can show, and gamma correction then encodes them for the display. The chunks of the effects modify the color along the way. + +A **compose slot** is a point of this conversion, at which the composition calls the chunks of the effects. Each effect applies at one of the four slots, which decides the color it receives, and so what it can do. The effects at a slot run one after another, each receiving the color the effect before it returned: + +```mermaid +flowchart TB + subgraph Hdr[linear HDR] + direction LR + Scene([scene texture]) --> SlotScene[["COMPOSESLOT_SCENE
fringing, sharpening"]] --> Alpha[alpha set aside] --> SlotHdr[["COMPOSESLOT_HDR
depth of field, SSAO, bloom,
color enhance, grading"]] + end + ToneMapping[tone mapping] + subgraph Ldr[display space] + direction LR + SlotLdr[["COMPOSESLOT_LDR
color LUT, vignette"]] --> Gamma[gamma correction] --> SlotOutput[[COMPOSESLOT_OUTPUT]] --> Output([output]) + end + Hdr --> ToneMapping --> Ldr +``` + +The slots are shown with the built-in effects applying at them, in their order. + +| Slot | The color it receives | Use it for | +| ---- | --------------------- | ---------- | +| `COMPOSESLOT_SCENE` | The scene color as sampled, with its alpha, before the effects of the later slots modify it. | Effects changing the scene image itself, which the other effects build on: those sampling the scene texture again around the pixel, such as a sharpening, and those changing the alpha. At a later slot, sampling the scene texture again would discard what the effects before them did. | +| `COMPOSESLOT_HDR` | The linear HDR color, before tone mapping. | Effects working with light, such as adding the light of a bloom, or darkening by the ambient occlusion. | +| `COMPOSESLOT_LDR` | The tone mapped color, in the 0-1 range of the display, before gamma correction. | Effects on the look of the final image, such as a vignette, or a color lookup table. | +| `COMPOSESLOT_OUTPUT` | The final values written to the screen, after gamma correction. | Effects on the exact values written, such as a dithering. | + +`COMPOSESLOT_SCENE` and `COMPOSESLOT_HDR` are both in linear HDR, with no conversion between them. All the effects at `COMPOSESLOT_SCENE` run before any effect at `COMPOSESLOT_HDR`, and only they can change the alpha of the output. + +The entry function of a chunk at `COMPOSESLOT_SCENE` takes the color as a `vec4`, with its alpha, and the uv, and returns the color as a `vec4`. At the other slots, it takes the color as a `vec3` and the uv, and returns the color as a `vec3`. + +### The built-in effects + +A camera frame registers its built-in effects in this order, which is the order they apply in at their slot. Each is identified by its id, which [`getEffect`](https://api.playcanvas.com/engine/classes/CameraFrame.html#geteffect) and [`insertEffectBefore`](https://api.playcanvas.com/engine/classes/CameraFrame.html#inserteffectbefore) take: + +| Id | Effect | Compose slot | +| -- | ------ | ------------ | +| `taa` | Temporal anti-aliasing, `cameraFrame.taa` | None, it only owns passes | +| `fringing` | Fringing, `cameraFrame.fringing` | `COMPOSESLOT_SCENE` | +| `cas` | Sharpening, `cameraFrame.rendering.sharpness` | `COMPOSESLOT_SCENE` | +| `dof` | Depth of field, `cameraFrame.dof` | `COMPOSESLOT_HDR` | +| `ssao` | Ambient occlusion, `cameraFrame.ssao` | `COMPOSESLOT_HDR`, with the `SSAOTYPE_COMBINE` type | +| `volumetricFog` | Volumetric fog, `cameraFrame.volumetricFog` | None, it only owns passes | +| `bloom` | Bloom, `cameraFrame.bloom` | `COMPOSESLOT_HDR` | +| `colorEnhance` | Color enhancement, `cameraFrame.colorEnhance` | `COMPOSESLOT_HDR` | +| `grading` | Color grading, `cameraFrame.grading` | `COMPOSESLOT_HDR` | +| `colorLut` | Color lookup table, `cameraFrame.colorLUT` | `COMPOSESLOT_LDR` | +| `vignette` | Vignette, `cameraFrame.vignette` | `COMPOSESLOT_LDR` | + +The effects you add apply after the built-in effects at their slot, in the order they are added. This includes the other effects of the engine: the [lens flare](lens-flare.md), `lensFlare`, applies at `COMPOSESLOT_HDR` after the color grading, while the dynamic sky, SMAA and auto exposure only own passes. To apply an effect before one of the built-in effects, register it with `insertEffectBefore`, see [Registering effects](#registering-effects). For example, an effect inserted before `bloom` acts on the scene before the light of the bloom is added, and the color grading then applies to its result. + +## Writing an effect + +An effect is constructed with the graphics device, its **id**, and its declarations: the compose slot it applies at, and its shader chunk in GLSL and WGSL. The chunk declares the uniforms the effect sets, and its entry function, named `apply`, which the composition calls at the slot of the effect. This chunk draws the image as dots: + + + + +```glsl +uniform float pixelationTilePixels; +uniform float pixelationIntensity; + +vec3 applyPixelation(vec3 color, vec2 uv) { + vec2 tileUV = vec2(pixelationTilePixels) * sceneTextureSize.zw; + vec2 centerUv = (floor(uv / tileUV) + 0.5) * tileUV; + + vec2 local = (uv - centerUv) / tileUV; + float dist = length(local); + float edge = fwidth(dist) * 1.5; + float mask = 1.0 - smoothstep(0.35, 0.35 + edge, dist); + return mix(color, color * mask, pixelationIntensity); +} +``` + + + + +```wgsl +uniform pixelationTilePixels: f32; +uniform pixelationIntensity: f32; + +fn applyPixelation(color: vec3f, uv: vec2f) -> vec3f { + let tileUV = vec2f(uniform.pixelationTilePixels) * uniform.sceneTextureSize.zw; + let centerUv = (floor(uv / tileUV) + vec2f(0.5, 0.5)) * tileUV; + + let local = (uv - centerUv) / tileUV; + let dist = length(local); + let edge = fwidth(dist) * 1.5; + let mask = 1.0 - smoothstep(0.35, 0.35 + edge, dist); + return mix(color, color * mask, uniform.pixelationIntensity); +} +``` + + + + +The effect class declares its parameters as fields, and applies them in `update`: + +```javascript +class PixelationEffect extends CameraFrameEffect { + // the size of one tile, in screen pixels + tilePixels = 8; + + // the blend between the original image (0) and the pixelated one (1) + intensity = 0.5; + + constructor(device) { + super(device, 'pixelation', { + slot: COMPOSESLOT_LDR, + glsl: pixelationGLSL, + wgsl: pixelationWGSL + }); + } + + // at zero intensity, the effect leaves the image as it is, so it is left out of the shader + get active() { + return this.enabled && this.intensity > 0; + } + + update() { + this.setUniform('pixelationTilePixels', this.tilePixels); + this.setUniform('pixelationIntensity', this.intensity); + } +} +``` + +An effect driven by data needs no class: construct a `CameraFrameEffect` with its declarations, and set its uniforms directly. + +### What the chunk can read + +Every chunk can read what the composition provides: the scene color, `sceneTexture`, and `sceneTextureSize`, holding the width, the height, 1 / width and 1 / height of the scene texture. Use the size for anything depending on the resolution, such as texel offsets, pixel sizes or the aspect ratio. + +An effect which lists `FRAMERESOURCE_DEPTH` in its [`requires`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#requires) option can also read the depth of the scene, with `getLinearScreenDepth(uv)`. + +## Registering effects + +Register an effect with a camera frame with [`addEffect`](https://api.playcanvas.com/engine/classes/CameraFrame.html#addeffect). Effects at the same compose slot run in the order they are registered, so an effect added after the built-in vignette, which is at `COMPOSESLOT_LDR` too, applies after it. To place an effect before another one, register it with [`insertEffectBefore`](https://api.playcanvas.com/engine/classes/CameraFrame.html#inserteffectbefore), with that effect, or its id, see [The built-in effects](#the-built-in-effects): + +```javascript +const pixelation = new PixelationEffect(app.graphicsDevice); +cameraFrame.addEffect(pixelation); + +// or, to apply it before the vignette +cameraFrame.insertEffectBefore(pixelation, 'vignette'); + +cameraFrame.update(); +``` + +[`getEffect`](https://api.playcanvas.com/engine/classes/CameraFrame.html#geteffect) returns a registered effect by its id, and [`removeEffect`](https://api.playcanvas.com/engine/classes/CameraFrame.html#removeeffect) removes it. + +Like the rest of the camera frame, an effect is configured when [`CameraFrame#update`](https://api.playcanvas.com/engine/classes/CameraFrame.html#update) is called: changes to its parameters, and adding or removing it, take effect at the next update. The camera frame does not own the effects you add: removing an effect, or destroying the camera frame, does not destroy it, so destroy it with [`destroy`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#destroy) once it is no longer used. + +## Parameters + +An effect applies its parameters in [`update`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#update), which the camera frame calls on each `CameraFrame#update` while the effect is active: + +- [`setUniform`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#setuniform) sets the value of a uniform the chunk reads. Like a material parameter, the value is stored by reference, so an array or a texture can be updated in place. +- [`setDefine`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#setdefine) sets a define on the compose shader, for variants of the chunk. Changing a define rebuilds the compose shader, so use defines for settings which change rarely, and uniforms for the others. Fixed defines can be set once, in the constructor. + +[`enabled`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#enabled) turns an effect on and off. An effect which does nothing for some values of its parameters, such as a zero intensity, overrides [`active`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#active), so its chunk is left out of the compose shader then. + +The few effects which depend on values changing every frame, such as the time, implement [`frameUpdate`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#frameupdate). It is called each frame the effect takes part in, with the values of that frame, including the time since the last frame, `dt`, and the sizes the scene renders at: + +```javascript +frameUpdate(frame) { + this.time += frame.dt; + this.setUniform('waveTime', this.time); +} +``` + +An effect adapting the exposure of the camera, as the auto exposure does, returns a scale from [`cameraExposureScale`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#cameraexposurescale). + +## Debug views + +An effect can provide debug views, which display an intermediate result in place of the output of the camera. List their names in the `debugViews` option, and implement a `debug()` function for each in the chunk, returning the color to display: + +```javascript +super(device, 'pixelation', { + slot: COMPOSESLOT_LDR, + glsl, wgsl, + debugViews: ['pixelationMask'] +}); +``` + +Display a view by setting it as the debug view of the camera frame: + +```javascript +cameraFrame.debug = 'pixelationMask'; +cameraFrame.update(); +``` + +## Overriding the chunk of an effect + +The chunk of an effect can be overridden by name, like the built-in shader chunks: a chunk set in [`ShaderChunks`](https://api.playcanvas.com/engine/classes/ShaderChunks.html) under `composePS` replaces the source of the effect. This works for the built-in effects too: + +```javascript +ShaderChunks.get(app.graphicsDevice, SHADERLANGUAGE_GLSL).set('composePixelationPS', myPixelationGLSL); +ShaderChunks.get(app.graphicsDevice, SHADERLANGUAGE_WGSL).set('composePixelationPS', myPixelationWGSL); +``` + +While an effect is active, its id in upper snake case is defined in the compose shader, `PIXELATION` here, should another chunk need to know. + +## Effects in the Editor + +To use an effect in the Editor, write a script deriving from `CameraFrameEffectScript`, from [`camera-frame-effect.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/camera-frame-effect.mjs), and add it to the camera entity, next to the [`cameraFrame` script](/user-manual/graphics/linear-workflow/hdr-rendering/#cameraframe-in-the-editor). The script declares the parameters of the effect as attributes, creates the effect in `createEffect`, and copies the attributes into it in `updateEffect`: + +```javascript +import { CameraFrameEffectScript } from './camera-frame-effect.mjs'; + +class PixelationEffectScript extends CameraFrameEffectScript { + static scriptName = 'pixelationEffect'; + + /** + * @attribute + * @range [0, 1] + */ + intensity = 0.5; + + createEffect(device) { + return new PixelationEffect(device); + } + + updateEffect(effect) { + effect.intensity = this.intensity; + } +} +``` + +The effect is registered with the camera frame as soon as both scripts exist, follows the enabled state of the script, and is destroyed with it. To expose the debug views of the effect, override the `debugView` getter of the script. The scripts of the [lens flare](lens-flare.md), the SMAA and the auto exposure effects in the engine repository are complete examples. + +## Example + +A pixelation effect, at `COMPOSESLOT_LDR`, applied after the built-in vignette. + + diff --git a/docs/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md b/docs/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md new file mode 100644 index 00000000000..f1c19c09b50 --- /dev/null +++ b/docs/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md @@ -0,0 +1,107 @@ +--- +title: Dynamic Sky +description: "The CameraFrame dynamic sky effect: an analytic day sky, the night sky, flat and volumetric clouds, a sun light kept in sync, and the lighting of the scene generated from the sky." +--- + +The [`DynamicSkyEffect`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html) renders the sky of a camera in place of the sky of the scene: a day sky of an analytic atmosphere lit by the sun, a night sky of stars and the moon, and optionally clouds drifting with the wind. The position of the sun sets the time of day, and the effect can keep a directional light in sync with it. It also generates the lighting of the scene from its sky. + +The dynamic sky is not a built-in effect of the camera frame: create it, and add it to a camera frame with [`addEffect`](https://api.playcanvas.com/engine/classes/CameraFrame.html#addeffect): + +```javascript +const sky = new DynamicSkyEffect(app.graphicsDevice); +sky.light = sunEntity.light; +sky.cloudType = CLOUDTYPE_VOLUMETRIC; +cameraFrame.addEffect(sky); +cameraFrame.update(); +``` + +Like all effects, its settings apply on the next [`CameraFrame#update`](https://api.playcanvas.com/engine/classes/CameraFrame.html#update). A day and night cycle moving the sun calls it every frame: + +```javascript +let time = 0; +app.on('update', (dt) => { + time += dt; + sky.elevation = Math.sin(time * 0.1) * 60; + cameraFrame.update(); +}); +``` + +## The sun and the time of day + +The [`azimuth`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#azimuth) and the [`elevation`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#elevation) of the sun, in degrees, set the time of day. An elevation below 0 places the sun below the horizon, which gives the night sky. [`rotation`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#rotation) turns the whole sky around the vertical axis, so the sky, the sun and the moon turn together. + +The effect keeps the directional light assigned to its [`light`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#light) in sync with the sky: its direction, color and intensity, and so the shadows it casts. By day the light shines from the sun at [`sunIntensity`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#sunintensity), dimmed by the clouds, and at night it fades to the moonlight, at [`moonIntensity`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#moonintensity) and in the [`moonColor`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#mooncolor). The sky scales with `sunIntensity` too, so it sets the brightness of the whole day, to match the exposure of the camera. + +## The day sky + +| Property | Description | +| -------- | ----------- | +| `turbidity` | The haziness of the atmosphere, 1-20. Higher values give a milkier sky and a larger glow around the sun. | +| `rayleigh` | The amount of the Rayleigh scattering, 0-4, setting the blueness of the sky. | +| `mieCoefficient` | The amount of the Mie scattering, 0-0.1, setting the brightness of the glow around the sun. | +| `mieDirectionalG` | How tightly the glow hugs the sun, 0-1. | +| `luminance` | The brightness of the sky, relative to the light of the sun. At 1, the sky lights the scene as a clear sky does, so a single exposure suits the sky and the sunlit ground. | +| `twilightGlow` | The strength of the warm glow at the horizon while the sun is just below it. | + +## The night sky + +| Property | Description | +| -------- | ----------- | +| `nightColor`, `nightBrightness` | The color and the brightness of the night sky. | +| `starBrightness` | The brightness of the stars. 0 disables them. | +| `starDensity` | The fraction of the cells of the sky holding a star, 0-1. | +| `starSize` | The scale of the size of the stars. | +| `moonDirection` | The direction toward the moon, turned by the rotation of the sky. | +| `moonSize` | The angular radius of the moon disc, in degrees. | +| `moonGlow` | The brightness of the moon disc. | + +## Clouds + +The sky has no clouds by default. Set [`cloudType`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#cloudtype) to add them: + +- `CLOUDTYPE_FLAT`: a single flat layer of noise, which is cheap. +- `CLOUDTYPE_VOLUMETRIC`: volumetric clouds, ray marched through a layer of 3D noise. They are the expensive part of the sky, and render to a texture of their own, at a reduced resolution. Their noise is generated on the CPU when they are first used. + +| Property | Description | +| -------- | ----------- | +| `cloudCoverage` | The fraction of the sky covered by clouds, 0-1, from a clear sky to overcast. | +| `cloudSoftness` | The softness of the clouds, 0.01-1, from dense clouds with crisp edges to translucent, wispy ones. | +| `cloudOpacity` | The opacity of the clouds, 0-1. | +| `cloudBrightness` | The brightness of the sunlight falling on the clouds. | +| `cloudNightBrightness` | The brightness of the moonlight on the clouds at night. | +| `cloudSize` | The distance in meters over which the cloud pattern repeats. Larger values give larger clouds. | +| `cloudHeight` | The altitude of the cloud layer, in meters. | +| `cloudThickness` | The thickness of the layer of the volumetric clouds, in meters. | +| `cloudSunDimming` | The fraction of the sunlight the clouds block at the full coverage, dimming the light. | +| `windSpeed`, `windDirection` | The speed the clouds drift with, in meters per second, and the direction they drift toward, in degrees. The clouds drift with the time of the application. | + +Two properties set the cost of the volumetric clouds: + +- [`cloudQuality`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#cloudquality), 0-1, sets the number of their steps through the cloud layer and toward the light. The cost grows about linearly, doubling from 0.5 to 1. Lower values give less defined edges and a flatter shading. +- [`cloudResolution`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#cloudresolution), 0.25-1, is the resolution the clouds render at, relative to the scene. The clouds are soft, so a reduced resolution saves most of their cost with little loss of detail. + +## Lighting the scene + +While its [`lighting`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#lighting) is enabled, the effect generates the lighting of the scene from its sky: the environment atlas lighting the materials, assigned to [`Scene#envAtlas`](https://api.playcanvas.com/engine/classes/Scene.html#envatlas). Only one effect should do so, the one of the main camera. + +Generating the lighting has a cost, so the effect generates it again only when the sky has changed enough: + +- [`lightingThreshold`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#lightingthreshold) is the angle the sun moves by before the lighting is generated again. +- [`lightingCloudThreshold`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#lightingcloudthreshold) is the distance the clouds drift by, as a fraction of their size, before it is generated again. 0, the default, disables it. +- [`lightingInterpolation`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#lightinginterpolation) blends the lighting from its previous update to its latest one, so it follows a gradually changing sky smoothly, instead of changing at once. +- [`bakeLighting`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#bakelighting) generates the lighting again before the next frame, for a change it does not regenerate for on its own. + +[`lightingResolution`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#lightingresolution) is the resolution of the sky the lighting is generated from, and [`atlasSize`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#atlassize) the size of the environment atlas. A larger atlas gives sharper reflections on glossy surfaces. + +The cameras without the effect, such as the cameras rendering planar reflections, render the sky of the scene from that environment atlas: a still image of this sky. + +## Limitations + +- The effect is not supported in XR. +- The sky does not work together with the [volumetric fog](volumetric-fog.md) of the camera frame: the clouds cast no shadows into the fog, and the fog is not lit by the light of the sky. + +## Example + +An analytic atmosphere over a day and night cycle, with stars, the moon and volumetric clouds drifting with the wind, lighting the scene and keeping the sun light in sync with the sky. + + diff --git a/docs/user-manual/graphics/posteffects/cameraframe/effect-passes.md b/docs/user-manual/graphics/posteffects/cameraframe/effect-passes.md new file mode 100644 index 00000000000..549f46dfc16 --- /dev/null +++ b/docs/user-manual/graphics/posteffects/cameraframe/effect-passes.md @@ -0,0 +1,167 @@ +--- +title: Effects with Passes +description: "Write CameraFrame effects which own frame passes: the stages of the camera frame, frame resources, creating and rebuilding passes, per-frame values, and resolving the scene." +--- + +A [custom effect](custom-effects.md) can own [frame passes](/user-manual/graphics/advanced-rendering/frame-graph/frame-passes/), for work the composition cannot do in a single sample per pixel: a blur at a lower resolution, rendering into the scene, or accumulating the frames over time. The built-in bloom, depth of field and temporal anti-aliasing are effects with passes. + +The camera frame asks the effect for its passes, hands it the **frame resources** it requires, such as the scene depth, and runs its passes at the **stages** of the frame they belong to. The effect writes the passes like any other render pass, typically as [shader quad passes](/user-manual/graphics/advanced-rendering/frame-graph/render-pass-shader-quad/), and its chunk can read what they render. An effect owning passes does not need a compose slot. + +## The stages of the camera frame + +A camera frame renders the passes of its effects at five stages, between the passes rendering the scene: + +```mermaid +flowchart TB + subgraph Scene[rendering the scene] + direction LR + Prepass[depth
prepass] --> PreScene[[preScene]] --> Opaque[opaque
scene] --> Grab[copy of the
scene color] --> PostOpaque[[postOpaque]] --> Transparent[transparent
scene] + end + subgraph Resolving[resolving the scene] + direction LR + PostScene[[postScene]] --> Resolve[[resolve]] --> Half[half resolution
scene] --> PostResolve[[postResolve]] + end + subgraph Output[the output] + direction LR + Compose[composition] --> After[layers after the
composition, such
as the UI] + end + Scene --> Resolving --> Output +``` + +The stages, in double-bordered boxes, are where the passes of the effects run. + +| Stage | Runs | Use it for | Effects of the engine | +| ----- | ---- | ---------- | --------------------- | +| `preScene` | After the depth prepass, when one renders, before the scene. | Rendering textures which the materials of the scene read. | Ambient occlusion with the `SSAOTYPE_LIGHTING` type, dynamic sky | +| `postOpaque` | After the opaque geometry, the skybox and the copy of the scene color, before the transparent geometry. | Rendering into the scene, under the transparent geometry. | None | +| `postScene` | After the scene, before it is resolved. | Rendering into the scene. | Ambient occlusion with the `SSAOTYPE_COMBINE` type, volumetric fog | +| `resolve` | Resolving the scene. | Passes which read the scene color and render a new one, such as an anti-aliasing, see [Resolving the scene](#resolving-the-scene). | TAA, SMAA | +| `postResolve` | After the scene is resolved, and its half resolution copy is made, before the composition. | Processing the final scene color, such as a bloom. | Depth of field, bloom, lens flare, auto exposure | + +At each stage, the passes of the effects run in the order the effects are registered: the built-in effects in the order listed in [The built-in effects](custom-effects.md#the-built-in-effects), and after them the effects added to the camera frame, in the order they are added, unless inserted before another effect with `insertEffectBefore`. + +## Frame resources + +An effect lists the frame resources it needs in its [`requires`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#requires) option, and the camera frame provides them while the effect is active: + +| Resource | Provides | +| -------- | -------- | +| `FRAMERESOURCE_DEPTH` | `resources.depth`, the scene depth. | +| `FRAMERESOURCE_PREPASSDEPTH` | `resources.prepassDepth`, the scene depth rendered by a depth prepass, before the scene renders, for passes at the `preScene` stage. | +| `FRAMERESOURCE_SCENECOLORHALF` | `resources.sceneColorHalf`, a half resolution copy of the resolved scene color. | +| `FRAMERESOURCE_SCENECOLORGRAB` | `resources.sceneColorGrab`, the handle of a copy of the opaque scene color, which the shaders sample as `uSceneColorMap` from the `postOpaque` stage. | +| `FRAMERESOURCE_SCENETARGET` | `resources.sceneTarget`, the render target the scene renders to, for passes rendering into the scene. | + +The resources stay the same objects for the life of the passes, so they can be handed to the passes once. Their textures are resized with the canvas, so do not keep their sizes. An effect whose requirements depend on a parameter overrides `requires` as a getter. + +How the scene depth is stored depends on what renders it, so a shader reads it with the `getLinearScreenDepth` function of the `screenDepthPS` chunk, given the defines describing the storage. Pass the defines of the depth to the shader, and add its key to the unique name of the shader, so that differently stored depths do not share a compiled shader: + +```javascript +const { depth } = resources; +this.shader = ShaderUtils.createShader(device, { + uniqueName: `InkOutlineShader-${depth.key}`, + attributes: { aPosition: SEMANTIC_POSITION }, + vertexChunk: 'quadVS', + fragmentDefines: depth.defines, + fragmentGLSL: inkGLSL, // includes "screenDepthPS", and calls getLinearScreenDepth(uv) + fragmentWGSL: inkWGSL +}); +``` + +## Creating the passes + +The effect creates its passes in [`createPasses`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#createpasses), from the resources it requires, adding each to the array of the stage it runs at, in the order they execute. One effect can run passes at several stages. It releases them in [`destroyPasses`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#destroypasses): + +```javascript +class InkOutlineEffect extends CameraFrameEffect { + width = 3; + + _pass = null; + + constructor(device) { + super(device, 'inkOutline', { + requires: [FRAMERESOURCE_SCENECOLORGRAB, FRAMERESOURCE_SCENETARGET, FRAMERESOURCE_DEPTH] + }); + } + + createPasses(resources, passes) { + // the pass draws the lines into the scene, after the opaque geometry + this._pass = new RenderPassInkOutline(this.device, this.cameraFrame.cameraComponent, resources.depth); + this._pass.init(resources.sceneTarget); + passes.postOpaque.push(this._pass); + } + + destroyPasses() { + this._pass?.destroy(); + this._pass = null; + } + + // called by cameraFrame.update(): apply the parameters to the pass + update() { + this._pass.width = this.width; + } +} +``` + +A pass rendering into a render target of the effect sizes it with [`resizeSource`](/user-manual/graphics/advanced-rendering/frame-graph/render-pass/#resizing-the-render-target), for example to follow the half resolution scene. The chunk of the effect reads what the passes render through a uniform it sets with `setUniform`, as a texture is stored by reference. + +### When the passes are rebuilt + +The camera frame creates the passes of its effects when it builds its own passes: on the first `CameraFrame#update`, and again each time it rebuilds them, after releasing the previous ones with `destroyPasses`. It rebuilds them when the options of the camera frame change, when an effect owning passes is added, removed, enabled or disabled, and when the requirements of such an effect, or its [`buildKey`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#buildkey), change. + +`buildKey` returns a string which changes whenever the passes need recreating: the parameters which change the passes, rather than their uniforms. Parameters the passes pick up in `update` do not belong in it: + +```javascript +buildKey() { + // a pass more renders at the high quality + return `${this.highQuality}`; +} +``` + +`destroyPasses` is also called when the effect has no passes, for example for an effect which was inactive when the passes were built, and by `destroy`. So destroy only what exists, and clear the references, as the example does. + +## Values of each frame + +The resources do not change from frame to frame. Values which do, such as the resolution the scene renders at, or the resolved scene color, which the temporal anti-aliasing alternates between two textures, are handed to [`frameUpdate`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#frameupdate) every frame, before any pass renders: + +```javascript +frameUpdate(frame) { + // the size of a pixel of the scene render target, which the pass renders into + this._pass.texelWidth = 1 / frame.renderWidth; + this._pass.texelHeight = 1 / frame.renderHeight; +} +``` + +## Resolving the scene + +An effect whose passes run at the `resolve` stage replaces the scene color, which the rest of the frame reads, with what its passes render. The camera frame calls its [`resolveScene`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#resolvescene) every frame, before `frameUpdate`, with the scene being resolved. The effect hands its passes the scene color the effects before it resolved, and replaces it with the texture its passes render to this frame: + +```javascript +resolveScene(scene) { + this._pass.setSourceTexture(scene.texture); + scene.texture = this._pass.outputTexture; +} +``` + +An effect resolving the scene to another resolution, such as an upscaler, also sets the `width` and the `height` of the scene. + +## TypeScript + +In TypeScript, the parameters of an overridden method are not typed from the base class. Import the types of the hooks, [`CameraFrameEffectResources`](https://api.playcanvas.com/engine/interfaces/CameraFrameEffectResources.html), [`CameraFrameEffectPasses`](https://api.playcanvas.com/engine/interfaces/CameraFrameEffectPasses.html), [`CameraFrameEffectScene`](https://api.playcanvas.com/engine/interfaces/CameraFrameEffectScene.html) and [`CameraFrameEffectContext`](https://api.playcanvas.com/engine/interfaces/CameraFrameEffectContext.html), to annotate them: + +```typescript +import { CameraFrameEffect } from 'playcanvas'; +import type { CameraFrameEffectPasses, CameraFrameEffectResources } from 'playcanvas'; + +class BlurEffect extends CameraFrameEffect { + createPasses(resources: CameraFrameEffectResources, passes: CameraFrameEffectPasses) { + // ... + } +} +``` + +## Example + +An ink outline effect draws lines where the depth or the brightness of the opaque scene changes abruptly. Its pass runs at the `postOpaque` stage, reading the copy of the opaque scene color and the scene depth, and drawing into the scene, so the transparent geometry renders over the lines. + + diff --git a/docs/user-manual/graphics/posteffects/cameraframe/extending-class.md b/docs/user-manual/graphics/posteffects/cameraframe/extending-class.md deleted file mode 100644 index 0c65f613018..00000000000 --- a/docs/user-manual/graphics/posteffects/cameraframe/extending-class.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: Extending FramePassCameraFrame Class -description: Extend FramePassCameraFrame to insert passes, reorder work, and tap intermediate scene textures in the HDR stack. ---- - -For more advanced customization, you can extend the `FramePassCameraFrame` class to add custom passes or modify the rendering pipeline, and wire it in by overriding [`CameraFrame.createRenderPass`](https://api.playcanvas.com/engine/classes/CameraFrame.html#createrenderpass). This approach gives you full control over the pass creation and ordering while still leveraging the built-in [`CameraFrame`](https://api.playcanvas.com/engine/classes/CameraFrame.html) effects. - -## Overview - -By extending `FramePassCameraFrame`, you can: - -- Add custom passes to the pipeline -- Modify or replace existing passes -- Control the order of pass execution -- Access intermediate textures from the rendering pipeline - -## Example: Adding a Custom Render Pass - -This example demonstrates how to extend the `FramePassCameraFrame` class to insert a custom render pass into the rendering pipeline. By overriding the `createPasses()` method, you can add your own processing step that operates on the scene texture. The `collectPasses()` method then controls where in the pipeline your custom pass executes—in this case, right before the final compose pass. This is useful when you need to apply custom effects that require their own render pass, such as edge detection, custom blur effects, or any processing that needs intermediate render targets. - -```javascript -import { CameraFrame, FramePassCameraFrame } from 'playcanvas'; - -class CustomFramePassCameraFrame extends FramePassCameraFrame { - createPasses(options) { - // Call the base implementation to create standard passes - super.createPasses(options); - - // Add your custom render pass - this.customPass = new MyCustomRenderPass(this.device, this.sceneTexture); - - // You can also modify or replace existing passes here - } - - collectPasses() { - // Override to control pass ordering - const passes = super.collectPasses(); - - // Insert your custom pass at the desired position - // This example inserts it before the compose pass - passes.splice(passes.indexOf(this.composePass), 0, this.customPass); - - return passes; - } -} - -// Use your custom class by extending CameraFrame -class MyCameraFrame extends CameraFrame { - createRenderPass() { - return new CustomFramePassCameraFrame(this.app, this, this.cameraComponent, this.options); - } -} - -// Create an instance of your custom CameraFrame -const cameraFrame = new MyCameraFrame(app, cameraEntity.camera); -cameraFrame.update(); -``` - -## Use Cases - -This approach is suitable for: - -- Adding render passes that require their own render targets -- Implementing complex multi-pass effects -- Integrating third-party rendering techniques -- Advanced users who need fine-grained control over the pipeline -- Effects that need to process intermediate results from other passes diff --git a/docs/user-manual/graphics/posteffects/cameraframe/index.md b/docs/user-manual/graphics/posteffects/cameraframe/index.md index b7820fa2181..222f0a643ca 100644 --- a/docs/user-manual/graphics/posteffects/cameraframe/index.md +++ b/docs/user-manual/graphics/posteffects/cameraframe/index.md @@ -13,7 +13,8 @@ The `CameraFrame` enables advanced rendering techniques including: - **[Volumetric Fog](volumetric-fog.md)** - Height fog and light shafts lit by directional, omni, and spot lights - **SSAO (Screen Space Ambient Occlusion)** - Enhances depth perception by simulating ambient light occlusion - **Depth of Field (DoF)** - Mimics camera focus effects, blurring objects outside the focal plane -- **Temporal Anti-Aliasing (TAA)** - Reduces visual artifacts by smoothing jagged edges over time +- **Temporal Anti-Aliasing (TAA)** - Reduces visual artifacts by smoothing jagged edges over time, and can upscale a scene rendered at a lower resolution +- **SMAA (Subpixel Morphological Anti-Aliasing)** - Smooths jagged edges within a single frame, without jittering the camera or ghosting - **Vignette** - Blends the image edges towards a configurable color using `vignette.color` - **Color Grading** - Adjusts brightness, contrast, saturation, and color tint for stylistic effects - **[Color Enhance](#color-enhance)** - Adjusts shadows, highlights, midtones, vibrance, and dehaze in HDR @@ -21,6 +22,11 @@ The `CameraFrame` enables advanced rendering techniques including: - **Fringing** - Chromatic aberration effect that simulates color channel separation - **Tone Mapping** - Controls how HDR colors are mapped to displayable range - **Sharpness** - Enhances image sharpness to counteract blurriness from TAA or upscaling +- **Auto Exposure** - Adapts the exposure of the camera to the brightness of what it sees +- **[Lens Flare](lens-flare.md)** - Flares of lights, fading as their light is covered by the scene +- **[Dynamic Sky](dynamic-sky.md)** - A day and night sky with clouds, lighting the scene and keeping the sun light in sync + +Apart from tone mapping, each of these is an effect, a [`CameraFrameEffect`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html). Most are built into the camera frame, such as `cameraFrame.bloom` and `cameraFrame.vignette`. SMAA, auto exposure, lens flare and dynamic sky are added to it with [`addEffect`](https://api.playcanvas.com/engine/classes/CameraFrame.html#addeffect). You can also write [effects of your own](custom-effects.md). ## Setup and Usage @@ -112,22 +118,22 @@ For Gaussian splats, see [scene depth requirements](volumetric-fog.md#gaussian-s ## Custom Post Processing -Modern post-processing can be customized and extended in several ways. Choose the approach that best fits your needs: +Modern post-processing can be extended in three ways. Choose the approach that best fits your needs: -### [Customizing the Compose Shader](compose-shader) +### [Custom Effects](custom-effects.md) -Extend the `CameraFrame` by adding effects to the final compose pass only. This is the simplest approach when you don't need additional render passes. +Add effects of your own to the composition of the `CameraFrame`, next to the built-in effects. An effect contributes a shader chunk to the single compose pass, so it costs no additional full-screen pass. -**Best for:** Simple screen-space effects, color adjustments, quick prototyping. +**Best for:** Screen-space effects, color adjustments, stylized looks. -### [Extending FramePassCameraFrame Class](extending-class) +### [Effects with Passes](effect-passes.md) -Extend the `CameraFrame` by adding custom frame passes. This allows you to integrate additional rendering techniques while leveraging built-in effects. +Write effects which own frame passes, rendering at the stages of the camera frame, such as before the scene, between its opaque and transparent geometry, or after it. -**Best for:** Multi-pass effects, advanced integrations, processing intermediate results. +**Best for:** Multi-pass effects, effects at a lower resolution, rendering into the scene, effects accumulating frames over time. -### [Custom Render Passes](/user-manual/graphics/advanced-rendering/frame-graph/camera-passes/) +### [Camera Passes](/user-manual/graphics/advanced-rendering/frame-graph/camera-passes/) -Build a complete custom post-processing stack without using `CameraFrame`. This gives you full control over the entire rendering pipeline. +Replace the rendering of a camera with frame passes of your own, without using `CameraFrame`. This gives you full control over the whole rendering of the camera. **Best for:** Complete custom pipelines, specialized rendering, maximum flexibility. diff --git a/docs/user-manual/graphics/posteffects/cameraframe/lens-flare.md b/docs/user-manual/graphics/posteffects/cameraframe/lens-flare.md new file mode 100644 index 00000000000..ce0077dfbcd --- /dev/null +++ b/docs/user-manual/graphics/posteffects/cameraframe/lens-flare.md @@ -0,0 +1,97 @@ +--- +title: Lens Flare +description: "The CameraFrame lens flare effect: shape libraries, flare looks and their elements, following lights, occlusion and fading, and the lens flare scripts for the Editor." +--- + +The [`LensFlareEffect`](https://api.playcanvas.com/engine/classes/LensFlareEffect.html) draws the flares of lights, or of any positions, in the scene. Each flare is a [`LensFlare`](https://api.playcanvas.com/engine/classes/LensFlare.html) following its light, whose **look** places shapes along the axis from the light through the center of the screen. The flares fade as their light is covered by the scene, as it leaves the screen, and for a spot light as the camera leaves its cone. + +The lens flare is not a built-in effect of the camera frame: create it, and add it to a camera frame with [`addEffect`](https://api.playcanvas.com/engine/classes/CameraFrame.html#addeffect). It draws the flares at a lower resolution than the scene, and adds them to the scene in linear HDR space, before tone mapping. + +## Setting up the flares + +The effect draws the shapes of all its flares from one **shape library**: textures, which it copies into a texture array, and which the elements of the flares reference by name. A shape is a texture with the shape in its color channels and its coverage in alpha, such as a white shape on a transparent background. + +```javascript +const lensFlare = new LensFlareEffect(app.graphicsDevice); +lensFlare.shapes = { + shapes: { + glow: glowAsset.resource, + star: starAsset.resource, + disc: discAsset.resource + } +}; +cameraFrame.addEffect(lensFlare); + +// a flare following the sun: a glow, a star and a train of four ghosts +const sun = new LensFlare({ + elements: [ + { shape: 'glow', size: 0.5, color: [1, 0.9, 0.7], intensity: 0.5 }, + { shape: 'star', size: 1.2, intensity: 0.4 }, + { shape: 'disc', offset: 0.5, size: 0.08, color: [0.4, 0.8, 1], count: 4, spread: 0.4 } + ] +}, { light: sunEntity.light }); +lensFlare.add(sun); + +cameraFrame.update(); +``` + +The shapes are stored at the same resolution, 512 pixels by default, set with the `resolution` of the shape library. Create a `LensFlare` for each light that flares, and register it with the effect with [`add`](https://api.playcanvas.com/engine/classes/LensFlareEffect.html#add). [`remove`](https://api.playcanvas.com/engine/classes/LensFlareEffect.html#remove) unregisters it. + +## Looks + +The look of a flare is the list of its elements, drawn in order. A look is plain data, shared by any number of flares, and can be loaded from a JSON asset. Each element places a shape on the axis of the flare: + +| Property | Description | +| -------- | ----------- | +| `shape` | The name of the shape, in the shape library of the effect. | +| `offset` | The position along the axis: 0 at the source, 1 at the center of the screen, 2 mirrored across the center, negative beyond the source. Defaults to 0. | +| `size` | The height of the element, as a fraction of the screen height. Defaults to 0.1. | +| `aspect` | The width of the element relative to its height. Defaults to 1. | +| `rotation` | The rotation of the element, in degrees. Defaults to 0. | +| `autoRotate` | Whether the element rotates to align with the axis, for streaks. Defaults to false. | +| `color` | The color of the element, as a `Color` or an array of three numbers. Defaults to white. | +| `intensity` | The brightness of the element. Defaults to 1. | +| `count` | The number of copies of the element, spread along the axis around its offset: a train of ghosts. Defaults to 1. | +| `spread` | The distance along the axis the copies are spread over, in the units of the offset. Defaults to 0. | + +The elements of a look are built into the effect when `CameraFrame#update` is called. To change a look, assign it to the [`look`](https://api.playcanvas.com/engine/classes/LensFlare.html#look) of the flare again, the same object included, and update the camera frame. + +## The source of a flare + +The second argument of the `LensFlare` constructor selects what the flare follows: + +- `light`: a light component. A directional light gives the direction of the source, and an omni or a spot light its position. A spot light also limits the flare to its cone. +- `node`: a node whose position is the source, for a flare without a light. +- `position`: a world position, for a source with neither a light nor a node. +- `direction`: a world direction towards a source infinitely far away, for a flare without a light. + +The color of the light tints the flare, and its intensity scales it, unless [`useLightColor`](https://api.playcanvas.com/engine/classes/LensFlare.html#uselightcolor) or [`useLightIntensity`](https://api.playcanvas.com/engine/classes/LensFlare.html#uselightintensity) is turned off. The [`color`](https://api.playcanvas.com/engine/classes/LensFlare.html#color), [`intensity`](https://api.playcanvas.com/engine/classes/LensFlare.html#intensity) and [`scale`](https://api.playcanvas.com/engine/classes/LensFlare.html#scale) of the flare tint, brighten and scale all its elements. + +## Occlusion and fading + +The effect measures how much of the source of each flare the scene covers, by testing the scene depth around it, and fades the flare by it. The properties of the flare shape this: + +- [`occlusionRadius`](https://api.playcanvas.com/engine/classes/LensFlare.html#occlusionradius) is the radius around the source the depth is tested in. A source partly covered fades partly. +- [`occlusionFade`](https://api.playcanvas.com/engine/classes/LensFlare.html#occlusionfade) is the time the measured visibility takes to follow a change, smoothing the fade. +- [`occlusionOffset`](https://api.playcanvas.com/engine/classes/LensFlare.html#occlusionoffset) moves the depth test from the source towards the camera, so the geometry of a lamp does not cover its own light. +- [`edgeFade`](https://api.playcanvas.com/engine/classes/LensFlare.html#edgefade) is the width of the band at the edge of the screen the flare fades out over, as its source leaves the screen, and [`allowOffScreen`](https://api.playcanvas.com/engine/classes/LensFlare.html#allowoffscreen) keeps the flare drawing while its source is just outside the screen. +- [`fadeStart`](https://api.playcanvas.com/engine/classes/LensFlare.html#fadestart) and [`fadeEnd`](https://api.playcanvas.com/engine/classes/LensFlare.html#fadeend) fade out the flare of a positional source with its distance from the camera. + +The properties of a flare are live: changes apply to the next frame rendered, without updating the camera frame. + +## The effect + +The [`intensity`](https://api.playcanvas.com/engine/classes/LensFlareEffect.html#intensity) of the effect sets the brightness of all its flares, and 0 disables it. The flares are drawn at a resolution relative to the scene, given by [`scale`](https://api.playcanvas.com/engine/classes/LensFlareEffect.html#scale), between 0.25 and 1, half of it by default. A lower scale is cheaper, and softens the flares. + +## Lens flares in the Editor + +The [`lens-flare.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/lens-flare.mjs) script file provides two scripts: + +- `lensFlareEffect`, on the camera entity next to the [`cameraFrame` script](/user-manual/graphics/linear-workflow/hdr-rendering/#cameraframe-in-the-editor), adds the effect to its camera frame, and holds the shape library: the texture assets of the shapes, their names and their resolution. +- `lensFlare`, on an entity, gives it a flare, with the elements of its look and the other properties of the flare as attributes. With a light component on the entity, the flare follows the light, and otherwise the position of the entity. + +## Example + +The sun of a procedural sky has a large flare with a diffraction burst and a train of ghosts, the lamps a small glow, and the spot lights flare only while the camera is inside their cone. + + diff --git a/docusaurus.config.js b/docusaurus.config.js index 99981746218..1fa422ceaa4 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -172,6 +172,9 @@ const config = { { from: ['/user-manual/supersplat/publishing/'], to: '/user-manual/supersplat/editor/publishing/' }, // CameraFrame custom render passes moved to the Frame Graph section { from: ['/user-manual/graphics/posteffects/cameraframe/custom-passes/'], to: '/user-manual/graphics/advanced-rendering/frame-graph/camera-passes/' }, + // CameraFrame compose shader customization and FramePassCameraFrame subclassing replaced by effects + { from: ['/user-manual/graphics/posteffects/cameraframe/compose-shader/'], to: '/user-manual/graphics/posteffects/cameraframe/custom-effects/' }, + { from: ['/user-manual/graphics/posteffects/cameraframe/extending-class/'], to: '/user-manual/graphics/posteffects/cameraframe/effect-passes/' }, ], createRedirects: (existingPath) => { // Create redirects from old paths prefixed with /en diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/compose-shader.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/compose-shader.md deleted file mode 100644 index 4d595ded2ce..00000000000 --- a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/compose-shader.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -title: Compose シェーダーのカスタマイズ -description: CameraFrame の compose チャンクを上書きして、最終フルスクリーン結合パスにユニフォームとピクセル効果を注入します。 ---- - -カスタムポストエフェクトを追加する最も簡単な方法は、すべてのエフェクトを合成してバックバッファへ出力する最終の compose パスをカスタマイズすることです。追加のレンダーパスが不要で、最終出力だけを変更したい場合に適しています。 - -## 概要 - -`CameraFrame` を作成する前にシェーダーチャンクを上書きすることで、カスタムシェーダーコードを注入できます。compose パスには、カスタマイズ用に空のチャンクが 3 つ用意されています。 - -- `composeDeclarationsPS` — カスタムユニフォーム宣言とヘルパー関数を追加 -- `composeMainStartPS` — `main` 関数の先頭にコードを追加 -- `composeMainEndPS` — 最終出力の直前、`main` 関数の末尾にコードを追加 - -## 例:簡単なピクセル化エフェクト - -ピクセル化エフェクトを追加する完全な例です。 - -```javascript -import { CameraFrame, ShaderChunks, SHADERLANGUAGE_GLSL, SHADERLANGUAGE_WGSL } from 'playcanvas'; - -// Override compose shader chunks before creating CameraFrame -const shaderChunks = ShaderChunks.get(graphicsDevice, SHADERLANGUAGE_GLSL); - -shaderChunks.set('composeDeclarationsPS', ` - uniform float pixelSize; -`); - -shaderChunks.set('composeMainEndPS', ` - // Apply pixelation effect - vec2 pixelatedUV = floor(uv0 / pixelSize) * pixelSize; - color = getLinear(texture2D(sceneTexture, pixelatedUV)); -`); - -// For WebGPU, also set WGSL chunks -const wgslChunks = ShaderChunks.get(graphicsDevice, SHADERLANGUAGE_WGSL); -wgslChunks.set('composeDeclarationsPS', ` - uniform pixelSize: f32; -`); - -wgslChunks.set('composeMainEndPS', ` - let pixelatedUV: vec2f = floor(input.uv0 / uniform.pixelSize) * uniform.pixelSize; - color = getLinear(textureSample(sceneTexture, sceneTextureSampler, pixelatedUV)); -`); - -// Now create the CameraFrame -const cameraFrame = new CameraFrame(app, cameraEntity.camera); -cameraFrame.update(); - -// Set the custom uniform value -app.on('update', () => { - graphicsDevice.scope.resolve('pixelSize').setValue(0.005); -}); -``` - -## 重要な注意 - -- **グローバルな適用**:シェーダーチャンクへの変更は、すべての `CameraFrame` インスタンスにグローバルに適用されます。 -- **WebGPU のサポート**:クロスプラットフォーム互換のため、GLSL と WGSL の両方のシェーダーチャンクを用意してください。 -- **タイミング**:シェーダーチャンクは、`CameraFrame` インスタンスを作成する前に設定する必要があります。 - -## 参考資料 - -- Custom Compose Shader の例 — 動作する完全なデモ - - - -## 用途 - -この方法は次のような場合に適しています。 - -- 単純なスクリーンスペースエフェクト(ビネット、色調整、歪み)を追加する -- 追加のテクスチャやレンダーパスを必要としないポストプロセスを行う -- 視覚効果の迅速なプロトタイピングを行う -- 最終的に合成された画像に対して動作するエフェクトを適用する diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/extending-class.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/extending-class.md deleted file mode 100644 index 59bcd2e1b4c..00000000000 --- a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/extending-class.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: FramePassCameraFrame クラスの拡張 -description: FramePassCameraFrame を拡張してパスを挿入し、処理順を並べ替え、HDR スタック内の中間シーンテクスチャを利用します。 ---- - -より高度なカスタマイズとして、`FramePassCameraFrame` クラスを拡張してカスタムパスを追加したり、レンダリングパイプラインを変更したりできます。サブクラスは [`CameraFrame.createRenderPass`](https://api.playcanvas.com/engine/classes/CameraFrame.html#createrenderpass) をオーバーライドして組み込みます。この方法では、組み込みの [`CameraFrame`](https://api.playcanvas.com/engine/classes/CameraFrame.html) エフェクトを活かしつつ、パス生成と実行順を細かく制御できます。 - -## 概要 - -`FramePassCameraFrame` を拡張すると、次のことができます。 - -- パイプラインにカスタムパスを追加する -- 既存のパスを変更または置き換える -- パス実行の順序を制御する -- レンダリングパイプラインから中間テクスチャにアクセスする - -## 例:カスタムレンダーパスの追加 - -この例では、`FramePassCameraFrame` クラスを拡張してレンダリングパイプラインにカスタムレンダーパスを挿入する方法を示します。`createPasses()` メソッドをオーバーライドすると、シーンテクスチャを対象とする独自の処理ステップを追加できます。`collectPasses()` メソッドでは、カスタムパスがパイプラインのどこで実行されるかを制御します。この例では最終の compose パスの直前に挿入しています。エッジ検出、カスタムブラー、中間レンダーターゲットが必要な処理など、独自のレンダーパスが要るエフェクトに便利です。 - -```javascript -import { CameraFrame, FramePassCameraFrame } from 'playcanvas'; - -class CustomFramePassCameraFrame extends FramePassCameraFrame { - createPasses(options) { - // Call the base implementation to create standard passes - super.createPasses(options); - - // Add your custom render pass - this.customPass = new MyCustomRenderPass(this.device, this.sceneTexture); - - // You can also modify or replace existing passes here - } - - collectPasses() { - // Override to control pass ordering - const passes = super.collectPasses(); - - // Insert your custom pass at the desired position - // This example inserts it before the compose pass - passes.splice(passes.indexOf(this.composePass), 0, this.customPass); - - return passes; - } -} - -// Use your custom class by extending CameraFrame -class MyCameraFrame extends CameraFrame { - createRenderPass() { - return new CustomFramePassCameraFrame(this.app, this, this.cameraComponent, this.options); - } -} - -// Create an instance of your custom CameraFrame -const cameraFrame = new MyCameraFrame(app, cameraEntity.camera); -cameraFrame.update(); -``` - -## 用途 - -このアプローチは次のような場合に適しています。 - -- 独自のレンダーターゲットを必要とするレンダーパスを追加する -- 複数パスにわたる複雑なエフェクトを実装する -- サードパーティのレンダリング手法を組み込む -- パイプラインを細かく制御したい上級ユーザー向け -- 他のパスからの中間結果を処理する必要があるエフェクト diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/index.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/index.md index 6069968b4fb..61966b7f4bd 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/index.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/index.md @@ -112,22 +112,22 @@ Gaussian Splat でフォグや被写界深度を使用する場合は、[シー ## カスタムポストプロセス -モダンなポストプロセスは、いくつかの方法でカスタマイズ・拡張できます。ニーズに合うアプローチを選んでください。 +モダンなポストプロセスは、3 つの方法で拡張できます。ニーズに合うアプローチを選んでください。 -### [Compose シェーダーのカスタマイズ](compose-shader) +### [カスタムエフェクト](custom-effects) -最終の compose パスにだけエフェクトを追加して `CameraFrame` を拡張します。追加のレンダーパスが不要な場合に最も簡単な方法です。 +組み込みエフェクトと並べて、独自のエフェクトを `CameraFrame` の合成に追加します。エフェクトは単一の compose パスにシェーダーチャンクを追加するため、フルスクリーンパスが増えることはありません。 -**向いている用途:** 単純なスクリーンスペースエフェクト、色調整、迅速なプロトタイピング。 +**向いている用途:** スクリーンスペースエフェクト、色調整、スタイライズされた表現。 -### [FramePassCameraFrame クラスの拡張](extending-class) +### [パスを持つエフェクト](effect-passes) -カスタムフレームパスを追加して `CameraFrame` を拡張します。組み込みエフェクトを活かしつつ、追加のレンダリング手法を組み込めます。 +フレームパスを持つエフェクトを作成し、シーンの前、不透明ジオメトリと半透明ジオメトリの間、シーンの後など、カメラフレームの各ステージでレンダリングします。 -**向いている用途:** マルチパスエフェクト、高度な連携、中間結果の処理。 +**向いている用途:** マルチパスエフェクト、低解像度でのエフェクト、シーンへの描画、複数フレームを蓄積するエフェクト。 -### [カスタムレンダーパス](/user-manual/graphics/advanced-rendering/frame-graph/camera-passes/) +### [カメラパス](/user-manual/graphics/advanced-rendering/frame-graph/camera-passes/) -`CameraFrame` を使わず、完全なカスタムポストプロセススタックを構築します。レンダリングパイプライン全体を完全に制御できます。 +`CameraFrame` を使わず、カメラのレンダリングを独自のフレームパスに置き換えます。カメラのレンダリング全体を完全に制御できます。 **向いている用途:** 完全カスタムのパイプライン、特殊なレンダリング、最大限の柔軟性。 diff --git a/llms/user-manual/engine/llms.txt b/llms/user-manual/engine/llms.txt index f39baf9e94b..df7cee4411d 100644 --- a/llms/user-manual/engine/llms.txt +++ b/llms/user-manual/engine/llms.txt @@ -106,6 +106,8 @@ Use this index to write code against the engine directly. React, Web Components ### Post-processing and rendering - [Modern Post Processing](/user-manual/graphics/posteffects/cameraframe/): The CameraFrame HDR stack: volumetric fog, bloom, SSAO, TAA, depth of field, color enhancement and grading. +- [Custom Effects](/user-manual/graphics/posteffects/cameraframe/custom-effects/): Writing CameraFrame effects with CameraFrameEffect: compose slots, the shader chunk, parameters and effect scripts. +- [Effects with Passes](/user-manual/graphics/posteffects/cameraframe/effect-passes/): CameraFrame effects owning frame passes: stages, frame resources, rebuilding passes and resolving the scene. - [Layers](/user-manual/graphics/layers/): Controlling render order and visibility with layers and the layer composition. - [Render Targets](/user-manual/graphics/advanced-rendering/render-targets/): Rendering into an offscreen texture and using the result in the scene. - [Frame Graph](/user-manual/graphics/advanced-rendering/frame-graph/): How a frame renders as logical passes in physical render and compute passes, and the engine's frame passes: adding passes to a camera, render passes, shader quad passes, rendering the scene into a texture, and compute passes. diff --git a/sidebars.js b/sidebars.js index 79b47ceea93..a984715f2c3 100644 --- a/sidebars.js +++ b/sidebars.js @@ -890,8 +890,10 @@ const sidebars = { }, items: [ 'user-manual/graphics/posteffects/cameraframe/volumetric-fog', - 'user-manual/graphics/posteffects/cameraframe/compose-shader', - 'user-manual/graphics/posteffects/cameraframe/extending-class', + 'user-manual/graphics/posteffects/cameraframe/lens-flare', + 'user-manual/graphics/posteffects/cameraframe/dynamic-sky', + 'user-manual/graphics/posteffects/cameraframe/custom-effects', + 'user-manual/graphics/posteffects/cameraframe/effect-passes', ], }, { From ff3151c1790193971fa6cc317efd6444c2ce1c6c Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Wed, 28 Oct 2026 15:47:42 +0000 Subject: [PATCH 3/8] docs: document the CameraFrame auto exposure effect and the effect scripts --- .../graphics/linear-workflow/hdr-rendering.md | 14 ++++ .../posteffects/cameraframe/auto-exposure.md | 79 +++++++++++++++++++ .../graphics/posteffects/cameraframe/index.md | 56 ++++++++++++- llms/user-manual/engine/llms.txt | 3 + sidebars.js | 1 + 5 files changed, 149 insertions(+), 4 deletions(-) create mode 100644 docs/user-manual/graphics/posteffects/cameraframe/auto-exposure.md diff --git a/docs/user-manual/graphics/linear-workflow/hdr-rendering.md b/docs/user-manual/graphics/linear-workflow/hdr-rendering.md index 84677bc550d..c5ba51444e4 100644 --- a/docs/user-manual/graphics/linear-workflow/hdr-rendering.md +++ b/docs/user-manual/graphics/linear-workflow/hdr-rendering.md @@ -157,6 +157,20 @@ This integration streamlines the process of setting up complex camera effects an ![CameraFrame Script](/img/user-manual/graphics/linear-workflow/camera-frame.png) +### Effect Scripts + +The effects which are not built into the camera frame have scripts of their own. Each adds its effect to the camera frame of the camera entity it is on, so add it to the same entity as the `cameraFrame` script: + +| Script | File | Adds | +| ------ | ---- | ---- | +| `autoExposureEffect` | [`auto-exposure.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/auto-exposure.mjs) | The [auto exposure](/user-manual/graphics/posteffects/cameraframe/auto-exposure/) | +| `smaaEffect` | [`smaa.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/smaa.mjs) | [SMAA](/user-manual/graphics/posteffects/cameraframe/#smaa) anti-aliasing | +| `lensFlareEffect` | [`lens-flare.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/lens-flare.mjs) | The [lens flare](/user-manual/graphics/posteffects/cameraframe/lens-flare/) effect, with its shape library. The `lensFlare` script from the same file gives the entities it is on a flare. | + +These scripts import [`camera-frame-effect.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/camera-frame-effect.mjs), so add it to the project next to them. It is also the base class for scripts of your own effects, see [Effects in the Editor](/user-manual/graphics/posteffects/cameraframe/custom-effects/#effects-in-the-editor). + +The effect of a script follows its enabled state, and is destroyed with it, which removes it from the camera frame. The **Debug** attribute of a script displays the debug view of its effect. It and the debug setting of the `cameraFrame` script override each other, and the last one changed is displayed. The [dynamic sky](/user-manual/graphics/posteffects/cameraframe/dynamic-sky/) has no Editor script, so set it up in code. + ## CameraFrame Tips - HDR bloom requires at least one renderable float format (e.g., RG11B10, RGBA16F, or RGBA32F). If none of these formats are supported by the device, HDR bloom is automatically disabled. diff --git a/docs/user-manual/graphics/posteffects/cameraframe/auto-exposure.md b/docs/user-manual/graphics/posteffects/cameraframe/auto-exposure.md new file mode 100644 index 00000000000..d179b708e03 --- /dev/null +++ b/docs/user-manual/graphics/posteffects/cameraframe/auto-exposure.md @@ -0,0 +1,79 @@ +--- +title: Auto Exposure +description: "The CameraFrame auto exposure effect: metering the brightness of the screen, adapting the exposure, cuts, reading the exposure and the histogram, the debug view, and the Editor script." +--- + +The [`AutoExposureEffect`](https://api.playcanvas.com/engine/classes/AutoExposureEffect.html) adapts the exposure of a camera to the brightness of what it sees, like the eye adapting to the dark or to the daylight. Every frame, it measures a histogram of the brightness of the screen, and averages the part of it between two fractions of the screen, leaving out the darkest and the brightest parts, such as deep shadows and the sun. It then changes the exposure gradually towards the one which displays that average as middle grey. + +The auto exposure is not a built-in effect of the camera frame: create it, and add it to a camera frame with [`addEffect`](https://api.playcanvas.com/engine/classes/CameraFrame.html#addeffect): + +```javascript +const autoExposure = new AutoExposureEffect(app.graphicsDevice); +cameraFrame.addEffect(autoExposure); +cameraFrame.update(); +``` + +## The exposure it adapts + +The effect adapts the exposure the camera renders with: the [exposure of the scene](https://api.playcanvas.com/engine/classes/Scene.html#exposure), or with [physical units](/user-manual/graphics/cameras/tone-mapping/#physical-exposure), the exposure of the physical camera. It scales it by up to [`minEv`](https://api.playcanvas.com/engine/classes/AutoExposureEffect.html#minev) and [`maxEv`](https://api.playcanvas.com/engine/classes/AutoExposureEffect.html#maxev) stops, so set the exposure of the camera for a typical view, and the effect adapts it from there. + +Every part of the frame which uses the exposure follows it, the tone mapping of the composition and the anti-aliasing among them. The effect needs tone mapping, as without it the composition applies no exposure: it is inactive while `rendering.toneMapping` is `TONEMAP_NONE`. + +## Metering + +The effect measures the brightness of the screen from the half resolution copy of the scene, into a histogram of 64 bins. Three properties choose the part of the screen it averages: + +| Property | Description | +| -------- | ----------- | +| `lowPercent` | The fraction of the screen, 0-1, left out of the average as its darkest part, such as deep shadows. Higher values meter the brighter parts of the screen, darkening the image. Defaults to 0.1. | +| `highPercent` | The fraction of the screen, 0-1, below which the average is taken, leaving out the brightest part above it, such as the sun and bright highlights. Defaults to 0.9. | +| `centerWeight` | How much the brightness of the center of the screen counts over its edges, 0-1. 0 meters the whole screen equally, 1 mostly its center. Defaults to 0.5. | + +## Adaptation + +| Property | Description | +| -------- | ----------- | +| `compensation` | The exposure compensation in EV: how much brighter than middle grey the average of the screen is displayed. Positive values brighten the image, negative values darken it, each step doubling or halving it. Defaults to 0. | +| `minEv` | The lowest exposure the effect adapts to, in EV relative to the exposure of the camera. -2 lets it darken the image to a quarter of its brightness, for bright scenes. Defaults to -6. | +| `maxEv` | The highest exposure the effect adapts to, in EV relative to the exposure of the camera. 2 lets it brighten the image four times, for dark scenes. Defaults to 6. | +| `speedDarkToLight` | How fast the exposure adapts when the scene gets brighter, as from a dark interior into the daylight. The exposure covers about two thirds of the change in 1 / `speedDarkToLight` seconds. Defaults to 3. | +| `speedLightToDark` | How fast the exposure adapts when the scene gets darker. The eye adapts to the dark more slowly than to the light, so this defaults to 1. | + +Like all effects, changes to these properties apply on the next [`CameraFrame#update`](https://api.playcanvas.com/engine/classes/CameraFrame.html#update). + +## Cuts + +After a cut to a different view, a gradual adaptation from the previous view looks out of place. Call [`reset`](https://api.playcanvas.com/engine/classes/AutoExposureEffect.html#reset) on a cut, and the effect adapts to the next measurement at once, ignoring the measurements of the view before the cut: + +```javascript +cameraEntity.setPosition(nextShot.position); +autoExposure.reset(); +``` + +The effect also adapts at once to its first measurement. + +## Reading the exposure + +The histogram is read back from the GPU, so the exposure follows the screen a frame or two late, which the gradual adaptation hides. Two properties give the results: + +- [`exposure`](https://api.playcanvas.com/engine/classes/AutoExposureEffect.html#exposure) is the exposure the camera renders with this frame, the exposure of the camera scaled by the adaptation. +- [`histogram`](https://api.playcanvas.com/engine/classes/AutoExposureEffect.html#histogram) is the latest histogram read back, a `Uint32Array` with the weight of each of its 64 bins, from the darkest to the brightest. It is updated in place as histograms arrive. + +## Debug view + +The `autoexposure` debug view displays the scene at the exposure the camera renders with, without the other effects, with the histogram of its brightness drawn along the bottom. The bins the average includes are drawn bright, the average in yellow, and the brightness the camera exposes as middle grey in blue. The two markers meet once the exposure has adapted. + +```javascript +cameraFrame.debug = 'autoexposure'; +cameraFrame.update(); +``` + +## Auto exposure in the Editor + +The `autoExposureEffect` script, from [`auto-exposure.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/auto-exposure.mjs), adds the effect to the camera frame of the camera entity it is on, next to the [`cameraFrame` script](/user-manual/graphics/linear-workflow/hdr-rendering/#cameraframe-in-the-editor). Its attributes are the properties of the effect, and its **Debug** attribute displays the debug view. + +## Example + +Walk from the sunlit courtyards into the dark tunnels between them, and the exposure adapts, faster to the light than to the dark. + + diff --git a/docs/user-manual/graphics/posteffects/cameraframe/index.md b/docs/user-manual/graphics/posteffects/cameraframe/index.md index 222f0a643ca..cfe6eec6a79 100644 --- a/docs/user-manual/graphics/posteffects/cameraframe/index.md +++ b/docs/user-manual/graphics/posteffects/cameraframe/index.md @@ -13,8 +13,8 @@ The `CameraFrame` enables advanced rendering techniques including: - **[Volumetric Fog](volumetric-fog.md)** - Height fog and light shafts lit by directional, omni, and spot lights - **SSAO (Screen Space Ambient Occlusion)** - Enhances depth perception by simulating ambient light occlusion - **Depth of Field (DoF)** - Mimics camera focus effects, blurring objects outside the focal plane -- **Temporal Anti-Aliasing (TAA)** - Reduces visual artifacts by smoothing jagged edges over time, and can upscale a scene rendered at a lower resolution -- **SMAA (Subpixel Morphological Anti-Aliasing)** - Smooths jagged edges within a single frame, without jittering the camera or ghosting +- **Temporal Anti-Aliasing (TAA)** - Reduces visual artifacts by smoothing jagged edges over time, and can [upscale](#taa-upscaling) a scene rendered at a lower resolution +- **[SMAA (Subpixel Morphological Anti-Aliasing)](#smaa)** - Smooths jagged edges within a single frame, without jittering the camera or ghosting - **Vignette** - Blends the image edges towards a configurable color using `vignette.color` - **Color Grading** - Adjusts brightness, contrast, saturation, and color tint for stylistic effects - **[Color Enhance](#color-enhance)** - Adjusts shadows, highlights, midtones, vibrance, and dehaze in HDR @@ -22,7 +22,7 @@ The `CameraFrame` enables advanced rendering techniques including: - **Fringing** - Chromatic aberration effect that simulates color channel separation - **Tone Mapping** - Controls how HDR colors are mapped to displayable range - **Sharpness** - Enhances image sharpness to counteract blurriness from TAA or upscaling -- **Auto Exposure** - Adapts the exposure of the camera to the brightness of what it sees +- **[Auto Exposure](auto-exposure.md)** - Adapts the exposure of the camera to the brightness of what it sees - **[Lens Flare](lens-flare.md)** - Flares of lights, fading as their light is covered by the scene - **[Dynamic Sky](dynamic-sky.md)** - A day and night sky with clouds, lighting the scene and keeping the sun light in sync @@ -65,9 +65,57 @@ cameraFrame.update(); `inner`, `outer`, and `curvature` control the shape and falloff. Setting `outer` below `inner` reverses the effect, applying it towards the center. +### TAA Upscaling {#taa-upscaling} + +`rendering.renderTargetScale`, 0.1-1, renders the scene at a lower resolution as an optimization. The post-processing runs at that resolution too, and the image is then stretched to the full resolution with bilinear filtering, before the UI renders over it at the full resolution. + +With TAA enabled, [`taa.upscale`](https://api.playcanvas.com/engine/classes/TaaEffect.html#upscale) accumulates the frames rendered at the lower resolution at the full resolution instead. The jitter of the camera places the samples of each frame at different positions within the full resolution pixels, which recovers much of the detail the lower resolution loses. The effects after the TAA, such as the bloom and the composition, then work with the full resolution scene. + +```javascript +cameraFrame.rendering.renderTargetScale = 0.5; +cameraFrame.taa.enabled = true; +cameraFrame.taa.upscale = true; +cameraFrame.rendering.sharpness = 0.5; +cameraFrame.update(); +``` + +Moving objects can blur or ghost more than with TAA alone, as the history is relied on for longer while upscaling. `rendering.sharpness` counteracts the softness TAA leaves. In the Editor, the `cameraFrame` script exposes these as its `taa.upscale`, `rendering.renderTargetScale` and `rendering.sharpness` attributes. The Temporal Anti-Aliasing example in [Examples](#examples) compares the full resolution with a lower one, upscaled with bilinear filtering or by the TAA, with and without sharpening. + +### SMAA {#smaa} + +The [`SmaaEffect`](https://api.playcanvas.com/engine/classes/SmaaEffect.html), Subpixel Morphological Anti-Aliasing, smooths the jagged edges of the geometry within a single frame. It finds the edges in the image, recognizes the shape of each aliased edge, and blends the pixels along it with their neighbors by the area the edge would cover without aliasing. Unlike TAA, it does not jitter the camera, needs no history and does not ghost, but it does not resolve detail finer than a pixel. + +SMAA is not a built-in effect of the camera frame, so add it with `addEffect`: + +```javascript +const smaa = new SmaaEffect(app.graphicsDevice); +cameraFrame.addEffect(smaa); +cameraFrame.update(); +``` + +Four properties set its quality and cost, defaulting to the high preset of the reference implementation: + +| Preset | `threshold` | `maxSearchSteps` | `maxSearchStepsDiag` | `cornerRounding` | +| ------ | ----------- | ---------------- | -------------------- | ---------------- | +| Low | 0.15 | 4 | 0 | 100 | +| Medium | 0.1 | 8 | 0 | 100 | +| High (default) | 0.1 | 16 | 8 | 25 | +| Ultra | 0.05 | 32 | 16 | 25 | + +- `threshold`, 0-0.5, is the brightness difference between neighboring pixels above which they are separated by an edge. Lower values smooth more edges at a higher cost, and can smooth texture detail along with the geometry. +- `maxSearchSteps`, 1-112, is the maximum number of steps searching for the end of a horizontal or vertical edge, each covering two pixels. The ends of longer edges are not found, which smooths them less accurately. +- `maxSearchStepsDiag`, 0-20, is the same for diagonal edges. 0 disables the detection of diagonal edges, which are then smoothed as horizontal and vertical ones. +- `cornerRounding`, 0-100, is how much the sharp corners of the geometry are rounded. 0 keeps them the sharpest, 100 smooths them like the rest of the edges. + +SMAA can be combined with multisampling, `rendering.samples`, which smooths the edges further at a higher cost. Combined with TAA, it resolves the scene after the TAA, unless inserted before it with `cameraFrame.insertEffectBefore(smaa, 'taa')`. Its debug views, `'smaaedges'` and `'smaaweights'`, show the edges it found and how much it blends the pixels across them. In the Editor, the `smaaEffect` script from [`smaa.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/smaa.mjs) adds it to the camera frame of its camera entity. + +This example compares the SMAA presets with TAA, TAA upscaling and multisampling: + + + ### Debug Views {#debug-views} -Set `cameraFrame.debug` to inspect an intermediate result: `'scene'`, `'ssao'`, `'bloom'`, `'vignette'`, `'dofcoc'` (depth-of-field circle of confusion), `'dofblur'`, or `'depth'`. +Set `cameraFrame.debug` to inspect an intermediate result: `'scene'`, `'ssao'`, `'bloom'`, `'vignette'`, `'dofcoc'` (depth-of-field circle of confusion), `'dofblur'`, or `'depth'`. The effects added to the camera frame provide their own views: `'smaaedges'` and `'smaaweights'` for [SMAA](#smaa), `'autoexposure'` for the [auto exposure](auto-exposure.md#debug-view), and `'lensflare'` for the [lens flare](lens-flare.md). ```javascript cameraFrame.debug = 'depth'; diff --git a/llms/user-manual/engine/llms.txt b/llms/user-manual/engine/llms.txt index df7cee4411d..402174c78ea 100644 --- a/llms/user-manual/engine/llms.txt +++ b/llms/user-manual/engine/llms.txt @@ -106,6 +106,9 @@ Use this index to write code against the engine directly. React, Web Components ### Post-processing and rendering - [Modern Post Processing](/user-manual/graphics/posteffects/cameraframe/): The CameraFrame HDR stack: volumetric fog, bloom, SSAO, TAA, depth of field, color enhancement and grading. +- [Auto Exposure](/user-manual/graphics/posteffects/cameraframe/auto-exposure/): The CameraFrame auto exposure: metering, adaptation speeds and limits, cuts, and reading the exposure back. +- [Lens Flare](/user-manual/graphics/posteffects/cameraframe/lens-flare/): CameraFrame lens flares: shape libraries, flare looks, following lights, occlusion and fading. +- [Dynamic Sky](/user-manual/graphics/posteffects/cameraframe/dynamic-sky/): A CameraFrame day and night sky with clouds, lighting the scene and keeping the sun light in sync. - [Custom Effects](/user-manual/graphics/posteffects/cameraframe/custom-effects/): Writing CameraFrame effects with CameraFrameEffect: compose slots, the shader chunk, parameters and effect scripts. - [Effects with Passes](/user-manual/graphics/posteffects/cameraframe/effect-passes/): CameraFrame effects owning frame passes: stages, frame resources, rebuilding passes and resolving the scene. - [Layers](/user-manual/graphics/layers/): Controlling render order and visibility with layers and the layer composition. diff --git a/sidebars.js b/sidebars.js index a984715f2c3..0d2b29b7166 100644 --- a/sidebars.js +++ b/sidebars.js @@ -890,6 +890,7 @@ const sidebars = { }, items: [ 'user-manual/graphics/posteffects/cameraframe/volumetric-fog', + 'user-manual/graphics/posteffects/cameraframe/auto-exposure', 'user-manual/graphics/posteffects/cameraframe/lens-flare', 'user-manual/graphics/posteffects/cameraframe/dynamic-sky', 'user-manual/graphics/posteffects/cameraframe/custom-effects', From 7b0dc4ad4fd03d5f54f2d8401c7afd762685e91b Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Wed, 28 Oct 2026 15:50:01 +0000 Subject: [PATCH 4/8] docs: link the translated pages by URL from the new pages, for the Japanese build --- .../graphics/advanced-rendering/frame-graph/index.md | 2 +- .../graphics/advanced-rendering/frame-graph/render-pass.md | 2 +- .../user-manual/graphics/posteffects/cameraframe/dynamic-sky.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/user-manual/graphics/advanced-rendering/frame-graph/index.md b/docs/user-manual/graphics/advanced-rendering/frame-graph/index.md index 7b0fa5e5cc7..0a4963f3bdb 100644 --- a/docs/user-manual/graphics/advanced-rendering/frame-graph/index.md +++ b/docs/user-manual/graphics/advanced-rendering/frame-graph/index.md @@ -3,7 +3,7 @@ title: Frame Graph description: "How the GPU renders a frame as a sequence of render and compute passes, and how a frame graph describes them." --- -The GPU renders into a [render target](../render-targets.md), a texture or a set of textures, or into the **back buffer**, which is displayed on the screen. Rendering into one of them, from start to end, is called a **physical render pass**. A frame takes several physical render passes, and a **frame graph** describes the steps of the frame which produce them, as **logical passes**. This page explains these concepts, and the pages in this section show how PlayCanvas implements them. +The GPU renders into a [render target](/user-manual/graphics/advanced-rendering/render-targets/), a texture or a set of textures, or into the **back buffer**, which is displayed on the screen. Rendering into one of them, from start to end, is called a **physical render pass**. A frame takes several physical render passes, and a **frame graph** describes the steps of the frame which produce them, as **logical passes**. This page explains these concepts, and the pages in this section show how PlayCanvas implements them. ## Physical render passes diff --git a/docs/user-manual/graphics/advanced-rendering/frame-graph/render-pass.md b/docs/user-manual/graphics/advanced-rendering/frame-graph/render-pass.md index 553cc1a7ec5..83529ea9f03 100644 --- a/docs/user-manual/graphics/advanced-rendering/frame-graph/render-pass.md +++ b/docs/user-manual/graphics/advanced-rendering/frame-graph/render-pass.md @@ -3,7 +3,7 @@ title: Render Passes description: "RenderPass draws into a render target or the back buffer: initializing it, resizing its render target, clearing, storing and mipmaps, and writing a pass." --- -A [`RenderPass`](https://api.playcanvas.com/engine/classes/RenderPass.html) is a [frame pass](frame-passes.md) which draws into a [render target](../render-targets.md). When it executes, its draw calls are recorded into a physical render pass for its render target: usually one of its own, which starts right before the pass executes and ends right after, or one it shares with the passes next to it which render into the same render target, see [Passes sharing a render target](#passes-sharing-a-render-target). +A [`RenderPass`](https://api.playcanvas.com/engine/classes/RenderPass.html) is a [frame pass](frame-passes.md) which draws into a [render target](/user-manual/graphics/advanced-rendering/render-targets/). When it executes, its draw calls are recorded into a physical render pass for its render target: usually one of its own, which starts right before the pass executes and ends right after, or one it shares with the passes next to it which render into the same render target, see [Passes sharing a render target](#passes-sharing-a-render-target). ## Classes derived from RenderPass diff --git a/docs/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md b/docs/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md index f1c19c09b50..f7b446bd074 100644 --- a/docs/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md +++ b/docs/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md @@ -98,7 +98,7 @@ The cameras without the effect, such as the cameras rendering planar reflections ## Limitations - The effect is not supported in XR. -- The sky does not work together with the [volumetric fog](volumetric-fog.md) of the camera frame: the clouds cast no shadows into the fog, and the fog is not lit by the light of the sky. +- The sky does not work together with the [volumetric fog](/user-manual/graphics/posteffects/cameraframe/volumetric-fog/) of the camera frame: the clouds cast no shadows into the fog, and the fog is not lit by the light of the sky. ## Example From 4697c331a58d382602cc68a0fd0d04a980c3841f Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Wed, 28 Oct 2026 16:16:27 +0000 Subject: [PATCH 5/8] docs: describe the dynamic sky in XR The sky and the flat clouds render for each XR view, while the volumetric clouds render once for the view of the camera and streak where the view of an eye extends past it. Add an XR section recommending the flat clouds in XR, and narrow the limitation to the volumetric clouds. --- .../graphics/posteffects/cameraframe/dynamic-sky.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md b/docs/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md index f7b446bd074..cab3cce7495 100644 --- a/docs/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md +++ b/docs/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md @@ -59,7 +59,7 @@ The effect keeps the directional light assigned to its [`light`](https://api.pla The sky has no clouds by default. Set [`cloudType`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#cloudtype) to add them: -- `CLOUDTYPE_FLAT`: a single flat layer of noise, which is cheap. +- `CLOUDTYPE_FLAT`: a single flat layer of noise with simple shading, which is cheap. The sky shader evaluates them for each pixel, like the rest of the sky, so they also work in [XR](#xr). - `CLOUDTYPE_VOLUMETRIC`: volumetric clouds, ray marched through a layer of 3D noise. They are the expensive part of the sky, and render to a texture of their own, at a reduced resolution. Their noise is generated on the CPU when they are first used. | Property | Description | @@ -95,9 +95,13 @@ Generating the lighting has a cost, so the effect generates it again only when t The cameras without the effect, such as the cameras rendering planar reflections, render the sky of the scene from that environment atlas: a still image of this sky. +## XR {#xr} + +In XR, the sky renders for each view, like the rest of the scene, and so do the flat clouds. The volumetric clouds render to their texture once, for the view of the camera, and each view reads them from it. Where the view of an eye extends past the view of the camera, the texture has no clouds to read, and they streak at its edge. Use `CLOUDTYPE_FLAT` clouds in XR. + ## Limitations -- The effect is not supported in XR. +- The volumetric clouds are not supported in XR, see [XR](#xr). - The sky does not work together with the [volumetric fog](/user-manual/graphics/posteffects/cameraframe/volumetric-fog/) of the camera frame: the clouds cast no shadows into the fog, and the fog is not lit by the light of the sky. ## Example From bccca255a26ba5891a8984a277a5c3fa94064777 Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Wed, 28 Oct 2026 16:16:28 +0000 Subject: [PATCH 6/8] docs: translate the frame graph and CameraFrame effects pages into Japanese Adds Japanese versions of the Frame Graph section and of the Custom Effects, Effects with Passes, Lens Flare, Dynamic Sky and Auto Exposure pages, with the headings keeping their English anchors so links into them resolve. Brings the Japanese camera frame overview and HDR Rendering pages up to date with the TAA upscaling, SMAA, debug view and effect script sections, and labels the Frame Graph and Render Passes sidebar categories. --- .../current.json | 8 + .../frame-graph/camera-passes.md | 95 +++++++ .../frame-graph/compute-pass.md | 99 +++++++ .../frame-graph/frame-passes.md | 114 ++++++++ .../advanced-rendering/frame-graph/index.md | 70 +++++ .../frame-graph/render-pass-forward.md | 56 ++++ .../frame-graph/render-pass-shader-quad.md | 169 ++++++++++++ .../frame-graph/render-pass.md | 141 ++++++++++ .../graphics/linear-workflow/hdr-rendering.md | 14 + .../posteffects/cameraframe/auto-exposure.md | 79 ++++++ .../posteffects/cameraframe/custom-effects.md | 249 ++++++++++++++++++ .../posteffects/cameraframe/dynamic-sky.md | 111 ++++++++ .../posteffects/cameraframe/effect-passes.md | 167 ++++++++++++ .../graphics/posteffects/cameraframe/index.md | 60 ++++- .../posteffects/cameraframe/lens-flare.md | 97 +++++++ 15 files changed, 1526 insertions(+), 3 deletions(-) create mode 100644 i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/camera-passes.md create mode 100644 i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/compute-pass.md create mode 100644 i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/frame-passes.md create mode 100644 i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/index.md create mode 100644 i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/render-pass-forward.md create mode 100644 i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/render-pass-shader-quad.md create mode 100644 i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/render-pass.md create mode 100644 i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/auto-exposure.md create mode 100644 i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/custom-effects.md create mode 100644 i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md create mode 100644 i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/effect-passes.md create mode 100644 i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/lens-flare.md diff --git a/i18n/ja/docusaurus-plugin-content-docs/current.json b/i18n/ja/docusaurus-plugin-content-docs/current.json index 9e0454daee1..6769d295a0d 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current.json +++ b/i18n/ja/docusaurus-plugin-content-docs/current.json @@ -15,6 +15,14 @@ "message": "高度なレンダリング", "description": "The label for category Advanced Rendering in sidebar userManualSidebar" }, + "sidebar.userManualSidebar.category.Frame Graph": { + "message": "フレームグラフ", + "description": "The label for category Frame Graph in sidebar userManualSidebar" + }, + "sidebar.userManualSidebar.category.Render Passes": { + "message": "レンダーパス", + "description": "The label for category Render Passes in sidebar userManualSidebar" + }, "sidebar.userManualSidebar.category.2D": { "message": "2D", "description": "The label for category 2D in sidebar userManualSidebar" diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/camera-passes.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/camera-passes.md new file mode 100644 index 00000000000..9daf178c39e --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/camera-passes.md @@ -0,0 +1,95 @@ +--- +title: カメラパス +description: "カメラへのフレームパスの追加:カメラの前後にレンダリングするパス、カメラのレンダリングを独自のパスに置き換える方法、そしてパスの所有者。" +--- + +各カメラは、[フレームグラフ](index.md)のうち自身が担当する部分を、[フレームパス](frame-passes.md)のグループとしてレンダリングします。デフォルトでは、これらはカメラのレイヤーをレンダリングするためにエンジンが作成するパスです。その前後に独自のパスを追加したり、それらを独自のパスに置き換えたりできます。 + +## カメラのパス {#the-passes-of-a-camera} + +各カメラは、パスを次の 3 つのグループに分けてこの順にレンダリングし、次のカメラが開始する前に 3 つすべてが完了します。 + +```mermaid +flowchart LR + CameraBefore[camera.beforePasses] --> Rendering[カメラのレンダリング:
デフォルトのパス、または camera.framePasses] --> CameraAfter[camera.afterPasses] +``` + +- [`beforePasses`](#before-passes) は、カメラがシーンをレンダリングする前にレンダリングされます。 +- カメラのレンダリングは、デフォルトのレンダリングか、[`framePasses`](#replacing-the-cameras-rendering) 内のパスのいずれかです。 +- [`afterPasses`](#after-passes) は、カメラがレンダリングするすべてのものの後にレンダリングされます。 + +## 前のパス {#before-passes} + +[`CameraComponent#beforePasses`](https://api.playcanvas.com/engine/classes/CameraComponent.html#beforepasses) 内のパスは、カメラのディレクショナルライトのシャドウマップの後、カメラがシーンをレンダリングする前にレンダリングされます。同じフレーム内で、シーンのマテリアルが読み取るテクスチャにレンダリングしたり、シーンが結果を使用するコンピュートシェーダーを実行したりするために使用します。 + +前のパスには、[`RenderPassForward`](render-pass-forward.md#limitations) のような、カメラのレイヤーをレンダリングするパスを含めることはできません。フレームが複数の XR ビューをレンダリングする場合、前のパスはビューごとにレンダリングされます。ただし、コンピュートパスは例外で、その [`xrViewIndex`](compute-pass.md#xr-views) が選択するビューで実行されます。 + +## 後のパス {#after-passes} + +[`CameraComponent#afterPasses`](https://api.playcanvas.com/engine/classes/CameraComponent.html#afterpasses) 内のパスは、カメラがそのフレームでレンダリングするすべてのもの(カメラのフレームパスを含む)の後にレンダリングされます。カメラがレンダリングした結果を同じフレーム内で処理するために使用します。たとえば、カメラのカラーマップを読み取る[コンピュートパス](compute-pass.md)で処理します。フレームが複数の XR ビューをレンダリングする場合、後のパスはすべてのビューの後に 1 回だけレンダリングされます。 + +## カメラのレンダリングを置き換える {#replacing-the-cameras-rendering} + +[`CameraComponent#framePasses`](https://api.playcanvas.com/engine/classes/CameraComponent.html#framepasses) 内のパスは、カメラのデフォルトのレンダリングの代わりに、配列の順にレンダリングされます。これらを使って、カメラのレンダリング方法を決定します。つまり、カメラのどのレイヤーをどのレンダーターゲットにレンダリングするか、そしてどのパスで結果を処理するかを決めます。通常、そのうちの 1 つは [`RenderPassForward`](render-pass-forward.md) で、カメラを使ってシーンのレイヤーをレンダリングします。 + +次の例では、フォワードパスがシーンをテクスチャにレンダリングし、[シェーダークアッドパス](render-pass-shader-quad.md)がそれに色味を付けて画面に描画します。 + +```javascript +// シーンのレンダリング先となる、バックバッファと同じサイズのテクスチャ +const texture = new Texture(app.graphicsDevice, { + name: 'SceneTexture', + width: 4, + height: 4, + format: PIXELFORMAT_RGBA8, + mipmaps: false, + minFilter: FILTER_LINEAR, + magFilter: FILTER_LINEAR, + addressU: ADDRESS_CLAMP_TO_EDGE, + addressV: ADDRESS_CLAMP_TO_EDGE +}); +const renderTarget = new RenderTarget({ colorBuffer: texture, depth: true }); + +// ワールドレイヤーと UI レイヤーをテクスチャにレンダリングし、テクスチャは毎フレームバックバッファのサイズにリサイズされます +const scenePass = new RenderPassForward(app.graphicsDevice, app.scene, cameraEntity.camera); +scenePass.name = 'ScenePass'; +scenePass.init(renderTarget, { resizeSource: null }); +scenePass.addLayer(worldLayer, false); +scenePass.addLayer(uiLayer, true); + +// テクスチャに色味を付けて画面に描画します +const tintPass = new TintPass(app.graphicsDevice, texture); +tintPass.init(null); + +cameraEntity.camera.framePasses = [scenePass, tintPass]; +``` + +カメラのデフォルトのレンダリングに戻すには、`framePasses` を `null` に設定します。 + +カメラの前のパスと後のパスは、引き続きフレームパスの前後でレンダリングされます。前のパスは、カメラのレイヤーをレンダリングするフレームパスのうち最初のものの前にレンダリングされ、そのようなフレームパスが 1 つもない場合はレンダリングされません。後のパスは、すべてのフレームパスの後にレンダリングされます。 + +カメラは、レイヤーを複数のフォワードパスでレンダリングすることもできます。たとえば、不透明レイヤー、次にそれらのレンダリング結果を読み取る独自のパス、そして半透明レイヤーという順です。 + +## パスの追加と削除 {#adding-and-removing-passes} + +パスを配列に追加し、レンダリングを停止するには配列から削除します。カメラはパスを破棄しないため、パスを削除したら破棄し、その後でパスのレンダリング先を破棄してください。 + +```javascript +const pass = new MyPass(app.graphicsDevice); +pass.name = 'MyPass'; +cameraEntity.camera.afterPasses.push(pass); + +// 不要になったら、パスを削除して破棄します +const passes = cameraEntity.camera.afterPasses; +passes.splice(passes.indexOf(pass), 1); +pass.destroy(); +``` + +## 例 {#examples} + +カメラがフレームパスでレンダリングします。フォワードパスがワールドレイヤーと UI レイヤーをテクスチャにレンダリングし、シェーダークアッドパスがそれに色味を付けて画面に描画します。 + + + +カメラの後のパスに含まれるコンピュートパスが、カメラがレンダリングした色のヒストグラムを作成します。 + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/compute-pass.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/compute-pass.md new file mode 100644 index 00000000000..ce45ddb1f0f --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/compute-pass.md @@ -0,0 +1,99 @@ +--- +title: コンピュートパス +description: "ComputePass はフレーム内の特定の位置でコンピュートシェーダーをディスパッチします:カメラへの追加、シーンマップに合わせたディスパッチサイズの設定、結果のリードバック、XR ビュー。" +--- + +[`ComputePass`](https://api.playcanvas.com/engine/classes/ComputePass.html) は、[コンピュート](/user-manual/graphics/shaders/compute-shaders/)インスタンスのリストを、1 つの物理コンピュートパス内で順番にディスパッチする[フレームパス](frame-passes.md)です。コンピュート処理をフレーム内の特定の位置に配置します。たとえばカメラのレンダリング後に配置して、同じフレーム内でカメラのシーン深度マップとシーンカラーマップを処理します。 + +代わりに、呼び出した時点でコンピュートインスタンスをすぐにディスパッチするには、[`GraphicsDevice#computeDispatch`](https://api.playcanvas.com/engine/classes/GraphicsDevice.html#computedispatch) を使用します。 + +:::warning + +コンピュートシェーダーは **WebGPU** でのみサポートされています。[`GraphicsDevice#supportsCompute`](https://api.playcanvas.com/engine/classes/GraphicsDevice.html#supportscompute) を参照してください。その他のデバイスでは、パスは何もディスパッチせずに実行されます。 + +::: + +## パスの作成 {#creating-the-pass} + +ディスパッチするコンピュートインスタンスを指定してパスを作成します。インスタンスはパスの [`computes`](https://api.playcanvas.com/engine/classes/ComputePass.html#computes) 配列に保持され、後からインスタンスを追加したり削除したりできます。パスには名前を設定します。名前は GPU プロファイリングでディスパッチのラベルになります。 + +```javascript +const compute = new Compute(app.graphicsDevice, shader, 'Histogram'); +compute.setParameter('bins', histogramBuffer); + +const pass = new ComputePass(app.graphicsDevice, [compute]); +pass.name = 'HistogramPass'; +``` + +## フレームへのパスの追加 {#adding-the-pass-to-a-frame} + +カメラのレンダリング前またはレンダリング後にディスパッチするには、パスをカメラの [`beforePasses`](https://api.playcanvas.com/engine/classes/CameraComponent.html#beforepasses) または [`afterPasses`](https://api.playcanvas.com/engine/classes/CameraComponent.html#afterpasses) に追加します。別のパスの 前のパスまたは 後のパスに追加することもできます。独自の [`framePasses`](camera-passes.md#replacing-the-cameras-rendering) でレンダリングするカメラでは、パスをその中に含めて、リスト内の位置でディスパッチすることもできます。たとえば、シーンをテクスチャにレンダリングするパスと、コンピュートシェーダーが書き込んだ内容を使用するパスの間です。カメラの 後のパスにあるパスは、同じフレームでカメラがレンダリングした内容を読み取ります。 + +```javascript +cameraEntity.camera.afterPasses.push(pass); +``` + +パスは 1 フレームに最大 1 回実行されます。 + +## カメラがレンダリングした内容の読み取り {#reading-what-the-camera-rendered} + +カメラの 後のパスにあるコンピュートパスは、同じフレームでカメラがレンダリングした深度マップとカラーマップを読み取れます。[`requestSceneDepthMap`](https://api.playcanvas.com/engine/classes/CameraComponent.html#requestscenedepthmap) と [`requestSceneColorMap`](https://api.playcanvas.com/engine/classes/CameraComponent.html#requestscenecolormap) でカメラにマップを要求し、[`setSceneDepthMap`](https://api.playcanvas.com/engine/classes/Compute.html#setscenedepthmap) と [`setSceneColorMap`](https://api.playcanvas.com/engine/classes/Compute.html#setscenecolormap) でコンピュートインスタンスにアタッチします。 + +```javascript +cameraEntity.camera.requestSceneColorMap(true); +compute.setSceneColorMap(cameraEntity.camera.sceneColorMapHandle); +``` + +`CameraFrame` でレンダリングするカメラでのマップの要求方法と、コンピュートシェーダーでの読み取り方法については、[シーン深度マップとカラーマップ](/user-manual/graphics/shaders/compute-shaders/#scene-depth-and-color-maps)を参照してください。 + +## ディスパッチ {#the-dispatch} + +各コンピュートインスタンスは、そのインスタンスに設定されたディスパッチサイズとパラメーターでディスパッチされます。これらはフレームのレンダリング前か、ディスパッチ直前の [`onBefore`](https://api.playcanvas.com/engine/classes/FramePass.html#onbefore) で設定します。`onBefore` は、シェーダーが書き込むバッファの準備(たとえばクリア)にも使用します。 + +[前述のとおり](#reading-what-the-camera-rendered)アタッチした、カメラのシーンカラーマップの各テクセルを処理するシェーダーは、カラーマップのサイズでディスパッチされます。そのハンドルは、カメラが最後にレンダリングしたマップのサイズを持ちます。このサイズは、カメラがレンダースケールでレンダリングする場合や、独自のレンダーターゲットにレンダリングする場合は、キャンバスのサイズとは異なります。深度マップのハンドルも同じ [`width`](https://api.playcanvas.com/engine/classes/SceneDepthMapHandle.html#width) と [`height`](https://api.playcanvas.com/engine/classes/SceneDepthMapHandle.html#height) を持ちます。 + +```javascript +pass.onBefore = () => { + // カメラが直前にレンダリングしたカラーマップのテクセルごとに 1 スレッド + const { width, height } = cameraEntity.camera.sceneColorMapHandle; + compute.setupDispatchThreads(width, height); + histogramBuffer.clear(); +}; +``` + +## 結果のリードバック {#reading-the-results-back} + +結果は [`onAfter`](https://api.playcanvas.com/engine/classes/FramePass.html#onafter) で読み取ります。そこで要求した読み取りは、ディスパッチの直後に記録されるためです。データは数フレーム後に届きます。 + +```javascript +pass.onAfter = () => { + histogramBuffer.read().then((data) => { + // 計算から数フレーム後に届くヒストグラム + }); +}; +``` + +## XR ビュー {#xr-views} + +フレームが複数の XR ビューをレンダリングする場合、[`xrViewIndex`](https://api.playcanvas.com/engine/classes/ComputePass.html#xrviewindex) はパスを実行するビューを選択し、パスはそのビュー内の自身の位置で実行されます。すべてのビューの前に 1 回だけ実行するには `-1` を使用します。コンピュートインスタンスは 1 フレームに最大 1 回しかディスパッチされないため、複数のビューに対してディスパッチするには、ビューごとに別々のコンピュートインスタンスを持つ `ComputePass` が必要です。カメラの 後のパスにあるパスは、すべてのビューの後に 1 回実行されます。 + +## パスの所有 {#owning-the-pass} + +パスはコンピュートインスタンスを破棄せず、カメラもパスを破棄しません。不要になったら、パスを削除してから、パスとコンピュートインスタンスを破棄します。 + +```javascript +const passes = cameraEntity.camera.afterPasses; +passes.splice(passes.indexOf(pass), 1); +pass.destroy(); +compute.destroy(); +``` + +## 例 {#examples} + +カメラの後に実行されるコンピュートパスが、カメラがレンダリングした色のヒストグラムを作成し、それがリードバックされて画面に描画されます。 + + + +カメラの後に実行されるコンピュートパスが、カメラのシーン深度マップとシーンカラーマップを読み取り、シーンを走査するスキャナーパルスを描画します。 + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/frame-passes.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/frame-passes.md new file mode 100644 index 00000000000..b8ecdb0338b --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/frame-passes.md @@ -0,0 +1,114 @@ +--- +title: フレームパス +description: "フレームグラフの論理パスである、エンジンのフレームパス:パスのクラス、ライフサイクル、ネスト、カメラへの追加、そしてパスの所有者。" +--- + +PlayCanvas では、[フレームグラフ](index.md)の各論理パスは**フレームパス**です。フレームパスはエンジンのオブジェクトで、フレームのレンダリング時にパスの処理を記録します。エンジンは自身のフレームをこの方法でレンダリングします。シャドウマップ、各カメラのシーン、ポストプロセスはすべてフレームパスです。また、独自のフレームパスを追加して、テクスチャにレンダリングしたり、カメラのレンダリング結果を処理したり、その結果に対してコンピュートシェーダーを実行したりできます。 + +エンジンは毎フレーム、フレームパスを順番にフレームグラフへ集め、それらを順に実行しながら、必要な物理パスを開始・終了します。物理パスを自分で開始・終了することはありません。 + +## パスのクラス {#the-pass-classes} + +すべてのフレームパスは、`FramePass` またはその派生クラスのインスタンスです。2 種類の物理パスには、それぞれ派生クラスが 1 つずつ対応します。レンダリング用の `RenderPass` と、コンピュートシェーダー用の `ComputePass` です。 + +```mermaid +classDiagram + FramePass <|-- RenderPass + FramePass <|-- ComputePass +``` + +| クラス | 用途 | +| ----- | ---------- | +| [`FramePass`](https://api.playcanvas.com/engine/classes/FramePass.html) | 基底クラス。物理パスを必要としない処理、または他のフレームパスのグループ。 | +| [`RenderPass`](render-pass.md) | レンダーターゲット、またはバックバッファへのレンダリング。 | +| [`ComputePass`](compute-pass.md) | フレーム内の特定の位置でのコンピュートシェーダーのディスパッチ。 | + +`RenderPass` は論理レンダーパスで、そのドローコールを自身のレンダーターゲット用の物理レンダーパスに記録します。通常、エンジンは `RenderPass` ごとに物理レンダーパスを開始し、その後に終了します。連続する `RenderPass` オブジェクトが同じレンダーターゲットにレンダリングする場合は、代わりに単一の物理レンダーパスを共有します。[フレームグラフ](index.md#the-frame-graph)を参照してください。同様に、エンジンは `ComputePass` のディスパッチを物理コンピュートパスに記録します。 + +エンジンは、レンダーターゲットのすべてのピクセルに対するシェーダーの実行やシーンのレンダリングなど、一般的な種類の描画のために `RenderPass` からクラスを派生させています。[レンダーパス](render-pass.md#classes-derived-from-renderpass)を参照してください。 + +## フレームパスのライフサイクル {#the-lifecycle-of-a-frame-pass} + +フレームパスは、自身が含まれる各フレームで 2 つのフェーズを経ます。フレームの構築中、何もレンダリングされる前に、エンジンはパスを更新し、フレームがどのパスをレンダリングするかを決定します。その後、フレームはパスを順番にレンダリングします。 + +```mermaid +flowchart TB + subgraph Build[フレームの構築] + direction LR + Enabled{enabled?} -->|はい| FrameUpdate[frameUpdate] --> Add[前のパス、パス自身、
後のパスが追加される] + Enabled -->|いいえ| Skip([前のパスと後のパスと
ともにスキップされる]) + end + subgraph Render[フレームのレンダリング] + direction LR + Before[before] --> OnBefore[onBefore] --> ExecuteEnabled{executeEnabled?} + ExecuteEnabled -->|はい| Execute[execute] --> After[after] + ExecuteEnabled -->|いいえ| After + After --> OnAfter[onAfter] + end + Build --> Render +``` + +サブクラスは、これらのメソッドをオーバーライドして処理を行います。 + +- [`frameUpdate`](https://api.playcanvas.com/engine/classes/FramePass.html#frameupdate) は、何もレンダリングされる前に、そのフレームに向けてパスを更新します。たとえば、レンダーターゲットのリサイズ、読み取るテクスチャの設定、前のパスや後のパスの追加や削除を行います。 +- [`before`](https://api.playcanvas.com/engine/classes/FramePass.html#before) と [`after`](https://api.playcanvas.com/engine/classes/FramePass.html#after) は、パスの処理の前後に実行されます。 +- [`execute`](https://api.playcanvas.com/engine/classes/FramePass.html#execute) は処理を記録します。`RenderPass` の場合、自身のレンダーターゲット用の物理レンダーパスの中で実行されます。 + +[`onBefore`](https://api.playcanvas.com/engine/classes/FramePass.html#onbefore) と [`onAfter`](https://api.playcanvas.com/engine/classes/FramePass.html#onafter) コールバックを使うと、サブクラスを作成せずにパスにフックできます。パスの実行直前にパラメーターを設定したり、パスが書き込むバッファをクリアしたり、実行直後にパスが書き込んだ内容を読み戻したりできます。 + +```javascript +pass.onBefore = () => { + buffer.clear(); +}; + +pass.onAfter = () => { + buffer.read().then((data) => { + // 結果は数フレーム後に届きます + }); +}; +``` + +### enabled と executeEnabled {#enabled-and-executeenabled} + +パスを無効にするプロパティは 2 つあり、ライフサイクルの異なる時点で作用します。 + +- [`enabled`](https://api.playcanvas.com/engine/classes/FramePass.html#enabled) を false に設定すると、フレームの構築時に、パスはその前のパスと後のパスとともにスキップされ、`frameUpdate` は呼び出されません。フレームが構築される前に変更してください。パスの `frameUpdate` で、そのパス自身を無効にすることはできません。 +- [`executeEnabled`](https://api.playcanvas.com/engine/classes/FramePass.html#executeenabled) を false に設定すると、`execute` のみがスキップされます。パスは引き続き更新され、前のパスと後のパスもレンダリングされ、`before`、`after`、`onBefore`、`onAfter` も呼び出されます。遅くとも `frameUpdate` の中で設定してください。 + +## 前のパスと後のパス {#before-and-after-passes} + +すべてのパスは [`beforePasses`](https://api.playcanvas.com/engine/classes/FramePass.html#beforepasses) と [`afterPasses`](https://api.playcanvas.com/engine/classes/FramePass.html#afterpasses) を持ちます。これらは、パスがレンダリングされるフレームごとに、パスの直前と直後にレンダリングされます。 + +```mermaid +flowchart LR + BeforePasses[pass.beforePasses] --> Pass[pass] --> AfterPasses[pass.afterPasses] +``` + +これによりパスをネストでき、素の `FramePass` で、まとめて有効化・無効化されるパスのグループを保持できます。 + +```javascript +// 2 つのブラーパスのグループ:グループを無効にすると両方がスキップされます +const blur = new FramePass(app.graphicsDevice); +blur.name = 'Blur'; +blur.beforePasses.push(horizontalBlurPass, verticalBlurPass); + +blur.enabled = false; +``` + +## カメラへのパスの追加 {#adding-passes-to-a-camera} + +カメラは[前のパス](camera-passes.md#before-passes)、次にカメラ自身のレンダリング(独自の[フレームパス](camera-passes.md#replacing-the-cameras-rendering)に置き換えることができます)、そして[後のパス](camera-passes.md#after-passes)の順にレンダリングします。[カメラパス](camera-passes.md)を参照してください。 + +## パスを単独でレンダリングする {#rendering-a-pass-on-its-own} + +[`render`](https://api.playcanvas.com/engine/classes/FramePass.html#render) を呼び出すと、パスをフレームの外でレンダリングすることもできます。たとえば、テクスチャに一度だけレンダリングする場合です。`before`、`onBefore`、`execute`、`after`、`onAfter` は実行されますが、フレームによる準備は行われません。`frameUpdate` は呼び出されず、前のパスと後のパスもレンダリングされません。 + +```javascript +pass.render(); +``` + +## パスの所有 {#owning-passes} + +パスが自動的に破棄されることはありません。カメラは自身のリスト内のパスを破棄せず、パスも自身の前のパスと後のパスを破棄しません。パスを作成したコードが、追加先のリストからパスを削除した後に [`destroy`](https://api.playcanvas.com/engine/classes/FramePass.html#destroy) を呼び出して破棄します。破棄したパスは、それ以降使用しないでください。また、パスは、レンダーターゲットやシェーダーなど、渡されたものも破棄しません。 + +[`name`](https://api.playcanvas.com/engine/classes/FramePass.html#name) で設定するパスの名前は、GPU プロファイリングやグラフィックスデバッガーでパスを識別するために使われます。デフォルトの名前はクラス名で、ミニファイされたビルドでは短縮されるため、明示的に設定してください。 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/index.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/index.md new file mode 100644 index 00000000000..22907b36dda --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/index.md @@ -0,0 +1,70 @@ +--- +title: フレームグラフ +description: "GPU がフレームを一連のレンダーパスとコンピュートパスとしてレンダリングする仕組みと、フレームグラフによるそれらの記述方法。" +--- + +GPU は、テクスチャまたはテクスチャのセットである[レンダーターゲット](/user-manual/graphics/advanced-rendering/render-targets/)か、画面に表示される**バックバッファ**にレンダリングします。そのいずれかへの開始から終了までのレンダリングを、**物理レンダーパス**と呼びます。1 つのフレームには複数の物理レンダーパスが必要で、**フレームグラフ**は、それらを生成するフレームのステップを**論理パス**として記述します。このページではこれらの概念を説明し、このセクションの各ページでは PlayCanvas がそれらをどのように実装しているかを示します。 + +## 物理レンダーパス {#physical-render-passes} + +レンダーターゲットは 1 つ以上のカラーバッファと、オプションで深度バッファとステンシルバッファを保持します。これらを**アタッチメント**と呼びます。バックバッファも同じアタッチメントを持ち、それらはキャンバスによって提供されます。物理レンダーパスは、次の 3 つのステップでこれらのアタッチメントにレンダリングします。 + +1. **開始**:アタッチメントを特定の値でクリアするか、すでに格納されている内容を読み込みます。 +2. **描画**:すべてのドローコールがアタッチメントにレンダリングします。 +3. **終了**:アタッチメントに描画された内容を保存するか、破棄します。 + +物理レンダーパスはグラフィックス API によって定義されます。WebGPU ではレンダーパスと呼ばれ、WebGL では 1 つのフレームバッファへの描画がこれに当たります。 + +## 物理コンピュートパス {#physical-compute-passes} + +コンピュートシェーダーはレンダーターゲットにレンダリングしません。代わりにディスパッチを実行し、バッファやテクスチャを直接読み書きします。GPU はディスパッチを**物理コンピュートパス**で実行します。物理コンピュートパスは、物理レンダーパスがドローコールをまとめるのと同じように、ディスパッチをまとめます。コンピュートシェーダーは WebGPU でのみサポートされています。 + +## パスで構成されるフレーム {#a-frame-of-passes} + +**フレーム**とは、アプリケーションがレンダリングする 1 枚の画像で、通常はディスプレイのリフレッシュごとに 1 回レンダリングされます。物理レンダーパスは単一のレンダーターゲットにレンダリングするため、フレームには複数の物理レンダーパスが必要です。たとえば、レンダリングしたばかりのテクスチャをサンプリングするために別のレンダーターゲットにレンダリングするには、1 つの物理レンダーパスを終了し、別の物理レンダーパスを開始します。シンプルなフレームでは 3 つの物理レンダーパスをレンダリングし、それぞれが直前のパスのレンダリング結果をサンプリングします。 + +```mermaid +flowchart LR + Shadow[シャドウマップ] -->|サンプリング| Scene[シーンテクスチャ] + Scene -->|サンプリング| Post["バックバッファ(表示)"] +``` + +各ボックスは 1 つの物理レンダーパスで、そのレンダリング先にちなんで名付けられています。ライトのシャドウマップ、シーンを保持するテクスチャ、そしてポストプロセスがシーンテクスチャを描画するバックバッファです。パスは互いに依存しているため、順序が重要です。テクスチャをサンプリングするパスは、そのテクスチャにレンダリングするパスの後にレンダリングされます。 + +## フレームグラフ {#the-frame-graph} + +**フレームグラフ**は、フレームのすべてのステップを実行順に記述します。各ステップが何にレンダリングし、何を描画または計算するかを表します。各ステップが**論理パス**です。レンダラーは、処理を進めながら物理パスを開始・終了するのではなく、まずそのフレームのフレームグラフを構築し、その後で論理パスを物理パスの中で実行します。ほとんどの論理レンダーパスはそれぞれ専用の物理レンダーパスを持ちますが、フレーム全体を事前に把握することで、レンダラーはフレームに必要な物理パスだけを開始・終了できます。 + +- **論理パスは物理レンダーパスを共有します**。同じレンダーターゲットにレンダリングする連続した論理パスは単一の物理レンダーパスに記録されるため、それらの間でアタッチメントの保存と再読み込みは行われません。 +- **アタッチメントは必要な場合にのみ保存されます**。後の論理パスが読み込むアタッチメントは保存され、それ以外は破棄できます。 + +次の例では、3 つの論理パスが 2 つの物理レンダーパスでフレームをレンダリングします。 + +```mermaid +flowchart LR + Opaque[論理パス:
不透明シーン] --> ScenePhysical[物理レンダーパス 1:
シーンテクスチャへ] + Transparent[論理パス:
半透明シーン] --> ScenePhysical + Post[論理パス:
ポストプロセス] --> BackPhysical[物理レンダーパス 2:
バックバッファへ] +``` + +### 複数のカメラ {#multiple-cameras} + +シーンには複数のカメラを配置できます。たとえば、反射やミニマップをテクスチャにレンダリングするカメラと、画面にレンダリングするメインカメラです。フレームグラフは、すべてのカメラの論理パスを、カメラの[優先度](https://api.playcanvas.com/engine/classes/CameraComponent.html#priority)の順に 1 つのフレームへ合成します。優先度の値が小さいカメラのパスが先になります。そのため、テクスチャにレンダリングするカメラには、そのテクスチャを使用するカメラよりも小さい優先度が必要です。これにより、テクスチャは使用される時点で準備ができています。カメラが必要とするシャドウマップは、そのカメラのシーンより前にレンダリングされます。 + +```mermaid +flowchart LR + Reflection[カメラ、優先度 -1:
そのパスが
反射テクスチャをレンダリング] --> Main[カメラ、優先度 0:
そのパスが反射をサンプリングし、
シーンをバックバッファに
レンダリング] +``` + +## パフォーマンス {#performance} + +物理レンダーパスの開始と終了には、特にモバイル GPU でコストがかかります。モバイル GPU は高速なオンチップメモリの小さなタイル単位でレンダリングし、そのメモリとメインメモリとの間でアタッチメントをコピーします。パスがアタッチメントを読み込むときはオンチップメモリに取り込み、保存するときはメインメモリに書き出します。そのため、フレームに必要な物理レンダーパスの数と、それらが何を読み込み、何を保存するかがパフォーマンスに影響します。アタッチメントを読み込む代わりにクリアすると取り込みのコピーを回避でき、同じレンダーターゲットにレンダリングする論理パス間で物理レンダーパスを共有することでも同様に回避できます。 + +## PlayCanvas では {#in-playcanvas} + +フレームグラフの各論理パスは、エンジンのオブジェクトであるフレームパスです。エンジンは自身のフレームをフレームパスでレンダリングしており、独自のフレームパスを追加することもできます。 + +- [フレームパス](frame-passes.md)は、フレームグラフの論理パスです。パスのクラスとそのライフサイクルを扱います。 +- [カメラパス](camera-passes.md)は、カメラの前後にレンダリングするか、カメラのレンダリングを置き換えます。 +- [レンダーパス](render-pass.md)は、レンダーターゲットにレンダリングします。レンダーターゲットのすべてのピクセルに対するシェーダーの実行や、シーンのレンダリングも含みます。 +- [コンピュートパス](compute-pass.md)は、コンピュートシェーダーを実行します。 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/render-pass-forward.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/render-pass-forward.md new file mode 100644 index 00000000000..06301d642cf --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/render-pass-forward.md @@ -0,0 +1,56 @@ +--- +title: シーンのレンダリング +description: "RenderPassForward はカメラでシーンのレイヤーをレンダリングします:パスの作成、レイヤーの追加、毎フレームの処理内容、カメラのレンダリングを独自のパスで置き換える方法。" +--- + +[`RenderPassForward`](https://api.playcanvas.com/engine/classes/RenderPassForward.html) は、1 つのカメラでシーンのレイヤーをレンダリングする [`RenderPass`](render-pass.md) です。エンジンがシーンのレンダリングに使用するパスでもあります。カメラの [`framePasses`](https://api.playcanvas.com/engine/classes/CameraComponent.html#framepasses) で使用すると、シーンを独自のレンダーターゲットにレンダリングし、表示される前に独自のパスで処理できます。 + +## パスの作成 {#creating-the-pass} + +グラフィックスデバイス、シーン、レイヤーをレンダリングするカメラコンポーネントを指定してパスを作成します。レンダリング先のレンダーターゲットで初期化し、レンダリングするレイヤーを順番に追加します。 + +```javascript +const worldLayer = app.scene.layers.getLayerByName('World'); +const uiLayer = app.scene.layers.getLayerById(LAYERID_UI); + +const scenePass = new RenderPassForward(app.graphicsDevice, app.scene, cameraEntity.camera); +scenePass.name = 'ScenePass'; +scenePass.init(renderTarget); + +scenePass.addLayer(worldLayer, false); +scenePass.addLayer(uiLayer, true); +``` + +各レイヤーには、不透明と透明のサブレイヤーがあります。[`addLayer`](https://api.playcanvas.com/engine/classes/RenderPassForward.html#addlayer) の 2 番目の引数でどちらをレンダリングするかを選択するため、両方をレンダリングするにはレイヤーを 2 回追加します。パスがレンダリングするレイヤーは、カメラもレンダリングする必要があります。[`CameraComponent#layers`](https://api.playcanvas.com/engine/classes/CameraComponent.html#layers) を参照してください。 + +## パスが毎フレーム行うこと {#what-the-pass-does-each-frame} + +パスは、カメラがレイヤーをレンダリングするのと同じ方法でレイヤーをレンダリングします。 + +- カメラとレイヤーのクリアフラグの要求に従って、レンダーターゲットをクリアします。そのレイヤーでクリアしないようにするには、`addLayer` の 3 番目の引数に `false` を渡します。 +- カメラに対してレイヤーのメッシュインスタンスをカリングし、それらに必要なディレクショナルシャドウをレンダリングします。 +- フレーム内でそのカメラをレンダリングする最初のパスである場合、カメラの [`beforePasses`](https://api.playcanvas.com/engine/classes/CameraComponent.html#beforepasses) をレンダリングします。 +- フレーム内でそのカメラをレンダリングする最初のパスからシーンの `prerender` イベントを、最後のパスから `postrender` を発火し、レンダリングする各レイヤーの前後で `prerender:layer` と `postrender:layer` を発火します。 + +## ガンマ補正とトーンマッピング {#gamma-correction-and-tone-mapping} + +デフォルトでは、パスはカメラのガンマ補正とトーンマッピングでレンダリングします。パスは [`gammaCorrection`](https://api.playcanvas.com/engine/classes/RenderPassForward.html#gammacorrection) と [`toneMapping`](https://api.playcanvas.com/engine/classes/RenderPassForward.html#tonemapping) でこれらを上書きできます。たとえば HDR パイプラインでは、シーンを浮動小数点のレンダーターゲットにリニアでレンダリングし、後続のパスでトーンマッピングします。一方、UI を画面にレンダリングするパスはカメラの設定を維持します。 + +```javascript +scenePass.gammaCorrection = GAMMA_NONE; +scenePass.toneMapping = TONEMAP_LINEAR; +``` + +## カメラのレンダリングの置き換え {#replacing-the-cameras-rendering} + +独自のパスでカメラをレンダリングするには、カメラの [`framePasses`](camera-passes.md#replacing-the-cameras-rendering) に `RenderPassForward` を追加し、それがレンダリングした内容を処理するパスも一緒に追加します。たとえば、シーンをテクスチャにレンダリングし、そのテクスチャを[シェーダークアッドパス](render-pass-shader-quad.md)で画面に描画します。カメラは複数のフォワードパスでレイヤーをレンダリングすることもできます。たとえば、まず不透明レイヤーをレンダリングし、次にそれらがレンダリングした内容を読み取る独自のパスを実行し、その後に透明レイヤーをレンダリングします。[カメラパス](camera-passes.md)を参照してください。 + +## 制限事項 {#limitations} + +`RenderPassForward` を別の `RenderPassForward` の下にネストすることはできません。つまり、その 前のパスや 後のパス、あるいはパスが自身より前にレンダリングするカメラの `beforePasses` に入れることはできません。別のカメラで先にレイヤーをレンダリングするには、パスを順番にレンダリングします。たとえば、`framePasses` 内の通常の `FramePass` の 前のパスとしてレンダリングするか、優先度の低いカメラでレンダリングします。 + +## 例 {#example} + +フォワードパスが World レイヤーと UI レイヤーをテクスチャにレンダリングし、シェーダークアッドパスがそのテクスチャに色味を付けて画面に描画します。 + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/render-pass-shader-quad.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/render-pass-shader-quad.md new file mode 100644 index 00000000000..20c2d996308 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/render-pass-shader-quad.md @@ -0,0 +1,169 @@ +--- +title: シェーダークアッドパス +description: "RenderPassShaderQuad はレンダーターゲット全体またはその一部のすべてのピクセルに対してシェーダーを実行します:シェーダーの記述、パラメーターの設定、レンダーステート、インスタンス化したクアッドと繰り返し描画するクアッド、drawQuadWithShader。" +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +[`RenderPassShaderQuad`](https://api.playcanvas.com/engine/classes/RenderPassShaderQuad.html) は、シェーダーでクアッドを描画する [`RenderPass`](render-pass.md) です。そのため、フラグメントシェーダーはクアッドが覆うピクセルごとに 1 回実行されます。覆う範囲はデフォルトではレンダーターゲット全体で、[ビューポート](#render-states-and-the-drawn-area)を設定するとその一部になります。これはポストプロセスエフェクトや、GPU でテクスチャを計算するあらゆるパスの基本的な構成要素です。 + +このパスは、その [`shader`](https://api.playcanvas.com/engine/classes/RenderPassShaderQuad.html#shader) 用のクアッドを作成し、レンダーステートを設定し、実行時にクアッドを描画します。シェーダーを設定してそのまま使用することも、シェーダーを作成してパラメーターを設定するパスの基底クラスとして使用することもできます。 + +## シェーダー {#the-shader} + +頂点シェーダーは、`SEMANTIC_POSITION` を使用する属性でクアッドの角を受け取ります。`quadVS` シェーダーチャンクがこの頂点シェーダーを実装し、クアッドの UV 座標を `uv0` varying でフラグメントシェーダーに渡すため、記述するのはフラグメントシェーダーだけです。パスが WebGL2 と WebGPU の両方で動作するように、GLSL と WGSL の両方で記述します。 + + + + +```glsl +uniform sampler2D sourceTexture; +uniform vec3 tint; +varying vec2 uv0; + +void main() { + vec4 color = texture2D(sourceTexture, uv0); + gl_FragColor = vec4(color.rgb * tint, color.a); +} +``` + + + + +```wgsl +var sourceTexture: texture_2d; +var sourceTextureSampler: sampler; +uniform tint: vec3f; +varying uv0: vec2f; + +@fragment fn fragmentMain(input: FragmentInput) -> FragmentOutput { + var output: FragmentOutput; + let color = textureSample(sourceTexture, sourceTextureSampler, input.uv0); + output.color = vec4f(color.rgb * uniform.tint, color.a); + return output; +} +``` + + + + +[`ShaderUtils.createShader`](https://api.playcanvas.com/engine/classes/ShaderUtils.html#createshader) に両方のフラグメントシェーダーを渡して、シェーダーを作成します。 + +```javascript +const shader = ShaderUtils.createShader(device, { + uniqueName: 'TintShader', + attributes: { aPosition: SEMANTIC_POSITION }, + vertexChunk: 'quadVS', + fragmentGLSL: tintGLSL, + fragmentWGSL: tintWGSL +}); +``` + +シェーダーは共有されるため、パスはシェーダーを破棄しません。 + +## シェーダーパラメーターの設定 {#setting-the-shader-parameters} + +シェーダーのテクスチャとユニフォームは、グラフィックスデバイスのスコープに設定されます。他のパスが同じ名前のパラメーターを設定することがあるため、クアッドを描画する直前に設定してください。パラメーターはパスの作成時に一度だけ解決し、オーバーライドした `execute` の中で、クアッドを描画する `super.execute()` を呼び出す前に値を設定します。 + +```javascript +// テクスチャに色を乗算してレンダーターゲットに描画するパス +class TintPass extends RenderPassShaderQuad { + constructor(device, sourceTexture) { + super(device); + this.name = 'TintPass'; + this.sourceTexture = sourceTexture; + this.tint = new Float32Array([1, 0.5, 0.5]); + this.shader = ShaderUtils.createShader(device, { + uniqueName: 'TintShader', + attributes: { aPosition: SEMANTIC_POSITION }, + vertexChunk: 'quadVS', + fragmentGLSL: tintGLSL, + fragmentWGSL: tintWGSL + }); + + // シェーダーパラメーターを一度だけ解決する + this.sourceTextureId = device.scope.resolve('sourceTexture'); + this.tintId = device.scope.resolve('tint'); + } + + execute() { + // クアッドを描画する直前にパラメーターを設定する + this.sourceTextureId.setValue(this.sourceTexture); + this.tintId.setValue(this.tint); + super.execute(); + } +} + +const pass = new TintPass(app.graphicsDevice, sourceTexture); +pass.init(renderTarget); + +// クアッドがレンダーターゲット全体を覆うため、内容をロードせずにクリアする +pass.setClearColor(Color.BLACK); +``` + +毎フレーム描画するにはパスをカメラに追加します([カメラパス](camera-passes.md)を参照してください)。または、`pass.render()` で一度だけレンダリングします。 + +## レンダーステートと描画領域 {#render-states-and-the-drawn-area} + +デフォルトでは、クアッドはレンダーターゲット全体を覆い、ブレンド、深度テスト、フェイスカリングなしで描画され、レンダーターゲットの内容を置き換えます。これを変更するには、次のプロパティを設定します。 + +- [`blendState`](https://api.playcanvas.com/engine/classes/RenderPassShaderQuad.html#blendstate) - クアッドをレンダーターゲットの内容と合成します。 +- [`depthState`](https://api.playcanvas.com/engine/classes/RenderPassShaderQuad.html#depthstate)、[`stencilFront`](https://api.playcanvas.com/engine/classes/RenderPassShaderQuad.html#stencilfront)、[`stencilBack`](https://api.playcanvas.com/engine/classes/RenderPassShaderQuad.html#stencilback) - クアッドをレンダーターゲットの深度またはステンシルに対してテストします。 +- [`viewport`](https://api.playcanvas.com/engine/classes/RenderPassShaderQuad.html#viewport)、および必要に応じて [`scissor`](https://api.playcanvas.com/engine/classes/RenderPassShaderQuad.html#scissor) - レンダーターゲットの一部だけに描画します。 + +たとえば、パスを直接使用して、テクスチャアトラスの 1 つのタイルにシェーダーを描画します。アトラスはクリアされないため、他のタイルは内容を保持します。 + +```javascript +const pass = new RenderPassShaderQuad(app.graphicsDevice); +pass.name = 'AtlasTile'; +pass.shader = tileShader; +pass.init(atlasRenderTarget); + +pass.viewport = new Vec4(256, 0, 256, 256); +pass.render(); + +// 同じタイルの上に 2 つ目のシェーダーをブレンドする +pass.shader = overlayShader; +pass.blendState = BlendState.ALPHABLEND; +pass.render(); + +pass.destroy(); +``` + +## 複数のクアッドの描画 {#drawing-several-quads} + +スプライトのように、それぞれがインデックスに応じて読み取るデータだけが異なる多数のクアッドを描画するには、[`numInstances`](https://api.playcanvas.com/engine/classes/RenderPassShaderQuad.html#numinstances) を設定して、1 回のインスタンス描画でまとめて描画します。各インスタンスは自身のインデックス(GLSL では `gl_InstanceID`、WGSL では `pcInstanceIndex`)を読み取ってクアッドのデータを取得し、たとえばクアッドを配置します。デフォルトでは `numInstances` は `undefined` で、インスタンス化せずに 1 つのクアッドを描画します。0 を設定すると何も描画しませんが、パス自体はレンダリングされるため、レンダーターゲットのクリアは引き続き行われます。 + +ライトごとに 1 つなど、異なるパラメーターで複数のクアッドを描画するには、`execute` をオーバーライドし、クアッドごとにパラメーターまたはビューポートを設定して `super.execute()` を呼び出します。これにより、パスのレンダーステートでクアッドが描画されます。 + +```javascript +execute() { + for (const light of this.lights) { + this.colorId.setValue(light.color); + this.viewport = light.screenRect; + super.execute(); + } +} +``` + +## クアッドの即時描画 {#drawing-a-quad-immediately} + +テクスチャを一度だけ生成する場合など、単発の描画には [`drawQuadWithShader`](https://api.playcanvas.com/engine/functions/drawQuadWithShader.html) を使用します。この関数は、パスをセットアップせずに、シェーダーでクアッドをレンダーターゲットへ即座に描画します。 + +```javascript +device.scope.resolve('scale').setValue(4); +drawQuadWithShader(device, renderTarget, shader); +``` + +呼び出すたびにパスを作成し、レンダリングして破棄するため、呼び出しごとにメモリの割り当てが発生します。グラフィックスデバイスに現在設定されているブレンドステートで描画し、レンダーターゲットの内容をクリアせずにロードします。クアッドを毎フレーム描画する場合や、独自のレンダーステートで描画する場合は、一度作成して再利用する `RenderPassShaderQuad` を使用してください。 + +## 例 {#examples} + +2 つの `RenderPassShaderQuad` パスが、テクスチャに光の軌跡を描きます。フェードパスは逆減算のブレンドステートでテクスチャを暗くし、ブラシパスは各ブラシの経路に沿ってドットを描画します。ブラシパスはドットごとにビューポートを設定し、すべてを 1 つのパスで描画します。 + + + +ポストプロセスエフェクト内の `RenderPassShaderQuad` が、深度や明るさが急激に変化する箇所で、シーンにインクの輪郭線を描画します。 + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/render-pass.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/render-pass.md new file mode 100644 index 00000000000..bcdd336c109 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/advanced-rendering/frame-graph/render-pass.md @@ -0,0 +1,141 @@ +--- +title: レンダーパス +description: "RenderPass はレンダーターゲットまたはバックバッファに描画します:初期化、レンダーターゲットのサイズ変更、クリア、ストアとミップマップ、パスの実装。" +--- + +[`RenderPass`](https://api.playcanvas.com/engine/classes/RenderPass.html) は、[レンダーターゲット](/user-manual/graphics/advanced-rendering/render-targets/)に描画する[フレームパス](frame-passes.md)です。実行されると、そのドローコールはレンダーターゲット用の物理レンダーパスに記録されます。通常はパス専用の物理レンダーパスで、パスの実行直前に開始し、実行直後に終了します。あるいは、同じレンダーターゲットにレンダリングする隣接したパスと共有する物理レンダーパスです。[レンダーターゲットを共有するパス](#passes-sharing-a-render-target)を参照してください。 + +## RenderPass から派生したクラス {#classes-derived-from-renderpass} + +`RenderPass` は、描画を行うパスの基底クラスです。一般的な種類の描画のために、エンジンはそこから派生したクラスを提供しています。これらはそのまま使用することも、独自のパスの基底クラスとして使用することもできます。 + +```mermaid +classDiagram + RenderPass <|-- RenderPassShaderQuad + RenderPass <|-- RenderPassForward +``` + +| クラス | 用途 | +| ----- | ---------- | +| [`RenderPassShaderQuad`](render-pass-shader-quad.md) | ポストプロセスエフェクトなど、レンダーターゲット全体またはその一部のすべてのピクセルに対してシェーダーを実行する。 | +| [`RenderPassForward`](render-pass-forward.md) | カメラでシーンのレイヤーをレンダリングする。 | + +独自のドローコールを記録するには、`RenderPass` 自体から派生します。[パスの実装](#writing-a-pass)を参照してください。このページの以降の内容は、派生クラスを含むすべてのレンダーパスに当てはまります。 + +## パスのセットアップ {#setting-up-the-pass} + +パスを作成したら、描画先のレンダーターゲットを指定して [`init`](https://api.playcanvas.com/engine/classes/RenderPass.html#init) を呼び出します。バックバッファに描画する場合は `null` を指定します。パスは、レンダリングする前、およびクリアとストアを設定する前に初期化する必要があります。 + +```javascript +const pass = new MyPass(app.graphicsDevice); +pass.name = 'MyPass'; +pass.init(renderTarget); +``` + +パスはレンダーターゲットを所有せず、破棄もしません。アタッチメントの異なるレンダーターゲットにパスを切り替えるには、`init` を再度呼び出します。同じレイアウト(同じカラーバッファ、深度とステンシル、サンプル数)を持つレンダーターゲットであれば、[`renderTarget`](https://api.playcanvas.com/engine/classes/RenderPass.html#rendertarget) に直接割り当てることもできます。たとえば、2 つのレンダーターゲットを毎フレーム交互に使用する場合です。 + +## レンダーターゲットのサイズ変更 {#resizing-the-render-target} + +`init` にオプションを渡すと、パスはレンダーターゲットのサイズも毎フレーム変更します。サイズは、テクスチャ、レンダーターゲット、またはバックバッファのサイズを `scaleX` と `scaleY` でスケールしたものになります。 + +```javascript +// バックバッファの半分のサイズのレンダーターゲット +pass.init(renderTarget, { + resizeSource: null, + scaleX: 0.5, + scaleY: 0.5 +}); + +// パスが処理するテクスチャと同じサイズのレンダーターゲット +pass.init(renderTarget, { resizeSource: sourceTexture }); +``` + +オプションはコピーされます。後から変更するには、パスの [`resizeSource`](https://api.playcanvas.com/engine/classes/RenderPass.html#resizesource)、[`scaleX`](https://api.playcanvas.com/engine/classes/RenderPass.html#scalex)、[`scaleY`](https://api.playcanvas.com/engine/classes/RenderPass.html#scaley) を設定します。たとえば、パスが処理するテクスチャが置き換えられたときは `resizeSource` を設定します。`resizeSource` がデフォルトの `undefined` の場合、レンダーターゲットのサイズはそのまま変更されません。 + +レンダーターゲットのサイズは、フレームの構築中にパスの `frameUpdate` で変更され、パスが更新された時点で新しいサイズになります。サイズが変わったフレームでは、そのパスより前に更新されたパスからは、引き続き以前のサイズが見えます。 + +## アタッチメントのクリア {#clearing-the-attachments} + +レンダーターゲットのアタッチメントとは、そのカラーバッファ、および深度バッファとステンシルバッファです。デフォルトでは、物理レンダーパスはそれらの内容をロードし、パスはその上に描画します。代わりに単一の値から開始するには、それらをクリアします。 + +```javascript +pass.setClearColor(Color.BLACK); +pass.setClearDepth(1); +pass.setClearStencil(0); +``` + +パスがアタッチメントを完全に覆う場合、タイルベースのモバイル GPU では、クリアはロードよりも高速でもあります。その場合、GPU は以前の内容をメモリから読み込まないためです。[`setClearColor`](https://api.playcanvas.com/engine/classes/RenderPass.html#setclearcolor) は、すべてのカラーバッファか、指定されたインデックスのカラーバッファをクリアします。 + +## アタッチメントのストア {#storing-the-attachments} + +物理レンダーパスが終了すると、パスはレンダリングした内容をレンダーターゲットのテクスチャに保持し、それらをサンプリングしたり、後続のパスでロードしたりできるようにします。保持されるのは、カラーバッファ(レンダーターゲットがマルチサンプルの場合は解決されたもの)と、深度バッファのテクスチャです。その他のアタッチメントは破棄されます。破棄されるのは、解決されるマルチサンプルカラーバッファのサンプル、ステンシル、およびレンダーターゲットが内部で作成する深度バッファです。 + +同じフレームの後続のパスが同じレンダーターゲットに描画し、それらをロードする場合は、フレームが自動的にストアします。単独でレンダリングするパスでは、[`setStoreColor`](https://api.playcanvas.com/engine/classes/RenderPass.html#setstorecolor)、[`setStoreDepth`](https://api.playcanvas.com/engine/classes/RenderPass.html#setstoredepth)、[`setStoreStencil`](https://api.playcanvas.com/engine/classes/RenderPass.html#setstorestencil) でストアします。 + +ミップマップを持つカラーバッファのミップマップも、パスの終了時に生成されます。複数のパスが同じレンダーターゲットに描画する場合、ミップマップが必要になるのは最後のパスの後だけなので、それ以外のパスでは [`setGenerateMipmaps`](https://api.playcanvas.com/engine/classes/RenderPass.html#setgeneratemipmaps) でオフにします。 + +```javascript +firstPass.setGenerateMipmaps(false); +``` + +`init` はクリア、ストア、ミップマップの設定をデフォルトにリセットするため、これらはパスを初期化した後に設定してください。 + +## レンダーターゲットを共有するパス {#passes-sharing-a-render-target} + +フレーム内では、同じレンダーターゲットに描画する連続したパスは 1 つの物理レンダーパスに統合されるため、それらの間でアタッチメントをストアしてロードする必要がなくなります。パスの間で何かを行う必要がある場合、それらのパスは統合されません。たとえば、後のパスのクリア、パスの間にある 前のパスまたは 後のパス、`onBefore` または `onAfter` コールバックがある場合です。 + +## パスの実装 {#writing-a-pass} + +サブクラスは、物理レンダーパスがアクティブな間に実行される [`execute`](https://api.playcanvas.com/engine/classes/FramePass.html#execute) でドローコールを記録します。パスの初期化後にクリアを設定するには [`postInit`](https://api.playcanvas.com/engine/classes/RenderPass.html#postinit) を、各フレームのレンダリング前にパスを更新するには [`frameUpdate`](https://api.playcanvas.com/engine/classes/RenderPass.html#frameupdate) をオーバーライドできます(サイズ変更を維持するために `super.frameUpdate()` を呼び出します)。作成したものは [`destroy`](https://api.playcanvas.com/engine/classes/FramePass.html#destroy) で解放し、`super.destroy()` を呼び出します。 + +```javascript +// レンダーターゲット全体を覆うクアッドをシェーダーで描画するパス +class MyPass extends RenderPass { + constructor(device, shader) { + super(device); + this.quadRender = new QuadRender(shader); + } + + postInit() { + // クアッドがターゲット全体を覆うため、内容をロードせずにクリアする + this.setClearColor(Color.BLACK); + } + + execute() { + // デフォルトの描画ステートはクアッドに適している:ブレンドなし、深度テストと深度書き込みなし + this.device.setDrawStates(); + this.quadRender.render(); + } + + destroy() { + this.quadRender.destroy(); + super.destroy(); + } +} +``` + +シェーダーで描画するクアッドについては、[`RenderPassShaderQuad`](render-pass-shader-quad.md) がすでにこれを行い、さらにレンダーステートも設定します。 + +## テクスチャに一度だけレンダリングする {#rendering-into-a-texture-once} + +内容を変更する必要があるときだけテクスチャにレンダリングするには、フレームの外で [`render`](https://api.playcanvas.com/engine/classes/RenderPass.html#render) を使用してパスを単独でレンダリングします。 + +```javascript +const renderTarget = new RenderTarget({ colorBuffer: texture, depth: false }); +const pass = new MyPass(app.graphicsDevice, shader); +pass.name = 'MyPass'; +pass.init(renderTarget); + +// テクスチャに描画する +pass.render(); + +// パスを破棄し、次にパスが所有していないレンダーターゲットを破棄する +pass.destroy(); +renderTarget.destroy(); +``` + +## 例 {#example} + +どのカメラにもアタッチされていない 2 つの `RenderPass` サブクラスが、マテリアルのテクスチャを生成します。パターンと、その高さから生成するノーマルマップです。これらは `prerender` イベントから単独でレンダリングされ、アニメーション中は毎フレーム、それ以外は設定が変更されたときだけレンダリングされます。 + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/linear-workflow/hdr-rendering.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/linear-workflow/hdr-rendering.md index 33f68490d35..3ad708c3c84 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/linear-workflow/hdr-rendering.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/linear-workflow/hdr-rendering.md @@ -150,6 +150,20 @@ cameraFrame.update(); ![CameraFrame Script](/img/user-manual/graphics/linear-workflow/camera-frame.png) +### エフェクトのスクリプト {#effect-scripts} + +カメラフレームに組み込まれていないエフェクトには、それぞれ専用のスクリプトがあります。各スクリプトは、それが付いているカメラエンティティのカメラフレームにエフェクトを追加するため、`cameraFrame` スクリプトと同じエンティティに追加してください。 + +| スクリプト | ファイル | 追加するもの | +| ---------- | -------- | ------------ | +| `autoExposureEffect` | [`auto-exposure.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/auto-exposure.mjs) | [自動露出](/user-manual/graphics/posteffects/cameraframe/auto-exposure/) | +| `smaaEffect` | [`smaa.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/smaa.mjs) | [SMAA](/user-manual/graphics/posteffects/cameraframe/#smaa) によるアンチエイリアス | +| `lensFlareEffect` | [`lens-flare.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/lens-flare.mjs) | [レンズフレア](/user-manual/graphics/posteffects/cameraframe/lens-flare/)のエフェクトと、その形状ライブラリ。同じファイルの `lensFlare` スクリプトは、それが付いているエンティティにフレアを付けます。 | + +これらのスクリプトは [`camera-frame-effect.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/camera-frame-effect.mjs) をインポートするため、このファイルもそれらと並べてプロジェクトに追加してください。このファイルは、独自のエフェクトのスクリプトの基底クラスでもあります。[エディターでのエフェクト](/user-manual/graphics/posteffects/cameraframe/custom-effects/#effects-in-the-editor)を参照してください。 + +スクリプトのエフェクトは、スクリプトの有効状態に従い、スクリプトとともに破棄され、その際にカメラフレームから取り除かれます。スクリプトの **Debug** 属性は、そのエフェクトのデバッグ表示を表示します。この属性と `cameraFrame` スクリプトのデバッグ設定は互いに上書きし合い、最後に変更した方が表示されます。[ダイナミックスカイ](/user-manual/graphics/posteffects/cameraframe/dynamic-sky/)にはエディター用のスクリプトがないため、コードで設定してください。 + ## CameraFrameのヒント - HDR bloomには、少なくとも1つのレンダリング可能な浮動小数点フォーマット(例: RG11B10、RGBA16F、またはRGBA32F)が必要です。これらのフォーマットのいずれもデバイスでサポートされていない場合、HDR bloomは自動的に無効になります。 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/auto-exposure.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/auto-exposure.md new file mode 100644 index 00000000000..ef2d859085f --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/auto-exposure.md @@ -0,0 +1,79 @@ +--- +title: 自動露出 +description: "CameraFrame の自動露出エフェクト:画面の明るさの測光、露出の順応、カット、露出とヒストグラムの読み取り、デバッグ表示、エディタースクリプト。" +--- + +[`AutoExposureEffect`](https://api.playcanvas.com/engine/classes/AutoExposureEffect.html) は、目が暗闇や日光に順応するように、カメラの露出を、カメラに映るものの明るさに順応させます。毎フレーム、画面の明るさのヒストグラムを測定し、画面の 2 つの割合の間にある部分を平均します。このとき、深い影や太陽など、最も暗い部分と最も明るい部分は除外されます。その後、その平均をミドルグレーとして表示する露出に向けて、露出を徐々に変化させます。 + +自動露出はカメラフレームの組み込みエフェクトではありません。作成して、[`addEffect`](https://api.playcanvas.com/engine/classes/CameraFrame.html#addeffect) でカメラフレームに追加してください。 + +```javascript +const autoExposure = new AutoExposureEffect(app.graphicsDevice); +cameraFrame.addEffect(autoExposure); +cameraFrame.update(); +``` + +## 順応させる露出 {#the-exposure-it-adapts} + +エフェクトは、カメラがレンダリングに使用する露出を順応させます。これは[シーンの露出](https://api.playcanvas.com/engine/classes/Scene.html#exposure)、または[物理単位](/user-manual/graphics/cameras/tone-mapping/#physical-exposure)を使用する場合は物理カメラの露出です。エフェクトは [`minEv`](https://api.playcanvas.com/engine/classes/AutoExposureEffect.html#minev) から [`maxEv`](https://api.playcanvas.com/engine/classes/AutoExposureEffect.html#maxev) 段の範囲で露出をスケーリングするため、カメラの露出は典型的なビューに合わせて設定し、そこからエフェクトに順応させてください。 + +合成のトーンマッピングやアンチエイリアシングなど、露出を使用するフレームのすべての部分がこの露出に従います。トーンマッピングがないと合成で露出が適用されないため、エフェクトにはトーンマッピングが必要です。`rendering.toneMapping` が `TONEMAP_NONE` の間、エフェクトは動作しません。 + +## 測光 {#metering} + +エフェクトは、シーンの半分の解像度のコピーから画面の明るさを測定し、64 ビンのヒストグラムにします。平均する画面の部分は、次の 3 つのプロパティで選択します。 + +| プロパティ | 説明 | +| -------- | ----------- | +| `lowPercent` | 深い影など、最も暗い部分として平均から除外する画面の割合で、0 から 1 の範囲です。値を大きくすると画面の明るい部分を測光するため、画像が暗くなります。デフォルトは 0.1 です。 | +| `highPercent` | 平均を取る上限となる画面の割合で、0 から 1 の範囲です。これより上にある、太陽や明るいハイライトなどの最も明るい部分は除外されます。デフォルトは 0.9 です。 | +| `centerWeight` | 画面の端に対して中央の明るさをどれだけ重視するかで、0 から 1 の範囲です。0 では画面全体を均等に測光し、1 では主に中央を測光します。デフォルトは 0.5 です。 | + +## 順応 {#adaptation} + +| プロパティ | 説明 | +| -------- | ----------- | +| `compensation` | EV 単位の露出補正。画面の平均をミドルグレーよりどれだけ明るく表示するかを指定します。正の値で画像が明るくなり、負の値で暗くなり、1 段ごとに明るさが 2 倍または半分になります。デフォルトは 0 です。 | +| `minEv` | エフェクトが順応する最低の露出で、カメラの露出に対する EV で指定します。-2 では、明るいシーン向けに画像を 4 分の 1 の明るさまで暗くできます。デフォルトは -6 です。 | +| `maxEv` | エフェクトが順応する最高の露出で、カメラの露出に対する EV で指定します。2 では、暗いシーン向けに画像を 4 倍まで明るくできます。デフォルトは 6 です。 | +| `speedDarkToLight` | 暗い室内から日光の下に出たときなど、シーンが明るくなったときに露出が順応する速さ。露出は 1 / `speedDarkToLight` 秒で変化のおよそ 3 分の 2 に達します。デフォルトは 3 です。 | +| `speedLightToDark` | シーンが暗くなったときに露出が順応する速さ。目は明るさよりも暗さに順応するのが遅いため、デフォルトは 1 です。 | + +すべてのエフェクトと同様に、これらのプロパティの変更は次の [`CameraFrame#update`](https://api.playcanvas.com/engine/classes/CameraFrame.html#update) で適用されます。 + +## カット {#cuts} + +別のビューへカットした後に、前のビューから徐々に順応すると不自然に見えます。カットの際に [`reset`](https://api.playcanvas.com/engine/classes/AutoExposureEffect.html#reset) を呼び出すと、エフェクトはカット前のビューの測定値を無視して、次の測定値にすぐに順応します。 + +```javascript +cameraEntity.setPosition(nextShot.position); +autoExposure.reset(); +``` + +エフェクトは、最初の測定値にもすぐに順応します。 + +## 露出の読み取り {#reading-the-exposure} + +ヒストグラムは GPU から読み戻されるため、露出は 1、2 フレーム遅れて画面に追従しますが、徐々に順応することでこの遅れは目立たなくなります。結果は次の 2 つのプロパティで取得できます。 + +- [`exposure`](https://api.playcanvas.com/engine/classes/AutoExposureEffect.html#exposure) は、このフレームでカメラがレンダリングに使用する露出で、カメラの露出を順応によってスケーリングしたものです。 +- [`histogram`](https://api.playcanvas.com/engine/classes/AutoExposureEffect.html#histogram) は、最後に読み戻されたヒストグラムで、最も暗いビンから最も明るいビンまで、64 個の各ビンの重みを持つ `Uint32Array` です。ヒストグラムが届くたびにインプレースで更新されます。 + +## デバッグ表示 {#debug-view} + +`autoexposure` デバッグ表示は、他のエフェクトを適用せずに、カメラがレンダリングに使用する露出でシーンを表示し、その明るさのヒストグラムを下部に描画します。平均に含まれるビンは明るく、平均は黄色で、カメラがミドルグレーとして露出する明るさは青で描画されます。露出が順応すると、2 つのマーカーが重なります。 + +```javascript +cameraFrame.debug = 'autoexposure'; +cameraFrame.update(); +``` + +## エディターでの自動露出 {#auto-exposure-in-the-editor} + +[`auto-exposure.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/auto-exposure.mjs) の `autoExposureEffect` スクリプトは、[`cameraFrame` スクリプト](/user-manual/graphics/linear-workflow/hdr-rendering/#cameraframe-in-the-editor)と並べてカメラエンティティに追加すると、そのカメラエンティティのカメラフレームにエフェクトを追加します。属性はエフェクトのプロパティで、**Debug** 属性でデバッグ表示を表示します。 + +## 例 {#example} + +日の当たる中庭から、その間にある暗いトンネルへと歩くと、露出が順応します。暗さよりも明るさへの順応のほうが速くなります。 + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/custom-effects.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/custom-effects.md new file mode 100644 index 00000000000..69fd04696fb --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/custom-effects.md @@ -0,0 +1,249 @@ +--- +title: カスタムエフェクト +description: "CameraFrameEffect で CameraFrame のポストプロセスエフェクトを作成:コンポーズスロット、シェーダーチャンク、パラメーター、エフェクトの登録、デバッグ表示、エディター用のエフェクトスクリプト。" +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +[`CameraFrame`](https://api.playcanvas.com/engine/classes/CameraFrame.html) は、[`CameraFrameEffect`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html) から派生したオブジェクトである**エフェクト**でポストプロセスをレンダリングします。組み込みエフェクトもエフェクトです。`cameraFrame.bloom`、`cameraFrame.vignette` などは、エフェクトクラスのインスタンスです。独自のエフェクトを作成し、組み込みエフェクトと並べてカメラフレームに登録できます。 + +ほとんどのエフェクトは、**合成**にシェーダーチャンクを追加します。合成は最終的な画像を描画する単一のパスで、その中でこれらのチャンクがすべて順番に実行されます。合成に追加しても、フルスクリーンパスが増えることはありません。低解像度でのブラーなど、合成ではピクセルあたり 1 回のサンプリングで行えない処理を必要とするエフェクトは、パスも持ちます。[パスを持つエフェクト](effect-passes.md)を参照してください。 + +## コンポーズスロット {#compose-slots} + +合成は、出力の各ピクセルに対して 1 回実行されるシェーダーです。シーンがそのピクセルにレンダリングした色を読み取り、画面に書き込まれる色に変換します。シーンは、光に比例し、任意の明るさを取るリニア HDR の色をレンダリングします。トーンマッピングがそれらをディスプレイで表示できる範囲にマッピングし、続いてガンマ補正がディスプレイ向けにエンコードします。エフェクトのチャンクは、その過程で色を変更します。 + +**コンポーズスロット**は、この変換の中で合成がエフェクトのチャンクを呼び出す位置です。各エフェクトは 4 つのスロットのいずれかで適用され、スロットによって受け取る色、つまりエフェクトで何ができるかが決まります。同じスロットのエフェクトは順番に実行され、それぞれが直前のエフェクトの返した色を受け取ります。 + +```mermaid +flowchart TB + subgraph Hdr[リニア HDR] + direction LR + Scene([シーンテクスチャ]) --> SlotScene[["COMPOSESLOT_SCENE
フリンジ、シャープニング"]] --> Alpha[アルファを退避] --> SlotHdr[["COMPOSESLOT_HDR
被写界深度、SSAO、ブルーム、
カラーエンハンス、グレーディング"]] + end + ToneMapping[トーンマッピング] + subgraph Ldr[ディスプレイ空間] + direction LR + SlotLdr[["COMPOSESLOT_LDR
カラー LUT、ビネット"]] --> Gamma[ガンマ補正] --> SlotOutput[[COMPOSESLOT_OUTPUT]] --> Output([出力]) + end + Hdr --> ToneMapping --> Ldr +``` + +各スロットには、そこで適用される組み込みエフェクトを適用順に示しています。 + +| スロット | 受け取る色 | 用途 | +| ---- | --------------------- | ---------- | +| `COMPOSESLOT_SCENE` | サンプリングされたままのシーンカラーとそのアルファ。後続のスロットのエフェクトが変更する前の色です。 | ほかのエフェクトの土台となる、シーンの画像そのものを変更するエフェクト。シャープニングのようにピクセルの周囲でシーンテクスチャを再度サンプリングするエフェクトや、アルファを変更するエフェクトです。後続のスロットでシーンテクスチャを再度サンプリングすると、それより前のエフェクトが行った処理が失われてしまいます。 | +| `COMPOSESLOT_HDR` | トーンマッピング前のリニア HDR の色。 | ブルームの光を加える、アンビエントオクルージョンで暗くするなど、光を扱うエフェクト。 | +| `COMPOSESLOT_LDR` | ガンマ補正前の、ディスプレイの 0-1 の範囲にトーンマッピングされた色。 | ビネットやカラールックアップテーブルなど、最終的な画像の見た目に関わるエフェクト。 | +| `COMPOSESLOT_OUTPUT` | ガンマ補正後の、画面に書き込まれる最終的な値。 | ディザリングなど、書き込まれる値そのものに関わるエフェクト。 | + +`COMPOSESLOT_SCENE` と `COMPOSESLOT_HDR` はどちらもリニア HDR で、その間に変換はありません。`COMPOSESLOT_SCENE` のエフェクトはすべて `COMPOSESLOT_HDR` のどのエフェクトよりも前に実行され、出力のアルファを変更できるのはこれらのエフェクトだけです。 + +`COMPOSESLOT_SCENE` のチャンクのエントリー関数は、アルファを含む `vec4` の色と uv を受け取り、`vec4` の色を返します。ほかのスロットでは、`vec3` の色と uv を受け取り、`vec3` の色を返します。 + +### 組み込みエフェクト {#the-built-in-effects} + +カメラフレームは組み込みエフェクトを次の順序で登録します。これが、各スロットでエフェクトが適用される順序になります。各エフェクトは ID で識別され、[`getEffect`](https://api.playcanvas.com/engine/classes/CameraFrame.html#geteffect) と [`insertEffectBefore`](https://api.playcanvas.com/engine/classes/CameraFrame.html#inserteffectbefore) はこの ID を受け取ります。 + +| ID | エフェクト | コンポーズスロット | +| -- | ------ | ------------ | +| `taa` | テンポラルアンチエイリアス、`cameraFrame.taa` | なし。パスのみを持ちます | +| `fringing` | フリンジ、`cameraFrame.fringing` | `COMPOSESLOT_SCENE` | +| `cas` | シャープニング、`cameraFrame.rendering.sharpness` | `COMPOSESLOT_SCENE` | +| `dof` | 被写界深度、`cameraFrame.dof` | `COMPOSESLOT_HDR` | +| `ssao` | アンビエントオクルージョン、`cameraFrame.ssao` | `COMPOSESLOT_HDR`(`SSAOTYPE_COMBINE` タイプの場合) | +| `volumetricFog` | ボリューメトリックフォグ、`cameraFrame.volumetricFog` | なし。パスのみを持ちます | +| `bloom` | ブルーム、`cameraFrame.bloom` | `COMPOSESLOT_HDR` | +| `colorEnhance` | カラーエンハンス、`cameraFrame.colorEnhance` | `COMPOSESLOT_HDR` | +| `grading` | カラーグレーディング、`cameraFrame.grading` | `COMPOSESLOT_HDR` | +| `colorLut` | カラールックアップテーブル、`cameraFrame.colorLUT` | `COMPOSESLOT_LDR` | +| `vignette` | ビネット、`cameraFrame.vignette` | `COMPOSESLOT_LDR` | + +追加したエフェクトは、そのスロットで組み込みエフェクトの後に、追加した順に適用されます。これはエンジンのほかのエフェクトにも当てはまります。[レンズフレア](lens-flare.md)(`lensFlare`)は `COMPOSESLOT_HDR` でカラーグレーディングの後に適用され、ダイナミックスカイ、SMAA、自動露出はパスのみを持ちます。組み込みエフェクトのいずれかより前にエフェクトを適用するには、`insertEffectBefore` で登録します。[エフェクトの登録](#registering-effects)を参照してください。たとえば、`bloom` の前に挿入したエフェクトはブルームの光が加わる前のシーンに作用し、その結果にカラーグレーディングが適用されます。 + +## エフェクトの作成 {#writing-an-effect} + +エフェクトは、グラフィックスデバイス、**ID**、および宣言(適用されるコンポーズスロットと、GLSL と WGSL のシェーダーチャンク)を指定して構築します。チャンクでは、エフェクトが設定するユニフォームと、`apply` という名前のエントリー関数を宣言します。合成は、エフェクトのスロットでこのエントリー関数を呼び出します。次のチャンクは、画像をドットとして描画します。 + + + + +```glsl +uniform float pixelationTilePixels; +uniform float pixelationIntensity; + +vec3 applyPixelation(vec3 color, vec2 uv) { + vec2 tileUV = vec2(pixelationTilePixels) * sceneTextureSize.zw; + vec2 centerUv = (floor(uv / tileUV) + 0.5) * tileUV; + + vec2 local = (uv - centerUv) / tileUV; + float dist = length(local); + float edge = fwidth(dist) * 1.5; + float mask = 1.0 - smoothstep(0.35, 0.35 + edge, dist); + return mix(color, color * mask, pixelationIntensity); +} +``` + + + + +```wgsl +uniform pixelationTilePixels: f32; +uniform pixelationIntensity: f32; + +fn applyPixelation(color: vec3f, uv: vec2f) -> vec3f { + let tileUV = vec2f(uniform.pixelationTilePixels) * uniform.sceneTextureSize.zw; + let centerUv = (floor(uv / tileUV) + vec2f(0.5, 0.5)) * tileUV; + + let local = (uv - centerUv) / tileUV; + let dist = length(local); + let edge = fwidth(dist) * 1.5; + let mask = 1.0 - smoothstep(0.35, 0.35 + edge, dist); + return mix(color, color * mask, uniform.pixelationIntensity); +} +``` + + + + +エフェクトクラスはパラメーターをフィールドとして宣言し、`update` で適用します。 + +```javascript +class PixelationEffect extends CameraFrameEffect { + // 1 タイルのサイズ(スクリーンピクセル単位) + tilePixels = 8; + + // 元の画像(0)とピクセル化した画像(1)のブレンド + intensity = 0.5; + + constructor(device) { + super(device, 'pixelation', { + slot: COMPOSESLOT_LDR, + glsl: pixelationGLSL, + wgsl: pixelationWGSL + }); + } + + // 強度が 0 のときはエフェクトが画像を変更しないため、シェーダーから除外されます + get active() { + return this.enabled && this.intensity > 0; + } + + update() { + this.setUniform('pixelationTilePixels', this.tilePixels); + this.setUniform('pixelationIntensity', this.intensity); + } +} +``` + +データで駆動するエフェクトにはクラスは不要です。宣言を指定して `CameraFrameEffect` を構築し、ユニフォームを直接設定します。 + +### チャンクが読み取れるもの {#what-the-chunk-can-read} + +すべてのチャンクは、合成が提供するものを読み取れます。シーンカラーの `sceneTexture` と、シーンテクスチャの幅、高さ、1 / 幅、1 / 高さを保持する `sceneTextureSize` です。テクセルのオフセット、ピクセルサイズ、アスペクト比など、解像度に依存するものにはこのサイズを使用してください。 + +[`requires`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#requires) オプションに `FRAMERESOURCE_DEPTH` を指定したエフェクトは、`getLinearScreenDepth(uv)` でシーンの深度も読み取れます。 + +## エフェクトの登録 {#registering-effects} + +[`addEffect`](https://api.playcanvas.com/engine/classes/CameraFrame.html#addeffect) でエフェクトをカメラフレームに登録します。同じコンポーズスロットのエフェクトは登録した順に実行されるため、同じく `COMPOSESLOT_LDR` にある組み込みのビネットの後に追加したエフェクトは、ビネットの後に適用されます。エフェクトを別のエフェクトより前に配置するには、そのエフェクトまたはその ID を指定して [`insertEffectBefore`](https://api.playcanvas.com/engine/classes/CameraFrame.html#inserteffectbefore) で登録します。[組み込みエフェクト](#the-built-in-effects)を参照してください。 + +```javascript +const pixelation = new PixelationEffect(app.graphicsDevice); +cameraFrame.addEffect(pixelation); + +// または、ビネットの前に適用する場合 +cameraFrame.insertEffectBefore(pixelation, 'vignette'); + +cameraFrame.update(); +``` + +[`getEffect`](https://api.playcanvas.com/engine/classes/CameraFrame.html#geteffect) は登録済みのエフェクトを ID で返し、[`removeEffect`](https://api.playcanvas.com/engine/classes/CameraFrame.html#removeeffect) はそれを削除します。 + +カメラフレームのほかの部分と同様に、エフェクトは [`CameraFrame#update`](https://api.playcanvas.com/engine/classes/CameraFrame.html#update) が呼び出されたときに設定されます。パラメーターの変更や、エフェクトの追加と削除は、次の update で反映されます。カメラフレームは追加されたエフェクトを所有しません。エフェクトを削除したり、カメラフレームを破棄したりしてもエフェクトは破棄されないため、使用しなくなったら [`destroy`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#destroy) で破棄してください。 + +## パラメーター {#parameters} + +エフェクトは [`update`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#update) でパラメーターを適用します。カメラフレームは、エフェクトがアクティブな間、`CameraFrame#update` のたびにこれを呼び出します。 + +- [`setUniform`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#setuniform) は、チャンクが読み取るユニフォームの値を設定します。マテリアルのパラメーターと同様に値は参照で保持されるため、配列やテクスチャはその場で更新できます。 +- [`setDefine`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#setdefine) は、チャンクのバリアント用に compose シェーダーに define を設定します。define を変更すると compose シェーダーが再ビルドされるため、めったに変わらない設定には define を、それ以外にはユニフォームを使用してください。固定の define はコンストラクターで一度だけ設定できます。 + +[`enabled`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#enabled) はエフェクトのオンとオフを切り替えます。強度が 0 の場合など、パラメーターの値によっては何もしないエフェクトは [`active`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#active) をオーバーライドし、そのときはチャンクが compose シェーダーから除外されるようにします。 + +時間など、毎フレーム変わる値に依存する一部のエフェクトは、[`frameUpdate`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#frameupdate) を実装します。これはエフェクトが参加する各フレームで、そのフレームの値とともに呼び出されます。値には、前のフレームからの経過時間 `dt` や、シーンがレンダリングされるサイズが含まれます。 + +```javascript +frameUpdate(frame) { + this.time += frame.dt; + this.setUniform('waveTime', this.time); +} +``` + +自動露出のようにカメラの露出を調整するエフェクトは、[`cameraExposureScale`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#cameraexposurescale) からスケールを返します。 + +## デバッグ表示 {#debug-views} + +エフェクトはデバッグ表示を提供できます。デバッグ表示は、カメラの出力の代わりに中間結果を表示します。`debugViews` オプションに名前を列挙し、チャンク内でそれぞれに対して、表示する色を返す `debug()` 関数を実装します。 + +```javascript +super(device, 'pixelation', { + slot: COMPOSESLOT_LDR, + glsl, wgsl, + debugViews: ['pixelationMask'] +}); +``` + +表示するには、カメラフレームのデバッグ表示として設定します。 + +```javascript +cameraFrame.debug = 'pixelationMask'; +cameraFrame.update(); +``` + +## エフェクトのチャンクのオーバーライド {#overriding-the-chunk-of-an-effect} + +エフェクトのチャンクは、組み込みのシェーダーチャンクと同様に名前でオーバーライドできます。[`ShaderChunks`](https://api.playcanvas.com/engine/classes/ShaderChunks.html) に `composePS` という名前で設定したチャンクが、エフェクトのソースを置き換えます。これは組み込みエフェクトでも機能します。 + +```javascript +ShaderChunks.get(app.graphicsDevice, SHADERLANGUAGE_GLSL).set('composePixelationPS', myPixelationGLSL); +ShaderChunks.get(app.graphicsDevice, SHADERLANGUAGE_WGSL).set('composePixelationPS', myPixelationWGSL); +``` + +エフェクトがアクティブな間は、ほかのチャンクが判別できるように、その ID をアッパースネークケースにしたもの(この例では `PIXELATION`)が compose シェーダーで define されます。 + +## エディターでのエフェクト {#effects-in-the-editor} + +エディターでエフェクトを使用するには、[`camera-frame-effect.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/camera-frame-effect.mjs) の `CameraFrameEffectScript` から派生したスクリプトを作成し、[`cameraFrame` スクリプト](/user-manual/graphics/linear-workflow/hdr-rendering/#cameraframe-in-the-editor)と並べてカメラエンティティに追加します。スクリプトはエフェクトのパラメーターをスクリプト属性として宣言し、`createEffect` でエフェクトを作成し、`updateEffect` で属性をエフェクトにコピーします。 + +```javascript +import { CameraFrameEffectScript } from './camera-frame-effect.mjs'; + +class PixelationEffectScript extends CameraFrameEffectScript { + static scriptName = 'pixelationEffect'; + + /** + * @attribute + * @range [0, 1] + */ + intensity = 0.5; + + createEffect(device) { + return new PixelationEffect(device); + } + + updateEffect(effect) { + effect.intensity = this.intensity; + } +} +``` + +エフェクトは両方のスクリプトが存在するとすぐにカメラフレームに登録され、スクリプトの有効状態に従い、スクリプトとともに破棄されます。エフェクトのデバッグ表示を公開するには、スクリプトの `debugView` ゲッターをオーバーライドします。エンジンリポジトリにある[レンズフレア](lens-flare.md)、SMAA、自動露出の各エフェクトのスクリプトが完全な例になります。 + +## 例 {#example} + +`COMPOSESLOT_LDR` で、組み込みのビネットの後に適用されるピクセル化エフェクト。 + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md new file mode 100644 index 00000000000..a6f3afdc00b --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md @@ -0,0 +1,111 @@ +--- +title: ダイナミックスカイ +description: "CameraFrame のダイナミックスカイエフェクト:解析的な昼の空、夜空、フラットな雲とボリューメトリックな雲、同期される太陽のライト、空から生成されるシーンのライティング。" +--- + +[`DynamicSkyEffect`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html) は、シーンの空の代わりにカメラの空をレンダリングします。太陽に照らされる解析的な大気による昼の空、星と月の夜空、そしてオプションで風に流れる雲です。太陽の位置で時刻が決まり、エフェクトはディレクショナルライトを太陽に同期させることができます。また、空からシーンのライティングを生成します。 + +ダイナミックスカイはカメラフレームの組み込みエフェクトではありません。作成して、[`addEffect`](https://api.playcanvas.com/engine/classes/CameraFrame.html#addeffect) でカメラフレームに追加してください。 + +```javascript +const sky = new DynamicSkyEffect(app.graphicsDevice); +sky.light = sunEntity.light; +sky.cloudType = CLOUDTYPE_VOLUMETRIC; +cameraFrame.addEffect(sky); +cameraFrame.update(); +``` + +すべてのエフェクトと同様に、設定は次の [`CameraFrame#update`](https://api.playcanvas.com/engine/classes/CameraFrame.html#update) で適用されます。太陽を動かす昼夜サイクルでは、これを毎フレーム呼び出します。 + +```javascript +let time = 0; +app.on('update', (dt) => { + time += dt; + sky.elevation = Math.sin(time * 0.1) * 60; + cameraFrame.update(); +}); +``` + +## 太陽と時刻 {#the-sun-and-the-time-of-day} + +太陽の方位角 [`azimuth`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#azimuth) と仰角 [`elevation`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#elevation)(いずれも度単位)で時刻を設定します。仰角が 0 未満の場合、太陽は地平線の下に置かれ、夜空になります。[`rotation`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#rotation) は空全体を鉛直軸の周りに回転させるため、空、太陽、月が一緒に回転します。 + +エフェクトは、[`light`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#light) に割り当てられたディレクショナルライトを空と同期させます。同期されるのはライトの方向、色、強度で、したがってライトが落とす影も空に合わせて変化します。昼間、ライトは [`sunIntensity`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#sunintensity) の強度で太陽から照らし、雲によって減光されます。夜間は月光へとフェードし、強度は [`moonIntensity`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#moonintensity)、色は [`moonColor`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#mooncolor) になります。空も `sunIntensity` に応じてスケーリングされるため、`sunIntensity` で一日全体の明るさを設定し、カメラの露出に合わせます。 + +## 昼の空 {#the-day-sky} + +| プロパティ | 説明 | +| -------- | ----------- | +| `turbidity` | 大気のかすみ具合で、1 から 20 の範囲です。値が大きいほど空が白く霞み、太陽の周りの光彩が大きくなります。 | +| `rayleigh` | レイリー散乱の量で、0 から 4 の範囲です。空の青さを設定します。 | +| `mieCoefficient` | ミー散乱の量で、0 から 0.1 の範囲です。太陽の周りの光彩の明るさを設定します。 | +| `mieDirectionalG` | 光彩が太陽の周りにどれだけ集中するかで、0 から 1 の範囲です。 | +| `luminance` | 太陽光に対する空の明るさ。1 では、空は晴天の空と同じようにシーンを照らすため、1 つの露出で空と日の当たる地面の両方に対応できます。 | +| `twilightGlow` | 太陽が地平線のすぐ下にある間の、地平線の暖かな光彩の強さ。 | + +## 夜空 {#the-night-sky} + +| プロパティ | 説明 | +| -------- | ----------- | +| `nightColor`、`nightBrightness` | 夜空の色と明るさ。 | +| `starBrightness` | 星の明るさ。0 で星を無効にします。 | +| `starDensity` | 星を含む空のセルの割合で、0 から 1 の範囲です。 | +| `starSize` | 星のサイズのスケール。 | +| `moonDirection` | 月への方向。空の回転に応じて回転します。 | +| `moonSize` | 月の円盤の角半径(度)。 | +| `moonGlow` | 月の円盤の明るさ。 | + +## 雲 {#clouds} + +デフォルトでは空に雲はありません。雲を追加するには [`cloudType`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#cloudtype) を設定します。 + +- `CLOUDTYPE_FLAT`:シンプルなシェーディングを施した、ノイズによる単一のフラットな層で、低コストです。空の他の部分と同様に、空のシェーダーがピクセルごとに雲を評価するため、[XR](#xr) でも動作します。 +- `CLOUDTYPE_VOLUMETRIC`:3D ノイズの層をレイマーチするボリューメトリックな雲です。空の中でコストの高い部分であり、専用のテクスチャに低い解像度でレンダリングされます。ノイズは初めて使用されるときに CPU で生成されます。 + +| プロパティ | 説明 | +| -------- | ----------- | +| `cloudCoverage` | 雲に覆われる空の割合で、0 から 1 の範囲です。快晴から曇天までを表します。 | +| `cloudSoftness` | 雲の柔らかさで、0.01 から 1 の範囲です。輪郭のくっきりした濃い雲から、半透明で薄くたなびく雲までを表します。 | +| `cloudOpacity` | 雲の不透明度で、0 から 1 の範囲です。 | +| `cloudBrightness` | 雲に当たる太陽光の明るさ。 | +| `cloudNightBrightness` | 夜間に雲に当たる月光の明るさ。 | +| `cloudSize` | 雲のパターンが繰り返される距離(メートル)。値が大きいほど雲が大きくなります。 | +| `cloudHeight` | 雲の層の高度(メートル)。 | +| `cloudThickness` | ボリューメトリックな雲の層の厚さ(メートル)。 | +| `cloudSunDimming` | 被覆率が最大のときに雲が遮る太陽光の割合で、ライトを減光します。 | +| `windSpeed`、`windDirection` | 雲が流れる速度(メートル毎秒)と、雲が流れていく方向(度)。雲はアプリケーションの時間に合わせて流れます。 | + +ボリューメトリックな雲のコストは、次の 2 つのプロパティで設定します。 + +- [`cloudQuality`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#cloudquality)(0 から 1)は、雲の層を通るステップ数と、ライトに向かうステップ数を設定します。コストはほぼ線形に増加し、0.5 から 1 で 2 倍になります。値を下げると輪郭がぼやけ、シェーディングが平坦になります。 +- [`cloudResolution`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#cloudresolution)(0.25 から 1)は、シーンに対する雲のレンダリング解像度です。雲は柔らかいため、解像度を下げても細部はほとんど失われず、コストの大部分を削減できます。 + +## シーンのライティング {#lighting-the-scene} + +[`lighting`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#lighting) が有効な間、エフェクトは空からシーンのライティングを生成します。これはマテリアルを照らす環境アトラスで、[`Scene#envAtlas`](https://api.playcanvas.com/engine/classes/Scene.html#envatlas) に割り当てられます。ライティングを生成するエフェクトは 1 つだけ、メインカメラのエフェクトにしてください。 + +ライティングの生成にはコストがかかるため、エフェクトは空が十分に変化したときにのみライティングを再生成します。 + +- [`lightingThreshold`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#lightingthreshold) は、ライティングが再生成されるまでに太陽が移動する角度です。 +- [`lightingCloudThreshold`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#lightingcloudthreshold) は、ライティングが再生成されるまでに雲が流れる距離で、雲のサイズに対する割合で指定します。デフォルトの 0 では無効になります。 +- [`lightingInterpolation`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#lightinginterpolation) は、前回の更新のライティングから最新のライティングへとブレンドし、一度に切り替わるのではなく、徐々に変化する空に滑らかに追従させます。 +- [`bakeLighting`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#bakelighting) は、次のフレームの前にライティングを再生成します。エフェクトが自動では再生成しない変更に使用します。 + +[`lightingResolution`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#lightingresolution) はライティングの生成元となる空の解像度で、[`atlasSize`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#atlassize) は環境アトラスのサイズです。アトラスが大きいほど、光沢のあるサーフェスの反射がシャープになります。 + +平面反射をレンダリングするカメラなど、エフェクトを持たないカメラは、その環境アトラスからシーンの空をレンダリングします。つまり、この空の静止画像です。 + +## XR {#xr} + +XR では、シーンの他の部分と同様に空は各ビューごとにレンダリングされ、フラットな雲も同様です。ボリューメトリックな雲は、カメラのビューに対して一度だけテクスチャにレンダリングされ、各ビューはそのテクスチャから雲を読み取ります。目のビューがカメラのビューの外側まで広がる部分では、テクスチャに読み取る雲がないため、テクスチャの端で雲が筋状に引き伸ばされます。XR では `CLOUDTYPE_FLAT` の雲を使用してください。 + +## 制限事項 {#limitations} + +- ボリューメトリックな雲は XR ではサポートされていません。[XR](#xr) を参照してください。 +- 空は、カメラフレームの[ボリューメトリックフォグ](/user-manual/graphics/posteffects/cameraframe/volumetric-fog/)と連携しません。雲はフォグに影を落とさず、フォグは空の光で照らされません。 + +## 例 {#example} + +昼夜サイクルを通した解析的な大気です。星、月、風に流れるボリューメトリックな雲があり、シーンを照らし、太陽のライトを空と同期させます。 + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/effect-passes.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/effect-passes.md new file mode 100644 index 00000000000..02a8dda85d6 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/effect-passes.md @@ -0,0 +1,167 @@ +--- +title: パスを持つエフェクト +description: "フレームパスを持つ CameraFrame のエフェクトを作成:カメラフレームのステージ、フレームリソース、パスの作成と再構築、フレームごとの値、シーンの解決。" +--- + +[カスタムエフェクト](custom-effects.md)は、合成ではピクセルあたり 1 回のサンプリングで行えない処理のために、[フレームパス](/user-manual/graphics/advanced-rendering/frame-graph/frame-passes/)を持つことができます。低解像度でのブラー、シーンへの描画、時間をかけたフレームの蓄積などです。組み込みのブルーム、被写界深度、テンポラルアンチエイリアスは、パスを持つエフェクトです。 + +カメラフレームはエフェクトにパスを要求し、シーンの深度など、エフェクトが必要とする**フレームリソース**を渡し、各パスをそれが属するフレームの**ステージ**で実行します。エフェクトはパスをほかのレンダーパスと同じように作成し、通常は[シェーダークアッドパス](/user-manual/graphics/advanced-rendering/frame-graph/render-pass-shader-quad/)として作成します。エフェクトのチャンクは、パスがレンダリングしたものを読み取れます。パスを持つエフェクトには、コンポーズスロットは必要ありません。 + +## カメラフレームのステージ {#the-stages-of-the-camera-frame} + +カメラフレームは、シーンをレンダリングするパスの間にある 5 つのステージで、エフェクトのパスをレンダリングします。 + +```mermaid +flowchart TB + subgraph Scene[シーンのレンダリング] + direction LR + Prepass[深度
プリパス] --> PreScene[[preScene]] --> Opaque[不透明な
シーン] --> Grab[シーンカラーの
コピー] --> PostOpaque[[postOpaque]] --> Transparent[半透明な
シーン] + end + subgraph Resolving[シーンの解決] + direction LR + PostScene[[postScene]] --> Resolve[[resolve]] --> Half[半解像度の
シーン] --> PostResolve[[postResolve]] + end + subgraph Output[出力] + direction LR + Compose[合成] --> After["UI など、
合成の後の
レイヤー"] + end + Scene --> Resolving --> Output +``` + +二重枠のボックスがステージで、エフェクトのパスはここで実行されます。 + +| ステージ | 実行タイミング | 用途 | エンジンのエフェクト | +| ----- | ---- | ---------- | --------------------- | +| `preScene` | 深度プリパスがレンダリングされる場合はその後、シーンの前。 | シーンのマテリアルが読み取るテクスチャのレンダリング。 | `SSAOTYPE_LIGHTING` タイプのアンビエントオクルージョン、ダイナミックスカイ | +| `postOpaque` | 不透明ジオメトリ、スカイボックス、シーンカラーのコピーの後、半透明ジオメトリの前。 | 半透明ジオメトリより下になる、シーンへの描画。 | なし | +| `postScene` | シーンの後、シーンが解決される前。 | シーンへの描画。 | `SSAOTYPE_COMBINE` タイプのアンビエントオクルージョン、ボリューメトリックフォグ | +| `resolve` | シーンの解決。 | アンチエイリアスなど、シーンカラーを読み取って新しいシーンカラーをレンダリングするパス。[シーンの解決](#resolving-the-scene)を参照してください。 | TAA、SMAA | +| `postResolve` | シーンが解決され、その半解像度のコピーが作成された後、合成の前。 | ブルームなど、最終的なシーンカラーの処理。 | 被写界深度、ブルーム、レンズフレア、自動露出 | + +各ステージでは、エフェクトのパスはエフェクトが登録された順に実行されます。まず組み込みエフェクトが[組み込みエフェクト](custom-effects.md#the-built-in-effects)に記載された順に実行され、その後にカメラフレームに追加されたエフェクトが追加された順に実行されます。ただし、`insertEffectBefore` でほかのエフェクトの前に挿入した場合は除きます。 + +## フレームリソース {#frame-resources} + +エフェクトは必要なフレームリソースを [`requires`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#requires) オプションに列挙し、カメラフレームはエフェクトがアクティブな間それらを提供します。 + +| リソース | 提供されるもの | +| -------- | -------- | +| `FRAMERESOURCE_DEPTH` | `resources.depth`。シーンの深度。 | +| `FRAMERESOURCE_PREPASSDEPTH` | `resources.prepassDepth`。シーンのレンダリング前に深度プリパスでレンダリングされたシーンの深度で、`preScene` ステージのパス用です。 | +| `FRAMERESOURCE_SCENECOLORHALF` | `resources.sceneColorHalf`。解決されたシーンカラーの半解像度のコピー。 | +| `FRAMERESOURCE_SCENECOLORGRAB` | `resources.sceneColorGrab`。不透明なシーンカラーのコピーのハンドルで、シェーダーは `postOpaque` ステージから `uSceneColorMap` としてサンプリングします。 | +| `FRAMERESOURCE_SCENETARGET` | `resources.sceneTarget`。シーンのレンダリング先のレンダーターゲットで、シーンに描画するパス用です。 | + +リソースはパスが存在する間は同じオブジェクトのままなので、パスに一度渡すだけで済みます。リソースのテクスチャはキャンバスに合わせてサイズが変更されるため、サイズを保持しないでください。必要なリソースがパラメーターに依存するエフェクトは、`requires` をゲッターとしてオーバーライドします。 + +シーンの深度の格納方法はそれをレンダリングするものによって異なるため、シェーダーは格納方法を表す define を指定したうえで、`screenDepthPS` チャンクの `getLinearScreenDepth` 関数で深度を読み取ります。深度の define をシェーダーに渡し、深度のキーをシェーダーの一意な名前に追加して、格納方法の異なる深度がコンパイル済みシェーダーを共有しないようにします。 + +```javascript +const { depth } = resources; +this.shader = ShaderUtils.createShader(device, { + uniqueName: `InkOutlineShader-${depth.key}`, + attributes: { aPosition: SEMANTIC_POSITION }, + vertexChunk: 'quadVS', + fragmentDefines: depth.defines, + fragmentGLSL: inkGLSL, // "screenDepthPS" をインクルードし、getLinearScreenDepth(uv) を呼び出します + fragmentWGSL: inkWGSL +}); +``` + +## パスの作成 {#creating-the-passes} + +エフェクトは [`createPasses`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#createpasses) で、必要なリソースからパスを作成し、各パスを実行されるステージの配列に実行順に追加します。1 つのエフェクトで複数のステージのパスを実行できます。パスは [`destroyPasses`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#destroypasses) で解放します。 + +```javascript +class InkOutlineEffect extends CameraFrameEffect { + width = 3; + + _pass = null; + + constructor(device) { + super(device, 'inkOutline', { + requires: [FRAMERESOURCE_SCENECOLORGRAB, FRAMERESOURCE_SCENETARGET, FRAMERESOURCE_DEPTH] + }); + } + + createPasses(resources, passes) { + // このパスは、不透明ジオメトリの後にシーンへ線を描画します + this._pass = new RenderPassInkOutline(this.device, this.cameraFrame.cameraComponent, resources.depth); + this._pass.init(resources.sceneTarget); + passes.postOpaque.push(this._pass); + } + + destroyPasses() { + this._pass?.destroy(); + this._pass = null; + } + + // cameraFrame.update() から呼び出され、パラメーターをパスに適用します + update() { + this._pass.width = this.width; + } +} +``` + +エフェクトのレンダーターゲットにレンダリングするパスは、たとえば半解像度のシーンに追従させるために、[`resizeSource`](/user-manual/graphics/advanced-rendering/frame-graph/render-pass/#resizing-the-render-target) でそのサイズを設定します。テクスチャは参照で保持されるため、エフェクトのチャンクは、`setUniform` で設定したユニフォームを通じてパスがレンダリングしたものを読み取ります。 + +### パスが再構築されるタイミング {#when-the-passes-are-rebuilt} + +カメラフレームは、自身のパスを構築するときにエフェクトのパスを作成します。最初の `CameraFrame#update` で作成し、パスを再構築するたびに、以前のパスを `destroyPasses` で解放したうえで再び作成します。パスが再構築されるのは、カメラフレームのオプションが変更されたとき、パスを持つエフェクトが追加、削除、有効化、無効化されたとき、およびそのようなエフェクトの必要なリソースまたは [`buildKey`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#buildkey) が変わったときです。 + +`buildKey` は、パスの再作成が必要になるたびに変わる文字列を返します。つまり、パスのユニフォームではなく、パスそのものを変えるパラメーターです。パスが `update` で取り込むパラメーターは含めないでください。 + +```javascript +buildKey() { + // 高品質ではパスが 1 つ多くレンダリングされます + return `${this.highQuality}`; +} +``` + +`destroyPasses` は、パスの構築時に非アクティブだったエフェクトなど、エフェクトがパスを持たないときにも呼び出され、`destroy` からも呼び出されます。そのため、例のように存在するものだけを破棄し、参照をクリアしてください。 + +## フレームごとの値 {#values-of-each-frame} + +リソースはフレームごとに変わりません。シーンがレンダリングされる解像度や、テンポラルアンチエイリアスが 2 つのテクスチャを交互に切り替える解決済みのシーンカラーなど、フレームごとに変わる値は、どのパスもレンダリングする前に、毎フレーム [`frameUpdate`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#frameupdate) に渡されます。 + +```javascript +frameUpdate(frame) { + // パスのレンダリング先である、シーンのレンダーターゲットの 1 ピクセルのサイズ + this._pass.texelWidth = 1 / frame.renderWidth; + this._pass.texelHeight = 1 / frame.renderHeight; +} +``` + +## シーンの解決 {#resolving-the-scene} + +パスが `resolve` ステージで実行されるエフェクトは、フレームの残りの部分が読み取るシーンカラーを、パスがレンダリングしたもので置き換えます。カメラフレームは毎フレーム、`frameUpdate` の前に、解決中のシーンを渡してエフェクトの [`resolveScene`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html#resolvescene) を呼び出します。エフェクトは、それより前のエフェクトが解決したシーンカラーをパスに渡し、そのシーンカラーを、このフレームでパスがレンダリングするテクスチャに置き換えます。 + +```javascript +resolveScene(scene) { + this._pass.setSourceTexture(scene.texture); + scene.texture = this._pass.outputTexture; +} +``` + +アップスケーラーなど、シーンを別の解像度に解決するエフェクトは、シーンの `width` と `height` も設定します。 + +## TypeScript {#typescript} + +TypeScript では、オーバーライドしたメソッドのパラメーターには基底クラスから型が付きません。フックの型である [`CameraFrameEffectResources`](https://api.playcanvas.com/engine/interfaces/CameraFrameEffectResources.html)、[`CameraFrameEffectPasses`](https://api.playcanvas.com/engine/interfaces/CameraFrameEffectPasses.html)、[`CameraFrameEffectScene`](https://api.playcanvas.com/engine/interfaces/CameraFrameEffectScene.html)、[`CameraFrameEffectContext`](https://api.playcanvas.com/engine/interfaces/CameraFrameEffectContext.html) をインポートして、パラメーターに型注釈を付けてください。 + +```typescript +import { CameraFrameEffect } from 'playcanvas'; +import type { CameraFrameEffectPasses, CameraFrameEffectResources } from 'playcanvas'; + +class BlurEffect extends CameraFrameEffect { + createPasses(resources: CameraFrameEffectResources, passes: CameraFrameEffectPasses) { + // ... + } +} +``` + +## 例 {#example} + +インクアウトラインエフェクトは、不透明なシーンの深度または明るさが急に変化する箇所に線を描画します。そのパスは `postOpaque` ステージで実行され、不透明なシーンカラーのコピーとシーンの深度を読み取ってシーンに描画するため、半透明ジオメトリは線の上にレンダリングされます。 + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/index.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/index.md index 61966b7f4bd..08d2ed3def4 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/index.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/index.md @@ -13,7 +13,8 @@ PlayCanvas では、視覚的に高度でパフォーマンスのよいモダン - **[ボリューメトリックフォグ](volumetric-fog.md)** — ディレクショナル、オムニ、スポットライトで照らされる高さに応じたフォグと光の筋 - **SSAO(Screen Space Ambient Occlusion)** — 環境光の遮蔽をシミュレートして奥行き感を強調 - **被写界深度(DoF)** — カメラのフォーカスを模し、焦点面の外側をぼかす -- **時間的反エイリアシング(TAA)** — 時間方向にエッジを平滑化し、ギザつきを軽減 +- **テンポラルアンチエイリアス(TAA)** — 時間方向にエッジを平滑化してギザつきを軽減。低い解像度でレンダリングしたシーンを[アップスケール](#taa-upscaling)することもできます +- **[SMAA(Subpixel Morphological Anti-Aliasing)](#smaa)** — カメラをジッターさせず、ゴーストも生じさせずに、単一フレーム内でギザつきを平滑化 - **ビネット** — `vignette.color` で指定した色に向かって画像の端をブレンド - **カラーグレーディング** — 明るさ、コントラスト、彩度、色味を調整してスタイルを付与 - **[カラーエンハンス](#color-enhance)** — HDR でシャドウ、ハイライト、中間調、自然な彩度、かすみの除去を調整 @@ -21,6 +22,11 @@ PlayCanvas では、視覚的に高度でパフォーマンスのよいモダン - **フリンジ** — 色チャンネルの分離をシミュレートする色収差 - **トーンマッピング** — HDR の色を表示可能な範囲へマッピングする方法を制御 - **シャープネス** — TAA やアップスケールによるぼけを抑えるため画像の鮮明さを強調 +- **[自動露出](auto-exposure.md)** — 見えているものの明るさに合わせてカメラの露出を順応させる +- **[レンズフレア](lens-flare.md)** — ライトのフレア。ライトがシーンに隠れるにつれてフェードします +- **[ダイナミックスカイ](dynamic-sky.md)** — 雲のある昼夜の空。シーンを照らし、太陽のライトを同期させます + +トーンマッピングを除き、これらはそれぞれエフェクト、つまり [`CameraFrameEffect`](https://api.playcanvas.com/engine/classes/CameraFrameEffect.html) です。多くは `cameraFrame.bloom` や `cameraFrame.vignette` のようにカメラフレームに組み込まれています。SMAA、自動露出、レンズフレア、ダイナミックスカイは、[`addEffect`](https://api.playcanvas.com/engine/classes/CameraFrame.html#addeffect) でカメラフレームに追加します。[独自のエフェクト](custom-effects.md)を作成することもできます。 ## セットアップと使い方 @@ -59,9 +65,57 @@ cameraFrame.update(); `inner`、`outer`、`curvature` は形状と減衰を制御します。`outer` を `inner` より小さくすると効果が反転し、中央に向かって適用されます。 +### TAA アップスケール {#taa-upscaling} + +`rendering.renderTargetScale`(0.1~1)は、最適化のためにシーンを低い解像度でレンダリングします。ポストプロセスもその解像度で実行され、画像はその後バイリニアフィルタリングでフル解像度に引き伸ばされ、その上に UI がフル解像度でレンダリングされます。 + +TAA が有効な場合、[`taa.upscale`](https://api.playcanvas.com/engine/classes/TaaEffect.html#upscale) を使うと、低い解像度でレンダリングしたフレームを、代わりにフル解像度で蓄積します。カメラのジッターによって各フレームのサンプルがフル解像度のピクセル内の異なる位置に置かれるため、低い解像度で失われるディテールの多くを取り戻せます。ブルームや合成など、TAA の後のエフェクトは、フル解像度のシーンを扱います。 + +```javascript +cameraFrame.rendering.renderTargetScale = 0.5; +cameraFrame.taa.enabled = true; +cameraFrame.taa.upscale = true; +cameraFrame.rendering.sharpness = 0.5; +cameraFrame.update(); +``` + +アップスケール中は履歴に長く頼るため、動くオブジェクトは TAA 単体のときよりもぼやけたり、ゴーストが出たりすることがあります。`rendering.sharpness` は、TAA が残す柔らかさを打ち消します。エディターでは、`cameraFrame` スクリプトがこれらを `taa.upscale`、`rendering.renderTargetScale`、`rendering.sharpness` 属性として公開しています。[例](#examples)の「時間的反エイリアシング」の例では、フル解像度と低い解像度を比較し、バイリニアフィルタリングまたは TAA によるアップスケールを、シャープニングの有無とあわせて確認できます。 + +### SMAA {#smaa} + +[`SmaaEffect`](https://api.playcanvas.com/engine/classes/SmaaEffect.html)(Subpixel Morphological Anti-Aliasing)は、ジオメトリのギザギザしたエッジを単一フレーム内で平滑化します。画像内のエッジを見つけ、エイリアスの生じた各エッジの形状を認識し、エイリアシングがなければそのエッジが覆う面積に応じて、エッジに沿ったピクセルを隣接するピクセルとブレンドします。TAA と異なり、カメラをジッターさせず、履歴を必要とせず、ゴーストも生じませんが、1 ピクセルより細かいディテールは解像できません。 + +SMAA はカメラフレームの組み込みエフェクトではないため、`addEffect` で追加します。 + +```javascript +const smaa = new SmaaEffect(app.graphicsDevice); +cameraFrame.addEffect(smaa); +cameraFrame.update(); +``` + +4 つのプロパティで品質とコストを設定します。デフォルトは、リファレンス実装の High プリセットです。 + +| プリセット | `threshold` | `maxSearchSteps` | `maxSearchStepsDiag` | `cornerRounding` | +| ---------- | ----------- | ---------------- | -------------------- | ---------------- | +| Low | 0.15 | 4 | 0 | 100 | +| Medium | 0.1 | 8 | 0 | 100 | +| High(デフォルト) | 0.1 | 16 | 8 | 25 | +| Ultra | 0.05 | 32 | 16 | 25 | + +- `threshold`(0~0.5)は、隣接するピクセルの間をエッジで隔てられているとみなす明るさの差です。値を小さくするとより多くのエッジを平滑化しますが、コストが上がり、ジオメトリとあわせてテクスチャのディテールも平滑化されることがあります。 +- `maxSearchSteps`(1~112)は、水平または垂直のエッジの端を探す最大ステップ数で、各ステップは 2 ピクセルを対象とします。より長いエッジの端は見つからないため、その平滑化の精度が下がります。 +- `maxSearchStepsDiag`(0~20)は、対角線のエッジに対する同じ値です。0 にすると対角線のエッジの検出が無効になり、それらは水平および垂直のエッジとして平滑化されます。 +- `cornerRounding`(0~100)は、ジオメトリの鋭い角をどの程度丸めるかです。0 で角を最も鋭く保ち、100 で他のエッジと同様に平滑化します。 + +SMAA はマルチサンプリング(`rendering.samples`)と組み合わせることができ、より高いコストでエッジをさらに平滑化します。TAA と組み合わせた場合は、`cameraFrame.insertEffectBefore(smaa, 'taa')` で TAA の前に挿入しない限り、TAA の後にシーンを解決します。デバッグ表示の `'smaaedges'` と `'smaaweights'` は、検出したエッジと、エッジをまたいでピクセルをどの程度ブレンドするかを表示します。エディターでは、[`smaa.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/smaa.mjs) の `smaaEffect` スクリプトが、それが付いているカメラエンティティのカメラフレームに SMAA を追加します。 + +次の例では、SMAA のプリセットを TAA、TAA アップスケール、マルチサンプリングと比較します。 + + + ### デバッグ表示 {#debug-views} -`cameraFrame.debug` に `'scene'`、`'ssao'`、`'bloom'`、`'vignette'`、`'dofcoc'`(被写界深度の錯乱円)、`'dofblur'`、`'depth'` のいずれかを設定すると、中間結果を確認できます。 +`cameraFrame.debug` に `'scene'`、`'ssao'`、`'bloom'`、`'vignette'`、`'dofcoc'`(被写界深度の錯乱円)、`'dofblur'`、`'depth'` のいずれかを設定すると、中間結果を確認できます。カメラフレームに追加したエフェクトは、独自の表示を提供します。[SMAA](#smaa) には `'smaaedges'` と `'smaaweights'`、[自動露出](auto-exposure.md#debug-view)には `'autoexposure'`、[レンズフレア](lens-flare.md)には `'lensflare'` があります。 ```javascript cameraFrame.debug = 'depth'; @@ -72,7 +126,7 @@ cameraFrame.update(); Gaussian Splat でフォグや被写界深度を使用する場合は、[シーン深度の要件](volumetric-fog.md#gaussian-splats)を参照してください。 -## 例 +## 例 {#examples} - HDR と Bloom、LUT — HDR ブルームとカラールックアップテーブルをデモ diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/lens-flare.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/lens-flare.md new file mode 100644 index 00000000000..c9b1e05f4ba --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/lens-flare.md @@ -0,0 +1,97 @@ +--- +title: レンズフレア +description: "CameraFrame のレンズフレアエフェクト:形状ライブラリ、フレアのルックとそのエレメント、ライトへの追従、オクルージョンとフェード、エディター用のレンズフレアスクリプト。" +--- + +[`LensFlareEffect`](https://api.playcanvas.com/engine/classes/LensFlareEffect.html) は、シーン内のライト、または任意の位置のフレアを描画します。各フレアはライトに追従する [`LensFlare`](https://api.playcanvas.com/engine/classes/LensFlare.html) で、その**ルック**が、ライトから画面の中心を通る軸に沿って形状を配置します。フレアは、ライトがシーンに覆われたとき、ライトが画面から外れたとき、そしてスポットライトの場合はカメラがその円錐の外に出たときにフェードします。 + +レンズフレアはカメラフレームの組み込みエフェクトではありません。作成して、[`addEffect`](https://api.playcanvas.com/engine/classes/CameraFrame.html#addeffect) でカメラフレームに追加してください。このエフェクトはフレアをシーンより低い解像度で描画し、トーンマッピングの前にリニア HDR 空間でシーンに加算します。 + +## フレアのセットアップ {#setting-up-the-flares} + +エフェクトは、すべてのフレアの形状を 1 つの**形状ライブラリ**から描画します。形状ライブラリはテクスチャの集まりで、エフェクトはそれらをテクスチャ配列にコピーし、フレアのエレメントは名前でそれらを参照します。形状は、カラーチャンネルに形状を、アルファに被覆率を持つテクスチャで、たとえば透明な背景上の白い形状です。 + +```javascript +const lensFlare = new LensFlareEffect(app.graphicsDevice); +lensFlare.shapes = { + shapes: { + glow: glowAsset.resource, + star: starAsset.resource, + disc: discAsset.resource + } +}; +cameraFrame.addEffect(lensFlare); + +// 太陽に追従するフレア:グロー、スター、4 つのゴーストの列 +const sun = new LensFlare({ + elements: [ + { shape: 'glow', size: 0.5, color: [1, 0.9, 0.7], intensity: 0.5 }, + { shape: 'star', size: 1.2, intensity: 0.4 }, + { shape: 'disc', offset: 0.5, size: 0.08, color: [0.4, 0.8, 1], count: 4, spread: 0.4 } + ] +}, { light: sunEntity.light }); +lensFlare.add(sun); + +cameraFrame.update(); +``` + +形状はすべて同じ解像度(デフォルトで 512 ピクセル)で格納され、この解像度は形状ライブラリの `resolution` で設定します。フレアを出す各ライトに `LensFlare` を作成し、[`add`](https://api.playcanvas.com/engine/classes/LensFlareEffect.html#add) でエフェクトに登録します。[`remove`](https://api.playcanvas.com/engine/classes/LensFlareEffect.html#remove) で登録を解除します。 + +## ルック {#looks} + +フレアのルックは、順に描画されるエレメントのリストです。ルックは単純なデータで、任意の数のフレアで共有でき、JSON アセットから読み込むこともできます。各エレメントは、フレアの軸上に形状を配置します。 + +| プロパティ | 説明 | +| -------- | ----------- | +| `shape` | エフェクトの形状ライブラリ内の形状の名前。 | +| `offset` | 軸に沿った位置。0 が光源、1 が画面の中心、2 が中心を挟んで反対側の鏡像の位置で、負の値は光源の先になります。デフォルトは 0 です。 | +| `size` | エレメントの高さ(画面の高さに対する割合)。デフォルトは 0.1 です。 | +| `aspect` | 高さに対するエレメントの幅。デフォルトは 1 です。 | +| `rotation` | エレメントの回転(度)。デフォルトは 0 です。 | +| `autoRotate` | エレメントが軸に合わせて回転するかどうか。ストリーク向けです。デフォルトは false です。 | +| `color` | エレメントの色。`Color` または 3 つの数値の配列で指定します。デフォルトは白です。 | +| `intensity` | エレメントの明るさ。デフォルトは 1 です。 | +| `count` | エレメントのコピーの数。コピーはオフセットを中心に軸に沿って配置され、ゴーストの列になります。デフォルトは 1 です。 | +| `spread` | コピーが広がる軸に沿った距離(オフセットの単位)。デフォルトは 0 です。 | + +ルックのエレメントは、`CameraFrame#update` が呼び出されたときにエフェクトに組み込まれます。ルックを変更するには、同じオブジェクトであってもフレアの [`look`](https://api.playcanvas.com/engine/classes/LensFlare.html#look) に再度代入し、カメラフレームを更新してください。 + +## フレアの光源 {#the-source-of-a-flare} + +`LensFlare` コンストラクターの第 2 引数で、フレアが追従する対象を選択します。 + +- `light`:ライトコンポーネント。ディレクショナルライトは光源の方向を、オムニまたはスポットライトは光源の位置を与えます。スポットライトの場合は、フレアがその円錐内に限定されます。 +- `node`:位置が光源となるノード。ライトのないフレアに使用します。 +- `position`:ワールド位置。ライトもノードもない光源に使用します。 +- `direction`:無限遠にある光源へ向かうワールド方向。ライトのないフレアに使用します。 + +[`useLightColor`](https://api.playcanvas.com/engine/classes/LensFlare.html#uselightcolor) または [`useLightIntensity`](https://api.playcanvas.com/engine/classes/LensFlare.html#uselightintensity) をオフにしない限り、ライトの色でフレアが色付けされ、ライトの強度でフレアがスケーリングされます。フレアの [`color`](https://api.playcanvas.com/engine/classes/LensFlare.html#color)、[`intensity`](https://api.playcanvas.com/engine/classes/LensFlare.html#intensity)、[`scale`](https://api.playcanvas.com/engine/classes/LensFlare.html#scale) は、そのすべてのエレメントをそれぞれ色付けし、明るくし、拡大縮小します。 + +## オクルージョンとフェード {#occlusion-and-fading} + +エフェクトは、各フレアの光源の周囲でシーン深度をテストすることで、光源がシーンにどれだけ覆われているかを測定し、それに応じてフレアをフェードさせます。この動作は、フレアの次のプロパティで調整します。 + +- [`occlusionRadius`](https://api.playcanvas.com/engine/classes/LensFlare.html#occlusionradius) は、深度をテストする光源周囲の半径です。部分的に覆われた光源は、部分的にフェードします。 +- [`occlusionFade`](https://api.playcanvas.com/engine/classes/LensFlare.html#occlusionfade) は、測定された可視性が変化に追従するまでの時間で、フェードを滑らかにします。 +- [`occlusionOffset`](https://api.playcanvas.com/engine/classes/LensFlare.html#occlusionoffset) は、深度テストを光源からカメラの方向へ移動し、ランプのジオメトリが自身の光を覆わないようにします。 +- [`edgeFade`](https://api.playcanvas.com/engine/classes/LensFlare.html#edgefade) は、光源が画面から外れるときにフレアがフェードアウトする、画面端の帯の幅です。[`allowOffScreen`](https://api.playcanvas.com/engine/classes/LensFlare.html#allowoffscreen) は、光源が画面のすぐ外にある間もフレアの描画を続けます。 +- [`fadeStart`](https://api.playcanvas.com/engine/classes/LensFlare.html#fadestart) と [`fadeEnd`](https://api.playcanvas.com/engine/classes/LensFlare.html#fadeend) は、位置を持つ光源のフレアを、カメラからの距離に応じてフェードアウトさせます。 + +フレアのプロパティは即座に反映されます。変更は、カメラフレームを更新しなくても、次にレンダリングされるフレームに適用されます。 + +## エフェクト {#the-effect} + +エフェクトの [`intensity`](https://api.playcanvas.com/engine/classes/LensFlareEffect.html#intensity) は、すべてのフレアの明るさを設定し、0 でエフェクトを無効にします。フレアはシーンに対する相対解像度で描画され、この解像度は [`scale`](https://api.playcanvas.com/engine/classes/LensFlareEffect.html#scale) で 0.25 から 1 の範囲で指定します。デフォルトはシーンの半分です。スケールを下げるとコストが下がり、フレアが柔らかくなります。 + +## エディターでのレンズフレア {#lens-flares-in-the-editor} + +[`lens-flare.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/lens-flare.mjs) スクリプトファイルは、2 つのスクリプトを提供します。 + +- `lensFlareEffect` は、[`cameraFrame` スクリプト](/user-manual/graphics/linear-workflow/hdr-rendering/#cameraframe-in-the-editor)と並べてカメラエンティティに追加し、そのカメラフレームにエフェクトを追加します。また、形状ライブラリ(形状のテクスチャアセット、その名前と解像度)を保持します。 +- `lensFlare` は、エンティティに追加してフレアを与えます。ルックのエレメントとフレアのその他のプロパティを属性として持ちます。エンティティにライトコンポーネントがある場合、フレアはそのライトに追従し、ない場合はエンティティの位置に追従します。 + +## 例 {#example} + +プロシージャルな空の太陽には回折による光芒とゴーストの列を持つ大きなフレアが、ランプには小さなグローがあり、スポットライトはカメラがその円錐内にある間だけフレアを出します。 + + From 6001780cfce23c413a5f39716a19effff2229ae2 Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Thu, 29 Oct 2026 09:04:45 +0000 Subject: [PATCH 7/8] docs: describe the dynamic sky Editor script The engine now has a dynamicSkyEffect script, from dynamic-sky.mjs. Add a section on it to the Dynamic Sky page, with a script of your own animating the time of day, list it in the effect scripts of the HDR Rendering page, and drop the note that the dynamic sky has no Editor script, in English and Japanese. --- .../graphics/linear-workflow/hdr-rendering.md | 3 ++- .../posteffects/cameraframe/dynamic-sky.md | 26 +++++++++++++++++++ .../graphics/linear-workflow/hdr-rendering.md | 3 ++- .../posteffects/cameraframe/dynamic-sky.md | 26 +++++++++++++++++++ 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/docs/user-manual/graphics/linear-workflow/hdr-rendering.md b/docs/user-manual/graphics/linear-workflow/hdr-rendering.md index c5ba51444e4..87341e0e1c8 100644 --- a/docs/user-manual/graphics/linear-workflow/hdr-rendering.md +++ b/docs/user-manual/graphics/linear-workflow/hdr-rendering.md @@ -166,10 +166,11 @@ The effects which are not built into the camera frame have scripts of their own. | `autoExposureEffect` | [`auto-exposure.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/auto-exposure.mjs) | The [auto exposure](/user-manual/graphics/posteffects/cameraframe/auto-exposure/) | | `smaaEffect` | [`smaa.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/smaa.mjs) | [SMAA](/user-manual/graphics/posteffects/cameraframe/#smaa) anti-aliasing | | `lensFlareEffect` | [`lens-flare.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/lens-flare.mjs) | The [lens flare](/user-manual/graphics/posteffects/cameraframe/lens-flare/) effect, with its shape library. The `lensFlare` script from the same file gives the entities it is on a flare. | +| `dynamicSkyEffect` | [`dynamic-sky.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/dynamic-sky.mjs) | The [dynamic sky](/user-manual/graphics/posteffects/cameraframe/dynamic-sky/) | These scripts import [`camera-frame-effect.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/camera-frame-effect.mjs), so add it to the project next to them. It is also the base class for scripts of your own effects, see [Effects in the Editor](/user-manual/graphics/posteffects/cameraframe/custom-effects/#effects-in-the-editor). -The effect of a script follows its enabled state, and is destroyed with it, which removes it from the camera frame. The **Debug** attribute of a script displays the debug view of its effect. It and the debug setting of the `cameraFrame` script override each other, and the last one changed is displayed. The [dynamic sky](/user-manual/graphics/posteffects/cameraframe/dynamic-sky/) has no Editor script, so set it up in code. +The effect of a script follows its enabled state, and is destroyed with it, which removes it from the camera frame. The **Debug** attribute of a script displays the debug view of its effect. It and the debug setting of the `cameraFrame` script override each other, and the last one changed is displayed. ## CameraFrame Tips diff --git a/docs/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md b/docs/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md index cab3cce7495..bf796a05a46 100644 --- a/docs/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md +++ b/docs/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md @@ -15,6 +15,8 @@ cameraFrame.addEffect(sky); cameraFrame.update(); ``` +In the Editor, its script adds it, see [The dynamic sky in the Editor](#the-dynamic-sky-in-the-editor). + Like all effects, its settings apply on the next [`CameraFrame#update`](https://api.playcanvas.com/engine/classes/CameraFrame.html#update). A day and night cycle moving the sun calls it every frame: ```javascript @@ -99,6 +101,30 @@ The cameras without the effect, such as the cameras rendering planar reflections In XR, the sky renders for each view, like the rest of the scene, and so do the flat clouds. The volumetric clouds render to their texture once, for the view of the camera, and each view reads them from it. Where the view of an eye extends past the view of the camera, the texture has no clouds to read, and they streak at its edge. Use `CLOUDTYPE_FLAT` clouds in XR. +## The dynamic sky in the Editor + +The `dynamicSkyEffect` script, from [`dynamic-sky.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/dynamic-sky.mjs), adds the effect to the camera frame of the camera entity it is on, next to the [`cameraFrame` script](/user-manual/graphics/linear-workflow/hdr-rendering/#cameraframe-in-the-editor). Its attributes are the properties of the effect, in the groups **Sun**, **Day**, **Night**, **Clouds** and **Lighting**. The **Light** attribute of the **Sun** group is the entity of the directional light kept in sync with the sun. + +The script does not animate the time of day. It copies its attributes into the effect every frame, so a script of your own on the same entity moves the sun by changing them: + +```javascript +import { Script } from 'playcanvas'; + +class DayNightCycle extends Script { + static scriptName = 'dayNightCycle'; + + time = 0; + + update(dt) { + this.time += dt; + const sky = this.entity.script.dynamicSkyEffect; + sky.sun.elevation = Math.sin(this.time * 0.1) * 60; + } +} + +export { DayNightCycle }; +``` + ## Limitations - The volumetric clouds are not supported in XR, see [XR](#xr). diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/linear-workflow/hdr-rendering.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/linear-workflow/hdr-rendering.md index 3ad708c3c84..ffa7d318cb3 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/linear-workflow/hdr-rendering.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/linear-workflow/hdr-rendering.md @@ -159,10 +159,11 @@ cameraFrame.update(); | `autoExposureEffect` | [`auto-exposure.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/auto-exposure.mjs) | [自動露出](/user-manual/graphics/posteffects/cameraframe/auto-exposure/) | | `smaaEffect` | [`smaa.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/smaa.mjs) | [SMAA](/user-manual/graphics/posteffects/cameraframe/#smaa) によるアンチエイリアス | | `lensFlareEffect` | [`lens-flare.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/lens-flare.mjs) | [レンズフレア](/user-manual/graphics/posteffects/cameraframe/lens-flare/)のエフェクトと、その形状ライブラリ。同じファイルの `lensFlare` スクリプトは、それが付いているエンティティにフレアを付けます。 | +| `dynamicSkyEffect` | [`dynamic-sky.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/dynamic-sky.mjs) | [ダイナミックスカイ](/user-manual/graphics/posteffects/cameraframe/dynamic-sky/) | これらのスクリプトは [`camera-frame-effect.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/camera-frame-effect.mjs) をインポートするため、このファイルもそれらと並べてプロジェクトに追加してください。このファイルは、独自のエフェクトのスクリプトの基底クラスでもあります。[エディターでのエフェクト](/user-manual/graphics/posteffects/cameraframe/custom-effects/#effects-in-the-editor)を参照してください。 -スクリプトのエフェクトは、スクリプトの有効状態に従い、スクリプトとともに破棄され、その際にカメラフレームから取り除かれます。スクリプトの **Debug** 属性は、そのエフェクトのデバッグ表示を表示します。この属性と `cameraFrame` スクリプトのデバッグ設定は互いに上書きし合い、最後に変更した方が表示されます。[ダイナミックスカイ](/user-manual/graphics/posteffects/cameraframe/dynamic-sky/)にはエディター用のスクリプトがないため、コードで設定してください。 +スクリプトのエフェクトは、スクリプトの有効状態に従い、スクリプトとともに破棄され、その際にカメラフレームから取り除かれます。スクリプトの **Debug** 属性は、そのエフェクトのデバッグ表示を表示します。この属性と `cameraFrame` スクリプトのデバッグ設定は互いに上書きし合い、最後に変更した方が表示されます。 ## CameraFrameのヒント diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md index a6f3afdc00b..e1f1e3fd089 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md @@ -15,6 +15,8 @@ cameraFrame.addEffect(sky); cameraFrame.update(); ``` +エディターでは、専用のスクリプトでエフェクトを追加します。[エディターでのダイナミックスカイ](#the-dynamic-sky-in-the-editor)を参照してください。 + すべてのエフェクトと同様に、設定は次の [`CameraFrame#update`](https://api.playcanvas.com/engine/classes/CameraFrame.html#update) で適用されます。太陽を動かす昼夜サイクルでは、これを毎フレーム呼び出します。 ```javascript @@ -99,6 +101,30 @@ app.on('update', (dt) => { XR では、シーンの他の部分と同様に空は各ビューごとにレンダリングされ、フラットな雲も同様です。ボリューメトリックな雲は、カメラのビューに対して一度だけテクスチャにレンダリングされ、各ビューはそのテクスチャから雲を読み取ります。目のビューがカメラのビューの外側まで広がる部分では、テクスチャに読み取る雲がないため、テクスチャの端で雲が筋状に引き伸ばされます。XR では `CLOUDTYPE_FLAT` の雲を使用してください。 +## エディターでのダイナミックスカイ {#the-dynamic-sky-in-the-editor} + +[`dynamic-sky.mjs`](https://github.com/playcanvas/engine/blob/main/scripts/esm/dynamic-sky.mjs) の `dynamicSkyEffect` スクリプトは、[`cameraFrame` スクリプト](/user-manual/graphics/linear-workflow/hdr-rendering/#cameraframe-in-the-editor)と並べてカメラエンティティに追加すると、そのカメラエンティティのカメラフレームにエフェクトを追加します。属性はエフェクトのプロパティで、**Sun**、**Day**、**Night**、**Clouds**、**Lighting** のグループに分かれています。**Sun** グループの **Light** 属性は、太陽に同期させるディレクショナルライトのエンティティです。 + +このスクリプトは時刻をアニメーションしません。スクリプトは毎フレーム属性をエフェクトにコピーするため、同じエンティティに付けた独自のスクリプトから属性を変更すると、太陽が動きます。 + +```javascript +import { Script } from 'playcanvas'; + +class DayNightCycle extends Script { + static scriptName = 'dayNightCycle'; + + time = 0; + + update(dt) { + this.time += dt; + const sky = this.entity.script.dynamicSkyEffect; + sky.sun.elevation = Math.sin(this.time * 0.1) * 60; + } +} + +export { DayNightCycle }; +``` + ## 制限事項 {#limitations} - ボリューメトリックな雲は XR ではサポートされていません。[XR](#xr) を参照してください。 From 2d9c942fabc97acc3ca86d829b6b6cd8d3887ba8 Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Sun, 1 Nov 2026 18:22:44 +0000 Subject: [PATCH 8/8] docs: describe the dynamic sky haze and the sky in other shaders, in English and Japanese --- .../posteffects/cameraframe/dynamic-sky.md | 116 +++++++++++++++++- .../posteffects/cameraframe/dynamic-sky.md | 116 +++++++++++++++++- 2 files changed, 226 insertions(+), 6 deletions(-) diff --git a/docs/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md b/docs/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md index bf796a05a46..481679cc0db 100644 --- a/docs/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md +++ b/docs/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md @@ -1,9 +1,12 @@ --- title: Dynamic Sky -description: "The CameraFrame dynamic sky effect: an analytic day sky, the night sky, flat and volumetric clouds, a sun light kept in sync, and the lighting of the scene generated from the sky." +description: "The CameraFrame dynamic sky effect: an analytic day sky, the night sky, flat and volumetric clouds, a sun light kept in sync, the lighting of the scene generated from the sky, and haze in the color of the sky." --- -The [`DynamicSkyEffect`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html) renders the sky of a camera in place of the sky of the scene: a day sky of an analytic atmosphere lit by the sun, a night sky of stars and the moon, and optionally clouds drifting with the wind. The position of the sun sets the time of day, and the effect can keep a directional light in sync with it. It also generates the lighting of the scene from its sky. +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +The [`DynamicSkyEffect`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html) renders the sky of a camera in place of the sky of the scene: a day sky of an analytic atmosphere lit by the sun, a night sky of stars and the moon, and optionally clouds drifting with the wind. The position of the sun sets the time of day, and the effect can keep a directional light in sync with it. It also generates the lighting of the scene from its sky, and can haze the scene with it. The dynamic sky is not a built-in effect of the camera frame: create it, and add it to a camera frame with [`addEffect`](https://api.playcanvas.com/engine/classes/CameraFrame.html#addeffect): @@ -97,6 +100,89 @@ Generating the lighting has a cost, so the effect generates it again only when t The cameras without the effect, such as the cameras rendering planar reflections, render the sky of the scene from that environment atlas: a still image of this sky. +## Haze + +While its [`haze`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#haze) is enabled, the effect adds aerial perspective to the scene its camera renders: with their distance, surfaces fade into the color of the sky behind them. The haze is warm toward a low sun, blue away from it, and follows the gradient of the night sky after the sunset. It is densest at its base height and thins out with the height above it, so the valleys stay hazy while the view from high up is clear. + +```javascript +sky.haze = true; +sky.hazeDensity = 0.0002; +sky.hazeHeight = 300; +sky.hazeBaseHeight = 0; +cameraFrame.update(); +``` + +| Property | Description | +| -------- | ----------- | +| `hazeDensity` | The density of the haze at its base height, per world unit: the fraction of the light it scatters over a unit of distance. A level view at the base height is 95% hidden at a distance of about 3 / density. | +| `hazeHeight` | The height over which the density falls to about a third, in world units. Lower values keep the haze closer to the ground. | +| `hazeBaseHeight` | The height the haze is densest at, in world units: usually the lowest ground of the scene, such as the level of the sea. Below it, the haze grows denser still. | + +Keep the density low. The haze takes the color of the sky near the horizon, which is much brighter than the sunlit ground, so even a few percent of haze brightens the distant surfaces noticeably. + +The haze takes the place of the fog of the camera, [`Scene#fog`](https://api.playcanvas.com/engine/classes/Scene.html#fog) or [`CameraComponent#fog`](https://api.playcanvas.com/engine/classes/CameraComponent.html#fog): while it is enabled, the effect gives its camera a fog of its own, and gives the camera its fog back once the haze is disabled. The other cameras keep their fog. + +- The lit materials, such as [`StandardMaterial`](https://api.playcanvas.com/engine/classes/StandardMaterial.html), are hazed. +- The particles and the gaussian splats render without fog on the camera. +- A material opts out of the haze, and renders without fog, with [`useFog`](https://api.playcanvas.com/engine/classes/StandardMaterial.html#usefog) set to `false`, or with the `DYNAMIC_SKY_NO_HAZE` define: + +```javascript +material.setDefine('DYNAMIC_SKY_NO_HAZE', true); +material.update(); +``` + +Enabling or disabling the haze compiles the shaders of the lit materials the camera renders again, so set it up front rather than toggling it during the game. + +The effect hazes the materials with a replacement of the `fogPS` shader chunk, for all the materials of the graphics device. A material replacing `fogPS` with a chunk of its own is not hazed, and finds the `FOG` define of its shaders set to `DYNAMICSKYHAZE` on the camera. + +## The sky in other shaders + +The effect sets the uniforms of its sky on the graphics device every frame, so the shaders of other materials can evaluate the same sky, for example a water shader reflecting it. Its shader chunks, in GLSL and WGSL, evaluate the sky from these uniforms: + +- `dynamicSkyAtmospherePS`: `dynamicSkyBase(dir, ambient)` returns the radiance of the sky in a direction, without the sun disc, the stars and the moon, and sets `ambient` to the light of the sky around it. The direction is in the space of the sky: a world space direction turned by the rotation of the sky, the `dynSkyRotation` uniform, with X flipped. +- `dynamicSkyHazePS`: `dynamicSkyHaze(color, cameraPos, worldPos)` returns a linear color seen through the [haze](#haze), for the shaders the haze does not reach on its own. + + + + +```glsl +#include "dynamicSkyAtmospherePS" +uniform mat3 dynSkyRotation; + +vec3 skyColor(vec3 worldDir) { + vec3 dir = worldDir * dynSkyRotation; + dir.x = -dir.x; + vec3 ambient; + return dynamicSkyBase(dir, ambient); +} +``` + + + + +```wgsl +#include "dynamicSkyAtmospherePS" +uniform dynSkyRotation: mat3x3f; + +fn skyColor(worldDir: vec3f) -> vec3f { + var dir = worldDir * uniform.dynSkyRotation; + dir.x = -dir.x; + var ambient: vec3f; + return dynamicSkyBase(dir, &ambient); +} +``` + + + + +On the CPU, [`getSkyColor`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#getskycolor) evaluates the same sky in a direction, for lighting worked out in a script, such as the light the sky gives to particles: + +```javascript +const zenith = sky.getSkyColor(Vec3.UP); +``` + +With more than one dynamic sky, a material renders with the sky that was updated last. + ## XR {#xr} In XR, the sky renders for each view, like the rest of the scene, and so do the flat clouds. The volumetric clouds render to their texture once, for the view of the camera, and each view reads them from it. Where the view of an eye extends past the view of the camera, the texture has no clouds to read, and they streak at its edge. Use `CLOUDTYPE_FLAT` clouds in XR. @@ -125,13 +211,37 @@ class DayNightCycle extends Script { export { DayNightCycle }; ``` +The [haze](#haze) is not one of the attributes of the script. Set it on the effect the script drives, its `effect`, which the script creates in its `initialize`: + +```javascript +import { Script } from 'playcanvas'; + +class SkyHaze extends Script { + static scriptName = 'skyHaze'; + + postInitialize() { + const sky = this.entity.script.dynamicSkyEffect.effect; + sky.haze = true; + sky.hazeDensity = 0.0002; + sky.hazeHeight = 300; + } +} + +export { SkyHaze }; +``` + ## Limitations - The volumetric clouds are not supported in XR, see [XR](#xr). - The sky does not work together with the [volumetric fog](/user-manual/graphics/posteffects/cameraframe/volumetric-fog/) of the camera frame: the clouds cast no shadows into the fog, and the fog is not lit by the light of the sky. +- The haze does not reach the particles and the gaussian splats, which render without fog on the camera with the haze. -## Example +## Examples An analytic atmosphere over a day and night cycle, with stars, the moon and volumetric clouds drifting with the wind, lighting the scene and keeping the sun light in sync with the sky. + +A desert landscape under the sky over a day, with the haze fading the distant ground into the color of the sky. + + diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md index e1f1e3fd089..7703752a28e 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/posteffects/cameraframe/dynamic-sky.md @@ -1,9 +1,12 @@ --- title: ダイナミックスカイ -description: "CameraFrame のダイナミックスカイエフェクト:解析的な昼の空、夜空、フラットな雲とボリューメトリックな雲、同期される太陽のライト、空から生成されるシーンのライティング。" +description: "CameraFrame のダイナミックスカイエフェクト:解析的な昼の空、夜空、フラットな雲とボリューメトリックな雲、同期される太陽のライト、空から生成されるシーンのライティング、空の色のヘイズ。" --- -[`DynamicSkyEffect`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html) は、シーンの空の代わりにカメラの空をレンダリングします。太陽に照らされる解析的な大気による昼の空、星と月の夜空、そしてオプションで風に流れる雲です。太陽の位置で時刻が決まり、エフェクトはディレクショナルライトを太陽に同期させることができます。また、空からシーンのライティングを生成します。 +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +[`DynamicSkyEffect`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html) は、シーンの空の代わりにカメラの空をレンダリングします。太陽に照らされる解析的な大気による昼の空、星と月の夜空、そしてオプションで風に流れる雲です。太陽の位置で時刻が決まり、エフェクトはディレクショナルライトを太陽に同期させることができます。また、空からシーンのライティングを生成し、空の色でシーンにヘイズをかけることもできます。 ダイナミックスカイはカメラフレームの組み込みエフェクトではありません。作成して、[`addEffect`](https://api.playcanvas.com/engine/classes/CameraFrame.html#addeffect) でカメラフレームに追加してください。 @@ -97,6 +100,89 @@ app.on('update', (dt) => { 平面反射をレンダリングするカメラなど、エフェクトを持たないカメラは、その環境アトラスからシーンの空をレンダリングします。つまり、この空の静止画像です。 +## ヘイズ {#haze} + +[`haze`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#haze) が有効な間、エフェクトはカメラがレンダリングするシーンに空気遠近法を加えます。サーフェスは距離に応じて、その背後にある空の色へとフェードします。ヘイズは低い太陽の方向では暖かく、太陽と反対の方向では青くなり、日没後は夜空のグラデーションに従います。ヘイズは基準の高さで最も濃く、その上では高さとともに薄くなるため、谷は霞んだまま、高い所からの眺めは澄んだままになります。 + +```javascript +sky.haze = true; +sky.hazeDensity = 0.0002; +sky.hazeHeight = 300; +sky.hazeBaseHeight = 0; +cameraFrame.update(); +``` + +| プロパティ | 説明 | +| -------- | ----------- | +| `hazeDensity` | 基準の高さでのヘイズの密度(ワールド単位あたり)。単位距離で散乱される光の割合です。基準の高さで水平に見ると、約 3 / 密度 の距離で 95% が隠れます。 | +| `hazeHeight` | 密度が約 3 分の 1 に下がる高さ(ワールド単位)。値を小さくすると、ヘイズが地面の近くにとどまります。 | +| `hazeBaseHeight` | ヘイズが最も濃くなる高さ(ワールド単位)。通常は海面など、シーンの最も低い地面です。それより下では、ヘイズはさらに濃くなります。 | + +密度は低く抑えてください。ヘイズは地平線付近の空の色をとり、その色は日の当たる地面よりはるかに明るいため、わずか数パーセントのヘイズでも遠くのサーフェスが目に見えて明るくなります。 + +ヘイズは、カメラのフォグ、つまり [`Scene#fog`](https://api.playcanvas.com/engine/classes/Scene.html#fog) または [`CameraComponent#fog`](https://api.playcanvas.com/engine/classes/CameraComponent.html#fog) の代わりになります。ヘイズが有効な間、エフェクトはカメラに独自のフォグを与え、ヘイズが無効になるとカメラのフォグを元に戻します。他のカメラは自身のフォグを保ちます。 + +- [`StandardMaterial`](https://api.playcanvas.com/engine/classes/StandardMaterial.html) など、ライティングされるマテリアルにヘイズがかかります。 +- パーティクルとガウシアンスプラットは、そのカメラではフォグなしでレンダリングされます。 +- マテリアルは、[`useFog`](https://api.playcanvas.com/engine/classes/StandardMaterial.html#usefog) を `false` に設定するか、`DYNAMIC_SKY_NO_HAZE` 定義を設定すると、ヘイズの対象から外れ、フォグなしでレンダリングされます。 + +```javascript +material.setDefine('DYNAMIC_SKY_NO_HAZE', true); +material.update(); +``` + +ヘイズを有効または無効にすると、カメラがレンダリングするライティングされるマテリアルのシェーダーが再コンパイルされます。そのため、ゲーム中に切り替えるのではなく、最初に設定してください。 + +エフェクトは、グラフィックスデバイスのすべてのマテリアルに対して `fogPS` シェーダーチャンクを置き換えることで、マテリアルにヘイズをかけます。`fogPS` を独自のチャンクに置き換えたマテリアルにはヘイズがかからず、そのカメラではシェーダーの `FOG` 定義が `DYNAMICSKYHAZE` に設定されます。 + +## 他のシェーダーでの空 {#the-sky-in-other-shaders} + +エフェクトは毎フレーム、空のユニフォームをグラフィックスデバイスに設定するため、他のマテリアルのシェーダーも同じ空を評価できます。たとえば、空を映す水のシェーダーです。エフェクトのシェーダーチャンク(GLSL と WGSL)は、これらのユニフォームから空を評価します。 + +- `dynamicSkyAtmospherePS`:`dynamicSkyBase(dir, ambient)` は、太陽の円盤、星、月を除いた、ある方向の空の放射輝度を返し、`ambient` にその周りの空の光を設定します。方向は空の空間で表します。つまり、ワールド空間の方向を空の回転(`dynSkyRotation` ユニフォーム)で回転させ、X を反転したものです。 +- `dynamicSkyHazePS`:`dynamicSkyHaze(color, cameraPos, worldPos)` は、[ヘイズ](#haze)を通して見たリニアな色を返します。ヘイズが自動では届かないシェーダーで使用します。 + + + + +```glsl +#include "dynamicSkyAtmospherePS" +uniform mat3 dynSkyRotation; + +vec3 skyColor(vec3 worldDir) { + vec3 dir = worldDir * dynSkyRotation; + dir.x = -dir.x; + vec3 ambient; + return dynamicSkyBase(dir, ambient); +} +``` + + + + +```wgsl +#include "dynamicSkyAtmospherePS" +uniform dynSkyRotation: mat3x3f; + +fn skyColor(worldDir: vec3f) -> vec3f { + var dir = worldDir * uniform.dynSkyRotation; + dir.x = -dir.x; + var ambient: vec3f; + return dynamicSkyBase(dir, &ambient); +} +``` + + + + +CPU では、[`getSkyColor`](https://api.playcanvas.com/engine/classes/DynamicSkyEffect.html#getskycolor) がある方向の同じ空を評価します。パーティクルに空が与える光など、スクリプトで計算するライティングに使用します。 + +```javascript +const zenith = sky.getSkyColor(Vec3.UP); +``` + +ダイナミックスカイが複数ある場合、マテリアルは最後に更新された空でレンダリングされます。 + ## XR {#xr} XR では、シーンの他の部分と同様に空は各ビューごとにレンダリングされ、フラットな雲も同様です。ボリューメトリックな雲は、カメラのビューに対して一度だけテクスチャにレンダリングされ、各ビューはそのテクスチャから雲を読み取ります。目のビューがカメラのビューの外側まで広がる部分では、テクスチャに読み取る雲がないため、テクスチャの端で雲が筋状に引き伸ばされます。XR では `CLOUDTYPE_FLAT` の雲を使用してください。 @@ -125,13 +211,37 @@ class DayNightCycle extends Script { export { DayNightCycle }; ``` +[ヘイズ](#haze)はスクリプトの属性ではありません。スクリプトが操作するエフェクト、つまりスクリプトが `initialize` で作成する `effect` に設定してください。 + +```javascript +import { Script } from 'playcanvas'; + +class SkyHaze extends Script { + static scriptName = 'skyHaze'; + + postInitialize() { + const sky = this.entity.script.dynamicSkyEffect.effect; + sky.haze = true; + sky.hazeDensity = 0.0002; + sky.hazeHeight = 300; + } +} + +export { SkyHaze }; +``` + ## 制限事項 {#limitations} - ボリューメトリックな雲は XR ではサポートされていません。[XR](#xr) を参照してください。 - 空は、カメラフレームの[ボリューメトリックフォグ](/user-manual/graphics/posteffects/cameraframe/volumetric-fog/)と連携しません。雲はフォグに影を落とさず、フォグは空の光で照らされません。 +- ヘイズはパーティクルとガウシアンスプラットには届きません。これらは、ヘイズのあるカメラではフォグなしでレンダリングされます。 -## 例 {#example} +## 例 {#examples} 昼夜サイクルを通した解析的な大気です。星、月、風に流れるボリューメトリックな雲があり、シーンを照らし、太陽のライトを空と同期させます。 + +1 日を通した空の下の砂漠の風景です。ヘイズによって、遠くの地面が空の色へとフェードします。 + +