Cocos2dx实现光影效果的两种方式

时间:2021-02-23 03:48:30

Shader 和 RenderTexture

先贴上两张效果图

Cocos2dx实现光影效果的两种方式

(Shader)

Cocos2dx实现光影效果的两种方式

(RenderTexture)

说一下实现的原因,因为项目中需要夜景,光影的效果。最初想到使用Shader来实现。实现之后。效果还不错。因为最初的测试是在Mac上跑的客户端,效果不错。但是放到手机端上之后。发现效率太低。超过3个光源之后,效率下降的太严重。不过既然做了,就拿出来分享一下。另一个则是用RenderTexture来实现的。效率则比Shader效率高很多。

Shader篇

思路讲解

  • Shader中的所有的数据都是真实像素点来进项渲染的,而程序中则更多的是逻辑像素点来进行的,所以代码中又关于Zoom相关的字段都是处理相关的位置转换的代码
  • 前文提到过关于光源超过三个的时候出现的问题。主要原因是,所有的渲染点都需要跟不同的光点位置进行运算。然后计算出他最后的使用哪个光电最亮(不透明度最低),然后取用这个值,所以一个光点运行一次,两个光点运行两次,以此类推。所以我做出的优化是减少判断的个数
  • 能够减少的位置有 全透明区域 全黑区域。其实全透明区域最容易判断,下文中的等于0就直接Break则是对于全透明区域的优化。全黑区域则相对复杂一些。
  • 全黑区域的优化。在C++代码中,我吧屏幕分成了24*16的网格,然后用C++代码与光点进行运算,初始化网格。然后把网格传入Shader中。Shader中则判定自己是不是处于全黑网格,如果处于全黑网格,则直接渲染为黑色不做后续处理
  • 其他内容应该非常容易理解了。不做赘述

Shader 代码

 #ifdef GL_ES
precision highp float;
#endif int screen_width = ;
int screen_height = ;
uniform float shader_zoom;
uniform vec4 night_color;
uniform vec2 light_pos[];
uniform float light_lenght[];
uniform float light_glare_lenght[];
uniform float light_all_length[];
uniform float light_all_length_sq[];
uniform float light_glare_lenght_sq[];
uniform int light_count;
uniform float screen_zoom;
uniform float screen_mapping[ * ];
//uniform sampler2D screen_mapping; float po_2_light_lenght[]; void main(void)
{
float f = 1.0; int i = ;
vec2 p;
float color;
float color_f;
float length_sq;
float length_f; int type = ; int x = int(gl_FragCoord.x / screen_zoom / shader_zoom);
int y = int(gl_FragCoord.y / screen_zoom / shader_zoom); while (i < light_count)
{
if(screen_mapping[y * screen_width + x] == 1.0)
{
break;
} if(f == 0.0)
{
break;
} p = gl_FragCoord.xy - light_pos[i].xy; length_sq = dot(p, p); if(length_sq >= light_all_length_sq[i])
{
i++;
continue;
} if(length_sq <= light_glare_lenght_sq[i])
{
f = 0.0;
i++;
continue;
} color = length(p) - light_glare_lenght[i];
color_f = clamp(color / light_lenght[i], 0.0, 1.0); if(color_f < f)
{
f = color_f;
} i++;
} gl_FragColor = vec4(f * night_color);
}

调用Shader的代码(C++)

 void NightLayer::onDraw(const cocos2d::Mat4& transform, uint32_t flags)
{
int x, y, i;
Vec2 postion; float screen_zoom = DataManager::getInstance()->getScreenZoom();
for (i = ; i < kScreenWidth * kScreenHeight; ++i)
{
_screen_mapping[i] = .f;
} for (y = ; y < kScreenHeight; ++y)
{
for (x = ; x < kScreenWidth ; ++x)
{
for (i = ; i < _light_count; ++i)
{
postion.x = (x + 0.5f) * kShaderZoom * screen_zoom;
postion.y = (y + 0.5f) * kShaderZoom * screen_zoom; if((postion - _light_pos[i]).lengthSquared() < pow((_light_all_length[i] + 14.2 * kShaderBaseZoom), ))
{
_screen_mapping[y * kScreenWidth + x] = .f;
}
}
}
} float w = _contentSize.width, h = _contentSize.height;
GLfloat vertices[] = {,, w,, w,h, ,, ,h, w,h}; auto glProgramState = getGLProgramState();
glProgramState->setVertexAttribPointer("a_position", , GL_FLOAT, GL_FALSE, , vertices); glProgramState->setUniformFloat("screen_zoom", screen_zoom);
glProgramState->setUniformFloat("shader_zoom", kShaderZoom);
glProgramState->setUniformInt("light_count", _light_count);
glProgramState->setUniformVec4("night_color", Vec4(0.055, 0.008, 0.008, ));
glProgramState->setUniformVec2v("light_pos", _light_count, _light_pos);
glProgramState->setUniformFloatv("light_lenght", _light_count, _light_length);
glProgramState->setUniformFloatv("light_glare_lenght", _light_count, _light_glare_lenght);
glProgramState->setUniformFloatv("light_all_length_sq", _light_count, _light_all_length_sq);
glProgramState->setUniformFloatv("light_glare_lenght_sq", _light_count, _light_glare_lenght_sq);
glProgramState->setUniformFloatv("screen_mapping", kScreenWidth * kScreenHeight, _screen_mapping); glProgramState->apply(transform); glDrawArrays(GL_TRIANGLES, , ); CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(,);
}

