前言
上一篇的教程介紹瞭如何繪製一張圖片,這次的目標是把圖片顯示到3D物體上,並進行三維變換。
Metal系列教程的程式碼地址;
OpenGL ES系列教程在這裡;
你的star和fork是我的源動力,你的意見能讓我走得更遠。
正文
核心思路
在圖片繪製的基礎上,給頂點資料增加z座標,並使用頂點的索引快取;為了實現三維變換,給頂點shader增加投影矩陣和模型變換矩陣。
效果展示
具體細節
1、新建MTKView、設定渲染管道、設定紋理資料
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
的修飾符有LYVertexInputIndexVertices
和LYVertexInputIndexMatrix
,與業務層的列舉變數一致;
在計算頂點座標的時候,增加了projectionMatrix
和 modelViewMatrix
的處理;
片元shader的texture
的修飾符是LYFragmentInputIndexTexture
;
嘗試把從圖片讀取顏色的程式碼遮蔽,使用上面的程式碼,可以得到頂點顏色的顯示結果;
總結
Metal的三維變換與OpenGL ES一樣,重點是如何初始化矩陣,並且把矩陣傳遞給頂點shader;同時Metal的Shader有語法檢測,使用列舉變數能在編譯階段就定位到問題。
這裡可以下載demo程式碼。