@xviewer.js/postprocessing 1.0.0-alpha.4 → 1.0.0-alpha.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +269 -2272
- package/dist/main.js.map +1 -1
- package/dist/module.js +273 -2275
- package/dist/module.js.map +1 -1
- package/package.json +22 -24
- package/types/BloomPlugin.d.ts +1 -0
- package/types/EffectComposerPlugin.d.ts +1 -1
- package/types/EffectPassPlugin.d.ts +1 -0
- package/types/FXAAPlugin.d.ts +1 -0
- package/types/MSAAPlugin.d.ts +1 -0
- package/types/SMAAPlugin.d.ts +1 -0
- package/types/TRAAPlugin.d.ts +1 -0
- package/types/ToneMappingPlugin.d.ts +1 -0
- package/types/getVelocityDepthNormalPass.d.ts +3 -0
- package/types/index.d.ts +0 -1
- package/types/MotionBlurPlugin.d.ts +0 -5
package/dist/module.js
CHANGED
|
@@ -1,19 +1,243 @@
|
|
|
1
|
-
import { Plugin,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { Plugin, PropertyManager } from '@xviewer.js/core';
|
|
2
|
+
import { RenderPass, EffectPass, EffectComposer, ToneMappingMode, ToneMappingEffect, BloomEffect, BlendFunction, FXAAEffect, SMAAPreset, EdgeDetectionMode, PredicationMode, SMAAEffect, Pass, Effect } from 'postprocessing';
|
|
3
|
+
import { HalfFloatType, ShaderMaterial, Uniform, Vector2, Matrix4, Vector3, NoBlending, GLSL3, FramebufferTexture, LinearFilter, Clock, Quaternion, WebGLRenderTarget, NearestFilter, ShaderChunk, Matrix3, UniformsUtils, Color, RGBAFormat, DepthTexture, DataTexture } from 'three';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
class EffectComposerPlugin extends Plugin {
|
|
6
|
+
static Instance(viewer) {
|
|
7
|
+
return viewer.getPlugin(EffectComposerPlugin, true);
|
|
8
|
+
}
|
|
9
|
+
get multisampling() {
|
|
10
|
+
return this._composer.multisampling;
|
|
11
|
+
}
|
|
12
|
+
set multisampling(v) {
|
|
13
|
+
this._composer.multisampling = v;
|
|
14
|
+
}
|
|
15
|
+
getPass(constructor) {
|
|
16
|
+
return this._composer.passes.find((v)=>v.constructor === constructor);
|
|
17
|
+
}
|
|
18
|
+
addPass(pass) {
|
|
19
|
+
this._composer.addPass(pass, this._composer.passes.length - 1);
|
|
20
|
+
this._checkOutputPass();
|
|
21
|
+
return pass;
|
|
22
|
+
}
|
|
23
|
+
removePass(pass) {
|
|
24
|
+
this._composer.removePass(pass);
|
|
25
|
+
this._checkOutputPass();
|
|
26
|
+
}
|
|
27
|
+
activePass(pass, v) {
|
|
28
|
+
pass.enabled = v;
|
|
29
|
+
this._checkOutputPass();
|
|
30
|
+
return pass;
|
|
31
|
+
}
|
|
32
|
+
_checkOutputPass() {
|
|
33
|
+
const count = this._composer.passes.filter((v)=>v.enabled && v.name !== "VelocityDepthNormalPass" && v !== this._outputPass).length;
|
|
34
|
+
this._outputPass.enabled = this._composer.multisampling > 0 && count === 1;
|
|
35
|
+
this._setRenderToScreen();
|
|
36
|
+
}
|
|
37
|
+
_setRenderToScreen() {
|
|
38
|
+
const passes = this._composer.passes;
|
|
39
|
+
for(let k = 0, i = passes.length; i--;){
|
|
40
|
+
let pass = passes[i];
|
|
41
|
+
if (pass.enabled && pass.name !== "VelocityDepthNormalPass" && k === 0) {
|
|
42
|
+
k = i;
|
|
43
|
+
}
|
|
44
|
+
pass.renderToScreen = k === i;
|
|
10
45
|
}
|
|
46
|
+
}
|
|
47
|
+
constructor(props){
|
|
48
|
+
super();
|
|
49
|
+
this.type = "EffectComposerPlugin";
|
|
50
|
+
this.install = ()=>{
|
|
51
|
+
const { renderer, scene, camera } = this.viewer;
|
|
52
|
+
this._renderPass = new RenderPass(scene, camera);
|
|
53
|
+
this._outputPass = new EffectPass(camera);
|
|
54
|
+
this._composer = new EffectComposer(renderer, Object.assign({
|
|
55
|
+
frameBufferType: HalfFloatType
|
|
56
|
+
}, props));
|
|
57
|
+
this._composer.addPass(this._renderPass);
|
|
58
|
+
this._composer.addPass(this._outputPass);
|
|
59
|
+
this.viewer._onResize = (width, height)=>this._composer.setSize(width, height);
|
|
60
|
+
this.viewer._onRender = (dt)=>this._composer.render(dt);
|
|
61
|
+
};
|
|
62
|
+
this.uninstall = ()=>{
|
|
63
|
+
this._composer.dispose();
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
class PassPlugin extends Plugin {
|
|
69
|
+
get enable() {
|
|
70
|
+
return this.pass.enabled;
|
|
71
|
+
}
|
|
72
|
+
set enable(v) {
|
|
73
|
+
this.setEnable(v);
|
|
74
|
+
}
|
|
75
|
+
get composer() {
|
|
76
|
+
return EffectComposerPlugin.Instance(this.viewer);
|
|
77
|
+
}
|
|
78
|
+
setEnable(v) {
|
|
79
|
+
this.composer.activePass(this.pass, v);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
class ToneMappingPlugin extends PassPlugin {
|
|
84
|
+
get mode() {
|
|
85
|
+
return this.effect.mode;
|
|
86
|
+
}
|
|
87
|
+
set mode(v) {
|
|
88
|
+
this.effect.mode = v;
|
|
89
|
+
}
|
|
90
|
+
constructor(props){
|
|
91
|
+
super();
|
|
92
|
+
this.type = "ToneMappingPlugin";
|
|
93
|
+
this.install = ()=>{
|
|
94
|
+
this.effect = new ToneMappingEffect(props);
|
|
95
|
+
this.pass = this.composer.addPass(new EffectPass(this.viewer.camera, this.effect));
|
|
96
|
+
};
|
|
97
|
+
this.uninstall = ()=>{
|
|
98
|
+
this.composer.removePass(this.pass);
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
PropertyManager._getClassProperties("ToneMappingPlugin").property("enable").property("mode", {
|
|
103
|
+
value: ToneMappingMode
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
class BloomPlugin extends PassPlugin {
|
|
107
|
+
get intensity() {
|
|
108
|
+
return this.effect.intensity;
|
|
109
|
+
}
|
|
110
|
+
set intensity(v) {
|
|
111
|
+
this.effect.intensity = v;
|
|
112
|
+
}
|
|
113
|
+
// @property({ min: 0, max: 10, step: 0.01 })
|
|
114
|
+
get luminanceThreshold() {
|
|
115
|
+
return this.effect.luminanceMaterial.threshold;
|
|
116
|
+
}
|
|
117
|
+
set luminanceThreshold(v) {
|
|
118
|
+
this.effect.luminanceMaterial.threshold = v;
|
|
119
|
+
}
|
|
120
|
+
// @property({ min: 0, max: 10, step: 0.01 })
|
|
121
|
+
get luminanceSmoothing() {
|
|
122
|
+
return this.effect.luminanceMaterial.smoothing;
|
|
123
|
+
}
|
|
124
|
+
set luminanceSmoothing(v) {
|
|
125
|
+
this.effect.luminanceMaterial.smoothing = v;
|
|
126
|
+
}
|
|
127
|
+
constructor(props){
|
|
128
|
+
super();
|
|
129
|
+
this.type = "BloomPlugin";
|
|
130
|
+
this.install = ()=>{
|
|
131
|
+
this.effect = new BloomEffect({
|
|
132
|
+
blendFunction: BlendFunction.ADD,
|
|
133
|
+
...props
|
|
134
|
+
});
|
|
135
|
+
this.pass = this.composer.addPass(new EffectPass(this.viewer.camera, this.effect));
|
|
136
|
+
};
|
|
137
|
+
this.uninstall = ()=>{
|
|
138
|
+
this.composer.removePass(this.pass);
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
PropertyManager._getClassProperties("BloomPlugin").property("enable").property("intensity", {
|
|
143
|
+
min: 0,
|
|
144
|
+
max: 2,
|
|
145
|
+
step: 0.01
|
|
146
|
+
}).property("luminanceThreshold", {
|
|
147
|
+
min: 0,
|
|
148
|
+
max: 10,
|
|
149
|
+
step: 0.01
|
|
150
|
+
}).property("luminanceSmoothing", {
|
|
151
|
+
min: 0,
|
|
152
|
+
max: 10,
|
|
153
|
+
step: 0.01
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
class FXAAPlugin extends PassPlugin {
|
|
157
|
+
constructor(){
|
|
158
|
+
super();
|
|
159
|
+
this.type = "FXAAPlugin";
|
|
160
|
+
this.install = ()=>{
|
|
161
|
+
this.pass = this.composer.addPass(new EffectPass(this.viewer.camera, new FXAAEffect()));
|
|
162
|
+
};
|
|
163
|
+
this.uninstall = ()=>{
|
|
164
|
+
this.composer.removePass(this.pass);
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
PropertyManager._getClassProperties("FXAAPlugin").property("enable");
|
|
11
169
|
|
|
12
|
-
|
|
13
|
-
}
|
|
170
|
+
class SMAAPlugin extends PassPlugin {
|
|
171
|
+
// @property({ value: SMAAPreset })
|
|
172
|
+
get preset() {
|
|
173
|
+
return this._preset;
|
|
174
|
+
}
|
|
175
|
+
set preset(v) {
|
|
176
|
+
if (this._preset !== v) {
|
|
177
|
+
this._preset = v;
|
|
178
|
+
this.effect.applyPreset(v);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// @property({ value: EdgeDetectionMode })
|
|
182
|
+
get edgeDetectionMode() {
|
|
183
|
+
return this.effect.edgeDetectionMaterial.edgeDetectionMode;
|
|
184
|
+
}
|
|
185
|
+
set edgeDetectionMode(v) {
|
|
186
|
+
this.effect.edgeDetectionMaterial.edgeDetectionMode = v;
|
|
187
|
+
}
|
|
188
|
+
// @property({ value: PredicationMode })
|
|
189
|
+
get predicationMode() {
|
|
190
|
+
return this.effect.edgeDetectionMaterial.predicationMode;
|
|
191
|
+
}
|
|
192
|
+
set predicationMode(v) {
|
|
193
|
+
this.effect.edgeDetectionMaterial.predicationMode = v;
|
|
194
|
+
}
|
|
195
|
+
constructor(props = {}){
|
|
196
|
+
super();
|
|
197
|
+
this.type = "SMAAPlugin";
|
|
198
|
+
this._preset = SMAAPreset.MEDIUM;
|
|
199
|
+
this.install = ()=>{
|
|
200
|
+
this.effect = new SMAAEffect(props);
|
|
201
|
+
this.pass = this.composer.addPass(new EffectPass(this.viewer.camera, this.effect));
|
|
202
|
+
};
|
|
203
|
+
this.uninstall = ()=>{
|
|
204
|
+
this.composer.removePass(this.pass);
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
PropertyManager._getClassProperties("SMAAPlugin").property("enable").property("preset", {
|
|
209
|
+
value: SMAAPreset
|
|
210
|
+
}).property("edgeDetectionMode", {
|
|
211
|
+
value: EdgeDetectionMode
|
|
212
|
+
}).property("predicationMode", {
|
|
213
|
+
value: PredicationMode
|
|
214
|
+
});
|
|
14
215
|
|
|
15
|
-
|
|
216
|
+
class MSAAPlugin extends Plugin {
|
|
217
|
+
get enable() {
|
|
218
|
+
return this.composer.multisampling > 0;
|
|
219
|
+
}
|
|
220
|
+
set enable(v) {
|
|
221
|
+
this.composer.multisampling = v ? this._maxSamples : 0;
|
|
222
|
+
}
|
|
223
|
+
get composer() {
|
|
224
|
+
return EffectComposerPlugin.Instance(this.viewer);
|
|
225
|
+
}
|
|
226
|
+
constructor(){
|
|
227
|
+
super();
|
|
228
|
+
this.type = "MSAAPlugin";
|
|
229
|
+
this._maxSamples = 4;
|
|
230
|
+
this.install = ()=>{
|
|
231
|
+
const { renderer } = this.viewer;
|
|
232
|
+
this._maxSamples = Math.min(4, renderer.capabilities.maxSamples);
|
|
233
|
+
this.composer.multisampling = this._maxSamples;
|
|
234
|
+
};
|
|
235
|
+
this.uninstall = ()=>{
|
|
236
|
+
this.composer.multisampling = 0;
|
|
237
|
+
};
|
|
238
|
+
}
|
|
16
239
|
}
|
|
240
|
+
PropertyManager._getClassProperties("MSAAPlugin").property("enable");
|
|
17
241
|
|
|
18
242
|
// from: https://news.ycombinator.com/item?id=17876741
|
|
19
243
|
// reference: http://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/
|
|
@@ -48,40 +272,12 @@ function jitter(width, height, camera, frame, jitterScale = 1) {
|
|
|
48
272
|
|
|
49
273
|
var vertexShader = "#define GLSLIFY 1\nvarying vec2 vUv;void main(){vUv=position.xy*0.5+0.5;gl_Position=vec4(position.xy,1.0,1.0);}"; // eslint-disable-line
|
|
50
274
|
|
|
51
|
-
var fragmentShader
|
|
275
|
+
var fragmentShader = "#define GLSLIFY 1\nvarying vec2 vUv;uniform highp sampler2D inputTexture;uniform highp sampler2D velocityTexture;uniform highp sampler2D depthTexture;uniform highp sampler2D lastVelocityTexture;uniform float maxBlend;uniform float neighborhoodClampIntensity;uniform bool fullAccumulate;uniform vec2 invTexSize;uniform float cameraNear;uniform float cameraFar;uniform mat4 projectionMatrix;uniform mat4 projectionMatrixInverse;uniform mat4 cameraMatrixWorld;uniform vec3 cameraPos;uniform vec3 prevCameraPos;uniform mat4 prevViewMatrix;uniform mat4 prevCameraMatrixWorld;uniform mat4 prevProjectionMatrix;uniform mat4 prevProjectionMatrixInverse;uniform float keepData;\n#define EPSILON 0.00001\n#define DIFFUSE_SPECULAR 0\n#define DIFFUSE 1\n#define SPECULAR 2\n#include <gbuffer_packing>\n#include <packing>\n#include <reproject>\nvec3 reprojectedUvDiffuse=vec3(-1.0),reprojectedUvSpecular=vec3(-1.0);void accumulate(inout vec4 outputColor,inout vec4 inp,inout vec4 acc,inout float roughness,inout float moveFactor,bool doReprojectSpecular){vec3 reprojectedUvConfidence=doReprojectSpecular ? reprojectedUvSpecular : reprojectedUvDiffuse;vec2 reprojectedUv=reprojectedUvConfidence.xy;float confidence=reprojectedUvConfidence.z;confidence=pow(confidence,confidencePower);float accumBlend=1.-1./(acc.a+1.0);accumBlend=mix(0.,accumBlend,confidence);float maxValue=(fullAccumulate ? 1. : maxBlend)*keepData;\n#if inputType != DIFFUSE\nconst float roughnessMaximum=0.1;if(doReprojectSpecular&&roughness>=0.0&&roughness<roughnessMaximum){float maxRoughnessValue=mix(0.,maxValue,roughness/roughnessMaximum);maxValue=mix(maxValue,maxRoughnessValue,min(100.*moveFactor,1.));}\n#endif\nfloat temporalReprojectMix=min(accumBlend,maxValue);acc.a=1./(1.-temporalReprojectMix)-1.;acc.a=min(65536.,acc.a);outputColor.rgb=mix(inp.rgb,acc.rgb,temporalReprojectMix);outputColor.a=acc.a;undoColorTransform(outputColor.rgb);}void reproject(inout vec4 inp,inout vec4 acc,sampler2D accumulatedTexture,inout bool wasSampled,bool doNeighborhoodClamp,bool doReprojectSpecular){vec3 uvc=doReprojectSpecular ? reprojectedUvSpecular : reprojectedUvDiffuse;vec2 uv=uvc.xy;acc=sampleReprojectedTexture(accumulatedTexture,uv);transformColor(acc.rgb);if(!wasSampled){inp.rgb=acc.rgb;return;}acc.a++;vec3 clampedColor=acc.rgb;int clampRadius=doReprojectSpecular&&roughness<0.25 ? 1 : 2;clampNeighborhood(inputTexture,clampedColor,inp.rgb,clampRadius,doReprojectSpecular);float r=doReprojectSpecular ? roughness : 1.0;float clampAggressiveness=min(1.,uvc.z*r);float clampIntensity=mix(0.,min(1.,moveFactor*50.+neighborhoodClampIntensity),clampAggressiveness);vec3 newColor=mix(acc.rgb,clampedColor,clampIntensity);float colorDiff=min(length(newColor-acc.rgb),1.);acc.a*=1.-colorDiff;acc.rgb=newColor;}void preprocessInput(inout highp vec4 texel,inout bool sampledThisFrame){sampledThisFrame=texel.r>=0.;texel.rgb=max(texel.rgb,vec3(0.));transformColor(texel.rgb);}void getTexels(inout highp vec4 inputTexel[textureCount],inout bool sampledThisFrame[textureCount]){\n#if inputType == DIFFUSE_SPECULAR\nhighp vec4 tex=textureLod(inputTexture,vUv,0.);unpackTwoVec4(tex,inputTexel[0],inputTexel[1]);preprocessInput(inputTexel[0],sampledThisFrame[0]);preprocessInput(inputTexel[1],sampledThisFrame[1]);\n#else\ninputTexel[0]=textureLod(inputTexture,vUv,0.0);preprocessInput(inputTexel[0],sampledThisFrame[0]);\n#endif\n}void computeGVariables(vec2 dilatedUv,float depth){worldPos=screenSpaceToWorldSpace(dilatedUv,depth,cameraMatrixWorld,projectionMatrixInverse);vec3 viewPos=(viewMatrix*vec4(worldPos,1.0)).xyz;viewDir=normalize(viewPos);vec3 viewNormal=(vec4(worldNormal,0.0)*viewMatrix).xyz;viewAngle=dot(-viewDir,viewNormal);}void computeReprojectedUv(float depth,vec3 worldPos,vec3 worldNormal){reprojectedUvDiffuse=getReprojectedUV(false,depth,worldPos,worldNormal);\n#if inputType == DIFFUSE_SPECULAR || inputType == SPECULAR\nreprojectedUvSpecular=getReprojectedUV(true,depth,worldPos,worldNormal);if(reprojectedUvSpecular.x==-1.0){reprojectedUvSpecular=reprojectedUvDiffuse;}\n#endif\n}void getRoughnessRayLength(inout highp vec4 inputTexel[textureCount]){\n#if inputType == DIFFUSE_SPECULAR\nrayLength=inputTexel[1].a;roughness=clamp(inputTexel[0].a,0.,1.);\n#elif inputType == SPECULAR\nvec2 data=unpackHalf2x16(floatBitsToUint(inputTexel[0].a));rayLength=data.r;roughness=clamp(data.g,0.,1.);\n#endif\n}void main(){vec2 dilatedUv=vUv;getVelocityNormalDepth(dilatedUv,velocity,worldNormal,depth);highp vec4 inputTexel[textureCount],accumulatedTexel[textureCount];bool textureSampledThisFrame[textureCount];getTexels(inputTexel,textureSampledThisFrame);\n#if inputType != DIFFUSE\nif(depth==1.0&&fwidth(depth)==0.0){discard;return;}\n#endif\ncurvature=getCurvature(worldNormal);computeGVariables(dilatedUv,depth);getRoughnessRayLength(inputTexel);computeReprojectedUv(depth,worldPos,worldNormal);moveFactor=min(dot(velocity,velocity)*10000.,1.);\n#pragma unroll_loop_start\nfor(int i=0;i<textureCount;i++){reproject(inputTexel[i],accumulatedTexel[i],accumulatedTexture[i],textureSampledThisFrame[i],neighborhoodClamp[i],reprojectSpecular[i]);accumulate(gOutput[i],inputTexel[i],accumulatedTexel[i],roughness,moveFactor,reprojectSpecular[i]);}\n#pragma unroll_loop_end\n}"; // eslint-disable-line
|
|
52
276
|
|
|
53
277
|
var reproject = "#define GLSLIFY 1\nvec2 dilatedUv,velocity;vec3 worldNormal,worldPos,viewDir;float depth,curvature,viewAngle,rayLength,angleMix;float roughness=1.;float moveFactor=0.;\n#define luminance(a) dot(vec3(0.2125, 0.7154, 0.0721), a)\nfloat getViewZ(const in float depth){\n#ifdef PERSPECTIVE_CAMERA\nreturn perspectiveDepthToViewZ(depth,cameraNear,cameraFar);\n#else\nreturn orthographicDepthToViewZ(depth,cameraNear,cameraFar);\n#endif\n}vec3 screenSpaceToWorldSpace(const vec2 uv,const float depth,mat4 curMatrixWorld,const mat4 projMatrixInverse){vec4 ndc=vec4((uv.x-0.5)*2.0,(uv.y-0.5)*2.0,(depth-0.5)*2.0,1.0);vec4 clip=projMatrixInverse*ndc;vec4 view=curMatrixWorld*(clip/clip.w);return view.xyz;}vec2 viewSpaceToScreenSpace(const vec3 position,const mat4 projMatrix){vec4 projectedCoord=projMatrix*vec4(position,1.0);projectedCoord.xy/=projectedCoord.w;projectedCoord.xy=projectedCoord.xy*0.5+0.5;return projectedCoord.xy;}\n#ifdef logTransform\nvoid transformColor(inout vec3 color){color=log(color+1.);}void undoColorTransform(inout vec3 color){color=exp(color)-1.;}\n#else\n#define transformColor\n#define undoColorTransform\n#endif\nvoid getNeighborhoodAABB(const sampler2D tex,const int clampRadius,inout vec3 minNeighborColor,inout vec3 maxNeighborColor,const bool isSpecular){for(int x=-clampRadius;x<=clampRadius;x++){for(int y=-clampRadius;y<=clampRadius;y++){vec2 offset=vec2(x,y)*invTexSize;vec2 neighborUv=vUv+offset;\n#if inputType == DIFFUSE_SPECULAR\nvec4 t1,t2;vec4 packedNeighborTexel=textureLod(inputTexture,neighborUv,0.0);unpackTwoVec4(packedNeighborTexel,t1,t2);vec4 neighborTexel=isSpecular ? t2 : t1;\n#else\nvec4 neighborTexel=textureLod(inputTexture,neighborUv,0.0);\n#endif\nif(neighborTexel.r>=0.){minNeighborColor=min(neighborTexel.rgb,minNeighborColor);maxNeighborColor=max(neighborTexel.rgb,maxNeighborColor);}}}}void clampNeighborhood(const sampler2D tex,inout vec3 color,vec3 inputColor,const int clampRadius,const bool isSpecular){undoColorTransform(inputColor);vec3 minNeighborColor=inputColor;vec3 maxNeighborColor=inputColor;getNeighborhoodAABB(tex,clampRadius,minNeighborColor,maxNeighborColor,isSpecular);transformColor(minNeighborColor);transformColor(maxNeighborColor);color=clamp(color,minNeighborColor,maxNeighborColor);}void getVelocityNormalDepth(inout vec2 dilatedUv,out vec2 vel,out vec3 normal,out float depth){vec2 centerUv=dilatedUv;vec4 velocityTexel=textureLod(velocityTexture,centerUv,0.0);vel=velocityTexel.rg;normal=unpackNormal(velocityTexel.b);depth=velocityTexel.a;}\n#define PLANE_DISTANCE 20.\n#define WORLD_DISTANCE 10.\n#define NORMAL_DISTANCE 1.\nfloat planeDistanceDisocclusionCheck(const vec3 worldPos,const vec3 lastWorldPos,const vec3 worldNormal,const float distFactor){vec3 toCurrent=worldPos-lastWorldPos;float distToPlane=abs(dot(toCurrent,worldNormal));return distToPlane/PLANE_DISTANCE*distFactor;}float worldDistanceDisocclusionCheck(const vec3 worldPos,const vec3 lastWorldPos,const float distFactor){return length(worldPos-lastWorldPos)/WORLD_DISTANCE*distFactor;}float normalDisocclusionCheck(const vec3 worldNormal,const vec3 lastWorldNormal,const float distFactor){return min(1.-dot(worldNormal,lastWorldNormal),1.)/NORMAL_DISTANCE*distFactor;}float validateReprojectedUV(const vec2 reprojectedUv,const vec3 worldPos,const vec3 worldNormal,const bool isHitPoint){if(reprojectedUv.x>1.0||reprojectedUv.x<0.0||reprojectedUv.y>1.0||reprojectedUv.y<0.0)return 0.;vec2 dilatedReprojectedUv=reprojectedUv;vec2 lastVelocity=vec2(0.0);vec3 lastWorldNormal=vec3(0.0);float lastDepth=0.0;getVelocityNormalDepth(dilatedReprojectedUv,lastVelocity,lastWorldNormal,lastDepth);vec3 lastWorldPos=screenSpaceToWorldSpace(dilatedReprojectedUv,lastDepth,prevCameraMatrixWorld,prevProjectionMatrixInverse);vec3 lastViewPos=(prevViewMatrix*vec4(lastWorldPos,1.0)).xyz;vec3 lastViewDir=normalize(lastViewPos);vec3 lastViewNormal=(vec4(lastWorldNormal,0.0)*prevViewMatrix).xyz;float lastViewAngle=dot(-lastViewDir,lastViewNormal);angleMix=abs(lastViewAngle-viewAngle);float viewZ=abs(getViewZ(depth));float distFactor=1.+1./(viewZ+1.0);float disoccl=0.;disoccl+=worldDistanceDisocclusionCheck(worldPos,lastWorldPos,distFactor);disoccl+=planeDistanceDisocclusionCheck(worldPos,lastWorldPos,worldNormal,distFactor);disoccl+=normalDisocclusionCheck(worldNormal,lastWorldNormal,distFactor);float confidence=1.-min(disoccl,1.);confidence=max(confidence,0.);confidence=pow(confidence,confidencePower);return confidence;}vec2 reprojectHitPoint(const vec3 rayOrig,const float rayLength){if(curvature>0.05||rayLength<0.01){return vec2(-1.);}vec3 cameraRay=normalize(rayOrig-cameraPos);vec3 parallaxHitPoint=cameraPos+cameraRay*rayLength;vec4 reprojectedHitPoint=prevProjectionMatrix*prevViewMatrix*vec4(parallaxHitPoint,1.0);reprojectedHitPoint.xyz/=reprojectedHitPoint.w;reprojectedHitPoint.xy=reprojectedHitPoint.xy*0.5+0.5;vec2 diffuseUv=vUv-velocity.xy;float m=min(max(0.,roughness-0.25)/0.25,1.);return reprojectedHitPoint.xy;}vec3 getReprojectedUV(const bool doReprojectSpecular,const float depth,const vec3 worldPos,const vec3 worldNormal){if(doReprojectSpecular){vec2 reprojectedUv=reprojectHitPoint(worldPos,rayLength);float confidence=validateReprojectedUV(reprojectedUv,worldPos,worldNormal,true);return vec3(reprojectedUv,confidence);}else{vec2 reprojectedUv=vUv-velocity;float confidence=validateReprojectedUV(reprojectedUv,worldPos,worldNormal,false);return vec3(reprojectedUv,confidence);}}vec4 BiCubicCatmullRom5Tap(sampler2D tex,vec2 P){vec2 Weight[3];vec2 Sample[3];vec2 UV=P/invTexSize;vec2 tc=floor(UV-0.5)+0.5;vec2 f=UV-tc;vec2 f2=f*f;vec2 f3=f2*f;vec2 w0=f2-0.5*(f3+f);vec2 w1=1.5*f3-2.5*f2+vec2(1.);vec2 w3=0.5*(f3-f2);vec2 w2=vec2(1.)-w0-w1-w3;Weight[0]=w0;Weight[1]=w1+w2;Weight[2]=w3;Sample[0]=tc-vec2(1.);Sample[1]=tc+w2/Weight[1];Sample[2]=tc+vec2(2.);Sample[0]*=invTexSize;Sample[1]*=invTexSize;Sample[2]*=invTexSize;float sampleWeight[5];sampleWeight[0]=Weight[1].x*Weight[0].y;sampleWeight[1]=Weight[0].x*Weight[1].y;sampleWeight[2]=Weight[1].x*Weight[1].y;sampleWeight[3]=Weight[2].x*Weight[1].y;sampleWeight[4]=Weight[1].x*Weight[2].y;vec4 Ct=textureLod(tex,vec2(Sample[1].x,Sample[0].y),0.)*sampleWeight[0];vec4 Cl=textureLod(tex,vec2(Sample[0].x,Sample[1].y),0.)*sampleWeight[1];vec4 Cc=textureLod(tex,vec2(Sample[1].x,Sample[1].y),0.)*sampleWeight[2];vec4 Cr=textureLod(tex,vec2(Sample[2].x,Sample[1].y),0.)*sampleWeight[3];vec4 Cb=textureLod(tex,vec2(Sample[1].x,Sample[2].y),0.)*sampleWeight[4];float WeightMultiplier=1./(sampleWeight[0]+sampleWeight[1]+sampleWeight[2]+sampleWeight[3]+sampleWeight[4]);return max((Ct+Cl+Cc+Cr+Cb)*WeightMultiplier,vec4(0.));}vec4 sampleReprojectedTexture(const sampler2D tex,const vec2 reprojectedUv){vec4 catmull=BiCubicCatmullRom5Tap(tex,reprojectedUv);return catmull;}float getCurvature(vec3 n){float curvature=length(fwidth(n));return curvature;}"; // eslint-disable-line
|
|
54
278
|
|
|
55
279
|
var gbuffer_packing = "#define GLSLIFY 1\nuniform highp sampler2D gBufferTexture;struct Material{highp vec4 diffuse;highp vec3 normal;highp float roughness;highp float metalness;highp vec3 emissive;};\n#define ONE_SAFE 0.999999\n#define NON_ZERO_OFFSET 0.0001\nconst highp float c_precision=256.0;const highp float c_precisionp1=c_precision+1.0;highp float color2float(in highp vec3 color){color=min(color+NON_ZERO_OFFSET,vec3(ONE_SAFE));return floor(color.r*c_precision+0.5)+floor(color.b*c_precision+0.5)*c_precisionp1+floor(color.g*c_precision+0.5)*c_precisionp1*c_precisionp1;}highp vec3 float2color(in highp float value){highp vec3 color;color.r=mod(value,c_precisionp1)/c_precision;color.b=mod(floor(value/c_precisionp1),c_precisionp1)/c_precision;color.g=floor(value/(c_precisionp1*c_precisionp1))/c_precision;color-=NON_ZERO_OFFSET;color=max(color,vec3(0.0));return color;}highp vec2 OctWrap(highp vec2 v){highp vec2 w=1.0-abs(v.yx);if(v.x<0.0)w.x=-w.x;if(v.y<0.0)w.y=-w.y;return w;}highp vec2 encodeOctWrap(highp vec3 n){n/=(abs(n.x)+abs(n.y)+abs(n.z));n.xy=n.z>0.0 ? n.xy : OctWrap(n.xy);n.xy=n.xy*0.5+0.5;return n.xy;}highp vec3 decodeOctWrap(highp vec2 f){f=f*2.0-1.0;highp vec3 n=vec3(f.x,f.y,1.0-abs(f.x)-abs(f.y));highp float t=max(-n.z,0.0);n.x+=n.x>=0.0 ?-t : t;n.y+=n.y>=0.0 ?-t : t;return normalize(n);}highp float packNormal(highp vec3 normal){return uintBitsToFloat(packHalf2x16(encodeOctWrap(normal)));}highp vec3 unpackNormal(highp float packedNormal){return decodeOctWrap(unpackHalf2x16(floatBitsToUint(packedNormal)));}highp vec4 packTwoVec4(highp vec4 v1,highp vec4 v2){highp vec4 encoded=vec4(0.0);v1+=NON_ZERO_OFFSET;v2+=NON_ZERO_OFFSET;highp uint v1r=packHalf2x16(v1.rg);highp uint v1g=packHalf2x16(v1.ba);highp uint v2r=packHalf2x16(v2.rg);highp uint v2g=packHalf2x16(v2.ba);encoded.r=uintBitsToFloat(v1r);encoded.g=uintBitsToFloat(v1g);encoded.b=uintBitsToFloat(v2r);encoded.a=uintBitsToFloat(v2g);return encoded;}void unpackTwoVec4(highp vec4 encoded,out highp vec4 v1,out highp vec4 v2){highp uint r=floatBitsToUint(encoded.r);highp uint g=floatBitsToUint(encoded.g);highp uint b=floatBitsToUint(encoded.b);highp uint a=floatBitsToUint(encoded.a);v1.rg=unpackHalf2x16(r);v1.ba=unpackHalf2x16(g);v2.rg=unpackHalf2x16(b);v2.ba=unpackHalf2x16(a);v1-=NON_ZERO_OFFSET;v2-=NON_ZERO_OFFSET;}vec4 unpackTwoVec4(highp vec4 encoded,const int index){highp uint r=floatBitsToUint(index==0 ? encoded.r : encoded.b);highp uint g=floatBitsToUint(index==0 ? encoded.g : encoded.a);vec4 v;v.rg=unpackHalf2x16(r);v.ba=unpackHalf2x16(g);v-=NON_ZERO_OFFSET;return v;}highp vec4 encodeRGBE8(highp vec3 rgb){highp vec4 vEncoded;highp float maxComponent=max(max(rgb.r,rgb.g),rgb.b);highp float fExp=ceil(log2(maxComponent));vEncoded.rgb=rgb/exp2(fExp);vEncoded.a=(fExp+128.0)/255.0;return vEncoded;}highp vec3 decodeRGBE8(highp vec4 rgbe){highp vec3 vDecoded;highp float fExp=rgbe.a*255.0-128.0;vDecoded=rgbe.rgb*exp2(fExp);return vDecoded;}highp float vec4ToFloat(highp vec4 vec){vec=min(vec+NON_ZERO_OFFSET,vec4(ONE_SAFE));highp uvec4 v=uvec4(vec*255.0);highp uint value=(v.a<<24u)|(v.b<<16u)|(v.g<<8u)|(v.r);return uintBitsToFloat(value);}highp vec4 floatToVec4(highp float f){highp uint value=floatBitsToUint(f);highp vec4 v;v.r=float(value&0xFFu)/255.0;v.g=float((value>>8u)&0xFFu)/255.0;v.b=float((value>>16u)&0xFFu)/255.0;v.a=float((value>>24u)&0xFFu)/255.0;v-=NON_ZERO_OFFSET;v=max(v,vec4(0.0));return v;}highp vec4 packGBuffer(highp vec4 diffuse,highp vec3 normal,highp float roughness,highp float metalness,highp vec3 emissive){highp vec4 gBuffer;gBuffer.r=vec4ToFloat(diffuse);gBuffer.g=packNormal(normal);gBuffer.b=color2float(vec3(roughness,metalness,0.));gBuffer.a=vec4ToFloat(encodeRGBE8(emissive));return gBuffer;}Material getMaterial(highp sampler2D gBufferTexture,highp vec2 uv){highp vec4 gBuffer=textureLod(gBufferTexture,uv,0.0);highp vec4 diffuse=floatToVec4(gBuffer.r);highp vec3 normal=unpackNormal(gBuffer.g);highp vec3 roughnessMetalness=float2color(gBuffer.b);highp float roughness=roughnessMetalness.r;highp float metalness=roughnessMetalness.g;highp vec3 emissive=decodeRGBE8(floatToVec4(gBuffer.a));return Material(diffuse,normal,roughness,metalness,emissive);}Material getMaterial(highp vec2 uv){return getMaterial(gBufferTexture,uv);}highp vec3 getNormal(highp sampler2D gBufferTexture,highp vec2 uv){return unpackNormal(textureLod(gBufferTexture,uv,0.0).g);}"; // eslint-disable-line
|
|
56
280
|
|
|
57
|
-
const getMaxMipLevel = (texture)=>{
|
|
58
|
-
const { width, height } = texture.image;
|
|
59
|
-
return Math.floor(Math.log2(Math.max(width, height))) + 1;
|
|
60
|
-
};
|
|
61
|
-
const createGlobalDisableIblRadianceUniform = ()=>{
|
|
62
|
-
if (!ShaderChunk.envmap_physical_pars_fragment.includes("iblRadianceDisabled")) {
|
|
63
|
-
ShaderChunk.envmap_physical_pars_fragment = ShaderChunk.envmap_physical_pars_fragment.replace("vec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {", /* glsl */ `
|
|
64
|
-
uniform bool iblRadianceDisabled;
|
|
65
|
-
|
|
66
|
-
vec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {
|
|
67
|
-
if(iblRadianceDisabled) return vec3(0.);
|
|
68
|
-
`);
|
|
69
|
-
}
|
|
70
|
-
if ("iblRadianceDisabled" in ShaderLib.physical.uniforms) return ShaderLib.physical.uniforms["iblRadianceDisabled"];
|
|
71
|
-
const globalIblRadianceDisabledUniform = {
|
|
72
|
-
value: false
|
|
73
|
-
};
|
|
74
|
-
ShaderLib.physical.uniforms.iblRadianceDisabled = globalIblRadianceDisabledUniform;
|
|
75
|
-
const { clone } = UniformsUtils;
|
|
76
|
-
UniformsUtils.clone = (uniforms)=>{
|
|
77
|
-
const result = clone(uniforms);
|
|
78
|
-
if ("iblRadianceDisabled" in uniforms) {
|
|
79
|
-
result.iblRadianceDisabled = globalIblRadianceDisabledUniform;
|
|
80
|
-
}
|
|
81
|
-
return result;
|
|
82
|
-
};
|
|
83
|
-
return globalIblRadianceDisabledUniform;
|
|
84
|
-
};
|
|
85
281
|
// source: https://github.com/mrdoob/three.js/blob/b9bc47ab1978022ab0947a9bce1b1209769b8d91/src/renderers/webgl/WebGLProgram.js#L228
|
|
86
282
|
// Unroll Loops
|
|
87
283
|
const unrollLoopPattern = /#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g;
|
|
@@ -98,7 +294,7 @@ function loopReplacer(match, start, end, snippet) {
|
|
|
98
294
|
|
|
99
295
|
class TemporalReprojectMaterial extends ShaderMaterial {
|
|
100
296
|
constructor(textureCount = 1){
|
|
101
|
-
let finalFragmentShader = fragmentShader
|
|
297
|
+
let finalFragmentShader = fragmentShader.replace("#include <reproject>", reproject).replace("#include <gbuffer_packing>", gbuffer_packing);
|
|
102
298
|
let definitions = "";
|
|
103
299
|
for(let i = 0; i < textureCount; i++){
|
|
104
300
|
definitions += /* glsl */ `
|
|
@@ -176,7 +372,7 @@ const didCameraMove = (camera, lastCameraPosition, lastCameraQuaternion)=>{
|
|
|
176
372
|
}
|
|
177
373
|
return false;
|
|
178
374
|
};
|
|
179
|
-
const getVisibleChildren
|
|
375
|
+
const getVisibleChildren = (object)=>{
|
|
180
376
|
const queue = [
|
|
181
377
|
object
|
|
182
378
|
];
|
|
@@ -296,7 +492,10 @@ class TemporalReprojectPass extends Pass {
|
|
|
296
492
|
this._scene = scene;
|
|
297
493
|
this._camera = camera;
|
|
298
494
|
this.textureCount = textureCount;
|
|
299
|
-
options =
|
|
495
|
+
options = {
|
|
496
|
+
...defaultTemporalReprojectPassOptions,
|
|
497
|
+
...options
|
|
498
|
+
};
|
|
300
499
|
this.renderTarget = new WebGLRenderTarget(1, 1, {
|
|
301
500
|
count: textureCount,
|
|
302
501
|
minFilter: NearestFilter,
|
|
@@ -389,778 +588,22 @@ class TRAAEffect extends Effect {
|
|
|
389
588
|
this._scene = scene;
|
|
390
589
|
this._camera = camera;
|
|
391
590
|
this.velocityDepthNormalPass = velocityDepthNormalPass;
|
|
392
|
-
options =
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
this.setSize(options.width, options.height);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
TRAAEffect.DefaultOptions = defaultTemporalReprojectPassOptions;
|
|
405
|
-
|
|
406
|
-
class CubeToEquirectEnvPass extends Pass {
|
|
407
|
-
dispose() {
|
|
408
|
-
this.renderTarget.dispose();
|
|
409
|
-
}
|
|
410
|
-
generateEquirectEnvMap(renderer, cubeMap, width = null, height = null, maxWidth = 4096) {
|
|
411
|
-
if (width === null && height === null) {
|
|
412
|
-
const w = cubeMap.source.data[0].width;
|
|
413
|
-
const widthEquirect = Math.pow(2, Math.ceil(Math.log2(2 * w * Math.pow(3, 0.5))));
|
|
414
|
-
const heightEquirect = Math.pow(2, Math.ceil(Math.log2(w * Math.pow(3, 0.5))));
|
|
415
|
-
width = widthEquirect;
|
|
416
|
-
height = heightEquirect;
|
|
417
|
-
}
|
|
418
|
-
if (width > maxWidth) {
|
|
419
|
-
width = maxWidth;
|
|
420
|
-
height = maxWidth / 2;
|
|
421
|
-
}
|
|
422
|
-
this.renderTarget.setSize(width, height);
|
|
423
|
-
this.fullscreenMaterial.uniforms.cubeMap.value = cubeMap;
|
|
424
|
-
const { renderTarget } = this;
|
|
425
|
-
renderer.setRenderTarget(renderTarget);
|
|
426
|
-
renderer.render(this.scene, this.camera);
|
|
427
|
-
// Create a new Float32Array to store the pixel data
|
|
428
|
-
const pixelBuffer = new Float32Array(width * height * 4);
|
|
429
|
-
renderer.readRenderTargetPixels(renderTarget, 0, 0, width, height, pixelBuffer);
|
|
430
|
-
// Create a new data texture
|
|
431
|
-
const equirectEnvMap = new DataTexture(pixelBuffer, width, height, RGBAFormat, FloatType);
|
|
432
|
-
// Set texture options
|
|
433
|
-
equirectEnvMap.wrapS = ClampToEdgeWrapping;
|
|
434
|
-
equirectEnvMap.wrapT = ClampToEdgeWrapping;
|
|
435
|
-
equirectEnvMap.minFilter = LinearMipMapLinearFilter;
|
|
436
|
-
equirectEnvMap.magFilter = LinearMipMapLinearFilter;
|
|
437
|
-
equirectEnvMap.needsUpdate = true;
|
|
438
|
-
equirectEnvMap.mapping = EquirectangularReflectionMapping;
|
|
439
|
-
return equirectEnvMap;
|
|
440
|
-
}
|
|
441
|
-
constructor(){
|
|
442
|
-
super("CubeToEquirectEnvPass");
|
|
443
|
-
this.renderTarget = new WebGLRenderTarget(1, 1, {
|
|
444
|
-
depthBuffer: false,
|
|
445
|
-
type: FloatType
|
|
446
|
-
});
|
|
447
|
-
this.fullscreenMaterial = new ShaderMaterial({
|
|
448
|
-
fragmentShader: /* glsl */ `
|
|
449
|
-
varying vec2 vUv;
|
|
450
|
-
uniform samplerCube cubeMap;
|
|
451
|
-
|
|
452
|
-
#define M_PI 3.1415926535897932384626433832795
|
|
453
|
-
|
|
454
|
-
// source: https://github.com/spite/CubemapToEquirectangular/blob/master/src/CubemapToEquirectangular.js
|
|
455
|
-
void main() {
|
|
456
|
-
float longitude = vUv.x * 2. * M_PI - M_PI + M_PI / 2.;
|
|
457
|
-
float latitude = vUv.y * M_PI;
|
|
458
|
-
|
|
459
|
-
vec3 dir = vec3(
|
|
460
|
-
- sin( longitude ) * sin( latitude ),
|
|
461
|
-
cos( latitude ),
|
|
462
|
-
- cos( longitude ) * sin( latitude )
|
|
463
|
-
);
|
|
464
|
-
|
|
465
|
-
dir.y = -dir.y;
|
|
466
|
-
|
|
467
|
-
gl_FragColor = textureCube( cubeMap, dir );
|
|
468
|
-
}
|
|
469
|
-
`,
|
|
470
|
-
vertexShader: vertexShader,
|
|
471
|
-
uniforms: {
|
|
472
|
-
cubeMap: {
|
|
473
|
-
value: null
|
|
474
|
-
}
|
|
475
|
-
},
|
|
476
|
-
blending: NoBlending,
|
|
477
|
-
depthWrite: false,
|
|
478
|
-
depthTest: false,
|
|
479
|
-
toneMapped: false
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
var img = "data:image/png;base64,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";
|
|
485
|
-
|
|
486
|
-
var blue_noise = "#define GLSLIFY 1\nuniform sampler2D blueNoiseTexture;uniform vec2 blueNoiseSize;uniform int blueNoiseIndex;uvec4 s1;ivec2 pixel;void rng_initialize(vec2 p,int index){pixel=ivec2(p);s1=uvec4(index,index*15843,index*31+4566,index*2345+58585);}void pcg4d(inout uvec4 v){v=v*1664525u+1013904223u;v.x+=v.y*v.w;v.y+=v.z*v.x;v.z+=v.x*v.y;v.w+=v.y*v.z;v=v ^(v>>16u);v.x+=v.y*v.w;v.y+=v.z*v.x;v.z+=v.x*v.y;v.w+=v.y*v.z;}ivec2 shift2(ivec2 size){pcg4d(s1);return(pixel+ivec2(s1.xy % 0x0fffffffu))% size;}vec4 blueNoise(vec2 uv,int index){if(index==0)return textureLod(blueNoiseTexture,uv*resolution/blueNoiseSize,0.0);rng_initialize(vUv*resolution,index);vec4 blueNoise=texelFetch(blueNoiseTexture,shift2(ivec2(blueNoiseSize)),0);return blueNoise;}vec4 blueNoise(){return blueNoise(vUv,int(blueNoiseIndex));}vec4 blueNoise(vec2 uv){return blueNoise(uv,int(blueNoiseIndex));}"; // eslint-disable-line
|
|
487
|
-
|
|
488
|
-
const blueNoiseSize = 128;
|
|
489
|
-
const highestSignedInt = 0x7fffffff;
|
|
490
|
-
const blueNoiseTexture = new TextureLoader().load(img, ()=>{
|
|
491
|
-
blueNoiseTexture.minFilter = NearestFilter;
|
|
492
|
-
blueNoiseTexture.magFilter = NearestFilter;
|
|
493
|
-
blueNoiseTexture.wrapS = RepeatWrapping;
|
|
494
|
-
blueNoiseTexture.wrapT = RepeatWrapping;
|
|
495
|
-
blueNoiseTexture.colorSpace = NoColorSpace;
|
|
496
|
-
});
|
|
497
|
-
const setupBlueNoise = (fragmentShader)=>{
|
|
498
|
-
let blueNoiseIndex = 0;
|
|
499
|
-
const startIndex = Math.floor(Math.random() * highestSignedInt);
|
|
500
|
-
const uniforms = {
|
|
501
|
-
blueNoiseTexture: {
|
|
502
|
-
value: blueNoiseTexture
|
|
503
|
-
},
|
|
504
|
-
blueNoiseSize: {
|
|
505
|
-
value: new Vector2(blueNoiseSize, blueNoiseSize)
|
|
506
|
-
},
|
|
507
|
-
blueNoiseIndex: {
|
|
508
|
-
get value () {
|
|
509
|
-
blueNoiseIndex = (startIndex + blueNoiseIndex + 1) % highestSignedInt;
|
|
510
|
-
return blueNoiseIndex;
|
|
511
|
-
},
|
|
512
|
-
set value (v){
|
|
513
|
-
blueNoiseIndex = v;
|
|
591
|
+
options = {
|
|
592
|
+
...options,
|
|
593
|
+
...{
|
|
594
|
+
maxBlend: 0.9,
|
|
595
|
+
neighborhoodClamp: true,
|
|
596
|
+
neighborhoodClampIntensity: 1,
|
|
597
|
+
neighborhoodClampRadius: 1,
|
|
598
|
+
logTransform: true,
|
|
599
|
+
confidencePower: 4
|
|
514
600
|
}
|
|
515
|
-
}
|
|
516
|
-
};
|
|
517
|
-
fragmentShader = fragmentShader.replace("uniform vec2 resolution;", "uniform vec2 resolution;\n" + blue_noise);
|
|
518
|
-
return {
|
|
519
|
-
uniforms,
|
|
520
|
-
fragmentShader
|
|
521
|
-
};
|
|
522
|
-
};
|
|
523
|
-
const useBlueNoise = (material)=>{
|
|
524
|
-
const { fragmentShader, uniforms } = setupBlueNoise(material.fragmentShader);
|
|
525
|
-
material.fragmentShader = fragmentShader;
|
|
526
|
-
material.uniforms = _extends({}, material.uniforms, uniforms);
|
|
527
|
-
material.needsUpdate = true;
|
|
528
|
-
};
|
|
529
|
-
|
|
530
|
-
class GBufferMaterial extends MeshPhysicalMaterial {
|
|
531
|
-
onBeforeCompile(shader) {
|
|
532
|
-
this.uniforms = shader.uniforms;
|
|
533
|
-
shader.uniforms.resolution = {
|
|
534
|
-
value: new Vector2(1, 1)
|
|
535
601
|
};
|
|
536
|
-
|
|
537
|
-
|
|
602
|
+
this.options = {
|
|
603
|
+
...defaultTemporalReprojectPassOptions,
|
|
604
|
+
...options
|
|
538
605
|
};
|
|
539
|
-
|
|
540
|
-
shader.vertexShader = shader.vertexShader.replace(/#include <lights_.*>/g, "");
|
|
541
|
-
shader.fragmentShader = shader.fragmentShader.replace(/#include <lights_.*>/g, "");
|
|
542
|
-
// delete all includes that have the pattern "#include <alpha...>"
|
|
543
|
-
shader.vertexShader = shader.vertexShader.replace(/#include <alpha.*>/g, "");
|
|
544
|
-
shader.fragmentShader = shader.fragmentShader.replace(/#include <alpha.*>/g, "");
|
|
545
|
-
// delete all includes that have the pattern "#include <aomap...>"
|
|
546
|
-
shader.vertexShader = shader.vertexShader.replace(/#include <aomap.*>/g, "");
|
|
547
|
-
shader.fragmentShader = shader.fragmentShader.replace(/#include <aomap.*>/g, "");
|
|
548
|
-
// delete all includes that have the pattern "#include <lightmap...>"
|
|
549
|
-
shader.vertexShader = shader.vertexShader.replace(/#include <lightmap.*>/g, "");
|
|
550
|
-
shader.fragmentShader = shader.fragmentShader.replace(/#include <lightmap.*>/g, "");
|
|
551
|
-
// delete all includes that have the pattern "#include <alphahash...>"
|
|
552
|
-
shader.vertexShader = shader.vertexShader.replace(/#include <alphahash.*>/g, "");
|
|
553
|
-
shader.fragmentShader = shader.fragmentShader.replace(/#include <alphahash.*>/g, "");
|
|
554
|
-
// delete all includes that have the pattern "#include <alphatest...>"
|
|
555
|
-
shader.vertexShader = shader.vertexShader.replace(/#include <alphatest.*>/g, "");
|
|
556
|
-
shader.fragmentShader = shader.fragmentShader.replace(/#include <alphatest.*>/g, "");
|
|
557
|
-
// remove opaque_fragment include
|
|
558
|
-
shader.fragmentShader = shader.fragmentShader.replace("#include <opaque_fragment>", "");
|
|
559
|
-
// remove colorspace_fragment include
|
|
560
|
-
shader.fragmentShader = shader.fragmentShader.replace("#include <colorspace_fragment>", "");
|
|
561
|
-
// delete the fog_fragment include
|
|
562
|
-
shader.fragmentShader = shader.fragmentShader.replace("#include <fog_fragment>", "");
|
|
563
|
-
shader.fragmentShader = shader.fragmentShader.replace("void main() {", /* glsl */ `
|
|
564
|
-
#define vUv gl_FragCoord.xy
|
|
565
|
-
uniform vec2 resolution;
|
|
566
|
-
uniform float cameraNotMovedFrames;
|
|
567
|
-
|
|
568
|
-
${gbuffer_packing}
|
|
569
|
-
|
|
570
|
-
void main() {
|
|
571
|
-
float a = opacity;
|
|
572
|
-
|
|
573
|
-
#ifdef USE_ALPHAMAP
|
|
574
|
-
a *= texture2D( alphaMap, vAlphaMapUv ).g;
|
|
575
|
-
#endif
|
|
576
|
-
|
|
577
|
-
if (cameraNotMovedFrames == 0.) {
|
|
578
|
-
if(a < 0.5) {
|
|
579
|
-
discard;
|
|
580
|
-
return;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
a = 1.;
|
|
584
|
-
} else if (a != 1.) {
|
|
585
|
-
float aStep = a > 0.5 ? 1. : 0.;
|
|
586
|
-
a = mix(a, aStep, (1. / (cameraNotMovedFrames * 0.1 + 1.)));
|
|
587
|
-
|
|
588
|
-
vec4 noise = blueNoise();
|
|
589
|
-
if (noise.x > a) {
|
|
590
|
-
discard;
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
`).replace("#include <dithering_fragment>", /* glsl */ `
|
|
595
|
-
#include <dithering_fragment>
|
|
596
|
-
|
|
597
|
-
vec3 worldNormal = normalize((vec4(normal, 1.) * viewMatrix).xyz);
|
|
598
|
-
|
|
599
|
-
vec4 gBuffer = packGBuffer(diffuseColor, worldNormal, roughnessFactor, metalnessFactor, totalEmissiveRadiance);
|
|
600
|
-
|
|
601
|
-
gl_FragColor = gBuffer;`);
|
|
602
|
-
const { uniforms, fragmentShader } = setupBlueNoise(shader.fragmentShader);
|
|
603
|
-
shader.uniforms = _extends({}, shader.uniforms, uniforms);
|
|
604
|
-
shader.fragmentShader = fragmentShader;
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
const gBufferMaterial = new GBufferMaterial();
|
|
608
|
-
function createGBufferMaterial(originalMaterial) {
|
|
609
|
-
const material = gBufferMaterial.clone();
|
|
610
|
-
copyAllPropsToGBufferMaterial(originalMaterial, material);
|
|
611
|
-
return material;
|
|
612
|
-
}
|
|
613
|
-
let props = Object.keys(gBufferMaterial);
|
|
614
|
-
// delete the ones that start with "_"
|
|
615
|
-
props = props.filter((key)=>!key.startsWith("_") && !key.startsWith("is") && key !== "uuid" && key !== "type" && key !== "transparent");
|
|
616
|
-
// this function attempts to copy all the props from the original material to the new GBufferMaterial
|
|
617
|
-
function copyAllPropsToGBufferMaterial(originalMaterial, gBufferMaterial) {
|
|
618
|
-
for (const key of props){
|
|
619
|
-
if (originalMaterial[key] !== undefined) {
|
|
620
|
-
gBufferMaterial[key] = originalMaterial[key];
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
const propsPrimitive = props.filter((key)=>typeof gBufferMaterial[key] === "string" || typeof gBufferMaterial[key] === "number");
|
|
625
|
-
function copyPropsToGBufferMaterial(originalMaterial, gBufferMaterial) {
|
|
626
|
-
for (const prop of propsPrimitive){
|
|
627
|
-
gBufferMaterial[prop] = originalMaterial[prop];
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
const backgroundColor$1 = new Color(0);
|
|
632
|
-
class GBufferPass extends Pass {
|
|
633
|
-
get texture() {
|
|
634
|
-
return this.renderTarget.texture;
|
|
635
|
-
}
|
|
636
|
-
get depthTexture() {
|
|
637
|
-
return this.renderTarget.depthTexture;
|
|
638
|
-
}
|
|
639
|
-
initGBufferRenderTarget() {
|
|
640
|
-
this.renderTarget = new WebGLRenderTarget(1, 1, {
|
|
641
|
-
type: FloatType,
|
|
642
|
-
minFilter: NearestFilter,
|
|
643
|
-
magFilter: NearestFilter,
|
|
644
|
-
depthBuffer: true
|
|
645
|
-
});
|
|
646
|
-
this.renderTarget.texture.name = "GBufferPass.Texture";
|
|
647
|
-
this.renderTarget.depthTexture = new DepthTexture(1, 1);
|
|
648
|
-
this.renderTarget.depthTexture.type = FloatType;
|
|
649
|
-
this.renderTarget.depthTexture.name = "GBufferPass.DepthTexture";
|
|
650
|
-
}
|
|
651
|
-
setSize(width, height) {
|
|
652
|
-
this.renderTarget.setSize(width, height);
|
|
653
|
-
}
|
|
654
|
-
dispose() {
|
|
655
|
-
super.dispose();
|
|
656
|
-
this.renderTarget.dispose();
|
|
657
|
-
}
|
|
658
|
-
setGBufferMaterialInScene() {
|
|
659
|
-
this.visibleMeshes = getVisibleChildren$1(this._scene);
|
|
660
|
-
const cameraMoved = didCameraMove(this._camera, this.lastCameraPosition, this.lastCameraQuaternion);
|
|
661
|
-
for (const c of this.visibleMeshes){
|
|
662
|
-
const originalMaterial = c.material;
|
|
663
|
-
let [cachedOriginalMaterial, gBufferMaterial] = this.cachedMaterials.get(c) || [];
|
|
664
|
-
// init a new material if the original material changed or if the cached material is missing
|
|
665
|
-
if (originalMaterial !== cachedOriginalMaterial) {
|
|
666
|
-
if (gBufferMaterial) gBufferMaterial.dispose();
|
|
667
|
-
gBufferMaterial = createGBufferMaterial(originalMaterial);
|
|
668
|
-
this.cachedMaterials.set(c, [
|
|
669
|
-
originalMaterial,
|
|
670
|
-
gBufferMaterial
|
|
671
|
-
]);
|
|
672
|
-
}
|
|
673
|
-
// gBufferMaterial.uniforms.resolution.value.set(this.renderTarget.width, this.renderTarget.height)
|
|
674
|
-
// gBufferMaterial.uniforms.frame.value = this.frame
|
|
675
|
-
if (gBufferMaterial.uniforms) {
|
|
676
|
-
gBufferMaterial.uniforms.cameraNotMovedFrames.value = cameraMoved ? 0 : (gBufferMaterial.uniforms.cameraNotMovedFrames.value + 1) % 0xffff;
|
|
677
|
-
}
|
|
678
|
-
c.visible = isChildMaterialRenderable(c, originalMaterial);
|
|
679
|
-
copyPropsToGBufferMaterial(originalMaterial, gBufferMaterial);
|
|
680
|
-
c.material = gBufferMaterial;
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
unsetGBufferMaterialInScene() {
|
|
684
|
-
for (const c of this.visibleMeshes){
|
|
685
|
-
const [originalMaterial] = this.cachedMaterials.get(c);
|
|
686
|
-
c.material = originalMaterial;
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
render(renderer) {
|
|
690
|
-
this.frame = (this.frame + 1) % 4096;
|
|
691
|
-
const { background } = this._scene;
|
|
692
|
-
this._scene.background = backgroundColor$1;
|
|
693
|
-
this.setGBufferMaterialInScene();
|
|
694
|
-
renderer.setRenderTarget(this.renderTarget);
|
|
695
|
-
renderer.render(this._scene, this._camera);
|
|
696
|
-
this.unsetGBufferMaterialInScene();
|
|
697
|
-
// reset state
|
|
698
|
-
this.lastCameraPosition.copy(this._camera.position);
|
|
699
|
-
this.lastCameraQuaternion.copy(this._camera.quaternion);
|
|
700
|
-
this._scene.background = background;
|
|
701
|
-
}
|
|
702
|
-
constructor(scene, camera){
|
|
703
|
-
super("GBufferPass");
|
|
704
|
-
this.frame = 21483;
|
|
705
|
-
this.cachedMaterials = new WeakMap();
|
|
706
|
-
this.visibleMeshes = [];
|
|
707
|
-
this.lastCameraPosition = new Vector3();
|
|
708
|
-
this.lastCameraQuaternion = new Quaternion();
|
|
709
|
-
this._scene = scene;
|
|
710
|
-
this._camera = camera;
|
|
711
|
-
this.initGBufferRenderTarget();
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
var fragmentShader$1 = "#define GLSLIFY 1\nvarying vec2 vUv;uniform sampler2D accumulatedTexture;uniform highp sampler2D depthTexture;uniform highp sampler2D velocityTexture;uniform sampler2D directLightTexture;uniform vec3 backgroundColor;uniform mat4 projectionMatrix;uniform mat4 projectionMatrixInverse;uniform mat4 cameraMatrixWorld;uniform float maxEnvMapMipLevel;uniform float rayDistance;uniform float thickness;uniform float envBlur;uniform vec2 resolution;uniform float cameraNear;uniform float cameraFar;uniform float nearMinusFar;uniform float nearMulFar;uniform float farMinusNear;struct EquirectHdrInfo{sampler2D marginalWeights;sampler2D conditionalWeights;sampler2D map;vec2 size;float totalSumWhole;float totalSumDecimal;};uniform EquirectHdrInfo envMapInfo;\n#define INVALID_RAY_COORDS vec2(-1.0);\n#define EPSILON 0.00001\n#define ONE_MINUS_EPSILON 1.0 - EPSILON\nvec2 invTexSize;\n#define MODE_SSGI 0\n#define MODE_SSR 1\n#include <packing>\n#include <gbuffer_packing>\n#include <ssgi_utils>\nvec2 RayMarch(inout vec3 dir,inout vec3 hitPos,vec4 random);vec2 BinarySearch(inout vec3 dir,inout vec3 hitPos);struct RayTracingInfo{float NoV;float NoL;float NoH;float LoH;float VoH;bool isDiffuseSample;bool isEnvSample;};struct RayTracingResult{vec3 gi;vec3 l;vec3 hitPos;bool isMissedRay;vec3 brdf;float pdf;};struct EnvMisSample{float pdf;float probability;bool isEnvSample;};vec3 worldNormal;vec3 doSample(const vec3 viewPos,const vec3 viewDir,const vec3 viewNormal,const vec3 worldPos,const float metalness,const float roughness,const bool isDiffuseSample,const bool isEnvSample,const float NoV,const float NoL,const float NoH,const float LoH,const float VoH,const vec4 random,inout vec3 l,inout vec3 hitPos,out bool isMissedRay,out vec3 brdf,out float pdf);void calculateAngles(inout vec3 h,inout vec3 l,inout vec3 v,inout vec3 n,inout float NoL,inout float NoH,inout float LoH,inout float VoH){h=normalize(v+l);NoL=clamp(dot(n,l),EPSILON,ONE_MINUS_EPSILON);NoH=clamp(dot(n,h),EPSILON,ONE_MINUS_EPSILON);LoH=clamp(dot(l,h),EPSILON,ONE_MINUS_EPSILON);VoH=clamp(dot(v,h),EPSILON,ONE_MINUS_EPSILON);}vec3 worldPos;Material mat;void main(){float unpackedDepth=textureLod(depthTexture,vUv,0.0).r;if(unpackedDepth==1.0){vec4 directLight=textureLod(directLightTexture,vUv,0.0);gl_FragColor=packTwoVec4(directLight,directLight);return;}mat=getMaterial(gBufferTexture,vUv);float roughnessSq=clamp(mat.roughness*mat.roughness,0.000001,1.0);invTexSize=1./resolution;float viewZ=getViewZ(unpackedDepth);vec3 viewPos=getViewPosition(viewZ);vec3 viewDir=normalize(viewPos);worldNormal=mat.normal;vec3 viewNormal=normalize((vec4(worldNormal,0.)*cameraMatrixWorld).xyz);worldPos=(cameraMatrixWorld*vec4(viewPos,1.)).xyz;vec3 n=viewNormal;vec3 v=-viewDir;float NoV=max(EPSILON,dot(n,v));vec3 V=(vec4(v,0.)*viewMatrix).xyz;vec3 N=worldNormal;vec4 random;vec3 H,l,h,F,T,B,envMisDir,gi;vec3 diffuseGI,specularGI,brdf,hitPos,specularHitPos;Onb(N,T,B);V=ToLocal(T,B,N,V);vec3 f0=mix(vec3(0.04),mat.diffuse.rgb,mat.metalness);float NoL,NoH,LoH,VoH,diffW,specW,invW,pdf,envPdf,diffuseSamples,specularSamples;bool isDiffuseSample,isEnvSample,isMissedRay;random=blueNoise();H=SampleGGXVNDF(V,roughnessSq,roughnessSq,random.r,random.g);if(H.z<0.0)H=-H;l=normalize(reflect(-V,H));l=ToWorld(T,B,N,l);l=(vec4(l,0.)*cameraMatrixWorld).xyz;l=normalize(l);calculateAngles(h,l,v,n,NoL,NoH,LoH,VoH);\n#if mode == MODE_SSGI\nF=F_Schlick(f0,VoH);diffW=(1.-mat.metalness)*luminance(mat.diffuse.rgb);specW=luminance(F);diffW=max(diffW,EPSILON);specW=max(specW,EPSILON);invW=1./(diffW+specW);diffW*=invW;isDiffuseSample=random.b<diffW;\n#else\nisDiffuseSample=false;\n#endif\nEnvMisSample ems;ems.pdf=1.;envMisDir=vec3(0.0);envPdf=1.;\n#ifdef importanceSampling\nems.pdf=sampleEquirectProbability(envMapInfo,random.rg,envMisDir);envMisDir=normalize((vec4(envMisDir,0.)*cameraMatrixWorld).xyz);ems.probability=dot(envMisDir,viewNormal);ems.probability*=mat.roughness;ems.probability=min(ONE_MINUS_EPSILON,ems.probability);ems.isEnvSample=random.a<ems.probability;if(ems.isEnvSample){ems.pdf/=1.-ems.probability;l=envMisDir;calculateAngles(h,l,v,n,NoL,NoH,LoH,VoH);}else{ems.pdf=1.-ems.probability;}\n#endif\nvec3 diffuseRay=ems.isEnvSample ? envMisDir : cosineSampleHemisphere(viewNormal,random.rg);vec3 specularRay=ems.isEnvSample ? envMisDir : l;\n#if mode == MODE_SSGI\nif(isDiffuseSample){l=diffuseRay;calculateAngles(h,l,v,n,NoL,NoH,LoH,VoH);gi=doSample(viewPos,viewDir,viewNormal,worldPos,mat.metalness,roughnessSq,isDiffuseSample,ems.isEnvSample,NoV,NoL,NoH,LoH,VoH,random,l,hitPos,isMissedRay,brdf,pdf);gi*=brdf;if(ems.isEnvSample){gi*=misHeuristic(ems.pdf,pdf);}else{gi/=pdf;}gi/=ems.pdf;diffuseSamples++;diffuseGI=mix(diffuseGI,gi,1./diffuseSamples);}\n#endif\nl=specularRay;calculateAngles(h,l,v,n,NoL,NoH,LoH,VoH);gi=doSample(viewPos,viewDir,viewNormal,worldPos,mat.metalness,roughnessSq,isDiffuseSample,ems.isEnvSample,NoV,NoL,NoH,LoH,VoH,random,l,hitPos,isMissedRay,brdf,pdf);gi*=brdf;if(ems.isEnvSample){gi*=misHeuristic(ems.pdf,pdf);}else{gi/=pdf;}gi/=ems.pdf;specularHitPos=hitPos;specularSamples++;specularGI=mix(specularGI,gi,1./specularSamples);\n#ifdef useDirectLight\nvec3 directLight=textureLod(directLightTexture,vUv,0.).rgb;diffuseGI+=directLight;specularGI+=directLight;\n#endif\nhighp vec4 gDiffuse,gSpecular;\n#if mode == MODE_SSGI\nif(diffuseSamples==0.0)diffuseGI=vec3(-1.0);gDiffuse=vec4(diffuseGI,mat.roughness);\n#endif\nhighp float rayLength=0.0;vec4 hitPosWS;vec3 cameraPosWS=cameraMatrixWorld[3].xyz;isMissedRay=hitPos.x>10.0e8;if(!isMissedRay){hitPosWS=cameraMatrixWorld*vec4(specularHitPos,1.0);rayLength=distance(cameraPosWS,hitPosWS.xyz);}highp uint packedRoughnessRayLength=packHalf2x16(vec2(rayLength,mat.roughness));highp float a=uintBitsToFloat(packedRoughnessRayLength);\n#if mode == MODE_SSGI\ngSpecular=vec4(specularGI,rayLength);gl_FragColor=packTwoVec4(gDiffuse,gSpecular);\n#else\ngSpecular=vec4(specularGI,a);gl_FragColor=gSpecular;\n#endif\n}vec3 getEnvColor(vec3 l,vec3 worldPos,float roughness,bool isDiffuseSample,bool isEnvSample){vec3 envMapSample;\n#ifdef USE_ENVMAP\nvec3 reflectedWS=normalize((vec4(l,0.)*viewMatrix).xyz);\n#ifdef BOX_PROJECTED_ENV_MAP\nreflectedWS=parallaxCorrectNormal(reflectedWS.xyz,envMapSize,envMapPosition,worldPos);reflectedWS=normalize(reflectedWS.xyz);\n#endif\nfloat mip=envBlur*maxEnvMapMipLevel;if(!isDiffuseSample&&roughness<0.15)mip*=roughness/0.15;envMapSample=sampleEquirectEnvMapColor(reflectedWS,envMapInfo.map,mip);float maxEnvLum=isEnvSample ? 100.0 : 25.0;if(maxEnvLum!=0.0){float envLum=luminance(envMapSample);if(envLum>maxEnvLum){envMapSample*=maxEnvLum/envLum;}}return envMapSample;\n#else\nreturn vec3(0.0);\n#endif\n}float getSaturation(vec3 c){float maxComponent=max(max(c.r,c.g),c.b);float minComponent=min(min(c.r,c.g),c.b);float delta=maxComponent-minComponent;if(maxComponent==minComponent){return 0.0;}else{return delta/maxComponent;}}vec3 doSample(const vec3 viewPos,const vec3 viewDir,const vec3 viewNormal,const vec3 worldPos,const float metalness,const float roughness,const bool isDiffuseSample,const bool isEnvSample,const float NoV,const float NoL,const float NoH,const float LoH,const float VoH,const vec4 random,inout vec3 l,inout vec3 hitPos,out bool isMissedRay,out vec3 brdf,out float pdf){float cosTheta=max(0.0,dot(viewNormal,l));if(isDiffuseSample){vec3 diffuseBrdf=evalDisneyDiffuse(NoL,NoV,LoH,roughness,metalness);pdf=NoL/M_PI;brdf=diffuseBrdf;}else{vec3 specularBrdf=evalDisneySpecular(roughness,NoH,NoV,NoL);pdf=GGXVNDFPdf(NoH,NoV,roughness);brdf=specularBrdf;}brdf*=cosTheta;pdf=max(EPSILON,pdf);hitPos=viewPos;vec2 coords=RayMarch(l,hitPos,random);bool allowMissedRays=false;\n#ifdef missedRays\nallowMissedRays=true;\n#endif\nisMissedRay=hitPos.x==10.0e9;vec3 envMapSample=vec3(0.);if(isMissedRay&&!allowMissedRays)return getEnvColor(l,worldPos,roughness,isDiffuseSample,isEnvSample);vec4 velocity=textureLod(velocityTexture,coords.xy,0.0);vec2 reprojectedUv=coords.xy-velocity.xy;vec3 SSGI;vec3 envColor=getEnvColor(l,worldPos,roughness,isDiffuseSample,isEnvSample);if(reprojectedUv.x>=0.0&&reprojectedUv.x<=1.0&&reprojectedUv.y>=0.0&&reprojectedUv.y<=1.0){vec4 reprojectedGI=textureLod(accumulatedTexture,reprojectedUv,0.);float saturation=getSaturation(mat.diffuse.rgb);reprojectedGI.rgb=mix(reprojectedGI.rgb,vec3(luminance(reprojectedGI.rgb)),(1.-roughness)*saturation*0.4);SSGI=reprojectedGI.rgb;float aspect=resolution.x/resolution.y;float border=0.15;float borderFactor=smoothstep(0.0,border,coords.x)*smoothstep(1.0,1.0-border,coords.x)*smoothstep(0.0,border,coords.y)*smoothstep(1.0,1.0-border,coords.y);borderFactor=sqrt(borderFactor);SSGI=mix(envColor,SSGI,borderFactor);}else{return envColor;}if(allowMissedRays){float ssgiLum=luminance(SSGI);float envLum=luminance(envMapSample);if(envLum>ssgiLum)SSGI=envMapSample;}return SSGI;}vec2 RayMarch(inout vec3 dir,inout vec3 hitPos,vec4 random){float rayHitDepthDifference;dir*=rayDistance/float(steps);vec2 uv;for(int i=1;i<steps;i++){float cs=1.-exp(-0.25*pow(float(i)+random.b-0.5,2.));hitPos+=dir*cs;uv=viewSpaceToScreenSpace(hitPos);float unpackedDepth=textureLod(depthTexture,uv,0.0).r;float z=getViewZ(unpackedDepth);rayHitDepthDifference=z-hitPos.z;if(rayHitDepthDifference>=0.0&&rayHitDepthDifference<thickness){if(refineSteps==0){return uv;}else{return BinarySearch(dir,hitPos);}}}hitPos.xyz=vec3(10.0e9);return uv;}vec2 BinarySearch(inout vec3 dir,inout vec3 hitPos){float rayHitDepthDifference;vec2 uv;dir*=0.5;hitPos-=dir;for(int i=0;i<refineSteps;i++){uv=viewSpaceToScreenSpace(hitPos);float unpackedDepth=textureLod(depthTexture,uv,0.0).r;float z=getViewZ(unpackedDepth);rayHitDepthDifference=z-hitPos.z;dir*=0.5;if(rayHitDepthDifference>=0.0){hitPos-=dir;}else{hitPos+=dir;}}uv=viewSpaceToScreenSpace(hitPos);return uv;}"; // eslint-disable-line
|
|
716
|
-
|
|
717
|
-
var ssgi_utils = "#define GLSLIFY 1\n#define PI M_PI\n#define luminance(a) dot(vec3(0.2125, 0.7154, 0.0721), a)\nfloat getViewZ(const in float depth){\n#ifdef PERSPECTIVE_CAMERA\nreturn nearMulFar/(farMinusNear*depth-cameraFar);\n#else\nreturn depth*nearMinusFar-cameraNear;\n#endif\n}vec3 getViewPosition(float viewZ){float clipW=projectionMatrix[2][3]*viewZ+projectionMatrix[3][3];vec4 clipPosition=vec4((vec3(vUv,viewZ)-0.5)*2.0,1.0);clipPosition*=clipW;vec3 p=(projectionMatrixInverse*clipPosition).xyz;p.z=viewZ;return p;}vec2 viewSpaceToScreenSpace(const vec3 position){vec4 projectedCoord=projectionMatrix*vec4(position,1.0);projectedCoord.xy/=projectedCoord.w;projectedCoord.xy=projectedCoord.xy*0.5+0.5;return projectedCoord.xy;}vec3 worldSpaceToViewSpace(vec3 worldPosition){vec4 viewPosition=viewMatrix*vec4(worldPosition,1.0);return viewPosition.xyz/viewPosition.w;}\n#ifdef BOX_PROJECTED_ENV_MAP\nuniform vec3 envMapSize;uniform vec3 envMapPosition;vec3 parallaxCorrectNormal(const vec3 v,const vec3 cubeSize,const vec3 cubePos,const vec3 worldPosition){vec3 nDir=normalize(v);vec3 rbmax=(.5*cubeSize+cubePos-worldPosition)/nDir;vec3 rbmin=(-.5*cubeSize+cubePos-worldPosition)/nDir;vec3 rbminmax;rbminmax.x=(nDir.x>0.)? rbmax.x : rbmin.x;rbminmax.y=(nDir.y>0.)? rbmax.y : rbmin.y;rbminmax.z=(nDir.z>0.)? rbmax.z : rbmin.z;float correction=min(min(rbminmax.x,rbminmax.y),rbminmax.z);vec3 boxIntersection=worldPosition+nDir*correction;return boxIntersection-cubePos;}\n#endif\n#define M_PI 3.1415926535897932384626433832795\nvec2 equirectDirectionToUv(const vec3 direction){vec2 uv=vec2(atan(direction.z,direction.x),acos(direction.y));uv/=vec2(2.0*M_PI,M_PI);uv.x+=0.5;uv.y=1.0-uv.y;return uv;}vec3 equirectUvToDirection(vec2 uv){uv.x-=0.5;uv.y=1.0-uv.y;float theta=uv.x*2.0*PI;float phi=uv.y*PI;float sinPhi=sin(phi);return vec3(sinPhi*cos(theta),cos(phi),sinPhi*sin(theta));}vec3 sampleEquirectEnvMapColor(const vec3 direction,const sampler2D map,const float lod){return textureLod(map,equirectDirectionToUv(direction),lod).rgb;}mat3 getBasisFromNormal(const vec3 normal){vec3 other;if(abs(normal.x)>0.5){other=vec3(0.0,1.0,0.0);}else{other=vec3(1.0,0.0,0.0);}vec3 ortho=normalize(cross(normal,other));vec3 ortho2=normalize(cross(normal,ortho));return mat3(ortho2,ortho,normal);}vec3 F_Schlick(const vec3 f0,const float theta){return f0+(1.-f0)*pow(1.0-theta,5.);}float F_Schlick(const float f0,const float f90,const float theta){return f0+(f90-f0)*pow(1.0-theta,5.0);}float D_GTR(const float roughness,const float NoH,const float k){float a2=pow(roughness,2.);return a2/(PI*pow((NoH*NoH)*(a2*a2-1.)+1.,k));}float SmithG(const float NDotV,const float alphaG){float a=alphaG*alphaG;float b=NDotV*NDotV;return(2.0*NDotV)/(NDotV+sqrt(a+b-a*b));}float GGXVNDFPdf(const float NoH,const float NoV,const float roughness){float D=D_GTR(roughness,NoH,2.);float G1=SmithG(NoV,roughness*roughness);return(D*G1)/max(0.00001,4.0*NoV);}float GeometryTerm(const float NoL,const float NoV,const float roughness){float a2=roughness*roughness;float G1=SmithG(NoV,a2);float G2=SmithG(NoL,a2);return G1*G2;}vec3 evalDisneyDiffuse(const float NoL,const float NoV,const float LoH,const float roughness,const float metalness){float FD90=0.5+2.*roughness*pow(LoH,2.);float a=F_Schlick(1.,FD90,NoL);float b=F_Schlick(1.,FD90,NoV);return vec3((a*b/PI)*(1.-metalness));}vec3 evalDisneySpecular(const float roughness,const float NoH,const float NoV,const float NoL){float D=D_GTR(roughness,NoH,2.);float G=GeometryTerm(NoL,NoV,pow(0.5+roughness*.5,2.));vec3 spec=vec3(D*G/(4.*NoL*NoV));return spec;}vec3 SampleGGXVNDF(const vec3 V,const float ax,const float ay,const float r1,const float r2){vec3 Vh=normalize(vec3(ax*V.x,ay*V.y,V.z));float lensq=Vh.x*Vh.x+Vh.y*Vh.y;vec3 T1=lensq>0. ? vec3(-Vh.y,Vh.x,0.)*inversesqrt(lensq): vec3(1.,0.,0.);vec3 T2=cross(Vh,T1);float r=sqrt(r1);float phi=2.0*PI*r2;float t1=r*cos(phi);float t2=r*sin(phi);float s=0.5*(1.0+Vh.z);t2=(1.0-s)*sqrt(1.0-t1*t1)+s*t2;vec3 Nh=t1*T1+t2*T2+sqrt(max(0.0,1.0-t1*t1-t2*t2))*Vh;return normalize(vec3(ax*Nh.x,ay*Nh.y,max(0.0,Nh.z)));}void Onb(const vec3 N,inout vec3 T,inout vec3 B){vec3 up=abs(N.z)<0.9999999 ? vec3(0,0,1): vec3(1,0,0);T=normalize(cross(up,N));B=cross(N,T);}vec3 ToLocal(const vec3 X,const vec3 Y,const vec3 Z,const vec3 V){return vec3(dot(V,X),dot(V,Y),dot(V,Z));}vec3 ToWorld(const vec3 X,const vec3 Y,const vec3 Z,const vec3 V){return V.x*X+V.y*Y+V.z*Z;}vec3 cosineSampleHemisphere(const vec3 n,const vec2 u){float r=sqrt(u.x);float theta=2.0*PI*u.y;vec3 b=normalize(cross(n,vec3(0.0,1.0,1.0)));vec3 t=cross(b,n);return normalize(r*sin(theta)*b+sqrt(1.0-u.x)*n+r*cos(theta)*t);}float equirectDirectionPdf(vec3 direction){vec2 uv=equirectDirectionToUv(direction);float theta=uv.y*PI;float sinTheta=sin(theta);if(sinTheta==0.0){return 0.0;}return 1.0/(2.0*PI*PI*sinTheta);}float sampleEquirectProbability(EquirectHdrInfo info,vec2 blueNoise,out vec3 direction){float v=textureLod(info.marginalWeights,vec2(blueNoise.x,0.0),0.).x;float u=textureLod(info.conditionalWeights,vec2(blueNoise.y,v),0.).x;vec2 uv=vec2(u,v);vec3 derivedDirection=equirectUvToDirection(uv);direction=derivedDirection;vec3 color=texture(info.map,uv).rgb;float totalSum=info.totalSumWhole+info.totalSumDecimal;float lum=luminance(color);float pdf=lum/totalSum;return info.size.x*info.size.y*pdf;}float misHeuristic(float a,float b){float aa=a*a;float bb=b*b;return aa/(aa+bb);}vec3 alignToNormal(const vec3 normal,const vec3 direction){vec3 tangent;vec3 bitangent;Onb(normal,tangent,bitangent);vec3 localDir=ToLocal(tangent,bitangent,normal,direction);vec3 localDirAligned=vec3(localDir.x,localDir.y,abs(localDir.z));vec3 alignedDir=ToWorld(tangent,bitangent,normal,localDirAligned);return alignedDir;}float getFlatness(vec3 g,vec3 rp){vec3 gw=fwidth(g);vec3 pw=fwidth(rp);float wfcurvature=length(gw)/length(pw);wfcurvature=smoothstep(0.0,30.,wfcurvature);return clamp(wfcurvature,0.,1.);}"; // eslint-disable-line
|
|
718
|
-
|
|
719
|
-
// source: https://github.com/gkjohnson/three-gpu-pathtracer/blob/main/src/uniforms/EquirectHdrInfoUniform.js
|
|
720
|
-
const workerOnMessage = ({ data: { width, height, isFloatType, flipY, data } })=>{
|
|
721
|
-
// from: https://github.com/mrdoob/three.js/blob/dev/src/extras/DataUtils.js
|
|
722
|
-
// importing modules doesn't seem to work for workers that were generated through createObjectURL() for some reason
|
|
723
|
-
const _tables = /* @__PURE__*/ _generateTables();
|
|
724
|
-
function _generateTables() {
|
|
725
|
-
// float32 to float16 helpers
|
|
726
|
-
const buffer = new ArrayBuffer(4);
|
|
727
|
-
const floatView = new Float32Array(buffer);
|
|
728
|
-
const uint32View = new Uint32Array(buffer);
|
|
729
|
-
const baseTable = new Uint32Array(512);
|
|
730
|
-
const shiftTable = new Uint32Array(512);
|
|
731
|
-
for(let i = 0; i < 256; ++i){
|
|
732
|
-
const e = i - 127;
|
|
733
|
-
// very small number (0, -0)
|
|
734
|
-
if (e < -27) {
|
|
735
|
-
baseTable[i] = 0x0000;
|
|
736
|
-
baseTable[i | 0x100] = 0x8000;
|
|
737
|
-
shiftTable[i] = 24;
|
|
738
|
-
shiftTable[i | 0x100] = 24;
|
|
739
|
-
// small number (denorm)
|
|
740
|
-
} else if (e < -14) {
|
|
741
|
-
baseTable[i] = 0x0400 >> -e - 14;
|
|
742
|
-
baseTable[i | 0x100] = 0x0400 >> -e - 14 | 0x8000;
|
|
743
|
-
shiftTable[i] = -e - 1;
|
|
744
|
-
shiftTable[i | 0x100] = -e - 1;
|
|
745
|
-
// normal number
|
|
746
|
-
} else if (e <= 15) {
|
|
747
|
-
baseTable[i] = e + 15 << 10;
|
|
748
|
-
baseTable[i | 0x100] = e + 15 << 10 | 0x8000;
|
|
749
|
-
shiftTable[i] = 13;
|
|
750
|
-
shiftTable[i | 0x100] = 13;
|
|
751
|
-
// large number (Infinity, -Infinity)
|
|
752
|
-
} else if (e < 128) {
|
|
753
|
-
baseTable[i] = 0x7c00;
|
|
754
|
-
baseTable[i | 0x100] = 0xfc00;
|
|
755
|
-
shiftTable[i] = 24;
|
|
756
|
-
shiftTable[i | 0x100] = 24;
|
|
757
|
-
// stay (NaN, Infinity, -Infinity)
|
|
758
|
-
} else {
|
|
759
|
-
baseTable[i] = 0x7c00;
|
|
760
|
-
baseTable[i | 0x100] = 0xfc00;
|
|
761
|
-
shiftTable[i] = 13;
|
|
762
|
-
shiftTable[i | 0x100] = 13;
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
// float16 to float32 helpers
|
|
766
|
-
const mantissaTable = new Uint32Array(2048);
|
|
767
|
-
const exponentTable = new Uint32Array(64);
|
|
768
|
-
const offsetTable = new Uint32Array(64);
|
|
769
|
-
for(let i = 1; i < 1024; ++i){
|
|
770
|
-
let m = i << 13 // zero pad mantissa bits
|
|
771
|
-
;
|
|
772
|
-
let e = 0 // zero exponent
|
|
773
|
-
;
|
|
774
|
-
// normalized
|
|
775
|
-
while((m & 0x00800000) === 0){
|
|
776
|
-
m <<= 1;
|
|
777
|
-
e -= 0x00800000 // decrement exponent
|
|
778
|
-
;
|
|
779
|
-
}
|
|
780
|
-
m &= ~0x00800000 // clear leading 1 bit
|
|
781
|
-
;
|
|
782
|
-
e += 0x38800000 // adjust bias
|
|
783
|
-
;
|
|
784
|
-
mantissaTable[i] = m | e;
|
|
785
|
-
}
|
|
786
|
-
for(let i = 1024; i < 2048; ++i){
|
|
787
|
-
mantissaTable[i] = 0x38000000 + (i - 1024 << 13);
|
|
788
|
-
}
|
|
789
|
-
for(let i = 1; i < 31; ++i){
|
|
790
|
-
exponentTable[i] = i << 23;
|
|
791
|
-
}
|
|
792
|
-
exponentTable[31] = 0x47800000;
|
|
793
|
-
exponentTable[32] = 0x80000000;
|
|
794
|
-
for(let i = 33; i < 63; ++i){
|
|
795
|
-
exponentTable[i] = 0x80000000 + (i - 32 << 23);
|
|
796
|
-
}
|
|
797
|
-
exponentTable[63] = 0xc7800000;
|
|
798
|
-
for(let i = 1; i < 64; ++i){
|
|
799
|
-
if (i !== 32) {
|
|
800
|
-
offsetTable[i] = 1024;
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
return {
|
|
804
|
-
floatView: floatView,
|
|
805
|
-
uint32View: uint32View,
|
|
806
|
-
baseTable: baseTable,
|
|
807
|
-
shiftTable: shiftTable,
|
|
808
|
-
mantissaTable: mantissaTable,
|
|
809
|
-
exponentTable: exponentTable,
|
|
810
|
-
offsetTable: offsetTable
|
|
811
|
-
};
|
|
812
|
-
}
|
|
813
|
-
function fromHalfFloat(val) {
|
|
814
|
-
const m = val >> 10;
|
|
815
|
-
_tables.uint32View[0] = _tables.mantissaTable[_tables.offsetTable[m] + (val & 0x3ff)] + _tables.exponentTable[m];
|
|
816
|
-
return _tables.floatView[0];
|
|
817
|
-
}
|
|
818
|
-
function colorToLuminance(r, g, b) {
|
|
819
|
-
// https://en.wikipedia.org/wiki/Relative_luminance
|
|
820
|
-
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
821
|
-
}
|
|
822
|
-
const binarySearchFindClosestIndexOf = (array, targetValue, offset = 0, count = array.length)=>{
|
|
823
|
-
let lower = offset;
|
|
824
|
-
let upper = offset + count - 1;
|
|
825
|
-
while(lower < upper){
|
|
826
|
-
const mid = lower + upper >> 1;
|
|
827
|
-
// check if the middle array value is above or below the target and shift
|
|
828
|
-
// which half of the array we're looking at
|
|
829
|
-
if (array[mid] < targetValue) {
|
|
830
|
-
lower = mid + 1;
|
|
831
|
-
} else {
|
|
832
|
-
upper = mid;
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
return lower - offset;
|
|
836
|
-
};
|
|
837
|
-
const gatherData = (data, width, height, flipY, marginalDataArray, conditionalDataArray)=>{
|
|
838
|
-
// "conditional" = "pixel relative to row pixels sum"
|
|
839
|
-
// "marginal" = "row relative to row sum"
|
|
840
|
-
// remove any y flipping for cdf computation
|
|
841
|
-
if (flipY) {
|
|
842
|
-
for(let y = 0, h = height - 1; y <= h; y++){
|
|
843
|
-
for(let x = 0, w = width * 4; x < w; x += 4){
|
|
844
|
-
const newY = h - y;
|
|
845
|
-
const ogIndex = y * w + x;
|
|
846
|
-
const newIndex = newY * w + x;
|
|
847
|
-
data[newIndex] = data[ogIndex];
|
|
848
|
-
data[newIndex + 1] = data[ogIndex + 1];
|
|
849
|
-
data[newIndex + 2] = data[ogIndex + 2];
|
|
850
|
-
data[newIndex + 3] = data[ogIndex + 3];
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
// track the importance of any given pixel in the image by tracking its weight relative to other pixels in the image
|
|
855
|
-
const pdfConditional = new Float32Array(width * height);
|
|
856
|
-
const cdfConditional = new Float32Array(width * height);
|
|
857
|
-
const pdfMarginal = new Float32Array(height);
|
|
858
|
-
const cdfMarginal = new Float32Array(height);
|
|
859
|
-
let totalSumValue = 0.0;
|
|
860
|
-
let cumulativeWeightMarginal = 0.0;
|
|
861
|
-
for(let y = 0; y < height; y++){
|
|
862
|
-
let cumulativeRowWeight = 0.0;
|
|
863
|
-
for(let x = 0; x < width; x++){
|
|
864
|
-
const i = y * width + x;
|
|
865
|
-
const r = data[4 * i + 0];
|
|
866
|
-
const g = data[4 * i + 1];
|
|
867
|
-
const b = data[4 * i + 2];
|
|
868
|
-
// the probability of the pixel being selected in this row is the
|
|
869
|
-
// scale of the luminance relative to the rest of the pixels.
|
|
870
|
-
// TODO: this should also account for the solid angle of the pixel when sampling
|
|
871
|
-
const weight = colorToLuminance(r, g, b);
|
|
872
|
-
cumulativeRowWeight += weight;
|
|
873
|
-
totalSumValue += weight;
|
|
874
|
-
pdfConditional[i] = weight;
|
|
875
|
-
cdfConditional[i] = cumulativeRowWeight;
|
|
876
|
-
}
|
|
877
|
-
// can happen if the row is all black
|
|
878
|
-
if (cumulativeRowWeight !== 0) {
|
|
879
|
-
// scale the pdf and cdf to [0.0, 1.0]
|
|
880
|
-
for(let i = y * width, l = y * width + width; i < l; i++){
|
|
881
|
-
pdfConditional[i] /= cumulativeRowWeight;
|
|
882
|
-
cdfConditional[i] /= cumulativeRowWeight;
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
cumulativeWeightMarginal += cumulativeRowWeight;
|
|
886
|
-
// compute the marginal pdf and cdf along the height of the map.
|
|
887
|
-
pdfMarginal[y] = cumulativeRowWeight;
|
|
888
|
-
cdfMarginal[y] = cumulativeWeightMarginal;
|
|
889
|
-
}
|
|
890
|
-
// can happen if the texture is all black
|
|
891
|
-
if (cumulativeWeightMarginal !== 0) {
|
|
892
|
-
// scale the marginal pdf and cdf to [0.0, 1.0]
|
|
893
|
-
for(let i = 0, l = pdfMarginal.length; i < l; i++){
|
|
894
|
-
pdfMarginal[i] /= cumulativeWeightMarginal;
|
|
895
|
-
cdfMarginal[i] /= cumulativeWeightMarginal;
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
// compute a sorted index of distributions and the probabilities along them for both
|
|
899
|
-
// the marginal and conditional data. These will be used to sample with a random number
|
|
900
|
-
// to retrieve a uv value to sample in the environment map.
|
|
901
|
-
// These values continually increase so it's okay to interpolate between them.
|
|
902
|
-
// we add a half texel offset so we're sampling the center of the pixel
|
|
903
|
-
for(let i = 0; i < height; i++){
|
|
904
|
-
const dist = (i + 1) / height;
|
|
905
|
-
const row = binarySearchFindClosestIndexOf(cdfMarginal, dist);
|
|
906
|
-
marginalDataArray[i] = (row + 0.5) / height;
|
|
907
|
-
}
|
|
908
|
-
for(let y = 0; y < height; y++){
|
|
909
|
-
for(let x = 0; x < width; x++){
|
|
910
|
-
const i = y * width + x;
|
|
911
|
-
const dist = (x + 1) / width;
|
|
912
|
-
const col = binarySearchFindClosestIndexOf(cdfConditional, dist, y * width, width);
|
|
913
|
-
conditionalDataArray[i] = (col + 0.5) / width;
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
return totalSumValue;
|
|
917
|
-
};
|
|
918
|
-
if (!isFloatType) {
|
|
919
|
-
const newData = new Float32Array(data.length);
|
|
920
|
-
// eslint-disable-next-line guard-for-in
|
|
921
|
-
for(let i = 0; i < data.length; i++){
|
|
922
|
-
newData[i] = fromHalfFloat(data[i]);
|
|
923
|
-
}
|
|
924
|
-
data = newData;
|
|
925
|
-
}
|
|
926
|
-
const marginalDataArray = new Float32Array(height);
|
|
927
|
-
const conditionalDataArray = new Float32Array(width * height);
|
|
928
|
-
const totalSumValue = gatherData(data, width, height, flipY, marginalDataArray, conditionalDataArray);
|
|
929
|
-
postMessage({
|
|
930
|
-
totalSumValue,
|
|
931
|
-
marginalDataArray,
|
|
932
|
-
conditionalDataArray
|
|
933
|
-
});
|
|
934
|
-
};
|
|
935
|
-
const blob = new Blob([
|
|
936
|
-
"onmessage = " + workerOnMessage
|
|
937
|
-
], {
|
|
938
|
-
type: "application/javascript"
|
|
939
|
-
});
|
|
940
|
-
const workerUrl = URL.createObjectURL(blob);
|
|
941
|
-
class EquirectHdrInfoUniform {
|
|
942
|
-
dispose() {
|
|
943
|
-
this.marginalWeights.dispose();
|
|
944
|
-
this.conditionalWeights.dispose();
|
|
945
|
-
this.map.dispose();
|
|
946
|
-
}
|
|
947
|
-
updateFrom(map) {
|
|
948
|
-
map = map.clone();
|
|
949
|
-
const { width, height, data } = map.image;
|
|
950
|
-
const { type } = map;
|
|
951
|
-
this.size.set(width, height);
|
|
952
|
-
return new Promise((resolve)=>{
|
|
953
|
-
var _this_worker;
|
|
954
|
-
(_this_worker = this.worker) == null ? void 0 : _this_worker.terminate();
|
|
955
|
-
this.worker = new Worker(workerUrl);
|
|
956
|
-
this.worker.postMessage({
|
|
957
|
-
width,
|
|
958
|
-
height,
|
|
959
|
-
isFloatType: type === FloatType,
|
|
960
|
-
flipY: map.flipY,
|
|
961
|
-
data
|
|
962
|
-
});
|
|
963
|
-
this.worker.onmessage = ({ data: { totalSumValue, marginalDataArray, conditionalDataArray } })=>{
|
|
964
|
-
this.dispose();
|
|
965
|
-
const { marginalWeights, conditionalWeights } = this;
|
|
966
|
-
marginalWeights.image = {
|
|
967
|
-
width: height,
|
|
968
|
-
height: 1,
|
|
969
|
-
data: marginalDataArray
|
|
970
|
-
};
|
|
971
|
-
marginalWeights.needsUpdate = true;
|
|
972
|
-
conditionalWeights.image = {
|
|
973
|
-
width,
|
|
974
|
-
height,
|
|
975
|
-
data: conditionalDataArray
|
|
976
|
-
};
|
|
977
|
-
conditionalWeights.needsUpdate = true;
|
|
978
|
-
const totalSumWhole = ~~totalSumValue;
|
|
979
|
-
const totalSumDecimal = totalSumValue - totalSumWhole;
|
|
980
|
-
this.totalSumWhole = totalSumWhole;
|
|
981
|
-
this.totalSumDecimal = totalSumDecimal;
|
|
982
|
-
this.map = map;
|
|
983
|
-
this.worker = null;
|
|
984
|
-
resolve(map);
|
|
985
|
-
};
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
constructor(){
|
|
989
|
-
// we use NearestFilter instead of LinearFilter because on many recent Apple devices filtering from such a texture does not work
|
|
990
|
-
// Default to a white texture and associated weights so we don't
|
|
991
|
-
// just render black initially.
|
|
992
|
-
const whiteTex = new DataTexture(new Float32Array([
|
|
993
|
-
1,
|
|
994
|
-
1,
|
|
995
|
-
1,
|
|
996
|
-
1
|
|
997
|
-
]), 1, 1);
|
|
998
|
-
whiteTex.type = FloatType;
|
|
999
|
-
whiteTex.format = RGBAFormat;
|
|
1000
|
-
whiteTex.minFilter = NearestFilter;
|
|
1001
|
-
whiteTex.magFilter = NearestFilter;
|
|
1002
|
-
whiteTex.wrapS = RepeatWrapping;
|
|
1003
|
-
whiteTex.wrapT = RepeatWrapping;
|
|
1004
|
-
whiteTex.generateMipmaps = false;
|
|
1005
|
-
whiteTex.needsUpdate = true;
|
|
1006
|
-
// Stores a map of [0, 1] value -> cumulative importance row & pdf
|
|
1007
|
-
// used to sampling a random value to a relevant row to sample from
|
|
1008
|
-
const marginalWeights = new DataTexture(new Float32Array([
|
|
1009
|
-
0,
|
|
1010
|
-
1
|
|
1011
|
-
]), 1, 2);
|
|
1012
|
-
marginalWeights.type = FloatType;
|
|
1013
|
-
marginalWeights.format = RedFormat;
|
|
1014
|
-
marginalWeights.minFilter = NearestFilter;
|
|
1015
|
-
marginalWeights.magFilter = NearestFilter;
|
|
1016
|
-
marginalWeights.generateMipmaps = false;
|
|
1017
|
-
marginalWeights.needsUpdate = true;
|
|
1018
|
-
// Stores a map of [0, 1] value -> cumulative importance column & pdf
|
|
1019
|
-
// used to sampling a random value to a relevant pixel to sample from
|
|
1020
|
-
const conditionalWeights = new DataTexture(new Float32Array([
|
|
1021
|
-
0,
|
|
1022
|
-
0,
|
|
1023
|
-
1,
|
|
1024
|
-
1
|
|
1025
|
-
]), 2, 2);
|
|
1026
|
-
conditionalWeights.type = FloatType;
|
|
1027
|
-
conditionalWeights.format = RedFormat;
|
|
1028
|
-
conditionalWeights.minFilter = NearestFilter;
|
|
1029
|
-
conditionalWeights.magFilter = NearestFilter;
|
|
1030
|
-
conditionalWeights.generateMipmaps = false;
|
|
1031
|
-
conditionalWeights.needsUpdate = true;
|
|
1032
|
-
this.map = whiteTex;
|
|
1033
|
-
this.marginalWeights = marginalWeights;
|
|
1034
|
-
this.conditionalWeights = conditionalWeights;
|
|
1035
|
-
// the total sum value is separated into two values to work around low precision
|
|
1036
|
-
// storage of floating values in structs
|
|
1037
|
-
this.totalSumWhole = 1;
|
|
1038
|
-
this.totalSumDecimal = 0;
|
|
1039
|
-
this.size = new Vector2();
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
class SSGIMaterial extends ShaderMaterial {
|
|
1044
|
-
constructor(){
|
|
1045
|
-
super({
|
|
1046
|
-
type: "SSGIMaterial",
|
|
1047
|
-
uniforms: {
|
|
1048
|
-
accumulatedTexture: new Uniform(null),
|
|
1049
|
-
gBufferTexture: new Uniform(null),
|
|
1050
|
-
depthTexture: new Uniform(null),
|
|
1051
|
-
velocityTexture: new Uniform(null),
|
|
1052
|
-
directLightTexture: new Uniform(null),
|
|
1053
|
-
blueNoiseTexture: new Uniform(null),
|
|
1054
|
-
projectionMatrix: new Uniform(new Matrix4()),
|
|
1055
|
-
projectionMatrixInverse: new Uniform(new Matrix4()),
|
|
1056
|
-
cameraMatrixWorld: new Uniform(new Matrix4()),
|
|
1057
|
-
viewMatrix: new Uniform(new Matrix4()),
|
|
1058
|
-
cameraNear: new Uniform(0),
|
|
1059
|
-
cameraFar: new Uniform(0),
|
|
1060
|
-
nearMulFar: new Uniform(0),
|
|
1061
|
-
nearMinusFar: new Uniform(0),
|
|
1062
|
-
farMinusNear: new Uniform(0),
|
|
1063
|
-
rayDistance: new Uniform(0),
|
|
1064
|
-
thickness: new Uniform(0),
|
|
1065
|
-
frame: new Uniform(0),
|
|
1066
|
-
envBlur: new Uniform(0),
|
|
1067
|
-
maxEnvMapMipLevel: new Uniform(0),
|
|
1068
|
-
envMapInfo: {
|
|
1069
|
-
value: new EquirectHdrInfoUniform()
|
|
1070
|
-
},
|
|
1071
|
-
envMapPosition: new Uniform(new Vector3()),
|
|
1072
|
-
envMapSize: new Uniform(new Vector3()),
|
|
1073
|
-
backgroundColor: new Uniform(new Color()),
|
|
1074
|
-
resolution: new Uniform(new Vector2()),
|
|
1075
|
-
blueNoiseRepeat: new Uniform(new Vector2())
|
|
1076
|
-
},
|
|
1077
|
-
defines: {
|
|
1078
|
-
steps: 20,
|
|
1079
|
-
refineSteps: 5,
|
|
1080
|
-
CUBEUV_TEXEL_WIDTH: 0,
|
|
1081
|
-
CUBEUV_TEXEL_HEIGHT: 0,
|
|
1082
|
-
CUBEUV_MAX_MIP: 0,
|
|
1083
|
-
vWorldPosition: "worldPos"
|
|
1084
|
-
},
|
|
1085
|
-
fragmentShader: fragmentShader$1.replace("#include <ssgi_utils>", ssgi_utils).replace("#include <gbuffer_packing>", gbuffer_packing),
|
|
1086
|
-
vertexShader,
|
|
1087
|
-
blending: NoBlending,
|
|
1088
|
-
depthWrite: false,
|
|
1089
|
-
depthTest: false,
|
|
1090
|
-
toneMapped: false
|
|
1091
|
-
});
|
|
1092
|
-
useBlueNoise(this);
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
const blackColor = new Color(0);
|
|
1097
|
-
class SSGIPass extends Pass {
|
|
1098
|
-
get texture() {
|
|
1099
|
-
return this.renderTarget.texture;
|
|
1100
|
-
}
|
|
1101
|
-
setSize(width, height) {
|
|
1102
|
-
this.renderTarget.setSize(width * this.ssgiEffect.resolutionScale, height * this.ssgiEffect.resolutionScale);
|
|
1103
|
-
this.gBufferPass.setSize(width, height);
|
|
1104
|
-
this.fullscreenMaterial.uniforms.resolution.value.set(this.renderTarget.width, this.renderTarget.height);
|
|
1105
|
-
}
|
|
1106
|
-
dispose() {
|
|
1107
|
-
super.dispose();
|
|
1108
|
-
this.renderTarget.dispose();
|
|
1109
|
-
this.renderTarget.dispose();
|
|
1110
|
-
this.fullscreenMaterial.dispose();
|
|
1111
|
-
}
|
|
1112
|
-
render(renderer) {
|
|
1113
|
-
this.frame = (this.frame + 1) % 4096;
|
|
1114
|
-
const { mask } = this._camera.layers;
|
|
1115
|
-
const hasSelection = this.ssgiEffect.selection.size > 0;
|
|
1116
|
-
this._camera.layers.set(hasSelection ? this.ssgiEffect.selection.layer : 0);
|
|
1117
|
-
// render G-Buffers
|
|
1118
|
-
this.gBufferPass.render(renderer);
|
|
1119
|
-
this._camera.layers.mask = mask;
|
|
1120
|
-
// update uniforms
|
|
1121
|
-
this.fullscreenMaterial.uniforms.frame.value = this.frame;
|
|
1122
|
-
this.fullscreenMaterial.uniforms.cameraNear.value = this._camera.near;
|
|
1123
|
-
this.fullscreenMaterial.uniforms.cameraFar.value = this._camera.far;
|
|
1124
|
-
this.fullscreenMaterial.uniforms.nearMinusFar.value = this._camera.near - this._camera.far;
|
|
1125
|
-
this.fullscreenMaterial.uniforms.farMinusNear.value = this._camera.far - this._camera.near;
|
|
1126
|
-
this.fullscreenMaterial.uniforms.nearMulFar.value = this._camera.near * this._camera.far;
|
|
1127
|
-
this.fullscreenMaterial.uniforms.accumulatedTexture.value = this.ssgiEffect.denoiser.texture;
|
|
1128
|
-
this.fullscreenMaterial.uniforms.velocityTexture.value = this.ssgiEffect.velocityTexture;
|
|
1129
|
-
const bgColor = this._scene.background instanceof Color ? this._scene.background : blackColor;
|
|
1130
|
-
this.fullscreenMaterial.uniforms.backgroundColor.value.copy(bgColor);
|
|
1131
|
-
renderer.setRenderTarget(this.renderTarget);
|
|
1132
|
-
renderer.render(this.scene, this.camera);
|
|
1133
|
-
}
|
|
1134
|
-
constructor(ssgiEffect, options){
|
|
1135
|
-
super("SSGIPass");
|
|
1136
|
-
this.defaultFragmentShader = "";
|
|
1137
|
-
this.frame = 21483;
|
|
1138
|
-
this.ssgiEffect = ssgiEffect;
|
|
1139
|
-
this._scene = ssgiEffect._scene;
|
|
1140
|
-
this._camera = ssgiEffect._camera;
|
|
1141
|
-
this.fullscreenMaterial = new SSGIMaterial();
|
|
1142
|
-
this.defaultFragmentShader = this.fullscreenMaterial.fragmentShader;
|
|
1143
|
-
// const { mode } = options
|
|
1144
|
-
this.renderTarget = new WebGLRenderTarget(1, 1, {
|
|
1145
|
-
type: FloatType,
|
|
1146
|
-
minFilter: NearestFilter,
|
|
1147
|
-
magFilter: NearestFilter,
|
|
1148
|
-
depthBuffer: false
|
|
1149
|
-
});
|
|
1150
|
-
this.renderTarget.texture.name = "SSGIPass.Texture";
|
|
1151
|
-
// set up basic uniforms that we don't have to update
|
|
1152
|
-
this.fullscreenMaterial.uniforms.cameraMatrixWorld.value = this._camera.matrixWorld;
|
|
1153
|
-
this.fullscreenMaterial.uniforms.viewMatrix.value = this._camera.matrixWorldInverse;
|
|
1154
|
-
this.fullscreenMaterial.uniforms.projectionMatrix.value = this._camera.projectionMatrix;
|
|
1155
|
-
this.fullscreenMaterial.uniforms.projectionMatrixInverse.value = this._camera.projectionMatrixInverse;
|
|
1156
|
-
if (ssgiEffect._camera.isPerspectiveCamera) this.fullscreenMaterial.defines.PERSPECTIVE_CAMERA = "";
|
|
1157
|
-
this.fullscreenMaterial.defines.mode = [
|
|
1158
|
-
"ssgi",
|
|
1159
|
-
"ssr"
|
|
1160
|
-
].indexOf(options.mode);
|
|
1161
|
-
this.gBufferPass = new GBufferPass(this._scene, this._camera);
|
|
1162
|
-
this.fullscreenMaterial.uniforms.gBufferTexture.value = this.gBufferPass.texture;
|
|
1163
|
-
this.fullscreenMaterial.uniforms.depthTexture.value = this.gBufferPass.depthTexture;
|
|
606
|
+
this.setSize(options.width, options.height);
|
|
1164
607
|
}
|
|
1165
608
|
}
|
|
1166
609
|
|
|
@@ -1266,11 +709,14 @@ const velocity_uniforms = {
|
|
|
1266
709
|
class VelocityDepthNormalMaterial extends ShaderMaterial {
|
|
1267
710
|
constructor(camera){
|
|
1268
711
|
super({
|
|
1269
|
-
uniforms:
|
|
1270
|
-
|
|
1271
|
-
|
|
712
|
+
uniforms: {
|
|
713
|
+
...UniformsUtils.clone(velocity_uniforms),
|
|
714
|
+
...{
|
|
715
|
+
cameraMatrixWorld: {
|
|
716
|
+
value: camera.matrixWorld
|
|
717
|
+
}
|
|
1272
718
|
}
|
|
1273
|
-
}
|
|
719
|
+
},
|
|
1274
720
|
vertexShader: /* glsl */ `
|
|
1275
721
|
#include <common>
|
|
1276
722
|
#include <uv_pars_vertex>
|
|
@@ -1392,20 +838,6 @@ const keepMaterialMapUpdated = (mrtMaterial, originalMaterial, prop, define, use
|
|
|
1392
838
|
}
|
|
1393
839
|
}
|
|
1394
840
|
};
|
|
1395
|
-
const getVisibleChildren = (object)=>{
|
|
1396
|
-
const queue = [
|
|
1397
|
-
object
|
|
1398
|
-
];
|
|
1399
|
-
const objects = [];
|
|
1400
|
-
while(queue.length !== 0){
|
|
1401
|
-
const mesh = queue.shift();
|
|
1402
|
-
if (mesh.material) objects.push(mesh);
|
|
1403
|
-
for (const c of mesh.children){
|
|
1404
|
-
if (c.visible) queue.push(c);
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
return objects;
|
|
1408
|
-
};
|
|
1409
841
|
|
|
1410
842
|
const backgroundColor = new Color(0);
|
|
1411
843
|
const zeroVec2 = new Vector2();
|
|
@@ -1448,7 +880,7 @@ class VelocityDepthNormalPass extends Pass {
|
|
|
1448
880
|
return this.renderTarget.texture;
|
|
1449
881
|
}
|
|
1450
882
|
setVelocityDepthNormalMaterialInScene() {
|
|
1451
|
-
this.visibleMeshes = getVisibleChildren
|
|
883
|
+
this.visibleMeshes = getVisibleChildren(this._scene);
|
|
1452
884
|
for (const c of this.visibleMeshes){
|
|
1453
885
|
const originalMaterial = c.material;
|
|
1454
886
|
let [cachedOriginalMaterial, velocityDepthNormalMaterial] = this.cachedMaterials.get(c) || [];
|
|
@@ -1530,1457 +962,22 @@ class VelocityDepthNormalPass extends Pass {
|
|
|
1530
962
|
}
|
|
1531
963
|
}
|
|
1532
964
|
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
}
|
|
1539
|
-
dispose() {
|
|
1540
|
-
this.renderTarget.dispose();
|
|
1541
|
-
}
|
|
1542
|
-
setSize(width, height) {
|
|
1543
|
-
this.renderTarget.setSize(width, height);
|
|
1544
|
-
}
|
|
1545
|
-
setSceneTexture(texture) {
|
|
1546
|
-
this.fullscreenMaterial.uniforms.sceneTexture.value = texture;
|
|
1547
|
-
}
|
|
1548
|
-
render(renderer) {
|
|
1549
|
-
this.fullscreenMaterial.uniforms.cameraNear.value = this._camera.near;
|
|
1550
|
-
this.fullscreenMaterial.uniforms.cameraFar.value = this._camera.far;
|
|
1551
|
-
renderer.setRenderTarget(this.renderTarget);
|
|
1552
|
-
renderer.render(this.scene, this.camera);
|
|
1553
|
-
}
|
|
1554
|
-
constructor(camera, textures, gBufferTexture, depthTexture, options = {}){
|
|
1555
|
-
super("DenoiserComposePass");
|
|
1556
|
-
this._camera = camera;
|
|
1557
|
-
this.renderTarget = new WebGLRenderTarget(1, 1, {
|
|
1558
|
-
depthBuffer: false,
|
|
1559
|
-
type: FloatType,
|
|
1560
|
-
minFilter: NearestFilter,
|
|
1561
|
-
magFilter: NearestFilter
|
|
1562
|
-
});
|
|
1563
|
-
this.renderTarget.texture.name = "DenoiserComposePass.Texture";
|
|
1564
|
-
let diffuseGiTexture;
|
|
1565
|
-
let specularGiTexture;
|
|
1566
|
-
if (options.inputType === "diffuseSpecular") {
|
|
1567
|
-
diffuseGiTexture = textures[0];
|
|
1568
|
-
specularGiTexture = textures[1];
|
|
1569
|
-
} else if (options.inputType === "diffuse") {
|
|
1570
|
-
diffuseGiTexture = textures[0];
|
|
1571
|
-
} else if (options.inputType === "specular") {
|
|
1572
|
-
specularGiTexture = textures[0];
|
|
1573
|
-
}
|
|
1574
|
-
var _indexOf;
|
|
1575
|
-
this.fullscreenMaterial = new ShaderMaterial({
|
|
1576
|
-
fragmentShader: /* glsl */ `
|
|
1577
|
-
varying vec2 vUv;
|
|
1578
|
-
uniform sampler2D sceneTexture;
|
|
1579
|
-
uniform highp sampler2D depthTexture;
|
|
1580
|
-
uniform sampler2D diffuseGiTexture;
|
|
1581
|
-
uniform sampler2D specularGiTexture;
|
|
1582
|
-
uniform mat4 cameraMatrixWorld;
|
|
1583
|
-
uniform mat4 projectionMatrix;
|
|
1584
|
-
uniform mat4 projectionMatrixInverse;
|
|
1585
|
-
uniform float cameraNear;
|
|
1586
|
-
uniform float cameraFar;
|
|
1587
|
-
|
|
1588
|
-
#include <common>
|
|
1589
|
-
#include <packing>
|
|
1590
|
-
|
|
1591
|
-
#define TYPE_DIFFUSE_SPECULAR 0
|
|
1592
|
-
#define TYPE_DIFFUSE 1
|
|
1593
|
-
#define TYPE_SPECULAR 2
|
|
1594
|
-
|
|
1595
|
-
${gbuffer_packing}
|
|
1596
|
-
${ssgi_poisson_compose_functions}
|
|
1597
|
-
|
|
1598
|
-
void main() {
|
|
1599
|
-
float depth = textureLod(depthTexture, vUv, 0.).r;
|
|
1600
|
-
|
|
1601
|
-
if(depth == 1. && fwidth(depth) == 0.){
|
|
1602
|
-
discard;
|
|
1603
|
-
return;
|
|
1604
|
-
}
|
|
1605
|
-
|
|
1606
|
-
// on Android there's a bug where using "vec3 normal = unpackNormal(textureLod(velocityTexture, vUv, 0.).b);" instead of
|
|
1607
|
-
// "vec3 normal = unpackNormal(velocity.b);" causes the normal to be distorted (possibly due to packHalf2x16 function)
|
|
1608
|
-
|
|
1609
|
-
Material mat = getMaterial(gBufferTexture, vUv);
|
|
1610
|
-
|
|
1611
|
-
vec3 viewNormal = (vec4(mat.normal, 0.) * cameraMatrixWorld).xyz;
|
|
1612
|
-
|
|
1613
|
-
float viewZ = -getViewZ(depth);
|
|
1614
|
-
|
|
1615
|
-
// view-space position of the current texel
|
|
1616
|
-
vec3 viewPos = getViewPosition(viewZ);
|
|
1617
|
-
vec3 viewDir = normalize(viewPos);
|
|
1618
|
-
|
|
1619
|
-
vec4 diffuseGi = textureLod(diffuseGiTexture, vUv, 0.);
|
|
1620
|
-
vec4 specularGi = textureLod(specularGiTexture, vUv, 0.);
|
|
1621
|
-
|
|
1622
|
-
vec3 gi = constructGlobalIllumination(diffuseGi.rgb, specularGi.rgb, viewDir, viewNormal, mat.diffuse.rgb, mat.emissive, mat.roughness, mat.metalness);
|
|
1623
|
-
|
|
1624
|
-
gl_FragColor = vec4(gi, 1.);
|
|
1625
|
-
}
|
|
1626
|
-
`,
|
|
1627
|
-
vertexShader: vertexShader,
|
|
1628
|
-
uniforms: {
|
|
1629
|
-
sceneTexture: {
|
|
1630
|
-
value: null
|
|
1631
|
-
},
|
|
1632
|
-
viewMatrix: {
|
|
1633
|
-
value: camera.matrixWorldInverse
|
|
1634
|
-
},
|
|
1635
|
-
cameraMatrixWorld: {
|
|
1636
|
-
value: camera.matrixWorld
|
|
1637
|
-
},
|
|
1638
|
-
projectionMatrix: {
|
|
1639
|
-
value: camera.projectionMatrix
|
|
1640
|
-
},
|
|
1641
|
-
projectionMatrixInverse: {
|
|
1642
|
-
value: camera.projectionMatrixInverse
|
|
1643
|
-
},
|
|
1644
|
-
cameraNear: {
|
|
1645
|
-
value: camera.near
|
|
1646
|
-
},
|
|
1647
|
-
cameraFar: {
|
|
1648
|
-
value: camera.far
|
|
1649
|
-
},
|
|
1650
|
-
gBufferTexture: {
|
|
1651
|
-
value: gBufferTexture
|
|
1652
|
-
},
|
|
1653
|
-
depthTexture: {
|
|
1654
|
-
value: depthTexture
|
|
1655
|
-
},
|
|
1656
|
-
diffuseGiTexture: {
|
|
1657
|
-
value: diffuseGiTexture
|
|
1658
|
-
},
|
|
1659
|
-
specularGiTexture: {
|
|
1660
|
-
value: specularGiTexture
|
|
1661
|
-
}
|
|
1662
|
-
},
|
|
1663
|
-
defines: {
|
|
1664
|
-
inputType: (_indexOf = [
|
|
1665
|
-
"diffuseSpecular",
|
|
1666
|
-
"diffuse",
|
|
1667
|
-
"specular"
|
|
1668
|
-
].indexOf(options.inputType)) != null ? _indexOf : 0
|
|
1669
|
-
},
|
|
1670
|
-
blending: NoBlending,
|
|
1671
|
-
depthWrite: false,
|
|
1672
|
-
depthTest: false,
|
|
1673
|
-
toneMapped: false
|
|
1674
|
-
});
|
|
1675
|
-
if (camera.isPerspectiveCamera) this.fullscreenMaterial.defines.PERSPECTIVE_CAMERA = "";
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
|
-
var fragmentShader = "#define GLSLIFY 1\nvarying vec2 vUv;uniform sampler2D inputTexture;uniform highp sampler2D depthTexture;uniform sampler2D normalTexture;uniform mat4 projectionMatrix;uniform mat4 projectionMatrixInverse;uniform mat4 cameraMatrixWorld;uniform float radius;uniform float phi;uniform float lumaPhi;uniform float depthPhi;uniform float normalPhi;uniform float roughnessPhi;uniform float specularPhi;uniform vec2 resolution;layout(location=0)out vec4 gOutput0;\n#if textureCount == 2\nuniform sampler2D inputTexture2;layout(location=1)out vec4 gOutput1;\n#endif\n#include <common>\n#include <gbuffer_packing>\n#define luminance(a) pow(dot(vec3(0.2125, 0.7154, 0.0721), a), 0.125)\n#if textureCount == 1\n#define inputTexture2 inputTexture\n#endif\nMaterial mat;vec3 normal;float depth;float glossiness;float specularFactor;struct InputTexel{vec3 rgb;float a;float luminance;float w;float totalWeight;bool isSpecular;};void toDenoiseSpace(inout vec3 color){color=log(color+1.);}void toLinearSpace(inout vec3 color){color=exp(color)-1.;}float getBasicNeighborWeight(inout vec2 neighborUv){\n#ifdef GBUFFER_TEXTURE\nMaterial neighborMat=getMaterial(gBufferTexture,neighborUv);vec3 neighborNormal=neighborMat.normal;float neighborDepth=textureLod(depthTexture,neighborUv,0.0).r;\n#else\nvec3 neighborDepthVelocityTexel=textureLod(normalTexture,neighborUv,0.).xyz;vec3 neighborNormal=unpackNormal(neighborDepthVelocityTexel.b);float neighborDepth=neighborDepthVelocityTexel.a;\n#endif\nif(neighborDepth==1.0)return 0.;float normalDiff=1.-max(dot(normal,neighborNormal),0.);float depthDiff=10000.*abs(depth-neighborDepth);\n#ifdef GBUFFER_TEXTURE\nfloat roughnessDiff=abs(mat.roughness-neighborMat.roughness);float wBasic=exp(-normalDiff*normalPhi-depthDiff*depthPhi-roughnessDiff*roughnessPhi);\n#else\nfloat wBasic=exp(-normalDiff*normalPhi-depthDiff*depthPhi);\n#endif\nreturn wBasic;}vec3 getNormal(Material mat){\n#ifdef GBUFFER_TEXTURE\nreturn mat.normal;\n#else\nvec3 depthVelocityTexel=textureLod(normalTexture,vUv,0.).xyz;return unpackNormal(depthVelocityTexel.b);\n#endif\n}\n#define SQRT_2 1.41421356237\nvec2 POISSON[8]=vec2[](vec2(-1.0,0.0),vec2(0.0,-1.0),vec2(1.0,0.0),vec2(0.0,1.0),vec2(-0.25*SQRT_2,-0.25*SQRT_2),vec2(0.25*SQRT_2,-0.25*SQRT_2),vec2(0.25*SQRT_2,0.25*SQRT_2),vec2(-0.25*SQRT_2,0.25*SQRT_2));void outputTexel(inout vec4 outputFrag,InputTexel inp){inp.rgb/=inp.totalWeight;outputFrag.rgb=inp.rgb;toLinearSpace(outputFrag.rgb);outputFrag.a=inp.a;}void applyWeight(inout InputTexel inp,vec2 neighborUv,float wBasic){float w=wBasic;vec4 t;if(inp.isSpecular){t=textureLod(inputTexture2,neighborUv,0.);w*=specularFactor;}else{t=textureLod(inputTexture,neighborUv,0.);}toDenoiseSpace(t.rgb);float disocclW=pow(w,0.1);float lumaDiff=abs(inp.luminance-luminance(t.rgb));lumaDiff=min(lumaDiff,0.5);float lumaFactor=exp(-lumaDiff*lumaPhi);w=mix(w*lumaFactor,disocclW,inp.w)*inp.w;w*=step(0.0001,w);inp.rgb+=w*t.rgb;inp.totalWeight+=w;}void main(){depth=textureLod(depthTexture,vUv,0.).r;if(depth==1.0&&fwidth(depth)==0.){discard;return;}InputTexel[textureCount]inputs;float maxAlpha=0.;\n#pragma unroll_loop_start\nfor(int i=0;i<textureCount;i++){{vec4 t;if(isTextureSpecular[i]){t=textureLod(inputTexture2,vUv,0.);}else{t=textureLod(inputTexture,vUv,0.);}float age=1./pow(t.a+1.,1.2*phi);t.rgb*=1.0003;toDenoiseSpace(t.rgb);InputTexel inp=InputTexel(t.rgb,t.a,luminance(t.rgb),age,1.,isTextureSpecular[i]);maxAlpha=max(maxAlpha,inp.a);inputs[i]=inp;}}\n#pragma unroll_loop_end\nmat=getMaterial(gBufferTexture,vUv);normal=getNormal(mat);glossiness=max(0.,4.*(1.-mat.roughness/0.25));specularFactor=exp(-glossiness*specularPhi);float flatness=1.-min(length(fwidth(normal)),1.);flatness=pow(flatness,2.)*0.75+0.25;vec4 random=blueNoise();float r=radius;float angle=random.r*2.*PI;float s=sin(angle),c=cos(angle);mat2 rm=r*flatness*mat2(c,-s,s,c);for(int i=0;i<8;i++){{vec2 offset=POISSON[i];vec2 neighborUv=vUv+rm*(offset/resolution);float wBasic=getBasicNeighborWeight(neighborUv);applyWeight(inputs[0],neighborUv,wBasic);\n#if textureCount == 2\napplyWeight(inputs[1],neighborUv,wBasic);\n#endif\n}}outputTexel(gOutput0,inputs[0]);\n#if textureCount == 2\noutputTexel(gOutput1,inputs[1]);\n#endif\n}"; // eslint-disable-line
|
|
1680
|
-
|
|
1681
|
-
const finalFragmentShader = fragmentShader.replace("#include <gbuffer_packing>", gbuffer_packing);
|
|
1682
|
-
const defaultPoissonBlurOptions = {
|
|
1683
|
-
iterations: 1,
|
|
1684
|
-
radius: 3,
|
|
1685
|
-
phi: 0.5,
|
|
1686
|
-
lumaPhi: 5,
|
|
1687
|
-
depthPhi: 2,
|
|
1688
|
-
normalPhi: 3.25,
|
|
1689
|
-
inputType: "diffuseSpecular" // can be "diffuseSpecular", "diffuse" or "specular"
|
|
1690
|
-
};
|
|
1691
|
-
class PoissonDenoisePass extends Pass {
|
|
1692
|
-
setSize(width, height) {
|
|
1693
|
-
this.renderTargetA.setSize(width, height);
|
|
1694
|
-
this.renderTargetB.setSize(width, height);
|
|
1695
|
-
this.fullscreenMaterial.uniforms.resolution.value.set(width, height);
|
|
1696
|
-
}
|
|
1697
|
-
get texture() {
|
|
1698
|
-
return this.renderTargetB.texture;
|
|
1699
|
-
}
|
|
1700
|
-
// can either be a GBufferPass or a VelocityDepthNormalPass
|
|
1701
|
-
setGBufferPass(gBufferPass) {
|
|
1702
|
-
if (gBufferPass instanceof GBufferPass) {
|
|
1703
|
-
this.fullscreenMaterial.uniforms.gBufferTexture.value = gBufferPass.texture;
|
|
1704
|
-
this.fullscreenMaterial.defines.GBUFFER_TEXTURE = "";
|
|
1705
|
-
} else {
|
|
1706
|
-
this.fullscreenMaterial.uniforms.normalTexture.value = gBufferPass.texture;
|
|
1707
|
-
}
|
|
1708
|
-
this.fullscreenMaterial.uniforms.depthTexture.value = gBufferPass.renderTarget.depthTexture;
|
|
1709
|
-
}
|
|
1710
|
-
setnNormalTexture(texture) {
|
|
1711
|
-
this.fullscreenMaterial.uniforms.normalTexture.value = texture;
|
|
1712
|
-
}
|
|
1713
|
-
setDepthTexture(texture) {
|
|
1714
|
-
this.fullscreenMaterial.uniforms.depthTexture.value = texture;
|
|
1715
|
-
}
|
|
1716
|
-
dispose() {
|
|
1717
|
-
super.dispose();
|
|
1718
|
-
this.renderTargetA.dispose();
|
|
1719
|
-
this.renderTargetB.dispose();
|
|
1720
|
-
this.fullscreenMaterial.dispose();
|
|
1721
|
-
}
|
|
1722
|
-
render(renderer) {
|
|
1723
|
-
for(let i = 0; i < 2 * this.iterations; i++){
|
|
1724
|
-
const horizontal = i % 2 === 0;
|
|
1725
|
-
const inputRenderTarget = horizontal ? this.renderTargetB : this.renderTargetA;
|
|
1726
|
-
this.fullscreenMaterial.uniforms["inputTexture"].value = i === 0 ? this.textures[0] : inputRenderTarget.texture[0];
|
|
1727
|
-
this.fullscreenMaterial.uniforms["inputTexture2"].value = i === 0 ? this.textures[1] : inputRenderTarget.texture[1];
|
|
1728
|
-
const renderTarget = horizontal ? this.renderTargetA : this.renderTargetB;
|
|
1729
|
-
renderer.setRenderTarget(renderTarget);
|
|
1730
|
-
renderer.render(this.scene, this.camera);
|
|
1731
|
-
}
|
|
1732
|
-
}
|
|
1733
|
-
constructor(camera, textures, options = defaultPoissonBlurOptions){
|
|
1734
|
-
super("PoissonBlurPass");
|
|
1735
|
-
this.iterations = defaultPoissonBlurOptions.iterations;
|
|
1736
|
-
this.index = 0;
|
|
1737
|
-
options = _extends({}, defaultPoissonBlurOptions, options);
|
|
1738
|
-
this.textures = textures;
|
|
1739
|
-
let isTextureSpecular = [
|
|
1740
|
-
false,
|
|
1741
|
-
true
|
|
1742
|
-
];
|
|
1743
|
-
if (options.inputType === "diffuse") isTextureSpecular = [
|
|
1744
|
-
false,
|
|
1745
|
-
false
|
|
1746
|
-
];
|
|
1747
|
-
if (options.inputType === "specular") isTextureSpecular = [
|
|
1748
|
-
true,
|
|
1749
|
-
true
|
|
1750
|
-
];
|
|
1751
|
-
const textureCount = options.inputType === "diffuseSpecular" ? 2 : 1;
|
|
1752
|
-
const fragmentShader = unrollLoops(finalFragmentShader.replaceAll("textureCount", textureCount));
|
|
1753
|
-
this.fullscreenMaterial = new ShaderMaterial({
|
|
1754
|
-
fragmentShader,
|
|
1755
|
-
vertexShader,
|
|
1756
|
-
uniforms: {
|
|
1757
|
-
depthTexture: {
|
|
1758
|
-
value: null
|
|
1759
|
-
},
|
|
1760
|
-
inputTexture: {
|
|
1761
|
-
value: textures[0]
|
|
1762
|
-
},
|
|
1763
|
-
inputTexture2: {
|
|
1764
|
-
value: textures[1]
|
|
1765
|
-
},
|
|
1766
|
-
gBufferTexture: {
|
|
1767
|
-
value: null
|
|
1768
|
-
},
|
|
1769
|
-
normalTexture: {
|
|
1770
|
-
value: null
|
|
1771
|
-
},
|
|
1772
|
-
projectionMatrix: {
|
|
1773
|
-
value: camera.projectionMatrix
|
|
1774
|
-
},
|
|
1775
|
-
projectionMatrixInverse: {
|
|
1776
|
-
value: camera.projectionMatrixInverse
|
|
1777
|
-
},
|
|
1778
|
-
cameraMatrixWorld: {
|
|
1779
|
-
value: camera.matrixWorld
|
|
1780
|
-
},
|
|
1781
|
-
viewMatrix: {
|
|
1782
|
-
value: camera.matrixWorldInverse
|
|
1783
|
-
},
|
|
1784
|
-
radius: {
|
|
1785
|
-
value: defaultPoissonBlurOptions.radius
|
|
1786
|
-
},
|
|
1787
|
-
phi: {
|
|
1788
|
-
value: defaultPoissonBlurOptions.phi
|
|
1789
|
-
},
|
|
1790
|
-
lumaPhi: {
|
|
1791
|
-
value: defaultPoissonBlurOptions.lumaPhi
|
|
1792
|
-
},
|
|
1793
|
-
depthPhi: {
|
|
1794
|
-
value: defaultPoissonBlurOptions.depthPhi
|
|
1795
|
-
},
|
|
1796
|
-
normalPhi: {
|
|
1797
|
-
value: defaultPoissonBlurOptions.normalPhi
|
|
1798
|
-
},
|
|
1799
|
-
roughnessPhi: {
|
|
1800
|
-
value: defaultPoissonBlurOptions.roughnessPhi
|
|
1801
|
-
},
|
|
1802
|
-
specularPhi: {
|
|
1803
|
-
value: defaultPoissonBlurOptions.specularPhi
|
|
1804
|
-
},
|
|
1805
|
-
resolution: {
|
|
1806
|
-
value: new Vector2()
|
|
1807
|
-
}
|
|
1808
|
-
},
|
|
1809
|
-
defines: {
|
|
1810
|
-
isTextureSpecular: "bool[2](" + isTextureSpecular.join(",") + ")"
|
|
1811
|
-
},
|
|
1812
|
-
glslVersion: GLSL3
|
|
1813
|
-
});
|
|
1814
|
-
useBlueNoise(this.fullscreenMaterial);
|
|
1815
|
-
const renderTargetOptions = {
|
|
1816
|
-
type: HalfFloatType,
|
|
1817
|
-
depthBuffer: false
|
|
1818
|
-
};
|
|
1819
|
-
this.renderTargetA = new WebGLMultipleRenderTargets(1, 1, textureCount, renderTargetOptions);
|
|
1820
|
-
this.renderTargetB = new WebGLMultipleRenderTargets(1, 1, textureCount, renderTargetOptions);
|
|
1821
|
-
// give the textures of renderTargetA and renderTargetB names
|
|
1822
|
-
this.renderTargetB.texture[0].name = "PoissonDenoisePass." + (isTextureSpecular[0] ? "specular" : "diffuse");
|
|
1823
|
-
if (textureCount > 1) {
|
|
1824
|
-
this.renderTargetB.texture[1].name = "PoissonDenoisePass." + (isTextureSpecular[1] ? "specular" : "diffuse");
|
|
1825
|
-
}
|
|
1826
|
-
const { uniforms } = this.fullscreenMaterial;
|
|
1827
|
-
uniforms["depthPhi"].value = options.depthPhi;
|
|
1828
|
-
uniforms["normalPhi"].value = options.normalPhi;
|
|
1829
|
-
}
|
|
1830
|
-
}
|
|
1831
|
-
PoissonDenoisePass.DefaultOptions = defaultPoissonBlurOptions;
|
|
1832
|
-
|
|
1833
|
-
const defaultDenosierOptions = {
|
|
1834
|
-
denoiseMode: "full",
|
|
1835
|
-
inputType: "diffuseSpecular",
|
|
1836
|
-
gBufferPass: null,
|
|
1837
|
-
velocityDepthNormalPass: null
|
|
1838
|
-
};
|
|
1839
|
-
class Denoiser {
|
|
1840
|
-
get texture() {
|
|
1841
|
-
switch(this.options.denoiseMode){
|
|
1842
|
-
case "full":
|
|
1843
|
-
case "full_temporal":
|
|
1844
|
-
return this.denoiserComposePass.texture;
|
|
1845
|
-
case "denoised":
|
|
1846
|
-
return this.denoisePass.texture;
|
|
1847
|
-
case "temporal":
|
|
1848
|
-
return this.temporalReprojectPass.texture;
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
reset() {
|
|
1852
|
-
this.temporalReprojectPass.reset();
|
|
1853
|
-
}
|
|
1854
|
-
setSize(width, height) {
|
|
1855
|
-
var _this_denoisePass, _this_denoiserComposePass;
|
|
1856
|
-
this.velocityDepthNormalPass.setSize(width, height);
|
|
1857
|
-
this.temporalReprojectPass.setSize(width, height);
|
|
1858
|
-
(_this_denoisePass = this.denoisePass) == null ? void 0 : _this_denoisePass.setSize(width, height);
|
|
1859
|
-
(_this_denoiserComposePass = this.denoiserComposePass) == null ? void 0 : _this_denoiserComposePass.setSize(width, height);
|
|
1860
|
-
}
|
|
1861
|
-
dispose() {
|
|
1862
|
-
var _this_denoisePass, _this_denoiserComposePass;
|
|
1863
|
-
this.velocityDepthNormalPass.dispose();
|
|
1864
|
-
this.temporalReprojectPass.dispose();
|
|
1865
|
-
(_this_denoisePass = this.denoisePass) == null ? void 0 : _this_denoisePass.dispose();
|
|
1866
|
-
(_this_denoiserComposePass = this.denoiserComposePass) == null ? void 0 : _this_denoiserComposePass.dispose();
|
|
1867
|
-
}
|
|
1868
|
-
render(renderer, inputBuffer = null) {
|
|
1869
|
-
var _this_denoisePass, _this_denoiserComposePass;
|
|
1870
|
-
if (this.isOwnVelocityDepthNormalPass) this.velocityDepthNormalPass.render(renderer);
|
|
1871
|
-
this.temporalReprojectPass.render(renderer);
|
|
1872
|
-
if (this.options.inputType !== "diffuseSpecular") {
|
|
1873
|
-
var _this_denoiserComposePass1;
|
|
1874
|
-
(_this_denoiserComposePass1 = this.denoiserComposePass) == null ? void 0 : _this_denoiserComposePass1.setSceneTexture(inputBuffer.texture);
|
|
1875
|
-
}
|
|
1876
|
-
(_this_denoisePass = this.denoisePass) == null ? void 0 : _this_denoisePass.render(renderer);
|
|
1877
|
-
(_this_denoiserComposePass = this.denoiserComposePass) == null ? void 0 : _this_denoiserComposePass.render(renderer);
|
|
1878
|
-
}
|
|
1879
|
-
constructor(scene, camera, texture, options = defaultDenosierOptions){
|
|
1880
|
-
var _this_denoisePass;
|
|
1881
|
-
options = _extends({}, defaultDenosierOptions, options);
|
|
1882
|
-
this.options = options;
|
|
1883
|
-
var _options_velocityDepthNormalPass;
|
|
1884
|
-
this.velocityDepthNormalPass = (_options_velocityDepthNormalPass = options.velocityDepthNormalPass) != null ? _options_velocityDepthNormalPass : new VelocityDepthNormalPass(scene, camera);
|
|
1885
|
-
this.isOwnVelocityDepthNormalPass = !options.velocityDepthNormalPass;
|
|
1886
|
-
const textureCount = options.inputType === "diffuseSpecular" ? 2 : 1;
|
|
1887
|
-
this.temporalReprojectPass = new TemporalReprojectPass(scene, camera, this.velocityDepthNormalPass, texture, textureCount, _extends({
|
|
1888
|
-
fullAccumulate: true,
|
|
1889
|
-
logTransform: true,
|
|
1890
|
-
copyTextures: !options.denoise,
|
|
1891
|
-
reprojectSpecular: [
|
|
1892
|
-
false,
|
|
1893
|
-
true
|
|
1894
|
-
],
|
|
1895
|
-
neighborhoodClamp: [
|
|
1896
|
-
true,
|
|
1897
|
-
true
|
|
1898
|
-
],
|
|
1899
|
-
neighborhoodClampRadius: 2,
|
|
1900
|
-
neighborhoodClampIntensity: 0.5
|
|
1901
|
-
}, options));
|
|
1902
|
-
const textures = this.temporalReprojectPass.renderTarget.texture.slice(0, textureCount);
|
|
1903
|
-
if (this.options.denoiseMode === "full" || this.options.denoiseMode === "denoised") {
|
|
1904
|
-
this.denoisePass = new PoissonDenoisePass(camera, textures, options);
|
|
1905
|
-
var _options_gBufferPass;
|
|
1906
|
-
this.denoisePass.setGBufferPass((_options_gBufferPass = options.gBufferPass) != null ? _options_gBufferPass : this.velocityDepthNormalPass);
|
|
1907
|
-
this.temporalReprojectPass.overrideAccumulatedTextures = this.denoisePass.renderTargetB.texture;
|
|
1908
|
-
}
|
|
1909
|
-
var _this_denoisePass_texture;
|
|
1910
|
-
const composerInputTextures = (_this_denoisePass_texture = (_this_denoisePass = this.denoisePass) == null ? void 0 : _this_denoisePass.texture) != null ? _this_denoisePass_texture : textures;
|
|
1911
|
-
if (options.denoiseMode.startsWith("full")) {
|
|
1912
|
-
this.denoiserComposePass = new DenoiserComposePass(camera, composerInputTextures, options.gBufferPass.texture, options.gBufferPass.renderTarget.depthTexture, options);
|
|
1913
|
-
}
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
|
-
|
|
1917
|
-
class GBufferDebugPass extends Pass {
|
|
1918
|
-
get texture() {
|
|
1919
|
-
return this.renderTarget.texture;
|
|
1920
|
-
}
|
|
1921
|
-
dispose() {
|
|
1922
|
-
this.renderTarget.dispose();
|
|
1923
|
-
}
|
|
1924
|
-
setSize(width, height) {
|
|
1925
|
-
this.renderTarget.setSize(width, height);
|
|
1926
|
-
}
|
|
1927
|
-
render(renderer) {
|
|
1928
|
-
renderer.setRenderTarget(this.renderTarget);
|
|
1929
|
-
renderer.render(this.scene, this.camera);
|
|
1930
|
-
}
|
|
1931
|
-
constructor(gBufferTexture){
|
|
1932
|
-
super("GBufferDebugPass");
|
|
1933
|
-
this.renderTarget = new WebGLRenderTarget(1, 1, {
|
|
1934
|
-
depthBuffer: false,
|
|
1935
|
-
type: FloatType,
|
|
1936
|
-
minFilter: NearestFilter,
|
|
1937
|
-
magFilter: NearestFilter
|
|
1938
|
-
});
|
|
1939
|
-
this.renderTarget.texture.name = "GBufferDebugPass.Texture";
|
|
1940
|
-
this.fullscreenMaterial = new ShaderMaterial({
|
|
1941
|
-
fragmentShader: /* glsl */ `
|
|
1942
|
-
varying vec2 vUv;
|
|
1943
|
-
uniform highp sampler2D depthTexture;
|
|
1944
|
-
uniform int mode;
|
|
1945
|
-
|
|
1946
|
-
#include <common>
|
|
1947
|
-
#include <packing>
|
|
1948
|
-
|
|
1949
|
-
${gbuffer_packing}
|
|
1950
|
-
|
|
1951
|
-
void main() {
|
|
1952
|
-
highp float depth = textureLod(depthTexture, vUv, 0.).r;
|
|
1953
|
-
|
|
1954
|
-
if(depth == 0.){
|
|
1955
|
-
gl_FragColor = vec4(0.);
|
|
1956
|
-
return;
|
|
1957
|
-
}
|
|
1958
|
-
|
|
1959
|
-
Material mat = getMaterial(gBufferTexture, vUv);
|
|
1960
|
-
|
|
1961
|
-
if (mode == 0) {
|
|
1962
|
-
gl_FragColor = vec4(mat.diffuse.rgb, 1.);
|
|
1963
|
-
} else if (mode == 1) {
|
|
1964
|
-
gl_FragColor = vec4(mat.diffuse.aaa, 1.);
|
|
1965
|
-
} else if (mode == 2) {
|
|
1966
|
-
gl_FragColor = vec4(mat.normal, 1.);
|
|
1967
|
-
} else if (mode == 3) {
|
|
1968
|
-
gl_FragColor = vec4(vec3(mat.roughness), 1.);
|
|
1969
|
-
} else if (mode == 4) {
|
|
1970
|
-
gl_FragColor = vec4(vec3(mat.metalness), 1.);
|
|
1971
|
-
} else {
|
|
1972
|
-
gl_FragColor = vec4(mat.emissive, 1.);
|
|
1973
|
-
}
|
|
1974
|
-
}
|
|
1975
|
-
`,
|
|
1976
|
-
vertexShader: vertexShader,
|
|
1977
|
-
uniforms: {
|
|
1978
|
-
gBufferTexture: {
|
|
1979
|
-
value: gBufferTexture
|
|
1980
|
-
},
|
|
1981
|
-
mode: {
|
|
1982
|
-
value: 0
|
|
1983
|
-
}
|
|
1984
|
-
},
|
|
1985
|
-
blending: NoBlending,
|
|
1986
|
-
depthWrite: false,
|
|
1987
|
-
depthTest: false,
|
|
1988
|
-
toneMapped: false
|
|
1989
|
-
});
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
|
-
/* eslint-disable max-len */ /**
|
|
1994
|
-
* Options of the SSGI effect
|
|
1995
|
-
* @typedef {Object} SSGIOptions
|
|
1996
|
-
* @property {Number} [distance] maximum distance a SSGI ray can travel to find what it reflects
|
|
1997
|
-
* @property {Number} [thickness] maximum depth difference between a ray and the particular depth at its screen position before refining with binary search; higher values will result in better performance
|
|
1998
|
-
* @property {Number} [envBlur] higher values will result in lower mipmaps being sampled which will cause less noise but also less detail regarding environment lighting
|
|
1999
|
-
* @property {Number} [importanceSampling] whether to use importance sampling for the environment map
|
|
2000
|
-
* @property {Number} [denoiseIterations] how many times the denoise filter runs, more iterations will denoise the frame better but need more performance
|
|
2001
|
-
* @property {Number} [radius] the radius of the denoiser, higher values will result in less noise on less detailled surfaces but more noise on detailled surfaces
|
|
2002
|
-
* @property {Number} [depthPhi] depth factor of the denoiser, higher values will use neighboring areas with different depth values more resulting in less noise but loss of details
|
|
2003
|
-
* @property {Number} [normalPhi] normals factor of the denoiser, higher values will use neighboring areas with different normals more resulting in less noise but loss of details and sharpness
|
|
2004
|
-
* @property {Number} [roughnessPhi] roughness factor of the denoiser setting how much the denoiser should only apply the blur to rougher surfaces, a value of 0 means the denoiser will blur mirror-like surfaces the same as rough surfaces
|
|
2005
|
-
* @property {Number} [specularPhi] specular factor of the denoiser setting how much the denoiser will blur specular reflections
|
|
2006
|
-
* @property {Number} [lumaPhi] luminance factor of the denoiser setting how aggressive the denoiser is on areas with different luminance
|
|
2007
|
-
* @property {Number} [steps] number of steps a SSGI ray can maximally do to find an object it intersected (and thus reflects)
|
|
2008
|
-
* @property {Number} [refineSteps] once we had our ray intersect something, we need to find the exact point in space it intersected and thus it reflects; this can be done through binary search with the given number of maximum steps
|
|
2009
|
-
* @property {boolean} [missedRays] if there should still be SSGI for rays for which a reflecting point couldn't be found; enabling this will result in stretched looking SSGI which can look good or bad depending on the angle
|
|
2010
|
-
* @property {Number} [resolutionScale] resolution of the SSGI effect, a resolution of 0.5 means the effect will be rendered at half resolution
|
|
2011
|
-
*/ /**
|
|
2012
|
-
* The options of the SSGI effect
|
|
2013
|
-
* @type {SSGIOptions}
|
|
2014
|
-
*/ const defaultSSGIOptions = {
|
|
2015
|
-
mode: "ssgi",
|
|
2016
|
-
distance: 10,
|
|
2017
|
-
thickness: 10,
|
|
2018
|
-
denoiseIterations: 1,
|
|
2019
|
-
denoiseKernel: 2,
|
|
2020
|
-
denoiseDiffuse: 10,
|
|
2021
|
-
denoiseSpecular: 10,
|
|
2022
|
-
radius: 3,
|
|
2023
|
-
phi: 0.5,
|
|
2024
|
-
lumaPhi: 5,
|
|
2025
|
-
depthPhi: 2,
|
|
2026
|
-
normalPhi: 50,
|
|
2027
|
-
roughnessPhi: 50,
|
|
2028
|
-
specularPhi: 50,
|
|
2029
|
-
envBlur: 0.5,
|
|
2030
|
-
importanceSampling: true,
|
|
2031
|
-
steps: 20,
|
|
2032
|
-
refineSteps: 5,
|
|
2033
|
-
resolutionScale: 1,
|
|
2034
|
-
missedRays: false,
|
|
2035
|
-
outputTexture: null
|
|
2036
|
-
};
|
|
2037
|
-
|
|
2038
|
-
var ssgi_compose = "#define GLSLIFY 1\nuniform sampler2D inputTexture;uniform sampler2D sceneTexture;uniform highp sampler2D depthTexture;uniform bool isDebug;uniform float cameraNear;uniform float cameraFar;\n#include <fog_pars_fragment>\nfloat getViewZ(const in float depth){\n#if PERSPECTIVE_CAMERA == 1\nreturn perspectiveDepthToViewZ(depth,cameraNear,cameraFar);\n#else\nreturn orthographicDepthToViewZ(depth,cameraNear,cameraFar);\n#endif\n}void mainImage(const in vec4 inputColor,const in vec2 uv,out vec4 outputColor){if(isDebug){outputColor=textureLod(inputTexture,uv,0.);return;}float depth=textureLod(depthTexture,uv,0.).r;vec3 ssgiClr;if(depth==1.0){ssgiClr=textureLod(sceneTexture,uv,0.).rgb;}else{ssgiClr=textureLod(inputTexture,uv,0.).rgb;\n#ifdef USE_FOG\nfloat viewZ=getViewZ(depth)*0.4;vFogDepth=-viewZ;\n#include <fog_fragment>\nssgiClr=mix(ssgiClr,fogColor,fogFactor);\n#endif\n}outputColor=vec4(ssgiClr,1.0);}"; // eslint-disable-line
|
|
2039
|
-
|
|
2040
|
-
const { render } = RenderPass.prototype;
|
|
2041
|
-
const globalIblRadianceDisabledUniform = createGlobalDisableIblRadianceUniform();
|
|
2042
|
-
class SSGIEffect extends Effect {
|
|
2043
|
-
updateUsingRenderPass() {
|
|
2044
|
-
if (this.isUsingRenderPass) {
|
|
2045
|
-
this.ssgiPass.fullscreenMaterial.defines.useDirectLight = "";
|
|
2046
|
-
} else {
|
|
2047
|
-
delete this.ssgiPass.fullscreenMaterial.defines.useDirectLight;
|
|
2048
|
-
}
|
|
2049
|
-
this.ssgiPass.fullscreenMaterial.needsUpdate = true;
|
|
2050
|
-
}
|
|
2051
|
-
reset() {
|
|
2052
|
-
this.denoiser.reset();
|
|
2053
|
-
}
|
|
2054
|
-
makeOptionsReactive(options) {
|
|
2055
|
-
let needsUpdate = false;
|
|
2056
|
-
const ssgiPassFullscreenMaterialUniforms = this.ssgiPass.fullscreenMaterial.uniforms;
|
|
2057
|
-
const ssgiPassFullscreenMaterialUniformsKeys = Object.keys(ssgiPassFullscreenMaterialUniforms);
|
|
2058
|
-
for (const key of Object.keys(options)){
|
|
2059
|
-
Object.defineProperty(this, key, {
|
|
2060
|
-
get () {
|
|
2061
|
-
return options[key];
|
|
2062
|
-
},
|
|
2063
|
-
set (value) {
|
|
2064
|
-
if (options[key] === value && needsUpdate) return;
|
|
2065
|
-
options[key] = value;
|
|
2066
|
-
switch(key){
|
|
2067
|
-
// denoiser
|
|
2068
|
-
case "denoiseIterations":
|
|
2069
|
-
if (this.denoiser.denoisePass) this.denoiser.denoisePass.iterations = value;
|
|
2070
|
-
break;
|
|
2071
|
-
case "radius":
|
|
2072
|
-
case "phi":
|
|
2073
|
-
case "lumaPhi":
|
|
2074
|
-
case "depthPhi":
|
|
2075
|
-
case "normalPhi":
|
|
2076
|
-
case "roughnessPhi":
|
|
2077
|
-
case "specularPhi":
|
|
2078
|
-
var _this_denoiser_denoisePass;
|
|
2079
|
-
if ((_this_denoiser_denoisePass = this.denoiser.denoisePass) == null ? void 0 : _this_denoiser_denoisePass.fullscreenMaterial.uniforms[key]) {
|
|
2080
|
-
this.denoiser.denoisePass.fullscreenMaterial.uniforms[key].value = value;
|
|
2081
|
-
this.reset();
|
|
2082
|
-
}
|
|
2083
|
-
break;
|
|
2084
|
-
case "denoiseIterations":
|
|
2085
|
-
case "radius":
|
|
2086
|
-
if (this.denoiser.denoisePass) this.denoiser.denoisePass[key] = value;
|
|
2087
|
-
break;
|
|
2088
|
-
// SSGI
|
|
2089
|
-
case "resolutionScale":
|
|
2090
|
-
this.setSize(this.lastSize.width, this.lastSize.height);
|
|
2091
|
-
this.reset();
|
|
2092
|
-
break;
|
|
2093
|
-
case "steps":
|
|
2094
|
-
case "refineSteps":
|
|
2095
|
-
this.ssgiPass.fullscreenMaterial.defines[key] = parseInt(value);
|
|
2096
|
-
this.ssgiPass.fullscreenMaterial.needsUpdate = needsUpdate;
|
|
2097
|
-
this.reset();
|
|
2098
|
-
break;
|
|
2099
|
-
case "importanceSampling":
|
|
2100
|
-
case "missedRays":
|
|
2101
|
-
if (value) {
|
|
2102
|
-
this.ssgiPass.fullscreenMaterial.defines[key] = "";
|
|
2103
|
-
} else {
|
|
2104
|
-
delete this.ssgiPass.fullscreenMaterial.defines[key];
|
|
2105
|
-
}
|
|
2106
|
-
this.ssgiPass.fullscreenMaterial.needsUpdate = needsUpdate;
|
|
2107
|
-
this.reset();
|
|
2108
|
-
break;
|
|
2109
|
-
case "distance":
|
|
2110
|
-
ssgiPassFullscreenMaterialUniforms.rayDistance.value = value;
|
|
2111
|
-
this.reset();
|
|
2112
|
-
break;
|
|
2113
|
-
case "outputTexture":
|
|
2114
|
-
if (!this.outputTexture) {
|
|
2115
|
-
return;
|
|
2116
|
-
}
|
|
2117
|
-
if (typeof value === "string") {
|
|
2118
|
-
if (this.gBufferDebugPass === undefined) {
|
|
2119
|
-
this.gBufferDebugPass = new GBufferDebugPass(this.ssgiPass.gBufferPass.texture);
|
|
2120
|
-
this.gBufferDebugPass.setSize(this.lastSize.width, this.lastSize.height);
|
|
2121
|
-
}
|
|
2122
|
-
const modes = [
|
|
2123
|
-
"diffuse",
|
|
2124
|
-
"alpha",
|
|
2125
|
-
"normal",
|
|
2126
|
-
"roughness",
|
|
2127
|
-
"metalness",
|
|
2128
|
-
"emissive"
|
|
2129
|
-
];
|
|
2130
|
-
const mode = modes.indexOf(value);
|
|
2131
|
-
this.gBufferDebugPass.fullscreenMaterial.uniforms.mode.value = mode;
|
|
2132
|
-
this.outputTexture = this.gBufferDebugPass.texture;
|
|
2133
|
-
} else if (this.gBufferDebugPass !== undefined && this.outputTexture !== this.gBufferDebugPass.texture) {
|
|
2134
|
-
this.gBufferDebugPass.dispose();
|
|
2135
|
-
delete this.gBufferDebugPass;
|
|
2136
|
-
}
|
|
2137
|
-
this.uniforms.get("isDebug").value = this.outputTexture !== this.denoiser.texture;
|
|
2138
|
-
break;
|
|
2139
|
-
// must be a uniform
|
|
2140
|
-
default:
|
|
2141
|
-
if (ssgiPassFullscreenMaterialUniformsKeys.includes(key)) {
|
|
2142
|
-
ssgiPassFullscreenMaterialUniforms[key].value = value;
|
|
2143
|
-
this.reset();
|
|
2144
|
-
}
|
|
2145
|
-
}
|
|
2146
|
-
}
|
|
2147
|
-
});
|
|
2148
|
-
// apply all uniforms and defines
|
|
2149
|
-
this[key] = options[key];
|
|
2150
|
-
}
|
|
2151
|
-
needsUpdate = true;
|
|
2152
|
-
}
|
|
2153
|
-
initialize(renderer, ...args) {
|
|
2154
|
-
super.initialize(renderer, ...args);
|
|
2155
|
-
this.ssgiPass.initialize(renderer, ...args);
|
|
2156
|
-
}
|
|
2157
|
-
setSize(width, height, force = false) {
|
|
2158
|
-
var _this_gBufferDebugPass, _this_cubeToEquirectEnvPass;
|
|
2159
|
-
if (width === undefined && height === undefined) return;
|
|
2160
|
-
if (!force && width === this.lastSize.width && height === this.lastSize.height && this.resolutionScale === this.lastSize.resolutionScale) {
|
|
2161
|
-
return;
|
|
2162
|
-
}
|
|
2163
|
-
this.ssgiPass.setSize(width, height);
|
|
2164
|
-
this.denoiser.setSize(width, height);
|
|
2165
|
-
(_this_gBufferDebugPass = this.gBufferDebugPass) == null ? void 0 : _this_gBufferDebugPass.setSize(width, height);
|
|
2166
|
-
this.sceneRenderTarget.setSize(width, height);
|
|
2167
|
-
(_this_cubeToEquirectEnvPass = this.cubeToEquirectEnvPass) == null ? void 0 : _this_cubeToEquirectEnvPass.setSize(width, height);
|
|
2168
|
-
this.lastSize = {
|
|
2169
|
-
width,
|
|
2170
|
-
height,
|
|
2171
|
-
resolutionScale: this.resolutionScale
|
|
2172
|
-
};
|
|
2173
|
-
}
|
|
2174
|
-
dispose() {
|
|
2175
|
-
var _this_cubeToEquirectEnvPass;
|
|
2176
|
-
super.dispose();
|
|
2177
|
-
this.ssgiPass.dispose();
|
|
2178
|
-
this.denoiser.dispose();
|
|
2179
|
-
(_this_cubeToEquirectEnvPass = this.cubeToEquirectEnvPass) == null ? void 0 : _this_cubeToEquirectEnvPass.dispose();
|
|
2180
|
-
RenderPass.prototype.render = render;
|
|
2181
|
-
}
|
|
2182
|
-
keepEnvMapUpdated(renderer) {
|
|
2183
|
-
const ssgiMaterial = this.ssgiPass.fullscreenMaterial;
|
|
2184
|
-
let environment = this._scene.environment;
|
|
2185
|
-
if (environment) {
|
|
2186
|
-
if (ssgiMaterial.uniforms.envMapInfo.value.mapUuid !== environment.uuid) {
|
|
2187
|
-
// if the environment is a cube texture, convert it to an equirectangular texture so we can sample it in the SSGI pass and use MIS
|
|
2188
|
-
if (environment.isCubeTexture) {
|
|
2189
|
-
if (!this.cubeToEquirectEnvPass) this.cubeToEquirectEnvPass = new CubeToEquirectEnvPass();
|
|
2190
|
-
environment = this.cubeToEquirectEnvPass.generateEquirectEnvMap(renderer, environment);
|
|
2191
|
-
environment.uuid = this._scene.environment.uuid;
|
|
2192
|
-
}
|
|
2193
|
-
if (!environment.generateMipmaps) {
|
|
2194
|
-
environment.generateMipmaps = true;
|
|
2195
|
-
environment.minFilter = LinearMipMapLinearFilter;
|
|
2196
|
-
environment.magFilter = LinearFilter;
|
|
2197
|
-
environment.needsUpdate = true;
|
|
2198
|
-
}
|
|
2199
|
-
if (environment.type === FloatType) {
|
|
2200
|
-
console.warn("SSGI: Environment map is FloatType, this causes the environment map to be black in the SSGI pass for many modern Apple devices. Please use HalfFloatType instead.");
|
|
2201
|
-
}
|
|
2202
|
-
ssgiMaterial.uniforms.envMapInfo.value.mapUuid = environment.uuid;
|
|
2203
|
-
const maxEnvMapMipLevel = getMaxMipLevel(environment);
|
|
2204
|
-
ssgiMaterial.uniforms.maxEnvMapMipLevel.value = maxEnvMapMipLevel;
|
|
2205
|
-
ssgiMaterial.uniforms.envMapInfo.value.map = environment;
|
|
2206
|
-
ssgiMaterial.defines.USE_ENVMAP = "";
|
|
2207
|
-
delete ssgiMaterial.defines.importanceSampling;
|
|
2208
|
-
if (this.importanceSampling) {
|
|
2209
|
-
ssgiMaterial.uniforms.envMapInfo.value.updateFrom(environment, renderer).then(()=>{
|
|
2210
|
-
ssgiMaterial.defines.importanceSampling = "";
|
|
2211
|
-
ssgiMaterial.needsUpdate = true;
|
|
2212
|
-
});
|
|
2213
|
-
} else {
|
|
2214
|
-
ssgiMaterial.uniforms.envMapInfo.value.map = environment;
|
|
2215
|
-
}
|
|
2216
|
-
this.reset();
|
|
2217
|
-
ssgiMaterial.needsUpdate = true;
|
|
2218
|
-
}
|
|
2219
|
-
} else if ("USE_ENVMAP" in ssgiMaterial.defines) {
|
|
2220
|
-
delete ssgiMaterial.defines.USE_ENVMAP;
|
|
2221
|
-
delete ssgiMaterial.defines.importanceSampling;
|
|
2222
|
-
ssgiMaterial.needsUpdate = true;
|
|
2223
|
-
}
|
|
2224
|
-
}
|
|
2225
|
-
get depthTexture() {
|
|
2226
|
-
return this.ssgiPass.gBufferPass.depthTexture;
|
|
2227
|
-
}
|
|
2228
|
-
update(renderer, inputBuffer) {
|
|
2229
|
-
var _this_gBufferDebugPass;
|
|
2230
|
-
this.keepEnvMapUpdated(renderer);
|
|
2231
|
-
const sceneBuffer = this.isUsingRenderPass ? inputBuffer : this.sceneRenderTarget;
|
|
2232
|
-
const hideMeshes = [];
|
|
2233
|
-
if (!this.isUsingRenderPass) {
|
|
2234
|
-
const children = [];
|
|
2235
|
-
for (const c of getVisibleChildren(this._scene)){
|
|
2236
|
-
if (c.isScene) return;
|
|
2237
|
-
c.visible = !isChildMaterialRenderable(c);
|
|
2238
|
-
c.visible ? hideMeshes.push(c) : children.push(c);
|
|
2239
|
-
}
|
|
2240
|
-
this.renderPass.render(renderer, this.sceneRenderTarget);
|
|
2241
|
-
for (const c of children)c.visible = true;
|
|
2242
|
-
for (const c of hideMeshes)c.visible = false;
|
|
2243
|
-
}
|
|
2244
|
-
this.ssgiPass.fullscreenMaterial.uniforms.directLightTexture.value = sceneBuffer.texture;
|
|
2245
|
-
this.ssgiPass.render(renderer);
|
|
2246
|
-
(_this_gBufferDebugPass = this.gBufferDebugPass) == null ? void 0 : _this_gBufferDebugPass.render(renderer);
|
|
2247
|
-
this.denoiser.render(renderer, inputBuffer);
|
|
2248
|
-
var _this_outputTexture_;
|
|
2249
|
-
this.uniforms.get("inputTexture").value = (_this_outputTexture_ = this.outputTexture[0]) != null ? _this_outputTexture_ : this.outputTexture;
|
|
2250
|
-
this.uniforms.get("sceneTexture").value = sceneBuffer.texture;
|
|
2251
|
-
this.uniforms.get("depthTexture").value = this.ssgiPass.gBufferPass.depthTexture;
|
|
2252
|
-
// update the fog uniforms
|
|
2253
|
-
if (this._scene.fog) {
|
|
2254
|
-
this.uniforms.get("fogColor").value = this._scene.fog.color;
|
|
2255
|
-
this.uniforms.get("fogNear").value = this._scene.fog.near;
|
|
2256
|
-
this.uniforms.get("fogFar").value = this._scene.fog.far;
|
|
2257
|
-
this.uniforms.get("fogDensity").value = this._scene.fog.density;
|
|
2258
|
-
this.uniforms.get("cameraNear").value = this._camera.near;
|
|
2259
|
-
this.uniforms.get("cameraFar").value = this._camera.far;
|
|
2260
|
-
}
|
|
2261
|
-
for (const c of hideMeshes)c.visible = true;
|
|
2262
|
-
globalIblRadianceDisabledUniform.value = true;
|
|
2263
|
-
cancelAnimationFrame(this.rAF2);
|
|
2264
|
-
cancelAnimationFrame(this.rAF);
|
|
2265
|
-
cancelAnimationFrame(this.usingRenderPassRAF);
|
|
2266
|
-
this.rAF = requestAnimationFrame(()=>{
|
|
2267
|
-
this.rAF2 = requestAnimationFrame(()=>{
|
|
2268
|
-
globalIblRadianceDisabledUniform.value = false;
|
|
2269
|
-
});
|
|
2270
|
-
});
|
|
2271
|
-
this.usingRenderPassRAF = requestAnimationFrame(()=>{
|
|
2272
|
-
const wasUsingRenderPass = this.isUsingRenderPass;
|
|
2273
|
-
this.isUsingRenderPass = false;
|
|
2274
|
-
if (wasUsingRenderPass != this.isUsingRenderPass) this.updateUsingRenderPass();
|
|
2275
|
-
});
|
|
2276
|
-
}
|
|
2277
|
-
constructor(composer, scene, camera, options){
|
|
2278
|
-
var _scene_fog;
|
|
2279
|
-
options = _extends({}, defaultSSGIOptions, options);
|
|
2280
|
-
let fragmentShader = ssgi_compose.replace("#include <fog_pars_fragment>", ShaderChunk.fog_pars_fragment.replace("varying", ""));
|
|
2281
|
-
// delete the line starting with gl_FragColor using a regex
|
|
2282
|
-
fragmentShader = fragmentShader.replace("#include <fog_fragment>", ShaderChunk.fog_fragment.replace(/.*gl_FragColor.*/g, ""));
|
|
2283
|
-
const defines = new Map();
|
|
2284
|
-
if (scene.fog) defines.set("USE_FOG", "");
|
|
2285
|
-
if ((_scene_fog = scene.fog) == null ? void 0 : _scene_fog.isFogExp2) defines.set("FOG_EXP2", "");
|
|
2286
|
-
super("SSGIEffect", fragmentShader, {
|
|
2287
|
-
type: "FinalSSGIMaterial",
|
|
2288
|
-
uniforms: new Map([
|
|
2289
|
-
[
|
|
2290
|
-
"inputTexture",
|
|
2291
|
-
new Uniform(null)
|
|
2292
|
-
],
|
|
2293
|
-
[
|
|
2294
|
-
"sceneTexture",
|
|
2295
|
-
new Uniform(null)
|
|
2296
|
-
],
|
|
2297
|
-
[
|
|
2298
|
-
"depthTexture",
|
|
2299
|
-
new Uniform(null)
|
|
2300
|
-
],
|
|
2301
|
-
[
|
|
2302
|
-
"isDebug",
|
|
2303
|
-
new Uniform(false)
|
|
2304
|
-
],
|
|
2305
|
-
[
|
|
2306
|
-
"fogColor",
|
|
2307
|
-
new Uniform(new Color())
|
|
2308
|
-
],
|
|
2309
|
-
[
|
|
2310
|
-
"fogNear",
|
|
2311
|
-
new Uniform(0)
|
|
2312
|
-
],
|
|
2313
|
-
[
|
|
2314
|
-
"fogFar",
|
|
2315
|
-
new Uniform(0)
|
|
2316
|
-
],
|
|
2317
|
-
[
|
|
2318
|
-
"fogDensity",
|
|
2319
|
-
new Uniform(0)
|
|
2320
|
-
],
|
|
2321
|
-
[
|
|
2322
|
-
"cameraNear",
|
|
2323
|
-
new Uniform(0)
|
|
2324
|
-
],
|
|
2325
|
-
[
|
|
2326
|
-
"cameraFar",
|
|
2327
|
-
new Uniform(0)
|
|
2328
|
-
]
|
|
2329
|
-
]),
|
|
2330
|
-
defines: new Map([
|
|
2331
|
-
[
|
|
2332
|
-
"PERSPECTIVE_CAMERA",
|
|
2333
|
-
camera.isPerspectiveCamera ? "1" : "0"
|
|
2334
|
-
],
|
|
2335
|
-
...defines
|
|
2336
|
-
])
|
|
2337
|
-
});
|
|
2338
|
-
this.selection = new Selection();
|
|
2339
|
-
this.isUsingRenderPass = true;
|
|
2340
|
-
this._scene = scene;
|
|
2341
|
-
this._camera = camera;
|
|
2342
|
-
this.composer = composer;
|
|
2343
|
-
if (options.mode === "ssr") {
|
|
2344
|
-
options.reprojectSpecular = true;
|
|
2345
|
-
options.neighborhoodClamp = true;
|
|
2346
|
-
options.inputType = "specular";
|
|
2347
|
-
} else if (options.mode === "ssgi") {
|
|
2348
|
-
options.reprojectSpecular = [
|
|
2349
|
-
false,
|
|
2350
|
-
true
|
|
2351
|
-
];
|
|
2352
|
-
options.neighborhoodClamp = [
|
|
2353
|
-
false,
|
|
2354
|
-
true
|
|
2355
|
-
];
|
|
2356
|
-
}
|
|
2357
|
-
if (typeof options.preset === "string") {
|
|
2358
|
-
switch(options.preset){
|
|
2359
|
-
case "low":
|
|
2360
|
-
options.steps = 10;
|
|
2361
|
-
options.refineSteps = 2;
|
|
2362
|
-
options.denoiseMode = "full_temporal";
|
|
2363
|
-
break;
|
|
2364
|
-
case "medium":
|
|
2365
|
-
options.steps = 20;
|
|
2366
|
-
options.refineSteps = 4;
|
|
2367
|
-
options.denoiseMode = "full";
|
|
2368
|
-
break;
|
|
2369
|
-
case "medium":
|
|
2370
|
-
options.steps = 40;
|
|
2371
|
-
options.refineSteps = 4;
|
|
2372
|
-
options.denoiseMode = "full";
|
|
2373
|
-
break;
|
|
2374
|
-
}
|
|
2375
|
-
}
|
|
2376
|
-
this.ssgiPass = new SSGIPass(this, options);
|
|
2377
|
-
this.denoiser = new Denoiser(scene, camera, this.ssgiPass.texture, _extends({
|
|
2378
|
-
gBufferPass: this.ssgiPass.gBufferPass,
|
|
2379
|
-
velocityDepthNormalPass: options.velocityDepthNormalPass
|
|
2380
|
-
}, options));
|
|
2381
|
-
this.lastSize = {
|
|
2382
|
-
width: options.width,
|
|
2383
|
-
height: options.height,
|
|
2384
|
-
resolutionScale: options.resolutionScale
|
|
2385
|
-
};
|
|
2386
|
-
this.sceneRenderTarget = new WebGLRenderTarget(1, 1, {
|
|
2387
|
-
colorSpace: SRGBColorSpace
|
|
2388
|
-
});
|
|
2389
|
-
this.renderPass = new RenderPass(this._scene, this._camera);
|
|
2390
|
-
this.renderPass.renderToScreen = false;
|
|
2391
|
-
this.setSize(options.width, options.height);
|
|
2392
|
-
const th = this;
|
|
2393
|
-
const ssgiRenderPass = this.renderPass;
|
|
2394
|
-
// eslint-disable-next-line space-before-function-paren
|
|
2395
|
-
RenderPass.prototype.render = function(...args) {
|
|
2396
|
-
if (this !== ssgiRenderPass) {
|
|
2397
|
-
const wasUsingRenderPass = th.isUsingRenderPass;
|
|
2398
|
-
th.isUsingRenderPass = true;
|
|
2399
|
-
if (wasUsingRenderPass != th.isUsingRenderPass) th.updateUsingRenderPass();
|
|
2400
|
-
}
|
|
2401
|
-
render.call(this, ...args);
|
|
2402
|
-
};
|
|
2403
|
-
this.makeOptionsReactive(options);
|
|
2404
|
-
this.outputTexture = this.denoiser.texture;
|
|
2405
|
-
// this.outputTexture = this.denoiser.denoisePass.textures[1]
|
|
2406
|
-
}
|
|
2407
|
-
}
|
|
2408
|
-
SSGIEffect.DefaultOptions = defaultSSGIOptions;
|
|
2409
|
-
|
|
2410
|
-
var motion_blur = "#define GLSLIFY 1\nuniform sampler2D inputTexture;uniform highp sampler2D velocityTexture;uniform vec2 resolution;uniform float intensity;uniform float jitter;uniform float deltaTime;uniform int frame;uniform vec2 texSize;void mainImage(const in vec4 inputColor,const in vec2 uv,out vec4 outputColor){vec2 velocity=textureLod(velocityTexture,vUv,0.0).xy;bool didMove=dot(velocity,velocity)>0.000000001;if(!didMove){outputColor=inputColor;return;}velocity*=intensity;vec4 blueNoise=blueNoise(vUv,frame);vec2 jitterOffset=jitter*velocity*blueNoise.xy;float frameSpeed=(1./100.)/deltaTime;vec2 startUv=vUv+(jitterOffset-velocity*0.5)*frameSpeed;vec2 endUv=vUv+(jitterOffset+velocity*0.5)*frameSpeed;startUv=max(vec2(0.),startUv);endUv=min(vec2(1.),endUv);vec3 motionBlurredColor=inputColor.rgb;for(float i=0.0;i<=samplesFloat;i++){vec2 reprojectedUv=mix(startUv,endUv,i/samplesFloat);vec3 neighborColor=textureLod(inputTexture,reprojectedUv,0.0).rgb;motionBlurredColor+=neighborColor;}motionBlurredColor/=samplesFloat+2.;outputColor=vec4(motionBlurredColor,inputColor.a);}"; // eslint-disable-line
|
|
2411
|
-
|
|
2412
|
-
// https://www.nvidia.com/docs/io/8230/gdc2003_openglshadertricks.pdf
|
|
2413
|
-
// http://john-chapman-graphics.blogspot.com/2013/01/per-object-motion-blur.html
|
|
2414
|
-
// reference code: https://github.com/gkjohnson/threejs-sandbox/blob/master/motionBlurPass/src/CompositeShader.js
|
|
2415
|
-
const defaultOptions = {
|
|
2416
|
-
intensity: 1,
|
|
2417
|
-
jitter: 1,
|
|
2418
|
-
samples: 16
|
|
2419
|
-
};
|
|
2420
|
-
class MotionBlurEffect extends Effect {
|
|
2421
|
-
makeOptionsReactive(options) {
|
|
2422
|
-
for (const key of Object.keys(options)){
|
|
2423
|
-
Object.defineProperty(this, key, {
|
|
2424
|
-
get () {
|
|
2425
|
-
return options[key];
|
|
2426
|
-
},
|
|
2427
|
-
set (value) {
|
|
2428
|
-
options[key] = value;
|
|
2429
|
-
switch(key){
|
|
2430
|
-
case "intensity":
|
|
2431
|
-
case "jitter":
|
|
2432
|
-
this.uniforms.get(key).value = value;
|
|
2433
|
-
break;
|
|
2434
|
-
}
|
|
2435
|
-
}
|
|
2436
|
-
});
|
|
2437
|
-
this[key] = options[key];
|
|
2438
|
-
}
|
|
2439
|
-
}
|
|
2440
|
-
initialize(renderer, ...args) {
|
|
2441
|
-
super.initialize(renderer, ...args);
|
|
2442
|
-
new TextureLoader().load(img, (blueNoiseTexture)=>{
|
|
2443
|
-
blueNoiseTexture.minFilter = NearestFilter;
|
|
2444
|
-
blueNoiseTexture.magFilter = NearestFilter;
|
|
2445
|
-
blueNoiseTexture.wrapS = RepeatWrapping;
|
|
2446
|
-
blueNoiseTexture.wrapT = RepeatWrapping;
|
|
2447
|
-
blueNoiseTexture.colorSpace = NoColorSpace;
|
|
2448
|
-
this.uniforms.get("blueNoiseTexture").value = blueNoiseTexture;
|
|
2449
|
-
});
|
|
2450
|
-
}
|
|
2451
|
-
update(renderer, inputBuffer, deltaTime) {
|
|
2452
|
-
this.uniforms.get("inputTexture").value = inputBuffer.texture;
|
|
2453
|
-
this.uniforms.get("deltaTime").value = Math.max(1 / 1000, deltaTime);
|
|
2454
|
-
const frame = renderer.info.render.frame % 4096;
|
|
2455
|
-
this.uniforms.get("frame").value = frame;
|
|
2456
|
-
this.uniforms.get("resolution").value.set(window.innerWidth, window.innerHeight);
|
|
2457
|
-
const noiseTexture = this.uniforms.get("blueNoiseTexture").value;
|
|
2458
|
-
if (noiseTexture && noiseTexture.source.data) {
|
|
2459
|
-
const { width, height } = noiseTexture.source.data;
|
|
2460
|
-
this.uniforms.get("blueNoiseSize").value.set(width, height);
|
|
2461
|
-
}
|
|
2462
|
-
}
|
|
2463
|
-
constructor(velocityPass, options = defaultOptions){
|
|
2464
|
-
options = _extends({}, defaultOptions, options);
|
|
2465
|
-
const { fragmentShader, uniforms } = setupBlueNoise(motion_blur);
|
|
2466
|
-
// convert the uniforms from type { uniform: value,... } to type ["uniform", value,...]
|
|
2467
|
-
const formattedUniforms = [];
|
|
2468
|
-
for (const key of Object.keys(uniforms)){
|
|
2469
|
-
formattedUniforms.push([
|
|
2470
|
-
key,
|
|
2471
|
-
uniforms[key]
|
|
2472
|
-
]);
|
|
2473
|
-
}
|
|
2474
|
-
super("MotionBlurEffect", fragmentShader, {
|
|
2475
|
-
type: "MotionBlurMaterial",
|
|
2476
|
-
uniforms: new Map([
|
|
2477
|
-
...formattedUniforms,
|
|
2478
|
-
[
|
|
2479
|
-
"inputTexture",
|
|
2480
|
-
new Uniform(null)
|
|
2481
|
-
],
|
|
2482
|
-
[
|
|
2483
|
-
"velocityTexture",
|
|
2484
|
-
new Uniform(velocityPass.texture)
|
|
2485
|
-
],
|
|
2486
|
-
[
|
|
2487
|
-
"resolution",
|
|
2488
|
-
new Uniform(new Vector2())
|
|
2489
|
-
],
|
|
2490
|
-
[
|
|
2491
|
-
"intensity",
|
|
2492
|
-
new Uniform(1)
|
|
2493
|
-
],
|
|
2494
|
-
[
|
|
2495
|
-
"jitter",
|
|
2496
|
-
new Uniform(1)
|
|
2497
|
-
],
|
|
2498
|
-
[
|
|
2499
|
-
"frame",
|
|
2500
|
-
new Uniform(0)
|
|
2501
|
-
],
|
|
2502
|
-
[
|
|
2503
|
-
"deltaTime",
|
|
2504
|
-
new Uniform(0)
|
|
2505
|
-
]
|
|
2506
|
-
]),
|
|
2507
|
-
defines: new Map([
|
|
2508
|
-
[
|
|
2509
|
-
"samples",
|
|
2510
|
-
options.samples.toFixed(0)
|
|
2511
|
-
],
|
|
2512
|
-
[
|
|
2513
|
-
"samplesFloat",
|
|
2514
|
-
options.samples.toFixed(0) + ".0"
|
|
2515
|
-
]
|
|
2516
|
-
])
|
|
2517
|
-
});
|
|
2518
|
-
this.pointsIndex = 0;
|
|
2519
|
-
this.makeOptionsReactive(options);
|
|
2520
|
-
}
|
|
2521
|
-
}
|
|
2522
|
-
|
|
2523
|
-
var ao_compose = "#define GLSLIFY 1\nuniform sampler2D inputTexture;uniform highp sampler2D depthTexture;uniform float power;uniform vec3 color;void mainImage(const in vec4 inputColor,const in vec2 uv,out vec4 outputColor){float unpackedDepth=textureLod(depthTexture,uv,0.).r;float ao=unpackedDepth>0.9999 ? 1.0 : textureLod(inputTexture,uv,0.0).a;ao=pow(ao,power);vec3 aoColor=mix(color,vec3(1.),ao);aoColor*=inputColor.rgb;outputColor=vec4(aoColor,inputColor.a);}"; // eslint-disable-line
|
|
2524
|
-
|
|
2525
|
-
const defaultAOOptions = _extends({
|
|
2526
|
-
resolutionScale: 1,
|
|
2527
|
-
spp: 8,
|
|
2528
|
-
distance: 2,
|
|
2529
|
-
distancePower: 1,
|
|
2530
|
-
power: 2,
|
|
2531
|
-
bias: 40,
|
|
2532
|
-
thickness: 0.075,
|
|
2533
|
-
color: new Color("black"),
|
|
2534
|
-
useNormalPass: false,
|
|
2535
|
-
velocityDepthNormalPass: null,
|
|
2536
|
-
normalTexture: null
|
|
2537
|
-
}, PoissonDenoisePass.DefaultOptions);
|
|
2538
|
-
class AOEffect extends Effect {
|
|
2539
|
-
makeOptionsReactive(options) {
|
|
2540
|
-
for (const key of Object.keys(options)){
|
|
2541
|
-
Object.defineProperty(this, key, {
|
|
2542
|
-
get () {
|
|
2543
|
-
return options[key];
|
|
2544
|
-
},
|
|
2545
|
-
set (value) {
|
|
2546
|
-
if (value === null || value === undefined) return;
|
|
2547
|
-
options[key] = value;
|
|
2548
|
-
switch(key){
|
|
2549
|
-
case "spp":
|
|
2550
|
-
this.aoPass.fullscreenMaterial.defines.spp = value.toFixed(0);
|
|
2551
|
-
this.aoPass.fullscreenMaterial.needsUpdate = true;
|
|
2552
|
-
break;
|
|
2553
|
-
case "distance":
|
|
2554
|
-
this.aoPass.fullscreenMaterial.uniforms.aoDistance.value = value;
|
|
2555
|
-
break;
|
|
2556
|
-
case "resolutionScale":
|
|
2557
|
-
this.setSize(this.lastSize.width, this.lastSize.height);
|
|
2558
|
-
break;
|
|
2559
|
-
case "power":
|
|
2560
|
-
this.uniforms.get("power").value = value;
|
|
2561
|
-
break;
|
|
2562
|
-
case "color":
|
|
2563
|
-
this.uniforms.get("color").value.copy(new Color(value));
|
|
2564
|
-
break;
|
|
2565
|
-
// denoiser
|
|
2566
|
-
case "iterations":
|
|
2567
|
-
case "radius":
|
|
2568
|
-
case "rings":
|
|
2569
|
-
case "samples":
|
|
2570
|
-
this.PoissonDenoisePass[key] = value;
|
|
2571
|
-
break;
|
|
2572
|
-
case "lumaPhi":
|
|
2573
|
-
case "depthPhi":
|
|
2574
|
-
case "normalPhi":
|
|
2575
|
-
this.PoissonDenoisePass.fullscreenMaterial.uniforms[key].value = Math.max(value, 0.0001);
|
|
2576
|
-
break;
|
|
2577
|
-
default:
|
|
2578
|
-
if (key in this.aoPass.fullscreenMaterial.uniforms) {
|
|
2579
|
-
this.aoPass.fullscreenMaterial.uniforms[key].value = value;
|
|
2580
|
-
}
|
|
2581
|
-
}
|
|
2582
|
-
},
|
|
2583
|
-
configurable: true
|
|
2584
|
-
});
|
|
2585
|
-
// apply all uniforms and defines
|
|
2586
|
-
this[key] = options[key];
|
|
2587
|
-
}
|
|
2588
|
-
}
|
|
2589
|
-
setSize(width, height) {
|
|
2590
|
-
var _this_normalPass;
|
|
2591
|
-
if (width === undefined || height === undefined) return;
|
|
2592
|
-
if (width === this.lastSize.width && height === this.lastSize.height && this.resolutionScale === this.lastSize.resolutionScale) {
|
|
2593
|
-
return;
|
|
2594
|
-
}
|
|
2595
|
-
(_this_normalPass = this.normalPass) == null ? void 0 : _this_normalPass.setSize(width, height);
|
|
2596
|
-
this.aoPass.setSize(width * this.resolutionScale, height * this.resolutionScale);
|
|
2597
|
-
this.PoissonDenoisePass.setSize(width, height);
|
|
2598
|
-
this.lastSize = {
|
|
2599
|
-
width,
|
|
2600
|
-
height,
|
|
2601
|
-
resolutionScale: this.resolutionScale
|
|
2602
|
-
};
|
|
2603
|
-
}
|
|
2604
|
-
get texture() {
|
|
2605
|
-
if (this.iterations > 0) {
|
|
2606
|
-
return this.PoissonDenoisePass.texture;
|
|
2607
|
-
}
|
|
2608
|
-
return this.aoPass.texture;
|
|
2609
|
-
}
|
|
2610
|
-
update(renderer) {
|
|
2611
|
-
var _this_normalPass;
|
|
2612
|
-
// check if TRAA is being used so we can animate the noise
|
|
2613
|
-
const hasTRAA = this.composer.passes.some((pass)=>{
|
|
2614
|
-
var _pass_effects;
|
|
2615
|
-
return pass.enabled && !pass.skipRendering && ((_pass_effects = pass.effects) == null ? void 0 : _pass_effects.some((effect)=>effect instanceof TRAAEffect));
|
|
2616
|
-
});
|
|
2617
|
-
// set animated noise depending on TRAA
|
|
2618
|
-
if (hasTRAA && !("animatedNoise" in this.aoPass.fullscreenMaterial.defines)) {
|
|
2619
|
-
this.aoPass.fullscreenMaterial.defines.animatedNoise = "";
|
|
2620
|
-
this.aoPass.fullscreenMaterial.needsUpdate = true;
|
|
2621
|
-
} else if (!hasTRAA && "animatedNoise" in this.aoPass.fullscreenMaterial.defines) {
|
|
2622
|
-
delete this.aoPass.fullscreenMaterial.defines.animatedNoise;
|
|
2623
|
-
this.aoPass.fullscreenMaterial.needsUpdate = true;
|
|
2624
|
-
}
|
|
2625
|
-
this.uniforms.get("inputTexture").value = this.texture;
|
|
2626
|
-
(_this_normalPass = this.normalPass) == null ? void 0 : _this_normalPass.render(renderer);
|
|
2627
|
-
this.aoPass.render(renderer);
|
|
2628
|
-
this.PoissonDenoisePass.render(renderer);
|
|
2629
|
-
}
|
|
2630
|
-
constructor(composer, camera, scene, aoPass, options = defaultAOOptions){
|
|
2631
|
-
super("AOEffect", ao_compose, {
|
|
2632
|
-
type: "FinalAOMaterial",
|
|
2633
|
-
uniforms: new Map([
|
|
2634
|
-
[
|
|
2635
|
-
"inputTexture",
|
|
2636
|
-
new Uniform(null)
|
|
2637
|
-
],
|
|
2638
|
-
[
|
|
2639
|
-
"depthTexture",
|
|
2640
|
-
new Uniform(null)
|
|
2641
|
-
],
|
|
2642
|
-
[
|
|
2643
|
-
"power",
|
|
2644
|
-
new Uniform(0)
|
|
2645
|
-
],
|
|
2646
|
-
[
|
|
2647
|
-
"color",
|
|
2648
|
-
new Uniform(new Color("black"))
|
|
2649
|
-
]
|
|
2650
|
-
])
|
|
2651
|
-
});
|
|
2652
|
-
this.lastSize = {
|
|
2653
|
-
width: 0,
|
|
2654
|
-
height: 0,
|
|
2655
|
-
resolutionScale: 0
|
|
2656
|
-
};
|
|
2657
|
-
this.composer = composer;
|
|
2658
|
-
this.aoPass = aoPass;
|
|
2659
|
-
options = _extends({}, defaultAOOptions, options);
|
|
2660
|
-
// set up depth texture
|
|
2661
|
-
if (!composer.depthTexture) composer.createDepthTexture();
|
|
2662
|
-
this.aoPass.fullscreenMaterial.uniforms.depthTexture.value = composer.depthTexture;
|
|
2663
|
-
this.uniforms.get("depthTexture").value = composer.depthTexture;
|
|
2664
|
-
// set up optional normal texture
|
|
2665
|
-
if (options.useNormalPass || options.normalTexture) {
|
|
2666
|
-
if (options.useNormalPass) this.normalPass = new NormalPass(scene, camera);
|
|
2667
|
-
var _options_normalTexture;
|
|
2668
|
-
const normalTexture = (_options_normalTexture = options.normalTexture) != null ? _options_normalTexture : this.normalPass.texture;
|
|
2669
|
-
this.aoPass.fullscreenMaterial.uniforms.normalTexture.value = normalTexture;
|
|
2670
|
-
this.aoPass.fullscreenMaterial.defines.useNormalTexture = "";
|
|
2671
|
-
}
|
|
2672
|
-
this.PoissonDenoisePass = new PoissonDenoisePass(camera, this.aoPass.texture, composer.depthTexture, {
|
|
2673
|
-
normalInRgb: true
|
|
2674
|
-
});
|
|
2675
|
-
this.makeOptionsReactive(options);
|
|
2676
|
-
}
|
|
2677
|
-
}
|
|
2678
|
-
AOEffect.DefaultOptions = defaultAOOptions;
|
|
2679
|
-
|
|
2680
|
-
class EffectComposerPlugin extends Plugin {
|
|
2681
|
-
static Instance(viewer) {
|
|
2682
|
-
return viewer.getPlugin(EffectComposerPlugin, true);
|
|
2683
|
-
}
|
|
2684
|
-
get multisampling() {
|
|
2685
|
-
return this._composer.multisampling;
|
|
2686
|
-
}
|
|
2687
|
-
set multisampling(v) {
|
|
2688
|
-
this._composer.multisampling = v;
|
|
2689
|
-
}
|
|
2690
|
-
getPass(constructor) {
|
|
2691
|
-
return this._composer.passes.find((v)=>v.constructor === constructor);
|
|
2692
|
-
}
|
|
2693
|
-
addPass(pass) {
|
|
2694
|
-
this._composer.addPass(pass, this._composer.passes.length - 1);
|
|
2695
|
-
this._checkOutputPass();
|
|
2696
|
-
return pass;
|
|
2697
|
-
}
|
|
2698
|
-
removePass(pass) {
|
|
2699
|
-
this._composer.removePass(pass);
|
|
2700
|
-
this._checkOutputPass();
|
|
2701
|
-
}
|
|
2702
|
-
activePass(pass, v) {
|
|
2703
|
-
pass.enabled = v;
|
|
2704
|
-
this._checkOutputPass();
|
|
2705
|
-
return pass;
|
|
2706
|
-
}
|
|
2707
|
-
getVelocityDepthNormalPass(autoAdd = false) {
|
|
2708
|
-
let vdnPass = this.getPass(VelocityDepthNormalPass);
|
|
2709
|
-
if (vdnPass === undefined && autoAdd) {
|
|
2710
|
-
vdnPass = this.addPass(new VelocityDepthNormalPass(this.viewer.scene, this.viewer.camera));
|
|
2711
|
-
}
|
|
2712
|
-
return vdnPass;
|
|
2713
|
-
}
|
|
2714
|
-
_checkOutputPass() {
|
|
2715
|
-
const vdnPass = this.getVelocityDepthNormalPass();
|
|
2716
|
-
const count = this._composer.passes.filter((v)=>v.enabled && v !== vdnPass && v !== this._outputPass).length;
|
|
2717
|
-
this._outputPass.enabled = this._composer.multisampling > 0 && count === 1;
|
|
2718
|
-
this._setRenderToScreen();
|
|
2719
|
-
}
|
|
2720
|
-
_setRenderToScreen() {
|
|
2721
|
-
const vdnPass = this.getVelocityDepthNormalPass();
|
|
2722
|
-
const passes = this._composer.passes;
|
|
2723
|
-
for(let k = 0, i = passes.length; i--;){
|
|
2724
|
-
let pass = passes[i];
|
|
2725
|
-
if (pass.enabled && pass !== vdnPass && k === 0) {
|
|
2726
|
-
k = i;
|
|
2727
|
-
}
|
|
2728
|
-
pass.renderToScreen = k === i;
|
|
2729
|
-
}
|
|
2730
|
-
}
|
|
2731
|
-
constructor(props){
|
|
2732
|
-
super();
|
|
2733
|
-
this.install = ()=>{
|
|
2734
|
-
const { renderer, scene, camera } = this.viewer;
|
|
2735
|
-
this._renderPass = new RenderPass(scene, camera);
|
|
2736
|
-
this._outputPass = new EffectPass(camera);
|
|
2737
|
-
this._composer = new EffectComposer(renderer, Object.assign({
|
|
2738
|
-
frameBufferType: HalfFloatType
|
|
2739
|
-
}, props));
|
|
2740
|
-
this._composer.addPass(this._renderPass);
|
|
2741
|
-
this._composer.addPass(this._outputPass);
|
|
2742
|
-
this.viewer._onResize = (width, height)=>this._composer.setSize(width, height);
|
|
2743
|
-
this.viewer._onRender = (dt)=>this._composer.render(dt);
|
|
2744
|
-
};
|
|
2745
|
-
this.uninstall = ()=>{
|
|
2746
|
-
this._composer.dispose();
|
|
2747
|
-
};
|
|
2748
|
-
}
|
|
2749
|
-
}
|
|
2750
|
-
|
|
2751
|
-
/******************************************************************************
|
|
2752
|
-
Copyright (c) Microsoft Corporation.
|
|
2753
|
-
|
|
2754
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
2755
|
-
purpose with or without fee is hereby granted.
|
|
2756
|
-
|
|
2757
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
2758
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
2759
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
2760
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
2761
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
2762
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
2763
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
2764
|
-
***************************************************************************** */
|
|
2765
|
-
/* global Reflect, Promise, SuppressedError, Symbol */
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
function __decorate(decorators, target, key, desc) {
|
|
2769
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2770
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2771
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
2772
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2773
|
-
}
|
|
2774
|
-
|
|
2775
|
-
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
2776
|
-
var e = new Error(message);
|
|
2777
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
2778
|
-
};
|
|
2779
|
-
|
|
2780
|
-
class PassPlugin extends Plugin {
|
|
2781
|
-
get enable() {
|
|
2782
|
-
return this.pass.enabled;
|
|
2783
|
-
}
|
|
2784
|
-
set enable(v) {
|
|
2785
|
-
this.setEnable(v);
|
|
2786
|
-
}
|
|
2787
|
-
get composer() {
|
|
2788
|
-
return EffectComposerPlugin.Instance(this.viewer);
|
|
2789
|
-
}
|
|
2790
|
-
setEnable(v) {
|
|
2791
|
-
this.composer.activePass(this.pass, v);
|
|
2792
|
-
}
|
|
2793
|
-
}
|
|
2794
|
-
__decorate([
|
|
2795
|
-
property
|
|
2796
|
-
], PassPlugin.prototype, "enable", null);
|
|
2797
|
-
|
|
2798
|
-
class ToneMappingPlugin extends PassPlugin {
|
|
2799
|
-
get mode() {
|
|
2800
|
-
return this.effect.mode;
|
|
2801
|
-
}
|
|
2802
|
-
set mode(v) {
|
|
2803
|
-
this.effect.mode = v;
|
|
2804
|
-
}
|
|
2805
|
-
constructor(props){
|
|
2806
|
-
super();
|
|
2807
|
-
this.install = ()=>{
|
|
2808
|
-
this.effect = new ToneMappingEffect(props);
|
|
2809
|
-
this.pass = this.composer.addPass(new EffectPass(this.viewer.camera, this.effect));
|
|
2810
|
-
};
|
|
2811
|
-
this.uninstall = ()=>{
|
|
2812
|
-
this.composer.removePass(this.pass);
|
|
2813
|
-
};
|
|
2814
|
-
}
|
|
2815
|
-
}
|
|
2816
|
-
__decorate([
|
|
2817
|
-
property({
|
|
2818
|
-
value: ToneMappingMode
|
|
2819
|
-
})
|
|
2820
|
-
], ToneMappingPlugin.prototype, "mode", null);
|
|
2821
|
-
|
|
2822
|
-
class MotionBlurPlugin extends PassPlugin {
|
|
2823
|
-
constructor(){
|
|
2824
|
-
super();
|
|
2825
|
-
this.install = ()=>{
|
|
2826
|
-
const effect = new MotionBlurEffect(this.composer.getVelocityDepthNormalPass(true));
|
|
2827
|
-
this.pass = this.composer.addPass(new EffectPass(this.viewer.camera, effect));
|
|
2828
|
-
};
|
|
2829
|
-
this.uninstall = ()=>{
|
|
2830
|
-
this.composer.removePass(this.pass);
|
|
2831
|
-
};
|
|
2832
|
-
}
|
|
2833
|
-
}
|
|
2834
|
-
|
|
2835
|
-
class BloomPlugin extends PassPlugin {
|
|
2836
|
-
get intensity() {
|
|
2837
|
-
return this.effect.intensity;
|
|
2838
|
-
}
|
|
2839
|
-
set intensity(v) {
|
|
2840
|
-
this.effect.intensity = v;
|
|
2841
|
-
}
|
|
2842
|
-
get luminanceThreshold() {
|
|
2843
|
-
return this.effect.luminanceMaterial.threshold;
|
|
2844
|
-
}
|
|
2845
|
-
set luminanceThreshold(v) {
|
|
2846
|
-
this.effect.luminanceMaterial.threshold = v;
|
|
2847
|
-
}
|
|
2848
|
-
get luminanceSmoothing() {
|
|
2849
|
-
return this.effect.luminanceMaterial.smoothing;
|
|
2850
|
-
}
|
|
2851
|
-
set luminanceSmoothing(v) {
|
|
2852
|
-
this.effect.luminanceMaterial.smoothing = v;
|
|
2853
|
-
}
|
|
2854
|
-
constructor(props){
|
|
2855
|
-
super();
|
|
2856
|
-
this.install = ()=>{
|
|
2857
|
-
this.effect = new BloomEffect(_extends({
|
|
2858
|
-
blendFunction: BlendFunction.ADD
|
|
2859
|
-
}, props));
|
|
2860
|
-
this.pass = this.composer.addPass(new EffectPass(this.viewer.camera, this.effect));
|
|
2861
|
-
};
|
|
2862
|
-
this.uninstall = ()=>{
|
|
2863
|
-
this.composer.removePass(this.pass);
|
|
2864
|
-
};
|
|
2865
|
-
}
|
|
2866
|
-
}
|
|
2867
|
-
__decorate([
|
|
2868
|
-
property({
|
|
2869
|
-
min: 0,
|
|
2870
|
-
max: 2,
|
|
2871
|
-
step: 0.01
|
|
2872
|
-
})
|
|
2873
|
-
], BloomPlugin.prototype, "intensity", null);
|
|
2874
|
-
__decorate([
|
|
2875
|
-
property({
|
|
2876
|
-
min: 0,
|
|
2877
|
-
max: 10,
|
|
2878
|
-
step: 0.01
|
|
2879
|
-
})
|
|
2880
|
-
], BloomPlugin.prototype, "luminanceThreshold", null);
|
|
2881
|
-
__decorate([
|
|
2882
|
-
property({
|
|
2883
|
-
min: 0,
|
|
2884
|
-
max: 10,
|
|
2885
|
-
step: 0.01
|
|
2886
|
-
})
|
|
2887
|
-
], BloomPlugin.prototype, "luminanceSmoothing", null);
|
|
2888
|
-
|
|
2889
|
-
class FXAAPlugin extends PassPlugin {
|
|
2890
|
-
constructor(){
|
|
2891
|
-
super();
|
|
2892
|
-
this.install = ()=>{
|
|
2893
|
-
this.pass = this.composer.addPass(new EffectPass(this.viewer.camera, new FXAAEffect()));
|
|
2894
|
-
};
|
|
2895
|
-
this.uninstall = ()=>{
|
|
2896
|
-
this.composer.removePass(this.pass);
|
|
2897
|
-
};
|
|
2898
|
-
}
|
|
2899
|
-
}
|
|
2900
|
-
|
|
2901
|
-
class SMAAPlugin extends PassPlugin {
|
|
2902
|
-
get preset() {
|
|
2903
|
-
return this._preset;
|
|
2904
|
-
}
|
|
2905
|
-
set preset(v) {
|
|
2906
|
-
if (this._preset !== v) {
|
|
2907
|
-
this._preset = v;
|
|
2908
|
-
this.effect.applyPreset(v);
|
|
2909
|
-
}
|
|
2910
|
-
}
|
|
2911
|
-
get edgeDetectionMode() {
|
|
2912
|
-
return this.effect.edgeDetectionMaterial.edgeDetectionMode;
|
|
2913
|
-
}
|
|
2914
|
-
set edgeDetectionMode(v) {
|
|
2915
|
-
this.effect.edgeDetectionMaterial.edgeDetectionMode = v;
|
|
2916
|
-
}
|
|
2917
|
-
get predicationMode() {
|
|
2918
|
-
return this.effect.edgeDetectionMaterial.predicationMode;
|
|
2919
|
-
}
|
|
2920
|
-
set predicationMode(v) {
|
|
2921
|
-
this.effect.edgeDetectionMaterial.predicationMode = v;
|
|
2922
|
-
}
|
|
2923
|
-
constructor(props = {}){
|
|
2924
|
-
super();
|
|
2925
|
-
this._preset = SMAAPreset.MEDIUM;
|
|
2926
|
-
this.install = ()=>{
|
|
2927
|
-
this.effect = new SMAAEffect(props);
|
|
2928
|
-
this.pass = this.composer.addPass(new EffectPass(this.viewer.camera, this.effect));
|
|
2929
|
-
};
|
|
2930
|
-
this.uninstall = ()=>{
|
|
2931
|
-
this.composer.removePass(this.pass);
|
|
2932
|
-
};
|
|
2933
|
-
}
|
|
2934
|
-
}
|
|
2935
|
-
__decorate([
|
|
2936
|
-
property({
|
|
2937
|
-
value: SMAAPreset
|
|
2938
|
-
})
|
|
2939
|
-
], SMAAPlugin.prototype, "preset", null);
|
|
2940
|
-
__decorate([
|
|
2941
|
-
property({
|
|
2942
|
-
value: EdgeDetectionMode
|
|
2943
|
-
})
|
|
2944
|
-
], SMAAPlugin.prototype, "edgeDetectionMode", null);
|
|
2945
|
-
__decorate([
|
|
2946
|
-
property({
|
|
2947
|
-
value: PredicationMode
|
|
2948
|
-
})
|
|
2949
|
-
], SMAAPlugin.prototype, "predicationMode", null);
|
|
2950
|
-
|
|
2951
|
-
class MSAAPlugin extends Plugin {
|
|
2952
|
-
get enable() {
|
|
2953
|
-
return this.composer.multisampling > 0;
|
|
2954
|
-
}
|
|
2955
|
-
set enable(v) {
|
|
2956
|
-
this.composer.multisampling = v ? this._maxSamples : 0;
|
|
2957
|
-
}
|
|
2958
|
-
get composer() {
|
|
2959
|
-
return EffectComposerPlugin.Instance(this.viewer);
|
|
2960
|
-
}
|
|
2961
|
-
constructor(){
|
|
2962
|
-
super();
|
|
2963
|
-
this._maxSamples = 4;
|
|
2964
|
-
this.install = ()=>{
|
|
2965
|
-
const { renderer } = this.viewer;
|
|
2966
|
-
this._maxSamples = Math.min(4, renderer.capabilities.maxSamples);
|
|
2967
|
-
this.composer.multisampling = this._maxSamples;
|
|
2968
|
-
};
|
|
2969
|
-
this.uninstall = ()=>{
|
|
2970
|
-
this.composer.multisampling = 0;
|
|
2971
|
-
};
|
|
965
|
+
function getVelocityDepthNormalPass(viewer, autoAdd = true) {
|
|
966
|
+
const composer = EffectComposerPlugin.Instance(viewer);
|
|
967
|
+
let vdnPass = composer.getPass(VelocityDepthNormalPass);
|
|
968
|
+
if (vdnPass === undefined && autoAdd) {
|
|
969
|
+
vdnPass = composer.addPass(new VelocityDepthNormalPass(viewer.scene, viewer.camera));
|
|
2972
970
|
}
|
|
971
|
+
return vdnPass;
|
|
2973
972
|
}
|
|
2974
|
-
__decorate([
|
|
2975
|
-
property
|
|
2976
|
-
], MSAAPlugin.prototype, "enable", null);
|
|
2977
973
|
|
|
2978
974
|
class TRAAPlugin extends PassPlugin {
|
|
2979
975
|
constructor(props = {}){
|
|
2980
976
|
super();
|
|
977
|
+
this.type = "TRAAPlugin";
|
|
2981
978
|
this.install = ()=>{
|
|
2982
979
|
const { scene, camera } = this.viewer;
|
|
2983
|
-
this._effect = new TRAAEffect(scene, camera, this.
|
|
980
|
+
this._effect = new TRAAEffect(scene, camera, getVelocityDepthNormalPass(this.viewer), props);
|
|
2984
981
|
this.pass = this.composer.addPass(new EffectPass(camera, this._effect));
|
|
2985
982
|
};
|
|
2986
983
|
this.uninstall = ()=>{
|
|
@@ -2988,6 +985,7 @@ class TRAAPlugin extends PassPlugin {
|
|
|
2988
985
|
};
|
|
2989
986
|
}
|
|
2990
987
|
}
|
|
988
|
+
PropertyManager._getClassProperties("TRAAPlugin").property("enable");
|
|
2991
989
|
|
|
2992
|
-
export { BloomPlugin, EffectComposerPlugin, FXAAPlugin, MSAAPlugin,
|
|
990
|
+
export { BloomPlugin, EffectComposerPlugin, FXAAPlugin, MSAAPlugin, SMAAPlugin, TRAAPlugin, ToneMappingPlugin };
|
|
2993
991
|
//# sourceMappingURL=module.js.map
|