基于CubeMap的反射效果
获取示例代码,本文代码在分支chapter24中。
本文将为大家介绍如何使用CubeMap制作反射效果,反射效果可以让材质具备光滑的质感,比如汽车的烤漆,就非常适合使用反射效果。先上2张效果图。我使用的是Blender内置的猴头模型进行的渲染测试。
Cube Map
我们首先来了解一下什么是CubeMap。即使你不了解CubeMap,但你一定知道全景图。随便打开一个XX地图,进入全景模式,就可以360度无死角的观察周边的环境了。地图的全景模式大多使用的是Cube Map的方式,使用上下左右前后各6张图,分别贴在以观察者为中心的正方体上,这样就可以形成一个假的3D环境了。读者可以从这个网站下载用于CubeMap的图片,下载下来的图片都已经标记好了应该在的位置,使用起来还是很方便的。neg开头的表示在负轴上,pos表示在正轴上。比如posx就是x正轴上的面所使用的贴图。
这个网站还可以预览Cube Map的全景效果。点击WebGL Preview即可。
在Shader中使用Cube Map
Shader提供了表示CubeMap的内置类型samplerCube
,samplerCube
和sampler2D
一样,都是贴图,不同的是需要使用textureCube
进行采样,采样的时候需要传递规范化后的三维向量而不是二维的UV。采样的代码如下。
textureCube(envMap, reflectVec)
textureCube
会采样reflectVec
向量在Cube Map上指向的点的像素。可以理解为求解向量和标准正方体的相交点。例子中我们根据法向量和视线向量计算用于采样的reflectVec
反射向量。然后把采样的颜色用于环境色的计算。
// 计算环境光
vec3 ambient = vec3(light.ambientIndensity) * material.ambientColor;
vec3 reflectVec = normalize(reflect(-eyeVector, transformedNormal));
ambient += 0.5 * diffuseStrength * textureCube(envMap, reflectVec).rgb;
Shader很简单,只有这部分的变化。
准备Cube Map
接下来我们在OC代码中为Shader准备Cube Map,Cube Map需要6张图,我通过前面说的网站下载了一套Cube Map贴图,按照posx,negx,posy,negy,posz,negz的顺序命名为cube-1,cube-2,….,GLKit生成CubeMap的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:@"jpg"];
[files addObject:filePath];
}
NSError *error;
self.cubeTexture = [GLKTextureLoader cubeMapWithContentsOfFiles:files options:nil error:&error];
}
将Cube Map传递给Shader
为了将Cube Map传递给Shader,我为GLContext
写了一个新的方法。
- (void)bindCubeTexture:(GLKTextureInfo *)textureInfo to:(GLenum)textureChannel uniformName:(NSString *)uniformName {
glActiveTexture(textureChannel);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureInfo.name);
GLuint textureID = (GLuint)textureChannel - (GLuint)GL_TEXTURE0;
[self setUniform1i:uniformName value:textureID];
}
和2D贴图主要的不同就是绑定到的target不一样,这里绑定到了GL_TEXTURE_CUBE_MAP
上,其他的操作和2D贴图都是一样的。最后在渲染时调用这个方法。这里我绑定到了通道3的纹理上。
[obj.context bindCubeTexture:self.cubeTexture to:GL_TEXTURE3 uniformName:@"envMap"];
创建猴头模型
使用之前编写的WavefrontOBJ类可以很方便的加载猴头的模型。
- (void)createMonkey {
UIImage *normalImage = [UIImage imageNamed:@"metal.jpg"];
GLKTextureInfo *normalMap = [GLKTextureLoader textureWithCGImage:normalImage.CGImage options:nil error:nil];
UIImage *diffuseImage = [UIImage imageNamed:@"metal.jpg"];
GLKTextureInfo *diffuseMap = [GLKTextureLoader textureWithCGImage:diffuseImage.CGImage options:nil error:nil];
NSString *objFile = [[NSBundle mainBundle] pathForResource:@"smoothMonkey" ofType:@"obj"];
WavefrontOBJ *sphere = [WavefrontOBJ objWithGLContext:self.glContext objFile:objFile diffuseMap:diffuseMap normalMap:normalMap];
sphere.modelMatrix = GLKMatrix4Identity;
[self.objects addObject:sphere];
}
到此,基于CubeMap的反射效果就完成了,该技术的重点就是Cube Map,掌握了它,其他都很简单。大家看完可能会发现这种反射效果无法反射周边的几何体,只能反射指定的CubeMap。如果我想制作一面镜子,可以反射周边的物体,该怎么办呢?这就是我下一篇文章将介绍的内容,如何制作实时的镜面反射效果。