http://blog.csdn.net/kesalin/article/details/8351935

前言

本文介绍了OpenGL ES 2.0 中的顶点缓冲对象(VBO: Vertex Buffer Object)和索引缓冲对象(IBO: Indice Buffer Object)的用法,

在之前的文章中图元的绘制没用使用VBO, 要绘制的顶点数据是以顶点数组的形式保存在客户端的内存中的,在每次调用glDrawArrays或者glDrawElements的时候,顶点数组都会被从客户端内存copy到显卡内存。

VBO的作用,就是把这份每次都要copy的顶点数组缓存在显卡的内存中,VBO使得我可以直接在显卡内存中分配并缓存顶点数据。这样就避免了每次调用draw的时候的copy操作,从而提高了渲染性能同时降低了内存带宽和设备的电量消耗。

IBO跟VBO的原理类似,只不过IBO是缓存了VBO绘制时候的Indices.

根据OpenGL ES 2.0 “Golden Book”的建议,要想获得最好的性能,就要尽可能使用VBO和IBO来完成绘制任务。

下面以一个具体的例子来展示如何使用VBO和IBO完成一个正方体的绘制。

效果图

图中这个绿色的正方体就是绘制的结果

实现

主要分三步:

  1. 准备好顶点数据,顶点索引数据;

  2. 创建VBO和IBO,分别和顶点数据、顶点索引数据绑定;

  3. 使用绑定后的VBO和IBO来完成绘制

1. 准备好顶点数据,顶点索引数据

正方体有八个顶点,定义如下:

绘制正方体主要是确定其八个顶点的坐标,如下图所示:

按编号从0~7以不同的颜色标识,下面定义出这8个顶点

// x,y,z,  u,v,   r,g,b,a
Vertex cubeVertex[] =
{
{ -1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }, // 0
{ 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }, // 1
{ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }, // 2
{ -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }, // 3
{ -1.0f, -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }, // 4
{ -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }, // 5
{ 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }, // 6
{ 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }, // 7
}; // 根据上面图中的顶点标号,可以很容易确定三角形绘制的顶点索引.
// 正方体共6个面,如下编号为0~5, 每个面是个四边形,由两个三角形组成
// 比如第0个面,由 0,1,2 这个三角形和 0,2,3 这个三角形组成。其他面依次类推
GLubyte cubeIndices[] =
{
0, 1, 2, 0, 2, 3, // Quad 0
4, 5, 6, 4, 6, 7, // Quad 1
5, 3, 2, 5, 2, 6, // Quad 2
4, 7, 1, 4, 1, 0, // Quad 3
7, 6, 2, 7, 2, 1, // Quad 4
4, 0, 3, 4, 3, 5 // Quad 5
};

2. 创建VBO和IBO,分别和顶点数据、顶点索引数据绑定

上面定义好了两个数组,接下来分别把顶点数组和索引数据绑定到VBO和IBO。


unsigned int _vbo;
unsigned int _ibo; // ======== 创建并绑定VBO glGenBuffers(1, &_vbo); //创建VBO,1代表创建一个, 传入的_vbo是一个unsigned int,仅保存一个VBO.
glBindBuffer(GL_ARRAY_BUFFER, _vbo); // 绑定新创建的VBO
glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertex), cubeVertex, GL_STATIC_DRAW); // 传数据, 具体参数说明后面给出
glBindBuffer(GL_ARRAY_BUFFER, 0); // 解除当前绑定的VBO // ========= 创建并绑定IBO glGenBuffers(1, &_ibo); // 同VBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ibo); // 同VBO,仅仅是第一个参数不一样
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof( cubeIndices ), cubeIndices, GL_STATIC_DRAW); // 同VBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

3. 使用绑定后的VBO和IBO来完成绘制

万事俱备,现在开始绘制:

