定制着色器和渲染后期处理

Posted Heavi的博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了定制着色器和渲染后期处理相关的知识,希望对你有一定的参考价值。

1.设置后期处理

    设置Three.js库为后期处理做准备,我们需要通过以下步骤对当前的配置进行修改:

    1)创建一个EffectComposer(效果组合器)对象,然后在该对象上添加后期处理通道。

    2)配置该对象,使它可以渲染我们的场景,并应用额外的后期处理步骤。

    3)在render循环中,使用EffectComposer渲染场景、应用通道,并输出结果。

   要使用后期处理,需要引入一些javaSscript文件。这些文件可以在Three.js发布包里找到。路径是examples/js/postprocessing和example/js/shaders。至少包含下面的文件:

<script type="text/javascript" src="../libs/postprocessing/ShaderPass.js"></script>
    <script type="text/javascript" src="../libs/shaders/CopyShader.js"></script>

    <script type="text/javascript" src="../libs/postprocessing/EffectComposer.js"></script>

    <script type="text/javascript" src="../libs/postprocessing/MaskPass.js"></script>
    <script type="text/javascript" src="../libs/postprocessing/FilmPass.js"></script>
    <script type="text/javascript" src="../libs/shaders/FilmShader.js"></script>
    <script type="text/javascript" src="../libs/postprocessing/RenderPass.js"></script>

    首先我们创建一个EffectComposer对象,你可以在这个对象的构造函数里出入WebGL-Renderer,如下所示:

var composer = new THREE.EffectComposer(webGLRenderer);

    接下来我们要在这个组合器中添加各种通道。第一个要加入的通道是RenderPass。这个通道会渲染场景,但不会讲渲染结果输出到屏幕上。

var renderPass = new THREE.RenderPass(scene, camera);
...
var composer = new THREE.EffectComposer(webGLRenderer);
 composer.addPass(renderPass);

    接下来我们要添加一个可以将结果输出到屏幕上的通道。这里使用FilmPass,我们想创建该对象,然后添加到效果组合器中。代码如下:

var effectFilm = new THREE.FilmPass(0.8, 0.325, 256, false);
effectFilm.renderToScreen = true;
var composer = new THREE.EffectComposer(webGLRenderer);
composer.addPass(effectFilm);

    正如你所看到的,我们创建了一个FilmPass对象,并将它的renderToScreen属性设置为true。最后,还需要修渲染循环:

function render(){
                stats.update();

                var delta = clock.getDelta();
                orbitControl.update(delta);

                sphere.rotation.y += 0.002;

                requestAnimationFrame(render);
                composer.render(delta);
            }

    这个代码里我们移出了“webGLRenderer.render(scene, camera);”,用“composer.render(delta)”代替。这将调用EffectComposer的render()函数。由于我们已经将FilmPass的renderToScreen属性设置为true,所以FilmPass的结果将会输出到屏幕上。

2.后期处理通道

    Three.js库提供了几个后期处理通道,你可以直接将其添加到EffectComposer对象。下表是这些通道的概览。

    通道/描述

    BloomPass/该通道会使得明亮区域渗入较暗的区域。模拟相机找到过多亮点的情形

    DotScreenPass/将一层黑点贴到代表原始图片的屏幕上

    FilmPass/通过扫描线和失真模拟电视屏幕

    MaskPass/在当前图片上贴一层掩膜,后续通道只会影响被贴的区域

    RenderPass/该通道在指定的场景和相机的基础上渲染出一个新场景

    ShaderPass/使用该通道你可以传入一个自定义的着色器,用来生成高级的、自定义的后期处理通道

    TexturePass/该通道可以将效果组合器的当前状态保存为一个纹理,然后可以在其他EffectComposer对象中将该纹理作为输入参数

    下面的例子使用了以上通道的中的BloomPass、DotScreenPass、FilmPass、RenderPass、ShaderPass、TexturePass。首先看下运行效果:

image

    上图标左上角运用BloomPass、右上角运用FilmPass、左下角运用DotScreenPass、右下角运用原始渲染结果。渲染处理代码如下:

