cocos2dx实现自定义2D地形
来源:互联网 发布:海岛奇兵潜艇升级数据 编辑:程序博客网 时间:2024/06/05 00:22
先来看看效果:
对于2D地形的生成,可以采用2种方法,
1.使用建模软件将2D的地形模型构建好直接使用Sprite3D导入
优点:不需要太多程序控制,只是简单的导入
缺点:需要建模,而且还要转换成2D坐标,很难使用chipmunk加入物理特性
2.根据自定义数据使用顶点数据和shader。
下面来讲解如何实现,之后分析优缺点。
推荐一篇博客
如何制作一个类似Tiny Wings的游戏 Cocos2d-x 2.1.4
如果你看懂了上面的博客,那么就跟容易理解笔者讲解的。
首先准备,图片资源
草地(地形表面) 土地(地形底部)
地形数据,其实是由许多点连成线组成的,如图
只要获取这些点就可以得到一个比较平滑的地形,如果点的间隔比较大,得到的地形就没有那么平滑,所以两个点的水平距离要根据需要设置。
由于主要讲解如何根据点生成地形,这里就不讲解如何生成这么多点,有兴趣的同学可以加
QQ:842723654,一起讨论。
有了数据,那么应该如何生成地形呢?
我们都知道大多数3D模型是由许多点组成的(有些模型也包含曲线),这些点就组成了许多三角形,
这些三角形就组成了一个模型,给这个模型赋予贴图,就得到了一个完整的模型。
同样利用这个道理,我们可以自己构造一个2D模型。
对于上面由线段组成的曲线,如果将每一个点复制后向下平移一定的距离,就能得到
许多矩形(实际就多了一倍的顶点,目前还是点),现在就要把这些矩形拆分成三角形,
每个矩形沿对角线切割,就得到了三角形。
让我们想想opengl绘制三角形的方法,
glBegin(GL_TRIANGLES); ..... glEnd(); 这种方法又慢又费资源。
glBegin(GL_TRIANGLE_STRIP); ..... glEnd(); 这种方法比刚才的快了一点
glDrawArrays(GL_TRIANGLE_STRIP, , ); 这种一次绘制的就是我们想要的,
其实cocos2dx也是利用glDrawArrays来减少绘制次数的,尤其是对于粒子,一次性
传入所有粒子的顶点,一次性绘制,岂能不快。
这里也提一下,假如提供了1,2,3,4,5,6,6个顶点:
GL_TRIANGLES,每三个顶点构成一个三角形,(1,2,3),(4,5,6),绘制2个三角形
GL_TRIANGLE_STRIP,前面的两个顶点与当前顶点构成一个三角形,
(1,2,3),(2,3,4),(3,4,5),(4,5,6),绘制4个三角形
可见如果要节约资源,采用GL_TRIANGLE_STRIP是个不错的选择。
关于地形顶点数据的存储,笔者采用文件存储,前4个字节,表示顶点的个数,
以后每8个字节表示一个顶点,因为Vec2就占8个字节,正好可以读入。
如下,载入数据:
auto data = FileUtils::getInstance()->getDataFromFile(fileName);
CC_BREAK_IF(data.isNull());
int numPoint = *((int*)data.getBytes()); // 读取顶点个数
_points.resize(numPoint);
memcpy(&_points[0], data.getBytes() + 4, data.getSize() - 4); // 读取顶点数据
这里_points是std::vector<Vec2> _points;
vector的数据是连续的,而且Vec2又占8个字节,又不需要特殊的构造。
下一步就是将顶点数据转换成三角形顶点
对于土地部分
bool TerrainBottom::initTriangle(std::vector<Vec2>& points){// trianglesVec2 pt(0.f, 0.f);for (std::size_t i=0; i< points.size(); ++i){pt.x = points[i].x;_triangles.push_back(pt);// 先下方顶点_triangles.push_back(points[i]);// 再上方顶点}_bUserTexCoord = false; // 是否使用自定义纹理坐标return true;}
只要保证顶部地形的走势就可以,底部要做的就是一直拖到屏幕最下方,
所以根据上面标有数字的图片,按照数字顺序添加顶点就可以了。
而对于表面(草地)部分
实际上就是给土地镶嵌一层花边,如图中的黑色粗线
只要将每个顶点的Y坐标上下平移一小段距离,得到图中的橘色多边形就可以了
bool TerrainSurface::initTriangle(std::vector<Vec2>& points){// trianglesVec2 pt;for (std::size_t i=0; i< points.size(); ++i){pt = points[i];_triangles.push_back(Vec2(pt.x, pt.y - 16.f)); // 向下平移_texCoords.push_back(Vec2(pt.x / 256.f, 0.f));_triangles.push_back(Vec2(pt.x, pt.y + 16.f)); // 向上平移_texCoords.push_back(Vec2(pt.x / 256.f, 1.f));}_bUserTexCoord = true;// 是否使用自定义纹理坐标return true;}
这样土地和表面的三角顶点数据就构造好了
纹理的处理
纹理采用的是GL_REPEAT,对于地形的显示至关重要。
// textureTexture2D::TexParams texParams;texParams.magFilter = GL_LINEAR;texParams.minFilter = GL_LINEAR;texParams.wrapS = GL_REPEAT;texParams.wrapT = GL_REPEAT;tex->setTexParameters(texParams);
那么纹理坐标应该怎么生成呢?
土地可以根据(坐标/图像的尺寸)得到。
表面水平方向/图像的宽带,竖直方向不能根据坐标,因为纹理是重复的,
只要将上顶点设为0.f,下顶点设为1.0f,就可以,纹理坐标Y坐标与opengl坐标系是相反的。
顶点坐标纹理都已准备好,下面就是如何绘制了.
Shader+绘制函数就可以搞定了,
继承Node,重载draw函数
void TerrainSprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags){_customCmd.init(_globalZOrder, transform, flags);_customCmd.func = CC_CALLBACK_0(TerrainSprite::onDraw, this, transform, flags);renderer->addCommand(&_customCmd);}
定义一个CustomCommand,目的调用自己的绘制方法,
void TerrainSprite::onDraw(const Mat4 &transform, uint32_t flags){getGLProgramState()->apply(transform);glDrawArrays(GL_TRIANGLE_STRIP, 0, _triangles.size());CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, _triangles.size());}
我们并没有使用glVertexPointer, glTexCoordPointer, 因为可以利用cocos2dx 的shader设置顶点属性
getGLProgramState()->setVertexAttribPointer,
void setVertexAttribPointer(const std::string &name, // 属性名 如attribute vec4 a_positionGLint size, // 每个元素占几个type GLenum type, // 元素分量类型 如Vec2 占2个float type = GL_FLOATGLboolean normalized, // 是否为法线? GLsizei stride, // 元素间隔 如 sizeof(Vec2)GLvoid *pointer // 元素指针);
可以这样提供给shader顶点坐标, 前提顶点着色器要有attribute vec4 a_position
getGLProgramState()->setVertexAttribPointer("a_position",2, GL_FLOAT, GL_FALSE, sizeof(Vec2), &_triangles[0]);
同理纹理坐标
getGLProgramState()->setVertexAttribPointer("a_texCoord",2, GL_FLOAT, GL_FALSE, sizeof(Vec2), &_texCoords[0])
可以参照tests的ShaderTests
到此我们已经知道地形绘制的方法,具体怎么实现呢?
组织方式
Terrain类管理地形的表面和土地
TerrainSurface类和TerrainBottom类分别显示地形的表面和土地
两者继承TerrainSprite类,TerrainSprite类显示自定义网格,
考虑一下Surface 和 Bottom 不同之处: 纹理,三角形数据,shader
仅仅几个参数不同,所以可以合并到TerrainSprite.
先来分析Terrain,载入地形数据,初始化表面和土地
lass Terrain : public Node{public:static Terrain* create(const std::string &fileName); // 根据文件生成地形bool init(const std::string &fileName);// 初始化private:bool loadTerrainData(const std::string &fileName); // 载入地形数据private:std::vector<Vec2> _points; // 保存顶点数据TerrainSurface* _surface; // 地形表面TerrainBottom* _bottom; // 地形土地};
初始化代码:
bool Terrain::init(const std::string &fileName){do {CC_BREAK_IF(!Node::init());// 基类初始化CC_BREAK_IF(!loadTerrainData(fileName)); // 载入数据_bottom = TerrainBottom::create(_points); // 土地CC_BREAK_IF(_bottom == nullptr);this->addChild(_bottom);_surface = TerrainSurface::create(_points); // 表面CC_BREAK_IF(_surface == nullptr);this->addChild(_surface);return true;} while(0);return false;}
然后TerrainSprite, 根据顶点数据构造三角顶点和纹理顶点,初始化shader,并绘制,头文件就不用看了,
由于三角形的处理不同所以提供了一个纯虚函数,让子类实现。
class TerrainSprite : public Node{public:static TerrainSprite* create(std::vector<Vec2>& points, Texture2D* tex, const std::string& vertFile, const std::string& fragFile);bool init(std::vector<Vec2>& points, Texture2D* tex, const std::string& vertFile, const std::string& fragFile);void draw(Renderer *renderer, const Mat4 &transform, uint32_t flags);void onDraw(const Mat4 &transform, uint32_t flags);protected:virtual bool initTriangles(std::vector<Vec2>& points) = 0;bool initShader(Texture2D* tex, const std::string &vertFile, const std::string& fragFile);protected:CustomCommand _customCmd;std::vector<Vec2> _triangles;std::vector<Vec2> _texCoords;bool _bUserTexCoord;};
先来看看初始化,初始化shader,顶点数据,设置参数
bool TerrainSprite::init(std::vector<Vec2>& points, Texture2D* tex, const std::string& vertFile, const std::string& fragFile){do {CC_BREAK_IF(!Node::init());CC_BREAK_IF(!initShader(tex, vertFile, fragFile));CC_BREAK_IF(!initTriangles(points));getGLProgramState()->setVertexAttribPointer("a_position", 2, GL_FLOAT, GL_FALSE, sizeof(Vec2), &_triangles[0]);// 顶点坐标if (_bUserTexCoord){getGLProgramState()->setVertexAttribPointer("a_texCoord", 2, GL_FLOAT, GL_FALSE, sizeof(Vec2), &_texCoords[0]); // 纹理坐标}return true;} while (0);return false;}
再来看看shader的初始化, 改变纹理参数, 向shader设置纹理及尺寸
bool TerrainSprite::initShader(Texture2D* tex, const std::string &vertFile, const std::string& fragFile){// textureTexture2D::TexParams texParams;texParams.magFilter = GL_LINEAR;texParams.minFilter = GL_LINEAR;texParams.wrapS = GL_REPEAT;texParams.wrapT = GL_REPEAT;tex->setTexParameters(texParams);// shaderauto glProgram = GLProgram::createWithFilenames(vertFile, fragFile);auto glProgramState = GLProgramState::getOrCreateWithGLProgram(glProgram);glProgramState->setUniformTexture("u_terrain", tex); glProgramState->setUniformVec2("u_texSize", Vec2(tex->getContentSize().width, tex->getContentSize().height));setGLProgramState(glProgramState);return true;}
最后分析TerrainSurface和TerrainBottom
class TerrainSurface : public TerrainSprite{public:static TerrainSurface* create(std::vector<Vec2>& points);protected:virtual bool initTriangles(std::vector<Vec2>& points);};
如何创建
TerrainSurface* TerrainSurface::create(std::vector<Vec2>& points){auto node = new (std::nothrow) TerrainSurface;if (node != nullptr){auto tex = Director::getInstance()->getTextureCache()->addImage("surface.png");node->init(points,tex, "glsl/terrain_surface.vert", "glsl/terrain_surface.frag");node->autorelease();return node;}delete node;return nullptr;}
重点是这句
node->init(points,tex, "glsl/terrain_surface.vert", "glsl/terrain_surface.frag");
纹理,shader,其实create还可以提供一个Texture2D*的参数以便得到不同的纹理。
initTriangles,在文章前面已经提到过,
TerrainBottom类似,具体请在文章最后下载源码查看。
Shader的编写
顶点着色器
attribute vec4 a_position;attribute vec2 a_texCoord;#ifdef GL_ESvarying mediump vec2 v_texCoord;#elsevarying vec2 v_texCoord;#endifuniform vec2 u_texSize;// 纹理尺寸,在bottom中用到void main(){ gl_Position = CC_MVPMatrix * a_position; // 一定记住是CC_MVPMatrixv_texCoord = a_texCoord; // 因为surface提供了纹理坐标v_texCoord = vec2(a_position.x / u_texSize.x, a_position.y / u_texSize.y); // bottom需要根据顶点计算使用a_position而不使用gl_Position,a_position是应用程序传递的坐标,而gl_Position是计算后在模型视图的位置,试着替换为gl_Position。}
片元着色器
uniform sampler2D u_terrain;#ifdef GL_ESvarying mediump vec2 v_texCoord;#elsevarying vec2 v_texCoord;#endifvoid main(){ gl_FragColor = texture2D(u_terrain, v_texCoord);}
使用很简单
_terrain = Terrain::create("map.data");this->addChild(_terrain);
可见自定义数据,可以通过shader轻松更改显示特效,同时还可以添加物理特性.
源码下载
- cocos2dx实现自定义2D地形
- cocos2dx实现自定义2D地形
- Unity 3d地形系统自定义画笔
- cocos2dx LOD地形剖析
- cocos2dx 自定义进度条的实现!
- 3D地形渲染
- Bullet(Cocos2dx)之创建地形
- Bullet(Cocos2dx)之创建地形
- Vision引擎中地形和 2D 概览地图介绍
- 2D地形对象区域优化-矩形合并算法
- 聚焦3D地形编程
- Unity 3D 地形元素
- cocos2dx学习笔记:自定义动作实现圆周运动
- 【cocos2dx 3.10】自定义事件的实现
- cocos2dx-js 实现自定义富文本
- 用Managed DirectX 9和C#实现3D地形可视化
- 第一人称游戏技术 - 实现在高低不平的3D地形上行走效果
- 多层地层和岩体3D表现及连续地形剖面的实现
- 解决init: sys_prop: permission denied uid:1003 name:service.bootanim.exit问题
- hdu 5171 GTY's birthday gift(矩阵快速幂)
- 深入分析 Java 中的中文编码问题
- Java细节
- MySQL批量更新死锁案例分析
- cocos2dx实现自定义2D地形
- Oracle 在ORDER BY 子句中加入主键或唯一键
- OpenCV学习篇之四 存取像素值
- 位图法
- 如何将Java项目转换为Java Web项目?
- 安卓Android控件ListView获取item中EditText值
- java并发编程-线程安全1:servlet访问统计
- 使用libcurl发送post请求
- Android screenOrientation 屏幕方向的设定与控制