Three.js中WebGLRenderer渲染源码解析

前言

Three.js是目前最流行的web端的三维框架之一,笔者作为对计算机图形学兴趣浓厚的前端程序员,web3D的开发者,阅读Three.js源码是提升技术、加深理解非常好的手段。Three.js经过多年发展,已经是一个功能非常庞大完善的框架了,那么,从何开始呢?笔者认为当然应该是从WebGLRenderer开始。WebGLRenderer是 Three.js 基于 WebGL 技术实现的渲染器,是最核心的组件。本文将从WebGLRenderer类的render方法入手,解析WebGLRenderer渲染的运行原理。

WebGL的绘制流程

这是一段来自的《WebGL编程指南》的代码,绘制了一个三角形。由此可以看到WebGL的绘制的大致流程。如下所示:

graph TD 创建并获取WebGL绘图上下文 --> 编写并编译着色器-->创建和绑定缓冲区-->设置顶点属性数据-->传递uniform变量-->绘制
js 复制代码
// RotatedTriangle_Matrix.js (c) matsuda
// Vertex shader program
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'uniform mat4 u_xformMatrix;\n' +
  'void main() {\n' +
  '  gl_Position = u_xformMatrix * a_Position;\n' +
  '}\n';

// Fragment shader program
var FSHADER_SOURCE =
  'void main() {\n' +
  '  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' +
  '}\n';

// The rotation angle
var ANGLE = 90.0;

function main() {
  // Retrieve <canvas> element
  var canvas = document.getElementById('webgl');

  // Get the rendering context for WebGL
  var gl = getWebGLContext(canvas);
  if (!gl) {
    console.log('Failed to get the rendering context for WebGL');
    return;
  }

  // Initialize shaders
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed to intialize shaders.');
    return;
  }
 
  // Write the positions of vertices to a vertex shader
  var n = initVertexBuffers(gl);
  if (n < 0) {
    console.log('Failed to set the positions of the vertices');
    return;
  }

  // Create a rotation matrix
  var radian = Math.PI * ANGLE / 180.0; // Convert to radians
  var cosB = Math.cos(radian), sinB = Math.sin(radian);

  // Note: WebGL is column major order
  var xformMatrix = new Float32Array([
     cosB, sinB, 0.0, 0.0,
    -sinB, cosB, 0.0, 0.0,
      0.0,  0.0, 1.0, 0.0,
      0.0,  0.0, 0.0, 1.0
  ]);

  // Pass the rotation matrix to the vertex shader
  var u_xformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix');
  if (!u_xformMatrix) {
    console.log('Failed to get the storage location of u_xformMatrix');
    return;
  }
  gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix);

  // Specify the color for clearing <canvas>
  gl.clearColor(0, 0, 0, 1);

  // Clear <canvas>
  gl.clear(gl.COLOR_BUFFER_BIT);

  // Draw the rectangle
  gl.drawArrays(gl.TRIANGLES, 0, n);
}

function initVertexBuffers(gl) {
  var vertices = new Float32Array([
    0, 0.5,   -0.5, -0.5,   0.5, -0.5
  ]);
  var n = 3; // The number of vertices

  // Create a buffer object
  var vertexBuffer = gl.createBuffer();
  if (!vertexBuffer) {
    console.log('Failed to create the buffer object');
    return false;
  }

  // Bind the buffer object to target
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  // Write date into the buffer object
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position');
    return -1;
  }
  // Assign the buffer object to a_Position variable
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

  // Enable the assignment to a_Position variable
  gl.enableVertexAttribArray(a_Position);

  return n;
}

WebGLRenderer的初始化

对WebGL绘制的流程有了一定的了解后,接下来可以开始介绍WebGLRenderer类的源码了,但是,在介绍渲染流程,先介绍一下initGLContext函数,这个函数中初始化了一系列变量,这些变量都是Three.js中和渲染相关的类的实例对象,这里挑选一些我认为重要的变量一一介绍。