var renderPass = new THREE.RenderPass(scene, camera); //保存渲染结果,但不会输出到屏幕
        var effectCopy = new THREE.ShaderPass(THREE.CopyShader); //传入了CopyShader着色器,用于拷贝渲染结果
        effectCopy.renderToScreen = true;  //设置输出到屏幕上

        var bloomPass = new THREE.BloomPass(3, 25, 5.0, 256); //BloomPass通道效果
        var effectFilm = new THREE.FilmPass(0.8, 0.325, 256, false); //FilmPass通道效果
        effectFilm.renderToScreen = true; //设置输出到屏幕上

        var dotScreenPass = new THREE.DotScreenPass(); // DotScrrenPass通道效果

        //渲染目标
        var composer = new THREE.EffectComposer(webGLRenderer);
        composer.addPass(renderPass);
        composer.addPass(effectCopy);

        var renderScene = new THREE.TexturePass(composer.renderTarget2);
        //左下角
        var composer1 = new THREE.EffectComposer(webGLRenderer);
        composer1.addPass(renderScene);
        composer1.addPass(dotScreenPass);
        composer1.addPass(effectCopy);
        //右下角
        var composer2  = new THREE.EffectComposer(webGLRenderer);
        composer2.addPass(renderScene);
        composer2.addPass(effectCopy);

        //左上角
        var composer3 = new THREE.EffectComposer(webGLRenderer);
        composer3.addPass(renderScene);
        composer3.addPass(bloomPass);
        composer3.addPass(effectCopy);

        //右上角
        var composer4 = new THREE.EffectComposer(webGLRenderer);
        composer4.addPass(renderScene);
        composer4.addPass(effectFilm);      
 
 

    代码原理比较简单,composer的作用是渲染最原始的效果,传入了renderPass和ShaderPass,renderPass运来创建一个新场景,ShaderPass用来输出结果到屏幕。接下来创建了一个类型为TexturePass的通道对象renderScene。可以将当前状态保存一份作为纹理。供后面的几个compoer使用。

    composer1首先使用renderScene创建新场景,然后添加dotScreenPass通道,最后使用effectCopy输出渲染。composer2、composer3、composer4相似。设置了处理通道后,在每次循环渲染时还得分别调用每个composer的render函数重新渲染:

function render() {
            stats.update();

            //sphere.rotation.y=step+=0.01;
            var delta = clock.getDelta();
            orbitControls.update(delta);

            sphere.rotation.y += 0.002;

            // render using requestAnimationFrame
            requestAnimationFrame(render);


            webGLRenderer.autoClear = false;
            webGLRenderer.clear();

            webGLRenderer.setViewport(0, 0, 2 * halfWidth, 2 * halfHeight);
            composer.render(delta);

            webGLRenderer.setViewport(0, 0, halfWidth, halfHeight);
            composer1.render(delta);

            webGLRenderer.setViewport(halfWidth, 0, halfWidth, halfHeight);
            composer2.render(delta);

            webGLRenderer.setViewport(0, halfHeight, halfWidth, halfHeight);
            composer3.render(delta);

            webGLRenderer.setViewport(halfWidth, halfHeight, halfWidth, halfHeight);
            composer4.render(delta);
        }

3.各种通道属性

    FilmPass用来创建类似电视的效果。通道属性的属性可通过构造函数传入,也可以直接修改uiniforms上对应的属性。属性说明如下:

    属性/描述

    noiseIntensity/通过该属性可以控制屏幕的颗粒程度

    scanlineIntensity/FilmPass会在屏幕上添加一些扫描线。通过该属性,可以指定扫描线的显著程度

    scanlineCount/该属性可以控制显示出来的扫描线数量

    grayscale/如果设置为true,输出结果将会转换为灰度图

    BoomPass用来在场景中添加泛光效果。属性可通过构造函数传入,也可直接修改uniforms属性。属性说明如下:
    属性/描述

    Strength/该属性定义的是泛光效果强度。值越高,明亮区域越明亮。而且渗入较暗区域的也就越多

    kernelSize/该属性控制的是泛光效果的偏移量

    sigma/可以控制泛光的锐利程度。值越高,泛光越模糊

    resolution/泛光效果的解析图。值太低,那么结果的方块会比较严重

    DotSreenPass用来将场景输出成点集。属性如下:

    center/通过center属性,可以微调点的偏移量

    angle/通过angle,可以更改对齐方式

    scale/该属性设置所有点的大小。scale越小,则点越大

4.使用掩膜的高级效果组合器

    Three.js库具有在特定区域应用通道的能力。我们采取如下步骤实现一个特定区域效果。例如下图的地图和火星。我们想在火星上应用一个彩色效果、在地球上应用褐色效果。我们采取如下步骤:

    1)创建一个座位背景图的场景。

    2)创建一个场景,里边有一个看上去像地球的球体。

    3)创建一个场景,里边有一个看上去像火星的球体。

    4)创建一个EffectComposer对象,将这三个场景渲染到一个图片里。

    5)在渲染成火星的球体上应用一个彩色效果。

    6)在渲染成地球的球体上应用褐色效果。

    下面是初始化的代码:

var sceneEarth = new THREE.Scene();
        var sceneMars = new THREE.Scene();
        var sceneBG = new THREE.Scene();

        var camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 1000);
        var cameraBG = new THREE.OrthographicCamera(-window.innerWidth, window.innerWidth, window.innerHeight, -window.innerHeight, -10000, 10000);
        cameraBG.position.z = 50;

        var webGLRenderer = new THREE.WebGLRenderer();
        webGLRenderer.setClearColor(new THREE.Color(0x000, 1.0));
        webGLRenderer.setSize(window.innerWidth, window.innerHeight);
        webGLRenderer.shadowMapEnabled = true;

        var sphere = createEarthMesh(new THREE.SphereGeometry(10, 40, 40));
        sphere.position.x = -10;
        var sphere2 = createMarshMesh(new THREE.SphereGeometry(5, 40, 40));
        sphere2.position.x = 10;
        sceneEarth.add(sphere);
        sceneMars.add(sphere2);

        camera.position.set(-10, 15, 25);

        camera.lookAt(new THREE.Vector3(0, 0, 0));

        var orbitControls = new THREE.OrbitControls(camera);
        orbitControls.autoRotate = false;
        var clock = new THREE.Clock();

        var ambi = new THREE.AmbientLight(0x181818);
        var ambi2 = new THREE.AmbientLight(0x181818);
        sceneEarth.add(ambi);
        sceneMars.add(ambi2);

        var spotLight = new THREE.DirectionalLight(0xffffff);
        spotLight.position.set(550, 100, 550);
        spotLight.intensity = 0.6;

        var spotLight2 = new THREE.DirectionalLight(0xffffff);
        spotLight2.position.set(550, 100, 550);
        spotLight2.intensity = 0.6;

        sceneEarth.add(spotLight);
        sceneMars.add(spotLight2);

        var materialColor = new THREE.MeshBasicMaterial({
            map: THREE.ImageUtils.loadTexture("../assets/textures/starry-deep-outer-space-galaxy.jpg"),
            depthWrite: false
        });

        var bgPlane = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), materialColor);
        bgPlane.position.z = -100;
        bgPlane.scale.set(window.innerWidth * 2, window.innerHeight * 2, 1);
        sceneBG.add(bgPlane);

        // add the output of the renderer to the html element
        document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

        var bgPass = new THREE.RenderPass(sceneBG, cameraBG);
        var renderPass = new THREE.RenderPass(sceneEarth, camera);
        renderPass.clear = false;
        var renderPass2 = new THREE.RenderPass(sceneMars, camera);
        renderPass2.clear = false;

        var effectCopy = new THREE.ShaderPass(THREE.CopyShader);
        effectCopy.renderToScreen = true;

        var clearMash = new THREE.ClearMaskPass();
        var earthMask = new THREE.MaskPass(sceneEarth, camera);
        var marsMask = new THREE.MaskPass(sceneMars, camera);
        var effectSepia = new THREE.ShaderPass(THREE.SepiaShader);
        effectSepia.uniforms["amount"].value = 0.8;

        var effectColorify = new THREE.ShaderPass(THREE.ColorifyShader);
        effectColorify.uniforms["color"].value.setRGB(0.5, 0.5, 1);

        var composer = new THREE.EffectComposer(webGLRenderer);
        composer.renderTarget1.stencilBuffer = true;
        composer.renderTarget2.stencilBuffer = true;

        composer.addPass(bgPass); // 添加背景渲染新场景通道
        composer.addPass(renderPass); // 添加地球渲染的新场景通道
        composer.addPass(renderPass2); // 添加月球渲染的新场景通道
        composer.addPass(marsMask); // 添加月球掩膜通道,以后所有的通道效果都只对月球有效,直到clearMask通道
        composer.addPass(effectColorify); // 添加颜色着色器通道
        composer.addPass(clearMash); //清理掩膜通道
        composer.addPass(earthMask); //添加地图掩膜通道,之后的所有通道效果都只对月球有效,直到clearMash通道
        composer.addPass(effectSepia); // 添加一种自定义着色器通道
        composer.addPass(clearMash); // 清理掩膜通道
        composer.addPass(effectCopy);

    下面是渲染代码:

function render() {
            webGLRenderer.autoClear = false;

            stats.update();

            var delta = clock.getDelta();
            orbitControls.update(delta);

            sphere.rotation.y += 0.002;
            sphere2.rotation.y += 0.002;

            requestAnimationFrame(render);
            composer.render(delta);
        }

