起因
最近在使用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.youkuaiyun.com/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.youkuaiyun.com/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>
参考资料
查看了很多文章,有的直接在代码中直接注释了文章内容
- 关于webgl_animation_cloth案例分析的,很详细
- 关于 Detectorjs 介绍
- 关于 ParametricGeometry 介绍
- 关于 Mesh 介绍
- 关于 MeshDepthMaterial 介绍
- 关于 场景失真 介绍