1
+ import Camera from './Camera' ;
2
+ import { SVDAG } from './SVDAG'
3
+ import { loadProgram } from './ShaderUtils' ;
4
+
5
+ // Coupled to the glsl shader
6
+ const UNIFORMS = [
7
+ 'time' , 'resolution' ,
8
+ 'viewMatInv' , 'projMatInv' ,
9
+ 'sceneBBoxMin' , 'sceneBBoxMax' , 'sceneCenter' , 'rootHalfSide' ,
10
+ 'maxIters' , 'drawLevel' , 'projectionFactor' ,
11
+ 'nodes' ,
12
+ 'viewerRenderMode' ,
13
+ 'selectedVoxelIndex' , 'uniqueColors' ,
14
+ 'lightPos' , 'enableShadows' , 'normalEpsilon' ,
15
+ ] as const ;
16
+ type Uniform = typeof UNIFORMS [ number ] ;
17
+
18
+ // Dict of uniform name to its ID
19
+ type UniformDict = {
20
+ [ T in Uniform ] : WebGLUniformLocation ;
21
+ }
22
+
23
+ export enum RenderMode {
24
+ ITERATIONS = 0 ,
25
+ DEPTH = 1 ,
26
+ DIFFUSE_LIGHTING = 2 ,
27
+ PATH_TRACING = 3 ,
28
+ }
29
+
30
+ export interface IRendererState {
31
+ renderMode : RenderMode ;
32
+ startTime : number ;
33
+ time : number ;
34
+ frame : number ;
35
+ pixelTolerance : number ;
36
+ renderScale : number ;
37
+ drawLevel : number ;
38
+ maxIterations : number ;
39
+ useMinDepthOptimization : boolean ;
40
+ showUniqueNodeColors : boolean ;
41
+ }
42
+
43
+ export default class Renderer {
44
+ gl : WebGL2RenderingContext ;
45
+ uniformDict : UniformDict ;
46
+ svdag : SVDAG ;
47
+
48
+ program : WebGLProgram ;
49
+ fragShader : WebGLShader ;
50
+ texture : WebGLTexture ;
51
+ // controller: OrbitController;
52
+
53
+ maxT3DTexels : number ;
54
+
55
+ minDepthTexId ?: number ;
56
+ fullDepthTexId ?: number ;
57
+
58
+ state : IRendererState = {
59
+ startTime : new Date ( ) . getTime ( ) / 1000 ,
60
+ time : 0 ,
61
+ frame : 0 ,
62
+ pixelTolerance : 1 ,
63
+ renderScale : 1 ,
64
+ drawLevel : 1 ,
65
+ maxIterations : 250 ,
66
+ renderMode : RenderMode . ITERATIONS ,
67
+ useMinDepthOptimization : true ,
68
+ showUniqueNodeColors : false ,
69
+ } ;
70
+
71
+ constructor (
72
+ public camera : Camera ,
73
+ public canvas : HTMLCanvasElement ,
74
+ ) {
75
+ this . gl = canvas . getContext ( "webgl2" ) as WebGL2RenderingContext ;
76
+ this . maxT3DTexels = this . gl . getParameter ( this . gl . MAX_3D_TEXTURE_SIZE ) ;
77
+ }
78
+
79
+ public render ( ) {
80
+ const { gl, canvas, state } = this ;
81
+
82
+ // TODO: Only update uniforms when they change, not all of them every time
83
+ this . setInitialUniforms ( ) ;
84
+
85
+ // Render
86
+ gl . clear ( gl . COLOR_BUFFER_BIT ) ;
87
+ gl . viewport ( 0 , 0 , canvas . width , canvas . height ) ;
88
+ gl . drawArrays ( gl . TRIANGLES , 0 , 3 ) ;
89
+
90
+ state . frame ++ ;
91
+ }
92
+
93
+ initScene ( svdag : SVDAG ) {
94
+ this . svdag = svdag ;
95
+ }
96
+
97
+ async initShaders ( ) {
98
+ const { gl, svdag } = this ;
99
+ [ this . program , this . fragShader ] = await loadProgram ( gl , svdag . nLevels ) ;
100
+ }
101
+
102
+ //////////////////////////////////////////////////////////////
103
+ ////////////////////////// UNIFORMS //////////////////////////
104
+ //////////////////////////////////////////////////////////////
105
+ initUniforms ( ) {
106
+ const { gl } = this ;
107
+ this . uniformDict = { } as any ;
108
+ UNIFORMS . forEach ( u => this . uniformDict [ u ] = gl . getUniformLocation ( this . program , u ) ) ;
109
+ }
110
+
111
+ getProjectionFactor ( pixelTolerance : number , screenDivisor : number ) {
112
+ const { canvas, camera } = this ;
113
+ const inv_2tan_half_fovy = 1.0 / ( 2.0 * Math . tan ( 0.5 * camera . fovY ) ) ;
114
+ const screen_tolerance = pixelTolerance / ( canvas . height / screenDivisor ) ;
115
+ return inv_2tan_half_fovy / screen_tolerance ;
116
+ }
117
+
118
+ setInitialUniforms ( ) {
119
+ const { canvas, gl, camera, uniformDict : ud , svdag, state } = this ;
120
+ if ( this . program === undefined ) return ;
121
+
122
+ gl . uniform2f ( ud . resolution , canvas . width , canvas . height ) ;
123
+
124
+ gl . uniform1i ( ud . nodes , 0 ) ;
125
+
126
+ gl . uniform3fv ( ud . sceneBBoxMin , svdag . bboxStart ) ;
127
+ gl . uniform3fv ( ud . sceneBBoxMax , svdag . bboxEnd ) ;
128
+ gl . uniform3fv ( ud . sceneCenter , svdag . bboxCenter ) ;
129
+ gl . uniform1f ( ud . rootHalfSide , svdag . rootSide / 2.0 ) ;
130
+
131
+ // TODO: Make light pos configurable, currently always bboxEnd
132
+ gl . uniform3fv ( ud . lightPos , svdag . bboxEnd ) ;
133
+
134
+ gl . uniform1ui ( ud . maxIters , state . maxIterations ) ;
135
+ gl . uniform1ui ( ud . drawLevel , state . drawLevel ) ;
136
+ gl . uniform1f ( ud . projectionFactor , this . getProjectionFactor ( state . pixelTolerance , 1 ) ) ;
137
+
138
+ gl . uniform1i ( ud . uniqueColors , state . showUniqueNodeColors ? 1 : 0 ) ;
139
+
140
+ gl . uniform1i ( ud . viewerRenderMode , state . renderMode ) ;
141
+
142
+ gl . uniformMatrix4fv ( ud . viewMatInv , false , camera . viewMatInv ) ;
143
+ gl . uniformMatrix4fv ( ud . projMatInv , false , camera . projMatInv ) ;
144
+
145
+ // Todo: make this a vec4 like shadertoy
146
+ gl . uniform1f ( ud . time , new Date ( ) . getTime ( ) / 1000 - state . startTime ) ;
147
+ }
148
+
149
+ // setFrameUniforms() {
150
+
151
+ // }
152
+
153
+
154
+ //////////////////////////////////////////////////////////////
155
+ ////////////////////// 3D TEXTURE DATA ///////////////////////
156
+ //////////////////////////////////////////////////////////////
157
+ createNodesTexture ( ) {
158
+ const { gl } = this ;
159
+ this . texture = gl . createTexture ( ) ;
160
+ gl . activeTexture ( gl . TEXTURE0 ) ;
161
+ gl . bindTexture ( gl . TEXTURE_3D , this . texture ) ;
162
+ gl . texParameteri ( gl . TEXTURE_3D , gl . TEXTURE_MIN_FILTER , gl . NEAREST ) ;
163
+ gl . texParameteri ( gl . TEXTURE_3D , gl . TEXTURE_MAG_FILTER , gl . NEAREST ) ;
164
+ gl . texParameteri ( gl . TEXTURE_3D , gl . TEXTURE_WRAP_S , gl . REPEAT ) ;
165
+ gl . texParameteri ( gl . TEXTURE_3D , gl . TEXTURE_WRAP_T , gl . REPEAT ) ;
166
+ gl . texParameteri ( gl . TEXTURE_3D , gl . TEXTURE_WRAP_R , gl . REPEAT ) ;
167
+ }
168
+
169
+ deleteNodesTexture ( ) {
170
+ if ( this . texture !== undefined ) {
171
+ this . gl . deleteTexture ( this . texture ) ;
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Updates the 3D texture with only the nodes that have been loaded since the last texture update
177
+ */
178
+ uploadTexData ( nNewNodes : number ) {
179
+ const { gl, svdag, maxT3DTexels } = this ;
180
+ const maxT3DTexelsPow2 = maxT3DTexels * maxT3DTexels ;
181
+ const neededTexels = svdag . nodes . length ;
182
+ const depthLayers = Math . ceil ( neededTexels / maxT3DTexelsPow2 ) ;
183
+ const nTexelsToAllocate = maxT3DTexelsPow2 * depthLayers ;
184
+
185
+ const chunkStart = svdag . dataLoadedOffset / 4 - nNewNodes ;
186
+ console . log ( chunkStart , svdag . dataLoadedOffset / 4 , nNewNodes ) ;
187
+
188
+ if ( chunkStart === 0 ) {
189
+ // For the first chunk, define the texture type and upload what data we have
190
+ console . log ( `Initial uploading of nodes to 3D texture (Resolution: ${ maxT3DTexels } x ${ maxT3DTexels } x ${ depthLayers } , nNodes: ${ svdag . dataLoadedOffset } /${ svdag . nodes . length } )...` ) ;
191
+
192
+ // Pad the data to fit the 3D texture dimensions (not required but makes it a bit easier)
193
+ if ( svdag . nodes . length != nTexelsToAllocate ) {
194
+ console . log ( 'Resizing node buffer from ' , svdag . nodes . length , 'to' , nTexelsToAllocate , '(3D Texture padding)' ) ;
195
+ const paddedNodes = new Uint32Array ( nTexelsToAllocate ) ;
196
+ paddedNodes . set ( svdag . nodes ) ;
197
+ svdag . nodes = paddedNodes ;
198
+ }
199
+
200
+ // Every "pixel" in the 3D texture will be used as a 32 bit int, so we set the type to a single 32 bit red pixel (R32UI)
201
+ gl . texStorage3D ( gl . TEXTURE_3D , 1 , gl . R32UI , maxT3DTexels , maxT3DTexels , depthLayers ) ;
202
+ // For the initial load, we'll load all of the data
203
+ gl . texSubImage3D ( gl . TEXTURE_3D , 0 , 0 , 0 , 0 , maxT3DTexels , maxT3DTexels , depthLayers , gl . RED_INTEGER , gl . UNSIGNED_INT , svdag . nodes ) ;
204
+
205
+ // gl.texImage3D(gl.TEXTURE_3D, 0, gl.R32UI, maxT3DTexels, maxT3DTexels, depthLayers, 0, gl.RED_INTEGER, gl.UNSIGNED_INT, svdag.nodes);
206
+ } else {
207
+ // For following chunks of data, upload one or more Z slices of the 3D texture
208
+ // TODO: Also take into account the yOffset - may need two texSubImage3D calls
209
+ const xOffset = 0 ;
210
+ const yOffset = 0 ; // Math.floor(chunkStart / maxT3DTexels) % maxT3DTexels;
211
+ const zOffset = Math . floor ( chunkStart / maxT3DTexelsPow2 ) ;
212
+
213
+ const chunkStartZ = zOffset ;
214
+ const chunkEndZ = Math . floor ( ( svdag . dataLoadedOffset / 4 ) / maxT3DTexelsPow2 ) ;
215
+
216
+ const updateWidth = maxT3DTexels ;
217
+ const updateHeight = maxT3DTexels ; // Math.ceil(svdag.dataLoadedOffset / maxT3DTexels) % maxT3DTexels - yOffset;
218
+ const updateDepth = ( chunkEndZ - chunkStartZ ) + 1 ;
219
+
220
+ const newNodes = svdag . nodes . slice (
221
+ yOffset * maxT3DTexels + chunkStartZ * maxT3DTexelsPow2 ,
222
+ maxT3DTexels + updateHeight * maxT3DTexels + chunkEndZ * maxT3DTexelsPow2 ,
223
+ ) ;
224
+
225
+ const progressPct = Math . round ( ( svdag . dataLoadedOffset / 4 ) / svdag . originalNodeLength * 100 ) ;
226
+ console . log ( `Uploading data to 3D texture (${ svdag . dataLoadedOffset / 4 } /${ svdag . originalNodeLength } [${ progressPct } %])...` ) ;
227
+ console . log ( '#layers' , depthLayers , 'start' , zOffset , 'chunkStart' , chunkStart , 'newNodes' , nNewNodes , 'end' , chunkEndZ , 'depth' , updateDepth )
228
+ gl . texSubImage3D ( gl . TEXTURE_3D , 0 , xOffset , yOffset , zOffset , updateWidth , updateHeight , updateDepth , gl . RED_INTEGER , gl . UNSIGNED_INT , newNodes ) ;
229
+ }
230
+ }
231
+
232
+ }
0 commit comments