ThreeJs學(xué)習(xí)筆記——渲染(render)分析

一虏冻、前言

ThreeJs 封裝了 WebGL 進(jìn)行渲染時所涉及到的相關(guān)概念,如光照弹囚,材質(zhì)厨相,紋理以及相機(jī)等。除此之外,其還抽象了場景(Scene)以及用于渲染的渲染器(WebGLRenderer)蛮穿。這些相關(guān)概念都被封裝成了一個對象庶骄,那么它們是如何協(xié)作的呢,關(guān)系又是如何呢践磅?這篇文章主要就是來分析一下 ThreeJs 中核心中的核心单刁,即場景,物體府适,光照羔飞,材質(zhì),紋理以及相機(jī)這些對象是如何渲染的檐春。

下面截取了一個渲染效果圖逻淌,看起來還不錯是不是。這是 ThreeJs 的官方 demo lights / spotlights 的渲染效果圖疟暖。這個 demo 中就基本涉及到了上面所提的核心對象卡儒,下面我將基于此 demo 來分析這些核心對象是如何被組織在一起進(jìn)行渲染的。

SpotLight.gif

二誓篱、demo 解析

Demo 有一點(diǎn)點(diǎn)長朋贬,對于不熟悉 ThreeJs 的人來說會有一點(diǎn)點(diǎn)難度,因此這里主要分析了構(gòu)建窜骄、初始化以及渲染 3 個部分來分別說明锦募。

1.構(gòu)建

// 構(gòu)建渲染器 WebGLRenderer            
var renderer = new THREE.WebGLRenderer();
// 設(shè)置顯示比例
renderer.setPixelRatio( window.devicePixelRatio );
// 構(gòu)建一個透視投影的相機(jī)
var camera = new THREE.PerspectiveCamera( 35, window.innerWidth / window.innerHeight, 1, 2000 );
// 構(gòu)建一個軌道控制器,主要就是通過鼠標(biāo)來控制相機(jī)沿目標(biāo)物體旋轉(zhuǎn)邻遏,從而達(dá)到像在旋轉(zhuǎn)場景一樣糠亩,可以從各個不同角度觀察物體
var controls = new THREE.OrbitControls( camera, renderer.domElement );
// 構(gòu)建場景
var scene = new THREE.Scene();
// 構(gòu)建Phong網(wǎng)格材質(zhì)MeshPhongMaterial,該材質(zhì)可以模擬具有鏡面高光的光澤表面准验,一個用于接收陰影的平面赎线,一個用于場景中的物體 Box
var matFloor = new THREE.MeshPhongMaterial();
var matBox = new THREE.MeshPhongMaterial( { color: 0xaaaaaa } );
// 構(gòu)建幾何體,同樣分別用于 平面 和 Box
var geoFloor = new THREE.PlaneBufferGeometry( 2000, 2000 );
var geoBox = new THREE.BoxBufferGeometry( 3, 1, 2 );
// 構(gòu)建平面網(wǎng)格 mesh
var mshFloor = new THREE.Mesh( geoFloor, matFloor );
mshFloor.rotation.x = - Math.PI * 0.5;
// 構(gòu)建 box 網(wǎng)格 mesh
var mshBox = new THREE.Mesh( geoBox, matBox );
// 構(gòu)建環(huán)境光
var ambient = new THREE.AmbientLight( 0x111111 );
// 構(gòu)建 3 個不同顏色的 聚光燈(SpotLight)
var spotLight1 = createSpotlight( 0xFF7F00 );
var spotLight2 = createSpotlight( 0x00FF7F );
var spotLight3 = createSpotlight( 0x7F00FF );
// 聲明用于描述聚光燈的 3 個不同光束幫助器
var lightHelper1, lightHelper2, lightHelper3;

上面代碼中糊饱,基本上每一行都添加了詳細(xì)的注釋垂寥,其中有調(diào)用了一個內(nèi)部的函數(shù) createSpotlight() ,如下另锋。

 function createSpotlight( color ) {

    var newObj = new THREE.SpotLight( color, 2 );

    newObj.castShadow = true;
    newObj.angle = 0.3;
    newObj.penumbra = 0.2;
    newObj.decay = 2;
    newObj.distance = 50;

    newObj.shadow.mapSize.width = 1024;
    newObj.shadow.mapSize.height = 1024;

    return newObj;

}

