Преглед на файлове

new lens effects rendering pipeline

olivier.g преди 10 години
родител
ревизия
cd5a682213
променени са 3 файла, в които са добавени 530 реда и са изтрити 0 реда
  1. 218 0
      Babylon/PostProcess/babylon.lensRenderingPipeline.ts
  2. 50 0
      Babylon/Shaders/chromaticAberration.fragment.fx
  3. 262 0
      Babylon/Shaders/depthOfField.fragment.fx

+ 218 - 0
Babylon/PostProcess/babylon.lensRenderingPipeline.ts

@@ -0,0 +1,218 @@
+/// <reference path="babylon.d.ts" />
+
+module BABYLON {
+    export class LensRenderingPipeline extends PostProcessRenderPipeline {
+
+    	// Lens effects can be of the following:
+    	// - chromatic aberration (slight shift of RGB colors)
+    	// - blur on the edge of the lens
+    	// - lens distortion
+    	// - depth-of-field 'bokeh' effect (shapes appearing in blured areas, stronger highlights)
+    	// - grain/dust-on-lens effect
+
+    	// Two additional texture samplers are needed:
+    	// - depth map (for depth-of-field)
+    	// - grain texture
+
+        /**
+        * The chromatic aberration PostProcess id in the pipeline
+        * @type {string}
+        */
+        public LensChromaticAberrationEffect: string = "LensChromaticAberrationEffect";
+        /**
+        * The depth-of-field PostProcess id in the pipeline
+        * @type {string}
+        */
+        public LensDepthOfFieldEffect: string = "LensDepthOfFieldEffect";
+
+        private _scene: Scene;
+        private _depthTexture: RenderTargetTexture;
+        private _grainTexture: Texture;
+
+        private _chromaticAberrationPostProcess: PostProcess;
+        private _depthOfFieldPostProcess: PostProcess;
+
+        private _edgeBlur: number;
+        private _grainAmount: number;
+        private _chromaticAberration: number;
+        private _distortion: number;
+        private _highlightsGain: number;
+        private _highlightsThreshold: number;
+        private _dofDepth: number;
+        private _dofAperture: number;
+        private _dofPentagon: boolean;
+        private _blurNoise: boolean;
+
+        /**
+         * @constructor
+         * @param {string} name - The rendering pipeline name
+         * @param {object} parameters - An object containing all parameters (see below)
+         * @param {BABYLON.Scene} scene - The scene linked to this pipeline
+         * @param {number} ratio - The size of the postprocesses (0.5 means that your postprocess will have a width = canvas.width 0.5 and a height = canvas.height 0.5)
+         * @param {BABYLON.Camera[]} cameras - The array of cameras that the rendering pipeline will be attached to
+
+         	Effect parameters are as follow:
+		 	{
+				chromatic_aberration: number;		// from 0 to x (1 for realism)
+				edge_blur: number;					// from 0 to x (1 for realism)
+				distortion: number;					// from 0 to x (1 for realism)
+				grain_amount: number;				// from 0 to 1
+				grain_texture: BABYLON.Texture;		// texture to use for grain effect; if unset, use random B&W noise
+				dof_focus_depth: number;			// depth-of-field: focus depth; unset to disable
+				dof_aperture: number;				// depth-of-field: focus blur bias (default: 1)
+				dof_pentagon: boolean;				// depth-of-field: makes a pentagon-like "bokeh" effect
+				dof_gain: boolean;					// depth-of-field: depthOfField gain (default: 1)
+				dof_threshold: boolean;				// depth-of-field: depthOfField threshold (default: 1)
+				blur_noise: boolean;				// add a little bit of noise to the blur (default: true)
+			}
+
+			Note: if an effect parameter is unset, effect is disabled
+         */
+        constructor(name: string, parameters: any, scene: Scene, ratio: number = 1.0, cameras?: Camera[]) {
+			super(scene.getEngine(), name);
+
+			this._scene = scene;
+
+			// Fetch texture samplers
+			this._depthTexture = scene.enableDepthRenderer().getDepthMap(); // Force depth renderer "on"
+			if(parameters.grain_texture) { this._grainTexture = parameters.grain_texture; }
+			else { this._createGrainTexture(); }
+
+			// save parameters
+			this._edgeBlur = parameters.edge_blur ? parameters.edge_blur : 0;
+			this._grainAmount = parameters.grain_amount ? parameters.grain_amount : 0;
+			this._chromaticAberration = parameters.chromatic_aberration ? parameters.chromatic_aberration : 0;
+			this._distortion = parameters.distortion ? parameters.distortion : 0;
+			this._highlightsGain = parameters.dof_gain ? parameters.dof_gain : 1;
+			this._highlightsThreshold = parameters.dof_threshold ? parameters.dof_threshold : 1;
+			this._dofDepth = parameters.dof_focus_depth !== undefined ? parameters.dof_focus_depth : -1;
+			this._dofAperture = parameters.dof_aperture ? parameters.dof_aperture : 1;
+			this._dofPentagon = parameters.dof_pentagon !== undefined ? parameters.dof_pentagon : true;
+			this._blurNoise = parameters.blur_noise !== undefined ? parameters.blur_noise : true;
+
+			// Create effects
+            this._createChromaticAberrationPostProcess(ratio);
+            this._createDepthOfFieldPostProcess(ratio);
+
+            // Set up pipeline
+            this.addEffect(new PostProcessRenderEffect(scene.getEngine(), this.LensChromaticAberrationEffect, () => { return this._chromaticAberrationPostProcess; }, true));
+            this.addEffect(new PostProcessRenderEffect(scene.getEngine(), this.LensDepthOfFieldEffect, () => { return this._depthOfFieldPostProcess; }, true));
+
+            // Finish
+            scene.postProcessRenderPipelineManager.addPipeline(this);
+            if(cameras) {
+                scene.postProcessRenderPipelineManager.attachCamerasToRenderPipeline(name, cameras);
+            }
+        }
+
+        // public methods
+        public setEdgeBlur(amount: number) { this._edgeBlur = amount; }
+        public disableEdgeBlur() { this._edgeBlur = 0; }
+        public setGrainAmount(amount: number) { this._grainAmount = amount; }
+        public disableGrain() { this._grainAmount = 0; }
+        public setChromaticAberration(amount: number) { this._chromaticAberration = amount; }
+        public disableChromaticAberration() { this._chromaticAberration = 0; }
+        public setEdgeDistortion(amount: number) { this._distortion = amount; }
+        public disableEdgeDistortion() { this._distortion = 0; }
+        public setHighlightsGain(amount: number) { this._highlightsGain = amount; }
+        public setHighlightsThreshold(amount: number) { this._highlightsThreshold = amount; }
+        public setFocusDepth(amount: number) { this._dofDepth = amount; }
+        public disableDepthOfField() { this._dofDepth = -1; }
+        public setAperture(amount: number) { this._dofAperture = amount; }
+        public enablePentagonBokeh() { this._dofPentagon = true; }
+        public disablePentagonBokeh() { this._dofPentagon = false; }
+        public enableNoiseBlur() { this._blurNoise = true; }
+        public disableNoiseBlur() { this._blurNoise = false; }
+
+        /**
+         * Removes the internal pipeline assets and detaches the pipeline from the scene cameras
+         */
+        public dispose(disableDepthRender: boolean = false): void {
+            this._scene.postProcessRenderPipelineManager.detachCamerasFromRenderPipeline(this._name, this._scene.cameras);
+
+            // this._originalColorPostProcess = undefined;
+            this._chromaticAberrationPostProcess = undefined
+            this._depthOfFieldPostProcess = undefined;
+
+            this._grainTexture.dispose();
+
+            if (disableDepthRender)
+                this._scene.disableDepthRenderer();
+        }
+
+        // colors shifting and distortion
+        private _createChromaticAberrationPostProcess(ratio: number): void {
+            this._chromaticAberrationPostProcess = new PostProcess("LensChromaticAberration", "./chromaticAberration",
+            	["chromatic_aberration", "screen_width", "screen_height"],		// uniforms
+            	[],											// samplers
+                ratio, null, Texture.TRILINEAR_SAMPLINGMODE,
+                this._scene.getEngine(), false);
+
+            this._chromaticAberrationPostProcess.onApply = (effect: Effect) => {
+            	effect.setFloat('chromatic_aberration', this._chromaticAberration);
+                effect.setFloat('screen_width', this._scene.getEngine().getRenderWidth());
+                effect.setFloat('screen_height', this._scene.getEngine().getRenderHeight());
+            };
+        }
+
+        // colors shifting and distortion
+        private _createDepthOfFieldPostProcess(ratio: number): void {
+            this._depthOfFieldPostProcess = new PostProcess("LensDepthOfField", "./depthOfField",
+            	[
+            		"gain", "threshold", "focus_depth", "aperture", "pentagon", "maxZ", "edge_blur",
+            		"chromatic_aberration", "distortion", "blur_noise", "grain_amount", "screen_width", "screen_height"
+            	],
+            	["depthSampler", "grainSampler"],
+                ratio, null, Texture.TRILINEAR_SAMPLINGMODE,
+                this._scene.getEngine(), false);
+
+            this._depthOfFieldPostProcess.onApply = (effect: Effect) => {
+	            effect.setBool('pentagon', this._dofPentagon);
+            	effect.setBool('blur_noise', this._blurNoise);
+            	effect.setFloat('maxZ', this._scene.activeCamera.maxZ);
+            	effect.setFloat('grain_amount', this._grainAmount);
+
+            	effect.setTexture("depthSampler", this._depthTexture);
+            	effect.setTexture("grainSampler", this._grainTexture);
+
+            	effect.setFloat('screen_width', this._scene.getEngine().getRenderWidth());
+            	effect.setFloat('screen_height', this._scene.getEngine().getRenderHeight());
+
+            	effect.setFloat('distortion', this._distortion);
+
+	            effect.setFloat('focus_depth', this._dofDepth);
+	            effect.setFloat('aperture', this._dofAperture);
+            	effect.setFloat('gain', this._highlightsGain);
+            	effect.setFloat('threshold', this._highlightsThreshold);
+
+            	effect.setFloat('edge_blur', this._edgeBlur);
+            };
+        }
+
+        // creates a black and white random noise texture, 512x512
+        private _createGrainTexture(): void {
+            var size = 512;
+
+            this._grainTexture = new DynamicTexture("LensNoiseTexture", size, this._scene, false, Texture.BILINEAR_SAMPLINGMODE);
+            this._grainTexture.wrapU = Texture.WRAP_ADDRESSMODE;
+            this._grainTexture.wrapV = Texture.WRAP_ADDRESSMODE;
+
+            var context = (<BABYLON.DynamicTexture>this._grainTexture).getContext();
+
+            var rand = (min, max) => {
+                return Math.random() * (max - min) + min;
+            }
+
+            var value;
+            for (var x = 0; x < size; x++) {
+                for (var y = 0; y < size; y++) {
+                    value = Math.floor(rand(0.42,0.58)*255);
+                    context.fillStyle = 'rgb(' + value + ', ' + value + ', ' + value + ')';
+                    context.fillRect(x, y, 1, 1);
+                }
+            }
+            (<BABYLON.DynamicTexture>this._grainTexture).update(false);
+        }
+
+    }
+}