相关参数注解

  • int screen_width = 24; 映射屏幕的宽度
  • int screen_height = 16; 映射屏幕的高度
  • uniform float shader_zoom; Shader的缩放值
  • uniform vec4 night_color; 夜晚的颜色
  • uniform vec2 light_pos[10]; 光点的位置
  • uniform float light_lenght[10]; 光点强光圈长度
  • uniform float light_glare_lenght[10]; 光点弱光圈长度
  • uniform float light_all_length[10]; 光点的总长度(强光跟弱光距离相加)
  • uniform float light_all_length_sq[10]; 光电总长度的平方
  • uniform float light_glare_lenght_sq[10]; 光电弱光圈的平方
  • uniform int light_count; 光点的个数
  • uniform float screen_zoom; 屏幕缩放比例
  • uniform float screen_mapping[24 * 16]; 屏幕映射字典

ScreenZoom的计算方式

额,这个并不是所有的游戏都是这么计算的。需要跟游戏的适配方式配合

 Size w_size = Director::getInstance()->getOpenGLView()->getFrameSize();
Size designResolutionSize = Director::getInstance()->getWinSize();
DataManager::getInstance()->setScreenZoom(w_size.height / designResolutionSize.height * Director::getInstance()->getOpenGLView()->getRetinaFactor());

我们的游戏适配方式

关键是第三个参数,以高度适配,所以上边的缩放计算也是以高度计算的

 glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::FIXED_HEIGHT);

RenderTexture 篇

思路讲解

先比对Shader RenderTexture 则更加简单暴力。简单来说就是在RenderTexture上贴图,只要把对应的图片放到对应的位置,然后展示出来就好。

相关代码

 void NightLayer::update(float dt)
{
_render->clear(, , , .f);
_render->begin();
// _render->beginWithClear(0, 0, 0, 1.f, 0, 0); for (int i = ; i < kPlayerMaxCount; ++i)
{
bool is_show = _light_player_count > i;
if(is_show)
{
_spr_lights_player[i]->setPosition(_light_player_pos[i]);
_spr_lights_player[i]->setScale(_light_player_length[i] / .f * 2.8f);
_spr_lights_player[i]->visit();
}
} for (int i = ; i < kCandleMaxCount; ++i)
{
bool is_show = _light_candle_count > i;
if(is_show)
{
_spr_lights_candle[i]->setPosition(_light_candle_pos[i]);
_spr_lights_candle[i]->setScale(_light_candle_length[i] / .f);
_spr_lights_candle[i]->visit();
}
} _render->end();
}

全文件大放送

NightLayer.hpp

 //
// NightLayer.hpp
// //
// //
// #ifndef NightLayer_hpp
#define NightLayer_hpp #include "cocos2d.h" //#define USING_SHADER
#define USING_RENDER_TEXTURE #include <stdio.h> class NightLayer : public cocos2d::Node
{
public:
CREATE_FUNC(NightLayer); virtual bool init() override; #ifdef USING_RENDER_TEXTURE
virtual void update(float dt) override;
#endif #ifdef USING_SHADER
virtual void draw(cocos2d::Renderer* renderer, const cocos2d::Mat4& transform, uint32_t flags) override;
#endif public:
#ifdef USING_SHADER
const static int kLightMaxCount = ;
cocos2d::Vec2 _light_pos[kLightMaxCount];
float _light_length[kLightMaxCount];
float _light_glare_lenght[kLightMaxCount];
float _light_all_length[kLightMaxCount];
float _light_all_length_sq[kLightMaxCount];
float _light_glare_lenght_sq[kLightMaxCount]; int _light_count;
#endif #ifdef USING_RENDER_TEXTURE
const static int kCandleMaxCount = ;
const static int kPlayerMaxCount = ; cocos2d::Vec2 _light_candle_pos[kCandleMaxCount];
cocos2d::Vec2 _light_player_pos[kPlayerMaxCount]; float _light_candle_length[kCandleMaxCount];
float _light_player_length[kPlayerMaxCount]; int _light_candle_count;
int _light_player_count; #endif protected:
NightLayer()
#ifdef USING_SHADER
:_light_count()
#endif
#ifdef USING_RENDER_TEXTURE
:_light_candle_count()
,_light_player_count()
,_render(nullptr)
#endif
{ }
virtual ~NightLayer(); #ifdef USING_RENDER_TEXTURE
cocos2d::RenderTexture * _render;
cocos2d::Sprite * _spr_lights_candle[kCandleMaxCount];
cocos2d::Sprite * _spr_lights_player[kPlayerMaxCount];
#endif #ifdef USING_SHADER cocos2d::Vec2 _resolution;
void onDraw(const cocos2d::Mat4& transform, uint32_t flags); bool initWithVertex(const std::string &vert, const std::string &frag);
void loadShaderVertex(const std::string &vert, const std::string &frag); cocos2d::CustomCommand _customCommand;
std::string _vertFileName;
std::string _fragFileName; const static int kScreenWidth = ;
const static int kScreenHeight = ;
float _screen_mapping[kScreenWidth * kScreenHeight];
#endif
}; #endif /* NightLayer_hpp */

