Cocos2dx 2D界面实现一个景深小效果 您所在的位置:网站首页 Oculusvr能实现近大远小的效果吗 Cocos2dx 2D界面实现一个景深小效果

Cocos2dx 2D界面实现一个景深小效果

2023-07-30 13:11| 来源: 网络整理| 查看: 265

Cocos2dx 2D界面实现一个景深小效果 前言:矫情一下 说正经的:1 方案琢磨2 主代码分析 TPerspectiveLayer关注点 1:TPerspectiveLayer::enableAction关注点 2:TPerspectiveLayer::visit关注点 3:TPerspectiveLayer::onGridBeginDraw关注点 4:TPerspectiveLayer::onGridEndDraw 3 辅代码分析 NoCullSprite4 使用说明 后续叨叨一下

前言: 矫情一下

公司做老虎机,总需要保持与时俱进,主题玩法和活动玩法都要跟上用户需求。 多数主题玩法的基本表现是这样式儿的:

在这里插入图片描述

制作人也会参考实体机的表现,或一些视频里的效果来设计玩法。 这儿就接到个需求,要按照一个实体机的表现来制作一个主题玩法,表现是这样式儿的: 在这里插入图片描述

仔细分析参考视频,主要需求点还是在这个有透视效果的滚动轮盘。 正向3X5的信号块矩阵表现是最终结算界面,而与结算盘面衔接,并逐渐向屏幕里面延伸的轴面表现是假数据。 但这已然不重要,重点还是这个向内延展的透视效果,是我们需要的。

公司现在的项目用的是coco2dx 3.16 C++ lua 版本,用cocos2dx做2d项目很方便,效率也很高。 所以就有问题了,视频中的表现,琢磨着无论怎样都需要一些简单的3D技术来支持: 1 要么把所有信号块改成具有3d属性的模型,来构架一个3d小场景,不断移动各个模型位置和角度来实现。 2 要么就把所有的2D信号块绘制到一个RT上,再将这个RT以纹理的方式赋予一个指定形状模型上。 3 最重要的是,需要一个透视投影来计算,才能达到效果。

当前项目已经稳定在线,还是不希望大动作的改动现有框架,尽量简便的在原有2D项目上来实现。 经过几天的捣鼓,基本实现了以下效果,表现是这样式儿的:

在这里插入图片描述

说正经的:

记录了这个小功能的考虑和开发过程

1 方案琢磨

为了这个小效果很方便的融入2D UI的各种层级中,还是放弃了架设3D场景的念头,采用RT和自建模型的方式操作。此过程中需要建立一个容量很大的RT,来承载足够数量的信号块,这里指定的RT宽度是屏幕宽度,高度是2048,RT最终绘制的效果如下: 在这里插入图片描述

看到此图,就能明白点儿了,我们就是要把这个RT当纹理,贴到一个模型上,RT上这些内容,早已超出屏幕绘制区域。这样才能保证透视效果的远处,有足够的内容来填充。

对于模型就建成参考图中的样式,是手写的顶点数据,并未借助3dmax或者maya等工具。 正面图: 在这里插入图片描述

侧面图(蓝色点为顶点位置) 在这里插入图片描述 两侧相对的顶点左右对称,间距一样,Y轴高的顶点Z轴比较大,达到透视效果。

2 主代码分析 TPerspectiveLayer

建立一个继承自Node的TPerspectiveLayer类,主要是重写visit函数,将我们需要渲染的内容都绘制在这个节点上,再将这个节点绘制到屏幕上,使用方法和基本Node一摸一样。

关注点 1:TPerspectiveLayer::enableAction

初始化RT和透视模型顶点数据

1 初始化需要的RT,长宽要自己定义好,屏幕宽 X 2048 :(2048高目前够用,能容纳20个信号块)

Texture2D::PixelFormat format = Texture2D::PixelFormat::RGBA8888; this->_texture = new (std::nothrow) Texture2D(); this->_texture->initWithData(data, dataLen, format, win.width, 2048, s);

2 定义一个 Grabber,主要通知gl为此texture2d指定缓冲区ID,绘制的时候直接调用就行

_grabber = new (std::nothrow) Grabber(); _grabber->grab(_texture);

其中grab函数主要实现以下内容

glBindFramebuffer(GL_FRAMEBUFFER, _FBO); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->getName(), 0);

3 根据指定数据初始化顶点,绘制模型用:(注意,在此节点释放的时候,要清理这些数据)

_vertices = malloc(numOfPoints * sizeof(Vec3)); _texCoordinates= malloc(numOfPoints * sizeof(Vec2)); _indices = (GLushort*)malloc(_gridSize.width * _gridSize.height * sizeof(GLushort) * 6); 重点:顶点位置需要根据滚动轮盘大小来定数值,因为屏幕大小对Viewport影响,在不同的机型上,顶点显示的透视效果会不一致,所以需要对常规分辨率的屏幕尺寸做顶点位置适配。 顶点UV也是十分重要的,也会根据视口不同有变化,需要手动调节下数值,达到适配效果。 关注点 2:TPerspectiveLayer::visit

我们要重点操作的函数 需要重写里面的内容

