ThreeJs學習筆記——渲染(render)分析

1、前言

ThreeJs 封裝了 WebGL 進行渲染時所涉及到的相關概念,如光照,材質,紋理以及相機等。除此以外,其還抽象了場景(Scene)以及用於渲染的渲染器(WebGLRenderer)。這些相關概念都被封裝成了一個對象,那麼它們是如何協做的呢,關係又是如何呢?這篇文章主要就是來分析一下 ThreeJs 中核心中的核心,即場景,物體,光照,材質,紋理以及相機這些對象是如何渲染的。html

下面截取了一個渲染效果圖,看起來還不錯是否是。這是 ThreeJs 的官方 demo lights / spotlights 的渲染效果圖。這個 demo 中就基本涉及到了上面所提的核心對象,下面我將基於此 demo 來分析這些核心對象是如何被組織在一塊兒進行渲染的。web

SpotLight.gif

2、demo 解析

Demo 有一點點長,對於不熟悉 ThreeJs 的人來講會有一點點難度,所以這裏主要分析了構建、初始化以及渲染 3 個部分來分別說明。canvas

1.構建

// 構建渲染器 WebGLRenderer            
var renderer = new THREE.WebGLRenderer();
// 設置顯示比例
renderer.setPixelRatio( window.devicePixelRatio );
// 構建一個透視投影的相機
var camera = new THREE.PerspectiveCamera( 35, window.innerWidth / window.innerHeight, 1, 2000 );
// 構建一個軌道控制器,主要就是經過鼠標來控制相機沿目標物體旋轉,從而達到像在旋轉場景同樣,能夠從各個不一樣角度觀察物體
var controls = new THREE.OrbitControls( camera, renderer.domElement );
// 構建場景
var scene = new THREE.Scene();
// 構建Phong網格材質MeshPhongMaterial,該材質能夠模擬具備鏡面高光的光澤表面,一個用於接收陰影的平面,一個用於場景中的物體 Box
var matFloor = new THREE.MeshPhongMaterial();
var matBox = new THREE.MeshPhongMaterial( { color: 0xaaaaaa } );
// 構建幾何體,一樣分別用於 平面 和 Box
var geoFloor = new THREE.PlaneBufferGeometry( 2000, 2000 );
var geoBox = new THREE.BoxBufferGeometry( 3, 1, 2 );
// 構建平面網格 mesh
var mshFloor = new THREE.Mesh( geoFloor, matFloor );
mshFloor.rotation.x = - Math.PI * 0.5;
// 構建 box 網格 mesh
var mshBox = new THREE.Mesh( geoBox, matBox );
// 構建環境光
var ambient = new THREE.AmbientLight( 0x111111 );
// 構建 3 個不一樣顏色的 聚光燈(SpotLight)
var spotLight1 = createSpotlight( 0xFF7F00 );
var spotLight2 = createSpotlight( 0x00FF7F );
var spotLight3 = createSpotlight( 0x7F00FF );
// 聲明用於描述聚光燈的 3 個不一樣光束幫助器
var lightHelper1, lightHelper2, lightHelper3;
複製代碼

上面代碼中,基本上每一行都添加了詳細的註釋,其中有調用了一個內部的函數 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;

}
複製代碼

這個方法,主要就是根據指定的顏色構建一個聚光燈並設置好相應的參數。這裏不論是相機、光照、材質仍是物體,其詳細的參數並不打算在這裏一一講述,有須要的話再進一步說明。bash

2.初始化

