UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

202 lines (183 loc) 7.21 kB
import { Color, FrontSide, Matrix4, ShaderMaterial, UniformsLib, UniformsUtils, Vector3, BufferGeometry, Texture, WebGLRenderer, Scene, Camera, Side, } from 'three'; import {IUniformColor, IUniformN, IUniformTexture, IUniformV3} from '../../../engine/nodes/utils/code/gl/Uniforms'; import {BaseReflector, BaseReflectorOptions} from './_BaseReflector'; import VERTEX from './water/vert.glsl'; import FRAGMENT from './water/frag.glsl'; import {PolyScene} from '../../../engine/scene/PolyScene'; /** * Work based on : * http://slayvin.net : Flat mirror for three.js * http://www.adelphi.edu/~stemkoski : An implementation of water shader based on the flat mirror * http://29a.ch/ && http://29a.ch/slides/2012/webglwater/ : Water shader explanations in WebGL */ export interface WaterMaterial extends ShaderMaterial { uniforms: { sunDirection: IUniformV3; sunColor: IUniformColor; wavesHeight: IUniformN; waterColor: IUniformColor; reflectionColor: IUniformColor; reflectionFresnel: IUniformN; distortionScale: IUniformN; size: IUniformN; alpha: IUniformN; time: IUniformN; timeScale: IUniformN; direction: IUniformV3; normalBias: IUniformN; // internals mirrorSampler: IUniformTexture; textureMatrix: {value: Matrix4}; eye: IUniformV3; }; } export interface WaterOptions extends BaseReflectorOptions { polyScene: PolyScene; alpha?: number; timeScale?: number; size?: number; direction?: Vector3; sunDirection?: Vector3; sunColor?: Color; wavesHeight?: number; waterColor?: Color; reflectionColor?: Color; reflectionFresnel?: number; waterNormals?: Texture; distortionScale?: number; normalBias?: number; side?: Side; useFog?: boolean; } export class Water extends BaseReflector<BufferGeometry, WaterMaterial> { public readonly isWater = true; // private _renderReflection = true; protected override _mirrorCameraMultipliedByMatrixWorld = false; constructor(geometry: BufferGeometry, protected override _options: WaterOptions) { super(geometry, _options); } protected _createMaterial() { const options = this._options; const alpha = options.alpha !== undefined ? options.alpha : 1.0; const timeScale = options.timeScale !== undefined ? options.timeScale : 1.0; const size = options.size !== undefined ? options.size : 0.0; const direction = options.direction !== undefined ? options.direction : new Vector3(0.0, 1.0, 0.0); const sunDirection = options.sunDirection !== undefined ? options.sunDirection : new Vector3(0.70707, 0.70707, 0.0); const sunColor = new Color(options.sunColor !== undefined ? options.sunColor : 0xffffff); const wavesHeight: number = options.wavesHeight !== undefined ? options.wavesHeight : 1; const waterColor = new Color(options.waterColor !== undefined ? options.waterColor : 0x7f7f7f); const reflectionColor = new Color(options.reflectionColor !== undefined ? options.reflectionColor : 0xffffff); const reflectionFresnel = options.reflectionFresnel !== undefined ? options.reflectionFresnel : 1; const distortionScale = options.distortionScale !== undefined ? options.distortionScale : 20.0; const side = options.side !== undefined ? options.side : FrontSide; const useFog = options.useFog !== undefined ? options.useFog : false; const normalBias: number = options.normalBias !== undefined ? options.normalBias : 0.001; const mirrorShader = { uniforms: UniformsUtils.merge([ UniformsLib['fog'], UniformsLib['lights'], { mirrorSampler: {value: null}, alpha: {value: 1.0}, time: {value: 0.0}, // do not assign time uniform here, as the uniforms as cloned shortly after timeScale: {value: 1.0}, size: {value: 1.0}, distortionScale: {value: 20.0}, textureMatrix: {value: new Matrix4()}, sunColor: {value: new Color(0x7f7f7f)}, sunDirection: {value: new Vector3(0.70707, 0.70707, 0)}, direction: {value: new Vector3().copy(BaseReflector.REFLECTOR_DEFAULT_UP)}, eye: {value: new Vector3()}, wavesHeight: {value: wavesHeight}, waterColor: {value: new Color(0x555555)}, reflectionColor: {value: new Color(0xffffff)}, reflectionFresnel: {value: 1}, normalBias: {value: normalBias}, }, ]), vertexShader: VERTEX, fragmentShader: FRAGMENT, }; const material = new ShaderMaterial({ fragmentShader: mirrorShader.fragmentShader, vertexShader: mirrorShader.vertexShader, uniforms: UniformsUtils.clone(mirrorShader.uniforms), lights: true, side: side, // fog: useFog, // transparent: true, // TODO: allow transparency when I have a good alpha model }) as WaterMaterial; material.fog = useFog; material.uniforms['time'] = this._options.polyScene.timeController.timeUniform(); material.uniforms['timeScale'].value = timeScale; material.uniforms['size'].value = size; material.uniforms['textureMatrix'].value = this.textureMatrix; material.uniforms['alpha'].value = alpha; material.uniforms['sunColor'].value = sunColor; material.uniforms['wavesHeight'].value = wavesHeight; material.uniforms['waterColor'].value = waterColor; material.uniforms['reflectionColor'].value = reflectionColor; material.uniforms['reflectionFresnel'].value = reflectionFresnel; material.uniforms['direction'].value = direction; material.uniforms['sunDirection'].value = sunDirection; material.uniforms['distortionScale'].value = distortionScale; material.uniforms['normalBias'].value = normalBias; material.uniforms['eye'].value = new Vector3(); return material; } protected _assignMaterialRenderTarget() { if (this.renderTarget) { this.material.uniforms['mirrorSampler'].value = this.renderTarget.texture; } } protected override _onBeforeRender(renderer: WebGLRenderer, scene: Scene, anyCamera: Camera) { super._onBeforeRender(renderer, scene, anyCamera); if (this.material) { this.material.uniforms['eye'].value.setFromMatrixPosition(anyCamera.matrixWorld); } } setReflectionActive(state: boolean) { this._options.active = state; if (state) { if (this.renderTarget) { this.material.uniforms['mirrorSampler'].value = this.renderTarget.texture; } } else { this.material.uniforms['mirrorSampler'].value = null; } } // override clone(recursive: boolean): this { // // we clone so that a cloned reflector does not share the same color // const clonedOptions = {...this._options}; // clonedOptions.sunDirection = this._options.sunDirection?.clone(); // clonedOptions.sunColor = this._options.sunColor?.clone(); // clonedOptions.waterColor = this._options.waterColor?.clone(); // const clonedGeometry = this.geometry.clone(); // const clonedWater = new Water(clonedGeometry, clonedOptions); // const material: WaterMaterial = clonedWater.material; // clonedWater.copy(this, recursive); // // the material and geometry needs to be added back after the copy, as Mesh.copy would override that // clonedWater.material = material; // clonedWater.geometry = clonedGeometry; // // TODO: // // - size is not passed correctly // // - make time dependent to update the time uniform attribute // clonedWater.updateMatrix(); // return clonedWater as this; // } }