這個方法滞项,主要就是根據(jù)指定的顏色構(gòu)建一個聚光燈并設(shè)置好相應(yīng)的參數(shù)。這里不管是相機(jī)夭坪、光照文判、材質(zhì)還是物體,其詳細(xì)的參數(shù)并不打算在這里一一講述室梅,有需要的話再進(jìn)一步說明戏仓。

2.初始化

function init() {
    ......

    // 將平面疚宇,box,環(huán)境光以及光源輔助器等全部添加到 scene 中
    scene.add( mshFloor );
    scene.add( mshBox );
    scene.add( ambient );
    scene.add( spotLight1, spotLight2, spotLight3 );
    scene.add( lightHelper1, lightHelper2, lightHelper3 );

    document.body.appendChild( renderer.domElement );
    onResize();
    window.addEventListener( 'resize', onResize, false );

    controls.target.set( 0, 7, 0 );
    controls.maxPolarAngle = Math.PI / 2;
    controls.update();

}

初始化主要就是將平面赏殃,box 敷待,光照這些都添加進(jìn)場景中,但是要注意嗓奢,相機(jī)并沒有被添加進(jìn)來讼撒。

3.渲染

function render() {

    TWEEN.update();

    if ( lightHelper1 ) lightHelper1.update();
    if ( lightHelper2 ) lightHelper2.update();
    if ( lightHelper3 ) lightHelper3.update();

    renderer.render( scene, camera );

    requestAnimationFrame( render );

}

渲染函數(shù) render() 中最關(guān)鍵的調(diào)用渲染器的 WebGLRenderer#render() 方法同時去渲染場景和相機(jī)浑厚。

根據(jù)上面的分析股耽,以及對 ThreeJs 源碼的分析,梳理出如下 2 個類圖關(guān)系钳幅。


WebGLRenderer.jpg

圖中物蝙,渲染器負(fù)責(zé)同時渲染場景以及相機(jī)。而光照和網(wǎng)格都被添加到場景中敢艰。幾何體以及材質(zhì)都是網(wǎng)格的 2 個基本屬性诬乞,也決定一個網(wǎng)格的形狀和表面紋理。

RenderObject.jpg

該圖是對上圖的補(bǔ)充钠导,說明光照震嫉,相機(jī)以及網(wǎng)格都屬于 Object3D 對象。在 ThreeJs 中還有許多的類都是繼承自 Object3D 的牡属。

三票堵、渲染分析

1.關(guān)于 WebGL 需要知道的基本知識

1.1 WebGL 的渲染管線

先來看一下 WebGL 的流水線渲染管線圖,如下所示逮栅。這個是必須要了解的悴势,我們可以不必完全理解渲染管線的每個步驟,但我們必須要知道渲染管線的這個流程措伐。

WebGL 流水線

渲染管線指的是WebGL程序的執(zhí)行過程特纤,如上圖所示,主要分為 4 個步驟:

  1. 頂點(diǎn)著色器的處理侥加,主要是一組矩陣變換操作捧存,用來把3D模型(頂點(diǎn)和原型)投影到viewport上,輸出是一個個的多邊形担败,比如三角形昔穴。

  2. 光柵化,也就是把三角形連接區(qū)域按一定的粒度逐行轉(zhuǎn)化成片元(fragement)氢架,類似于2D空間中傻咖,可以把這些片元看做是3D空間的一個像素點(diǎn)。

  3. 片元著色器的處理岖研,為每個片元添加顏色或者紋理卿操。只要給出紋理或者顏色警检,以及紋理坐標(biāo)(uv),管線就會根據(jù)紋理坐標(biāo)進(jìn)行插值運(yùn)算害淤,將紋理或者圖片著色在相應(yīng)的片元上扇雕。

  4. 把3D空間的片元合并輸出為2D像素數(shù)組并顯示在屏幕上。

1.2 WebGL 一般的開發(fā)流程

因為作者也沒進(jìn)行過原生的 WebGL 開發(fā)窥摄,而是一上來就擼起了 ThreeJs镶奉。所以 這里僅根據(jù) Open GL ES 的開發(fā)流程,繪制出如下流程圖崭放。

OpenGL ES 開發(fā)流程圖.jpg

