Tizen 的自定义 3D 图形
PUBLISHED
这篇文章的目的是使开发人员熟悉 Box2d 和 WebGL,并描述如何在 Tizen 应用程序中使用这些外部库。 这些主题都通过一个示例应用程序滚球游戏进行描述。
本文展示了如何 Tizen 的 Web 应用程序中添加和使用 Box2dWeb。 我们解释库的基础知识: 创建画布、世界、静态和动态对象。 您还将学习如何在调试模式下模拟物理世界。 本文的第二部分在 HTML 画布上介绍了基本的 WebGL。 您将学习如何创建三维物体,赋予它们质感和在渲染循环中绘制。 虽然这篇文章中所涵盖的问题很简单,我们希望目标读者具备计算机图形学的一些基本知识。
1 示例应用程序和先决条件
我们提供了一个名叫“滚球”的示例游戏以演示对物理和图形库的使用。 它使用 Box2dWeb 来模拟物理世界和 WebGL 以渲染图形 。 示例应用程序基于 undefinedQuery Mobile 1.2.0undefined 框架,使您能够创建具有直观且一致的用户界面的高度可扩展的 web 应用程序。 为能使用 jQuery Mobile,您的应用程序必须也包含undefined jQuery 1.8.undefined0 库。 undefined该示例应用程序在 Tizen SDK 2.0.0a2 上通过测试undefined。
undefined游戏主视图
代码示例使用自定义函数将日志消息发送到控制台。 它们位于应用程序的 js/lib/internal 文件夹中。
2 物理引擎 – Box2dWeb
Box2D 是一个开源的 C + + 库,用于在 2D 世界模拟刚体。 它根据 zlib 授权发布。 它已经被移植到许多不同的编程语言和环境中,包括 Java、Adobe、C#、Flash 和 JavaScript。
有两个主要开放源代码 Box2dWeb 端口到 JavaScript: Box2dJS (undefinedhttp://box2d-js.sourceforge.net/undefined) 和 Box2dweb (undefinedhttp://code.google.com/p/box2dweb/undefined)。 我们为示例游戏选择了第二个,因为这是一个最新的端口,并且存储在一个单一的文件中。
您可以在 undefinedhttp://code.google.com/p/box2dweb/undefined 上找到文件。 这是针对 Box2dFlash 的文件,但是 Box2dWeb 的结构与其类似。 这是因为 Box2dWeb 是从 Box2dFlash 编译,并且 JavaScript 1.6 和 ActionScript 2.0 两者都基于相同的 ECMAScript 标准。 您还可能会发现本教程非常有用:undefined http://www.box2d.org/manual.htmundefinedl。
Box2dWeb 是一个物理引擎。 在我们示例应用程序中的所有图形都由 WebGL 生成,这将在这篇文章的第二部分中加以解释。
在示例应用程序中,我们使用一个 2D 图形引擎,因为滚球只在二维空间中的一个平坦区域移动。 WebGL 用于实现更为真实的视图,并允许用户更改视图角度。
Box2dWeb 模拟刚体运动和相互作用,包括碰撞与关节。 该库还提供了可用于显示对象的数据。 它可以模拟由多边形、圆形和边缘形组成的物体。 重力、摩擦力和归还(使对象反弹的力)等力量可以应用于对象。
添加 Box2dWeb 到 Tizen 应用程序
要在 Tizen 应用程序中开始使用 Box2dWeb,您应该将库文件导入您的项目。 该文件还必须在 index.html 文件中声明:
您现在可以从您的 JavaScript 文件访问 Box2dWeb 函数。
创建画布
Box2dWeb 的画布应该在 index.html 文件中定义:
你可以通过编程方式设置画布的宽度和高度,使它适合屏幕:
var canvas = document.getElementById('canvas'); canvas.width = window.screen.availWidth; canvas.height = window.screen.availHeight;
window.screen.availHeight 函数返回可用的屏幕高度,无顶栏。
也许还应该禁用帆布滚动。 要做到这一点,您必须创建一个侦听器,以用于触摸移动事件,并对它们调用 preventDefault() 函数:
canvas.addEventListener("touchmove", handleTouchMove, true); function handleTouchMove(e) { e.preventDefault(); }
创建一个 Box2dWeb 世界
首先,您必须创建 Box2dWeb 世界的对象。 它负责管理内存、对象和模拟。
var world = new b2World(new b2Vec2(0, 0) // gravity , true // allow sleep );
构造函数采用两个参数: 重力和 allowSleep。 在示例应用程序中,我们将重力矢量设置为零,因为滚球将只在一个平面区域移动。 第二个参数 allowSleep 决定是否让物体在休息的时候进入睡眠状态。 模拟物体耗用大量的设备资源,所以当物体停止时,我们要暂停模拟,并使用睡眠状态,这种状态使用很少的 CPU 开销。 物体在与另一物体碰撞时唤醒。 您也可以手动执行操作:
bodyDef.awake = true;
创建地面
Box2dWeb 机构是库所使用的基本对象。 您可以定义它们的位置、形状和其他如摩擦力或密度等参数。 在我们的示例应用程序中,我们有一个矩形区域用于滚球。 它由四个矩形对象限制。
要创建一个物体,按照下列步骤操作:
- 建立物体定义
对于地面,我们使用静态物体:
var bodyDef = new b2BodyDef; bodyDef.type = b2Body.b2_staticBody; bodyDef.position.Set(0, 0);
- 定义夹具
夹具为物体赋予形状,并添加材料属性,如密度、摩擦力和恢复力。 一个夹具具有一个单一的形状。 一个物体可以有很多夹具。
下面的代码示例显示了如何为矩形创建一个夹具,该夹具从顶部限制地面。 其他三个矩形均以相似的方式创建。 唯一的变化是宽度、 高度和位置。
//create fixture object var fixDef = new b2FixtureDef; //define basic parameters fixDef.density = 1.0; fixDef.friction = 0.5; fixDef.restitution = 0.2; //scale for debug mode (it will be explained in the Setting debug draw section) var SCALE = 30; // canvas width and height in meters var height = canvas.height / SCALE; var width = canvas.width / SCALE; var borderSize = height / 50; //define shape fixDef.shape = new b2PolygonShape; fixDef.shape.SetAsBox(width, borderSize);
多边形的形状只能是凸多边形。 这意味着,每一个内角均小于或等于180 度,两点之间的每一条线段都保持在多边形的内部或边界上。
- 接下来,我们设置物体的位置,将夹具绑定到物体,并将物体添加到世界:
bodyDef.position.Set(0, 0); world.CreateBody(bodyDef).CreateFixture(fixDef);
创建动态物体
现在,我们已经创造了世界和地面,我们可以创建滚球了。 为此,我们将使用动态物体。
var bodyDef = new b2BodyDef; bodyDef.type = b2Body.b2_dynamicBody; bodyDef.linearDamping = 1.0; bodyDef.angularDamping = 1.0;
阻尼用于降低移动体的速度。 它应该介于 0 和无穷大之间。 线性阻尼降低线速度,而角度阻尼降低角速度。
其余操作与静态物体完全相同。 对于滚球,我们使用一个圆形:
//radius of the main sphere, 1/10 of the screen width var mainSphereRadius = width / 10; fixDef.shape = new b2CircleShape(mainSphereRadius);
Box2dWeb 物体可以存储一些额外的用户数据。 在示例应用程序中,我们用它在游戏中保存滚球的当前状态信息(活动/闲置)。 这个额外信息在渲染循环中被传递给 WebGL 引擎。
var sphereBody = world.CreateBody(bodyDef); sphereBody.SetUserData(data); //in example application data is a number representing boule state
设置调试画
Box2dWeb 不提供任何用于显示物体的功能。 它只是计算所有物体的位置和旋转。 您在 Box2dWeb 示例中看到的视图是在调试模式中创建的。
此模式可用于调试目的。 它是在开发过程中非常有用,因为您可以在向物体附加图形层之前检查物体的外观和动作是否符合预期的效果。 在示例应用程序中,调试模式可以通过 main.js 文件中的一个全球调试变量进行开启/关闭切换。
var debugDraw = new b2DebugDraw(); debugDraw.SetSprite(document.getElementById("canvas").getContext("2d")); debugDraw.SetDrawScale(30); debugDraw.SetFillAlpha(0.5); debugDraw.SetLineThickness(1.0); debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit); world.SetDebugDraw(debugDraw);
上面的示例代码中最重要的值是的 scale 值(这里是 30)。 Box2dWeb 使用其自己的单位系统。 在渲染图形时,您需要将它转换为像素坐标。 scale 值 30 表示 1 米= 30 个像素。
请记住,示例应用程序中的所有形状位置和大小都相对于屏幕宽度和高度计算。
模拟世界
Box2dWeb 在离散的时间点刷新物体的位置和其它参数。 它通过一个解决物理方程的计算算法(称为 integrator)实现。 根据 BOX2D 手册,游戏需要相当于至少 60Hz(1/60秒)的刷新频率。 此外,Box2dWeb 使用限制求解器。 它在模拟中逐个解决所有限制。 要正确做到这一点,我们需要数次遍历所有的限制。 迭代次数由两个参数定义:速度迭代和位置迭代。 增加这些值会提高计算的精确度,但会影响性能。 通过时间步长、速度迭代和位置迭代这三个值,我们可以调用 Box2dWeb Step() 函数:
world.Step(1 / 60, 10, 10);
提示 Box2dWeb 世界使用 MKS 单位:米、公斤和秒。 它最适合于典型的现实世界大小的对象。 移动的对象应该在 0.1 至 10 米之间。 静态对象可达 50 米。 |
功能语法:world.Step(timeStep、velocityIterations、positionIterations);
上述数值来自库分布的演示。 您可以选择您自己的参数。
现在,我们可以在屏幕上绘制调试数据(如果我们处于调试模式):
world.DrawDebugData();
在每个 world 步骤后,您应该清除用 ClearForces 函数在您的物体上应用的任何力量:
world.ClearForces();
获得被触摸的物体
本文仅演示了 Box2dWeb API 的一部分。 有关更多的功能和更详细的说明,请参阅文档或手册。
用户通过触摸屏幕来控制游戏。 要获得任何给定坐标的物体,您应该创建一个对 b2World 的查询:
var selectedBody; /** * Get b2Body object that was touched * @param touchX x touch coordinate * @param touchY y touch coordinare * @returns Body, that was touched (b2Body object) */ function getBodyAtTouch(touchX, touchY) { var aabb = new b2AABB(); aabb.lowerBound.Set(touchX - 0.001, touchY - 0.001); aabb.upperBound.Set(touchX + 0.001, touchY + 0.001); // Query the world for overlapping shapes. selectedBody = null; world.QueryAABB(getBodyCB, aabb); return selectedBody; } /** * Get body touched and save it to selectBody variable * @param fixture Touched fixture * @returns true if success, false otherwise */ function getBodyCB(fixture) { if (fixture.GetBody().GetType() != b2Body.b2_staticBody) { if (fixture.GetShape().TestPoint(fixture.GetBody().GetTransform(), new b2Vec2(touchX, touchY))) { selectedBody = fixture.GetBody(); return false; } } return true; }
为了获得被触摸的物体,我们使用 QueryAABB() 方法。 它有两个参数。 第一个是一个回调函数,带有一个参数 - found 夹具。 在第二个参数中,我们传递要搜索的区域的坐标。 在这种情况下它只是一个单点,所以我们用一个小的搜索区域(一个正方形,边长为 0,001)。 该查询返回一个夹具。 我们检查夹具是不是地体(是不是静态)并从它得到 b2Body 对象。
3 WebGL
您刚才了解到 Box2DWeb 是一个物理引擎,所以除它以外,我们需要一些图形化的环境来显示形状。 我们决定为此使用 WebGL。
WebGL 是一个 Web 标准,支持在 HTML5 画布上绘图和绘制 2D、2.5D 或 3D 世界。 WebGL 基于 OpenGL ES 2.0,与其非常相似。 Tizen 的自定义 3D 图形 在其中除了我们在示例应用程序中使用的库之外,还可以发现很多有用的函数。 有 OpenGL 经验的开发人员将能非常迅速地适应该新环境。
WebGL是一个低级别、跨平台的 API。 它使用着色语言 (GLSL),直接在 GPU 处理命令。 因此 WebGL 提供了强大的性能和硬件加速。 另一方面它也不是很容易的,所以我们在讲解时不讲得太深。
GLSL
GLSL 是 OpenGL 的着色语言。 它基于 C 语言,其创建目的是为了让开发人员能不使用程序集语言而直接访问图形处理单元。
在这篇文章中,我们将使用一个额外的库,称为 glMatrix v0.9.5。 它提供很多有用的功能,使在 WebGL 的编程容易得多。 GlMatrix 库可以在这里找到:http://code.google.com/p/glmatrix/
在以下各节中,您将学习到如何设置 WebGL 环境、创建对象、将它们加载到的 WebGL 缓冲区并在 HTML5 画布上绘制它们。
在 HTML5 画布上初始化 WebGL
要使用特定的画布,我们需要得到它的上下文。 画布创建并显露一个固定的绘图表面。 您可以决定您要使用什么样的背景。 在我们的案例中,这将是一个 WebGL 的 3D 上下文:“WebGL 实验”。 画布也可以绘制 2D 的上下文。 要访问渲染上下文,使用 getContext() 方法,如下所示:
canvas = document.getElementById('canvas'); gl = canvas.getContext("experimental-webgl");
访问画布上下文后,我们可以关注管理在画布上绘图的着色器。
设置着色器
为了在画布上绘制任何东西,WebGL 需要两件东西:
- Clipspace 坐标
- 颜色
这两个参数必须传递到使用着色器的 WebGL。 由于 WebGL 绘制需要两件东西,因此也需要两个着色器。
- 顶点着色器
顶点着色器对付顶点。 - 片段着色器
此着色器着颜色像素。
最简单的着色器可以通过以下方式定义:
attribute vec2 aVertexPosition; void main() { gl_Position = vec4(aVertexPosition, 0, 1); } void main() { gl_FragColor = vec4(0,0,1,1); }
这些着色器都必须被定义。 上面的代码将以蓝色填补整个画布。 为了绘制一些更复杂的 3D 对象,我们将需要使用以下着色器:
attribute vec3 aVertexPosition; attribute vec3 aVertexNormal; attribute vec4 aVertexColor; attribute vec2 aTextureCoord; uniform mat4 uMVMatrix; uniform mat4 uPMatrix; uniform mat3 uNMatrix; varying vec2 vTextureCoord; void main(void) { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vTextureCoord = aTextureCoord; } precision mediump float; varying vec2 vTextureCoord; uniform sampler2D uSampler; void main(void) { gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)); }
上述着色器都比较复杂,但幸运的是,您不需要知道它们工作的细节。
着色器 - 基础知识
由于 WebGL 是 2D 的,我们需要为它提供 2D 坐标和相应着色。 但后来一切都在 3D 中完成,这是怎么做到的呢?顶点着色器的作用是将我们在 3D 所做的一切更改到 2D,并把它传递给 WebGL。 这是着色器的作用。 我们将我们的 3D 对象传递给着色器,着色器将它们更改为 2D、着色,并将结果传递到 WebGL 以在画布上绘制所有的东西。
有关如何改变 3D 到 2D 的指令存储在投影矩阵(uPMatrix“)中。 第二个重要的矩阵是一个模型视图矩阵 (uMVMatrix)。 它存储我们对我们的对象执行的所有操作。 这意味着如果我们将一些对象移动和缩放,有关这些操作的其他一些信息将存储在模型视图矩阵中。
总之,我们必须向着色器提供我们的 3D 对象、我们对这些对象想要执行的操作、以及我们如何要将这些元素转换为 2D 坐标等信息。
初始化着色器
现在着色器已经设置完毕,我们需要将它们的代码传递给 WebGL,以便将它们编译。 这可以通过以下方式完成:
var vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, str); gl.compileShader(vertexShader);
和片段着色器
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, str); gl.compileShader(fragmentShader);
传递给 shaderSource 函数的 str 变量必须包含一个适当的着色器实现,我们刚才在上一节中描述了着色器。
在 WebGL 中,着色器通过一个着色器程序处理。 我们必须创建此程序,并将我们编译的着色器链接到此程序。 没有着色器程序,着色器将无法工作。 这里是演示如何执行此操作的示例代码(请参阅 http://learningwebgl.com 博客):
var fragmentShader = getShader(gl, "shader-fs"); var vertexShader = getShader(gl, "shader-vs"); shaderProgram = gl.createProgram(); gl.attachShader(shaderProgram, vertexShader); gl.attachShader(shaderProgram, fragmentShader); gl.linkProgram(shaderProgram); if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { alert("Could not initialise shaders"); } gl.useProgram(shaderProgram);
正如前面提到,要绘制更复杂的东西,我们需要向着色器提供对象的坐标、颜色(或纹理)、一个投影矩阵和一个模型视图矩阵。 具体做法是创建 JavaScript 变量和着色器变量之间的某种桥梁。 下面的代码段负责将数据从 JavaScript 变量数据发送到着色器变量。
shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram,"aVertexPosition"); gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord"); gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute); shaderProgram.vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor"); gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute); shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix"); shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix"); shaderProgram.nMatrixUniform = gl.getUniformLocation(shaderProgram, "uNMatrix"); shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");
现在 WebGL 已经启动,而着色器已加载。
现在我们可以继续进行对象创建。 在绘制任何对象之前,必须首先创建该对象。 在 WebGL 创建一个对象意味着定义其顶点、索引和颜色或纹理坐标等。 所有这些参数必须传递到 WebGL 缓冲区。 然后每次我们想要绘制一个对象时,我们需要将这些缓冲区设置为活动,以告诉 WebGL 我们想要绘制此特定的对象。
在 WebGL 中创建对象
下面我们将介绍如何创建一个简单的 WebGL 对象 - 一个长方体。 这将是滚球游戏的地面对象。
要创建一个对象,您必须:
1. 定义范围
从 -1 到 1 的 WebGL 坐标的顶点。 这意味着每个对象的顶点坐标必须介于 -1 和 1 之间。 这没有问题,因为稍后我们将每个对象缩放到适合我们需要的大小。
地面的顶点如图中所示。
长方体坐标
请注意,x 轴是红色的,y 轴是蓝色的,而 z 轴是绿色的。 此坐标体系和 OpenGL ES 坐标体系是完全相同的。
共有 8 顶点,所以顶点数组看起来应该像这样:
var vertices = [ -1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 1.0, 1.0, 0.0, -1.0, 1.0, 0.0, -1.0, -1.0, -0.1, 1.0, -1.0, -0.1, 1.0, 1.0, -0.1, -1.0, 1.0, -0.1, ];
我们现在需要将此数组转化为一个缓冲区,并将它链接到 WebGL:
groundVertexPositionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, groundVertexPositionBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); groundVertexPositionBuffer.itemSize = 3; groundVertexPositionBuffer.numItems = 8;
2. 连接顶点
顶点需要正确连接到彼此,形成一个对象。 由于 WebGL 绘制的基本形状是三角形,所有的对象必须从三角形创建。 这些三角形创建一个长方体的表面。 通过简单的计算,我们定义 12 个三角形,并创建一个将存储它们坐标的数组:
var cubeVertexIndices = [ 0, 1, 2, 0, 2, 3, 0, 4, 3, 4, 7, 3, 0, 4, 1, 1, 5, 4, 1, 5, 6, 1, 6, 2, 3, 7, 2, 2, 6, 7, 7, 4, 6, 6, 4, 5 ];
上述数组中的每个数字是先前定义的顶点数组的索引。 因此,第一个三角形将从 0、1 和 2 顶点创建。 第二个三角形将从 0、2 和 3 的顶点创建等等,以此类推。 这个图像显示长方体顶点索引。
长方体顶点的索引
现在,我们创建一个缓冲区,并将其链接到 WebGL:
groundVertexIndexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, groundVertexIndexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW); groundVertexIndexBuffer.itemSize = 1; groundVertexIndexBuffer.numItems = 36;
3. 纹理对象
3. 为了创建纹理,我们定义纹理坐标,以便片段着色器知道在哪里绘制纹理。 每个顶点必须有自己的纹理坐标。 纹理是 2D 图像。 这意味着每个顶点将有一个与纹理点对应的 (x,y) 坐标对。 由于我们的长方体将模拟地面,我们需要通过两个顶部三角形和两个底部三角形纹理舒展草地。 请参阅下面的代码:
var textureCoords = [ 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, ];
在游戏开始时被点 4、5、6 和 7 限制的长方体表面将是用户可见的地面。 所有滚球都将放在这张表面上。 这就是为什么纹理必须通过这些点拉伸,而纹理坐标的范围是从点 4 的 0,0 到点 6 的 1,1。
您应该基于坐标创建一个缓冲区,并将其链接到 WebGL:
groundVertexTextureCoordBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, groundVertexTextureCoordBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW); groundVertexTextureCoordBuffer.itemSize = 2; groundVertexTextureCoordBuffer.numItems = 4;
不过我们还没有加载文件纹理。 我们现在就来加载(有关详情,请参阅 http://learningwebgl.com):
var texture = gl.createTexture(); texture.image = new Image(); texture.image.onload = function() { handleLoadedTexture(texture) } texture.image.src = "images/Grass.jpg";
加载映像时调用 handleLoadedTexture。 您可以参考下面的代码:
var handleLoadedTexture = function (texture) { gl.bindTexture(gl.TEXTURE_2D, texture); gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.bindTexture(gl.TEXTURE_2D, null); };
加载纹理文件后,我们需要将 WebGL 与纹理参考链接。 然后,我们可以将纹理设置为主动。 由于顶点、索引和纹理坐标已经加载到 WebGL,而且纹理图像也加载并链接,地面的创建已完成。
但是不能马上绘制地面物体,因为 WebGL 不知道我们要画什么。
绘制对象
绘图前的最后一个命令是将对象的缓冲区设置为活动。 为此,我们使用下面的代码:
gl.bindBuffer(gl.ARRAY_BUFFER, groundVertexPositionBuffer); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, groundVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, groundVertexTextureCoordBuffer); gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, groundVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, texture); gl.uniform1i(shaderProgram.samplerUniform, 0); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, groundVertexIndexBuffer);
我们现在需要调用最后一个方法以实际绘制各元素:
gl.drawElements(gl.TRIANGLES, groundVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
上面的代码将强制 WebGL 绘制我们已经设置的活动缓冲区。
上面的代码将强制 WebGL 绘制我们已经设置的活动缓冲区。 这是因为在 3D 世界 WebGL 中,需要设置其它一些参数,如透视图或模型视图和投影矩阵。
我们将使用一个额外的库来帮助我们进行设置。 glMatrix 库还能快速处理矩阵运算。
WebGL 设置
您必须设置几个重要的参数:
- 设置画布的背景色。 此方法将清除画布,并填入用户指定的 RGB 颜色。 最后一个参数是透明度。
gl.clearColor(0.0, 0.0, 0.5, 0.5);
- 启用缓冲深度:
这实际上将添加 z 轴。 通常 WebGL 相互绘制元素。 这意味着,最后绘制的元素将被绘制于所有东西的顶部,不论其 z 坐标如何。 启用深度缓冲时,我们告诉 WebGL 考虑 z 参数。 因此,接近相机的物体将被绘制在其它物体之上。
gl.enable(gl.DEPTH_TEST);
- 设置 WebGL 大小:
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
- 清除颜色和深度缓冲区
在每一个渲染迭代中,必须清除颜色和深度缓冲区以正确显示 3D 世界。 否则,以前绘制对象的踪迹将会保留并显示在画布上。
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
视角和视锥
您可以根据喜好创建并加载尽可能多的对象。 此外,您可以指定其位置,并指示 WebGL 在屏幕上绘制。 静止的 WebGL 将只绘制从相机角度看可见的物体,而不渲染视图范围之外的对象,从而不至于为它们浪费时间和资源。 这就是为什么必须使用 perspective 方法来法指定 WebGL 的视锥。 请参考下面的图像了解什么是视锥:
WebGL 视椎
视椎就是 zNear 和 zFar 之间金字塔的右面部分。 左面是相机。 使用 perspective() 方法设置上述两个变量(zNear 和 zFar)及金字塔的角度。 如果您现在希望让您的对象可见,我们必须将它们放置在这个视锥中,否则在渲染画面时不会考虑它们。
下面您可以找到为滚球游戏创建的角度:
mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);
45 值是金字塔的角度,0.1 和 100.0 是 zNear 和 zFar 的设置。 这意味着只有 0.1 至 100.0 z 坐标之间的对象会在屏幕上绘制。 如前面所述,pMatrix 是负责将 3D 场景更改为 2D 场景的投影矩阵。 这是因为 WebGL 基本上是在 2D 空间中工作,我们需要使用角度来创建 3D 的错觉。 通过创建投影矩阵的透视方法,我们向着色器提供如何进行从 3D 转换到 2D 的信息。
平移和旋转
可以在对象上执行的最简单和最基本的操作就是平移和旋转。 它们被用来在 WebGL 中移动和改变对象的大小。 将在对象上执行的所有操作必须都存储在一个模型视图矩阵中,供着色器以后使用。 这意味着每个操作被保存到一个矩阵。 做到这一点的最简单方法就是从单位矩阵开始。 单位矩阵是一个矩阵,不影响任何对象。
mat4.identity(mvMatrix);
移动和旋转操作方法如下:
mat4.translate(mvMatrix, [0, 0, -4]); mat4.rotateX(mvMatrix, tiltAngle); mat4.rotateZ(mvMatrix, twistAngle);
为了转换,我们必须通过 x、y 和 z 轴的转变传递一个向量。
如您所见,我们对要画的对象不指定任何参考。 这就是为什么我们要将一些缓冲区设为活跃状态,而 WebGL 则使用它们绘制对象。 另一个重要的事情是,我们不使用坐标告诉 WebGL 在哪里绘制,但我们在 3D 空间中移动照相机,而 WebGL 从当前相机位置的角度绘制它们。 所以,如果要绘制与 z 轴最接近的下一个元素,您必须以 z 的正值使用 translate() 方法。
这里还有一个可以缩放对象的功能:
mat4.scale(mvMatrix, [1, gl.viewportHeight/gl.viewportWidth, 1]);
对地面对象使用此功能将缩放地面对象以适合屏幕大小。
如您所见,这些功能中的每一个都传递对模型视图矩阵的引用。 因此,我们刚才执行的所有操作都将直接保存到 mvMatrix。 正如前面所述,mvMatrix 将在着色器中使用。
4 WebGL 接口为 Box2DWeb 提供
到现在为止,我们已经描述了 Box2D 的物理引擎和绘图环境 WebGL。 要做的最后一件事就是将它们连接。 在我们的滚球游戏中,我们采用的方法是由 WebGL 提供用于显示元素的简单方法,而 Box2D 使用这些方法来绘制。
我们决定将 WebGL 的绘图功能完全脱离游戏逻辑。 因此创建了滚球游戏应用程序中的 webgl.js 模块。 它提供了以下方法:
- webGLStart(canvas)
此方法在引用画布上初始化 WebGL。
调用此方法足以配置绘图画布。 此方法是负责我们在 WebGL 节的开头所述的所有着色器配置。 - initSphere (radius, sphereType)
以指定的类型在 WebGL 中创建一个球体。 在这个函数中创建了球面顶点和索引,加载了合适的纹理。 此函数负责“在 WebGL 中创建对象”一段中所描述的所有一切。 - drawScene(sphereData, distance, tiltAngle, twistAngle)
此函数用于进行 WebGL 的设置,如视角。 它还在变量 sphereData 提供的位置绘制所有先前创建的球体。
在这个函数中执行的操作在下面的章节中描述: “WebGL 的设置”、“透视和视锥”、“绘图对象”和“平移和旋转”。
渲染循环由 Box2D 在 game.js 模块中以更新函数执行。 在每次物理计算后,Box2D 调用更新函数。 此函数更新相机和板之间的球的位置以及距离。 这些参数被传递给 drawScene 方法。 调用 drawScene 方法时,便在画布上绘制一个新的框架。
5 游戏规则
游戏基于流行的滚球游戏游戏: http://en.wikipedia.org/wiki/Boules。 该游戏的目的是使用户的滚球(杰克)尽可能地接近目标滚球。
开始:
每个球员都有两个相同颜色的滚球。 它们被放置在屏幕的短边沿。 杰克(目标滚球)放在屏幕的另一侧。
玩法:
- 球员轮流移动各自的滚球。 球员在移动滚球后可以放大或旋转游戏板。 然后他点击屏幕,让下一位球员开始。
- 每个球员都可以移动三次
- 您可以轮到您的时候移动任何滚球(例如一名球员可以移动一个滚球三次,而让其余的滚球留住原地,或者移动一个滚球两次,另一个一次)。
- 每个球员只能移动自己的滚球
- 杰克仅可在被另一个滚球击中时移动
- 球的速度只取决于移动向量的长度(而不是您滑动手指的速度)
结束:
计算杰克和球员滚球之间的距离。 其滚球最接近杰克的球员赢。 游戏结束后,结果显示在另一个页面上。
结果屏幕
6 总结
本文介绍了如何使用 Box2dWeb 物理引擎和 WebGL 图形引擎来创建 Tizen 应用程序。 您可以使用这些库为 Tizen 创建高度互动的游戏。 本文还显示,移动应用程序中物理学可以由外部库处理,使开发人员能专注于应用程序逻辑和用户界面。