js 复制代码
function initGLContext() {
	..........................................省略..........................................
		
	utils = new WebGLUtils( _gl, extensions );
	capabilities = new WebGLCapabilities( _gl, extensions, parameters, utils );
	state = new WebGLState( _gl, extensions );
	info = new WebGLInfo( _gl );
	properties = new WebGLProperties();
	textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info );
	cubemaps = new WebGLCubeMaps( _this );
	cubeuvmaps = new WebGLCubeUVMaps( _this );
	attributes = new WebGLAttributes( _gl );
	bindingStates = new WebGLBindingStates( _gl, attributes );
	geometries = new WebGLGeometries( _gl, attributes, info, bindingStates );
	objects = new WebGLObjects( _gl, geometries, attributes, info );
	morphtargets = new WebGLMorphtargets( _gl, capabilities, textures );
	clipping = new WebGLClipping( properties );
	programCache = new WebGLPrograms( _this, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping );
	materials = new WebGLMaterials( _this, properties );
	renderLists = new WebGLRenderLists();
	renderStates = new WebGLRenderStates( extensions );
	background = new WebGLBackground( _this, cubemaps, cubeuvmaps, state, objects, _alpha, premultipliedAlpha );
	shadowMap = new WebGLShadowMap( _this, objects, capabilities );
	uniformsGroups = new WebGLUniformsGroups( _gl, info, capabilities, state );

	bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info );
	indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info );
	..........................................省略..........................................
}

WebGLState

WebGLState的作用是管理和维护 WebGL 渲染上下文的状态。它封装了与颜色、深度和模板缓冲区、混合、剔除、纹理处理以及其他 WebGL 特定设置相关的功能。举例说明,比如:

setMaterial方法主要设置材质属性,包括:

  • 面选择(前/后/双面,gl.CULL_FACE、gl.frontFace)。
  • 混合设置(gl.BLEND、gl.blendFunc等)。
  • 深度buffer测试/写入。
  • 颜色buffer写入。
  • 模板buffer测试和操作。
  • 多边形偏移(gl.POLYGON_OFFSET_FILL)。
  • alpha 值转换为像素的采样覆盖率设置(gl.SAMPLE_ALPHA_TO_COVERAGE)。

对帧缓冲区操作的封装,包括:

ColorBuffer构造函数,封装了如下方法:

  • setMask:颜色掩码设置。
  • setClear:清除颜色值设置。
  • setLocked:锁定缓冲区状态。

DepthBuffer构造函数,封装了如下方法:

  • setTest:深度测试启用/禁用。
  • setFunc:深度函数的设置。
  • setMask:深度掩码设置。
  • setClear:清除深度值。
  • setReversed:EXT_clip_control设置反向深度行为通过。

StencilBuffer构造函数,封装了如下方法:

  • setTest:模板测试启用/禁用。
  • setFunc:模板函数的设置。
  • setOp:设置操作。
  • setMask:模板写入掩码的设置。
  • setClear:清除模板。

activeTexture方法,激活纹理单元。

bindTexture方法,将纹理绑定到目标。

bindFramebuffer方法,绑定帧缓冲。

drawBuffers方法,设置绘制缓冲区。

WebGLRenderStates

WebGLRenderStates包含一个WeakMap,key为scene对象,value为WebGLRenderState,WebGLRenderState主要包含light对象、带shadow的light对象、camera。

WebGLRenderLists

WebGLRenderLists包含一个WeakMap,key为scene对象,value为WebGLRenderList,WebGLRenderList按照material的属性,将场景scene中的每个object分为三种类型:

  • 透明(transparent)
  • 透光物体(transmissive)
  • 不透明(transparent) 这三种类型分别保存一个数组,数组中每个元素都是一个对象,这个对象的属性包括:object、geometry、material、顺序、投影到屏幕上的z值,group(几何体顶点的群组编号,顶点的群组编号对应material数组的index,如果material是数组,则每个material都会对应一个几何体中的一组顶点)。