流程圖中關(guān)鍵的第一步在于創(chuàng)建著色器(Shader)程序哨苛,著色器程序主要用 GLSL(GL Shading Language) 語言編寫,其直接由 GPU 來執(zhí)行币砂。第二步是設(shè)置頂點(diǎn)建峭,紋理以及其他屬性,如我們創(chuàng)建的幾何圖元 Box决摧,加載的 obj 文件亿蒸,以及用于矩陣變換的模型矩陣,視圖矩陣以及投影矩陣等掌桩。第三步便是進(jìn)行頂點(diǎn)的繪制边锁,如以點(diǎn)繪制,以直線繪制以及以三角形繪制波岛,對于圖元茅坛,大部分是以三角形繪制。

1.3 坐標(biāo)系以及矩陣變換

關(guān)于坐標(biāo)系與矩陣變換盆色,這里一個幅圖總結(jié)的很不錯灰蛙,畫的很詳細(xì),一眼就能看出其中的意思隔躲。

坐標(biāo)系與矩陣變換

關(guān)于 WebGL 的基本就介紹這么多摩梧,這里的目的是為了讓后面的分析有個簡單的鋪墊。如果感興趣宣旱,可以參考更多大牛專門介紹 WebGL / Open GL ES 的文章仅父。

2.渲染器 WebGLRenderer 的初始化

WebGLRenderer 的初始化主要在它的構(gòu)造方法 WebGLRenderer() 和 initGLContext() 中。這里先看看構(gòu)造方法 WebGLRenderer() 浑吟。

2.1 構(gòu)造方法 WebGLRenderer()

其初始化的屬性很多笙纤。這里主要關(guān)注其 2 個最核心的屬性 canvas 以及 context。

function WebGLRenderer( parameters ) {

    console.log( 'THREE.WebGLRenderer', REVISION );

    parameters = parameters || {};
    // 如果參數(shù)中有 canvas组力,就有參數(shù)中的省容,如果沒有就通過 document.createElementNS() 來創(chuàng)建一個。和 2D 的概念一樣燎字,這里的 canvas 主要是用來進(jìn)行 3D 渲染的畫布腥椒。
    var _canvas = parameters.canvas !== undefined ? parameters.canvas : document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' ),
        _context = parameters.context !== undefined ? parameters.context : null,
    ......
    // initialize

    var _gl;
    ......
    // 從 canvas 中獲取 context阿宅。參數(shù) webgl 是其中之一,其還可以獲取 2d 的笼蛛。這里獲取到 webgl 的 context洒放,那就意味者可以通過它進(jìn)行 3D 繪制了。
    _gl = _context || _canvas.getContext( 'webgl', contextAttributes ) || _canvas.getContext( 'experimental-webgl', contextAttributes );
    ......
    function initGLContext() {
        ......
        _this.context = _gl;
        ......
    }
    ......
}

如上面的代碼以及注釋滨砍,canvas 就是 html 的標(biāo)準(zhǔn)元素<canvas>往湿,這玩意兒在 Android 那里也叫做 canvas,反正就代表畫布的意思惋戏。而 context 則是從該畫布獲取到的 'webgl' 上下文领追,這個上下文是 WebGLRenderingContext,WebGLRenderingContext 接口提供基于 OpenGL ES 2.0 的繪圖上下文日川,用于在 HTML <canvas> 元素內(nèi)繪圖蔓腐。后續(xù)的關(guān)于 Open GL ES 的相關(guān)操作都是基于此進(jìn)行的。當(dāng)然龄句,這里還只是創(chuàng)建了用于 Open GL ES 的 WebGLContext,還沒有進(jìn)行初始化散罕。下面再來詳細(xì)看看它在 initGLContext() 方法中是如何進(jìn)行初始化的分歇,初始化中具體又詳細(xì)做了什么具體的事情。

2.2 初始化上下文方法 initGLContext()

