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

以上,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 );

					}

以上,对四个材质参数中每个,都循环 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 );
}

以上,每个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;

最后,提一下在 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' );

以上,有点费解,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等光照模型)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值