UNPKG

solution

Version:

An animation library for different types of liquids.

428 lines (326 loc) 11.3 kB
import { WaterMaterial } from "../materials"; import { Pass } from "postprocessing"; import THREE from "three"; /** * The water pass renders the reflection and refraction of the given scene to a * texture which is then used by a water material during the normal rendering * process. The material's time value is updated automatically. * * You may disable the water material if you want to use the generated textures * for something else. * * Add this pass to an EffectComposer and follow it up with a RenderPass. * * Manipulate the water mesh however you like. You may also add the water material * to other objects. * * @class WaterPass * @constructor * @extends Pass * @param {Scene} scene - The scene to render. * @param {Camera} camera - The main camera. * @param {Boolean} lightPosition - The main light position. * @param {Texture} normalMap - A normalmap for the waves. * @param {Object} [options] - Additional options. * @param {Boolean} [options.disableWater=false] - Whether the water material should be created and updated every frame. * @param {Boolean} [options.reflection=true] - Whether the reflection should be rendered. * @param {Boolean} [options.refraction=true] - Whether the refraction should be rendered. * @param {Number} [options.resolution=256] - The render texture resolution. * @param {Number} [options.clipBias=0.2] - The clip plane offset. */ export function WaterPass(scene, camera, lightPosition, options) { Pass.call(this, scene, camera, null); if(options === undefined) { options = {}; } /** * The reflection render texture. * * @property renderTargetReflection * @type WebGLRenderTarget */ this.renderTargetReflection = new THREE.WebGLRenderTarget(1, 1, { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter }); this.renderTargetReflection.texture.generateMipmaps = false; /** * The refraction render texture. * * @property renderTargetRefraction * @type WebGLRenderTarget */ this.renderTargetRefraction = this.renderTargetReflection.clone(); this.resolution = (options.resolution === undefined) ? 256 : options.resolution; /** * The reflection camera. * * @property reflectionCamera * @type Camera * @private */ this.reflectionCamera = this.camera.clone(); /** * The refraction camera. * * @property refractionCamera * @type Camera * @private */ this.refractionCamera = this.reflectionCamera.clone(); /** * Whether the reflection texture should be rendered. * * @property renderReflection * @type Boolean */ this.renderReflection = (options.reflection !== undefined) ? options.reflection : true; /** * Whether the refraction texture should be rendered. * * @property renderRefraction * @type Boolean */ this.renderRefraction = (options.refraction !== undefined) ? options.refraction : true; /** * The water material. * * @property material * @type WaterMaterial */ this.material = options.disableWater ? null : new WaterMaterial({ lightPosition: lightPosition, normalMap: options.normalMap, lowQuality: options.lowQuality }); if(this.material !== null) { this.material.uniforms.reflectionMap.value = this.renderTargetReflection; this.material.uniforms.refractionMap.value = this.renderTargetRefraction; } /** * A plane mesh that represents the actual reflection/refraction plane. * * @property mesh * @type Mesh */ /* var geometry = new THREE.BufferGeometry(1, 1); geometry.type = "WaterBufferGeometry"; var vertices = new Float32Array(4 * 3); vertices[0] = 2000; vertices[1] = 0; vertices[2] = -2000; vertices[3] = -2000; vertices[4] = 0; vertices[5] = 2000; vertices[6] = -2000; vertices[7] = 0; vertices[8] = -2000; vertices[9] = 2000; vertices[10] = 0; vertices[11] = 2000; var normals = new Float32Array(vertices.length); normals[0] = 0; normals[1] = 1; normals[2] = 0; normals[3] = 0; normals[4] = 1; normals[5] = 0; normals[6] = 0; normals[7] = 1; normals[8] = 0; normals[9] = 0; normals[10] = 1; normals[11] = 0; var uvs = new Float32Array(4 * 2); uvs[0] = 0; uvs[1] = 0; uvs[2] = 1; uvs[3] = 1; uvs[4] = 1; uvs[5] = 0; uvs[6] = 0; uvs[7] = 1; var indices = new Uint16Array(6); indices[0] = 1; indices[1] = 0; indices[2] = 2; indices[3] = 0; indices[4] = 1; indices[5] = 3; geometry.setIndex(new THREE.BufferAttribute(indices, 1)); geometry.addAttribute("position", new THREE.BufferAttribute(vertices, 3)); geometry.addAttribute("normal", new THREE.BufferAttribute(normals, 3)); geometry.addAttribute("uv", new THREE.BufferAttribute(uvs, 2)); */ let geometry = new THREE.PlaneBufferGeometry(1, 1); let tangents = new Float32Array(4 * 4); tangents[0] = 1; tangents[1] = 0; tangents[2] = 0; tangents[3] = -1; tangents[4] = 1; tangents[5] = 0; tangents[6] = 0; tangents[7] = -1; tangents[8] = 1; tangents[9] = 0; tangents[10] = 0; tangents[11] = -1; tangents[12] = 1; tangents[13] = 0; tangents[14] = 0; tangents[15] = -1; geometry.addAttribute("tangent", new THREE.BufferAttribute(tangents, 4)); this.mesh = new THREE.Mesh(geometry, this.material); this.scene.add(this.mesh); /** * The reflection texture matrix for UV-mapping. * * @property textureMatrix * @type Matrix4 * @private */ this.textureMatrix = new THREE.Matrix4(); /** * The reflection plane. * * @property plane * @type Plane * @private */ this.plane = new THREE.Plane(); /** * The normal of the reflection plane. * * @property normal * @type Vector3 * @private */ this.normal = new THREE.Vector3(0, 0, 1); /** * The world position of the reflection plane. * * @property worldPosition * @type Vector3 * @private */ this.worldPosition = new THREE.Vector3(); /** * The world position of the camera. * * @property worldPosition * @type Vector3 * @private */ this.cameraWorldPosition = new THREE.Vector3(); /** * A rotation matrix for the plane. * * @property rotationMatrix * @type Matrix4 * @private */ this.rotationMatrix = new THREE.Matrix4(); /** * A look-at point. * * @property lookAtPosition * @type Vector3 * @private */ this.lookAtPosition = new THREE.Vector3(0, 0, -1); /** * Clip plane. * * @property clipPlane * @type Vector4 */ this.clipPlane = new THREE.Vector4(); /** * Clip bias. * * @property clipBias * @type Number */ this.clipBias = (options.clipBias !== undefined) ? options.clipBias : 0.2; // Update the matrices. this.update(); } WaterPass.prototype = Object.create(Pass.prototype); WaterPass.prototype.constructor = WaterPass; /** * The resolution of the render targets. * The value should be a power of two. * * @property resolution * @type Number * @default 256 */ Object.defineProperty(WaterPass.prototype, "resolution", { get: function() { return this.renderTargetReflection.width; }, set: function(x) { if(typeof x === "number" && x > 0) { this.renderTargetReflection.setSize(x, x); this.renderTargetRefraction.setSize(x, x); } } }); /** * Renders the reflection and refraction textures. * * @method render * @param {WebGLRenderer} renderer - The renderer to use. * @param {WebGLRenderTarget} readBuffer - The read buffer. * @param {WebGLRenderTarget} writeBuffer - The write buffer. * @param {Number} delta - The render delta time. */ WaterPass.prototype.render = function(renderer, readBuffer, writeBuffer, delta) { if(this.mesh.matrixNeedsUpdate) { this.update(); } //this.mesh.matrixNeedsUpdate = true; let visible = this.material.visible; this.material.visible = false; if(this.renderReflection) { renderer.render(this.scene, this.reflectionCamera, this.renderTargetReflection, true); } if(this.renderRefraction) { renderer.render(this.scene, this.refractionCamera, this.renderTargetRefraction, true); } this.material.visible = visible; if(this.material !== null) { this.material.uniforms.time.value += delta; } }; /** * Updates the matrices. * * @method update * @private */ const view = new THREE.Vector3(); const target = new THREE.Vector3(); const q = new THREE.Vector4(); WaterPass.prototype.update = function() { this.mesh.updateMatrixWorld(); this.camera.updateMatrixWorld(); this.worldPosition.setFromMatrixPosition(this.mesh.matrixWorld); this.cameraWorldPosition.setFromMatrixPosition(this.camera.matrixWorld); this.rotationMatrix.extractRotation(this.mesh.matrixWorld); this.normal.set(0, 0, 1); this.normal.applyMatrix4(this.rotationMatrix); view.copy(this.worldPosition).sub(this.cameraWorldPosition); view.reflect(this.normal).negate(); view.add(this.worldPosition); this.rotationMatrix.extractRotation(this.camera.matrixWorld); this.lookAtPosition.set(0, 0, -1); this.lookAtPosition.applyMatrix4(this.rotationMatrix); this.lookAtPosition.add(this.cameraWorldPosition); target.copy(this.worldPosition).sub(this.lookAtPosition); target.reflect(this.normal).negate(); target.add(this.worldPosition); this.mesh.up.set(0, -1, 0); this.mesh.up.applyMatrix4(this.rotationMatrix); this.mesh.up.reflect(this.normal).negate(); this.reflectionCamera.position.copy(view); this.reflectionCamera.up = this.mesh.up; this.reflectionCamera.lookAt(target); this.reflectionCamera.updateProjectionMatrix(); this.reflectionCamera.updateMatrixWorld(); this.reflectionCamera.matrixWorldInverse.getInverse(this.reflectionCamera.matrixWorld); // Update the reflection texture matrix. this.textureMatrix.set( 0.5, 0.0, 0.0, 0.5, 0.0, 0.5, 0.0, 0.5, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 1.0 ); this.textureMatrix.multiply(this.reflectionCamera.projectionMatrix); this.textureMatrix.multiply(this.reflectionCamera.matrixWorldInverse); // Now update projection matrix with new clip plan. // Code from: http://www.terathon.com/code/oblique.html // Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf this.plane.setFromNormalAndCoplanarPoint(this.normal, this.worldPosition); this.plane.applyMatrix4(this.reflectionCamera.matrixWorldInverse); this.clipPlane.set(this.plane.normal.x, this.plane.normal.y, this.plane.normal.z, this.plane.constant); let projectionMatrix = this.reflectionCamera.projectionMatrix; q.x = (Math.sign(this.clipPlane.x) + projectionMatrix.elements[8]) / projectionMatrix.elements[0]; q.y = (Math.sign(this.clipPlane.y) + projectionMatrix.elements[9]) / projectionMatrix.elements[5]; q.z = -1.0; q.w = (1.0 + projectionMatrix.elements[10]) / projectionMatrix.elements[14]; // Calculate the scaled plane vector. let c = this.clipPlane.multiplyScalar(2.0 / this.clipPlane.dot(q)); // Replacing the third row of the projection matrix. projectionMatrix.elements[2] = c.x; projectionMatrix.elements[6] = c.y; projectionMatrix.elements[10] = c.z + 1.0 + this.clipBias;// minus? projectionMatrix.elements[14] = c.w; }; /** * Adjusts the format of the render targets. * * @method initialise * @param {WebGLRenderer} renderer - The renderer. * @param {Boolean} alpha - Whether the renderer uses the alpha channel or not. */ WaterPass.prototype.initialise = function(renderer, alpha) { if(!alpha) { this.renderTargetReflection.texture.format = THREE.RGBFormat; this.renderTargetRefraction.texture.format = THREE.RGBFormat; } };