基于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的内置类型samplerCubesamplerCubesampler2D一样,都是贴图,不同的是需要使用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。如果我想制作一面镜子,可以反射周边的物体,该怎么办呢?这就是我下一篇文章将介绍的内容,如何制作实时的镜面反射效果。

Updated: