6

Metal 示例之天空盒子中的反射和折射

 3 years ago
source link: http://blog.danthought.com/programming/2018/07/13/metal-by-example-cub-map/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Metal 示例之天空盒子中的反射和折射

本文主要通过自己对 Metal By Example 理解编写,这一篇文章讲解 Metal 中实现天空盒子,还有在模型上的反射和折射。

Metal By Example Cover

天空盒子(Skybox)就是一个在立方体内部装饰了纹理的场景,在立方体中心设置一个相机来看到的视角。

这里的立方体纹理不同于之前提到过 2D 纹理,立方体纹理在 Metal 是左手系坐标:

Metal By Example Coordinate

立方体纹理由 6 张图片组成,其中负 y (ny or negative y) 在最中心:

Metal By Example Unwrapped Cube Map
MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor textureCubeDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
                                                                                                size:cubeSize
                                                                                           mipmapped:NO];

id<MTLTexture> texture = [device newTextureWithDescriptor:textureDescriptor];

Slice 和 Cube Texture 对应关系:

Face number Cube texture face 0 Positive X 1 Negative X 2 Positive Y 3 Negative Y 4 Positive Z 5 Negative Z

for (size_t slice = 0; slice < 6; ++slice) {
  NSString *imageName = imageNameArray[slice];
  UIImage *image = [UIImage imageNamed:imageName];
  uint8_t *imageData = [self dataForImage:image];
  
  [texture replaceRegion:region
             mipmapLevel:0
                   slice:slice
               withBytes:imageData
             bytesPerRow:bytesPerRow
           bytesPerImage:bytesPerImage];
  free(imageData);
}

天空盒子的 Vertex Shader,纹理坐标就是立方体的物体空间:

vertex ProjectedVertex vertex_skybox(Vertex inVertex             [[stage_in]],
                                     constant Uniforms &uniforms [[buffer(1)]],
                                     uint vid                    [[vertex_id]])
{
    float4 position = inVertex.position;
    
    ProjectedVertex outVert;
    outVert.position = uniforms.modelViewProjectionMatrix * position;
    outVert.texCoords = position;
    return outVert;
}

Fragment Shader 并没有什么特别的,像以前一样将纹理和纹理坐标进行 Sample,只是将 z 坐标取反来适配 Sampler:

fragment half4 fragment_cube_lookup(ProjectedVertex vert          [[stage_in]],
                                    constant Uniforms &uniforms   [[buffer(0)]],
                                    texturecube<half> cubeTexture [[texture(0)]],
                                    sampler cubeSampler           [[sampler(0)]])
{
    float3 texCoords = float3(vert.texCoords.x, vert.texCoords.y, -vert.texCoords.z);
    return cubeTexture.sample(cubeSampler, texCoords);
}

光在两种物质分界面上改变传播方向又返回原来物质中的现象,叫做光的反射。

Metal 中实现,可以逆向的来想,有一条射线从摄像机发射到物体表面,反射后到立方体纹理上的交汇处。

Metal 中使用内置函数 reflect 来实现此功能,注意纹理坐标基于世界坐标来计算:

Metal By Example Reflection
vertex ProjectedVertex vertex_reflect(Vertex inVertex             [[stage_in]],
                                      constant Uniforms &uniforms [[buffer(1)]],
                                      uint vid                    [[vertex_id]])
{
    float4 modelPosition = inVertex.position;
    float4 modelNormal = inVertex.normal;
    
    float4 worldCameraPosition = uniforms.worldCameraPosition;
    float4 worldPosition = uniforms.modelMatrix * modelPosition;
    float4 worldNormal = normalize(uniforms.normalMatrix * modelNormal);
    float4 worldEyeDirection = normalize(worldPosition - worldCameraPosition);
    
    ProjectedVertex outVert;
    outVert.position = uniforms.modelViewProjectionMatrix * modelPosition;
    outVert.texCoords = reflect(worldEyeDirection, worldNormal);
    
    return outVert;
}

光从一种透明介质斜射入另一种透明介质时,传播方向一般会发生变化,这种现象叫光的折射,光在发生折射时入射角与折射角符合斯涅尔定律(Snell’sLaw),入射角 θI 与折射角 θT 的正弦之比叫做介质的绝对折射率,简称折射率(Index of Refraction)。

Metal 中使用内置函数 refract 来实现此功能,采用从空气进入玻璃的折射率来计算:

Metal By Example Refraction
// some common indices of refraction
constant float kEtaAir = 1.000277;
//constant float kEtaWater = 1.333;
constant float kEtaGlass = 1.5;

constant float kEtaRatio = kEtaAir / kEtaGlass;


vertex ProjectedVertex vertex_refract(Vertex inVertex             [[stage_in]],
                                      constant Uniforms &uniforms [[buffer(1)]],
                                      uint vid                    [[vertex_id]])
{
    float4 modelPosition = inVertex.position;
    float4 modelNormal = inVertex.normal;

    float4 worldCameraPosition = uniforms.worldCameraPosition;
    float4 worldPosition = uniforms.modelMatrix * modelPosition;
    float4 worldNormal = normalize(uniforms.normalMatrix * modelNormal);
    float4 worldEyeDirection = normalize(worldPosition - worldCameraPosition);

    ProjectedVertex outVert;
    outVert.position = uniforms.modelViewProjectionMatrix * modelPosition;
    outVert.texCoords = refract(worldEyeDirection, worldNormal, kEtaRatio);

    return outVert;
}

Core Motion

利用 Core Motion 来获取设备的方向信息来转换场景的位置:

self.motionManager = [[CMMotionManager alloc] init];
if (self.motionManager.deviceMotionAvailable)
{
  self.motionManager.deviceMotionUpdateInterval = 1 / 60.0;
  CMAttitudeReferenceFrame frame = CMAttitudeReferenceFrameXTrueNorthZVertical;
  [self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:frame];
}
CMDeviceMotion *motion = self.motionManager.deviceMotion;
CMRotationMatrix m = motion.attitude.rotationMatrix;

// permute rotation matrix from Core Motion to get scene orientation
vector_float4 X = { m.m12, m.m22, m.m32, 0 };
vector_float4 Y = { m.m13, m.m23, m.m33, 0 };
vector_float4 Z = { m.m11, m.m21, m.m31, 0 };
vector_float4 W = {     0,     0,     0, 1 };

matrix_float4x4 orientation = { X, Y, Z, W };
self.renderer.sceneOrientation = orientation;

代码和效果

danjiang / mbe-sample-code / CubeMapping


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK