forked from mrdoob/three.js
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathViewportDepthNode.js
294 lines (231 loc) · 9.35 KB
/
ViewportDepthNode.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
import Node from '../core/Node.js';
import { float, log, log2, nodeImmutable, nodeProxy } from '../tsl/TSLBase.js';
import { cameraNear, cameraFar } from '../accessors/Camera.js';
import { positionView } from '../accessors/Position.js';
import { viewportDepthTexture } from './ViewportDepthTextureNode.js';
/**
* This node offers a collection of features in context of the depth logic in the fragment shader.
* Depending on {@link ViewportDepthNode#scope}, it can be used to define a depth value for the current
* fragment or for depth evaluation purposes.
*
* @augments Node
*/
class ViewportDepthNode extends Node {
static get type() {
return 'ViewportDepthNode';
}
/**
* Constructs a new viewport depth node.
*
* @param {('depth'|'depthBase'|'linearDepth')} scope - The node's scope.
* @param {?Node} [valueNode=null] - The value node.
*/
constructor( scope, valueNode = null ) {
super( 'float' );
/**
* The node behaves differently depending on which scope is selected.
*
* - `ViewportDepthNode.DEPTH_BASE`: Allows to define a value for the current fragment's depth.
* - `ViewportDepthNode.DEPTH`: Represents the depth value for the current fragment (`valueNode` is ignored).
* - `ViewportDepthNode.LINEAR_DEPTH`: Represents the linear (orthographic) depth value of the current fragment.
* If a `valueNode` is set, the scope can be used to convert perspective depth data to linear data.
*
* @type {('depth'|'depthBase'|'linearDepth')}
*/
this.scope = scope;
/**
* Can be used to define a custom depth value.
* The property is ignored in the `ViewportDepthNode.DEPTH` scope.
*
* @type {?Node}
* @default null
*/
this.valueNode = valueNode;
/**
* This flag can be used for type testing.
*
* @type {boolean}
* @readonly
* @default true
*/
this.isViewportDepthNode = true;
}
generate( builder ) {
const { scope } = this;
if ( scope === ViewportDepthNode.DEPTH_BASE ) {
return builder.getFragDepth();
}
return super.generate( builder );
}
setup( { camera } ) {
const { scope } = this;
const value = this.valueNode;
let node = null;
if ( scope === ViewportDepthNode.DEPTH_BASE ) {
if ( value !== null ) {
node = depthBase().assign( value );
}
} else if ( scope === ViewportDepthNode.DEPTH ) {
if ( camera.isPerspectiveCamera ) {
node = viewZToPerspectiveDepth( positionView.z, cameraNear, cameraFar );
} else {
node = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar );
}
} else if ( scope === ViewportDepthNode.LINEAR_DEPTH ) {
if ( value !== null ) {
if ( camera.isPerspectiveCamera ) {
const viewZ = perspectiveDepthToViewZ( value, cameraNear, cameraFar );
node = viewZToOrthographicDepth( viewZ, cameraNear, cameraFar );
} else {
node = value;
}
} else {
node = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar );
}
}
return node;
}
}
ViewportDepthNode.DEPTH_BASE = 'depthBase';
ViewportDepthNode.DEPTH = 'depth';
ViewportDepthNode.LINEAR_DEPTH = 'linearDepth';
export default ViewportDepthNode;
// NOTE: viewZ, the z-coordinate in camera space, is negative for points in front of the camera
/**
* TSL function for converting a viewZ value to an orthographic depth value.
*
* @tsl
* @function
* @param {Node<float>} viewZ - The viewZ node.
* @param {Node<float>} near - The camera's near value.
* @param {Node<float>} far - The camera's far value.
* @returns {Node<float>}
*/
export const viewZToOrthographicDepth = ( viewZ, near, far ) => viewZ.add( near ).div( near.sub( far ) );
/**
* TSL function for converting an orthographic depth value to a viewZ value.
*
* @tsl
* @function
* @param {Node<float>} depth - The orthographic depth.
* @param {Node<float>} near - The camera's near value.
* @param {Node<float>} far - The camera's far value.
* @returns {Node<float>}
*/
export const orthographicDepthToViewZ = ( depth, near, far ) => near.sub( far ).mul( depth ).sub( near );
/**
* TSL function for converting a viewZ value to a perspective depth value.
*
* Note: {link https://twitter.com/gonnavis/status/1377183786949959682}.
*
* @tsl
* @function
* @param {Node<float>} viewZ - The viewZ node.
* @param {Node<float>} near - The camera's near value.
* @param {Node<float>} far - The camera's far value.
* @returns {Node<float>}
*/
export const viewZToPerspectiveDepth = ( viewZ, near, far ) => near.add( viewZ ).mul( far ).div( far.sub( near ).mul( viewZ ) );
/**
* TSL function for converting a perspective depth value to a viewZ value.
*
* @tsl
* @function
* @param {Node<float>} depth - The perspective depth.
* @param {Node<float>} near - The camera's near value.
* @param {Node<float>} far - The camera's far value.
* @returns {Node<float>}
*/
export const perspectiveDepthToViewZ = ( depth, near, far ) => near.mul( far ).div( far.sub( near ).mul( depth ).sub( far ) );
/**
* TSL function for converting a viewZ value to a logarithmic depth value.
*
* @tsl
* @function
* @param {Node<float>} viewZ - The viewZ node.
* @param {Node<float>} near - The camera's near value.
* @param {Node<float>} far - The camera's far value.
* @returns {Node<float>}
*/
export const viewZToLogarithmicDepth = ( viewZ, near, far ) => {
// NOTE: viewZ must be negative--see explanation at the end of this comment block.
// The final logarithmic depth formula used here is adapted from one described in an
// article by Thatcher Ulrich (see http://tulrich.com/geekstuff/log_depth_buffer.txt),
// which was an improvement upon an earlier formula one described in an
// Outerra article (https://outerra.blogspot.com/2009/08/logarithmic-z-buffer.html).
// Ulrich's formula is the following:
// z = K * log( w / cameraNear ) / log( cameraFar / cameraNear )
// where K = 2^k - 1, and k is the number of bits in the depth buffer.
// The Outerra variant ignored the camera near plane (it assumed it was 0) and instead
// opted for a "C-constant" for resolution adjustment of objects near the camera.
// Outerra states: "Notice that the 'C' variant doesn’t use a near plane distance, it has it
// set at 0" (quote from https://outerra.blogspot.com/2012/11/maximizing-depth-buffer-range-and.html).
// Ulrich's variant has the benefit of constant relative precision over the whole near-far range.
// It was debated here whether Outerra's "C-constant" or Ulrich's "near plane" variant should
// be used, and ultimately Ulrich's "near plane" version was chosen.
// Outerra eventually made another improvement to their original "C-constant" variant,
// but it still does not incorporate the camera near plane (for this version,
// see https://outerra.blogspot.com/2013/07/logarithmic-depth-buffer-optimizations.html).
// Here we make 4 changes to Ulrich's formula:
// 1. Clamp the camera near plane so we don't divide by 0.
// 2. Use log2 instead of log to avoid an extra multiply (shaders implement log using log2).
// 3. Assume K is 1 (K = maximum value in depth buffer; see Ulrich's formula above).
// 4. To maintain consistency with the functions "viewZToOrthographicDepth" and "viewZToPerspectiveDepth",
// we modify the formula here to use 'viewZ' instead of 'w'. The other functions expect a negative viewZ,
// so we do the same here, hence the 'viewZ.negate()' call.
// For visual representation of this depth curve, see https://www.desmos.com/calculator/uyqk0vex1u
near = near.max( 1e-6 ).toVar();
const numerator = log2( viewZ.negate().div( near ) );
const denominator = log2( far.div( near ) );
return numerator.div( denominator );
};
/**
* TSL function for converting a logarithmic depth value to a viewZ value.
*
* @tsl
* @function
* @param {Node<float>} depth - The logarithmic depth.
* @param {Node<float>} near - The camera's near value.
* @param {Node<float>} far - The camera's far value.
* @returns {Node<float>}
*/
export const logarithmicDepthToViewZ = ( depth, near, far ) => {
// NOTE: we add a 'negate()' call to the return value here to maintain consistency with
// the functions "orthographicDepthToViewZ" and "perspectiveDepthToViewZ" (they return
// a negative viewZ).
const exponent = depth.mul( log( far.div( near ) ) );
return float( Math.E ).pow( exponent ).mul( near ).negate();
};
/**
* TSL function for defining a value for the current fragment's depth.
*
* @tsl
* @function
* @param {Node<float>} value - The depth value to set.
* @returns {ViewportDepthNode<float>}
*/
const depthBase = /*@__PURE__*/ nodeProxy( ViewportDepthNode, ViewportDepthNode.DEPTH_BASE );
/**
* TSL object that represents the depth value for the current fragment.
*
* @tsl
* @type {ViewportDepthNode}
*/
export const depth = /*@__PURE__*/ nodeImmutable( ViewportDepthNode, ViewportDepthNode.DEPTH );
/**
* TSL function for converting a perspective depth value to linear depth.
*
* @tsl
* @function
* @param {?Node<float>} [value=null] - The perspective depth. If `null` is provided, the current fragment's depth is used.
* @returns {ViewportDepthNode<float>}
*/
export const linearDepth = /*@__PURE__*/ nodeProxy( ViewportDepthNode, ViewportDepthNode.LINEAR_DEPTH ).setParameterLength( 0, 1 );
/**
* TSL object that represents the linear (orthographic) depth value of the current fragment
*
* @tsl
* @type {ViewportDepthNode}
*/
export const viewportLinearDepth = /*@__PURE__*/ linearDepth( viewportDepthTexture() );
depth.assign = ( value ) => depthBase( value );