webgl像机世界
本来打算接下来的webgl相关都用原生来写的,但发现时间上并不允许我这么做(诸多原因)
接下来的webgl相关博文,可能都是基于three来写了。
用了three后发觉它简化了n多n多n多的步骤。不过并不是意味着就放弃学习原生了,有些东西依然需要原生,比如shaderMaterial,不懂着色器你玩毛= =
再说了,没有原生的基础也并不能把框架/库 发挥的淋漓精致!
这篇实现 ↓
拖拽鼠标处理相机的旋转,wsad四个按键处理前后左右运动。
这个世界中几个平台,每个平台都要处理与相机的碰撞,最大的主平台上有2个箭头的区域,如果检测到进入该区域,就会传送至上方小平台中,上方的小平台也可通过箭头区域再传送回主平台。
主要就是上面这些东西扯到了摄像机的操作。
在摄像机系统中,要知道摄像机的位置、观察方向、还有几个互相垂直的向量。
摄像机的位置最简单不过了, camera.position.set(0,1,1) 完事。
观察方向可以指向z轴的反方向,如果位置是(0,1,1),那么lookAt就是(0,1,0)。
然后要确定那几个互相垂直的向量,那几个向量可以用来处理摄像机的运动。wsad就是前后左后4个方向的向量,那么上方和下方的向量可以通过叉乘得到了。
接着要处理摄像机的转向,目的就是为了旋转那几个互相垂直的向量,相机需要朝着那些方向向量运动,但是这里要忽略y轴的方向,这里仅考虑xz方向即可(对于这个demo来说)。
我这里使用欧拉角来处理旋转的,通过旋转俯仰角和偏航角,确定那几个方向向量。
这就是lookAt单位向量
this.v3_look = new Vec(sin(θ)*sin(φ),cos(θ),sin(θ)*cos(φ));
加上相机位置得出最终的lookAt
//... const v = this.v3_camera.add(this.v3_look); camera.lookAt(new THREE.Vector3(v.x,v.y,v.z));
对Walk对象 添加wsad
Walk.add('w',()=>{ _.v3_speed = _.v3_look.clone(); const v = _.v3_speed.scale(_.speed.front); x += v.x , z+=v.z; }); Walk.add('s',()=>{ _.v3_speed = _.v3_look.clone().scale(-1); const v = _.v3_speed.scale(_.speed.back); x += v.x,z += v.z; }); Walk.add('d',()=>{ _.v3_speed = _.v3_look_right.clone(); const v = _.v3_speed.scale(_.speed.right); x += v.x,z += v.z; }); Walk.add('a',()=>{ _.v3_speed = _.v3_look_right.clone().scale(-1); const v = _.v3_speed.scale(_.speed.left); x += v.x,z += v.z; });
增减两方位角
el.addEventListener('mousemove',e=>{ if(!behavior.rotate) return; if(!bx || !by) return; delta_x = (e.pageX-bx)*-.2,delta_y = (e.pageY-by)*.3; bx = e.pageX,by = e.pageY; _.φ += delta_x,_.θ += delta_y; });
到这儿已经可以操作一个可转动的相机,并且让它运动起来了~
相机动起来后,可能就会与物体发生碰撞,需要做碰撞检测。其实在three中做物体碰撞检测是很简单的,一些几何问题已经帮我们做了。相机与物体碰撞,其实就是检测lookat那条射线是否与物体的某个面相交,只要解决这个问题就好了。
three中有Raycaster类,我们只要提供射线向量和需检测的物体们就可以了。当检测到碰撞后还需要响应,这里没有考虑什么真实物理效果,我就简单的给出一个推动向量,让相机往后移一下。。
当摄像机运动到了那些检测区域,需要自动的往目标方向转动,随后还需沿着路径运动。
关于转动,这里我用的是slerp插值。如下:
如果v0和v1沿着圆弧运动,其中的插值向量是v(t)
因为有v(t)=av0+bv1
再对它们同时做叉乘
v0 × v(t)= bv0 × v1
v(t) × v1 = av0 × v1
转换为模的形式
|v0||v(t)|sin(1-c) = b|v0||v1|sin(c)
|v1||v(t)|sin((1-t)c) = a|v0||v1|sin(c)
化简求出a b
a = sin((1-t)c)/sin(c)
b = sin(tc)/sin(c)
所以最终如下
slerp(v0,v1,t) = [sin((1-t)c)/sin(c)] v0 + [sin(tc)/sin(c)] v1
这里相机沿着一条直线在运动,lookat就是这条直线,非常的简单。相机还可以是曲线路径,比如下图这样。这个就有点麻烦了,需要求出梯度,终值和初值的lookat要与两平台平行。
整个demo关于相机的东西就这些了~
这篇还有额外的两个东西,一个是全景图,还一个是点光源,这俩之前的博文有提到过,这里稍微有点不同。
之前的全景图是用css3实现的。这篇用的是three,如果用原生webgl的话,应该使用samplerCube 而不是 samper2D。
点光源和平行光是差不多的,平行光对于每个顶点的光照方向都是一致的,而点光源需要为每个顶点计算出不同的光线,就多出这一步,其他基本一样。
在之前平行光的基础上改改就好 ,如下
void main(){ vec4 smp = texture2D(u_smp, v_texture);
//...
//..
//.. vec3 normal = normalize(v_normal);
//关键 计算各个点的光线向量 vec3 li_di = normalize(light1-v_position); float nn = max(dot(li_di,normal ),0.01); // gl_FragColor = smp*vec4(light2,1.0) +smp*nn*vec4(light1_co,1.0); }
有没感觉目前整个场景空荡荡的?
之后会慢慢丰富起来的,我设想在每个平台上都存在一些关键技巧,比如模型的选中、模型的动画、自定义着色器、 粒子化等。。。
这些以后再慢慢添加吧。
bye bye