WebGLShadowMap

WebGLShadowMap的作用是实现光源投射阴影的功能,它的两个主要属性: type可以选择四个枚举值:BasicShadowMap、PCFShadowMap、PCFSoftShadowMap、VSMShadowMap,其中后三种都是软阴影。 enabled:渲染器是否开启阴影功能,默认不开启。 这个类还有一个最核心的方法render,这个方法就是在场景中的各个光源相机下渲染一张深度图,这个深度图将被用于后续的渲染阴影。

WebGLAttributes

WebGLAttributes的作用是管理WebGL的顶点属性,即WebGL绘制流程中提到的创建、绑定缓冲区(vbo),为geometry的attributes中的每个属性都创建了一个缓冲区(attributes是一个对象,对象的key为属性名称,如position,uv,normal,color等,value为一个BufferAttribute的对象),并且设置缓冲区中的数据。还提供了更新属性的缓冲区数据的功能,如果某属性的版本发生变化,就更新缓冲区中的数据。

其中包含一个名为buffers的WeakMap,key值为场景中geometry的BufferAttribute,value为一个对象,包含创建的缓冲区、数据类型、占据的字节大小等。

WebGLObjects

每一帧更新一次geometry中的顶点属性的数据,调用WebGLAttributes中的update方法。

WebGLBindingStates

WebGLBindingStates的作用是管理每一个需要渲染的geometry的顶点属性,如:position、uv、color等,管理vao和顶点缓冲区,为每一个被渲染的geometry创建一个state,用于存储geometry顶点属性的启用状态。如果在geometry的attributes中没有该属性,则不启用,设置默认值,即调用gl.vertexAttrib*。如果属性启用,则调用gl.vertexAttribPointer和gl.enableVertexAttribArray,为每个属性设置启用状态和访问方式,使得着色器可以从缓冲区中找到数据。

WebGLUniforms

这个构造函数管理全部的uniform变量,主要功能就是获取各个uniform变量的信息,包括名称、类型、存储地址等。给不同类型uniform(包括纹理类型)变量选择不同的更新接口,主要功能就是设置uniform变量的值。

WebGLTextures

管理全部纹理,根据Three.js中的Texture类型对象,创建、绑定、激活WebGL中的纹理对象,设置WebGL纹理对象的关键参数,如gl.UNPACK_FLIP_Y_WEBGL、gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL等,上传纹理数据(一般是图像数据)。这里管理的主要作用就是对同一个纹理,在数据和属性不变的情况下,不进行重复的创建、上传、设置参数等操作,使得纹理可以被复用。

WebGLPrograms

管理WebGLProgram,根据材质的类型选择合适的着色器代码,根据不同的参数,在着色器代码上拼接不同的宏定义,编译着色器程序,使用着色器程序。

render方法

接下来介绍渲染器的render方法,使用过Three.js同学都知道,Three.js一个场景的最后一步一般都是调用这个render函数,而且这个函数一般要放在requestAnimationFrame中。

在准备这篇文章时,笔者刻意省略了xr、裁剪(clipping)、透光度(transmission)、vsm阴影贴图、MRT、LightProbe等话题,主要是这些话题占用的篇幅比较大,想写清楚需要准备的知识点很多,考虑还是放在以后的专题文章中介绍。

按照代码的顺序,将render函数按照源码拆分成几段来介绍,中间有一些省略:

js 复制代码
 ..........................................省略..........................................
	if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld();
	if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld();
 ..........................................省略..........................................
	currentRenderState = renderStates.get( scene, renderStateStack.length );
	currentRenderState.init( camera );
	renderStateStack.push( currentRenderState );
	_projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
	_frustum.setFromProjectionMatrix( _projScreenMatrix );
 ..........................................省略..........................................
	currentRenderList = renderLists.get( scene, renderListStack.length );
	currentRenderList.init();
	renderListStack.push( currentRenderList );

