学习OpenGL ES之教你实现SkyBox

本系列所有文章目录

获取示例代码


占位图之神保佑我吧!

前言

不知道大家是否还记得基于CubeMap的反射效果里介绍的CubeMap,本文将使用CubeMap来制作3D游戏中的一个经典技术,天空盒(SkyBox)。老规矩,先来看下效果图。效果中的地形模型在前面的文章中已经介绍过了。没看过的可以点此查看

原理

SkyBox正如其名,它是基于正方体模型的渲染技术。将6个方向拍下来的天空图片分别贴在正方体的对应面上,从内部观察正方体,就可以得到一个密闭的天空环境了。下面是一张典型的SkyBox贴图。


可以把它分成上下左右前后六张图,然后创建CubeMap。标准化ModelMatrix矩阵处理后的顶点位置,就可以用来在CubeMap上进行采样了。渲染SkyBox时不需要使用光照模型,直接使用采样CubeMap得到的颜色即可。所以需要为天空盒单独写一个Fragment Shader。

天空盒几何体

首先我们为天空盒准备基本的Cube几何体,创建SkyBox类,让它继承Cube类,这样就具备了渲染一个Cube的能力。然后我们对draw方法进行一些特殊处理。因为我们需要在内部观察这个Cube,所以我们需要让这个Cube的正面被裁剪,反面显示出来。glCullFace(GL_FRONT);正是用来做这件事情。渲染完后我们还需要将裁减状态回归到裁减背面。至于glDepthMask(GL_FALSE);的作用,我会在后面提到。

- (void)draw:(GLContext *)glContext {
    glCullFace(GL_FRONT);
    glDepthMask(GL_FALSE);
    [super draw:glContext];
    glDepthMask(GL_TRUE);
    glCullFace(GL_BACK);
}

准备天空CubeMap

接下来我们准备用于渲染天空盒的CubeMap,用于生成CubeMap的6张图已经在项目中包含。


使用GLKit提供的API可以很方便的生成CubeMap。

- (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直接使用了。

Fragment Shader

我为SkyBox编写了新的Fragment Shader , frag_skybox.glsl。内容非常简单,生成CubeMap的采样向量,然后返回采样结果。

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的缩放量。同时你还要注意天空盒没有超出你的视距。你如果单独给天空盒指定一个足够视距的投影矩阵那是最好。

- (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,后续渲染的物体就必然在天空盒的前面了。下面是渲染代码。

- (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];
    }];
}

扩展

除了天空盒,还有天空穹,形象来说就是用一个半球盖在场景上,贴在半球上的贴图也和天空盒不同,大致如下图所示。和我们用手机拍的全景图很像。


总结

本文使用CubeMap实现了常用的游戏技术SkyBox,当然你也可以使用其他方式为Cube进行贴图。比如把6张图放在一起,然后为Cube设置适当的UV映射。读者可以去寻找或者自己制作其他环境的天空贴图,譬如夜晚,日落等等,放在项目中观察效果。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容