笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。
环境映射是一种用来模拟光滑表面对周围环境的反射技术,常见的如镜子、光亮漆面的金属等等。这种技术的实现主要通过将一张带有周围环境的贴图附在所需要表现的多边形表面来实现的。
目前在实时3D游戏画面渲染中经常使用的有两种环境映射:球形环境映射和立方体环境映射。
市面上很多引擎自身提供了这种功能,但是Cocos2d-x引擎并没有为开发者提供球形映射渲染效果,它只提供了立方体环境映射Shader比如天空盒的环境映射,球形环境映射效果需开发者自己实现,球形环境映射效果的实现只需要一张贴图就可以完成,反射渲染效果也是在Shader中实现的,先说一下其实现思路:
球形环境映射是模拟在球体表面产生环境映射的技术,通过对普通贴图的UV坐标进行调整计算来产生在球体表面应产生的扭曲。
UV的计算利用球体表面的法线来计算。计算公式如下:
u=Nx/2+0.5
v=Ny/2+0.5
计算公式中的Nx和Ny是表面法线的x和y分量,除以2将区间限制在[-0.5,0.5],+0.5将区间调整至UV坐标应在的[0,1]区间。在这个公式的计算下,当球体正中表面法线正对摄像机的地方,坐标不会有任何扭曲;周围点依次随着Nx和Ny分量的增大而产生扭曲。球体背面的剔除面可以根据法线Z分量的正负来判断。
下面根据上述思路先从Shader编程中的顶点着色器开始,顶点着色器完整代码如下所示:
attribute vec4 a_position;
attribute vec3 a_normal;
varying vec2 env_mapping_index;
void main(void)
{
vec3 v_normalVector = normalize(CC_NormalMatrix * a_normal);
env_mapping_index.x = v_normalVector.x / 2.0 + 0.5;
env_mapping_index.y = v_normalVector.y / 2.0 + 0.5;
gl_Position = CC_MVPMatrix * a_position;
}
顶点着色器计算的反射位置env_mapping_index会传到片段着色器中,片段着色器实现完整代码如下所示:
#ifdef GL_ES
varying medium vec2 v_texture_coord;
precision medium float;
#else
varying vec2 v_texture_coord;
#endif
uniform sampler2D sampler_env;
varying vec2 env_mapping_index;
void main(void)
{
gl_FragColor=vec4((texture2D(sampler_env,vec2(env_mapping_index.x, 1 - env_mapping_index.y))).rgb,1.0);
}
贴图和shader脚本完成后,接下来将其在代码中的效果实现出来,先把模型加载出来,程序代码片段如下所示:
auto sprite_Zerkalo = Sprite3D::create("astronaut/Zerkalo.c3t");
sprite_Base->addChild(sprite_Zerkalo);
sprite_Zerkalo->setRotation3D(Vec3(90, 0, 0));
sprite_Zerkalo->setPosition3D(Vec3(0, 0, 0));
继续代码的编写,下面是对顶点着色器和片段着色器的加载以及反射贴图的加载如下所示:
auto glprogram_Zerkalo = GLProgram::createWithFilenames("astronaut/zerkalo.vert", "astronaut/zerkalo.frag");
auto _state_Zerkalo = GLProgramState::getOrCreateWithGLProgram(glprogram_Zerkalo);
sprite_Zerkalo->setGLProgramState(_state_Zerkalo);
//加载反射贴图
auto textrue_astronaut = Director::getInstance()->getTextureCache()->addImage("astronaut/mars.png");
Texture2D::TexParams tRepeatParams2;
tRepeatParams2.magFilter = GL_LINEAR;
tRepeatParams2.minFilter = GL_LINEAR;
tRepeatParams2.wrapS = GL_CLAMP_TO_EDGE;
tRepeatParams2.wrapT = GL_CLAMP_TO_EDGE;
textrue_astronaut->setTexParameters(tRepeatParams2);
_state_Zerkalo->setUniformTexture("sampler_env", textrue_astronaut);
宇航员头盔展示的是反射贴图效果,是不是感觉非常赞?