Skip to content

Commit b57a9d4

Browse files
committed
precompute textyres and calculations for better performance (2x-4x faster)
1 parent 40dc8d9 commit b57a9d4

File tree

5 files changed

+107
-24
lines changed

5 files changed

+107
-24
lines changed

src/lib/shaders/surface_heatmap.frag

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ precision highp float;
44
uniform float uScaleFactor; // Scaling factor to adjust color sensitivity
55
varying vec2 vUv;
66

7+
// Constant for normalization (faster than division)
8+
const float INV_255 = 0.00392156862745;
9+
710
void main() {
811
float value = texture2D(volumeTex, vUv).r;
9-
// Normalize the value to the expected range of your data
10-
value = value / 255.0;
12+
// Normalize the value to the expected range of your data (multiply is faster than divide)
13+
value = value * INV_255;
1114
// Apply the scaling factor
1215
value = clamp(value * uScaleFactor, 0.0, 1.0);
1316

src/lib/shaders/volume.frag

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
precision highp float;
2-
in vec3 rayDirUnnorm;
1+
// Default to mediump for performance, use highp only where needed for precision
2+
precision mediump float;
3+
4+
// Need highp for position accumulators to avoid precision issues
5+
in highp vec3 rayDirUnnorm;
36
in vec3 lightDir;
47

58
uniform sampler3D volumeTex;
9+
uniform sampler2D dataConversionTex;
610
uniform float dtScale;
711
uniform float inScatFactor;
812
uniform float finalGamma;
@@ -27,9 +31,18 @@ uniform float far;
2731

2832
uniform float uTransparency;
2933

34+
// Constants moved to file level for performance
35+
const float PI = 3.14159265358979323846;
36+
3037
// Three.js adds built-in uniforms and attributes:
3138
// https://threejs.org/docs/#api/en/renderers/webgl/WebGLProgram
3239
// uniform vec3 cameraPosition;
40+
41+
// Fast interleaved gradient noise for dithering (much faster than sin/fract)
42+
float interleavedGradientNoise(vec2 coord) {
43+
return fract(52.9829189 * fract(0.06711056 * coord.x + 0.00583715 * coord.y));
44+
}
45+
3346
vec2 intersectBox(vec3 orig,vec3 dir){
3447
vec3 boxMin=vec3(-.5)*boxSize;
3548
vec3 boxMax=vec3(.5)*boxSize;
@@ -50,7 +63,6 @@ float cameraDistanceFromDepth(float depth){
5063
}
5164

5265
float phaseHG(float cosTheta, float g) {
53-
float PI=3.14159265358979323846;
5466
float denom=1.0+g*g+2.0*g*cosTheta;
5567
return (1.-g*g)/(4.0*PI*denom*sqrt(denom));
5668
}
@@ -72,7 +84,8 @@ float getShadow(vec3 pos, vec3 step, vec2 tbounds, float tstep, float stepLength
7284
samplePos += (n * step);
7385

7486
float v=texture(volumeTex,samplePos).r;
75-
float ql=(v==0.0)?0.:(dataScale*pow(dataEpsilon/dataScale,1.0-v)-dataEpsilon);
87+
// Use lookup texture for fast conversion (replaces expensive pow calculation)
88+
float ql=texture(dataConversionTex,vec2(v,0.5)).r;
7689
if(ql==0.)
7790
{
7891
n*=1.5;
@@ -91,11 +104,11 @@ float getShadow(vec3 pos, vec3 step, vec2 tbounds, float tstep, float stepLength
91104
}
92105

93106
void main(void){
94-
vec3 rayDir=normalize(rayDirUnnorm);
107+
highp vec3 rayDir=normalize(rayDirUnnorm);
95108

96109
// Reflect z-axis to make the top level face the viewer
97110
//rayDir.z=-rayDir.z;
98-
vec3 cameraPositionAdjusted=cameraPosition;
111+
highp vec3 cameraPositionAdjusted=cameraPosition;
99112
//cameraPositionAdjusted.z=-cameraPosition.z;
100113

101114
// Find the part of the ray that intersects the box, where this part is
@@ -129,20 +142,20 @@ void main(void){
129142
}
130143

131144
// Ray starting point, in the "real" space where the box may not be a cube.
132-
vec3 p=cameraPositionAdjusted+tBox.x*rayDir;
145+
highp vec3 p=cameraPositionAdjusted+tBox.x*rayDir;
133146

134-
// Dither to reduce banding (aliasing).
147+
// Dither to reduce banding (aliasing) using fast gradient noise
135148
// https://www.marcusbannerman.co.uk/articles/VolumeRendering.html
136-
float random=fract(sin(gl_FragCoord.x*12.9898+gl_FragCoord.y*78.233)*43758.5453);
149+
float random=interleavedGradientNoise(gl_FragCoord.xy);
137150
random*=5.;
138151
p+=random*dt*rayDir;
139152

140153
// Ray starting point, and change in ray point with each step, for the space where
141154
// the box has been warped to a cube, for accessing the cubical data texture.
142155
// The vec3(0.5) is necessary because rays are defined in the space where the box is
143156
// centered at the origin, but texture look-ups have the origin at a box corner.
144-
vec3 pSized=p/boxSize+vec3(.5);
145-
vec3 dPSized=(rayDir*dt)/boxSize;
157+
highp vec3 pSized=p/boxSize+vec3(.5);
158+
highp vec3 dPSized=(rayDir*dt)/boxSize;
146159
vec3 dPShadow=(lightDir*dtS)/boxSize;
147160

148161
// Most browsers do not need this initialization, but add it to be safe.
@@ -162,26 +175,39 @@ void main(void){
162175
float dz=length(distvec*dPShadow);
163176
float transmittance_threshold=0.01;
164177
vec3 dg=vec3(1)/vec3(volumeTexSize);
165-
for(float t=tBox.x;t<tBox.y;t+=dt){
178+
179+
// Hoist constant calculations out of loop for performance
180+
float cosTheta=dot(rayDir,-lightDir);
181+
float phase=phaseHG(cosTheta,gHG);
182+
183+
// Adaptive step size for empty space skipping
184+
float stepMultiplier=1.0;
185+
186+
for(float t=tBox.x;t<tBox.y;t+=dt*stepMultiplier){
166187

167188
float v=texture(volumeTex,pSized - displacement).r;
168-
float ql=(v==0.0)?0.:(dataScale*pow(dataEpsilon/dataScale,1.0-v)-dataEpsilon);
189+
// Use lookup texture for fast conversion (replaces expensive pow calculation)
190+
float ql=texture(dataConversionTex,vec2(v,0.5)).r;
169191
if(ql==0.0)
170192
{
171-
pSized+=dPSized;
193+
// Increase step size in empty space for faster traversal
194+
stepMultiplier=2.0;
195+
pSized+=dPSized*stepMultiplier;
172196
continue;
173197
}
198+
199+
// Reset to normal step size when we hit data
200+
stepMultiplier=1.0;
174201
float height=bottomHeight+(0.5-pSized.z)*distvec.z;
175202

176203
// extinction parameter
177204
float ext=0.1*extinction(ql,height);
178205

179-
// Henyey-Greenstein phase function
180-
float cosTheta=dot(rayDir,-lightDir);
181-
float phase=phaseHG(cosTheta,gHG);
182-
183-
// Shadowing
184-
float shadow=ql>0.?getShadow(pSized,dPShadow,tBoxShadow,dt,dz,distvec.z,dg):1.0;
206+
// Shadowing - skip expensive calculation when transmittance is very low (won't affect result much)
207+
float shadow=1.0;
208+
if(ql>0.0 && transmittance>0.1) {
209+
shadow=getShadow(pSized,dPShadow,tBoxShadow,dt,dz,distvec.z,dg);
210+
}
185211
//float shadow=1.0;
186212

187213
// Ambient Lighting: linear approx
@@ -207,7 +233,7 @@ void main(void){
207233
break;
208234
}
209235

210-
// Move to the next point along the ray.
236+
// Move to the next point along the ray (normal step when data is present)
211237
pSized+=dPSized;
212238
}
213239

src/lib/shaders/volume_transfer.frag

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ uniform float uTransparency;
2828
// https://threejs.org/docs/#api/en/renderers/webgl/WebGLProgram
2929
// uniform vec3 cameraPosition;
3030

31+
// Fast interleaved gradient noise for dithering (much faster than sin/fract)
32+
float interleavedGradientNoise(vec2 coord) {
33+
return fract(52.9829189 * fract(0.06711056 * coord.x + 0.00583715 * coord.y));
34+
}
35+
3136
vec2 intersectBox(vec3 orig,vec3 dir){
3237
vec3 boxMin=vec3(-.5)*boxSize;
3338
vec3 boxMax=vec3(.5)*boxSize;
@@ -98,7 +103,7 @@ void main(void){
98103
vec3 illumination=vec3(0.,0.,0.);
99104
float transmittance=1.;
100105
float transmittance_threshold=0.05;
101-
vec3 random=fract(sin(gl_FragCoord.x*12.9898+gl_FragCoord.y*78.233)*43758.5453)*dt*rayDir/8.0;
106+
vec3 random=interleavedGradientNoise(gl_FragCoord.xy)*dt*rayDir/8.0;
102107
for(float t=tBox.x;t<tBox.y;t+=dt){
103108
// look 8 steps ahead
104109
float value=texture(coarseVolumeTex, pSized - displacement).r;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import * as THREE from 'three';
2+
3+
/**
4+
* Creates a lookup texture for fast data value conversion
5+
* Replaces the expensive computation: dataScale * pow(dataEpsilon/dataScale, 1.0-v) - dataEpsilon
6+
*
7+
* @param dataScale - The data scale factor
8+
* @param dataEpsilon - The epsilon value for data conversion
9+
* @returns A 1D DataTexture containing precomputed conversion values
10+
*/
11+
export function makeDataConversionTex(dataScale: number, dataEpsilon: number) {
12+
const size = 256; // One entry for each possible uint8 value
13+
const data = new Float32Array(size);
14+
15+
for (let i = 0; i < size; i++) {
16+
const v = i / 255.0; // Normalized value [0, 1]
17+
18+
// Original calculation from shader:
19+
// float ql = (v == 0.0) ? 0.0 : (dataScale * pow(dataEpsilon/dataScale, 1.0-v) - dataEpsilon);
20+
if (v === 0.0) {
21+
data[i] = 0.0;
22+
} else {
23+
data[i] = dataScale * Math.pow(dataEpsilon / dataScale, 1.0 - v) - dataEpsilon;
24+
}
25+
}
26+
27+
const texture = new THREE.DataTexture(
28+
data,
29+
size,
30+
1,
31+
THREE.RedFormat,
32+
THREE.FloatType
33+
);
34+
35+
texture.minFilter = THREE.LinearFilter;
36+
texture.magFilter = THREE.LinearFilter;
37+
texture.wrapS = THREE.ClampToEdgeWrapping;
38+
texture.wrapT = THREE.ClampToEdgeWrapping;
39+
texture.needsUpdate = true;
40+
41+
return texture;
42+
}

src/routes/viewer/sceneSetup/initMaterial.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@ import fragmentShaderSurfaceHeatMap from '$lib/shaders/surface_heatmap.frag'; /
99

1010
import { voxelSizes, boxSizes } from '../stores/allSlices.store';
1111
import { makeRainTransferTex } from '$lib/utils/makeRainTransferTex';
12+
import { makeDataConversionTex } from '$lib/utils/makeDataConversionTex';
1213

1314
import { cameraFar, cameraNear } from './create3DScene';
1415
import { cloudLayerSettings, rainLayerSettings, temperatureLayerSettings } from '../stores/viewer.store';
1516

1617
// Run only once at mount
1718
const transferTexture = makeRainTransferTex();
1819

20+
// Data conversion parameters from updateMaterial.ts
21+
const qlScale = 0.00446;
22+
const dataEpsilon = 1e-10;
23+
const qlConversionTexture = makeDataConversionTex(qlScale, dataEpsilon);
24+
1925
const sunLightDir = new THREE.Vector3(0.0, 0.5, 0.5);
2026
const sunLightColor = new THREE.Color(0.99, 0.83, 0.62);
2127
const sunLight = new THREE.DirectionalLight(sunLightColor.getHex(), 1.0);
@@ -48,6 +54,7 @@ export function initMaterial({ variable }): THREE.Material {
4854
uTransparency: { value: get(cloudLayerSettings).opacity / 100 },
4955
boxSize: new THREE.Uniform(get(boxSizes)[variable]),
5056
volumeTex: new THREE.Uniform(null),
57+
dataConversionTex: new THREE.Uniform(qlConversionTexture),
5158
voxelSize: new THREE.Uniform(get(voxelSizes)[variable]),
5259
sunLightDir: new THREE.Uniform(sunLight.position),
5360
sunLightColor: new THREE.Uniform(lightColorV),

0 commit comments

Comments
 (0)