function init() {
	......

    // 將平面,box,環境光以及光源輔助器等所有添加到 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 ,光照這些都添加進場景中,可是要注意,相機並無被添加進來。app

3.渲染

function render() {

	TWEEN.update();

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

	renderer.render( scene, camera );

	requestAnimationFrame( render );

}
複製代碼

渲染函數 render() 中最關鍵的調用渲染器的 WebGLRenderer#render() 方法同時去渲染場景和相機。dom

根據上面的分析,以及對 ThreeJs 源碼的分析,梳理出以下 2 個類圖關係。 ide

WebGLRenderer.jpg

圖中,渲染器負責同時渲染場景以及相機。而光照和網格都被添加到場景中。幾何體以及材質都是網格的 2 個基本屬性,也決定一個網格的形狀和表面紋理。函數

RenderObject.jpg

該圖是對上圖的補充,說明光照,相機以及網格都屬於 Object3D 對象。在 ThreeJs 中還有許多的類都是繼承自 Object3D 的。工具

3、渲染分析

1.關於 WebGL 須要知道的基本知識

1.1 WebGL 的渲染管線

先來看一下 WebGL 的流水線渲染管線圖,以下所示。這個是必需要了解的,咱們能夠沒必要徹底理解渲染管線的每一個步驟,但咱們必需要知道渲染管線的這個流程。

WebGL 流水線

渲染管線指的是WebGL程序的執行過程,如上圖所示,主要分爲 4 個步驟:

  1. 頂點着色器的處理,主要是一組矩陣變換操做,用來把3D模型(頂點和原型)投影到viewport上,輸出是一個個的多邊形,好比三角形。

  2. 光柵化,也就是把三角形鏈接區域按必定的粒度逐行轉化成片元(fragement),相似於2D空間中,能夠把這些片元看作是3D空間的一個像素點。

  3. 片元着色器的處理,爲每一個片元添加顏色或者紋理。只要給出紋理或者顏色,以及紋理座標(uv),管線就會根據紋理座標進行插值運算,將紋理或者圖片着色在相應的片元上。

  4. 把3D空間的片元合併輸出爲2D像素數組並顯示在屏幕上。

1.2 WebGL 通常的開發流程

由於做者也沒進行過原生的 WebGL 開發,而是一上來就擼起了 ThreeJs。因此 這裏僅根據 Open GL ES 的開發流程,繪製出以下流程圖。

OpenGL ES  開發流程圖.jpg

流程圖中關鍵的第一步在於建立着色器(Shader)程序,着色器程序主要用 GLSL(GL Shading Language) 語言編寫,其直接由 GPU 來執行。第二步是設置頂點,紋理以及其餘屬性,如咱們建立的幾何圖元 Box,加載的 obj 文件,以及用於矩陣變換的模型矩陣,視圖矩陣以及投影矩陣等。第三步即是進行頂點的繪製,如以點繪製,以直線繪製以及以三角形繪製,對於圖元,大部分是以三角形繪製。

1.3 座標系以及矩陣變換

關於座標系與矩陣變換,這裏一個幅圖總結的很不錯,畫的很詳細,一眼就能看出其中的意思。

座標系與矩陣變換

關於 WebGL 的基本就介紹這麼多,這裏的目的是爲了讓後面的分析有個簡單的鋪墊。若是感興趣,能夠參考更多大牛專門介紹 WebGL / Open GL ES 的文章。

2.渲染器 WebGLRenderer 的初始化

WebGLRenderer 的初始化主要在它的構造方法 WebGLRenderer() 和 initGLContext() 中。這裏先看看構造方法 WebGLRenderer() 。

####2.1 構造方法 WebGLRenderer() 其初始化的屬性不少。這裏主要關注其 2 個最核心的屬性 canvas 以及 context。

function WebGLRenderer( parameters ) {

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

	parameters = parameters || {};
    // 若是參數中有 canvas,就有參數中的,若是沒有就經過 document.createElementNS() 來建立一個。和 2D 的概念同樣,這裏的 canvas 主要是用來進行 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。參數 webgl 是其中之一,其還能夠獲取 2d 的。這裏獲取到 webgl 的 context,那就意味者能夠經過它進行 3D 繪製了。
	_gl = _context || _canvas.getContext( 'webgl', contextAttributes ) || _canvas.getContext( 'experimental-webgl', contextAttributes );
    ......
    function initGLContext() {
        ......
        _this.context = _gl;
        ......
    }
    ......
}
複製代碼

如上面的代碼以及註釋,canvas 就是 html 的標準元素 ,這玩意兒在 Android 那裏也叫作 canvas,反正就表明畫布的意思。而 context 則是從該畫布獲取到的 'webgl' 上下文,這個上下文是 WebGLRenderingContext,WebGLRenderingContext 接口提供基於 OpenGL ES 2.0 的繪圖上下文,用於在 HTML 元素內繪圖。後續的關於 Open GL ES 的相關操做都是基於此進行的。固然,這裏還只是建立了用於 Open GL ES 的 WebGLContext,尚未進行初始化。下面再來詳細看看它在 initGLContext() 方法中是如何進行初始化的,初始化中具體又詳細作了什麼具體的事情。

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

function initGLContext() {

		/**
		 * 擴展特性
		 */
		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 );
		/**
		 * 狀態
		 */
		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 );
		/**
		 * 屬性存儲輔助類,主要實現 JavaScript 中的變量或者數組、紋理圖片傳遞到 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() 方法中看起來有不少的組件初始化,但實質這些組件也只是進行一個最基本的構造而已,沒有進一步更深刻的過程。所以,這裏也粗略的看一下便可。

2.WebGLRenderer#render() 函數分析

整個 render 的過程是十分複雜的,也是漫長的,須要咱們耐心去看,去理解。先來簡單過一下它的時序圖。

Render時序圖.jpg

從時序圖可見,其涉及到的相對象以及步驟是比較多的,共 20 步。其中涉及到的主要對象有:Scene,Camera,WebGLRenderStates,WebGLRenderLists,WebGLBackground,WebGLProgram,_gl,WebGLBufferRenderer。咱們比較熟悉的是 Scene,由於咱們的Object / Mesh 都是被添加到它裏面的,另外還有 Camera,咱們必需要有一個相機來告訴咱們以怎麼樣的視角來觀看這個 3D 世界。另一些不熟悉的對象,WebGLRenderList 管理着咱們須要拿去 render 的 Object / Mesh,WebGLBackground 描述了場景的背景,WebGLProgram 則建立了用於連接、執行 Shader 的程序,而 WebGLBufferRenderer 則是整個 3D 世界被 render 到的目的地。 這裏不會按照時序圖,逐步逐步地進行分析,而是挑重點,同時保持與前面所述的 OpenGL ES 的流程一致性上進行分析。

render() 函數

this.render = function ( scene, camera, renderTarget, forceClear ) {
        // 前面是一些參數的校驗,這裏省略
		// 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.視景體矩陣計算,爲相機的投影矩陣與相機的世界矩陣的逆矩陣的叉乘?
		_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.強制使用場景的材質 overrideMaterial 來統一 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 的物體進行 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. 視景體矩陣計算,爲相機的投影矩陣與相機的世界矩陣的逆矩陣的叉乘。
  6. WebGLRenderList 的初始化。
  7. shadow 的繪製。
  8. 背景的繪製。
  9. render scene。
  10. 若是overrideMaterial,則強制使用場景的材質 overrideMaterial 來統一 render 物體。
  11. 分別對 opaque 和 transparent 的物體進行 render。

但這裏咱們沒必要關注每一個處理的細節,僅從幾個重要的點着手去理解以及分析。

2.1 update scene graph

即更新整個場景圖,主要就是更新每一個物體的 matrix。若是其含有孩子節點,則還會逐級更新。在這裏,每一個物體的 matrix 是經過其 position,quaternion以及scale 計算得來的,也就是其模型矩陣,而 matrixWorld 又是根據 matrix 計算得來的。若是當前節點沒有父節點,則 matrix 就是 matrixWorld。而若是有的話,那 matrixWorld 則爲父節點的 matrixWorld 與當前節點 matrix 的叉乘。也就是說當前節點的 matrixWorld 是相對於其父親節點的。

2.2 WebGLRenderList 的初始化

WebGLRenderList 的初始化init()方法自己並無什麼,其只是在 WebGLRenderLists 中經過將 scene.id 和 camera.id 創建起必定的關聯。而這裏更重要的目的是肯定有哪些對象是要被渲染出來的,這個最主要的實現就在 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 );

					}

				}

			}

		}

		// 對每一個孩子進行遞歸遍歷

		var children = object.children;

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

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

		}

	}

複製代碼

從方法中,咱們大體獲得以下結論:

  1. 只有可見的光照,精靈,mesh,line,point 會被實際渲染出來。而若是咱們只是 new 一個 Object3D 而被指到具體的 3D 對象上,那麼理論上它是不會被渲染的。
  2. 光照與其餘 Object3D 不同,它是另外單獨被放在 currentRenderState 中的。
  3. 對於整個要渲染的場景圖利用遞歸進行遍歷,以確保場景圖中的每個可渲染的 3D object 均可以被渲染出來。這裏簡單回顧一下,Sence 也是繼承自 Object3D 的,而燈光以及可被渲染的 Object 都是做爲它的孩子被加入到 Sence 中的。

經過 WebGLRenderList 的初始化基本就肯定了當前哪些 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 對象,而後調用 renderObject() 方法進行進一步渲染。看來髒活都交給了 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 );

		}

		......

	}
複製代碼

關於計算 mode view matrix 以及 normal matrix,這裏我也不太看明白,因此我選擇先跳過。先分析後面的步驟。這裏不論是否 isImmediateRenderObject 其流程上差不太多,因此這裏先分析 renderBufferDirect()。

renderBufferDirect()方法

this.renderBufferDirect = function ( camera, fog, geometry, material, object, group ) {
		......
        // 1.經過WebGLState設置材質的一些屬性
		state.setMaterial( material, frontFaceCW );
        // 2.設置 program
		var program = setProgram( camera, fog, material, object );
		......
		if ( updateBuffers ) {
        // 3.設置頂點屬性
			setupVertexAttributes( material, program, geometry );
			if ( index !== null ) {
              // 4.綁定 buffer
				_gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, attribute.buffer );
			}
		}
		......
        // 5.根據不一樣網格類型肯定相應的繪製模式
		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.調用 WebGLBufferRenderer#render() 方法進行渲染
			renderer.render( drawStart, drawCount );
		}
	};
複製代碼

renderBufferDirect()方法是一個比較重要的方法,在這裏能夠看到一個物體被渲染的「最小完整流程」。

  1. 經過WebGLState設置材質的一些屬性。這個比較形象,由於整個 OpenGL / ES 它就是一個狀態機。這裏所設置的材質屬性也是直接調用底層的 gl_xxx() 之類的方法。而這裏實際就是設置瞭如 CULL_FACE,depthTest,depthWrite,colorWrite 等等。
  2. 設置 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,也就是咱們在流程圖中所說的建立程序
  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;
    // 獲取頂點 shader 以及片元 shader
	var vertexShader = shader.vertexShader;
	var fragmentShader = shader.fragmentShader;
    ......
    // 建立 program 
    var program = gl.createProgram();
    ......
    // 構造最終用於進行渲染的 glsl,而且調用 WebGLShader 構造出 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 關聯 shader
	gl.attachShader( program, glVertexShader );
	gl.attachShader( program, glFragmentShader );
    ......
    // 連接 program
    gl.linkProgram( program );
    ......
}
複製代碼

program 的初始化方法也是很是多的,這裏簡化出關鍵部分。再回憶一下前面的流程圖,就會明白這裏主要就是建立 program、shader ,關聯 program 和 shader,以及連接程序。連接好了程序以後接下來就能夠經過 useProgram() 使用 program 了,這一步驟在 setProgram() 中建立好 program 就調用了。 3. 設置頂點屬性,就是將咱們在外面所構造的 geometry 的頂點送到 shader 中去。 4. 綁定 buffer。 5. 根據不一樣網格類型肯定相應的繪製模式,如以 LINES 進行繪製,以TRIANGLES 進行繪製。 6. 調用 WebGLBufferRenderer#render() 方法進行渲染。以下,就是進行最後的 drawArrays() 調用,將上層建立的 geometry 以及 material(組合起來就叫作 mesh) 渲染到 3D 場景的 canvas 中。

function render( start, count ) {

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

}
複製代碼

4、總結

文章一樣以一篇 demo 爲入口對渲染過程進行了一個簡要的分析,其中還介紹了 OpenGL / WebGL 所須要知道的基礎知識。這其中瞭解了 OpenGL 的繪製流程以及各座標系之間的關係以及轉換,然後面的分析都是沿着這個繪製流程進行的。

然而,因爲做者的水平有限,而 OpenGL / WebGL 又是如此的強大,實在不能面面俱到,甚至對某些知識點也沒法透徹分析。所以,還請見諒。

最後,感謝你能讀到並讀完此文章,若是分析的過程當中存在錯誤或者疑問都歡迎留言討論。若是個人分享可以幫助到你,還請記得幫忙點個贊吧,謝謝。

相關文章
相關標籤/搜索