void TPerspectiveLayer::visit(Renderer *renderer, const Mat4 &parentTransform, uint32_t parentFlags) { Code 1 : //-- 各种渲染准备 具体参考各个继承自Node的visit函数 – Code 2:重点:渲染子节点和自身之前,执行一个 TPerspectiveLayer::onGridBeginDraw 函数,目的是将GL的绘制缓冲区设置成咱们上面创建好的RT,希望接下来的内容都绘制在这个RT上,就当个预渲染函数。

_gridBeginCommand.init(_globalZOrder); _gridBeginCommand.func = CC_CALLBACK_0( TPerspectiveLayer::onGridBeginDraw, this); renderer->addCommand(&_gridBeginCommand);

Code 3: //-- 渲染子节点和自身 具体参考各个继承自Node的visit函数 – Code 4: 重点:已经将所有子节点和自身绘制到了RT上,执行一个 TPerspectiveLayer::onGridEndDraw 函数,目的是将这个RT作为纹理映射到模型上,并将这个模型绘制到屏幕。

_gridEndCommand.init(_globalZOrder); _gridEndCommand.func = CC_CALLBACK_0( TPerspectiveLayer::onGridEndDraw, this); renderer->addCommand( &_gridEndCommand );

Code 5: //-- 恢复投影,便于接下来的渲染 具体参考各个继承自Node的visit函数 – }

关注点 3:TPerspectiveLayer::onGridBeginDraw

上面提到的预渲染步骤,我们也详细说下

void TPerspectiveLayer::onGridBeginDraw { Code 1: set2DProjection(); 说明:设置一个正焦投影,在创建投影的时候,我们需要设置投影的大小,以用来照射的范围更大,能容纳更多的信号块

Director *director = Director::getInstance(); Size size = director->getWinSizeInPixels(); director->loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); Mat4 orthoMatrix; //-- 注意此处正焦投影的宽高 是我们需要修改的 目前指定2048 -- Mat4::createOrthographicOffCenter(0, size.width, 0, _rtHeight, -1, 1, &orthoMatrix); director->multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, orthoMatrix); director->loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); GL::setProjectionMatrixDirty(); **重点**:其中参数 _rtHeight 指定给了==2048==,并不是屏幕的高度,为什么要指定高度,为什么是 2048 ,上面简述流程1和代码分析创建RT的时候已经说明

Code 2: 设置视口

Size size = director->getWinSizeInPixels(); glViewport(0, 0, (GLsizei)(size.width), (GLsizei)(2048) ); **重点**:需要将视口也同样指定到和投影范围大小,视口高度也是==2048== ,这样才能容纳需要渲染的内容

Code 3: 切换渲染缓冲区 _grabber->beforeRender(_texture); 说明:开启指定的绘制缓冲区,此时将gl会将绘制的缓冲区替换成RT beforeRender主要实现以下:

glBindFramebuffer(GL_FRAMEBUFFER, _FBO); glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

}

关注点 4:TPerspectiveLayer::onGridEndDraw

当所有节点都渲染到RT后,需要进行***全局很重要步骤***,就是绘制透视效果的模型,并贴上纹理

void TPerspectiveLayer::onGridEndDraw { Code 1: _grabber->afterRender(_texture);

**重点**:停止渲染到纹理, Node 所有子节点已经绘制到 RT 上 接下来切换渲染缓冲区,将刚才的RT换回默认的屏幕缓冲区

afterRender函数主要执行以下恢复操作:

glBindFramebuffer(GL_FRAMEBUFFER, _oldFBO); glClearColor(_oldClearColor[0], _oldClearColor[1], _oldClearColor[2], _oldClearColor[3]);

Code 2: set3DProjection() 说明:接下来我们需要以3d视角来绘制事先创建好的顶点,才能实现透视效果

Director *director = Director::getInstance(); Size size = director->getWinSizeInPixels(); float zeye = director->getZEye(); Mat4 matrixPerspective, matrixLookup; //-- 重新设置透视投影 主要是为了加大远截面可视距离 目前指定 5000 -- Mat4::createPerspective(60, (GLfloat)size.width/size.height, 10, 5000, &matrixPerspective); Vec3 eye(size.width/2, size.height/2, zeye), center(size.width/2, size.height/2, 0.0f), up(0.0f, 1.0f, 0.0f); Mat4::createLookAt(eye, center, up, &matrixLookup); Mat4 proj3d = matrixPerspective * matrixLookup; director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, proj3d); director->loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); GL::setProjectionMatrixDirty(); **重点**:一定要注意创建透视投影的远截面参数==5000==,要设置大一点,这样才能满足我们模型顶部顶点的最大Z值

Code 3: 绘制顶点数据 目前所有像素都绘制到默认的屏幕缓冲区上

//-- 恢复视口 将需要绘制顶点都放在屏幕内 director->setViewport(); const auto& vp = Camera::getDefaultViewport(); glViewport(vp._left, vp._bottom, vp._width, vp._height); //-- 设置纹理,就是上面的RT -- GL::bindTexture2D(_texture->getName()); int n = _gridSize.width * _gridSize.height; //-- 设置顶点格式 -- GL::enableVertexAttribs( GL::VERTEX_ATTRIB_FLAG_POSITION | GL::VERTEX_ATTRIB_FLAG_TEX_COORD ); _shaderProgram->use(); _shaderProgram->setUniformsForBuiltins(); //-- 设置顶点数据 -- glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, 0, _vertices); glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, 0, _texCoordinates); //-- 绘制 -- glDrawElements(GL_TRIANGLES, (GLsizei) n*6, GL_UNSIGNED_SHORT, _indices); 3 辅代码分析 NoCullSprite

建立一个继承自Sprite的NoCullSprite类,主要为重写draw函数,使其在出了视口(viewport)和窗口(winsize)外还能被渲染,不被剔除,使用方法和基本的Sprite一摸一样。

**重点**:Cocos的Sprite draw函数是下面这样式儿的 在渲染的时候会检测是否在投影内 是否在屏幕内: void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) { if (_texture == nullptr) { return; } #if CC_USE_CULLING // Don't calculate the culling if the transform was not updated auto visitingCamera = Camera::getVisitingCamera(); auto defaultCamera = Camera::getDefaultCamera(); if (visitingCamera == nullptr) { _insideBounds = true; } else if (visitingCamera == defaultCamera) { _insideBounds = ((flags & FLAGS_TRANSFORM_DIRTY) || visitingCamera->isViewProjectionUpdated()) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds; } else { // XXX: this always return true since _insideBounds = renderer->checkVisibility(transform, _contentSize); } if(_insideBounds) #endif { _trianglesCommand.init(_globalZOrder, _texture, getGLProgramState(), _blendFunc, _polyInfo.triangles, transform, flags); renderer->addCommand(&_trianglesCommand); #if CC_SPRITE_DEBUG_DRAW _debugDrawNode->clear(); auto count = _polyInfo.triangles.indexCount/3; auto indices = _polyInfo.triangles.indices; auto verts = _polyInfo.triangles.verts; for(ssize_t i = 0; i if (_texture == nullptr) { return; } //-- 相对于基类 取消掉各种剔除判断 -- if( true ) { _trianglesCommand.init(_globalZOrder, _texture, getGLProgramState(), _blendFunc, _polyInfo.triangles, transform, flags); renderer->addCommand(&_trianglesCommand); } } 4 使用说明

1 在这个透视效果的轮盘上,所有需要创建的信号块或者特效,都采用新的NoCullSprite类来创建。 2 创建的信号块按照原来的游戏逻辑add到TPerspectiveLayer创建的Node上,会在重写的visit方法中,将这个Node的所有子节点都渲染到指定的RT上,渲染完成后,再将这个RT,以纹理模式贴在自建模型上。(此自建模型所有数据都存在这个Node类里,在Node节点释放的时候,要释放掉相关的顶点缓冲,索引缓冲和UV缓冲数据 )。 3 至此,这个TPerspectiveLayer自建节点,就可以像其他CCNode一样,任意穿插在各个UI层级中。

**希望这个思路能说明白** 后续 叨叨一下

本以为这个功能算是结束了,又来个需求,这个透视的旋转轮盘,要根据2D UI的大小和位置进行缩放和平移。

结果最担心的问题还是出现了: 1 由于根结点自身的缩放和平移,而RT的大小并没有改变,导致绘制在RT上的像素偏移和大量的丢失。 2 由于在绘制模型的时候,3D投影和视口都是指定的,并且是直接绘制到屏幕缓冲区上的,并不会动态的和根结点进行缩放和平移。

寻思后,还是搬出了第二个RT,以这个作为载体,来承载模型的绘制,然后对这个RT进行缩放和平移,进行UI匹配。

现在把所有的流程再简单梳理一遍:

1 初始化工作: 创建RT1: 大小为屏幕宽度 X 2048 创建透视模型顶点数据:2X10 个顶点,自定义其3D位置和UV 创建RT2: 尺寸为屏幕大小 创建四边形顶点数据:4个顶点,位置为屏幕四个角 2 绘制工作: Step 1: 设置缓冲区为RT1 设置2D Projection,将投影大小设置为 屏幕宽度 X 2048 设置Viewport,将视口大小设置为 屏幕宽度 X 2048 调用根节点的Visit,绘制所有的子节点到RT1 注:此时第一步完成,所有需要的信号块都已在RT1上。 Step 2: 设置缓冲区为RT2 设置Viewport,将视口大小设置为 屏幕宽度大小 设置3D Perjection 指定即将绘制的透视模型的纹理为RT1 绘制透视模型顶点 注:此时第二部完成,已经将这个具有透视效果的模型绘制到了一张屏幕大小的RT2上 Step 3: 恢复屏幕默认缓冲区,即将把这个RT2绘制到屏幕上 恢复默认视口 设置即将绘制的模型纹理为RT2 绘制方片模型顶点 注:此时,通过改变这个RT2顶点的位置,可以将这个透视效果的轮盘,不会变形和丢失渲染像素,画到屏幕上,过程有些凌乱,还算是能完美表现

在这里插入图片描述

注:完全是个人瞎捣鼓 还没想出来更简单的方案



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有