ThreeJS 渲染管线

1,602 阅读3分钟

渲染过程1

  • 在Three.js的渲染中,大概可以分为以下几步:
    • 清空当前帧缓冲区,更新MVP矩阵;
    • 将物体分为透明和不透明两类,按照离摄像机从近到远排序(也可在Object3D单独设置renderOrder);
    • 根据灯光信息,阴影计算,如果有开启平面裁剪就对进行剪裁;
    • 开始逐个渲染物体,按以下顺序,背景、不透明物体、透明物体;
    • 渲染前后还有两个类似于生命周期的回调函数,scene.onBeforeRender和scene.onAfterRender;
    • 最后将深度、模版测试、多边形偏移恢复默认。
  • 分开透明物体和非透明物体的原因
    • 一、为了最大限度地避免overdraw,一个重要的优化策略就是控制绘制顺序。由于深度测试的存在,如果我们可以保证物体都是从前往后绘制的,那么就可以很大程度上减少overdraw。这是因为,在后面绘制的物体由于无法通过深度测试,因此,就不会再进行后面的渲染处理。
    • 二、把透明物体抽出来最后渲染,是为了和不透明物体进行颜色混合。
  • 代码分五步
    • 获取webgl上下文
    • 初始化着色器代码
    • 创建指定类型的着色器代码,上传source源码,并编译
    • 创建着色器程序,添加着色器,链接webgl
    • 初始化着色器缓冲区
    • 创建缓冲区,绑定到程序,并推入数据(position、normal、uv、color以及MVP矩阵、灯光信息等数据)
    • 将缓冲区写入着色器指定属性,并激活
    • 纹理绑定
    • 绘制(选择模式、设置顶点个数)
  • 关于物体
    • renderObjects方法:
      • 遍历渲染列表,如果是 摄像机阵列,再遍历摄像机,将object, scene, camera, geometry, material, group丢给renderObject处理单个物体。
    • renderObject方法:
      • renderObject就一个作用,判断物体是否为立即渲染物体(object.isImmediateRenderObject):
    • setProgram方法
      • 在material变化时调用initMaterial初始化着色器,每种Material有自己对应的shader代码(通过WebGLPrograms获得,这是threejs内置的),在根据environment、fog、envMap、needsLights等不同设置,会再拼接其他的shader代码,最后组成一个完整的shader代码,传入一些需要的uniform数据(包括绑定纹理),把这个program挂在materia,materia存入WebGLProperties对象里。
      • 解决了指定不同的着色器代码、推入不同的数据(除了顶点相关),绑定纹理的问题。
    • 立即渲染物体和普通物体表现在代码上的区别
      • 普通物体会在第一次绘制保存下position、normal、uv和color数据,下一帧绘制,如果没有改变,直接bindVertexArrayOES,就可以了,不用一个个bindBuffer。采用VAO的方式可以降低数据传输,在大场景绘制时,可以提高性能。
this.renderBufferDirect = function ( camera, scene, geometry, material, object, group ) {
        if ( scene === null ) scene = _emptyScene; // renderBufferDirect second parameter used to be fog (could be null)
        const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 );
        // 获取着色器程序
        const program = setProgram( camera, scene, material, object );
        // 设置绘制所需状态
        state.setMaterial( material, frontFaceCW );
        // 获取索引和顶点数据
        let index = geometry.index;
        const position = geometry.attributes.position;
        // 判断有无索引和顶点数据
        if ( index === null ) {
                if ( position === undefined || position.count === 0 ) return;
        } else if ( index.count === 0 ) {
                return;
        }
        // 线框模式
        let rangeFactor = 1;
        if ( material.wireframe === true ) {
                index = geometries.getWireframeAttribute( geometry );
                rangeFactor = 2;
        }
        //设置变形动画
        if ( material.morphTargets || material.morphNormals ) {
                morphtargets.update( object, geometry, material, program );
        }
        //绑定顶点数据
        bindingStates.setup( object, material, program, geometry, index );
        let attribute;
        let renderer = bufferRenderer;
        if ( index !== null ) {
                attribute = attributes.get( index );
                renderer = indexedBufferRenderer;
                renderer.setIndex( attribute );
        }
        //计算需要绘制顶点个数
        const dataCount = ( index !== null ) ? index.count : position.count;
        const rangeStart = geometry.drawRange.start * rangeFactor;
        const rangeCount = geometry.drawRange.count * rangeFactor;
        const groupStart = group !== null ? group.start * rangeFactor : 0;
        const groupCount = group !== null ? group.count * rangeFactor : Infinity;
        const drawStart = Math.max( rangeStart, groupStart );
        const drawEnd = Math.min( dataCount, rangeStart + rangeCount, groupStart + groupCount ) - 1;
        const drawCount = Math.max( 0, drawEnd - drawStart + 1 );
        if ( drawCount === 0 ) return;
        //设置绘制模式和绘制方法
        if ( object.isMesh ) {
                if ( material.wireframe === true ) {
                        state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() );
                        renderer.setMode( _gl.LINES );
                } else {
                        renderer.setMode( _gl.TRIANGLES );
                }
        } else if ( object.isLine ) {
                let lineWidth = material.linewidth;
                if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material
                state.setLineWidth( lineWidth * getTargetPixelRatio() );
                if ( object.isLineSegments ) {
                        renderer.setMode( _gl.LINES );
                } else if ( object.isLineLoop ) {
                        renderer.setMode( _gl.LINE_LOOP );
                } else {
                        renderer.setMode( _gl.LINE_STRIP );
                }
        } else if ( object.isPoints ) {
                renderer.setMode( _gl.POINTS );
        } else if ( object.isSprite ) {
                renderer.setMode( _gl.TRIANGLES );
        }
        if ( object.isInstancedMesh ) {
                renderer.renderInstances( drawStart, drawCount, object.count );
        } else if ( geometry.isInstancedBufferGeometry ) {
                const instanceCount = Math.min( geometry.instanceCount, geometry._maxInstanceCount );
                renderer.renderInstances( drawStart, drawCount, instanceCount );
        } else {
                renderer.render( drawStart, drawCount );
        }
};