+ 50 - 0
Babylon/Shaders/chromaticAberration.fragment.fx

@@ -0,0 +1,50 @@
+/*
+	BABYLON.JS Chromatic Aberration GLSL Shader
+	Author: Olivier Guyot
+	Separates very slightly R, G and B colors on the edges of the screen
+	Inspired by Francois Tarlier & Martins Upitis	
+*/
+
+#ifdef GL_ES
+precision highp float;
+#endif
+
+// samplers
+uniform sampler2D textureSampler;	// original color
+
+// uniforms
+uniform float chromatic_aberration;
+uniform float screen_width;
+uniform float screen_height;
+
+// varyings
+varying vec2 vUV;
+
+
+void main(void)
+{
+	vec2 centered_screen_pos = vec2(vUV.x-0.5, vUV.y-0.5);
+	float radius2 = centered_screen_pos.x*centered_screen_pos.x
+					+ centered_screen_pos.y*centered_screen_pos.y;
+	float radius = sqrt(radius2);
+
+	vec4 original = texture2D(textureSampler, vUV);
+
+	if(chromatic_aberration > 0.0) {
+		//index of refraction of each color channel, causing chromatic dispersion
+		vec3 ref_indices = vec3(0.6, 0.3, 0.0);
+		float ref_shiftX = chromatic_aberration * radius * 12.0 / screen_width;
+		float ref_shiftY = chromatic_aberration * radius * 12.0 / screen_height;
+
+		// shifts for red, green & blue
+		vec2 ref_coords_r = vec2(vUV.x + ref_indices.r*ref_shiftX, vUV.y + ref_indices.r*ref_shiftY*0.5);
+		vec2 ref_coords_g = vec2(vUV.x + ref_indices.g*ref_shiftX, vUV.y + ref_indices.g*ref_shiftY*0.5);
+		vec2 ref_coords_b = vec2(vUV.x + ref_indices.b*ref_shiftX, vUV.y + ref_indices.b*ref_shiftY*0.5);
+
+		original.r = texture2D(textureSampler, ref_coords_r).r;
+		original.g = texture2D(textureSampler, ref_coords_g).g;
+		original.b = texture2D(textureSampler, ref_coords_b).b;
+	}
+
+	gl_FragColor = original;
+}

