diff --git a/docs/api/en/helpers/RaycasterHelper.html b/docs/api/en/helpers/RaycasterHelper.html
new file mode 100644
index 00000000000000..f3640eb52224ae
--- /dev/null
+++ b/docs/api/en/helpers/RaycasterHelper.html
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+ [page:Object3D] →
+
+ [name]
+
+
+ A 3D object for visualizing a [page:Raycaster Raycaster].
+
+
+ Code Example
+
+
+ const origin = new THREE.Vector3( - 4, 0, 0 );
+ const direction = new THREE.Vector3( 1, 0, 0 );
+ const raycaster = new THREE.Raycaster( origin, direction );
+ raycaster.near = 1;
+ raycaster.far = 8;
+
+ const raycasterHelper = new THREE.RaycasterHelper( raycaster );
+ scene.add( raycasterHelper );
+
+ function render() {
+
+ const intersects = raycaster.intersectObjects( scene.children );
+
+ raycasterHelper.hits = intersects;
+ raycasterHelper.update();
+
+ renderer.render( scene, camera );
+
+ }
+
+ window.requestAnimationFrame(render);
+
+
+ Examples
+
+ [example:webgl_raycaster_helper WebGL / raycaster / helper]
+
+ Constructor
+
+
+ [name]([param:Raycaster raycaster], [param:Number numberOfHitsToVisualize], [param:Number sphereRadius],
+ [param:Number nearFarSize], [param:Array colors])
+
+
+ [page:Raycaster raycaster] -- Raycaster to visualize.
+ [page:Number numberOfHitsToVisualize] -- Maximum hits to visualize. Default is `20`.
+ [page:Number sphereRadius] -- Origin sphere radius. Default is `0.04`.
+ [page:Number nearFarSize] -- Near/Far plane size. Default is `0.1`.
+ [page:Array colors] -- Colors for the helper elements. Default is `{ near: 0xffffff, far: 0xffffff, originToNear: 0x333333, nearToFar: 0xffffff, origin: [ 0x0eec82, 0xff005b ] }`
+
+
+ Properties
+ See the base [page:Object3D] class for common properties.
+
+ [property:Array hits]
+ An array of [page:Raycaster Raycaster]'s intersections.
+
+ Methods
+ See the base [page:Object3D] class for common methods.
+
+ [method:undefined setColors]([param:Array colors])
+
+ colors -- A partial array of `colors`, as defined in the constructor's params.
+
+
+
+ [method:undefined update]()
+
+
+ Updates the helper's geometries based on the helper's `hits`.
+
+
+ [method:undefined dispose]()
+
+ Frees the GPU-related resources allocated by this instance. Call this
+ method whenever this instance is no longer used in your app.
+
+
+ Source
+
+
+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+
+
+
diff --git a/docs/list.json b/docs/list.json
index eadf2f26603bb0..228f591e38d1e2 100644
--- a/docs/list.json
+++ b/docs/list.json
@@ -166,6 +166,7 @@
"HemisphereLightHelper": "api/en/helpers/HemisphereLightHelper",
"PlaneHelper": "api/en/helpers/PlaneHelper",
"PointLightHelper": "api/en/helpers/PointLightHelper",
+ "RaycasterHelper": "api/en/helpers/RaycasterHelper",
"SkeletonHelper": "api/en/helpers/SkeletonHelper",
"SpotLightHelper": "api/en/helpers/SpotLightHelper"
},
diff --git a/examples/files.json b/examples/files.json
index 1948537f3331e8..f89d739678e3c5 100644
--- a/examples/files.json
+++ b/examples/files.json
@@ -188,6 +188,7 @@
"webgl_points_waves",
"webgl_portal",
"webgl_raycaster_bvh",
+ "webgl_raycaster_helper",
"webgl_raycaster_sprite",
"webgl_raycaster_texture",
"webgl_read_float_buffer",
diff --git a/examples/screenshots/webgl_raycaster_helper.jpg b/examples/screenshots/webgl_raycaster_helper.jpg
new file mode 100644
index 00000000000000..2c109ecaf757de
Binary files /dev/null and b/examples/screenshots/webgl_raycaster_helper.jpg differ
diff --git a/examples/webgl_raycaster_helper.html b/examples/webgl_raycaster_helper.html
new file mode 100644
index 00000000000000..61c0f49eea008f
--- /dev/null
+++ b/examples/webgl_raycaster_helper.html
@@ -0,0 +1,108 @@
+
+
+
+ three.js webgl - helpers
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Three.Core.js b/src/Three.Core.js
index f7a10261f0014e..3e462645e31f5d 100644
--- a/src/Three.Core.js
+++ b/src/Three.Core.js
@@ -127,6 +127,7 @@ export { Quaternion } from './math/Quaternion.js';
export { Color } from './math/Color.js';
export { ColorManagement } from './math/ColorManagement.js';
export { SphericalHarmonics3 } from './math/SphericalHarmonics3.js';
+export { RaycasterHelper } from './helpers/RaycasterHelper.js';
export { SpotLightHelper } from './helpers/SpotLightHelper.js';
export { SkeletonHelper } from './helpers/SkeletonHelper.js';
export { PointLightHelper } from './helpers/PointLightHelper.js';
diff --git a/src/helpers/RaycasterHelper.js b/src/helpers/RaycasterHelper.js
new file mode 100644
index 00000000000000..4ef29536d287e9
--- /dev/null
+++ b/src/helpers/RaycasterHelper.js
@@ -0,0 +1,188 @@
+import { BufferGeometry } from '../core/BufferGeometry.js';
+import { Float32BufferAttribute } from '../core/BufferAttribute.js';
+import { InstancedMesh } from '../objects/InstancedMesh.js';
+import { LineBasicMaterial } from '../materials/LineBasicMaterial.js';
+import { Line } from '../objects/Line.js';
+import { MeshBasicMaterial } from '../materials/MeshBasicMaterial.js';
+import { Mesh } from '../objects/Mesh.js';
+import { Object3D } from '../core/Object3D.js';
+import { SphereGeometry } from '../geometries/SphereGeometry.js';
+import { Vector3 } from '../math/Vector3.js';
+
+const _o = /*@__PURE__*/ new Object3D();
+const _v = /*@__PURE__*/ new Vector3();
+
+class RaycasterHelper extends Object3D {
+
+ constructor( raycaster, numberOfHitsToVisualize = 20, sphereRadius = .04, nearFarSize = .1, colors = {
+ near: 0xffffff,
+ far: 0xffffff,
+ originToNear: 0x333333,
+ nearToFar: 0xffffff,
+ origin: [ 0x0eec82, 0xff005b ],
+ } ) {
+
+ super();
+ this.raycaster = raycaster;
+ this.numberOfHitsToVisualize = numberOfHitsToVisualize;
+
+ this.hits = [];
+
+ this.colors = colors;
+
+ this.origin = new Mesh(
+ new SphereGeometry( sphereRadius, 32 ),
+ new MeshBasicMaterial()
+ );
+ this.origin.name = 'RaycasterHelper_origin';
+ this.origin.raycast = () => null;
+
+ const geometry = new BufferGeometry();
+ geometry.setAttribute( 'position', new Float32BufferAttribute( [
+ - nearFarSize, nearFarSize, 0,
+ nearFarSize, nearFarSize, 0,
+ nearFarSize, - nearFarSize, 0,
+ - nearFarSize, - nearFarSize, 0,
+ - nearFarSize, nearFarSize, 0
+ ], 3 ) );
+
+ this.near = new Line( geometry, new LineBasicMaterial() );
+ this.near.name = 'RaycasterHelper_near';
+ this.near.raycast = () => null;
+
+ this.far = new Line( geometry, new LineBasicMaterial() );
+ this.far.name = 'RaycasterHelper_far';
+ this.far.raycast = () => null;
+
+ this.nearToFar = new Line( new BufferGeometry(), new LineBasicMaterial() );
+ this.nearToFar.name = 'RaycasterHelper_nearToFar';
+ this.nearToFar.raycast = () => null;
+
+ this.nearToFar.geometry.setFromPoints( [ _v, _v ] );
+
+ this.originToNear = new Line(
+ this.nearToFar.geometry.clone(),
+ new LineBasicMaterial()
+ );
+ this.originToNear.name = 'RaycasterHelper_originToNear';
+ this.originToNear.raycast = () => null;
+
+ this.hitPoints = new InstancedMesh(
+ new SphereGeometry( sphereRadius ),
+ new MeshBasicMaterial(),
+ this.numberOfHitsToVisualize
+ );
+ this.hitPoints.name = 'RaycasterHelper_hits';
+ this.hitPoints.raycast = () => null;
+
+ this.add( this.nearToFar );
+ this.add( this.originToNear );
+
+ this.add( this.near );
+ this.add( this.far );
+
+ this.add( this.origin );
+ this.add( this.hitPoints );
+
+ this.setColors();
+
+ }
+
+ setColors( colors ) {
+
+ const _colors = {
+ ...this.colors,
+ ...colors,
+ };
+
+ this.near.material.color.set( _colors.near );
+ this.far.material.color.set( _colors.far );
+ this.nearToFar.material.color.set( _colors.nearToFar );
+ this.originToNear.material.color.set( _colors.originToNear );
+
+ }
+
+ update() {
+
+ const origin = this.raycaster.ray.origin;
+ const direction = this.raycaster.ray.direction;
+
+ this.origin.position.copy( origin );
+
+ this.near.position
+ .copy( origin )
+ .add( direction.clone().multiplyScalar( this.raycaster.near ) );
+
+ this.far.position
+ .copy( origin )
+ .add( direction.clone().multiplyScalar( this.raycaster.far ) );
+
+ this.far.lookAt( origin );
+ this.near.lookAt( origin );
+
+ let pos = this.nearToFar.geometry.getAttribute( 'position' );
+ pos.set( [ ...this.near.position, ...this.far.position ] );
+ pos.needsUpdate = true;
+
+ pos = this.originToNear.geometry.getAttribute( 'position' );
+ pos.set( [ ...origin, ...this.near.position ] );
+ pos.needsUpdate = true;
+
+ /**
+ * Update hit points visualization
+ */
+
+ const hits = Array.isArray( this.hits ) ? this.hits : [];
+
+ for ( let i = 0; i < this.numberOfHitsToVisualize; i ++ ) {
+
+ const hit = hits[ i ];
+
+ if ( hit ) {
+
+ const { point } = hit;
+ _o.position.copy( point );
+ _o.scale.setScalar( 1 );
+
+ } else {
+
+ _o.scale.setScalar( 0 );
+
+ }
+
+ _o.updateMatrix();
+
+ this.hitPoints.setMatrixAt( i, _o.matrix );
+
+ }
+
+ this.hitPoints.instanceMatrix.needsUpdate = true;
+
+ /**
+ * Update the color of the origin based on wether there are hits.
+ */
+ this.origin.material.color.set(
+ this.hits.length > 0 ? this.colors.origin[ 0 ] : this.colors.origin[ 1 ]
+ );
+
+ }
+
+ dispose() {
+
+ this.origin.geometry.dispose();
+ this.origin.material.dispose();
+ this.near.geometry.dispose();
+ this.near.material.dispose();
+ this.far.geometry.dispose();
+ this.far.material.dispose();
+ this.nearToFar.geometry.dispose();
+ this.nearToFar.material.dispose();
+ this.originToNear.geometry.dispose();
+ this.originToNear.material.dispose();
+ this.hitPoints.dispose();
+
+ }
+
+}
+
+export { RaycasterHelper };