threejs官网中的example第一个案例webgl_animation_cloth

本文深入解析Three.js中的webgl_animation_cloth案例,详细介绍了如何使用Three.js实现布料模拟,包括代码结构、关键函数说明及性能优化技巧。

webgl_animation_cloth

起因

最近在使用pano2VR,是通过封装的threejs,所以就看了一下threejs。这是研究的第一个案例https://threejs.org/examples/#webgl_animation_cloth

代码详解

<!DOCTYPE html>
<html lang="en">
	<head>
		<title>three.js webgl - cloth simulation</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<style>
			body {
				font-family: Monospace;
				background-color: #000;
				color: #000;
				margin: 0px;
				overflow: hidden;
			}

			#info {
				position: absolute;
				padding: 10px;
				width: 100%;
				text-align: center;
			}

			a {
				text-decoration: underline;
				cursor: pointer;
			}

		</style>
	</head>

	<body>
		<div id="info">Simple Cloth Simulation<br/>
			Verlet integration with relaxed constraints<br/>
			<a onclick="wind = !wind;">Wind</a> |
			<a onclick="sphere.visible = !sphere.visible;">Ball</a> |
			<a onclick="togglePins();">Pins</a>
		</div>

		<script src="../build/three.js"></script>
		<!--引入一个兼容性检测
			(1)判断canvas兼容。
			(2)判断webgl兼容性。
			(3)在页面添加不兼容提示信息。-->
		<script src="js/Detector.js"></script>
		<!--引入一个使用鼠标观察物体的库,可动态观察、缩放和平移-->
		<script src="js/controls/OrbitControls.js"></script>
		<!--引入一个javascript性能检测库-->
		<script src="js/libs/stats.min.js"></script>
		<!--引入一个使用松弛约束解算器进行布料模拟的库-->
		<script src="js/Cloth.js"></script>

		<script>

			/* testing cloth simulation */

			var pinsFormation = [];
			var pins = [ 6 ];

			pinsFormation.push( pins );

			pins = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
			pinsFormation.push( pins );

			pins = [ 0 ];
			pinsFormation.push( pins );

			pins = []; // cut the rope ;)
			pinsFormation.push( pins );

			pins = [ 0, cloth.w ]; // classic 2 pins
			pinsFormation.push( pins );

			pins = pinsFormation[ 1 ];


			function togglePins() {

				pins = pinsFormation[ ~~ ( Math.random() * pinsFormation.length ) ];

			}
			//这是在使用Detector,检测兼容性;如果不兼容,会提示库代码里面getWebGLErrorMessage方法里面的英文提示内容
			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();

			var container, stats;
			var camera, scene, renderer;

			var clothGeometry;
			var sphere;
			var object;

			init();
			animate();

			function init() {

				container = document.createElement( 'div' );
				document.body.appendChild( container );

				// scene

				scene = new THREE.Scene();
				scene.background = new THREE.Color( 0xcce0ff );
				// 设置场景的雾化距离(第一次参数是雾的颜色,第二个数值表示雾从哪个距离开始显示默认1,第三个表示雾的结束位置默认1000)
				scene.fog = new THREE.Fog( 0xcce0ff, 500, 10000 );

				// camera
				//透视相机:构造一个视锥体垂直视野角度为30,视锥体长宽比为window.innerWidth / window.innerHeight,视锥体近端面为1,远端面为10000的透视摄像机
				camera = new THREE.PerspectiveCamera( 30, window.innerWidth / window.innerHeight, 1, 10000 );
				camera.position.set( 1000, 50, 1500 );

				// lights

				var light, materials;
				// 为场景添加一个环境光,均匀地洒在场景上
				scene.add( new THREE.AmbientLight( 0x666666 ) );
				// 创建一个平行光线,可以产生投影,第一个参数表示光线的颜色,16进制默认为白色,第二个表示光的强度,默认1
				light = new THREE.DirectionalLight( 0xdfebff, 1 );
				// 设置光线从(50,200,100) 到 (0,0,0) 沿着这条线照射
				light.position.set( 50, 200, 100 );
				// 将光线的向量与所传入的标量1.3进行相乘。
				light.position.multiplyScalar( 1.3 );
				//castShadow 如果设置为 true 该平行光会产生动态阴影。 警告: 这样做的代价比较高而且需要一直调整到阴影看起来正确
				light.castShadow = true;
				// 设置阴影贴图的宽度和高度
				light.shadow.mapSize.width = 1024;
				light.shadow.mapSize.height = 1024;

				var d = 300;
				// 在光的世界里。这用于生成场景的深度图;从光的角度来看,其他物体背后的物体将处于阴影中。
				light.shadow.camera.left = - d;
				light.shadow.camera.right = d;
				light.shadow.camera.top = d;
				light.shadow.camera.bottom = - d;

				light.shadow.camera.far = 1000;

				scene.add( light );

				// cloth material
				// 创建一个texture加载器,内部可以加载图片
				var loader = new THREE.TextureLoader();
				var clothTexture = loader.load( 'textures/patterns/circuit_pattern.png' );
				/**
				 * anisotropy 沿着轴,通过具有最高纹素密度的像素的样本数。 默认情况下,这个值为1。
				 * 设置一个较高的值将会产生比基本的mipmap更清晰的效果,代价是需要使用更多纹理样本
				*/
				clothTexture.anisotropy = 16;
				//MeshLambertMaterial是一种高级材质,可以用来创建暗淡的并不光亮的表面
				var clothMaterial = new THREE.MeshLambertMaterial( {
					map: clothTexture,			//颜色贴图  类型为Texture
					side: THREE.DoubleSide,		//定义将要渲染哪一面 - 正面,背面或两者,LeftSide单侧
					alphaTest: 0.5,				//透明度 设置运行alphaTest时要使用的alpha值。如果不透明度低于此值,则不会渲染材质。默认值为0。
					// color:0x00ffff            可以设置材质的颜色,即使是加载的图片也能改变颜色
				} );
 
				// cloth geometry
				/**
				 * 创造几何体,根据传入的公式
				 * clothFunction该函数以u、v值作为参数定义每个顶点的位置。需要返回three.vector3的值,u和v的范围是0-1,此方法在clothjs中
				 * cloth.w	该属性定义u值应该分成多少份
				 * cloth.h	该属性定义v值应该分成多少份
				*/
				clothGeometry = new THREE.ParametricGeometry( clothFunction, cloth.w, cloth.h );

				// cloth mesh
				/**
				 * 属性:position	rotation	scale	translateX/Y/Z
				 * 前三种属性设置有三种方式
				 * 		直接设置相关坐标轴单个轴设置	cube.position.x = 5;
				 * 		一次性设置x,y和z坐标的值(下面采用的这种方式)		cube.position.set(5,6,7);
				 * 		因为它们都是一个THREE.Vector3对象,所以我们可以直接赋值一个新的对象给它	cube.position = new THREE.Vector3(5,6,7);
				*/
				object = new THREE.Mesh( clothGeometry, clothMaterial );//这是常规写法,创建网格,需要一个几何体和一个或多个材质
				object.position.set( 0, 0, 0 );
				object.castShadow = true;
				scene.add( object );
				//这个材质不受光线和材质属性的影响,主要由与物体离相机的距离决定 很容易结合其他材质产生阴影效果;其参数类型与MeshLambertMaterial相似
				object.customDepthMaterial = new THREE.MeshDepthMaterial( {

					depthPacking: THREE.RGBADepthPacking,
					map: clothTexture,							
					alphaTest: 0.5

				} );

				// sphere
				//创建球体,至于为啥不用ParametricGeometry这种创建几何体的方式创建,可以参考https://www.jianshu.com/p/2e66b7db5549
				var ballGeo = new THREE.SphereBufferGeometry( ballSize, 32, 16 );
				var ballMaterial = new THREE.MeshLambertMaterial();

				sphere = new THREE.Mesh( ballGeo, ballMaterial );
				sphere.castShadow = true;
				sphere.receiveShadow = true;
				scene.add( sphere );

				// ground
				//使用草坪图片制作材质覆盖到场景中,与布料的创建一致
				var groundTexture = loader.load( 'textures/terrain/grasslight-big.jpg' );
				//贴面的重复方式
				groundTexture.wrapS = groundTexture.wrapT = THREE.RepeatWrapping;
				groundTexture.repeat.set( 25, 25 );
				groundTexture.anisotropy = 16;

				var groundMaterial = new THREE.MeshLambertMaterial( { map: groundTexture } );
				//创建平面
				var mesh = new THREE.Mesh( new THREE.PlaneBufferGeometry( 20000, 20000 ), groundMaterial );
				mesh.position.y = - 250;
				mesh.rotation.x = - Math.PI / 2;
				mesh.receiveShadow = true;
				scene.add( mesh );

				// poles
				//创建长方体,挂布料的杆子;下面的坐标都是以杆的中心点计算的
				var poleGeo = new THREE.BoxBufferGeometry( 5, 375, 5 );
				var poleMat = new THREE.MeshLambertMaterial();

				//这是左右杆
				var mesh = new THREE.Mesh( poleGeo, poleMat );
				//这是杆中心点坐标
				mesh.position.x = - 125;
				mesh.position.y = - 62;
				mesh.receiveShadow = true;
				mesh.castShadow = true;
				scene.add( mesh );

				var mesh = new THREE.Mesh( poleGeo, poleMat );
				mesh.position.x = 125;
				mesh.position.y = - 62;
				mesh.receiveShadow = true;
				mesh.castShadow = true;
				scene.add( mesh );
				//横杆
				var mesh = new THREE.Mesh( new THREE.BoxBufferGeometry( 255, 5, 5 ), poleMat );
				mesh.position.y = - 250 + ( 750 / 2 );
				mesh.position.x = 0;
				mesh.receiveShadow = true;
				mesh.castShadow = true;
				scene.add( mesh );
				//这是左右杆下面的小长方体
				var gg = new THREE.BoxBufferGeometry( 10, 10, 10 );
				var mesh = new THREE.Mesh( gg, poleMat );
				mesh.position.y = - 250;
				mesh.position.x = 125;
				mesh.receiveShadow = true;
				mesh.castShadow = true;
				scene.add( mesh );

				var mesh = new THREE.Mesh( gg, poleMat );
				mesh.position.y = - 250;
				mesh.position.x = - 125;
				mesh.receiveShadow = true;
				mesh.castShadow = true;
				scene.add( mesh );

				//这是坐标轴的起点位置,没加投影,只是看看坐标轴而已
				// var mesh = new THREE.Mesh( gg, poleMat );
				// mesh.position.y = 0;
				// mesh.position.x = 0;
				// scene.add( mesh );

				// renderer:具体查看http://www.manongjc.com/article/25286.html
				//WebGLRenderer具体的一些参数可以看这个文章,https://blog.csdn.net/weixin_41111068/article/details/82491985
				renderer = new THREE.WebGLRenderer( { antialias: true } );//开启反锯齿,默认是false
				//自动帮你根据设备dpr设置canvas的实际物理像素以及视口的大小,你也可以输入更大的dpr以获得“HD-DPI”品质的渲染
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );

				container.appendChild( renderer.domElement );
				//所有纹理和颜色都是预乘伽马
				renderer.gammaInput = true;
				//所有纹理和颜色需要以预乘伽马输出
				renderer.gammaOutput = true;
				//阴影贴图的引用,默认值为false
				renderer.shadowMap.enabled = true;

				// controls:具体可见https://blog.csdn.net/objdreammylife/article/details/81486018
				//这里初始化了OrbitControlsjs用于对场景的操作
				var controls = new THREE.OrbitControls( camera, renderer.domElement );
				controls.maxPolarAngle = Math.PI * 0.5;		//最大仰视角和俯视角
				//相机移动距离
				controls.minDistance = 1000;				
				controls.maxDistance = 5000;

				// performance monitor
				//右上角帧数
				stats = new Stats();
				container.appendChild( stats.dom );

				//监听页面变化,更新相机投影

				window.addEventListener( 'resize', onWindowResize, false );
				//球体不显示
				sphere.visible = ! true;

			}

			//

			function onWindowResize() {
				// 更新相机投影矩阵
				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();

				renderer.setSize( window.innerWidth, window.innerHeight );

			}

			//

			function animate() {
				//执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画
				requestAnimationFrame( animate );

				var time = Date.now();

				var windStrength = Math.cos( time / 7000 ) * 20 + 40;
				//定义在clothjs中
				windForce.set( Math.sin( time / 2000 ), Math.cos( time / 3000 ), Math.sin( time / 1000 ) )
				//方法在threejs中
				windForce.normalize()
				windForce.multiplyScalar( windStrength );
				//定义在clothjs中
				simulate( time );
				render();
				stats.update();

			}

			function render() {
				//clothjs中创建
				var p = cloth.particles;

				for ( var i = 0, il = p.length; i < il; i ++ ) {
					//这是几何图形
					clothGeometry.vertices[ i ].copy( p[ i ].position );

				}

				clothGeometry.verticesNeedUpdate = true;

				clothGeometry.computeFaceNormals();
				clothGeometry.computeVertexNormals();

				sphere.position.copy( ballPosition );

				renderer.render( scene, camera );

			}

		</script>
	</body>