5.用ShaderPass定制效果

    通过ShaderPass,我们可以传递一个自定义的着色器,将大量额外的效果应用到场景中。Three.js扩展的着色器主要分为简单着色器、模糊着色器、高级效果着色器。

    简单着色器列表:

    MirrorShader/该着色器可以为部分屏幕创建镜面效果

    HueStaturationShader/该着色器可以改变颜色的色调和饱和度

    VignetteShader/该着色器可以添加晕映效果。该效果可以在图片中央的周围显示黑色的边框

    ColorCorrectionShader/通过这个着色器,你可以调整颜色的分布

    RGBShiftSader/该着色器可以将构成颜色的红、绿、蓝分开

    BrightnessContrasShader/该着色器可以更改图片的亮度和对比度

    ColorifyShader/可以在屏幕上蒙上一层颜色

    SepiaShader/可以在屏幕上创建出类似乌贼墨的效果

    模糊效果着色器:

    HorizontalBlurShader和VerticalBlurShader/这两个着色器在场景中应用模糊效果

    HorizontalTiltShiftShader和VerticalTiltShiftShader/这两个着色器可以创建出移轴效果。在移轴效果中只有部分图片显示得比较锐利,从而创建出一个看上去像是微缩景观的场景

    TriangleBlurShader/该着色器使用基于三角形的方法,咱场景中应用模糊效果

    高级效果的着色器:

    BleachBypassShader/该着色器可以创建一种漂白效果。在该效果下,图片上像素是镀了一层银

   EdgeShader/该着色器可以探测图片中的锐利边界,并突出显示这些边界

    FXAAShader/该着色器可以在后期处理阶段应用锯齿效果。如果在渲染抗锯齿影响效率,那么久可以使用该着色器

    FocusShader/这是一个简单的着色器,其结果是中央区域渲染得比较锐利,但周围比较模糊

6.定制自定义着色器

    为Three.js库创建自定义的着色器,需要实现两个组件:vertexShader和fragmentShader。组件vertexShader可以用来调整每个顶点的位置,组件fragmentShader可以从来决定每个像素的颜色。对于后期处理着色器来说,我们只要实现fragmentShader即可,然后使用Three.js提供的额、默认的vertexShader。

    我们以定制一个灰度着色器为例。首先要创建一个js文件,存放着色器源代码,这里我们命名一个custom-shader.js文件。内容如下:

THREE.CustomGrayScaleShader = {
    uniforms: {
        "tDiffuse": {type: "t", value: null},
        "rPower": {type: "f", value: 0.2126},
        "gPower": {type: "f", value: 0.7152},
        "bPower": {type: "f", value: 0.0722}
    },
    vertexShader: [
        "varying vec2 vUv;",
        "void main(){",
        "vUv = uv;",
        "gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);",
        "}"
    ].join("\\n"),
    fragmentShader: [
        "uniform float rPower;",
        "uniform float gPower;",
        "uniform float bPower;",
        "uniform sampler2D tDiffuse;",
        "varying vec2 vUv;",
        "void main(){",
        "vec4 texel = texture2D(tDiffuse, vUv);",
        "float gray = texel.r * rPower + texel.g * gPower + texel.b * bPower;",
        "gl_FragColor = vec4(vec3(gray), texel.a);",
        "}"
    ].join("\\n")
}

    我们为Threee.js创建了一个叫做CustomGrayScaleShader的自定义着色器该对象包含uniforms、vertexShader和fragmentShader三个属性。uniforms定义了通过javascript外部传入的变量,vertexShader定义了顶点着色器,fragmentShader定义了片元着色器代码。

    这里需要注意的是顶点着色器中的uv变量,是着色器代码的内部变量,表示纹理上的texel(纹理上的像素),铜鼓varying vec2 vUv变量传递给片元着色器。着色器代码定义好后,我们就可以通过以下形式在javascript代码中使用了:

var renderPass = new THREE.RenderPass(scene, camera);
        var effectCopy = new THREE.ShaderPass(THREE.CopyShader);
        effectCopy.renderToScreen = true;
        var shaderPass = new THREE.ShaderPass(THREE.CustomGrayScaleShader);
        shaderPass.enabled = false;

        var bitPass = new THREE.ShaderPass(THREE.CustomBitShader);
        bitPass.enabled = false;

        var composer = new THREE.EffectComposer(webGLRenderer);
        composer.addPass(renderPass);
        composer.addPass(shaderPass);
        composer.addPass(bitPass);
        composer.addPass(effectCopy);

    代码中创建了两个着色器通道,分别适合shaderPass和bitPass。shaderPass正好使用的是我们创建的自定义着色器THREE.CustomGrayScaleShader。

以上是关于定制着色器和渲染后期处理的主要内容,如果未能解决你的问题,请参考以下文章

Unity中Shader的三种基本类型

跨多个着色器的 OpenGL 统一

片段着色器是不是处理来自顶点着色器的所有像素?

在GLSL ES中的片段着色器上旋转纹理

oc渲染器提示着色器失败

使用vue学习three.js之渲染后期处理-使用MirrorShader定制效果实现镜像效果