UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

165 lines (133 loc) 5.89 kB
// How to use: // - create a reflection entity with a camera component, set up its layers to what you want to reflect. // - add the planarRenderer script to it, set the sceneCameraEntity to the main camera of the scene. // - call frameUpdate on the script to update the reflection texture. This needs to be called // after the main camera properties including the transform has been set already. // Note: Objects that use the reflected texture cannot be in the layers the reflection camera renders. var PlanarRenderer = pc.createScript('planarRenderer'); PlanarRenderer.attributes.add('sceneCameraEntity', { type: 'entity', description: 'The entity containing the main camera of the scene.' }); PlanarRenderer.attributes.add('scale', { title: 'Scale', description: 'Scale of the texture compared to the render buffer of the main camera.', type: 'number', default: 0.5 }); PlanarRenderer.attributes.add('mipmaps', { title: 'Mipmaps', description: 'If set to true, mipmaps will be and autogenerated.', type: 'boolean', default: false }); PlanarRenderer.attributes.add('depth', { title: 'Depth', description: 'If set to true, depth buffer will be created.', type: 'boolean', default: true }); PlanarRenderer.attributes.add('planePoint', { type: 'vec3', default: [0, 0, 0], title: 'Plane Point', description: 'Point on a reflection plane.' }); PlanarRenderer.attributes.add('planeNormal', { type: 'vec3', default: [0, 1, 0], title: 'Plane Normal', description: 'Normal of a reflection plane.' }); // initialize code called once per entity PlanarRenderer.prototype.initialize = function () { this.plane = new pc.Plane(); this.reflectionMatrix = new pc.Mat4(); // sceneCameraEntity needs to be set var sceneCamera = this.sceneCameraEntity.camera; if (!sceneCamera) { console.error('PlanarRenderer component requires cameraEntity attribute to be set.'); return; } // this entity needs to have camera component as well var planarCamera = this.entity.camera; if (!planarCamera) { console.error('PlanarRenderer component requires a camera component on the same entity.'); return; } // When the camera is finished rendering, trigger onPlanarPostRender event on the entity. // This can be listened to by the user, and the resulting texture can be further processed (e.g prefiltered) this.evtPostRender = this.app.scene.on('postrender', (cameraComponent) => { if (planarCamera === cameraComponent) { this.entity.fire('onPlanarPostRender'); } }); // when the script is destroyed, remove event listeners this.on('destroy', () => { this.evtPostRender.off(); }); }; PlanarRenderer.prototype.updateRenderTarget = function () { // main camera resolution var sceneCamera = this.sceneCameraEntity.camera; var sceneCameraWidth = sceneCamera.renderTarget?.width ?? this.app.graphicsDevice.width; var sceneCameraHeight = sceneCamera.renderTarget?.height ?? this.app.graphicsDevice.height; // reflection texture resolution var width = Math.floor(sceneCameraWidth * this.scale); var height = Math.floor(sceneCameraHeight * this.scale); // limit maximum texture size width = Math.min(width, this.app.graphicsDevice.maxTextureSize); height = Math.min(height, this.app.graphicsDevice.maxTextureSize); var planarCamera = this.entity.camera; if (!planarCamera.renderTarget || planarCamera.renderTarget.width !== width || planarCamera.renderTarget.height !== height) { // destroy old render target if (planarCamera.renderTarget) { this.texture.destroy(); planarCamera.renderTarget.destroy(); } // Create texture render target with specified resolution and mipmap generation this.texture = new pc.Texture(this.app.graphicsDevice, { name: `${this.entity.name}:PlanarRenderer-`, width: width, height: height, format: pc.PIXELFORMAT_SRGBA8, mipmaps: this.mipmaps, addressU: pc.ADDRESS_CLAMP_TO_EDGE, addressV: pc.ADDRESS_CLAMP_TO_EDGE, minFilter: pc.FILTER_LINEAR_MIPMAP_LINEAR, magFilter: pc.FILTER_LINEAR }); // render target var renderTarget = new pc.RenderTarget({ colorBuffer: this.texture, depth: this.depth }); planarCamera.renderTarget = renderTarget; } }; PlanarRenderer.prototype.frameUpdate = function () { this.updateRenderTarget(); var planarCamera = this.entity.camera; if (planarCamera.enabled) { // update reflection camera orientation by mirroring the scene camera by the plane this.plane.setFromPointNormal(this.planePoint, this.planeNormal); this.reflectionMatrix.setReflection(this.plane.normal, this.plane.distance); var pos = this.sceneCameraEntity.getPosition(); var reflectedPos = this.reflectionMatrix.transformPoint(pos); var target = pos.clone().add(this.sceneCameraEntity.forward); var reflectedTarget = this.reflectionMatrix.transformPoint(target); this.entity.setPosition(reflectedPos); this.entity.lookAt(reflectedTarget); // copy other properties from the scene camera var sceneCamera = this.sceneCameraEntity.camera; planarCamera.fov = sceneCamera.fov; planarCamera.orthoHeight = sceneCamera.orthoHeight; planarCamera.nearClip = sceneCamera.nearClip; planarCamera.farClip = sceneCamera.farClip; planarCamera.aperture = sceneCamera.aperture; planarCamera.sensitivity = sceneCamera.sensitivity; planarCamera.shutter = sceneCamera.shutter; return this.texture; } return null; };