Skip to content

Commit 10e38a4

Browse files
authored
Merge pull request #750 from gkjohnson/webgpu/mobile-2
WebGPUPathTracer: Use custom float texture filter for mobile
2 parents d915f7c + e99e5d9 commit 10e38a4

10 files changed

Lines changed: 100 additions & 30 deletions

package-lock.json

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"@types/node": "^24.3.1",
4444
"@types/three": "^0.184.0",
4545
"@typescript-eslint/eslint-plugin": "^8.40.0",
46+
"@vitejs/plugin-basic-ssl": "^2.3.0",
4647
"canvas-capture": "^2.0.5",
4748
"eslint": "^8.56.0",
4849
"eslint-config-mdcs": "^5.0.0",

src/textures/ProceduralEquirectTexture.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ import {
22
ClampToEdgeWrapping,
33
Color,
44
DataTexture,
5+
DataUtils,
56
EquirectangularReflectionMapping,
7+
HalfFloatType,
68
LinearFilter,
79
RepeatWrapping,
810
RGBAFormat,
911
Spherical,
1012
Vector2,
11-
FloatType
1213
} from 'three';
1314

1415
const _uv = new Vector2();
@@ -20,8 +21,8 @@ export class ProceduralEquirectTexture extends DataTexture {
2021
constructor( width = 512, height = 512 ) {
2122

2223
super(
23-
new Float32Array( width * height * 4 ),
24-
width, height, RGBAFormat, FloatType, EquirectangularReflectionMapping,
24+
new Uint16Array( width * height * 4 ),
25+
width, height, RGBAFormat, HalfFloatType, EquirectangularReflectionMapping,
2526
RepeatWrapping, ClampToEdgeWrapping, LinearFilter, LinearFilter,
2627
);
2728

@@ -53,10 +54,10 @@ export class ProceduralEquirectTexture extends DataTexture {
5354

5455
const i = y * width + x;
5556
const i4 = 4 * i;
56-
data[ i4 + 0 ] = ( _color.r );
57-
data[ i4 + 1 ] = ( _color.g );
58-
data[ i4 + 2 ] = ( _color.b );
59-
data[ i4 + 3 ] = ( 1.0 );
57+
data[ i4 + 0 ] = DataUtils.toHalfFloat( _color.r );
58+
data[ i4 + 1 ] = DataUtils.toHalfFloat( _color.g );
59+
data[ i4 + 2 ] = DataUtils.toHalfFloat( _color.b );
60+
data[ i4 + 3 ] = DataUtils.toHalfFloat( 1.0 );
6061

6162
}
6263

src/webgpu/PathTracerBackend.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ColorManagement, FloatType, LinearFilter, RGBAFormat } from 'three';
1+
import { ColorManagement, FloatType, RGBAFormat } from 'three';
22
import { RedIntegerFormat, StorageTexture, UnsignedIntType } from 'three/webgpu';
33
import { ZeroOutKernel } from './compute/ZeroOutKernel.js';
44
import { GltfCompliantMaterial } from './materials/GltfCompliantMaterial.js';
@@ -18,15 +18,13 @@ export class PathTracerBackend {
1818
this.outputTarget = new StorageTexture( 1, 1, );
1919
this.outputTarget.format = RGBAFormat;
2020
this.outputTarget.type = FloatType;
21-
this.outputTarget.magFilter = LinearFilter;
2221
this.outputTarget.colorSpace = ColorManagement.workingColorSpace;
2322
this.outputTarget.name = 'Output #0';
2423
this.outputTarget.generateMipmaps = false;
2524

2625
this.prevOutputTarget = new StorageTexture( 1, 1, );
2726
this.prevOutputTarget.format = RGBAFormat;
2827
this.prevOutputTarget.type = FloatType;
29-
this.prevOutputTarget.magFilter = LinearFilter;
3028
this.prevOutputTarget.colorSpace = ColorManagement.workingColorSpace;
3129
this.prevOutputTarget.name = 'Output #1';
3230
this.prevOutputTarget.generateMipmaps = false;

src/webgpu/RenderTarget2DArray.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
MeshBasicMaterial,
23
RenderTarget,
34
RGBAFormat,
45
UnsignedByteType,
@@ -7,7 +8,6 @@ import {
78
NoToneMapping,
89
QuadMesh,
910
NoBlending,
10-
MeshBasicMaterial,
1111
} from 'three/webgpu';
1212

1313
function getTextureHash( texture ) {

src/webgpu/WebGPUPathTracer.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,12 @@ export class WebGPUPathTracer {
7070
this._resetTime = - 1;
7171
this._fadeState = 0;
7272
this._size = new Vector2();
73+
this._blitQuad = new FullScreenQuad( new RenderToScreenNodeMaterial() );
74+
75+
// avoid mipmap gen on copy, not always supported with float type
7376
this._lowResTarget = new StorageTexture( 1, 1 );
7477
this._lowResTarget.type = FloatType;
75-
this._blitQuad = new FullScreenQuad( new RenderToScreenNodeMaterial() );
78+
this._lowResTarget.generateMipmaps = false;
7679

7780
// options
7881
this.minSamples = 1;

src/webgpu/materials/GltfCompliantMaterial.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { wgslFn, texture, textureStore, globalId } from 'three/tsl';
2-
import { StorageTexture, RedFormat, LinearFilter, FloatType, TextureLoader } from 'three/webgpu';
2+
import { StorageTexture, RedFormat, LinearFilter, TextureLoader, HalfFloatType } from 'three/webgpu';
33
import { wgslTagFn } from '../lib/nodes/WGSLTagFnNode';
44
import { PathtracingMaterial } from './PathtracingMaterial';
55
import { specularBrdfFunc, diffuseBrdfFunc, fresnelMixFunc, conductorFresnelFunc, albedoIntegralMetallic } from '../nodes/material.wgsl';
@@ -29,6 +29,7 @@ export class GltfCompliantMaterial extends PathtracingMaterial {
2929
if ( calculateTurquinTexture ) {
3030

3131
this.turquinTexture = new StorageTexture( 32, 32 );
32+
this.turquinTexture.type = HalfFloatType;
3233

3334
} else {
3435

@@ -38,7 +39,6 @@ export class GltfCompliantMaterial extends PathtracingMaterial {
3839
}
3940

4041
this.turquinTexture.format = RedFormat;
41-
this.turquinTexture.type = FloatType;
4242
this.turquinTexture.minFilter = LinearFilter;
4343
this.turquinTexture.magFilter = LinearFilter;
4444

src/webgpu/materials/RenderToScreenMaterial.js

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,35 @@
11
import { MeshBasicNodeMaterial, NoToneMapping, StorageTexture } from 'three/webgpu';
22
import { uv, varying, texture, vec4, toneMapping, uniform, wgslFn } from 'three/tsl';
3+
import { wgslTagFn } from '../lib/nodes/WGSLTagFnNode.js';
4+
5+
// TODO: we could fall back to hardware-based filtering if available but it has to be specifically
6+
// requested and available on the renderer which we don't have access to immediately. It's possible this
7+
// can be detected during build time via a custom node to adjust the sample approach used?
8+
const sampleTexelFn = wgslFn( /* wgsl */`
9+
fn sampleTexel( tex: texture_2d<f32>, coord: vec2f ) -> vec4f {
10+
11+
// Manual bilinear filtering using textureLoad to support filterable float32 textures
12+
// on all devices
13+
let size = vec2f( textureDimensions( tex, 0 ) );
14+
let pxCoord = coord * size - 0.5;
15+
let px = vec2i( floor( pxCoord ) );
16+
let fr = fract( pxCoord );
17+
18+
// get the four sibling samples
19+
let s00 = textureLoad( tex, clamp( px, vec2i( 0 ), vec2i( size ) - 1 ), 0 );
20+
let s10 = textureLoad( tex, clamp( px + vec2i( 1, 0 ), vec2i( 0 ), vec2i( size ) - 1 ), 0 );
21+
let s01 = textureLoad( tex, clamp( px + vec2i( 0, 1 ), vec2i( 0 ), vec2i( size ) - 1 ), 0 );
22+
let s11 = textureLoad( tex, clamp( px + vec2i( 1, 1 ), vec2i( 0 ), vec2i( size ) - 1 ), 0 );
23+
24+
// interpolate on the x axis
25+
let y0 = mix( s00, s10, fr.x );
26+
let y1 = mix( s01, s11, fr.x );
27+
28+
// then y
29+
return mix( y0, y1, fr.y );
30+
31+
}
32+
` );
333

434
// Material to apply tone mapping _before_ applying alpha blending
535
export class RenderToScreenNodeMaterial extends MeshBasicNodeMaterial {
@@ -68,28 +98,41 @@ export class RenderToScreenNodeMaterial extends MeshBasicNodeMaterial {
6898

6999
super();
70100

71-
const texNode = texture( new StorageTexture(), varying( uv() ) );
101+
const texNode = texture( new StorageTexture() );
72102
this._texNode = texNode;
73103

74-
const fromTexNode = texture( new StorageTexture(), varying( uv() ) );
104+
const fromTexNode = texture( new StorageTexture() );
75105
this._fromTexNode = fromTexNode;
76106

107+
const texUV = varying( uv() );
108+
77109
const transitionUniform = uniform( 1.0 );
78110
this._transitionUniform = transitionUniform;
79111

80-
const fadedColor = wgslFn( /* wgsl */`
81-
fn fade( col0: vec4f, col1: vec4f, transition: f32 ) -> vec4f {
112+
// NOTE: varyings cannot be referenced directly and must be passed as arguments
113+
const getFadedColorFn = wgslTagFn/* wgsl */`
114+
fn fade( uv: vec2f ) -> vec4f {
115+
116+
if ( ${ transitionUniform } <= 0.0 ) {
117+
118+
return ${ sampleTexelFn }( ${ fromTexNode }, uv );
119+
120+
} else if ( ${ transitionUniform } >= 1.0 ) {
121+
122+
return ${ sampleTexelFn }( ${ texNode }, uv );
123+
124+
} else {
125+
126+
let col0 = ${ sampleTexelFn }( ${ fromTexNode }, uv );
127+
let col1 = ${ sampleTexelFn }( ${ texNode }, uv );
128+
return mix( col0, col1, ${ transitionUniform } );
82129
83-
return mix( col0, col1, transition );
130+
}
84131
85132
}
86-
` )( {
87-
col0: fromTexNode,
88-
col1: texNode,
89-
transition: transitionUniform,
90-
} );
133+
`;
91134

92-
const toneMappingNode = toneMapping( NoToneMapping, 1.0, fadedColor );
135+
const toneMappingNode = toneMapping( NoToneMapping, 1.0, getFadedColorFn( texUV ) );
93136
this._toneMapping = toneMappingNode;
94137

95138
// apply alpha _after_ applying tone mapping

src/webgpu/nodes/material.wgsl.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ export const conductorFresnelFunc = ( turquinTexture ) => wgslFn( /* wgsl */ `
358358
let ss = bsdf * schlickFresnelVec( abs( VdotH ), f0, vec3f( 1 ) );
359359
360360
let uv = vec2( NdotV, sqrt( alpha ) );
361-
let energySs = max( textureSampleLevel( turquinTexture, turquinTexture_sampler, uv, 0 ).r, 1e-5);
361+
let energySs = max( textureSampleLevel( turquinTexture, turquinTexture_sampler, uv, 0 ).r, 1e-5 );
362362
363363
return ss * ( 1.0 + f0 * ( 1.0 - energySs ) / energySs );
364364
@@ -371,7 +371,7 @@ export const conductorFresnelFunc = ( turquinTexture ) => wgslFn( /* wgsl */ `
371371
export const albedoIntegralMetallic = wgslFn( /* wgsl */ `
372372
373373
fn albedo(
374-
texture: texture_storage_2d<r32float, write>,
374+
texture: texture_storage_2d<r16float, write>,
375375
376376
globalId: vec3u,
377377
) -> void {
@@ -413,7 +413,7 @@ export const albedoIntegralMetallic = wgslFn( /* wgsl */ `
413413
414414
result /= f32( INTEGRATION_SAMPLES );
415415
416-
textureStore(texture, globalId.xy, vec4( result ));
416+
textureStore( texture, globalId.xy, vec4( result ) );
417417
418418
}
419419

vite.config.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { searchForWorkspaceRoot } from 'vite';
2+
import basicSsl from '@vitejs/plugin-basic-ssl';
23
import fs from 'fs';
34

4-
export default {
5+
export default ( { mode } ) => ( {
6+
7+
plugins: mode === 'ssl' ? [ basicSsl() ] : [],
58

69
root: './example/',
710
base: '',
@@ -26,4 +29,4 @@ export default {
2629
optimizeDeps: {
2730
exclude: [ 'three-mesh-bvh' ],
2831
},
29-
};
32+
} );

0 commit comments

Comments
 (0)