瀏覽代碼

Merge pull request #6538 from TrevorDev/effectRenderer

create effect renderer.
David Catuhe 6 年之前
父節點
當前提交
70764a2f82

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

@@ -22,6 +22,7 @@
 - Added support for sound sprites [Doc](https://doc.babylonjs.com/how_to/playing_sounds_and_music#playing-a-sound-sprite) ([Deltakosh](https://github.com/deltakosh/))
 - Display Oculus Quest controller when using a Quest in WebVR ([TrevorDev](https://github.com/TrevorDev))
 - Added startAndReleaseDragOnPointerEvents property to pointerDragBehavior which can be set to false for custom drag triggering ([TrevorDev](https://github.com/TrevorDev))
+- Effect renderer to render one or multiple shader effects to a texture ([TrevorDev](https://github.com/TrevorDev))
 - Added url parameters to web request modifiers ([PierreLeBlond](https://github.com/PierreLeBlond))
 
 ### Engine

+ 8 - 0
src/Engines/engine.ts

@@ -1564,6 +1564,14 @@ export class Engine {
     }
 
     /**
+     * Gets a string idenfifying the name of the class
+     * @returns "Engine" string
+     */
+    public getClassName(): string {
+        return "Engine";
+    }
+
+    /**
      * Returns true if the stencil buffer has been enabled through the creation option of the context.
      */
     public get isStencilEnable(): boolean {

+ 10 - 8
src/Materials/Textures/texture.ts

@@ -10,6 +10,7 @@ import { _AlphaState } from "../../States/index";
 import { _TypeStore } from '../../Misc/typeStore';
 import { _DevTools } from '../../Misc/devTools';
 import { IInspectable } from '../../Misc/iInspectable';
+import { Engine } from '../../Engines/engine';
 
 declare type CubeTexture = import("../../Materials/Textures/cubeTexture").CubeTexture;
 declare type MirrorTexture = import("../../Materials/Textures/mirrorTexture").MirrorTexture;
@@ -257,7 +258,7 @@ export class Texture extends BaseTexture {
      * This represents a texture in babylon. It can be easily loaded from a network, base64 or html input.
      * @see http://doc.babylonjs.com/babylon101/materials#texture
      * @param url define the url of the picture to load as a texture
-     * @param scene define the scene the texture will belong to
+     * @param scene define the scene or engine the texture will belong to
      * @param noMipmap define if the texture will require mip maps or not
      * @param invertY define if the texture needs to be inverted on the y axis during loading
      * @param samplingMode define the sampling mode we want for the texture while fectching from it (Texture.NEAREST_SAMPLINGMODE...)
@@ -267,8 +268,8 @@ export class Texture extends BaseTexture {
      * @param deleteBuffer define if the buffer we are loading the texture from should be deleted after load
      * @param format define the format of the texture we are trying to load (Engine.TEXTUREFORMAT_RGBA...)
      */
-    constructor(url: Nullable<string>, scene: Nullable<Scene>, noMipmap: boolean = false, invertY: boolean = true, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE, onLoad: Nullable<() => void> = null, onError: Nullable<(message?: string, exception?: any) => void> = null, buffer: Nullable<string | ArrayBuffer | HTMLImageElement | Blob> = null, deleteBuffer: boolean = false, format?: number) {
-        super(scene);
+    constructor(url: Nullable<string>, sceneOrEngine: Nullable<Scene | Engine>, noMipmap: boolean = false, invertY: boolean = true, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE, onLoad: Nullable<() => void> = null, onError: Nullable<(message?: string, exception?: any) => void> = null, buffer: Nullable<string | ArrayBuffer | HTMLImageElement | Blob> = null, deleteBuffer: boolean = false, format?: number) {
+        super((sceneOrEngine && sceneOrEngine.getClassName() === "Scene") ? (sceneOrEngine as Scene) : null);
 
         this.name = url || "";
         this.url = url;
@@ -281,12 +282,13 @@ export class Texture extends BaseTexture {
             this._format = format;
         }
 
-        scene = this.getScene();
+        var scene = this.getScene();
+        var engine = (sceneOrEngine && (sceneOrEngine as Engine).getCaps) ? (sceneOrEngine as Engine) : (scene ? scene.getEngine() : null);
 
-        if (!scene) {
+        if (!engine) {
             return;
         }
-        scene.getEngine().onBeforeTextureInitObservable.notifyObservers(this);
+        engine.onBeforeTextureInitObservable.notifyObservers(this);
 
         let load = () => {
             if (this._texture) {
@@ -331,8 +333,8 @@ export class Texture extends BaseTexture {
         this._texture = this._getFromCache(this.url, noMipmap, samplingMode, invertY);
 
         if (!this._texture) {
-            if (!scene.useDelayedTextureLoading) {
-                this._texture = scene.getEngine().createTexture(this.url, noMipmap, invertY, scene, samplingMode, load, onError, this._buffer, undefined, this._format);
+            if (!scene || !scene.useDelayedTextureLoading) {
+                this._texture = engine.createTexture(this.url, noMipmap, invertY, scene, samplingMode, load, onError, this._buffer, undefined, this._format);
                 if (deleteBuffer) {
                     delete this._buffer;
                 }

+ 192 - 0
src/Materials/effectRenderer.ts

@@ -0,0 +1,192 @@
+import { Nullable } from '../types';
+import { Texture } from '../Materials/Textures/texture';
+import { Engine } from '../Engines/engine';
+import { VertexBuffer } from '../Meshes/buffer';
+import { Viewport } from '../Maths/math';
+import { Constants } from '../Engines/constants';
+import { Observable } from '../Misc/observable';
+import { Effect } from './effect';
+import { DataBuffer } from '../Meshes/dataBuffer';
+
+/**
+ * Helper class to render one or more effects
+ */
+export class EffectRenderer {
+    // Fullscreen quad buffers
+    private static _Vertices = [1, 1, -1, 1, -1, -1, 1, -1];
+    private static _Indices = [0, 1, 2, 0, 2, 3];
+    private _vertexBuffers: {[key: string]: VertexBuffer};
+    private _indexBuffer: DataBuffer;
+
+    private _ringBufferIndex = 0;
+    private _ringScreenBuffer: Nullable<Array<Texture>> = null;
+
+    private _getNextFrameBuffer(incrementIndex = true) {
+        if (!this._ringScreenBuffer) {
+            this._ringScreenBuffer = [];
+            for (var i = 0; i < 2; i++) {
+                var internalTexture = this.engine.createRenderTargetTexture(
+                    {
+                        width: this.engine.getRenderWidth(true),
+                        height: this.engine.getRenderHeight(true),
+                    },
+                    {
+                        generateDepthBuffer: false,
+                        generateStencilBuffer: false,
+                        generateMipMaps: false,
+                        samplingMode: Constants.TEXTURE_NEAREST_NEAREST,
+                    },
+                );
+                var texture = new Texture("", null);
+                texture._texture = internalTexture;
+                this._ringScreenBuffer.push(texture);
+            }
+        }
+        var ret = this._ringScreenBuffer[this._ringBufferIndex];
+        if (incrementIndex) {
+            this._ringBufferIndex = (this._ringBufferIndex + 1) % 2;
+        }
+        return ret;
+    }
+
+    /**
+     * Creates an effect renderer
+     * @param engine the engine to use for rendering
+     */
+    constructor(private engine: Engine) {
+        this._vertexBuffers = {
+            [VertexBuffer.PositionKind]: new VertexBuffer(engine, EffectRenderer._Vertices, VertexBuffer.PositionKind, false, false, 2),
+        };
+        this._indexBuffer = engine.createIndexBuffer(EffectRenderer._Indices);
+    }
+
+    /**
+     * renders one or more effects to a specified texture
+     * @param effectWrappers list of effects to renderer
+     * @param outputTexture texture to draw to, if null it will render to the screen
+     */
+    render(effectWrappers: Array<EffectWrapper> | EffectWrapper, outputTexture: Nullable<Texture> = null) {
+        if (!Array.isArray(effectWrappers)) {
+            effectWrappers = [effectWrappers];
+        }
+
+        // Ensure all effects are ready
+        for (var wrapper of effectWrappers) {
+            if (!wrapper.effect.isReady()) {
+                return;
+            }
+        }
+
+        effectWrappers.forEach((effectWrapper, i) => {
+            var renderTo = outputTexture;
+
+            // for any next effect make it's input the output of the previous effect
+            if (i !== 0) {
+                effectWrapper.effect.onBindObservable.addOnce(() => {
+                    effectWrapper.effect.setTexture("textureSampler", this._getNextFrameBuffer(false));
+                });
+            }
+
+            // Set the output to the next screenbuffer
+            if ((effectWrappers as Array<EffectWrapper>).length > 1 && i != (effectWrappers as Array<EffectWrapper>).length - 1) {
+                renderTo = this._getNextFrameBuffer();
+            }else {
+                renderTo = outputTexture;
+            }
+
+            // Reset state
+            this.engine.setViewport(new Viewport(0, 0, 1, 1));
+            this.engine.enableEffect(effectWrapper.effect);
+
+            // Bind buffers
+            if (renderTo) {
+                this.engine.bindFramebuffer(renderTo.getInternalTexture()!);
+            }
+            this.engine.bindBuffers(this._vertexBuffers, this._indexBuffer, effectWrapper.effect);
+            effectWrapper.onApplyObservable.notifyObservers({});
+
+            // Render
+            this.engine.drawElementsType(Constants.MATERIAL_TriangleFillMode, 0, 6);
+            if (renderTo) {
+                this.engine.unBindFramebuffer(renderTo.getInternalTexture()!);
+            }
+        });
+    }
+
+    /**
+     * Disposes of the effect renderer
+     */
+    dispose() {
+        if (this._ringScreenBuffer) {
+            this._ringScreenBuffer.forEach((b) => {
+                b.dispose();
+            });
+            this._ringScreenBuffer = null;
+        }
+
+        var vertexBuffer = this._vertexBuffers[VertexBuffer.PositionKind];
+        if (vertexBuffer) {
+            vertexBuffer.dispose();
+            delete this._vertexBuffers[VertexBuffer.PositionKind];
+        }
+
+        if (this._indexBuffer) {
+            this.engine._releaseBuffer(this._indexBuffer);
+        }
+    }
+}
+
+/**
+ * Options to create an EffectWrapper
+ */
+interface EffectWrapperCreationOptions {
+    /**
+     * Engine to use to create the effect
+     */
+    engine: Engine;
+    /**
+     * Fragment shader for the effect
+     */
+    fragmentShader: string;
+    /**
+     * Attributes to use in the shader
+     */
+    attributeNames: Array<string>;
+    /**
+     * Uniforms to use in the shader
+     */
+    uniformNames: Array<string>;
+    /**
+     * Texture sampler names to use in the shader
+     */
+    samplerNames: Array<string>;
+}
+
+/**
+ * Wraps an effect to be used for rendering
+ */
+export class EffectWrapper {
+    /**
+     * Event that is fired right before the effect is drawn (should be used to update uniforms)
+     */
+    public onApplyObservable = new Observable<{}>();
+    /**
+     * The underlying effect
+     */
+    public effect: Effect;
+
+    /**
+     * Creates an effect to be renderer
+     * @param creationOptions options to create the effect
+     */
+    constructor(creationOptions: EffectWrapperCreationOptions) {
+        this.effect = new Effect({fragmentSource: creationOptions.fragmentShader, vertex: "postprocess"}, creationOptions.attributeNames, creationOptions.uniformNames, creationOptions.samplerNames, creationOptions.engine);
+    }
+
+     /**
+     * Disposes of the effect wrapper
+     */
+    public dispose() {
+        this.effect.dispose();
+    }
+}

+ 2 - 1
src/Materials/index.ts

@@ -14,4 +14,5 @@ export * from "./standardMaterial";
 export * from "./Textures/index";
 export * from "./uniformBuffer";
 export * from "./materialFlags";
-export * from "./Node/index";
+export * from "./Node/index";
+export * from "./effectRenderer";