Metal天空盒全景

关于立方体图片动态的渲染主要有两个:一个是坐标的转换,一个是立方体图片的渲染

 

1、坐标的转换

正常的坐标转换流程是:本地坐标通过模型矩阵转换成世界坐标、世界坐标通过观察空间矩阵转换成视图空间坐标、视图空间坐标通过裁切矩阵转换成裁切空间坐标,裁切空间坐标通过视口转换转换成屏幕坐标;

具体可以看如下的流程Metal天空盒全景

 

如下是提供的

- (matrix_float4x4)getMetalMatrixFromGLKMatrix:(GLKMatrix4)matrix {
    matrix_float4x4 ret = (matrix_float4x4){
        simd_make_float4(matrix.m00, matrix.m01, matrix.m02, matrix.m03),
        simd_make_float4(matrix.m10, matrix.m11, matrix.m12, matrix.m13),
        simd_make_float4(matrix.m20, matrix.m21, matrix.m22, matrix.m23),
        simd_make_float4(matrix.m30, matrix.m31, matrix.m32, matrix.m33),
    };
    return ret;
}

- (void)setupMatrixWithEncoder:(id<MTLRenderCommandEncoder>)renderEncoder {

    static float angle = 0, angleLook = 0;
    if (self.rotationEyePosition.on) {
        angle += self.slider.value;
    }
    if (self.rotationEyeLookat.on) {
        angleLook += self.slider.value;
    }

    // 调整眼睛的位置
    self.eyePosition = GLKVector3Make(2.0f * sinf(angle),
                                      2.0f * cosf(angle),
                                      0.0f);

    // 调整观察的位置
    self.lookAtPosition = GLKVector3Make(2.0f * sinf(angleLook),
                                         2.0f * cosf(angleLook),
                                         2.0f);


    CGSize size = self.view.bounds.size;
    float aspect = fabs(size.width / size.height);
    GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(85.0f), aspect, 0.1f, 20.f); // 投影变换矩阵
    GLKMatrix4 modelViewMatrix = GLKMatrix4MakeLookAt(
                                                      self.eyePosition.x,
                                                      self.eyePosition.y,
                                                      self.eyePosition.z,
                                                      self.lookAtPosition.x,
                                                      self.lookAtPosition.y,
                                                      self.lookAtPosition.z,
                                                      self.upVector.x,
                                                      self.upVector.y,
                                                      self.upVector.z); // 模型变换矩阵

    LYMatrix matrix = {[self getMetalMatrixFromGLKMatrix:projectionMatrix], [self getMetalMatrixFromGLKMatrix:modelViewMatrix]}; // 转成Metal能接受的格式

    [renderEncoder setVertexBytes:&matrix
                           length:sizeof(matrix)
                          atIndex:LYVertexInputIndexMatrix]; // 设置buffer
}

刚开始提供的坐标是世界坐标,则只需要观察模型矩阵和裁切模型矩阵即可。关于viewport translate则不需要咱们考虑,底层会做对应的转换

 

观察空间模型矩阵是指在哪个位置以如何的角度和方向看世界坐标上面的物体则需要:观察的位置、观察的方向和上向量;通过这几个向量则可以计算转换成观察空间模型矩阵。具体可以参考https://learnopengl-cn.github.io/01%20Getting%20started/09%20Camera/

裁切空间模型是指期望显示的空间区域则需要:视野的大小、宽长的比例和远近能看到的位置。具体可以参考https://learnopengl-cn.github.io/01%20Getting%20started/08%20Coordinate%20Systems/ 的裁剪空间。

 

具体的shader转换

vertex RasterizerData
vertexShader(uint vertexID [[ vertex_id ]], // 顶点索引
             constant LYVertex *vertexArray [[ buffer(LYVertexInputIndexVertices) ]], // 顶点数据
             constant LYMatrix *matrix [[ buffer(LYVertexInputIndexMatrix) ]]) { // 变换矩阵
    RasterizerData out; // 输出数据
    out.clipSpacePosition = matrix->projectionMatrix * matrix->modelViewMatrix * vertexArray[vertexID].position; // 变换处理
    out.textureCoordinate = vertexArray[vertexID].textureCoordinate; // 纹理坐标
    out.pixelColor = vertexArray[vertexID].color; // 顶点颜色,调试用
    return out;
}