function initGLContext() {

        /**
         * 擴(kuò)展特性
         */
        extensions = new WebGLExtensions( _gl );

        capabilities = new WebGLCapabilities( _gl, extensions, parameters );

        if ( ! capabilities.isWebGL2 ) {

            extensions.get( 'WEBGL_depth_texture' );
            extensions.get( 'OES_texture_float' );
            extensions.get( 'OES_texture_half_float' );
            extensions.get( 'OES_texture_half_float_linear' );
            extensions.get( 'OES_standard_derivatives' );
            extensions.get( 'OES_element_index_uint' );
            extensions.get( 'ANGLE_instanced_arrays' );

        }

        extensions.get( 'OES_texture_float_linear' );
        /**
         * 工具類
         */
        utils = new WebGLUtils( _gl, extensions, capabilities );
        /**
         * 狀態(tài)
         */
        state = new WebGLState( _gl, extensions, utils, capabilities );
        state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ) );
        state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ) );

        info = new WebGLInfo( _gl );
        properties = new WebGLProperties();
        /**
         * 紋理輔助類
         */
        textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info );
        /**
         * 屬性存儲輔助類欧漱,主要實現(xiàn) JavaScript 中的變量或者數(shù)組职抡、紋理圖片傳遞到 WebGL 中
         */
        attributes = new WebGLAttributes( _gl );
        /**
         * 幾何圖元
         */
        geometries = new WebGLGeometries( _gl, attributes, info );
        /**
         * Object 類存儲類
         */
        objects = new WebGLObjects( geometries, info );
        morphtargets = new WebGLMorphtargets( _gl );
        /**
         * WebGL program
         */
        programCache = new WebGLPrograms( _this, extensions, capabilities );
        renderLists = new WebGLRenderLists();
        renderStates = new WebGLRenderStates();
        /**
         * 背景
         */
        background = new WebGLBackground( _this, state, objects, _premultipliedAlpha );
        /**
         * Buffer
         */
        bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info, capabilities );
        indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info, capabilities );

        info.programs = programCache.programs;

        _this.context = _gl;
        _this.capabilities = capabilities;
        _this.extensions = extensions;
        _this.properties = properties;
        _this.renderLists = renderLists;
        _this.state = state;
        _this.info = info;

}

initGLContext() 方法中初始化了很多的組件,有的組件很容易就能看出來是作什么用的误甚,而有的組件可能就沒那么好知道意思缚甩,需要等到具體分析 render() 方法時,用到時再來理解窑邦。不過擅威,雖然 initGLContext() 方法中看起來有很多的組件初始化,但實質(zhì)這些組件也只是進(jìn)行一個最基本的構(gòu)造而已冈钦,沒有進(jìn)一步更深入的過程郊丛。因此,這里也粗略的看一下即可瞧筛。

2.WebGLRenderer#render() 函數(shù)分析

整個 render 的過程是十分復(fù)雜的厉熟,也是漫長的,需要我們耐心去看较幌,去理解揍瑟。先來簡單過一下它的時序圖。


Render時序圖.jpg

從時序圖可見乍炉,其涉及到的相對象以及步驟是比較多的绢片,共 20 步嘁字。其中涉及到的主要對象有:Scene,Camera,WebGLRenderStates,WebGLRenderLists,WebGLBackground,WebGLProgram,_gl,WebGLBufferRenderer。我們比較熟悉的是 Scene杉畜,因為我們的Object / Mesh 都是被添加到它里面的纪蜒,另外還有 Camera,我們必須要有一個相機(jī)來告訴我們以怎么樣的視角來觀看這個 3D 世界此叠。另外一些不熟悉的對象纯续,WebGLRenderList 管理著我們需要拿去 render 的 Object / Mesh,WebGLBackground 描述了場景的背景灭袁,WebGLProgram 則創(chuàng)建了用于鏈接猬错、執(zhí)行 Shader 的程序,而 WebGLBufferRenderer 則是整個 3D 世界被 render 到的目的地茸歧。
這里不會按照時序圖倦炒,逐步逐步地進(jìn)行分析,而是挑重點(diǎn)软瞎,同時保持與前面所述的 OpenGL ES 的流程一致性上進(jìn)行分析逢唤。

render() 函數(shù)

