大魔导师戴戴戴の博文 转载请保留原文链接!

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

 

posted @ 2016-10-25 10:22  戴戴戴x  阅读(599)  评论(0编辑  收藏  举报