如上计算出了clipSpacePosition的位置;记住矩阵的计算是从右往左看的

 

二、立方体图片的渲染

1、普通的加载纹理的流程

- (void)setupTexture {
    UIImage *image = [UIImage imageNamed:@"image"];
    if(!image)
    {
        NSLog(@"Failed to create the image");
        return ;
    }
    
    MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
    textureDescriptor.pixelFormat = MTLPixelFormatRGBA8Unorm;
    textureDescriptor.width = image.size.width;
    textureDescriptor.height = image.size.height;
    self.texture = [self.mtkView.device newTextureWithDescriptor:textureDescriptor];
    
    MTLRegion region = {{ 0, 0, 0 }, {image.size.width, image.size.height, 1}};
    Byte *imageBytes = [self loadImage:image];
    if (imageBytes) {
        [self.texture replaceRegion:region
                    mipmapLevel:0
                      withBytes:imageBytes
                    bytesPerRow:4 * image.size.width];
        free(imageBytes);
        imageBytes = NULL;
    }
}
    static const LYVertex quadVertices[] =
    {
        // 上面
        {{-6.0f, 6.0f, 6.0f, 1.0f},      {1.0f, 0.0f, 0.0f},       {0.0f, 2.0f/6}},//左上 0
        {{-6.0f, -6.0f, 6.0f, 1.0f},     {0.0f, 0.0f, 1.0f},       {0.0f, 3.0f/6}},//左下 2
        {{6.0f, -6.0f, 6.0f, 1.0f},      {1.0f, 1.0f, 1.0f},       {1.0f, 3.0f/6}},//右下 3
。
。
。
。

如上将整张图片直接载入纹理,然后vertex再获取对应的每个三角形要显示的纹理区域即可

 

第二种方法是通过立方体纹理的方式

    MTLTextureDescriptor* textureDesc = [MTLTextureDescriptor textureCubeDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm size:image.size.width mipmapped:NO];
    self.texture = [self.mtkView.device newTextureWithDescriptor:textureDesc];
    
    Byte* imageBuffer = [self loadImage:image];
    if (imageBuffer) {
        int width = image.size.width;
        int pixelSize = width * width * 4;
        for (int i = 0; i < 6; ++i) {
            [self.texture replaceRegion:(MTLRegion){{0, 0, 0}, {width, width, 1}} mipmapLevel:0 slice:i withBytes:imageBuffer + pixelSize * i bytesPerRow:width * 4 bytesPerImage:pixelSize];
        }
        free(imageBuffer);
        imageBuffer = NULL;
    }

将图片的数据切割成六个面,通过slice的标记载入到texture中

vertex RasterizerData
vertexShader(uint vertexID [[ vertex_id ]],
             constant LYVertex *vertexArray [[ buffer(LYVertexInputIndexVertices) ]],
             constant LYMatrix *matrix [[ buffer(LYVertexInputIndexMatrix) ]]) {
    RasterizerData out;
    out.clipSpacePosition = matrix->projectionMatrix * matrix->modelViewMatrix * vertexArray[vertexID].position;
    out.textureCoordinate = vertexArray[vertexID].position.xyz; // 这里使用的是position的xyz,而不是纹理的二维坐标,因为纹理是使用立方体的方式
    out.pixelColor = vertexArray[vertexID].color;
    
    return out;
}





// 片源着色器的颜色获取
half4 colorTex = textureColor.sample(textureSampler, input.textureCoordinate); // 这里input.textureCoordinate是三维的

 

参考:

https://www.jianshu.com/p/a938db5a7ccf

https://learnopengl-cn.github.io/01%20Getting%20started/08%20Coordinate%20Systems/

上一篇:667. 优美的排列 II


下一篇:剑指offer_040 矩阵中最大的矩形