Metal入門教程(二)三維變換

落影發表於2018-07-07

前言

Metal入門教程(一)圖片繪製

上一篇的教程介紹瞭如何繪製一張圖片,這次的目標是把圖片顯示到3D物體上,並進行三維變換。

Metal系列教程的程式碼地址
OpenGL ES系列教程在這裡

你的star和fork是我的源動力,你的意見能讓我走得更遠

正文

核心思路

圖片繪製的基礎上,給頂點資料增加z座標,並使用頂點的索引快取;為了實現三維變換,給頂點shader增加投影矩陣和模型變換矩陣

效果展示

Metal入門教程(二)三維變換

具體細節

1、新建MTKView、設定渲染管道、設定紋理資料

Metal入門教程(一)圖片繪製

2、設定頂點資料
- (void)setupVertex {
    static const LYVertex quadVertices[] =
    {  // 頂點座標                          頂點顏色                    紋理座標
        {{-0.5f, 0.5f, 0.0f, 1.0f},      {0.0f, 0.0f, 0.5f},       {0.0f, 1.0f}},//左上
        {{0.5f, 0.5f, 0.0f, 1.0f},       {0.0f, 0.5f, 0.0f},       {1.0f, 1.0f}},//右上
        {{-0.5f, -0.5f, 0.0f, 1.0f},     {0.5f, 0.0f, 1.0f},       {0.0f, 0.0f}},//左下
        {{0.5f, -0.5f, 0.0f, 1.0f},      {0.0f, 0.0f, 0.5f},       {1.0f, 0.0f}},//右下
        {{0.0f, 0.0f, 1.0f, 1.0f},       {1.0f, 1.0f, 1.0f},       {0.5f, 0.5f}},//頂點
    };
    self.vertices = [self.mtkView.device newBufferWithBytes:quadVertices
                                                 length:sizeof(quadVertices)
                                                options:MTLResourceStorageModeShared];
    static int indices[] =
    { // 索引
        0, 3, 2,
        0, 1, 3,
        0, 2, 4,
        0, 4, 1,
        2, 3, 4,
        1, 4, 3,
    };
    self.indexs = [self.mtkView.device newBufferWithBytes:indices
                                                     length:sizeof(indices)
                                                    options:MTLResourceStorageModeShared];
    self.indexCount = sizeof(indices) / sizeof(int);
}
複製程式碼

LYVertex由頂點座標、頂點顏色、紋理座標組成;
索引快取的建立和頂點快取的建立一樣,本質都是存放資料的快取;

3、設定投影變換和模型變換矩陣
- (void)setupMatrixWithEncoder:(id<MTLRenderCommandEncoder>)renderEncoder {
    CGSize size = self.view.bounds.size;
    float aspect = fabs(size.width / size.height);
    GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(90.0), aspect, 0.1f, 10.f);
    GLKMatrix4 modelViewMatrix = GLKMatrix4Translate(GLKMatrix4Identity, 0.0f, 0.0f, -2.0f);
    static float x = 0.0, y = 0.0, z = M_PI;
    if (self.rotationX.on) {
        x += self.slider.value;
    }
    if (self.rotationY.on) {
        y += self.slider.value;
    }
    if (self.rotationZ.on) {
        z += self.slider.value;
    }
    modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, x, 1, 0, 0);
    modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, y, 0, 1, 0);
    modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, z, 0, 0, 1);
    
    LYMatrix matrix = {[self getMetalMatrixFromGLKMatrix:projectionMatrix], [self getMetalMatrixFromGLKMatrix:modelViewMatrix]};
    
    [renderEncoder setVertexBytes:&matrix
                           length:sizeof(matrix)
                          atIndex:LYVertexInputIndexMatrix];
}
複製程式碼

projectionMatrix 是投影變換矩陣,modelViewMatrix是模型變換矩陣,為了方便理解,把繞x、y、z軸旋轉用三次GLKMatrix4Rotate實現。
沒有找到Metal和MetalKit快捷建立矩陣的方法,於是用了GLKit的方法進行建立,再通過getMetalMatrixFromGLKMatrix:方法進行轉換,方法如下:

/**
 找了很多文件,都沒有發現metalKit或者simd相關的介面可以快捷建立矩陣的,於是只能從GLKit裡面借力

 @param matrix GLKit的矩陣
 @return 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;
}
複製程式碼
4、具體渲染過程
        id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];

        [renderEncoder setViewport:(MTLViewport){0.0, 0.0, self.viewportSize.x, self.viewportSize.y, -1.0, 1.0 }];
        [renderEncoder setRenderPipelineState:self.pipelineState];
        [self setupMatrixWithEncoder:renderEncoder];
        
        [renderEncoder setVertexBuffer:self.vertices
                                offset:0
                               atIndex:LYVertexInputIndexVertices];
        [renderEncoder setFrontFacingWinding:MTLWindingCounterClockwise];
        [renderEncoder setCullMode:MTLCullModeBack];
複製程式碼

頂點資料設定的index引數使用了列舉變數LYVertexInputIndexVertices,這樣可以保證和shader裡面的索引對齊;
在設定完頂點資料後,還增加CullMode(剔除模式),MTLWindingCounterClockwise表示對順時針順序的三角形進行剔除。

5、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;
}

fragment float4 // 片元
samplingShader(RasterizerData input [[stage_in]],
               texture2d<half> textureColor [[ texture(LYFragmentInputIndexTexture) ]])
{
    constexpr sampler textureSampler (mag_filter::linear,
                                      min_filter::linear);
    
//    half4 colorTex = textureColor.sample(textureSampler, input.textureCoordinate);
    half4 colorTex = half4(input.pixelColor.x, input.pixelColor.y, input.pixelColor.z, 1);
    return float4(colorTex);
}
複製程式碼

頂點shader的buffer的修飾符有LYVertexInputIndexVerticesLYVertexInputIndexMatrix,與業務層的列舉變數一致;
在計算頂點座標的時候,增加了projectionMatrixmodelViewMatrix的處理;

片元shader的texture的修飾符是LYFragmentInputIndexTexture
嘗試把從圖片讀取顏色的程式碼遮蔽,使用上面的程式碼,可以得到頂點顏色的顯示結果;

總結

Metal的三維變換與OpenGL ES一樣,重點是如何初始化矩陣,並且把矩陣傳遞給頂點shader;同時Metal的Shader有語法檢測,使用列舉變數能在編譯階段就定位到問題。

這裡可以下載demo程式碼。

Metal入門教程(二)三維變換


相關文章