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;
36in vec3 lightDir;
47
58uniform sampler3D volumeTex;
9+ uniform sampler2D dataConversionTex;
610uniform float dtScale;
711uniform float inScatFactor;
812uniform float finalGamma;
@@ -27,9 +31,18 @@ uniform float far;
2731
2832uniform 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+
3346vec2 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
5265float 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
93106void 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
0 commit comments