glBindTexture(GL_TEXTURE_2D, _textureCube);         // 立方体的纹理,忽略
glUniformMatrix4fv(_mvp, 1, false, mvpDog.data()); // 投影矩阵,忽略 // ========== 使用 VBO和IBO进行绘制
glBindBuffer(GL_ARRAY_BUFFER, _vbo); // 绑定之前创建好的VBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ibo); // 绑定之前创建好的IBO int offset = 0; // 从_vbo中取数据要使用偏移量来指定位置
// x,y,z 在数组的开头,因此偏移量是0.
glVertexAttribPointer(_position, 3, GL_FLOAT, false, sizeof(Vertex), reinterpret_cast<void*>(offset)); offset += 3 * sizeof(float); // 纹理坐标在x,y,z之后,因此要加上3个float的偏移量
glVertexAttribPointer(_uv, 2, GL_FLOAT, false, sizeof(Vertex), reinterpret_cast<void*>(offset)); glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, 0); // 使用_ibo指定的36个索引来绘制。 glBindBuffer(GL_ARRAY_BUFFER, 0); // 使用完要解除VBO绑定
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); // 使用完要解除IBO绑定

完整的源代码和Shader

VboScene.h

#pragma  once

#include "gl_include.h"
#include "ELShaderProgram.h"
#include <string>
#include <vector> NS_BEGIN(elloop);
NS_BEGIN(vbo_scene); class FirstCamera
{
public:
FirstCamera()
: _moveSpeed(5)
, _eye(0, 10, 0)
, _look(0.5, -0.4, -0.5)
, _up(0, 1, 0)
, _right(1, 0, 0)
{
}
CELL::float3 _eye;
CELL::float3 _look;
CELL::float3 _up;
CELL::float3 _right;
float _moveSpeed; void updateCamera(float dt)
{
using namespace CELL;
float3 tempLook = _look;
float3 direction = _look - _eye;
direction = normalize(direction);
unsigned char keys[300];
GetKeyboardState(keys);
if (keys[VK_UP] & 0x80)
{
_eye -= direction * (-_moveSpeed) * dt;
_look -= direction * (-_moveSpeed) * dt;
} if (keys[VK_DOWN] & 0x80)
{
_eye += direction * (-_moveSpeed) * dt;
_look += direction * (-_moveSpeed) * dt;
} if (keys[VK_LEFT] & 0x80)
{
_eye -= _right * _moveSpeed * dt;
_look -= _right * _moveSpeed * dt;
} if (keys[VK_RIGHT] & 0x80)
{
_eye += _right * _moveSpeed * dt;
_look += _right * _moveSpeed * dt;
}
}
}; class VboScene : public ShaderProgram
{
public:
struct Vertex
{
float x, y, z;
float u, v;
float r, g, b, a;
}; static VboScene* create();
void begin() override;
void end() override;
void render() override; uniform _mvp;
uniform _textureBg;
attribute _position;
attribute _uv; unsigned int _textureBgId;
unsigned int _textureCube; unsigned int _vbo;
unsigned int _ibo; FirstCamera _camera; protected: bool init();
VboScene()
: _mvp(-1)
, _textureBg(-1)
, _textureBgId(0)
, _position(-1)
, _uv(-1)
, _textureCube(0)
, _vbo(0)
, _ibo(0)
{
_vsFileName = "shaders/3D_projection_vs.glsl";
_fsFileName = "shaders/3D_projection_fs.glsl";
}
~VboScene()
{
glDeleteTextures(1, &_textureBgId);
glDeleteTextures(1, &_textureCube);
}
unsigned int loadMipMap(const std::vector<std::string> &fileNames);
unsigned int loadTexture(const std::string &fileName );
}; NS_END(vbo_scene);
NS_END(elloop);

VboScene.cpp