</html>

参考资料

查看了很多文章,有的直接在代码中直接注释了文章内容

<think>我们正在处理一个关于Three.js的问题,具体是关于`webgl_materials_envmaps_groundprojected`案例中全景图变形的问题。用户遇到了全景图在应用地面投影环境贴图时出现变形的情况。 首先,我们需要理解地面投影环境贴图(Ground Projected Environment Map)的作用。它通常用于将环境贴图(如全景图)投影到地面上,以创建反射效果,常见于室外场景中。然而,如果设置不当,可能会导致贴图变形。 根据用户提到的示例`webgl_materials_envmaps_groundprojected`,我们可以参考Three.js官方示例中的代码。该示例展示了如何创建一个地面投影环境贴图。 常见问题原因: 1. 环境贴图(全景图)的宽高比与场景中地面的宽高比不一致,导致拉伸变形。 2. 投影盒(projection box)的尺寸设置不正确,使得环境贴图在投影时发生扭曲。 3. 相机位置或视角问题,导致观察时出现变形。 解决方案: 1. 确保环境贴图的宽高比与地面的宽高比一致。例如,如果地面是一个矩形,那么环境贴图也应该具有相同的宽高比,或者进行适当的裁剪。 2. 调整投影盒的尺寸,使其与地面的尺寸相匹配。在示例中,投影盒是通过`Box3`来定义的,我们需要根据实际地面的大小来设置这个盒子。 3. 检查相机的设置,确保视角合适,避免广角镜头带来的畸变。 在`webgl_materials_envmaps_groundprojected`示例中,关键代码部分如下: ```javascript // 创建环境贴图 new RGBELoader() .load( 'textures/equirectangular/royal_esplanade_1k.hdr', function ( texture ) { texture.mapping = THREE.EquirectangularReflectionMapping; // 创建投影盒 const projectionBox = new Box3(); projectionBox.setFromCenterAndSize( new Vector3( 0, 0, 0 ), new Vector3( 30, 30, 30 ) ); // 创建地面投影环境贴图材质 const material = new MeshStandardMaterial( { color: 0xffffff, envMap: texture, envMapIntensity: 0.5 } ); const groundProjectedEnv = new GroundProjectedEnv( texture, projectionBox ); groundProjectedEnv.material = material; groundProjectedEnv.scale.set( 50, 50, 50 ); scene.add( groundProjectedEnv ); } ); ``` 注意:在示例中,投影盒的大小设置为30x30x30,而地面投影环境贴图对象的缩放设置为50。如果实际地面的尺寸与投影盒的尺寸不匹配,就可能导致变形。 因此,我们可以根据实际地面的尺寸调整投影盒的尺寸和位置,以及地面投影环境贴图对象的缩放。例如,如果地面是一个长100宽50的矩形,那么投影盒应该设置为: ```javascript projectionBox.setFromCenterAndSize( new Vector3( 0, 0, 0 ), new Vector3( 100, 10, 50 ) ); ``` 同时,调整地面投影环境贴图对象的缩放,使其覆盖整个地面: ```javascript groundProjectedEnv.scale.set( 100, 1, 50 ); ``` 另外,确保环境贴图本身是等距柱状投影(equirectangular)的全景图,并且没有畸变。如果环境贴图本身有畸变,那么投影到地面上也会出现变形。 如果问题仍然存在,可能需要调整材质的环境贴图强度(envMapIntensity)或其他参数,以减少视觉上的变形感。 最后,如果用户使用的是自定义全景图,请确保全景图的质量和比例适合地面投影。 总结步骤: 1. 检查环境贴图与地面尺寸比例是否匹配。 2. 调整投影盒的尺寸和位置以匹配地面。 3. 调整地面投影环境贴图对象的缩放和位置。 4. 检查相机设置,避免广角畸变。 希望这些解决方案能帮助用户解决全景图变形的问题。 §§相关问题§§ 1. 如何为Three.js场景创建合适的环境贴图? 2. 地面投影环境贴图(GroundProjectedEnv)的工作原理是什么? 3.Three.js中,如何调整环境贴图的亮度和强度?</think>### Three.js 全景图变形问题解决方案 在 `webgl_materials_envmaps_groundprojected` 案例中出现全景图变形问题,通常由以下原因及解决方案构成: #### 常见原因与解决方案 1. **贴图比例不匹配** - **问题**:环境贴图宽高比与投影区域比例不一致 - **解决**: ```javascript // 调整投影区域尺寸匹配贴图比例 const projectionSize = new THREE.Vector3( texture.image.width * scaleFactor, projectionHeight, texture.image.height * scaleFactor ); projectionBox.setFromCenterAndSize(center, projectionSize); ``` 2. **投影盒(Projection Box)设置不当** - **问题**:`Box3` 定义的范围与场景尺寸不匹配 - **解决**: ```javascript // 精确计算场景边界 const sceneBoundingBox = new THREE.Box3().setFromObject(scene); const projectionBox = new THREE.Box3() .copy(sceneBoundingBox) .expandByScalar(1.2); // 增加20%余量 ``` 3. **坐标系未对齐** - **问题**:环境贴图原点与场景坐标系不一致 - **解决**: ```javascript // 将投影中心对齐场景中心 scene.updateMatrixWorld(true); const center = new THREE.Vector3(); sceneBoundingBox.getCenter(center); projectionBox.setFromCenterAndSize(center, size); ``` 4. **材质参数需要调整** - **问题**:`envMapIntensity` 或粗糙度参数导致视觉变形 - **解决**: ```javascript material.envMapIntensity = 0.8; // 降低反射强度 material.roughness = 0.5; // 增加表面粗糙度 ``` #### 推荐优化方案 1. **使用自定义着色器**(解决复杂变形): ```glsl // 片段着色器中添加投影校正 vec3 groundProjectedUV = vec3( position.x / projectionSize.x, position.z / projectionSize.z, 0.5 ); vec4 envColor = texture2D(envMap, groundProjectedUV.xy); ``` 2. **添加边缘混合**(消除硬边界): ```javascript material.onBeforeCompile = (shader) => { shader.fragmentShader = shader.fragmentShader.replace( '#include <output_fragment>', `float edgeFactor = 1.0 - smoothstep(0.8, 1.0, distance(vUv, vec2(0.5))); gl_FragColor.rgb = mix(gl_FragColor.rgb, envColor.rgb, edgeFactor); #include <output_fragment>` ); }; ``` 3. **启用纹理各向异性过滤**: ```javascript texture.anisotropy = renderer.capabilities.getMaxAnisotropy(); ``` #### 调试建议 1. 使用 `THREE.AxesHelper` 可视化坐标系: ```javascript scene.add(new THREE.AxesHelper(10)); ``` 2. 通过 `console.log(projectionBox)` 输出投影盒参数验证 3. 逐步调整 `GroundProjectedEnv` 的 `scale` 属性: ```javascript groundProjectedEnv.scale.set(1.1, 1.0, 0.9); // 分别调整XYZ轴缩放 ``` > **性能提示**:对于动态场景,每帧调用 `projectionBox.setFromObject(scene)` 会降低性能,建议在场景变化时手动更新投影盒[^1]。 [^1]: 参考 Three.js 官方文档中关于环境投影的性能优化建议 [^2]: 基于 webgl_clipping_stencil 案例的模板缓冲区应用经验
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值