this.render = function ( scene, camera, renderTarget, forceClear ) {
        // 前面是一些參數(shù)的校驗,這里省略
        // 1.reset caching for this frame
        ......
        // 2.update scene graph

        if ( scene.autoUpdate === true ) scene.updateMatrixWorld();

        // 3.update camera matrices and frustum

        if ( camera.parent === null ) camera.updateMatrixWorld();

        .....

        // 4. init WebGLRenderState

        currentRenderState = renderStates.get( scene, camera );
        currentRenderState.init();

        scene.onBeforeRender( _this, scene, camera, renderTarget );
        // 5.視景體矩陣計算涤浇,為相機(jī)的投影矩陣與相機(jī)的世界矩陣的逆矩陣的叉乘鳖藕?
        _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
        _frustum.setFromMatrix( _projScreenMatrix );
          
        _localClippingEnabled = this.localClippingEnabled;
        _clippingEnabled = _clipping.init( this.clippingPlanes, _localClippingEnabled, camera );
       // 6.WebGLRenderList 的初始化
        currentRenderList = renderLists.get( scene, camera );
        currentRenderList.init();

        projectObject( scene, camera, _this.sortObjects );

        ......

        // 7. shadow 的繪制

        if ( _clippingEnabled ) _clipping.beginShadows();

        var shadowsArray = currentRenderState.state.shadowsArray;

        shadowMap.render( shadowsArray, scene, camera );

        currentRenderState.setupLights( camera );

        if ( _clippingEnabled ) _clipping.endShadows();

        //

        if ( this.info.autoReset ) this.info.reset();

        if ( renderTarget === undefined ) {

            renderTarget = null;

        }

        this.setRenderTarget( renderTarget );

        // 8.背景的繪制

        background.render( currentRenderList, scene, camera, forceClear );

        // 9.render scene

        var opaqueObjects = currentRenderList.opaque;
        var transparentObjects = currentRenderList.transparent;

        if ( scene.overrideMaterial ) {
                        // 10.強(qiáng)制使用場景的材質(zhì) overrideMaterial 來統(tǒng)一 render 物體。
            var overrideMaterial = scene.overrideMaterial;

            if ( opaqueObjects.length ) renderObjects( opaqueObjects, scene, camera, overrideMaterial );
            if ( transparentObjects.length ) renderObjects( transparentObjects, scene, camera, overrideMaterial );

        } else {
                        // 11.分別對 opaque 和 transparent 的物體進(jìn)行 render
            // opaque pass (front-to-back order)

            if ( opaqueObjects.length ) renderObjects( opaqueObjects, scene, camera );

            // transparent pass (back-to-front order)

            if ( transparentObjects.length ) renderObjects( transparentObjects, scene, camera );

        }

        // Generate mipmap if we're using any kind of mipmap filtering
                .....

        // Ensure depth buffer writing is enabled so it can be cleared on next render

        state.buffers.depth.setTest( true );
        state.buffers.depth.setMask( true );
        state.buffers.color.setMask( true );

        state.setPolygonOffset( false );

        scene.onAfterRender( _this, scene, camera );

        ......
        currentRenderList = null;
        currentRenderState = null;

    };

render() 是渲染的核心只锭,粗略地看它做了大概以下的事情著恩。

  1. reset caching for this frame。
  2. update scene graph蜻展。
  3. update camera matrices and frustum喉誊。
  4. init WebGLRenderState。
  5. 視景體矩陣計算纵顾,為相機(jī)的投影矩陣與相機(jī)的世界矩陣的逆矩陣的叉乘伍茄。
  6. WebGLRenderList 的初始化。
  7. shadow 的繪制片挂。
  8. 背景的繪制幻林。
  9. render scene。
  10. 如果overrideMaterial音念,則強(qiáng)制使用場景的材質(zhì) overrideMaterial 來統(tǒng)一 render 物體沪饺。
  11. 分別對 opaque 和 transparent 的物體進(jìn)行 render。

但這里我們不必關(guān)注每個處理的細(xì)節(jié)闷愤,僅從幾個重要的點(diǎn)著手去理解以及分析整葡。

2.1 update scene graph

即更新整個場景圖,主要就是更新每個物體的 matrix讥脐。如果其含有孩子節(jié)點(diǎn)遭居,則還會逐級更新啼器。在這里,每個物體的 matrix 是通過其 position,quaternion以及scale 計算得來的俱萍,也就是其模型矩陣端壳,而 matrixWorld 又是根據(jù) matrix 計算得來的。如果當(dāng)前節(jié)點(diǎn)沒有父節(jié)點(diǎn)枪蘑,則 matrix 就是 matrixWorld损谦。而如果有的話,那 matrixWorld 則為父節(jié)點(diǎn)的 matrixWorld 與當(dāng)前節(jié)點(diǎn) matrix 的叉乘岳颇。也就是說當(dāng)前節(jié)點(diǎn)的 matrixWorld 是相對于其父親節(jié)點(diǎn)的照捡。

2.2 WebGLRenderList 的初始化

WebGLRenderList 的初始化init()方法本身并沒有什么,其只是在 WebGLRenderLists 中通過將 scene.id 和 camera.id 建立起一定的關(guān)聯(lián)话侧。而這里更重要的目的是確定有哪些對象是要被渲染出來的栗精,這個最主要的實現(xiàn)就在 projectObject() 方法中。