这段代码为最开始的更新scene对象,更新camera,初始化WebGLRenderState,如上文所说,这个对象负责管理光源和阴影,renderStateStack是一个栈,主要用来在嵌套渲染中隔离不同的renderState(比如某个模型在onBeforeRender中调用render方法),为每次render调用都创建一个独立的WebGLRenderState。初始化WebGLRenderList也类似,这个对象主要负责管理要渲染的object。

js 复制代码
function projectObject( object, camera, groupOrder, sortObjects ) {

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

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

	if ( visible ) {

		if ( object.isGroup ) {

			groupOrder = object.renderOrder;

		} else if ( object.isLOD ) {

			if ( object.autoUpdate === true ) object.update( camera );

		} else if ( object.isLight ) {

			currentRenderState.pushLight( object );

			if ( object.castShadow ) {

				currentRenderState.pushShadow( object );

			}

		} else if ( object.isSprite ) {

			if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) {

				if ( sortObjects ) {

					_vector4.setFromMatrixPosition( object.matrixWorld )
						.applyMatrix4( _projScreenMatrix );

				}

				const geometry = objects.update( object );
				const material = object.material;

				if ( material.visible ) {

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

				}

			}

		} else if ( object.isMesh || object.isLine || object.isPoints ) {

			if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) {

				const geometry = objects.update( object );
				const material = object.material;

				if ( sortObjects ) {

					if ( object.boundingSphere !== undefined ) {

						if ( object.boundingSphere === null ) object.computeBoundingSphere();
						_vector4.copy( object.boundingSphere.center );

					} else {

						if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
						_vector4.copy( geometry.boundingSphere.center );

					}

					_vector4
						.applyMatrix4( object.matrixWorld )
						.applyMatrix4( _projScreenMatrix );

				}

				if ( Array.isArray( material ) ) {

					const groups = geometry.groups;

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

						const group = groups[ i ];
						const groupMaterial = material[ group.materialIndex ];

						if ( groupMaterial && groupMaterial.visible ) {

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

						}

					}

				} else if ( material.visible ) {

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

				}

			}

		}

	}

	const children = object.children;

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

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

	}

}

接下来调用projectObject函数,上面贴的是projectObject函数的源码,这个函数比较清晰,就是将light对象传入到currentRenderState中。

对所有的点线面object调用objects.update获取geometry,objects.update这个方法里面会为每个attribute都创建一个缓冲区,并设置缓冲区数据。如果geometry的attributes中的某个attribute有更新,会更新缓冲区数据。

然后判断object是否在相机的视椎体中,即能否被相机看见,如果可以,就加入到currentRenderList中。再递归调用这个方法,对object的children也执行该操作。

js 复制代码
currentRenderList.finish();

if ( _this.sortObjects === true ) {

	currentRenderList.sort( _opaqueSort, _transparentSort );

}

将上一次渲染中currentRenderList中多余的元素清空,给currentRenderList排序,这里可以设置自定义的排序方法,如果没设置,会根据groupOrder、renderOrder、材质id、投影后的z值依次比较排序。

js 复制代码
const shadowsArray = currentRenderState.state.shadowsArray;
shadowMap.render( shadowsArray, scene, camera );

渲染shadowMap,将shadowMap通过渲染写在开启shadow的light对象的map中,shadowMap本质上就是一张深度图,记录着场景中在light的相机中看到的最小深度。这样在最终渲染时就可以依照深度对比来判断某一点是否在阴影内。

js 复制代码
currentRenderState.setupLights();

这里会初始化一个currentRenderState中的WebGLLights类型的对象lights,这个lights对象会根据场景中的light的类型和属性值,初始化和更新与光照有关的uniforms数据。

js 复制代码
if ( _renderBackground ) background.render( scene );

renderScene( currentRenderList, scene, camera );

清空上一帧的缓冲区,渲染场景。

