Преглед изворни кода

Merge pull request #5837 from julien-moreau/master

First pass for ocean post-process
David Catuhe пре 6 година
родитељ
комит
c4760460ad

+ 5 - 0
Tools/Config/config.json

@@ -246,6 +246,11 @@
                 "output": "babylon.digitalRainPostProcess.min.js",
                 "entry": "./legacy/legacy-digitalRain.ts",
                 "preventLoadLibrary": true
+            },
+            {
+                "output": "babylon.oceanPostProcess.min.js",
+                "entry": "./legacy/legacy-ocean.ts",
+                "preventLoadLibrary": true
             }
         ],
         "build": {

+ 3 - 0
dist/preview release/what's new.md

@@ -122,6 +122,9 @@
 
 ### Viewer
 
+### Post-Processes Library
+- Added the `Ocean` post-process ([julien-moreau](https://github.com/julien-moreau))
+
 ### Materials Library
 - Added the `cameraOffset` vector property in the `SkyMaterial` to get an offset according to the horizon ([julien-moreau](https://github.com/julien-moreau))
 - Fixed `GradientMaterial` to consider disableLighting working as emissive ([julien-moreau](https://github.com/julien-moreau))

+ 38 - 7
postProcessLibrary/index.html

@@ -58,7 +58,24 @@
 			var hemisphericLight = new BABYLON.HemisphericLight("hemi", new BABYLON.Vector3(0, 1, 0), scene);
 			
 			// Create meshes
-			var sphere = BABYLON.Mesh.CreateSphere("sphere", 48, 30.0, scene);			
+			var sphere = BABYLON.Mesh.CreateSphere("sphere", 48, 30.0, scene);
+
+			var skybox = BABYLON.Mesh.CreateBox("skyBox", 1000.0, scene);
+			var skyboxMaterial = new BABYLON.StandardMaterial("skyBox", scene);
+			skyboxMaterial.backFaceCulling = false;
+			skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture("/Playground/textures/TropicalSunnyDay", scene);
+			skyboxMaterial.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE;
+			skyboxMaterial.diffuseColor = new BABYLON.Color3(0, 0, 0);
+			skyboxMaterial.specularColor = new BABYLON.Color3(0, 0, 0);
+			skyboxMaterial.disableLighting = true;
+			skybox.material = skyboxMaterial;
+			skybox.setEnabled(false);
+			
+			// Materials
+			sphere.material = new BABYLON.StandardMaterial("std", scene);
+			sphere.material.diffuseTexture = new BABYLON.Texture("/Playground/textures/amiga.jpg", scene);
+			sphere.material.diffuseTexture.uScale = 5;
+			sphere.material.diffuseTexture.vScale = 5;
 
 			// Register a render loop to repeatedly render the scene
 			engine.runRenderLoop(function () {
@@ -71,30 +88,44 @@
 				engine.resize();
 			});
 
+			// Post-processes
 			var aaPostProcess = new BABYLON.AsciiArtPostProcess("AsciiArt", camera);
 			var drPostProcess = new BABYLON.DigitalRainPostProcess("AsciiArt", camera);
 
+			var oceanPostProcess = new BABYLON.OceanPostProcess("Ocean", camera);
+			if (oceanPostProcess.isSupported) {
+				oceanPostProcess.refractionTexture.renderList = scene.meshes;
+				oceanPostProcess.reflectionTexture.renderList = scene.meshes;
+				oceanPostProcess.reflectionEnabled = oceanPostProcess.refractionEnabled = true;
+			}
+			
 			//camera.detachPostProcess(aaPostProcess);
 			camera.detachPostProcess(drPostProcess);
+			camera.detachPostProcess(oceanPostProcess);
 
 			var gui = new dat.GUI();
 			var options = {
 				postProcess: "asciiArt"
 			}
 
-			gui.add(options, 'postProcess', ['asciiArt', 'digitalRain']).onFinishChange(function () {
-				
+			gui.add(options, 'postProcess', ['asciiArt', 'digitalRain', 'ocean']).onFinishChange(function () {
+				camera.detachPostProcess(aaPostProcess);
+				camera.detachPostProcess(drPostProcess);
+				camera.detachPostProcess(oceanPostProcess);
+
+				skybox.setEnabled(false);
+
 				switch (options.postProcess) {
 					case "asciiArt":
-						camera.detachPostProcess(aaPostProcess);
-						camera.detachPostProcess(drPostProcess);
 						camera.attachPostProcess(aaPostProcess);
 						break;
 					case "digitalRain":
-						camera.detachPostProcess(aaPostProcess);
-						camera.detachPostProcess(drPostProcess);
 						camera.attachPostProcess(drPostProcess);
 						break;
+					case "ocean":
+						camera.attachPostProcess(oceanPostProcess);
+						skybox.setEnabled(true);
+						break;
 				}
 			});
 		}

+ 2 - 1
postProcessLibrary/src/index.ts

@@ -1,2 +1,3 @@
 export * from "./asciiArt";
-export * from "./digitalRain";
+export * from "./digitalRain";
+export * from "./ocean";

+ 14 - 0
postProcessLibrary/src/legacy/legacy-ocean.ts

@@ -0,0 +1,14 @@
+import * as postProcessLibrary from "../ocean/index";
+
+/**
+ * This is the entry point for the UMD module.
+ * The entry point for a future ESM package should be index.ts
+ */
+var globalObject = (typeof global !== 'undefined') ? global : ((typeof window !== 'undefined') ? window : undefined);
+if (typeof globalObject !== "undefined") {
+    for (var key in postProcessLibrary) {
+        (<any>globalObject).BABYLON[key] = (<any>postProcessLibrary)[key];
+    }
+}
+
+export * from "../ocean/index";

+ 1 - 0
postProcessLibrary/src/ocean/index.ts

@@ -0,0 +1 @@
+export * from "./oceanPostProcess";

+ 259 - 0
postProcessLibrary/src/ocean/oceanPostProcess.fragment.fx

@@ -0,0 +1,259 @@
+/*
+ * "Seascape" by Alexander Alekseev aka TDM - 2014
+ * Babylon.js integration by luaacro https://twitter.com/Luaacro
+ * License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
+ * Contact: tdmaav@gmail.com
+ */
+
+// Uniforms
+uniform sampler2D textureSampler;
+uniform sampler2D positionSampler;
+
+#ifdef REFLECTION_ENABLED
+uniform sampler2D reflectionSampler;
+#endif
+#ifdef REFRACTION_ENABLED
+uniform sampler2D refractionSampler;
+#endif
+
+uniform float time;
+uniform vec2 resolution;
+uniform vec3 cameraRotation;
+uniform vec3 cameraPosition;
+
+// Varyings
+varying vec2 vUV;
+
+// Constants
+const int NUM_STEPS = 8;
+const float PI	 	= 3.141592;
+const float EPSILON	= 1e-3;
+#define EPSILON_NRM (0.1 / resolution.x)
+
+// Sea
+const int ITER_GEOMETRY = 8;
+const int ITER_FRAGMENT = 5;
+const float SEA_HEIGHT = 0.6;
+const float SEA_CHOPPY = 4.0;
+const float SEA_SPEED = 0.8;
+const float SEA_FREQ = 0.16;
+const vec3 SEA_BASE = vec3(0.1,0.19,0.22);
+const vec3 SEA_WATER_COLOR = vec3(0.8,0.9,0.6);
+#define SEA_TIME (1.0 + time * SEA_SPEED)
+const mat2 octave_m = mat2(1.6,1.2,-1.2,1.6);
+
+// Math
+mat3 fromEuler(vec3 ang)
+{
+	vec2 a1 = vec2(sin(ang.x),cos(ang.x));
+    vec2 a2 = vec2(sin(ang.y),cos(ang.y));
+    vec2 a3 = vec2(sin(ang.z),cos(ang.z));
+    mat3 m;
+    m[0] = vec3(a1.y*a3.y+a1.x*a2.x*a3.x,a1.y*a2.x*a3.x+a3.y*a1.x,-a2.y*a3.x);
+	m[1] = vec3(-a2.y*a1.x,a1.y*a2.y,a2.x);
+	m[2] = vec3(a3.y*a1.x*a2.x+a1.y*a3.x,a1.x*a3.x-a1.y*a3.y*a2.x,a2.y*a3.y);
+	return m;
+}
+
+float hash( vec2 p )
+{
+	float h = dot(p,vec2(127.1,311.7));	
+    return fract(sin(h)*43758.5453123);
+}
+
+float noise( in vec2 p )
+{
+    vec2 i = floor( p );
+    vec2 f = fract( p );	
+	vec2 u = f*f*(3.0-2.0*f);
+    return -1.0+2.0*mix( mix( hash( i + vec2(0.0,0.0) ), 
+                     hash( i + vec2(1.0,0.0) ), u.x),
+                mix( hash( i + vec2(0.0,1.0) ), 
+                     hash( i + vec2(1.0,1.0) ), u.x), u.y);
+}
+
+// Lighting
+float diffuse(vec3 n,vec3 l,float p)
+{
+    return pow(dot(n,l) * 0.4 + 0.6,p);
+}
+
+float specular(vec3 n,vec3 l,vec3 e,float s)
+{
+    float nrm = (s + 8.0) / (PI * 8.0);
+    return pow(max(dot(reflect(e,n),l),0.0),s) * nrm;
+}
+
+// Sea
+float sea_octave(vec2 uv, float choppy)
+{
+    uv += noise(uv);        
+    vec2 wv = 1.0-abs(sin(uv));
+    vec2 swv = abs(cos(uv));    
+    wv = mix(wv,swv,wv);
+    return pow(1.0-pow(wv.x * wv.y,0.65),choppy);
+}
+
+float map(vec3 p)
+{
+    float freq = SEA_FREQ;
+    float amp = SEA_HEIGHT;
+    float choppy = SEA_CHOPPY;
+    vec2 uv = p.xz; uv.x *= 0.75;
+    
+    float d, h = 0.0;    
+    for(int i = 0; i < ITER_GEOMETRY; i++)
+    {
+    	d = sea_octave((uv+SEA_TIME)*freq,choppy);
+    	d += sea_octave((uv-SEA_TIME)*freq,choppy);
+        h += d * amp;        
+    	uv *= octave_m; freq *= 1.9; amp *= 0.22;
+        choppy = mix(choppy,1.0,0.2);
+    }
+    return p.y - h;
+}
+
+float map_detailed(vec3 p)
+{
+    float freq = SEA_FREQ;
+    float amp = SEA_HEIGHT;
+    float choppy = SEA_CHOPPY;
+    vec2 uv = p.xz; uv.x *= 0.75;
+    
+    float d, h = 0.0;    
+    for(int i = 0; i < ITER_FRAGMENT; i++)
+    {
+    	d = sea_octave((uv+SEA_TIME)*freq,choppy);
+    	d += sea_octave((uv-SEA_TIME)*freq,choppy);
+        h += d * amp;        
+    	uv *= octave_m; freq *= 1.9; amp *= 0.22;
+        choppy = mix(choppy,1.0,0.2);
+    }
+    return p.y - h;
+}
+
+vec3 getSeaColor(vec3 p, vec3 n, vec3 l, vec3 eye, vec3 dist)
+{
+    float fresnel = clamp(1.0 - dot(n, -eye), 0.0, 1.0);
+    fresnel = pow(fresnel, 3.0) * 0.65;
+
+    #if defined(REFLECTION_ENABLED) || defined(REFRACTION_ENABLED)
+    vec2 reflectionUv = vec2(vUV.x, vUV.y + normalize(n).y);
+    #endif
+
+    #ifdef REFLECTION_ENABLED
+    vec3 reflected = texture2D(reflectionSampler, reflectionUv).rgb * (1.0 - fresnel);
+    #else
+    vec3 eyeNormal = reflect(eye, n);
+    eyeNormal.y = max(eyeNormal.y,0.0);
+    vec3 reflected = vec3(pow(1.0-eyeNormal.y,2.0), 1.0-eyeNormal.y, 0.6+(1.0-eyeNormal.y)*0.4);
+    #endif
+
+    #ifdef REFRACTION_ENABLED
+    vec3 refracted = SEA_BASE + diffuse(n, l, 80.0) * SEA_WATER_COLOR * 0.12;
+    refracted += (texture2D(refractionSampler, reflectionUv).rgb * fresnel);
+    #else
+    vec3 refracted = SEA_BASE + diffuse(n, l, 80.0) * SEA_WATER_COLOR * 0.12;
+    #endif
+
+    vec3 color = mix(refracted, reflected, fresnel);
+    
+    float atten = max(1.0 - dot(dist, dist) * 0.001, 0.0);
+    color += SEA_WATER_COLOR * (p.y - SEA_HEIGHT) * 0.18 * atten;
+    
+    color += vec3(specular(n,l,eye,60.0));
+    
+    return color;
+}
+
+// Tracing
+vec3 getNormal(vec3 p, float eps)
+{
+    vec3 n;
+    n.y = map_detailed(p);    
+    n.x = map_detailed(vec3(p.x+eps,p.y,p.z)) - n.y;
+    n.z = map_detailed(vec3(p.x,p.y,p.z+eps)) - n.y;
+    n.y = eps;
+    return normalize(n);
+}
+
+float heightMapTracing(vec3 ori, vec3 dir, out vec3 p)
+{
+    float tm = 0.0;
+    float tx = 1000.0;    
+    float hx = map(ori + dir * tx);
+    if(hx > 0.0) return tx;   
+    float hm = map(ori + dir * tm);    
+    float tmid = 0.0;
+    for(int i = 0; i < NUM_STEPS; i++)
+    {
+        tmid = mix(tm,tx, hm/(hm-hx));                   
+        p = ori + dir * tmid;                   
+    	float hmid = map(p);
+        if(hmid < 0.0)
+        {
+        	tx = tmid;
+            hx = hmid;
+        }
+        else
+        {
+            tm = tmid;
+            hm = hmid;
+        }
+    }
+    return tmid;
+}
+
+// Main
+void main()
+{
+    #ifdef NOT_SUPPORTED
+    // Just work as a pass through
+    gl_FragColor = texture2D(textureSampler, vUV);
+    #else
+	vec2 uv = vUV;
+    uv = uv * 2.0 - 1.0;
+    uv.x *= resolution.x / resolution.y;
+    
+    // ray
+    vec3 ang = vec3(cameraRotation.z, cameraRotation.x, cameraRotation.y);
+    vec3 ori = vec3(cameraPosition.x, cameraPosition.y, -cameraPosition.z);
+
+    vec3 dir = normalize(vec3(uv.xy, -3.0));
+    dir = normalize(dir) * fromEuler(ang);
+    
+    // Tracing
+    vec3 p;
+    heightMapTracing(ori, dir, p);
+    vec3 dist = p - ori;
+
+    vec3 n = getNormal(p, dot(dist, dist) * EPSILON_NRM);
+    vec3 light = normalize(vec3(0.0, 1.0, 0.8));
+    
+    // Color
+    float seaFact = clamp(max(ori.y, 0.0), 0.0, 1.0);
+    vec3 position = texture2D(positionSampler, vUV).rgb;
+    vec3 baseColor = texture2D(textureSampler, vUV).rgb;
+
+    vec3 color = baseColor;
+
+    if (max(position.y, 0.0) < p.y)
+    {
+        // Sea above
+        color = mix(
+            baseColor,
+            getSeaColor(p, n, light, dir, dist),
+            pow(smoothstep(0.0, -0.05, dir.y), 0.3)
+        ) * seaFact;
+    }
+
+    color = mix(
+        color,
+        baseColor * SEA_BASE + diffuse(n, n, 80.0) * SEA_WATER_COLOR * 0.12,
+        1.0 - seaFact
+    );
+
+    // post
+    gl_FragColor = vec4(pow(color, vec3(0.75)), 1.0);
+    #endif
+}

+ 249 - 0
postProcessLibrary/src/ocean/oceanPostProcess.ts

@@ -0,0 +1,249 @@
+import { TargetCamera } from "babylonjs/Cameras/targetCamera";
+import { Texture } from "babylonjs/Materials/Textures/texture";
+import { MirrorTexture } from "babylonjs/Materials/Textures/mirrorTexture";
+import { RenderTargetTexture } from "babylonjs/Materials/Textures/renderTargetTexture";
+import { GeometryBufferRenderer } from "babylonjs/Rendering/geometryBufferRenderer";
+import { Effect } from "babylonjs/Materials/effect";
+import { PostProcess } from "babylonjs/PostProcesses/postProcess";
+import { Vector2, Vector3, Plane, Matrix, Epsilon } from "babylonjs/Maths/math";
+import { Nullable } from "babylonjs/types";
+
+import "./oceanPostProcess.fragment";
+
+/**
+ * Option available in the Ocean Post Process.
+ */
+export interface IOceanPostProcessOptions {
+    /**
+     * The size of the reflection RTT (number if square, or {width: number, height:number} or {ratio:} to define a ratio from the main scene)
+     */
+    reflectionSize?: number | { width: number; height: number } | { ratio: number };
+    /**
+     * The size of the refraction RTT (number if square, or {width: number, height:number} or {ratio:} to define a ratio from the main scene)
+     */
+    refractionSize?: number | { width: number; height: number } | { ratio: number };
+}
+
+/**
+ * OceanPostProcess helps rendering an infinite ocean surface that can reflect and refract environment.
+ *
+ * Simmply add it to your scene and let the nerd that lives in you have fun.
+ * Example usage:
+ *  var pp = new OceanPostProcess("myOcean", camera);
+ *  pp.reflectionEnabled = true;
+ *  pp.refractionEnabled = true;
+ */
+export class OceanPostProcess extends PostProcess {
+
+    /**
+     * Gets a boolean indicating if the real-time reflection is enabled on the ocean.
+     */
+    public get reflectionEnabled(): boolean {
+        return this._reflectionEnabled;
+    }
+
+    /**
+     * Sets weither or not the real-time reflection is enabled on the ocean.
+     * Is set to true, the reflection mirror texture will be used as reflection texture.
+     */
+    public set reflectionEnabled(enabled: boolean) {
+        if (this._reflectionEnabled === enabled) {
+            return;
+        }
+
+        this._reflectionEnabled = enabled;
+        this.updateEffect(this._getDefines());
+
+        // Remove or add custom render target
+        const customRenderTargets = this.getCamera().getScene().customRenderTargets;
+        if (!enabled) {
+            const index = customRenderTargets.indexOf(this.reflectionTexture);
+            if (index !== -1) {
+                customRenderTargets.splice(index, 1);
+            }
+        } else {
+            customRenderTargets.push(this.reflectionTexture);
+        }
+    }
+
+    /**
+     * Gets a boolean indicating if the real-time refraction is enabled on the ocean.
+     */
+    public get refractionEnabled(): boolean {
+        return this._refractionEnabled;
+    }
+
+    /**
+     * Sets weither or not the real-time refraction is enabled on the ocean.
+     * Is set to true, the refraction render target texture will be used as refraction texture.
+     */
+    public set refractionEnabled(enabled: boolean) {
+        if (this._refractionEnabled === enabled) {
+            return;
+        }
+
+        this._refractionEnabled = enabled;
+        this.updateEffect(this._getDefines());
+
+        // Remove or add custom render target
+        const customRenderTargets = this.getCamera().getScene().customRenderTargets;
+        if (!enabled) {
+            const index = customRenderTargets.indexOf(this.refractionTexture);
+            if (index !== -1) {
+                customRenderTargets.splice(index, 1);
+            }
+        } else {
+            customRenderTargets.push(this.refractionTexture);
+        }
+    }
+
+    /**
+     * Gets wether or not the post-processes is supported by the running hardware.
+     * This requires draw buffer supports.
+     */
+    public get isSupported(): boolean {
+        return this._geometryRenderer !== null && this._geometryRenderer.isSupported;
+    }
+
+    /**
+     * This is the reflection mirror texture used to display reflections on the ocean.
+     * By default, render list is empty.
+     */
+    public reflectionTexture: MirrorTexture;
+    /**
+     * This is the refraction render target texture used to display refraction on the ocean.
+     * By default, render list is empty.
+     */
+    public refractionTexture: RenderTargetTexture;
+
+    private _time: number = 0;
+    private _cameraRotation: Vector3 = Vector3.Zero();
+    private _cameraViewMatrix: Matrix = Matrix.Identity();
+    private _reflectionEnabled: boolean = false;
+    private _refractionEnabled: boolean = false;
+    private _geometryRenderer: Nullable<GeometryBufferRenderer>;
+
+    /**
+     * Instantiates a new Ocean Post Process.
+     * @param name the name to give to the postprocess.
+     * @camera the camera to apply the post process to.
+     * @param options optional object following the IOceanPostProcessOptions format used to customize reflection and refraction render targets sizes.
+     */
+    constructor(name: string, camera: TargetCamera, options: IOceanPostProcessOptions = { }) {
+        super(name,
+            "oceanPostProcess",
+            ["time", "resolution", "cameraPosition", "cameraRotation"],
+            ["positionSampler", "reflectionSampler", "refractionSampler"],
+            {
+                width: camera.getEngine().getRenderWidth(),
+                height: camera.getEngine().getRenderHeight()
+            },
+            camera,
+            Texture.TRILINEAR_SAMPLINGMODE,
+            camera.getEngine(),
+            true);
+
+        // Get geometry shader
+        this._geometryRenderer = camera.getScene().enableGeometryBufferRenderer(1.0);
+        if (this._geometryRenderer && this._geometryRenderer.isSupported) {
+            // Eanble position buffer
+            this._geometryRenderer.enablePosition = true;
+
+            // Create mirror textures
+            this.reflectionTexture = new MirrorTexture("oceanPostProcessReflection", options.reflectionSize || { width: 512, height: 512 }, camera.getScene());
+            this.reflectionTexture.mirrorPlane = Plane.FromPositionAndNormal(Vector3.Zero(), new Vector3(0, -1, 0));
+
+            this.refractionTexture = new RenderTargetTexture("oceanPostProcessRefraction", options.refractionSize || { width: 512, height: 512 }, camera.getScene());
+        } else {
+            this.updateEffect("#define NOT_SUPPORTED\n");
+        }
+
+        // On apply the post-process
+        this.onApply = (effect: Effect) => {
+            if (!this._geometryRenderer || !this._geometryRenderer.isSupported) {
+                return;
+            }
+
+            const engine = camera.getEngine();
+            const scene = camera.getScene();
+
+            this._time += engine.getDeltaTime() * 0.001;
+            effect.setFloat("time", this._time);
+
+            effect.setVector2("resolution", new Vector2(engine.getRenderWidth(), engine.getRenderHeight()));
+
+            if (scene) {
+                // Position
+                effect.setVector3("cameraPosition", camera.globalPosition);
+
+                // Rotation
+                this._computeCameraRotation(camera);
+                effect.setVector3("cameraRotation", this._cameraRotation);
+
+                // Samplers
+                effect.setTexture("positionSampler", this._geometryRenderer.getGBuffer().textures[2]);
+
+                if (this._reflectionEnabled) {
+                    effect.setTexture("reflectionSampler", this.reflectionTexture);
+                }
+                if (this._refractionEnabled) {
+                    effect.setTexture("refractionSampler", this.refractionTexture);
+                }
+            }
+        };
+    }
+
+    /**
+     * Returns the appropriate defines according to the current configuration.
+     */
+    private _getDefines(): string {
+        const defines: string[] = [];
+
+        if (this._reflectionEnabled) {
+            defines.push("#define REFLECTION_ENABLED");
+        }
+
+        if (this._refractionEnabled) {
+            defines.push("#define REFRACTION_ENABLED");
+        }
+
+        return defines.join("\n");
+    }
+
+    /**
+     * Computes the current camera rotation as the shader requires a camera rotation.
+     */
+    private _computeCameraRotation(camera: TargetCamera): void {
+        camera.upVector.normalize();
+        const target = camera.getTarget();
+        camera._initialFocalDistance = target.subtract(camera.position).length();
+        if (camera.position.z === target.z) {
+            camera.position.z += Epsilon;
+        }
+
+        const direction = target.subtract(camera.position);
+        camera._viewMatrix.invertToRef(this._cameraViewMatrix);
+
+        this._cameraRotation.x = Math.atan(this._cameraViewMatrix.m[6] / this._cameraViewMatrix.m[10]);
+
+        if (direction.x >= 0.0) {
+            this._cameraRotation.y = (-Math.atan(direction.z / direction.x) + Math.PI / 2.0);
+        } else {
+            this._cameraRotation.y = (-Math.atan(direction.z / direction.x) - Math.PI / 2.0);
+        }
+
+        this._cameraRotation.z = 0;
+
+        if (isNaN(this._cameraRotation.x)) {
+            this._cameraRotation.x = 0;
+        }
+
+        if (isNaN(this._cameraRotation.y)) {
+            this._cameraRotation.y = 0;
+        }
+
+        if (isNaN(this._cameraRotation.z)) {
+            this._cameraRotation.z = 0;
+        }
+    }
+}