NightLayer.cpp

 //
// NightLayer.cpp
// //
// //
// #include "NightLayer.hpp"
#include "DataManager.h" USING_NS_CC; namespace
{
#ifdef USING_SHADER
const float kShaderBaseZoom = .f;
const float kShaderZoom = kShaderBaseZoom * .f;
#endif
} #ifdef USING_SHADER bool NightLayer::initWithVertex(const std::string &vert, const std::string &frag)
{
_vertFileName = vert;
_fragFileName = frag;
#if CC_ENABLE_CACHE_TEXTURE_DATA
auto listener = EventListenerCustom::create(EVENT_RENDERER_RECREATED, [this](EventCustom* event){
this->setGLProgramState(nullptr);
loadShaderVertex(_vertFileName, _fragFileName);
}); _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
#endif loadShaderVertex(vert, frag); scheduleUpdate(); Size size = Director::getInstance()->getWinSize();
setContentSize(size);
setAnchorPoint(Vec2(0.5f, 0.5f)); return true;
} void NightLayer::loadShaderVertex(const std::string &vert, const std::string &frag)
{
auto fileUtiles = FileUtils::getInstance(); // frag
auto fragmentFilePath = fileUtiles->fullPathForFilename(frag);
auto fragSource = fileUtiles->getStringFromFile(fragmentFilePath); // vert
std::string vertSource;
if (vert.empty()) {
vertSource = ccPositionTextureColor_vert;
} else {
std::string vertexFilePath = fileUtiles->fullPathForFilename(vert);
vertSource = fileUtiles->getStringFromFile(vertexFilePath);
} auto glprogram = GLProgram::createWithByteArrays(vertSource.c_str(), fragSource.c_str());
auto glprogramstate = GLProgramState::getOrCreateWithGLProgram(glprogram);
setGLProgramState(glprogramstate);
} #endif NightLayer::~NightLayer()
{
#ifdef USING_RENDER_TEXTURE for (int i = ; i < kCandleMaxCount; ++i)
{
CC_SAFE_RELEASE(_spr_lights_candle[i]);
} for (int i = ; i < kPlayerMaxCount; ++i)
{
CC_SAFE_RELEASE(_spr_lights_player[i]);
} #endif
} bool NightLayer::init()
{
bool success = false; do {
if(!Node::init())
{
break;
} #ifdef USING_SHADER
initWithVertex("", "shaders/night.fsh");
_resolution = Director::getInstance()->getOpenGLView()->getFrameSize();
#endif #ifdef USING_RENDER_TEXTURE
Size size = Director::getInstance()->getWinSize(); for (int i = ; i < kCandleMaxCount; ++i)
{
_spr_lights_candle[i] = Sprite::create("imgs/light_candle.png");
_spr_lights_candle[i]->retain();
_spr_lights_candle[i]->setBlendFunc({GL_DST_COLOR, GL_ZERO});
} for (int i = ; i < kPlayerMaxCount; ++i)
{
_spr_lights_player[i] = Sprite::create("imgs/light_player.png");
_spr_lights_player[i]->retain();
_spr_lights_player[i]->setBlendFunc({GL_DST_COLOR, GL_ZERO});
} _render = RenderTexture::create(size.width, size.height, Texture2D::PixelFormat::RGBA4444, GL_DEPTH24_STENCIL8);;
this->addChild(_render);
#endif success = true;
} while (); return success;
} #ifdef USING_RENDER_TEXTURE
void NightLayer::update(float dt)
{
_render->clear(, , , .f);
_render->begin();
// _render->beginWithClear(0, 0, 0, 1.f, 0, 0); for (int i = ; i < kPlayerMaxCount; ++i)
{
bool is_show = _light_player_count > i;
if(is_show)
{
_spr_lights_player[i]->setPosition(_light_player_pos[i]);
_spr_lights_player[i]->setScale(_light_player_length[i] / .f * 2.8f);
_spr_lights_player[i]->visit();
}
} for (int i = ; i < kCandleMaxCount; ++i)
{
bool is_show = _light_candle_count > i;
if(is_show)
{
_spr_lights_candle[i]->setPosition(_light_candle_pos[i]);
_spr_lights_candle[i]->setScale(_light_candle_length[i] / .f);
_spr_lights_candle[i]->visit();
}
} _render->end();
}
#endif #ifdef USING_SHADER
void NightLayer::draw(cocos2d::Renderer* renderer, const cocos2d::Mat4& transform, uint32_t flags)
{
_customCommand.init(_globalZOrder, transform, flags);
_customCommand.func = CC_CALLBACK_0(NightLayer::onDraw, this, transform, flags);
renderer->addCommand(&_customCommand);
}
#endif #ifdef USING_SHADER
void NightLayer::onDraw(const cocos2d::Mat4& transform, uint32_t flags)
{
int x, y, i;
Vec2 postion; float screen_zoom = DataManager::getInstance()->getScreenZoom();
for (i = ; i < kScreenWidth * kScreenHeight; ++i)
{
_screen_mapping[i] = .f;
} for (y = ; y < kScreenHeight; ++y)
{
for (x = ; x < kScreenWidth ; ++x)
{
for (i = ; i < _light_count; ++i)
{
postion.x = (x + 0.5f) * kShaderZoom * screen_zoom;
postion.y = (y + 0.5f) * kShaderZoom * screen_zoom; if((postion - _light_pos[i]).lengthSquared() < pow((_light_all_length[i] + 14.2 * kShaderBaseZoom), ))
{
_screen_mapping[y * kScreenWidth + x] = .f;
}
}
}
} float w = _contentSize.width, h = _contentSize.height;
GLfloat vertices[] = {,, w,, w,h, ,, ,h, w,h}; auto glProgramState = getGLProgramState();
glProgramState->setVertexAttribPointer("a_position", , GL_FLOAT, GL_FALSE, , vertices); glProgramState->setUniformFloat("screen_zoom", screen_zoom);
glProgramState->setUniformFloat("shader_zoom", kShaderZoom);
glProgramState->setUniformInt("light_count", _light_count);
glProgramState->setUniformVec4("night_color", Vec4(0.055, 0.008, 0.008, ));
glProgramState->setUniformVec2v("light_pos", _light_count, _light_pos);
glProgramState->setUniformFloatv("light_lenght", _light_count, _light_length);
glProgramState->setUniformFloatv("light_glare_lenght", _light_count, _light_glare_lenght);
glProgramState->setUniformFloatv("light_all_length_sq", _light_count, _light_all_length_sq);
glProgramState->setUniformFloatv("light_glare_lenght_sq", _light_count, _light_glare_lenght_sq);
glProgramState->setUniformFloatv("screen_mapping", kScreenWidth * kScreenHeight, _screen_mapping); glProgramState->apply(transform); glDrawArrays(GL_TRIANGLES, , ); CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(,);
} #endif

