From ab8364f52e290ab5becd28637787f6ec029b7da4 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 28 Apr 2026 14:56:44 +0200 Subject: [PATCH 1/2] VolumeShader: Support perspective cameras. (#33493) --- examples/jsm/shaders/VolumeShader.js | 73 +++++++++++----------------- examples/webgl_texture3d.html | 2 +- 2 files changed, 30 insertions(+), 45 deletions(-) diff --git a/examples/jsm/shaders/VolumeShader.js b/examples/jsm/shaders/VolumeShader.js index 069f2d7ffeea1f..cf6a710fa1a41c 100644 --- a/examples/jsm/shaders/VolumeShader.js +++ b/examples/jsm/shaders/VolumeShader.js @@ -31,34 +31,23 @@ const VolumeRenderShader1 = { vertexShader: /* glsl */` - varying vec4 v_nearpos; - varying vec4 v_farpos; varying vec3 v_position; + varying vec3 v_cameraInObj; + varying vec3 v_viewDirInObj; void main() { - // Prepare transforms to map to "camera view". See also: - // https://threejs.org/docs/#api/renderers/webgl/WebGLProgram - mat4 viewtransformf = modelViewMatrix; - mat4 viewtransformi = inverse(modelViewMatrix); - - // Project local vertex coordinate to camera position. Then do a step - // backward (in cam coords) to the near clipping plane, and project back. Do - // the same for the far clipping plane. This gives us all the information we - // need to calculate the ray and truncate it to the viewing cone. vec4 position4 = vec4(position, 1.0); - vec4 pos_in_cam = viewtransformf * position4; - // Intersection of ray and near clipping plane (z = -1 in clip coords) - pos_in_cam.z = -pos_in_cam.w; - v_nearpos = viewtransformi * pos_in_cam; + v_position = position; - // Intersection of ray and far clipping plane (z = +1 in clip coords) - pos_in_cam.z = pos_in_cam.w; - v_farpos = viewtransformi * pos_in_cam; + // Express the camera position and view direction in the object's local + // space so the fragment shader can build the per-fragment view ray. + // For perspective cameras, rays converge at v_cameraInObj. + // For orthographic cameras, rays travel along v_viewDirInObj. + v_cameraInObj = (inverse(modelMatrix) * vec4(cameraPosition, 1.0)).xyz; + v_viewDirInObj = (inverse(modelViewMatrix) * vec4(0.0, 0.0, -1.0, 0.0)).xyz; - // Set varyings and output pos - v_position = position; - gl_Position = projectionMatrix * viewMatrix * modelMatrix * position4; + gl_Position = projectionMatrix * modelViewMatrix * position4; }`, fragmentShader: /* glsl */` @@ -75,8 +64,8 @@ const VolumeRenderShader1 = { uniform sampler2D u_cmdata; varying vec3 v_position; - varying vec4 v_nearpos; - varying vec4 v_farpos; + varying vec3 v_cameraInObj; + varying vec3 v_viewDirInObj; // The maximum distance through our rendering volume is sqrt(3). const int MAX_STEPS = 887; // 887 for 512^3, 1774 for 1024^3 @@ -96,33 +85,29 @@ const VolumeRenderShader1 = { void main() { - // Normalize clipping plane info - vec3 farpos = v_farpos.xyz / v_farpos.w; - vec3 nearpos = v_nearpos.xyz / v_nearpos.w; - - // Calculate unit vector pointing in the view direction through this fragment. - vec3 view_ray = normalize(nearpos.xyz - farpos.xyz); - - // Compute the (negative) distance to the front surface or near clipping plane. - // v_position is the back face of the cuboid, so the initial distance calculated in the dot - // product below is the distance from near clip plane to the back of the cuboid - float distance = dot(nearpos - v_position, view_ray); - distance = max(distance, min((-0.5 - v_position.x) / view_ray.x, - (u_size.x - 0.5 - v_position.x) / view_ray.x)); - distance = max(distance, min((-0.5 - v_position.y) / view_ray.y, - (u_size.y - 0.5 - v_position.y) / view_ray.y)); - distance = max(distance, min((-0.5 - v_position.z) / view_ray.z, - (u_size.z - 0.5 - v_position.z) / view_ray.z)); - - // Now we have the starting position on the front surface - vec3 front = v_position + view_ray * distance; + // Per-fragment ray direction in object space, pointing from the back + // face toward the camera. For perspective cameras the rays converge + // at the camera position; for orthographic cameras they are parallel + // to the view direction. + vec3 view_ray = isOrthographic + ? normalize(-v_viewDirInObj) + : normalize(v_cameraInObj - v_position); + + // Slab-based ray/AABB intersection: v_position lies on the back face + // of the cuboid, so stepping along view_ray traverses the volume and + // exits through the front face at t = distance. + vec3 t1 = (vec3(-0.5) - v_position) / view_ray; + vec3 t2 = (u_size - vec3(0.5) - v_position) / view_ray; + vec3 tmax = max(t1, t2); + float distance = min(min(tmax.x, tmax.y), tmax.z); // Decide how many steps to take - int nsteps = int(-distance / relative_step_size + 0.5); + int nsteps = int(distance / relative_step_size + 0.5); if ( nsteps < 1 ) discard; // Get starting location and step vector in texture coordinates + vec3 front = v_position + view_ray * distance; vec3 step = ((v_position - front) / u_size) / float(nsteps); vec3 start_loc = front / u_size; diff --git a/examples/webgl_texture3d.html b/examples/webgl_texture3d.html index 983d55632c511c..520b993b3ab752 100644 --- a/examples/webgl_texture3d.html +++ b/examples/webgl_texture3d.html @@ -50,7 +50,7 @@ renderer.setSize( window.innerWidth, window.innerHeight ); document.body.appendChild( renderer.domElement ); - // Create camera (The volume renderer does not work very well with perspective yet) + // Create camera const h = 512; // frustum height const aspect = window.innerWidth / window.innerHeight; camera = new THREE.OrthographicCamera( - h * aspect / 2, h * aspect / 2, h / 2, - h / 2, 1, 1000 ); From db337c036ccfce3d94829c628f9738a8e7de3323 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Wed, 29 Apr 2026 01:15:05 +0900 Subject: [PATCH 2/2] Examples: Fix final alpha issues (#33496) --- examples/webgpu_lines_fat.html | 4 ++-- examples/webgpu_lines_fat_wireframe.html | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/webgpu_lines_fat.html b/examples/webgpu_lines_fat.html index 114f25f7e52029..7290e02e229c5c 100644 --- a/examples/webgpu_lines_fat.html +++ b/examples/webgpu_lines_fat.html @@ -110,7 +110,7 @@ linewidth: 5, // in world units with size attenuation, pixels otherwise vertexColors: true, dashed: false, - alphaToCoverage: true, + alphaToCoverage: false, } ); @@ -209,7 +209,7 @@ 'line type': 0, 'world units': false, 'width': 5, - 'alphaToCoverage': true, + 'alphaToCoverage': false, 'dashed': false, 'dash offset': 0, 'dash scale': 1, diff --git a/examples/webgpu_lines_fat_wireframe.html b/examples/webgpu_lines_fat_wireframe.html index 857a83458784fc..5480f16b030690 100644 --- a/examples/webgpu_lines_fat_wireframe.html +++ b/examples/webgpu_lines_fat_wireframe.html @@ -139,8 +139,6 @@ // main scene - renderer.setClearColor( 0x000000, 0 ); - renderer.setViewport( 0, 0, window.innerWidth, window.innerHeight ); renderer.autoClear = true;