+ 262 - 0
Babylon/Shaders/depthOfField.fragment.fx

@@ -0,0 +1,262 @@
+/*
+	BABYLON.JS Depth-of-field GLSL Shader
+	Author: Olivier Guyot
+	Does depth-of-field blur, edge blur, highlights enhancing
+	Inspired by Francois Tarlier & Martins Upitis
+*/
+
+#ifdef GL_ES
+precision highp float;
+#endif
+
+
+// samplers
+uniform sampler2D textureSampler;
+uniform sampler2D depthSampler;
+uniform sampler2D grainSampler;
+
+// uniforms
+uniform float grain_amount;
+uniform bool pentagon;
+uniform float maxZ;
+uniform bool blur_noise;
+uniform float screen_width;
+uniform float screen_height;
+uniform float distortion;
+uniform float focus_depth;
+uniform float aperture;
+uniform float gain;
+uniform float threshold;
+uniform float edge_blur;
+
+// varyings
+varying vec2 vUV;
+
+// constants
+#define PI 3.14159265
+const int RING_1_SAMPLES = 4;
+const int RING_2_SAMPLES = 6;
+const int RING_3_SAMPLES = 9;
+const int RING_4_SAMPLES = 12;
+const int RING_5_SAMPLES = 16;
+//const int RING_6_SAMPLES = 15;
+const float RING_STEP_DIST = 0.4;			// a new blur ring is added each time this distance is passed
+const float PENTAGON_ANGLE_SUB = 1.2566;		// 2PI / 5
+const float PENTAGON_ANGLE_SUB_HALF = 0.6283;	// 2PI / 10
+
+// common calculations
+vec2 centered_screen_pos;
+float radius2;
+float radius;
+
+
+// applies edge distortion on texture coords
+vec2 getDistortedCoords(vec2 coords) {
+
+	if(distortion == 0.0) { return coords; }
+
+	vec2 direction = 1.0 * normalize(centered_screen_pos);
+	vec2 dist_coords = vec2(0.5, 0.5);
+	dist_coords.x = 0.5 + direction.x * radius2 * 1.0;
+	dist_coords.y = 0.5 + direction.y * radius2 * 1.0;
+	float dist_amount = clamp(distortion*0.23, 0.0, 1.0);
+
+	dist_coords = mix(coords, dist_coords, dist_amount);
+
+	return dist_coords;
+}
+
+// picks either original screen color or highlights only
+vec4 getColor(vec2 coords, bool highlight) {
+
+	vec4 color = texture2D(textureSampler, coords);
+
+	if(highlight) {
+		float luminance = dot(color.rgb, vec3(0.2125, 0.7154, 0.0721));
+		float lum_threshold;
+		if(threshold > 1.0) { lum_threshold = 0.94 + 0.01 * threshold; }
+		else { lum_threshold = 0.5 + 0.44 * threshold; }
+		if(luminance < lum_threshold) {
+			color.rgb = vec3(0.0, 0.0, 0.0);
+			color.a = 1.0;
+		}
+	}
+
+	return color;
+}
+
+// returns a modifier to be applied on the radius, in order to simulate a pentagon
+float pentagonShape(float angle) {
+    float a1 = mod(angle, PENTAGON_ANGLE_SUB) / PENTAGON_ANGLE_SUB - 0.5;
+    float a2 = 0.5 - a1 * a1;
+    return 1.35 - 0.94 * a2;
+}
+
+// returns original screen color after blur
+vec4 getBlurColor(vec2 coords, float size, bool highlight) {
+
+	float w = (size/screen_width);
+	float h = (size/screen_height);
+
+	vec4 col = getColor(coords, highlight);
+	if(size == 0.0) { return col; }
+
+	float s = 1.0;
+	float pw;			// sample x relative coord
+	float ph;			// sample y relative coord
+	float bias = 0.65;	// inner/outer ring bias
+	if(highlight) { bias = 0.95; }
+	float sample_angle;
+	float ratio_rings;
+	float ring_radius;
+	float penta;		// pentagon shape modifier
+
+	int ring_count;
+	if(size >= 6.0 * RING_STEP_DIST) { ring_count = 6; }
+	else if(size >= 5.0 * RING_STEP_DIST) { ring_count = 5; }
+	else if(size >= 4.0 * RING_STEP_DIST) { ring_count = 4; }
+	else if(size >= 3.0 * RING_STEP_DIST) { ring_count = 3; }
+	else if(size >= 2.0 * RING_STEP_DIST) { ring_count = 2; }
+	else { ring_count = 1; }
+	
+	// RING 1
+	if(size > RING_STEP_DIST) {
+		ring_radius = size / float(ring_count);
+		ratio_rings = 1.0 / float(ring_count);
+		for(int i = 0; i < RING_1_SAMPLES; i++) {
+			sample_angle = PI *2.0 * float(i) / float(RING_1_SAMPLES);
+			if(pentagon) { penta = pentagonShape(sample_angle); }
+			else { penta = 1.0; }
+			pw = cos( sample_angle ) * penta * ring_radius;
+			ph = sin( sample_angle ) * penta * ring_radius;
+			col += getColor(coords + vec2(pw*w,ph*h), highlight) * mix( 1.0, ratio_rings, bias );
+			s += 1.0 * mix(1.0, ratio_rings, bias);
+		}
+	}	
+
+	// RING 2
+	if(size > RING_STEP_DIST * 2.0) {
+		ring_radius = 2.0 * size / float(ring_count);
+		ratio_rings = 2.0 / float(ring_count);
+		for(int i = 0; i < RING_2_SAMPLES; i++) {
+			sample_angle = PI *2.0 * float(i) / float(RING_2_SAMPLES);
+			if(pentagon) { penta = pentagonShape(sample_angle); }
+			else { penta = 1.0; }
+			pw = cos( sample_angle ) * penta * ring_radius;
+			ph = sin( sample_angle ) * penta * ring_radius;
+			col += getColor(coords + vec2(pw*w,ph*h), highlight) * mix( 1.0, ratio_rings, bias );
+			s += 1.0 * mix(1.0, ratio_rings, bias);  
+		}
+	}	
+
+	// RING 3
+	if(size > RING_STEP_DIST * 3.0) {
+		ring_radius = 3.0 * size / float(ring_count);
+		ratio_rings = 3.0 / float(ring_count);
+		for(int i = 0; i < RING_3_SAMPLES; i++) {
+			sample_angle = PI *2.0 * float(i) / float(RING_3_SAMPLES);
+			if(pentagon) { penta = pentagonShape(sample_angle); }
+			else { penta = 1.0; }
+			pw = cos( sample_angle ) * penta * ring_radius;
+			ph = sin( sample_angle ) * penta * ring_radius;
+			col += getColor(coords + vec2(pw*w,ph*h), highlight) * mix( 1.0, ratio_rings, bias );
+			s += 1.0 * mix(1.0, ratio_rings, bias);  
+		}
+	}	
+
+	// RING 4
+	if(size > RING_STEP_DIST * 4.0) {
+		ring_radius = 4.0 * size / float(ring_count);
+		ratio_rings = 4.0 / float(ring_count);
+		for(int i = 0; i < RING_4_SAMPLES; i++) {
+			sample_angle = PI *2.0 * float(i) / float(RING_4_SAMPLES);
+			if(pentagon) { penta = pentagonShape(sample_angle); }
+			else { penta = 1.0; }
+			pw = cos( sample_angle ) * penta * ring_radius;
+			ph = sin( sample_angle ) * penta * ring_radius;
+			col += getColor(coords + vec2(pw*w,ph*h), highlight) * mix( 1.0, ratio_rings, bias );
+			s += 1.0 * mix(1.0, ratio_rings, bias);  
+		}
+	}	
+
+	// RING 5
+	if(size > RING_STEP_DIST * 5.0) {
+		ring_radius = 5.0 * size / float(ring_count);
+		ratio_rings = 5.0 / float(ring_count);
+		for(int i = 0; i < RING_5_SAMPLES; i++) {
+			sample_angle = PI *2.0 * float(i) / float(RING_5_SAMPLES);
+			if(pentagon) { penta = pentagonShape(sample_angle); }
+			else { penta = 1.0; }
+			pw = cos( sample_angle ) * penta * ring_radius;
+			ph = sin( sample_angle ) * penta * ring_radius;
+			col += getColor(coords + vec2(pw*w,ph*h), highlight) * mix( 1.0, ratio_rings, bias );
+			s += 1.0 * mix(1.0, ratio_rings, bias);  
+		}
+	}	
+
+	col /= s;		// scales color according to samples taken
+	col.a = 1.0;
+
+	return col;
+}
+
+// on-the-fly constant noise
+vec2 rand(vec2 co)
+{
+	float noise1 = (fract(sin(dot(co ,vec2(12.9898,78.233))) * 43758.5453));
+	float noise2 = (fract(sin(dot(co ,vec2(12.9898,78.233)*2.0)) * 43758.5453));
+	return clamp(vec2(noise1,noise2),0.0,1.0);
+}
+
+void main(void)
+{
+
+	// Common calc
+	centered_screen_pos = vec2(vUV.x-0.5, vUV.y-0.5);
+	radius2 = centered_screen_pos.x*centered_screen_pos.x + centered_screen_pos.y*centered_screen_pos.y;
+	radius = sqrt(radius2);
+
+	vec4 final_color;
+	vec2 distorted_coords = getDistortedCoords(vUV);
+	vec2 texels_coords = vec2(vUV.x * screen_width, vUV.y * screen_height);	// varies from 0 to SCREEN_WIDTH or _HEIGHT
+
+	// blur from depth of field effect
+	float dof_blur_amount = 0.0;
+	if(focus_depth != -1.0) {
+		vec4 depth_sample = texture2D(depthSampler, distorted_coords);
+		float depth = depth_sample.r;
+		dof_blur_amount = abs(depth - focus_depth) * aperture * 3.5;
+		if(dof_blur_amount < 0.05) { dof_blur_amount = 0.0; }				// no blur at all
+		else if( depth - focus_depth < 0.0 ) { dof_blur_amount *= 2.0; }	// blur more when close to camera
+		dof_blur_amount = clamp(dof_blur_amount, 0.0, 1.0);
+	}
+
+	// blur from edge blur effect
+	float edge_blur_amount = 0.0;
+	if(edge_blur > 0.0) {
+		edge_blur_amount = clamp( ( radius*2.0 - 1.0 + 0.15*edge_blur ) * 1.5 , 0.0 , 1.0 ) * 1.3;
+	}
+
+	// total blur amount
+	float blur_amount = max(edge_blur_amount, dof_blur_amount);
+
+	// apply blur if necessary
+	if(blur_amount == 0.0) {
+		gl_FragColor = getColor(distorted_coords, false);
+	} else {
+		gl_FragColor = getBlurColor(distorted_coords, blur_amount * 1.7, false)
+					   + gain * blur_amount*getBlurColor(distorted_coords, blur_amount * 2.75, true);
+
+		if(blur_noise) {
+			// we put a slight amount of noise in the blurred color
+			vec2 noise = rand(distorted_coords) * 0.01 * blur_amount;
+			vec2 blurred_coord = vec2(distorted_coords.x + noise.x, distorted_coords.y + noise.y);
+			gl_FragColor = 0.04 * getColor(blurred_coord, false) + 0.96 * gl_FragColor;
+		}
+	}
+
+	if(grain_amount > 0.0) {
+		vec4 grain_color = texture2D(grainSampler, texels_coords*0.003);
+		gl_FragColor.rgb += ( -0.5 + grain_color.rgb ) * 0.20;
+	}
+}