|
16 | 16 | </div> |
17 | 17 |
|
18 | 18 | <small> |
19 | | - Compute ping/pong texture using GPU. |
| 19 | + Compute ping/pong texture using GPU (pure TSL). |
20 | 20 | </small> |
21 | 21 | </div> |
22 | 22 |
|
|
34 | 34 | <script type="module"> |
35 | 35 |
|
36 | 36 | import * as THREE from 'three/webgpu'; |
37 | | - import { storageTexture, wgslFn, code, instanceIndex, uniform, NodeAccess } from 'three/tsl'; |
| 37 | + import { storageTexture, textureStore, Fn, instanceIndex, uniform, float, vec2, vec4, uvec2, ivec2, int, NodeAccess } from 'three/tsl'; |
38 | 38 |
|
39 | 39 | import WebGPU from 'three/addons/capabilities/WebGPU.js'; |
40 | 40 |
|
|
45 | 45 | let phase = true; |
46 | 46 | let lastUpdate = - 1; |
47 | 47 |
|
| 48 | + const width = 512, height = 512; |
| 49 | + |
48 | 50 | const seed = uniform( new THREE.Vector2() ); |
49 | 51 |
|
50 | 52 | init(); |
|
68 | 70 | // texture |
69 | 71 |
|
70 | 72 | const hdr = true; |
71 | | - const width = 512, height = 512; |
72 | 73 |
|
73 | 74 | pingTexture = new THREE.StorageTexture( width, height ); |
74 | 75 | pongTexture = new THREE.StorageTexture( width, height ); |
|
80 | 81 |
|
81 | 82 | } |
82 | 83 |
|
83 | | - const wgslFormat = hdr ? 'rgba16float' : 'rgba8unorm'; |
84 | | - |
85 | | - const readPing = storageTexture( pingTexture ).setAccess( NodeAccess.READ_ONLY ); |
86 | | - const writePing = storageTexture( pingTexture ).setAccess( NodeAccess.WRITE_ONLY ); |
87 | | - const readPong = storageTexture( pongTexture ).setAccess( NodeAccess.READ_ONLY ); |
88 | | - const writePong = storageTexture( pongTexture ).setAccess( NodeAccess.WRITE_ONLY ); |
89 | | - |
90 | | - // compute init |
91 | | - |
92 | | - const rand2 = code( ` |
93 | | - fn rand2( n: vec2f ) -> f32 { |
94 | | -
|
95 | | - return fract( sin( dot( n, vec2f( 12.9898, 4.1414 ) ) ) * 43758.5453 ); |
| 84 | + const rand2 = Fn( ( [ n ] ) => { |
96 | 85 |
|
97 | | - } |
| 86 | + return n.dot( vec2( 12.9898, 4.1414 ) ).sin().mul( 43758.5453 ).fract(); |
98 | 87 |
|
99 | | - fn blur( image : texture_storage_2d<${wgslFormat}, read>, uv : vec2i ) -> vec4f { |
| 88 | + } ); |
100 | 89 |
|
101 | | - var color = vec4f( 0.0 ); |
102 | | -
|
103 | | - color += textureLoad( image, uv + vec2i( - 1, 1 )); |
104 | | - color += textureLoad( image, uv + vec2i( - 1, - 1 )); |
105 | | - color += textureLoad( image, uv + vec2i( 0, 0 )); |
106 | | - color += textureLoad( image, uv + vec2i( 1, - 1 )); |
107 | | - color += textureLoad( image, uv + vec2i( 1, 1 )); |
108 | | -
|
109 | | - return color / 5.0; |
110 | | - } |
111 | | -
|
112 | | - fn getUV( posX: u32, posY: u32 ) -> vec2f { |
113 | | -
|
114 | | - let uv = vec2f( f32( posX ) / ${ width }.0, f32( posY ) / ${ height }.0 ); |
| 90 | + // Create storage texture nodes with proper access |
| 91 | + const writePing = storageTexture( pingTexture ).setAccess( NodeAccess.WRITE_ONLY ); |
| 92 | + const readPing = storageTexture( pingTexture ).setAccess( NodeAccess.READ_ONLY ); |
| 93 | + const writePong = storageTexture( pongTexture ).setAccess( NodeAccess.WRITE_ONLY ); |
| 94 | + const readPong = storageTexture( pongTexture ).setAccess( NodeAccess.READ_ONLY ); |
115 | 95 |
|
116 | | - return uv; |
| 96 | + const computeInit = Fn( () => { |
117 | 97 |
|
118 | | - } |
119 | | - ` ); |
| 98 | + const posX = instanceIndex.mod( width ); |
| 99 | + const posY = instanceIndex.div( width ); |
| 100 | + const indexUV = uvec2( posX, posY ); |
| 101 | + const uv = vec2( float( posX ).div( width ), float( posY ).div( height ) ); |
120 | 102 |
|
121 | | - const computeInitWGSL = wgslFn( ` |
122 | | - fn computeInitWGSL( writeTex: texture_storage_2d<${ wgslFormat }, write>, index: u32, seed: vec2f ) -> void { |
| 103 | + const r = rand2( uv.add( seed.mul( 100 ) ) ).sub( rand2( uv.add( seed.mul( 300 ) ) ) ); |
| 104 | + const g = rand2( uv.add( seed.mul( 200 ) ) ).sub( rand2( uv.add( seed.mul( 300 ) ) ) ); |
| 105 | + const b = rand2( uv.add( seed.mul( 200 ) ) ).sub( rand2( uv.add( seed.mul( 100 ) ) ) ); |
123 | 106 |
|
124 | | - let posX = index % ${ width }; |
125 | | - let posY = index / ${ width }; |
126 | | - let indexUV = vec2u( posX, posY ); |
127 | | - let uv = getUV( posX, posY ); |
| 107 | + textureStore( writePing, indexUV, vec4( r, g, b, 1 ) ); |
128 | 108 |
|
129 | | - let r = rand2( uv + seed * 100 ) - rand2( uv + seed * 300 ); |
130 | | - let g = rand2( uv + seed * 200 ) - rand2( uv + seed * 300 ); |
131 | | - let b = rand2( uv + seed * 200 ) - rand2( uv + seed * 100 ); |
| 109 | + } ); |
132 | 110 |
|
133 | | - textureStore( writeTex, indexUV, vec4( r, g, b, 1 ) ); |
| 111 | + computeInitNode = computeInit().compute( width * height ); |
134 | 112 |
|
135 | | - } |
136 | | - `, [ rand2 ] ); |
| 113 | + // compute ping-pong: blur function using .load() for textureLoad |
| 114 | + const blur = Fn( ( [ readTex, uv ] ) => { |
137 | 115 |
|
138 | | - computeInitNode = computeInitWGSL( { writeTex: storageTexture( pingTexture ), index: instanceIndex, seed } ).compute( width * height ); |
| 116 | + const c0 = readTex.load( uv.add( ivec2( - 1, 1 ) ) ); |
| 117 | + const c1 = readTex.load( uv.add( ivec2( - 1, - 1 ) ) ); |
| 118 | + const c2 = readTex.load( uv.add( ivec2( 0, 0 ) ) ); |
| 119 | + const c3 = readTex.load( uv.add( ivec2( 1, - 1 ) ) ); |
| 120 | + const c4 = readTex.load( uv.add( ivec2( 1, 1 ) ) ); |
139 | 121 |
|
140 | | - // compute loop |
| 122 | + return c0.add( c1 ).add( c2 ).add( c3 ).add( c4 ).div( 5.0 ); |
141 | 123 |
|
142 | | - const computePingPongWGSL = wgslFn( ` |
143 | | - fn computePingPongWGSL( readTex: texture_storage_2d<${wgslFormat}, read>, writeTex: texture_storage_2d<${ wgslFormat }, write>, index: u32 ) -> void { |
| 124 | + } ); |
144 | 125 |
|
145 | | - let posX = index % ${ width }; |
146 | | - let posY = index / ${ width }; |
147 | | - let indexUV = vec2i( i32( posX ), i32( posY ) ); |
| 126 | + // compute loop: read from one texture, blur, write to another |
| 127 | + const computePingPong = Fn( ( [ readTex, writeTex ] ) => { |
148 | 128 |
|
149 | | - let color = blur( readTex, indexUV ).rgb; |
| 129 | + const posX = instanceIndex.mod( width ); |
| 130 | + const posY = instanceIndex.div( width ); |
| 131 | + const indexUV = ivec2( int( posX ), int( posY ) ); |
150 | 132 |
|
151 | | - textureStore( writeTex, indexUV, vec4f( color * 1.05, 1 ) ); |
| 133 | + const color = blur( readTex, indexUV ); |
152 | 134 |
|
153 | | - } |
154 | | - `, [ rand2 ] ); |
| 135 | + textureStore( writeTex, indexUV, vec4( color.rgb.mul( 1.05 ), 1 ) ); |
155 | 136 |
|
156 | | - // |
| 137 | + } ); |
157 | 138 |
|
158 | | - computeToPong = computePingPongWGSL( { readTex: readPing, writeTex: writePong, index: instanceIndex } ).compute( width * height ); |
159 | | - computeToPing = computePingPongWGSL( { readTex: readPong, writeTex: writePing, index: instanceIndex } ).compute( width * height ); |
| 139 | + computeToPong = computePingPong( readPing, writePong ).compute( width * height ); |
| 140 | + computeToPing = computePingPong( readPong, writePing ).compute( width * height ); |
160 | 141 |
|
161 | 142 | // |
162 | 143 |
|
|
0 commit comments