js 复制代码
function renderScene( currentRenderList, scene, camera, viewport ) {

    const opaqueObjects = currentRenderList.opaque;
    const transmissiveObjects = currentRenderList.transmissive;
    const transparentObjects = currentRenderList.transparent;

    currentRenderState.setupLightsView( camera );

    if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, camera );

    if ( viewport ) state.viewport( _currentViewport.copy( viewport ) );

    if ( opaqueObjects.length > 0 ) renderObjects( opaqueObjects, scene, camera );
    if ( transmissiveObjects.length > 0 ) renderObjects( transmissiveObjects, scene, camera );
    if ( transparentObjects.length > 0 ) renderObjects( transparentObjects, scene, camera );

    // 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 );

}

这是renderScene函数的源码,currentRenderList分为opaque、transmissive、transparent分别调用renderObjects函数渲染,本文只关注opaque(不透明)类型。renderObjects函数遍历currentRenderList,每个元素都存储着object、geometry、material、group(几何体顶点的群组编号),每个元素都调用renderObject函数。

js 复制代码
function renderObject( object, scene, camera, geometry, material, group ) {

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

        object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
        object.normalMatrix.getNormalMatrix( object.modelViewMatrix );

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

        if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) {

                material.side = BackSide;
                material.needsUpdate = true;
                _this.renderBufferDirect( camera, scene, geometry, material, object, group );

                material.side = FrontSide;
                material.needsUpdate = true;
                _this.renderBufferDirect( camera, scene, geometry, material, object, group );

                material.side = DoubleSide;

        } else {

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

        }

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

}

这是renderObject函数的源码,开始先调用object的onBeforeRender,然后计算object的模型视图矩阵(modelViewMatrix)和法线矩阵(normalMatrix),modelViewMatrix是模型顶点从模型的局部空间变换到视图空间的矩阵,normalMatrix是模型顶点的法线从模型的局部空间变换到视图空间的矩阵。normalMatrix是modelViewMatrix的逆转置矩阵。然后调用renderBufferDirect方法渲染到帧缓冲区。最后调用object的onAfterRender。

js 复制代码
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, geometry, material, object );

        state.setMaterial( material, frontFaceCW );

        //

        let index = geometry.index;
        let rangeFactor = 1;

        if ( material.wireframe === true ) {

                index = geometries.getWireframeAttribute( geometry );

                if ( index === undefined ) return;

                rangeFactor = 2;

        }

        //

        const drawRange = geometry.drawRange;
        const position = geometry.attributes.position;

        let drawStart = drawRange.start * rangeFactor;
        let drawEnd = ( drawRange.start + drawRange.count ) * rangeFactor;

        if ( group !== null ) {

                drawStart = Math.max( drawStart, group.start * rangeFactor );
                drawEnd = Math.min( drawEnd, ( group.start + group.count ) * rangeFactor );

        }

        if ( index !== null ) {

                drawStart = Math.max( drawStart, 0 );
                drawEnd = Math.min( drawEnd, index.count );

        } else if ( position !== undefined && position !== null ) {

                drawStart = Math.max( drawStart, 0 );
                drawEnd = Math.min( drawEnd, position.count );

        }

        const drawCount = drawEnd - drawStart;

        if ( drawCount < 0 || drawCount === Infinity ) return;

        //

        bindingStates.setup( object, material, program, geometry, index );

        let attribute;
        let renderer = bufferRenderer;

        if ( index !== null ) {

                attribute = attributes.get( index );

                renderer = indexedBufferRenderer;
                renderer.setIndex( attribute );

        }

        //

        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.isBatchedMesh ) {

                if ( object._multiDrawInstances !== null ) {

                        // @deprecated, r174
                        warnOnce( 'THREE.WebGLRenderer: renderMultiDrawInstances has been deprecated and will be removed in r184. Append to renderMultiDraw arguments and use indirection.' );
                        renderer.renderMultiDrawInstances( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount, object._multiDrawInstances );

                } else {

                        if ( ! extensions.get( 'WEBGL_multi_draw' ) ) {

                                const starts = object._multiDrawStarts;
                                const counts = object._multiDrawCounts;
                                const drawCount = object._multiDrawCount;
                                const bytesPerElement = index ? attributes.get( index ).bytesPerElement : 1;
                                const uniforms = properties.get( material ).currentProgram.getUniforms();
                                for ( let i = 0; i < drawCount; i ++ ) {

                                        uniforms.setValue( _gl, '_gl_DrawID', i );
                                        renderer.render( starts[ i ] / bytesPerElement, counts[ i ] );

                                }

                        } else {

                                renderer.renderMultiDraw( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount );

                        }

                }

        } else if ( object.isInstancedMesh ) {

                renderer.renderInstances( drawStart, drawCount, object.count );

        } else if ( geometry.isInstancedBufferGeometry ) {

                const maxInstanceCount = geometry._maxInstanceCount !== undefined ? geometry._maxInstanceCount : Infinity;
                const instanceCount = Math.min( geometry.instanceCount, maxInstanceCount );

                renderer.renderInstances( drawStart, drawCount, instanceCount );

        } else {

                renderer.render( drawStart, drawCount );

        }

};