#include "scenes/VboScene.h"
#include "app_control/ELDirector.h"
#include "math/ELGeometry.h"
#include "include/freeImage/FreeImage.h" NS_BEGIN(elloop);
NS_BEGIN(vbo_scene); void VboScene::begin()
{
glUseProgram(_programId);
glEnableVertexAttribArray(_position);
glEnableVertexAttribArray(_uv);
} void VboScene::end()
{
glDisableVertexAttribArray(_uv);
glDisableVertexAttribArray(_position);
glUseProgram(0);
} bool VboScene::init()
{
_valid = ShaderProgram::initWithFile(_vsFileName, _fsFileName);
if ( _valid )
{
_position = glGetAttribLocation(_programId, "_position");
_uv = glGetAttribLocation(_programId, "_uv");
_textureBg = glGetUniformLocation(_programId, "_textureBg");
_mvp = glGetUniformLocation(_programId, "_mvp");
} std::vector<std::string> fileNames =
{
"images/p32x32.bmp",
"images/p16x16.bmp",
"images/p8x8.bmp",
"images/p4x4.bmp",
"images/p2X2.bmp",
"images/p1x1.bmp"
}; _textureBgId = loadMipMap(fileNames);
_textureCube = loadTexture("images/1.jpg"); _camera._eye = CELL::float3(1, 1, 1);
_camera._look = CELL::float3(0.5f, -0.4f, -5.5f);
_camera._up = CELL::float3(0.0f, 1.0f, 0.0f);
_camera._right = CELL::float3(1.0f, 0.0f, 0.0f); Vertex cubeVertex[] =
{
{ -1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }, // 0
{ 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }, // 1
{ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }, // 2
{ -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }, // 3
{ -1.0f, -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }, // 4
{ -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }, // 5
{ 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }, // 6
{ 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }, // 7
}; GLubyte cubeIndices[] =
{
0, 1, 2, 0, 2, 3, // Quad 0
4, 5, 6, 4, 6, 7, // Quad 1
5, 3, 2, 5, 2, 6, // Quad 2
4, 7, 1, 4, 1, 0, // Quad 3
7, 6, 2, 7, 2, 1, // Quad 4
4, 0, 3, 4, 3, 5 // Quad 5
}; // create vertex buffer object.
glGenBuffers(1, &_vbo);
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertex), cubeVertex, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0); glGenBuffers(1, &_ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof( cubeIndices ), cubeIndices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); // or
/* glBufferData(GL_ARRAY_BUFFER, sizeof vertexes, 0, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof vertexes, vertexes);*/ return _valid;
} VboScene* VboScene::create()
{
auto * self = new VboScene();
if ( self && self->init() )
{
self->autorelease();
return self;
}
return nullptr;
} unsigned int VboScene::loadMipMap(const std::vector<std::string> &fileNames)
{
unsigned int textureId = 0; glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_2D, textureId); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); size_t nums = fileNames.size();
for (size_t i=0; i<nums; ++i)
{
FREE_IMAGE_FORMAT format = FreeImage_GetFileType(fileNames[i].c_str(), 0); FIBITMAP *bitmap = FreeImage_Load(format, fileNames[i].c_str(), 0); bitmap = FreeImage_ConvertTo24Bits(bitmap); BYTE *pixels = FreeImage_GetBits(bitmap); int width = FreeImage_GetWidth(bitmap);
int height = FreeImage_GetHeight(bitmap); for (size_t j = 0; j < width*height * 3; j += 3)
{
BYTE temp = pixels[j];
pixels[j] = pixels[j + 2];
pixels[j + 2] = temp;
} glTexImage2D(GL_TEXTURE_2D, i, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels); FreeImage_Unload(bitmap);
} return textureId;
} void VboScene::render()
{
using namespace CELL; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); auto director = Director::getInstance();
Size s = director->getFrameSize();
float width = s._width;
float height = s._height; glViewport(0, 0, width, height); _camera.updateCamera(0.016); float gSize = 100;
float gPos = -5;
float repeat = 100; Vertex ground[] =
{
{ -gSize, gPos, -gSize, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f },
{ gSize, gPos, -gSize, repeat, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f },
{ gSize, gPos, gSize, repeat, repeat, 1.0f, 1.0f, 1.0f, 1.0f }, { -gSize, gPos, -gSize, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f },
{ gSize, gPos, gSize, repeat, repeat, 1.0f, 1.0f, 1.0f, 1.0f },
{ -gSize, gPos, gSize, 0.0f, repeat, 1.0f, 1.0f, 1.0f, 1.0f },
}; begin(); matrix4 matWorld(1);
matrix4 matView = lookAt(_camera._eye, _camera._look, _camera._up);
matrix4 matProj = perspective(45.0f, width / height, 0.1f, 100.f);
matrix4 mvp = matProj * matView * matWorld; // draw ground.
glUniform1i(_textureBg, 0);
glBindTexture(GL_TEXTURE_2D, _textureBgId);
glUniformMatrix4fv(_mvp, 1, false, mvp.data());
glVertexAttribPointer(_position, 3, GL_FLOAT, false, sizeof(Vertex), &ground[0].x);
glVertexAttribPointer(_uv, 2, GL_FLOAT, false, sizeof(Vertex), &ground[0].u);
glDrawArrays(GL_TRIANGLES, 0, sizeof(ground) / sizeof (ground[0])); // draw cube,
matrix4 matTrans;
matTrans.translate(0, 0, -1);
matrix4 mvpDog = matProj * matView * matTrans; glBindTexture(GL_TEXTURE_2D, _textureCube);
glUniformMatrix4fv(_mvp, 1, false, mvpDog.data()); glBindBuffer(GL_ARRAY_BUFFER, _vbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ibo); int offset = 0;
glVertexAttribPointer(_position, 3, GL_FLOAT, false, sizeof(Vertex), reinterpret_cast<void*>(offset)); offset += 3 * sizeof(float); // u,v is after x,y,z three floats.
glVertexAttribPointer(_uv, 2, GL_FLOAT, false, sizeof(Vertex), reinterpret_cast<void*>(offset)); glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, 0); // use ibo. glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); end();
} unsigned int VboScene::loadTexture(const std::string &fileName)
{
unsigned int textureId = 0; FREE_IMAGE_FORMAT format = FreeImage_GetFileType(fileName.c_str(), 0); FIBITMAP *bitmap = FreeImage_Load(format, fileName.c_str(), 0); int width = FreeImage_GetWidth(bitmap);
int height = FreeImage_GetHeight(bitmap); BYTE *pixels = FreeImage_GetBits(bitmap); int pixelFormat = GL_RGB;
if (FIF_PNG == format)
{
bitmap = FreeImage_ConvertTo32Bits(bitmap);
pixelFormat = GL_RGBA;
for (size_t i = 0; i < width*height * 4; i += 4)
{
BYTE temp = pixels[i];
pixels[i] = pixels[i + 2];
pixels[i + 2] = temp;
}
}
else
{
bitmap = FreeImage_ConvertTo24Bits(bitmap);
for (size_t i = 0; i < width*height * 3; i += 3)
{
BYTE temp = pixels[i];
pixels[i] = pixels[i + 2];
pixels[i + 2] = temp;
}
} glGenTextures(1, &textureId); glBindTexture(GL_TEXTURE_2D, textureId); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, pixelFormat, width, height, 0, pixelFormat, GL_UNSIGNED_BYTE, pixels); FreeImage_Unload(bitmap); return textureId;
} NS_END(vbo_scene);
NS_END(elloop);