night.fsh

 #ifdef GL_ES
precision highp float;
#endif int screen_width = ;
int screen_height = ;
uniform float shader_zoom;
uniform vec2 resolution;
uniform vec4 night_color;
uniform vec2 light_pos[];
uniform float light_lenght[];
uniform float light_glare_lenght[];
uniform float light_all_length[];
uniform float light_all_length_sq[];
uniform float light_glare_lenght_sq[];
uniform int light_count;
uniform float screen_zoom;
uniform float screen_mapping[ * ];
//uniform sampler2D screen_mapping; float po_2_light_lenght[]; void main(void)
{
float f = 1.0; int i = ;
vec2 p;
float color;
float color_f;
float length_sq;
float length_f; int type = ; int x = int(gl_FragCoord.x / screen_zoom / shader_zoom);
int y = int(gl_FragCoord.y / screen_zoom / shader_zoom); // f = screen_mapping[y * screen_width + x]; while (i < light_count)
{
if(screen_mapping[y * screen_width + x] == 1.0)
{
break;
} if(f == 0.0)
{
break;
} p = gl_FragCoord.xy - light_pos[i].xy; length_sq = dot(p, p); if(length_sq >= light_all_length_sq[i])
{
i++;
continue;
} if(length_sq <= light_glare_lenght_sq[i])
{
f = 0.0;
i++;
continue;
} color = length(p) - light_glare_lenght[i];
color_f = clamp(color / light_lenght[i], 0.0, 1.0); if(color_f < f)
{
f = color_f;
} i++;
} gl_FragColor = vec4(f * night_color);
}