Metal三维变换


一、当要绘制的三角形有正反面之分的时候则要注意三角形的缠绕顺序是以顺时针作为缠绕还是逆时针座位缠绕。这里我一逆时针作为正面的缠绕

- (void) initVertexBuffer {
    static YCVertexBuffer vertexBuffer[] = {
        {{-0.5, 0.5, 0, 1}, {0.0f, 0.0f, 0.5f}},
        {{0.5, 0.5, 0, 1}, {0.0f, 0.5f, 0.0f}},
        {{-0.5, -0.5, 0, 1}, {0.5f, 0.0f, 1.0f}},
        {{0.5, -0.5, 0, 1}, {0.0f, 0.0f, 0.5f}},
        {{0, 0, 1, 1}, {1.0f, 1.0f, 1.0f}}
    };
    self.vertexBuffer = [self.mtkView.device newBufferWithBytes:vertexBuffer length:sizeof(vertexBuffer) options:MTLResourceStorageModeShared];
    
    static int iIndexBuffer[] = {
        4, 1, 0,
        4, 3, 1,
        4, 2, 3,
        4, 0, 2,
        0, 3, 2,
        0, 1, 3
    };
    self.indexBuffer = [self.mtkView.device newBufferWithBytes:iIndexBuffer length:sizeof(iIndexBuffer) options:MTLResourceStorageModeShared];
    self.numIndex = sizeof(iIndexBuffer) / sizeof(int);
}

如果在纸上画出对应index的各个点的顺序就可以看出来

vertexbuffer的缠绕顺序固定了,则在渲染的时候需要设定以逆时针作为缠绕顺序,并将背面切割掉只留正面

        [renderEncoder setFrontFacingWinding:MTLWindingCounterClockwise];
        [renderEncoder setCullMode:MTLCullModeBack];

二、由于使用了index的方式进行绘制,则在每一帧画三角形的时候需要指出使用的是index的方式绘制

[renderEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle indexCount:self.numIndex indexType:MTLIndexTypeUInt32 indexBuffer:self.indexBuffer indexBufferOffset:0];

但是由于metal shader需要的是vertexbuffer的数据,则在设置metal 的vertexbuffer时候设置的依旧是vertexbuffer

[renderEncoder setVertexBuffer:self.vertexBuffer offset:0 atIndex:YCVertexInputIndexVertices];

只是由index指出这次要由哪几个vertexbuffer数据来绘制。

三、由于要绘制三维的图像,则需要确认各个顶点的位置,各个顶点的位置则需要通过左边的变换得到。如果给的是本地坐标则需要:局部坐标-》世界坐标-〉视图空间-》裁剪空间-〉屏幕显示

这里默认的已经给了世界坐标,则需要再进行视图坐标和裁剪坐标的转换。裁剪空间-〉屏幕显示 的转换则无需关心,该操作会在vertexshader内部自动转换

公式是:positon = projection * view * model * local;

具体为啥是该公式可以看:https://learnopengl-cn.github.io/01%20Getting%20started/08%20Coordinate%20Systems/

- (void)renderMatrix:(id)renderEncoder {
    CGSize size = self.view.bounds.size;
    float aspect = size.width / size.height;
    GLKMatrix4 project = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(90), aspect, 0.1, 100); // 指定了广角为左右看90度的视角范围,长宽比为aspect,远近为距离自己视角位置的0.1到100的距离,超出位置的则会被切割掉不显示
    GLKMatrix4 modelView = GLKMatrix4Translate(GLKMatrix4Identity, 0, 0, -2.0f); // 视图模型,由于Z轴指向的是我们的位置,当物体距离我们越远则方向要往负方向,则要乘以一个负数
    
    // 在这里对视图模型进行一定的旋转
    static float x = 0, y = 0, z = M_PI;
    x += 0.02;
    y += 0.01;
    z += 0.01;
    modelView = GLKMatrix4RotateX(modelView, x);
    modelView = GLKMatrix4RotateY(modelView, y);
    modelView = GLKMatrix4RotateZ(modelView, z);
    YCTranslateMatrix ycMatrix = (YCTranslateMatrix) {
        [self getMetalMatrix4FromGLKMatrix4:project],
        [self getMetalMatrix4FromGLKMatrix4:modelView]
    };
    
    [renderEncoder setVertexBytes:&ycMatrix length:sizeof(ycMatrix) atIndex:YCVertexInputIndexMatrix];
}

// 将gl的matrix转换成metal的metal格式
- (matrix_float4x4)getMetalMatrix4FromGLKMatrix4:(GLKMatrix4)matrix {
    return (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),
    };
}

由于我们给的vertexbuffer是世界坐标,则提供对应的视图模型和裁剪模型即可

四、每次绘制的函数调用

- (void)drawInMTKView:(nonnull MTKView *)view {
    id commandBuffer = [self.commandQueue commandBuffer];
    MTLRenderPassDescriptor* renderDesc = view.currentRenderPassDescriptor;
    if (renderDesc) {
        renderDesc.colorAttachments[0].clearColor = MTLClearColorMake(0, 0.5, 0.5, 1);
        renderDesc.colorAttachments[0].loadAction = MTLLoadActionClear;
        id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderDesc];
        [renderEncoder setViewport:(MTLViewport){0, 0, self.viewportSize.x, self.viewportSize.y, -1, 1}];
        [renderEncoder setRenderPipelineState:self.pipeline];
        [renderEncoder setFrontFacingWinding:MTLWindingCounterClockwise];
        [renderEncoder setCullMode:MTLCullModeBack];
        [renderEncoder setVertexBuffer:self.vertexBuffer offset:0 atIndex:YCVertexInputIndexVertices];
        
        // 设置渲染matrix
        [self renderMatrix:renderEncoder];
        
        [renderEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle indexCount:self.numIndex indexType:MTLIndexTypeUInt32 indexBuffer:self.indexBuffer indexBufferOffset:0];
        [renderEncoder endEncoding];
        [commandBuffer presentDrawable:view.currentDrawable];
    }
    [commandBuffer commit];
}

五、metal 的shader

typedef struct {
    float4 clipSpacePosition[[position]];
    float3 pixelColor;
}RasterizerData;

vertex RasterizerData
vertexShader(uint vertexId[[vertex_id]],
             constant YCVertexBuffer* vertexBuffer[[buffer(YCVertexInputIndexVertices)]],
             constant YCTranslateMatrix* matrixMatrix[[buffer(YCVertexInputIndexMatrix)]]) {
    RasterizerData out;
    out.clipSpacePosition = matrixMatrix->project * matrixMatrix->modelView * vertexBuffer[vertexId].position;
    out.pixelColor = vertexBuffer[vertexId].color;
    
    return out;
}

fragment float4
fragmentShader(RasterizerData input[[stage_in]]) {
    half4 outColor = half4(input.pixelColor.x, input.pixelColor.y, input.pixelColor.z, 1.0f);
    
    return float4(outColor);
}

如上可以看出求出clipSpacePosition 使用的是矩阵的乘法 , 通过视图模型和裁剪模型的转换,最终转换成clipSpacePosition 的顶点位置

pixelColor 则是插值的方式显示