function projectObject( object, camera, sortObjects ) {

        if ( object.visible === false ) return;

        var visible = object.layers.test( camera.layers );

        if ( visible ) {
            // 是否為光照
            if ( object.isLight ) {

                currentRenderState.pushLight( object );

                ......

            } else if ( object.isSprite ) {
                // 是否為精靈
                if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) {

                    ......

                    currentRenderList.push( object, geometry, material, _vector3.z, null );

                }

            } else if ( object.isImmediateRenderObject ) {
                // 是否為立即要渲染的 Object
                ......

                currentRenderList.push( object, null, object.material, _vector3.z, null );

            } else if ( object.isMesh || object.isLine || object.isPoints ) {
                // 是否為 mesh,line,points 
                ......
                if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) {

                    ......

                    if ( Array.isArray( material ) ) {

                        var groups = geometry.groups;

                        for ( var i = 0, l = groups.length; i < l; i ++ ) {

                            ......

                            if ( groupMaterial && groupMaterial.visible ) {

                                currentRenderList.push( object, geometry, groupMaterial, _vector3.z, group );

                            }

                        }

                    } else if ( material.visible ) {

                         // 可見即可渲染

                        currentRenderList.push( object, geometry, material, _vector3.z, null );

                    }

                }

            }

        }

        // 對每個孩子進(jìn)行遞歸遍歷

        var children = object.children;

        for ( var i = 0, l = children.length; i < l; i ++ ) {

            projectObject( children[ i ], camera, sortObjects );

        }

    }

從方法中瞻鹏,我們大致得到如下結(jié)論:

  1. 只有可見的光照悲立,精靈,mesh乙漓,line级历,point 會被實際渲染出來。而如果我們只是 new 一個 Object3D 而被指到具體的 3D 對象上叭披,那么理論上它是不會被渲染的。
  2. 光照與其他 Object3D 不一樣玩讳,它是另外單獨(dú)被放在 currentRenderState 中的涩蜘。
  3. 對于整個要渲染的場景圖利用遞歸進(jìn)行遍歷,以確保場景圖中的每一個可渲染的 3D object 都可以被渲染出來熏纯。這里簡單回顧一下同诫,Sence 也是繼承自 Object3D 的,而燈光以及可被渲染的 Object 都是作為它的孩子被加入到 Sence 中的樟澜。

通過 WebGLRenderList 的初始化基本就確定了當(dāng)前哪些 Object3D 對象是需要渲染的误窖,接下來就是逐個 Object3D 的渲染了。

2.3 renderObjects

function renderObjects( renderList, scene, camera, overrideMaterial ) {

        for ( var i = 0, l = renderList.length; i < l; i ++ ) {

            var renderItem = renderList[ i ];

            ......

            if ( camera.isArrayCamera ) {

                ......

            } else {

                _currentArrayCamera = null;

                renderObject( object, scene, camera, geometry, material, group );

            }

        }

    }

renderObjects 就是遍歷所有的 Object3D 對象秩贰,然后調(diào)用 renderObject() 方法進(jìn)行進(jìn)一步渲染霹俺。看來臟活都交給了 renderObject()毒费。

2.4 renderObject

function renderObject( object, scene, camera, geometry, material, group ) {

        ......
        // 計算 mode view matrix 以及 normal matrix
        object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
        object.normalMatrix.getNormalMatrix( object.modelViewMatrix );

        if ( object.isImmediateRenderObject ) {

            ......

        } else {

            _this.renderBufferDirect( camera, scene.fog, geometry, material, object, group );

        }

        ......

    }

關(guān)于計算 mode view matrix 以及 normal matrix丙唧,這里我也不太看明白,所以我選擇先跳過觅玻。先分析后面的步驟想际。這里不管是否 isImmediateRenderObject 其流程上差不太多培漏,所以這里先分析 renderBufferDirect()。

renderBufferDirect()方法

this.renderBufferDirect = function ( camera, fog, geometry, material, object, group ) {
        ......
        // 1.通過WebGLState設(shè)置材質(zhì)的一些屬性
        state.setMaterial( material, frontFaceCW );
        // 2.設(shè)置 program
        var program = setProgram( camera, fog, material, object );
        ......
        if ( updateBuffers ) {
        // 3.設(shè)置頂點(diǎn)屬性
            setupVertexAttributes( material, program, geometry );
            if ( index !== null ) {
              // 4.綁定 buffer
                _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, attribute.buffer );
            }
        }
        ......
        // 5.根據(jù)不同網(wǎng)格類型確定相應(yīng)的繪制模式
        if ( object.isMesh ) {
            if ( material.wireframe === true ) {
                ......
                renderer.setMode( _gl.LINES );
            } else {
                switch ( object.drawMode ) {
                    case TrianglesDrawMode:
                        renderer.setMode( _gl.TRIANGLES );
                        break;
                    case TriangleStripDrawMode:
                        renderer.setMode( _gl.TRIANGLE_STRIP );
                        break;
                    case TriangleFanDrawMode:
                        renderer.setMode( _gl.TRIANGLE_FAN );
                        break;
                }
            }
        } else if ( object.isLine ) {
            ......
            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 ( geometry && geometry.isInstancedBufferGeometry ) {
            if ( geometry.maxInstancedCount > 0 ) {
                renderer.renderInstances( geometry, drawStart, drawCount );
            }
        } else {
            // 6.調(diào)用 WebGLBufferRenderer#render() 方法進(jìn)行渲染
            renderer.render( drawStart, drawCount );
        }
    };

renderBufferDirect()方法是一個比較重要的方法胡本,在這里可以看到一個物體被渲染的“最小完整流程”牌柄。

  1. 通過WebGLState設(shè)置材質(zhì)的一些屬性。這個比較形象侧甫,因為整個 OpenGL / ES 它就是一個狀態(tài)機(jī)珊佣。這里所設(shè)置的材質(zhì)屬性也是直接調(diào)用底層的 gl_xxx() 之類的方法。而這里實際就是設(shè)置了如 CULL_FACE,depthTest,depthWrite,colorWrite 等等闺骚。
  2. 設(shè)置 program彩扔。
function setProgram( camera, fog, material, object ) {
  .....
  .....
  var materialProperties = properties.get( material );
  var lights = currentRenderState.state.lights;
  if ( material.needsUpdate ) {
    initMaterial( material, fog, object );
    material.needsUpdate = false;
  }
  ......
  // 這里的 program 即 WebGLProgram,也就是我們在流程圖中所說的創(chuàng)建程序
  var program = materialProperties.program,
    p_uniforms = program.getUniforms(),
    m_uniforms = materialProperties.shader.uniforms;
  if ( state.useProgram( program.program ) ) {
        refreshProgram = true;
        refreshMaterial = true;
        refreshLights = true;
  }
  ......
  p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix );
  p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix );
  p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld );
  return program;
}

這個方法本身是很長的僻爽,這里省略了一萬字....
我們再來看看其主要所做的事情虫碉,這里的 program 就是 WebGLProgram。而想知道 program 具體是什么胸梆,這里就涉及到了 WebGLProgram 的初始化敦捧。

function WebGLProgram( renderer, extensions, code, material, shader, parameters, capabilities ) {
    var gl = renderer.context;
    var defines = material.defines;
    // 獲取頂點(diǎn) shader 以及片元 shader
    var vertexShader = shader.vertexShader;
    var fragmentShader = shader.fragmentShader;
    ......
    // 創(chuàng)建 program 
    var program = gl.createProgram();
    ......
    // 構(gòu)造最終用于進(jìn)行渲染的 glsl,并且調(diào)用 WebGLShader 構(gòu)造出 shader
    var vertexGlsl = prefixVertex + vertexShader;
    var fragmentGlsl = prefixFragment + fragmentShader;
    // console.log( '*VERTEX*', vertexGlsl );
    // console.log( '*FRAGMENT*', fragmentGlsl );
    var glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl );
    var glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl );
    // 將 program 關(guān)聯(lián) shader
    gl.attachShader( program, glVertexShader );
    gl.attachShader( program, glFragmentShader );
    ......
    // 鏈接 program
    gl.linkProgram( program );
    ......
}