顶点shader: 3D_projection_vs.glsl

precision lowp float;

attribute   vec3    _position;
uniform mat4 _mvp; attribute vec2 _uv;
varying vec2 _outUv; void main()
{
vec4 pos = vec4(_position.x, _position.y, _position.z, 1);
gl_Position = _mvp * pos;
_outUv = _uv;
}

片段shader: 3D_projection_fs.glsl


precision lowp float; varying vec2 _outUv;
uniform sampler2D _textureBg; void main()
{
vec4 bgColor = texture2D(_textureBg, _outUv);
gl_FragColor = bgColor;
}

源码下载

如果源码对您有帮助,请帮忙在github上给我点个Star, 感谢 :)

参考

本文侧重实战,api的使用细节请参考下面第二个链接,那里有各种OpenGL的API说明。

【C++ OpenGL ES 2.0编程笔记】8: 使用VBO和IBO绘制立方体 【转】的更多相关文章

  1. OpenGL ES 2.0 渲染管线 学习笔记

    图中展示整个OpenGL ES 2.0可编程管线 图中Vertex Shader和Fragment Shader 是可编程管线: Vertex Array/Buffer objects 顶点数据来源, ...

  2. 《OpenGL® ES™ 3.0 Programming Guide》读书笔记1 ----总览

    OpenGL ES 3.0 Graphics Pipeline OpenGL ES 3.0 Vertex Shader Transform feedback: Additionally, OpenGL ...

  3. OpenGL ES 3.0 基础知识

    首先要了解OpenGL的图形管线有哪些内容,再分别去了解其中的相关的关系: 管线分别包括了顶点缓冲区/数组对象,定点着色器,纹理,片段着色器,变换反馈,图元装配,光栅化,逐片段操作,帧缓冲区.其中顶点 ...

  4. OpenGL ES 2.0 Shader 调试新思路(二): 做一个可用的原型

    OpenGL ES 2.0 Shader 调试新思路(二): 做一个可用的原型 目录 背景介绍 请参考前文OpenGL ES 2.0 Shader 调试新思路(一): 改变提问方式 优化 ledCha ...

  5. OpenGL ES 3.0顶点着色器(一)

    OpenGL ES 3.0流程图 1.Vertex Shader(顶点着色器) 顶点着色实现了一种通用的可编程方法操作顶点. 顶点着色器的输入包括以下几个: • Shader program.程序的顶 ...

  6. 梳理 Opengl ES 3.0 (二)剖析一个GLSL程序

    OpenGL ES shading language 3.0 也被称作 GLSL,是个 C风格的编程语言. Opengl ES 3.0内部有两种可编程处理单元,即Vertex processor和Fr ...

  7. 梳理 Opengl ES 3.0 (一)宏观着眼

    Opengl ES 可以理解为是在嵌入式设备上工作的一层用于处理图形显示的软件,是Opengl 的缩水版本. 下图是它的工作流程示意图: 注意图中手机左边的EGL Layer Opengl ES是跨平 ...

  8. 【AR实验室】OpenGL ES绘制相机(OpenGL ES 1.0版本)

    0x00 - 前言 之前做一些移动端的AR应用以及目前看到的一些AR应用,基本上都是这样一个套路:手机背景显示现实场景,然后在该背景上进行图形学绘制.至于图形学绘制时,相机外参的解算使用的是V-SLA ...

  9. 在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping)

    在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping) 视差贴图 最近一直在研究如何在我的 iPad 2(只支持 OpenGL ES 2.0, 不支持 3.0) 上实现 视 ...

