-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
render-pass-camera-frame.js
229 lines (178 loc) · 7.57 KB
/
render-pass-camera-frame.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
import {
LAYERID_SKYBOX,
LAYERID_IMMEDIATE,
PIXELFORMAT_RGBA8,
ADDRESS_CLAMP_TO_EDGE,
FILTER_LINEAR,
RenderPass,
RenderPassColorGrab,
RenderPassForward,
RenderTarget,
Texture
} from "playcanvas";
import { RenderPassBloom } from "./render-pass-bloom.js";
import { RenderPassCompose } from "./render-pass-compose.js";
import { RenderPassTAA } from "./render-pass-taa.js";
class RenderPassCameraFrame extends RenderPass {
app;
scenePass;
composePass;
bloomPass;
taaPass;
_bloomEnabled = true;
_renderTargetScale = 1;
/**
* @type {RenderTarget}
* @private
*/
_rt = null;
constructor(app, options = {}) {
super(app.graphicsDevice);
this.app = app;
this.options = this.sanitizeOptions(options);
this.setupRenderPasses(this.options);
}
destroy() {
if (this._rt) {
this._rt.destroyTextureBuffers();
this._rt.destroy();
this._rt = null;
}
// destroy all passes we created
this.beforePasses.forEach(pass => pass.destroy());
this.beforePasses = null;
}
sanitizeOptions(options) {
const defaults = {
camera: null,
samples: 2,
sceneColorMap: true,
// skybox is the last layer rendered before the grab passes
lastGrabLayerId: LAYERID_SKYBOX,
lastGrabLayerIsTransparent: false,
// immediate layer is the last layer rendered before the post-processing
lastSceneLayerId: LAYERID_IMMEDIATE,
lastSceneLayerIsTransparent: true,
// TAA
taaEnabled: false
};
return Object.assign({}, defaults, options);
}
set renderTargetScale(value) {
this._renderTargetScale = value;
if (this.scenePass) {
this.scenePass.options.scaleX = value;
this.scenePass.options.scaleY = value;
}
}
get renderTargetScale() {
return this._renderTargetScale;
}
set bloomEnabled(value) {
if (this._bloomEnabled !== value) {
this._bloomEnabled = value;
this.composePass.bloomTexture = value ? this.bloomPass.bloomTexture : null;
this.bloomPass.enabled = value;
}
}
get bloomEnabled() {
return this._bloomEnabled;
}
set lastMipLevel(value) {
this.bloomPass.lastMipLevel = value;
}
get lastMipLevel() {
return this.bloomPass.lastMipLevel;
}
setupRenderPasses(options) {
const { app, device } = this;
const { scene, renderer } = app;
const composition = scene.layers;
const cameraComponent = options.camera;
const targetRenderTarget = cameraComponent.renderTarget;
// create a render target to render the scene into
const format = device.getRenderableHdrFormat() || PIXELFORMAT_RGBA8;
const sceneTexture = new Texture(device, {
name: 'SceneTexture',
width: 4,
height: 4,
format: format,
mipmaps: false,
minFilter: FILTER_LINEAR,
magFilter: FILTER_LINEAR,
addressU: ADDRESS_CLAMP_TO_EDGE,
addressV: ADDRESS_CLAMP_TO_EDGE
});
const rt = new RenderTarget({
colorBuffer: sceneTexture,
depth: true,
samples: options.samples
});
this._rt = rt;
// ------ SCENE RENDERING WITH OPTIONAL GRAB PASS ------
// render pass that renders the scene to the render target. Render target size automatically
// matches the back-buffer size with the optional scale. Note that the scale parameters
// allow us to render the 3d scene at lower resolution, improving performance.
this.scenePass = new RenderPassForward(device, composition, scene, renderer);
this.scenePass.init(rt, {
resizeSource: targetRenderTarget,
scaleX: this.renderTargetScale,
scaleY: this.renderTargetScale
});
// layers this pass renders depend on the grab pass being used
const lastLayerId = options.sceneColorMap ? options.lastGrabLayerId : options.lastSceneLayerId;
const lastLayerIsTransparent = options.sceneColorMap ? options.lastGrabLayerIsTransparent : options.lastSceneLayerIsTransparent;
let clearRenderTarget = true;
let lastAddedIndex = 0;
lastAddedIndex = this.scenePass.addLayers(composition, cameraComponent, lastAddedIndex, clearRenderTarget, lastLayerId, lastLayerIsTransparent);
clearRenderTarget = false;
// grab pass allowing us to copy the render scene into a texture and use for refraction
// the source for the copy is the texture we render the scene to
let colorGrabPass;
let scenePassTransparent;
if (options.sceneColorMap) {
colorGrabPass = new RenderPassColorGrab(device);
colorGrabPass.source = rt;
// if grab pass is used, render the layers after it (otherwise they were already rendered)
scenePassTransparent = new RenderPassForward(device, composition, scene, renderer);
scenePassTransparent.init(rt);
lastAddedIndex = scenePassTransparent.addLayers(composition, cameraComponent, lastAddedIndex, clearRenderTarget, options.lastSceneLayerId, options.lastSceneLayerIsTransparent);
}
// ------ TAA ------
let sceneTextureWithTaa = sceneTexture;
if (options.taaEnabled) {
this.taaPass = new RenderPassTAA(device, sceneTexture);
sceneTextureWithTaa = this.taaPass.accumulationTexture;
}
// ------ BLOOM GENERATION ------
// create a bloom pass, which generates bloom texture based on the just rendered scene texture
this.bloomPass = new RenderPassBloom(app.graphicsDevice, sceneTextureWithTaa, format);
// ------ COMPOSITION ------
// create a compose pass, which combines the scene texture with the bloom texture
this.composePass = new RenderPassCompose(app.graphicsDevice);
// this.composePass.sceneTexture = sceneTextureWithTaa;
this.composePass.bloomTexture = this.bloomPass.bloomTexture;
// compose pass renders directly to target renderTarget
this.composePass.init(targetRenderTarget);
// ------ AFTER COMPOSITION RENDERING ------
// final pass renders directly to the target renderTarget on top of the bloomed scene, and it renders a transparent UI layer
const afterPass = new RenderPassForward(device, composition, scene, renderer);
afterPass.init(targetRenderTarget);
// add all remaining layers the camera renders
afterPass.addLayers(composition, cameraComponent, lastAddedIndex, clearRenderTarget);
// use these prepared render passes in the order they should be executed
const allPasses = [this.scenePass, colorGrabPass, scenePassTransparent, this.taaPass, this.bloomPass, this.composePass, afterPass];
this.beforePasses = allPasses.filter(element => element !== undefined);
}
frameUpdate() {
super.frameUpdate();
// scene texture is either output of taa pass or the scene render target
const sceneTexture = this.taaPass?.update() ?? this._rt.colorBuffer;
// TAA accumulation buffer is double buffered, assign the current one to the follow up passes.
this.composePass.sceneTexture = sceneTexture;
if (this.bloomEnabled) {
this.bloomPass.sourceTexture = sceneTexture;
}
}
}
export { RenderPassCameraFrame };