renderBufferDirect方法的功能是渲染每个object。首先调用setProgram函数设置并获取一个Program对象,这里不再贴setProgram源码了,大体总结下,这个函数做了以下操作:

  • 根据material的类型获取着色器代码模版。
  • 根据material属性值,在着色器代码上拼接不同的宏定义。
  • 编译着色器,创建WebGL的program对象。
  • 复用相同的program,不重复编译。
  • 根据material的uniforms字段值,设置program中的uniform变量值。

接下来执行setMaterial方法,上文介绍了,不再赘述。

然后重要的一步就是执行bindingStates.setup方法,这里也不在贴源码了,这个方法的作用是为着色器中的attribute变量设置启用状态和访问方式,使得着色器可以从缓冲器中找到数据。具体的如果属性在geometry的attributes中没有该属性,则不启用,设置默认值,即调用gl.vertexAttrib*。如果属性启用,则调用gl.vertexAttribPointer和gl.enableVertexAttribArray。

最后就是根据有没有geometry.index选择bufferRenderer还是indexedBufferRenderer来进行最终绘制,bufferRenderer使用gl.drawArrays,indexedBufferRenderer使用gl.drawElements。根据object类型选择图元类型,根据geometry.drawRange计算顶点(索引)数量和初始的偏移量。

至此,WebGLRenderer的render方法的主体流程就分析到这里。作为框架的渲染器,事无巨细介绍WebGLRenderer的源码远远超过一篇文章的篇幅,所以这篇文章不会逐行介绍所有源码,而是尽量聚焦主要的渲染流程,主要目的是对Three.js的渲染器工作原理有一个整体大致的认识,也是为理解Three.js其他部分的源码打基础。对于一些分支话题也做了适当的简化省略,笔者计划将在后续的文章中对这些话题做专题介绍。

相关推荐
小桥风满袖1 小时前
Three.js-硬要自学系列31之专项学习动画混合
前端·css·three.js
Mintopia2 小时前
Three.js 自定义几何体:解锁 3D 世界的创造力
前端·javascript·three.js
小桥风满袖20 小时前
Three.js-硬要自学系列30之专项学习环境光
前端·css·three.js
Sword991 天前
💡 前端福音!Trae × Blender-MCP终极融合,3D建模从此告别"手残党"
ai编程·three.js·trae
Mintopia1 天前
Three.js 动态加载 GLTF 模型:高效加载和渲染复杂的 3D 模型
前端·javascript·three.js
小桥风满袖2 天前
Three.js-硬要自学系列29之专项学习透明贴图
前端·css·three.js
Danta2 天前
从 0 开始学习 Three.js(2)😁
前端·javascript·three.js
Mintopia2 天前
Three.js 力导向图:让数据跳起优雅的华尔兹
前端·javascript·three.js
Mintopia3 天前
Three.js 物理引擎:给你的 3D 世界装上 “牛顿之魂”
前端·javascript·three.js