随机推荐

  1. Django-models class Meta:元类

    Django模型之Meta选项详解 Model 元数据就是 "不是一个字段的任何数据" -- 比如排序选项, admin 选项等等.   Django模型类的Meta是一个内部类, ...

  2. Django-urls路由分发

      例如:127.0.0.0:8000/blog/lucaq.html,当有多个应用时,需要在blog应用下进行路由,我们在blog应用下做一个urls路由分发,就需要include模块实现.   导 ...

  3. 无线路由器中WMM/Short GI/AP隔离各是什么功能, 开启时PC无法ping通手机.

      无线路由器的WMM功能和开启ap隔离,以及开启ShortGI有什么用 无线路由器中有开启WMM.开启Short GI和开启AP隔离分别代表什么呢?这是我在我的TP-LINK无线路由器TL-WR84 ...

  4. 什么是SVC模式【转】

    转自:http://blog.csdn.net/jobsss/article/details/7548550 版权声明:本文为博主原创文章,未经博主允许不得转载. ARM 处理器有二十七个寄存器,其中 ...

  5. DRF的限流功能

    限流,可以对接口访问的频次进行限制,以减轻服务器压力. 全局配置 在settings.py上添加 REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES': ( 'r ...

  6. slatstack Master的配置

    Salt系统非常简单并且易于配置,Salt系统的两个组件都有各自的配置文件.如"salt-master"是通过主配置文件来配置的,"salt-minion"是通 ...

  7. codeforces 739E

    官方题解是一个n2logn的dp做法 不过有一个简单易想的费用流做法 对每个小精灵,连边(A,i,1,pi) (B,i,1,ui) (i,t,1,0) (i,t,1,-pi*ui) 最后连边(s,A, ...

  8. AC日记——Andryusha and Colored Balloons codeforces 780c

    C - Andryusha and Colored Balloons 思路: 水题: 代码: #include <cstdio> #include <cstring> #inc ...

  9. Appscan计划扫描与扩展程序

    计划扫描:工具-->扫描调度程序 或者参考:http://pic.dhe.ibm.com/infocenter/apsshelp/v8r6m0/topic/com.ibm.help.common ...

  10. POJ 3264 Balanced Lineup 【线段树/区间最值差】

    Balanced Lineup Time Limit: 5000MS Memory Limit: 65536K Total Submissions: 62103 Accepted: 29005 Cas ...