不知道你们是否还记得基于CubeMap的反射效果里介绍的CubeMap,本文将使用CubeMap来制做3D游戏中的一个经典技术,天空盒(SkyBox)。老规矩,先来看下效果图。效果中的地形模型在前面的文章中已经介绍过了。没看过的能够点此查看。 html
SkyBox正如其名,它是基于正方体模型的渲染技术。将6个方向拍下来的天空图片分别贴在正方体的对应面上,从内部观察正方体,就能够获得一个密闭的天空环境了。下面是一张典型的SkyBox贴图。bash
首先咱们为天空盒准备基本的Cube几何体,建立SkyBox类,让它继承Cube类,这样就具有了渲染一个Cube的能力。而后咱们对draw
方法进行一些特殊处理。由于咱们须要在内部观察这个Cube,因此咱们须要让这个Cube的正面被裁剪,反面显示出来。glCullFace(GL_FRONT);
正是用来作这件事情。渲染完后咱们还须要将裁减状态回归到裁减背面。至于glDepthMask(GL_FALSE);
的做用,我会在后面提到。ui
- (void)draw:(GLContext *)glContext {
glCullFace(GL_FRONT);
glDepthMask(GL_FALSE);
[super draw:glContext];
glDepthMask(GL_TRUE);
glCullFace(GL_BACK);
}
复制代码
接下来咱们准备用于渲染天空盒的CubeMap,用于生成CubeMap的6张图已经在项目中包含。spa
- (void)createCubeTexture {
NSMutableArray *files = [NSMutableArray new];
for (int i = 0; i < 6; ++i) {
NSString *filename = [NSString stringWithFormat:@"cube-%d", i + 1];
NSString *filePath = [[NSBundle mainBundle] pathForResource:filename ofType:@"tga"];
[files addObject:filePath];
}
NSError *error;
self.cubeTexture = [GLKTextureLoader cubeMapWithContentsOfFiles:files options:nil error:&error];
}
复制代码
cubeMapWithContentsOfFiles
接受的图片文件顺序为Right(右), Left(左), Top(上), Bottom(下), Front(前), Back(后)
,这个顺序能够在API的注释中找到。生成好cubeTexture
后,后面就能够传递给天空盒的Fragment Shader直接使用了。3d
我为SkyBox编写了新的Fragment Shader , frag_skybox.glsl。内容很是简单,生成CubeMap的采样向量,而后返回采样结果。code
precision highp float;
varying vec2 fragUV;
varying vec3 fragPosition;
uniform samplerCube envMap;
uniform mat4 modelMatrix;
void main(void) {
vec3 sampleVector = normalize(modelMatrix * vec4(fragPosition, 1.0)).xyz;
gl_FragColor = textureCube(envMap, sampleVector);
}
复制代码
正如前言所说,SkyBox的渲染不须要光照模型。在生成SkyBox模型时,将这个Fragment Shader用做SkyBox的渲染。为了使天空看起来更真实,咱们须要把天空盒缩放到足够大,离咱们足够远。这里我用了1000的缩放量。同时你还要注意天空盒没有超出你的视距。你若是单独给天空盒指定一个足够视距的投影矩阵那是最好。orm
- (void)createSkyBox {
NSString *vertexShaderPath = [[NSBundle mainBundle] pathForResource:@"vertex" ofType:@".glsl"];
NSString *fragmentShaderPath = [[NSBundle mainBundle] pathForResource:@"frag_skybox" ofType:@".glsl"];
GLContext *skyGlContext = [GLContext contextWithVertexShaderPath:vertexShaderPath fragmentShaderPath:fragmentShaderPath];
self.skyBox = [[SkyBox alloc] initWithGLContext:skyGlContext diffuseMap:nil normalMap:nil];
self.skyBox.modelMatrix = GLKMatrix4MakeScale(1000, 1000, 1000);
}
复制代码
咱们但愿天空应该老是在全部场景物体以后,因此渲染时,先渲染天空盒,在渲染其余物体。而且在渲染天空盒时禁用深度写入 ,就是前面用到的glDepthMask(GL_FALSE);
所作的事。这样渲染完天空盒后,深度缓冲区中全部的深度值依旧时1.0,后续渲染的物体就必然在天空盒的前面了。下面是渲染代码。cdn
- (void)drawObjects {
[self.skyBox.context active];
[self.skyBox.context setUniformMatrix4fv:@"projectionMatrix" value:self.projectionMatrix];
[self.skyBox.context setUniformMatrix4fv:@"cameraMatrix" value:self.cameraMatrix];
[self.skyBox.context bindCubeTexture:self.cubeTexture to:GL_TEXTURE4 uniformName:@"envMap"];
[self.skyBox draw: self.skyBox.context];
[self.objects enumerateObjectsUsingBlock:^(GLObject *obj, NSUInteger idx, BOOL *stop) {
[obj.context active];
[obj.context setUniform1f:@"elapsedTime" value:(GLfloat)self.elapsedTime];
[obj.context setUniformMatrix4fv:@"projectionMatrix" value:self.projectionMatrix];
[obj.context setUniformMatrix4fv:@"cameraMatrix" value:self.cameraMatrix];
[obj.context setUniform3fv:@"eyePosition" value:self.eyePosition];
[obj.context setUniform3fv:@"light.direction" value:self.light.direction];
[obj.context setUniform3fv:@"light.color" value:self.light.color];
[obj.context setUniform1f:@"light.indensity" value:self.light.indensity];
[obj.context setUniform1f:@"light.ambientIndensity" value:self.light.ambientIndensity];
[obj.context setUniform3fv:@"material.diffuseColor" value:self.material.diffuseColor];
[obj.context setUniform3fv:@"material.ambientColor" value:self.material.ambientColor];
[obj.context setUniform3fv:@"material.specularColor" value:self.material.specularColor];
[obj.context setUniform1f:@"material.smoothness" value:self.material.smoothness];
[obj.context setUniform1i:@"useNormalMap" value:self.useNormalMap];
[obj.context bindCubeTexture:self.cubeTexture to:GL_TEXTURE4 uniformName:@"envMap"];
[obj draw:obj.context];
}];
}
复制代码
除了天空盒,还有天空穹,形象来讲就是用一个半球盖在场景上,贴在半球上的贴图也和天空盒不一样,大体以下图所示。和咱们用手机拍的全景图很像。 htm
本文使用CubeMap实现了经常使用的游戏技术SkyBox,固然你也可使用其余方式为Cube进行贴图。好比把6张图放在一块儿,而后为Cube设置适当的UV映射。读者能够去寻找或者本身制做其余环境的天空贴图,譬如夜晚,日落等等,放在项目中观察效果。blog