• three.js webgl_tiled_forward 例子分析


    three.js 中 webgl_tiled_forward 是比较难理解的一个官方样例,我第一次看时,看得一头雾水,看得快睡着了,比较枯燥。。。
    这个例子,就是展示场景中 有多个光源时,如何提升渲染效率;思路就是把屏幕按行列分割成一个个 tile 格子,片元着色器去计算光照时,根据片元所在tile 格子,剔除不相关光源;这样每个片元着色器,在计算光照时 就不用考虑所有光源,极大提升了渲染的性能。

    以上,是比较粗略的概括,现在结合代码来,详细看看。
    首先,这个例子中 创建了 32个会影响渲染的点光源。

    负责创建光源的代码在 init 函数中:

    const Heads = [
    	{ type: 'physical', uniforms: { 'diffuse': 0x888888, 'metalness': 1.0, 'roughness': 0.66 }, defines: {} },
    	{ type: 'standard', uniforms: { 'diffuse': 0x666666, 'metalness': 0.1, 'roughness': 0.33 }, defines: {} },
    	{ type: 'phong', uniforms: { 'diffuse': 0x777777, 'shininess': 20 }, defines: {} },
    	{ type: 'phong', uniforms: { 'diffuse': 0x555555, 'shininess': 10 }, defines: { TOON: 1 } }
    			];
    			
    function init( geom ) {
    
    		const sphereGeom = new THREE.SphereGeometry( 0.5, 32, 32 );
    		const tIndex = Math.round( Math.random() * 3 );
    
    		Object.keys( Heads ).forEach( function ( t, index ) {
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    以上,Heads 保存了四个 参数对象 用于创建四种不同的材质,这里有四次循环。

    function init( geom ) {
    
    				const sphereGeom = new THREE.SphereGeometry( 0.5, 32, 32 );
    				const tIndex = Math.round( Math.random() * 3 );
    
    				Object.keys( Heads ).forEach( function ( t, index ) {
    				...
    				for ( let i = 0; i < 8; i ++ ) {
    				for ( let i = 0; i < 8; i ++ ) {
    
    						const color = new THREE.Color().setHSL( Math.random(), 1.0, 0.5 );
    						const l = new THREE.Group();
    
    						l.add( new THREE.Mesh(
    							sphereGeom,
    							new THREE.MeshBasicMaterial( {
    								color: color
    							} )
    						) );
    
    						l.add( new THREE.Mesh(
    							sphereGeom,
    							new THREE.MeshBasicMaterial( {
    								color: color,
    								transparent: true,
    								opacity: 0.033
    							} )
    						) );
    
    						l.children[ 1 ].scale.set( 6.66, 6.66, 6.66 );
    
    						l._light = {
    							color: color,
    							radius: RADIUS,
    							decay: 1,
    							sy: Math.random(),
    							sr: Math.random(),
    							sc: Math.random(),
    							py: Math.random() * Math.PI,
    							pr: Math.random() * Math.PI,
    							pc: Math.random() * Math.PI,
    							dir: Math.random() > 0.5 ? 1 : - 1
    						};
    
    						lights.push( l );
    						g.add( l );
    
    					}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    以上,对四个材质参数中每个,都循环 8 次,创建 8个 点光源 ’壳子‘,用于在渲染时,直观地 实时的 展示点光源的位置,颜色和运动方式。
    每个 ’壳子‘ 由两个球形网格组成,第一个球形网格比较小,表示球形灯的灯芯;第二个球形网格等比放大 6.66 倍表示球形灯发光后形成的光晕,每个球形灯的发光颜色随机生成
    _light 对象保存真正创建点光源要用的参数(color radius decay) 以及 决定光源在渲染过程中如何运动的参数
    init 函数只是间接的创建点光源,真正创建点光源是在shader 代码中,这些光源不是通常意义上的光源,没有添加到场景树中。这些光源只作用于特定的材质 ShaderMaterial
    点光源在片元着色器中创建,只影响光照计算
    点光源在片元着色器中创建,只影响光照计算,只作用于特定材质。

    函数 ‘function update( now )’ 对 32个片元着色器光源进行位置更新

    const State = {
    		rows: 0,
    		cols: 0,
    		width: 0,
    		height: 0,
    		tileData: { value: null },
    		tileTexture: { value: null },
    		lightTexture: {
    			value: new THREE.DataTexture( new Float32Array( 32 * 2 * 4 ), 32, 2, THREE.RGBAFormat, THREE.FloatType )
    		},
    	};
    	
    function resizeTiles() {
    
    	const width = window.innerWidth;
    	const height = window.innerHeight;
    
    	State.width = width;
    	State.height = height;
    	State.cols = Math.ceil( width / 32 );
    	State.rows = Math.ceil( height / 32 );
    	State.tileData.value = [ width, height, 0.5 / Math.ceil( width / 32 ), 0.5 / Math.ceil( height / 32 ) ];
    	State.tileTexture.value = new THREE.DataTexture( new Uint8Array( State.cols * State.rows * 4 ), State.cols, State.rows );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    以上,每个tile格子都是 32 x 32 像素的正方形格子;
    lightTexture 用于告知每个片元着色器,32个光源中 每个光源的位置 颜色 光源影响范围 radius,衰减系数 decay
    tileTexture 用于告知每个片元着色器,所有 tile 格子中的 每个格子 会被 32个光源中 哪些光源影响光照计算
    ‘function tileLights( renderer, scene, camera )’ 负责更新这两个纹理
    tileLights 调用了 lightBounds 方法计算 着色器光源 投影到屏幕后的二维包围盒坐标;包围盒大小只与前面的 radius 参数成正比

    格子的划分是基于屏幕坐标系的,lightBounds 计算出的包围盒也是屏幕坐标系的,tileLights 对 lights 数组做迭代时,前半段更新 lightTexture ,后半段更新 tileTexture,计算出每个格子tile 分别会被哪些 着色器光源影响。
    tileLights 方法的调用时机

    scene.onBeforeRender = tileLights;
    
    • 1

    最后,提一下在 THREE.ShaderChunk[ ‘lights_fragment_end’ ] 尾部添加的片元着色器代码:

    THREE.ShaderChunk[ 'lights_fragment_end' ] += [
    	'',
    	'#if defined TILED_FORWARD',
    	'vec2 tUv = floor(gl_FragCoord.xy / tileData.xy * 32.) / 32. + tileData.zw;',
    	'vec4 tile = texture2D(tileTexture, tUv);',
    	'for (int i=0; i < 4; i++) {',
    	'	float tileVal = tile.x * 255.;',
    	'  	tile.xyzw = tile.yzwx;',
    	'	if(tileVal == 0.){ continue; }',
    	'  	float tileDiv = 128.;',
    	'	for (int j=0; j < 8; j++) {',
    	'  		if (tileVal < tileDiv) {  tileDiv *= 0.5; continue; }',
    	'		tileVal -= tileDiv;',
    	'		tileDiv *= 0.5;',
    	'  		PointLight pointlight;',
    	'		float uvx = (float(8 * i + j) + 0.5) / 32.;',
    	'  		vec4 lightData = texture2D(lightTexture, vec2(uvx, 0.));',
    	'  		vec4 lightColor = texture2D(lightTexture, vec2(uvx, 1.));',
    	'  		pointlight.position = lightData.xyz;',
    	'  		pointlight.distance = lightData.w;',
    	'  		pointlight.color = lightColor.rgb;',
    	'  		pointlight.decay = lightColor.a;',
    	'  		getPointLightInfo( pointlight, geometry, directLight );',
    	'		RE_Direct( directLight, geometry, material, reflectedLight );',
    	'	}',
    	'}',
    	'#endif'
    ].join( '\n' );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    以上,有点费解,tileTexture中每个像素 有 x y z w 四个分量
    tile.xyzw = tile.yzwx; 在一个长度为 4 的循环,每个分量都被读取出来,乘以 255.0 保存到浮点数变量 tileVal 中, tileVal 为 0时,表示当前片元所属的格子,没有被任何着色器光源影响到。
    长度为 8 的子循环,是对 8比特分量的每一个 位(比特) 进行读取,如果一个位 值为1,就表示 该格子 有 被编号为 i = n,j = m 的着色器光源影响到 (0 <= n <= 3, 0 <= m <= 7),然后要创建点光源,带入到各种光照模型中进行光照计算(lambert blinn-phong,standard, physics-pbr等光照模型)。

  • 相关阅读:
    公网IP与私网IP的区别
    MySQL查询为啥慢了?
    [Linux]----进程间通信之共享内存
    JAVA集合源码
    Java long类型转换易犯的错误
    java计算机毕业设计河东街摊位管理系统MyBatis+系统+LW文档+源码+调试部署
    MySQL 45 讲 | 14 count(*)这么慢,我该怎么办?
    java毕业设计畅言情感互助网站mybatis+源码+调试部署+系统+数据库+lw
    医院信息化的三种演进建设模式
    【CSDN】如何开启CSDN文章下的显示微信公众号、微信号、官方网站、QQ号、QQ群 ?
  • 原文地址:https://blog.csdn.net/mu399/article/details/127844961