加载场景,基本相机操作
从文件加载模型数据,并控制相机实现在模型中行走。
效果如图11。
图11
<div id="loadingtext">正在加载世界……</div>
加载文件可能需要些时间,放一句提示能适当提高用户体验,在这个基础上也可以根据自己的喜好加一些css,再在加载完成之后用JS代码去掉这个提示。
function webGLStart()
{
//...
loadWorld();
gl.enable(gl.DEPTH_TEST);
}
function loadWorld()
{
$.getJSON(
"/Public/json/world.json",
function(data)
{
handleLoadedWorld(data);
}
);
}
DEPTH_TEST这次是需要的。用jQuery的ajax来拿存好的数据world.json,由于json是JS常用数据传输格式,这里的getJSON是$.ajax方法的特例。关于ajax、get、post这里就不多解释了。
var worldVertexPositionBuffer = null;
var worldVertexTextureCoordBuffer = null;
function handleLoadedWorld(data)
{
var vertexCount = 0;
var vertexPositions = [];
var vertexTextureCoords = [];
for(var i = 0; typeof(data[i]) != "undefined"; i ++)
{
//环境中一个部分的顶点坐标
vertexPositions =
vertexPositions.concat(data[i].vertexPositions);
//然后是纹理坐标
vertexTextureCoords =
vertexTextureCoords.concat(data[i].vertexTextureCoords);
vertexCount ++;
}
worldVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, worldVertexPositionBuffer);
gl.bufferData(gl.ARRAY_BUFFER,
new Float32Array(vertexPositions), gl.STATIC_DRAW);
worldVertexPositionBuffer.itemSize = 3;
worldVertexPositionBuffer.numItems = data.vertexCount;
worldVertexTextureCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, worldVertexTextureCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER,
new Float32Array(vertexTextureCoords), gl.STATIC_DRAW);
worldVertexTextureCoordBuffer.itemSize = 2;
worldVertexTextureCoordBuffer.numItems = data.vertexCount;
$("#loadingtext").text("");
}
从事先准备的json文件拿到顶点与纹理的坐标数据,把数据放到对应数组里之后,就是我们熟悉的事情了。这里的world.json只是自己编写的数据,是标准的json格式,不过并不是什么规范的3D数据。
function drawScene()
{
//...
mat4.rotate(mvMatrix, mvMatrix, degToRad(-pitch), [1, 0, 0]);
mat4.rotate(mvMatrix, mvMatrix, degToRad(-yaw), [0, 1, 0]);
mat4.translate(mvMatrix, mvMatrix, [-xPos, -yPos, -zPos]);
}
设置相机,让视角在场景中移动。WebGL并不支持直接的相机操作,不过模拟一个不难。相机需要怎么动,让整个场景相反地动。
var pitch = 0;
var pitchRate = 0;
var yaw = 0;
var yawRate = 0;
var xPos = 0;
var yPos = 0.4;
var zPos = 0;
var speed = 0;
function handleKeys()
{
if(currentlyPressedKeys[188])
{
//","/"<"逗号键
pitchRate = -0.1;
}
else if(currentlyPressedKeys[190])
{
//"."/">"句号键
pitchRate = 0.1;
}
else
{
pitchRate = 0;
}
if (currentlyPressedKeys[65])
{
//A
yawRate = 0.1;
}
else if (currentlyPressedKeys[68])
{
//D
yawRate = -0.1;
}
else
{
yawRate = 0;
}
if(currentlyPressedKeys[87])
{
//W
speed = 0.003;
}
else if(currentlyPressedKeys[83])
{
//S
speed = -0.003;
}
else
{
speed = 0;
}
}
var lastTime = 0;
var joggingAngle = 0;
function animate()
{
var timeNow = new Date().getTime();
if(lastTime != 0)
{
var elapsed = timeNow - lastTime;
if(speed != 0)
{
xPos -= Math.sin(degToRad(yaw)) * speed * elapsed;
zPos -= Math.cos(degToRad(yaw)) * speed * elapsed;
joggingAngle += elapsed * 0.6;
yPos = Math.sin(degToRad(joggingAngle)) / 20 + 0.4;
}
yaw += yawRate * elapsed;
pitch += pitchRate * elapsed;
}
lastTime = timeNow;
}
和之前处理移动类似,把XZ理解为运动的平面,Y理解为高度,joggingAngle是个有意思的小把戏,模仿走得过程的“颠簸”,玩FPS游戏应该都能感觉到。
本节主要介绍了一种数据获取的方式和模拟相机效果的实现技巧,具体的代码都是已经熟悉的内容了。