program 的初始化方法也是非常多的碰镜,這里簡化出關(guān)鍵部分兢卵。再回憶一下前面的流程圖,就會明白這里主要就是創(chuàng)建 program绪颖、shader 秽荤,關(guān)聯(lián) program 和 shader,以及鏈接程序柠横。鏈接好了程序之后接下來就可以通過 useProgram() 使用 program 了窃款,這一步驟在 setProgram() 中創(chuàng)建好 program 就調(diào)用了。

  1. 設(shè)置頂點(diǎn)屬性牍氛,就是將我們在外面所構(gòu)造的 geometry 的頂點(diǎn)送到 shader 中去晨继。
  2. 綁定 buffer。
  3. 根據(jù)不同網(wǎng)格類型確定相應(yīng)的繪制模式搬俊,如以 LINES 進(jìn)行繪制紊扬,以TRIANGLES 進(jìn)行繪制。
  4. 調(diào)用 WebGLBufferRenderer#render() 方法進(jìn)行渲染唉擂。如下餐屎,就是進(jìn)行最后的 drawArrays() 調(diào)用,將上層創(chuàng)建的 geometry 以及 material(組合起來就叫做 mesh) 渲染到 3D 場景的 canvas 中楔敌。
function render( start, count ) {

    gl.drawArrays( mode, start, count );
    info.update( count, mode );

}

四啤挎、總結(jié)

文章同樣以一篇 demo 為入口對渲染過程進(jìn)行了一個簡要的分析,其中還介紹了 OpenGL / WebGL 所需要知道的基礎(chǔ)知識。這其中了解了 OpenGL 的繪制流程以及各坐標(biāo)系之間的關(guān)系以及轉(zhuǎn)換庆聘,而后面的分析都是沿著這個繪制流程進(jìn)行的胜臊。

然而,由于作者的水平有限伙判,而 OpenGL / WebGL 又是如此的強(qiáng)大象对,實在不能面面俱到,甚至對某些知識點(diǎn)也無法透徹分析宴抚。因此勒魔,還請見諒。

最后菇曲,感謝你能讀到并讀完此文章冠绢,如果分析的過程中存在錯誤或者疑問都?xì)g迎留言討論。如果我的分享能夠幫助到你常潮,還請記得幫忙點(diǎn)個贊吧弟胀,謝謝。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末喊式,一起剝皮案震驚了整個濱河市孵户,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌岔留,老刑警劉巖夏哭,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異献联,居然都是意外死亡竖配,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門里逆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來械念,“玉大人,你說我怎么就攤上這事运悲。” “怎么了项钮?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵班眯,是天一觀的道長。 經(jīng)常有香客問我烁巫,道長署隘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任亚隙,我火速辦了婚禮磁餐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己诊霹,他們只是感情好羞延,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著脾还,像睡著了一般伴箩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鄙漏,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天嗤谚,我揣著相機(jī)與錄音,去河邊找鬼怔蚌。 笑死巩步,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的桦踊。 我是一名探鬼主播椅野,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼钞钙!你這毒婦竟也來了鳄橘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤芒炼,失蹤者是張志新(化名)和其女友劉穎瘫怜,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體本刽,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鲸湃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了子寓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片暗挑。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖斜友,靈堂內(nèi)的尸體忽然破棺而出炸裆,到底是詐尸還是另有隱情,我是刑警寧澤鲜屏,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布烹看,位于F島的核電站,受9級特大地震影響洛史,放射性物質(zhì)發(fā)生泄漏惯殊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一也殖、第九天 我趴在偏房一處隱蔽的房頂上張望土思。 院中可真熱鬧,春花似錦、人聲如沸己儒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽址愿。三九已至该镣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間响谓,已是汗流浹背损合。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留娘纷,地道東北人嫁审。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像赖晶,于是被迫代替她去往敵國和親律适。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 池邊枯柳吐新芽遏插, 新燕銜泥入人家捂贿。 日暖攜友踏山青, 潑墨絲竹會春茶胳嘲。
    墨道塵傷閱讀 277評論 0 3
  • 第一章 鈴鈴鈴~鈴鈴鈴·~ 鬧鐘不停地呼喚著我起床厂僧,我迷迷糊糊的,老半天才摸著鬧鐘把它關(guān)了了牛。開始起床颜屠,隨便穿...
    帥云吞1閱讀 141評論 0 0
  • 有時候我們需要swiper的切換效果及一些方便的屬性方法,但是又不想用戶手指滑動觸發(fā)鹰祸。那么該怎么來阻止用戶的手動觸...
    三人_閱讀 4,752評論 0 0
  • 給之前一輛水箱進(jìn)油的C7換防凍液甫窟,之前為了清理管路,給加的清水蛙婴,這次放水粗井,有了一個大膽的想法,不泄壓街图,把水管卡子剝...
    京心達(dá)查曉旭閱讀 199評論 0 0