VR games के लिए WebXR की बुनियादी बातें
WebXR, VR और AR को ब्राउज़र में लाता है। खिलाड़ी आपके game को बिना कुछ डाउनलोड किए headset में अनुभव कर सकते हैं।
1) WebXR support की जाँच करें
async function checkXRSupport() {
if (!navigator.xr) {
return { vr: false, ar: false }
}
const vr = await navigator.xr.isSessionSupported('immersive-vr')
const ar = await navigator.xr.isSessionSupported('immersive-ar')
return { vr, ar }
}
// Usage
const support = await checkXRSupport()
if (support.vr) {
showVRButton()
}2) एक VR session शुरू करना
let xrSession = null
let xrRefSpace = null
async function startVR() {
try {
xrSession = await navigator.xr.requestSession('immersive-vr', {
requiredFeatures: ['local-floor'],
optionalFeatures: ['hand-tracking'],
})
xrSession.addEventListener('end', onSessionEnd)
// Set up rendering
const gl = canvas.getContext('webgl2', { xrCompatible: true })
await xrSession.updateRenderState({
baseLayer: new XRWebGLLayer(xrSession, gl),
})
// Get reference space
xrRefSpace = await xrSession.requestReferenceSpace('local-floor')
// Start render loop
xrSession.requestAnimationFrame(onXRFrame)
} catch (err) {
console.error('Failed to start VR:', err)
}
}
function onSessionEnd() {
xrSession = null
xrRefSpace = null
}3) XR render loop
function onXRFrame(time, frame) {
const session = frame.session
session.requestAnimationFrame(onXRFrame)
const pose = frame.getViewerPose(xrRefSpace)
if (!pose) return
const glLayer = session.renderState.baseLayer
gl.bindFramebuffer(gl.FRAMEBUFFER, glLayer.framebuffer)
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
// Render for each eye
for (const view of pose.views) {
const viewport = glLayer.getViewport(view)
gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height)
// Get camera matrices
const viewMatrix = view.transform.inverse.matrix
const projectionMatrix = view.projectionMatrix
// Render scene with these matrices
renderScene(viewMatrix, projectionMatrix)
}
}4) Input sources (controllers)
xrSession.addEventListener('inputsourceschange', (e) => {
for (const source of e.added) {
console.log('Controller added:', source.handedness) // 'left' or 'right'
}
for (const source of e.removed) {
console.log('Controller removed:', source.handedness)
}
})
function processInput(frame) {
for (const source of xrSession.inputSources) {
if (!source.gamepad) continue
const gamepad = source.gamepad
// Trigger (usually index 0)
const triggerPressed = gamepad.buttons[0]?.pressed
const triggerValue = gamepad.buttons[0]?.value || 0
// Grip (usually index 1)
const gripPressed = gamepad.buttons[1]?.pressed
// Thumbstick
const thumbstickX = gamepad.axes[2] || 0
const thumbstickY = gamepad.axes[3] || 0
// A/B buttons (index 4, 5)
const buttonA = gamepad.buttons[4]?.pressed
const buttonB = gamepad.buttons[5]?.pressed
}
}यह न मानें कि हर डिवाइस में gamepad होता है
ऊपर दिया गया code triggers और thumbsticks के लिए source.gamepad पढ़ता है। यह Meta Quest जैसे controller वाले headsets पर काम करता है, लेकिन Apple Vision Pro (visionOS 2 के Safari में WebXR डिफ़ॉल्ट रूप से चालू है) या Samsung Galaxy XR और दूसरे Android XR डिवाइसों पर कुछ नहीं करता, जहाँ प्राथमिक input gaze-and-pinch या hand tracking होता है। उन headsets पर source.gamepad null रहता है और session.inputSources तब तक खाली रहता है जब तक उपयोगकर्ता वाकई pinch नहीं करता।
जो एक interaction हर डिवाइस की गारंटी देता है, यानी "primary action", उसके लिए buttons को पोल करने के बजाय select events सुनें। WebXR एक controller trigger, एक hand pinch और एक Vision Pro gaze-and-pinch सबके लिए एक जैसे selectstart, select और selectend देता है, इसलिए एक ही handler इन सबको कवर कर लेता है:
xrSession.addEventListener('selectstart', (e) => {
// e.inputSource is the controller, hand, or transient pinch
// e.frame lets you read its targetRaySpace pose
})
xrSession.addEventListener('select', (e) => {
// primary action completed (trigger pulled, pinch released on target)
})
xrSession.addEventListener('selectend', (e) => {})controller वाले headsets पर thumbstick locomotion और अतिरिक्त buttons के लिए gamepad code रखें, लेकिन उसे if (source.gamepad) के पीछे रखें और अपने मुख्य "select/grab/activate" interaction को इन events के ज़रिए चलाएँ ताकि game hand- और gaze-driven डिवाइसों पर भी काम करता रहे।
5) Controller positions
function getControllerPose(frame, source) {
if (!source.gripSpace) return null
const pose = frame.getPose(source.gripSpace, xrRefSpace)
if (!pose) return null
return {
position: pose.transform.position,
orientation: pose.transform.orientation,
matrix: pose.transform.matrix,
}
}
function renderControllers(frame) {
for (const source of xrSession.inputSources) {
const pose = getControllerPose(frame, source)
if (pose) {
renderControllerModel(pose, source.handedness)
}
}
}6) पॉइंटिंग के लिए Ray casting
function getControllerRay(frame, source) {
if (!source.targetRaySpace) return null
const pose = frame.getPose(source.targetRaySpace, xrRefSpace)
if (!pose) return null
const origin = pose.transform.position
const direction = {
x: -pose.transform.matrix[8],
y: -pose.transform.matrix[9],
z: -pose.transform.matrix[10],
}
return { origin, direction }
}
function checkRayIntersection(ray, objects) {
let closest = null
let closestDist = Infinity
for (const obj of objects) {
const dist = rayIntersectBox(ray, obj.boundingBox)
if (dist !== null && dist < closestDist) {
closest = obj
closestDist = dist
}
}
return closest
}7) Teleportation locomotion
class TeleportSystem {
constructor() {
this.targetPosition = null
this.isAiming = false
}
update(frame, inputSources) {
for (const source of inputSources) {
if (source.handedness !== 'left') continue
const gamepad = source.gamepad
const thumbstickY = gamepad?.axes[3] || 0
if (thumbstickY < -0.5) {
// Aiming
this.isAiming = true
const ray = getControllerRay(frame, source)
this.targetPosition = this.findTeleportTarget(ray)
} else if (this.isAiming) {
// Release - teleport
if (this.targetPosition) {
player.position.x = this.targetPosition.x
player.position.z = this.targetPosition.z
}
this.isAiming = false
this.targetPosition = null
}
}
}
findTeleportTarget(ray) {
// Intersect with ground plane
if (ray.direction.y >= 0) return null
const t = -ray.origin.y / ray.direction.y
if (t < 0 || t > 10) return null
return {
x: ray.origin.x + ray.direction.x * t,
y: 0,
z: ray.origin.z + ray.direction.z * t,
}
}
render() {
if (this.isAiming && this.targetPosition) {
renderTeleportMarker(this.targetPosition)
}
}
}8) Smooth locomotion
function updateSmoothLocomotion(frame, inputSources, dt) {
for (const source of inputSources) {
if (source.handedness !== 'left') continue
const gamepad = source.gamepad
if (!gamepad) continue
const moveX = gamepad.axes[2] || 0
const moveY = gamepad.axes[3] || 0
// Apply deadzone
if (Math.abs(moveX) < 0.1) moveX = 0
if (Math.abs(moveY) < 0.1) moveY = 0
// Get head direction for movement
const pose = frame.getViewerPose(xrRefSpace)
if (!pose) continue
const headMatrix = pose.transform.matrix
const forward = { x: -headMatrix[8], z: -headMatrix[10] }
const right = { x: headMatrix[0], z: headMatrix[2] }
// Normalize to XZ plane
const len = Math.sqrt(forward.x ** 2 + forward.z ** 2)
forward.x /= len
forward.z /= len
const speed = 3 * dt
player.position.x += (forward.x * -moveY + right.x * moveX) * speed
player.position.z += (forward.z * -moveY + right.z * moveX) * speed
}
}9) VR comfort के दिशानिर्देश
// Vignette during movement to reduce motion sickness
function renderComfortVignette(movementSpeed) {
const intensity = Math.min(movementSpeed / 5, 0.5)
if (intensity < 0.1) return
// Render dark edges that increase with speed
renderVignette(intensity)
}
// Snap turning
let snapTurnCooldown = 0
function handleSnapTurn(gamepad, dt) {
snapTurnCooldown -= dt
const thumbstickX = gamepad.axes[2] || 0
if (Math.abs(thumbstickX) > 0.7 && snapTurnCooldown <= 0) {
const angle = Math.sign(thumbstickX) * (Math.PI / 4) // 45 degrees
player.rotation += angle
snapTurnCooldown = 0.3 // Prevent rapid turns
}
}10) पूरा WebXR सेटअप
class VRGame {
constructor(canvas) {
this.canvas = canvas
this.gl = canvas.getContext('webgl2', { xrCompatible: true })
this.session = null
this.refSpace = null
this.teleport = new TeleportSystem()
}
async checkSupport() {
if (!navigator.xr) return false
return await navigator.xr.isSessionSupported('immersive-vr')
}
async start() {
this.session = await navigator.xr.requestSession('immersive-vr', {
requiredFeatures: ['local-floor'],
})
await this.session.updateRenderState({
baseLayer: new XRWebGLLayer(this.session, this.gl),
})
this.refSpace = await this.session.requestReferenceSpace('local-floor')
this.session.addEventListener('end', () => this.onEnd())
this.session.requestAnimationFrame((t, f) => this.onFrame(t, f))
}
onFrame(time, frame) {
this.session.requestAnimationFrame((t, f) => this.onFrame(t, f))
const dt = (time - this.lastTime) / 1000
this.lastTime = time
// Update game logic
this.teleport.update(frame, this.session.inputSources)
// Render
const pose = frame.getViewerPose(this.refSpace)
if (!pose) return
const glLayer = this.session.renderState.baseLayer
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, glLayer.framebuffer)
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT)
for (const view of pose.views) {
const vp = glLayer.getViewport(view)
this.gl.viewport(vp.x, vp.y, vp.width, vp.height)
this.render(view.transform.inverse.matrix, view.projectionMatrix)
}
this.teleport.render()
}
render(viewMatrix, projectionMatrix) {
// Your rendering code here
}
onEnd() {
this.session = null
}
}संबंधित
- WebGL की बुनियादी बातें
- WebGPU की शुरुआत
- Game input handling
- 2026 में Web Games का Tech Stack — व्यापक tech परिदृश्य में WebXR कहाँ बैठता है
- ब्राउज़र में Three.js + USDC — XR scenes के लिए 3D assets लोड करना
बाहरी संसाधन
- MDN: WebXR Device API — पूरा API reference
- Immersive Web — WebXR samples, tools और browser support tables
- Three.js WebXR guide — Three.js के साथ VR content