当前位置: 首页 > news >正文

学习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映射。读者可以去寻找或者自己制作其他环境的天空贴图,譬如夜晚,日落等等,放在项目中观察效果。

相关文章:

  • windows下安装rabbitmq以及php扩展amqp
  • python 反射
  • 01. Consul 入门
  • 程序员行话(黑话)汇总
  • ZJOI2006 书架
  • sdfasdf
  • 设计模式六大原则
  • 10.15 iptables filter表案例 10.16/10.17/10.18 iptable
  • 前端工程化(Gulp、Webpack)-webpack
  • Squirrel GUI+ Phoenix 连接Hbase
  • 集群介绍,keepalived介绍,用keepalived配置高可用集群
  • 011-Spring Boot 运行流程分析SpringApplication.run
  • Linux Centos 7 - 系统安装
  • 宝哥iOS网络篇-AFNetworking基础使用指南
  • JS数组方法汇总
  • 【407天】跃迁之路——程序员高效学习方法论探索系列(实验阶段164-2018.03.19)...
  • Android Volley源码解析
  • Android开源项目规范总结
  • export和import的用法总结
  • JavaScript设计模式之工厂模式
  • node学习系列之简单文件上传
  • RxJS 实现摩斯密码(Morse) 【内附脑图】
  • 基于webpack 的 vue 多页架构
  • 基于游标的分页接口实现
  • 精益 React 学习指南 (Lean React)- 1.5 React 与 DOM
  • 力扣(LeetCode)56
  • 如何解决微信端直接跳WAP端
  • 使用前端开发工具包WijmoJS - 创建自定义DropDownTree控件(包含源代码)
  • 为物联网而生:高性能时间序列数据库HiTSDB商业化首发!
  • 鱼骨图 - 如何绘制?
  • 《天龙八部3D》Unity技术方案揭秘
  • k8s使用glusterfs实现动态持久化存储
  • #100天计划# 2013年9月29日
  • (1)(1.11) SiK Radio v2(一)
  • (4.10~4.16)
  • (Redis使用系列) Springboot 使用redis实现接口幂等性拦截 十一
  • (附源码)ssm考试题库管理系统 毕业设计 069043
  • (一)eclipse Dynamic web project 工程目录以及文件路径问题
  • (轉)JSON.stringify 语法实例讲解
  • ***微信公众号支付+微信H5支付+微信扫码支付+小程序支付+APP微信支付解决方案总结...
  • .bat批处理(四):路径相关%cd%和%~dp0的区别
  • .net core 微服务_.NET Core 3.0中用 Code-First 方式创建 gRPC 服务与客户端
  • .NET Core6.0 MVC+layui+SqlSugar 简单增删改查
  • .net Signalr 使用笔记
  • .net wcf memory gates checking failed
  • .net websocket 获取http登录的用户_如何解密浏览器的登录密码?获取浏览器内用户信息?...
  • .Net 垃圾回收机制原理(二)
  • .NET/C# 在代码中测量代码执行耗时的建议(比较系统性能计数器和系统时间)
  • /usr/bin/perl:bad interpreter:No such file or directory 的解决办法
  • @EnableConfigurationProperties注解使用
  • [ C++ ] STL_stack(栈)queue(队列)使用及其重要接口模拟实现
  • [ 网络基础篇 ] MAP 迈普交换机常用命令详解
  • [2013][note]通过石墨烯调谐用于开关、传感的动态可重构Fano超——
  • [2018][note]用于超快偏振开关和动态光束分裂的all-optical有源THz超表——
  • [2019/05/17]解决springboot测试List接口时JSON传参异常