前言
上一篇的教程介紹了如何繪制一張圖片甘萧,這次的目標(biāo)是把圖片顯示到3D物體上蛾绎,并進(jìn)行三維變換。
Metal系列教程的代碼地址晰韵;
OpenGL ES系列教程在這里链瓦;
你的star和fork是我的源動(dòng)力,你的意見能讓我走得更遠(yuǎn)。
正文
核心思路
在圖片繪制的基礎(chǔ)上慈俯,給頂點(diǎn)數(shù)據(jù)增加z坐標(biāo)渤刃,并使用頂點(diǎn)的索引緩存;為了實(shí)現(xiàn)三維變換贴膘,給頂點(diǎn)shader增加投影矩陣和模型變換矩陣卖子。
效果展示
具體細(xì)節(jié)
1、新建MTKView刑峡、設(shè)置渲染管道洋闽、設(shè)置紋理數(shù)據(jù)
2突梦、設(shè)置頂點(diǎn)數(shù)據(jù)
- (void)setupVertex {
static const LYVertex quadVertices[] =
{ // 頂點(diǎn)坐標(biāo) 頂點(diǎn)顏色 紋理坐標(biāo)
{{-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}},//頂點(diǎn)
};
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
由頂點(diǎn)坐標(biāo)诫舅、頂點(diǎn)顏色、紋理坐標(biāo)組成宫患;
索引緩存的創(chuàng)建和頂點(diǎn)緩存的創(chuàng)建一樣刊懈,本質(zhì)都是存放數(shù)據(jù)的緩存;
3娃闲、設(shè)置投影變換和模型變換矩陣
- (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軸旋轉(zhuǎn)用三次GLKMatrix4Rotate
實(shí)現(xiàn)属拾。
沒有找到Metal和MetalKit快捷創(chuàng)建矩陣的方法将谊,于是用了GLKit的方法進(jìn)行創(chuàng)建,再通過getMetalMatrixFromGLKMatrix:
方法進(jìn)行轉(zhuǎn)換捌年,方法如下:
/**
找了很多文檔瓢娜,都沒有發(fā)現(xiàn)metalKit或者simd相關(guān)的接口可以快捷創(chuàng)建矩陣的,于是只能從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];
頂點(diǎn)數(shù)據(jù)設(shè)置的index參數(shù)使用了枚舉變量LYVertexInputIndexVertices
眠砾,這樣可以保證和shader里面的索引對齊;
在設(shè)置完頂點(diǎn)數(shù)據(jù)后托酸,還增加CullMode
(剔除模式)褒颈,MTLWindingCounterClockwise
表示對順時(shí)針順序的三角形進(jìn)行剔除。
5励堡、Shader處理
vertex RasterizerData // 頂點(diǎn)
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);
}
頂點(diǎn)shader的buffer
的修飾符有LYVertexInputIndexVertices
和LYVertexInputIndexMatrix
谷丸,與業(yè)務(wù)層的枚舉變量一致;
在計(jì)算頂點(diǎn)坐標(biāo)的時(shí)候应结,增加了projectionMatrix
和 modelViewMatrix
的處理刨疼;
片元shader的texture
的修飾符是LYFragmentInputIndexTexture
泉唁;
嘗試把從圖片讀取顏色的代碼屏蔽,使用上面的代碼揩慕,可以得到頂點(diǎn)顏色的顯示結(jié)果亭畜;
遇到的問題
問題1:結(jié)構(gòu)體初始化錯(cuò)誤
因?yàn)閐emo的頂點(diǎn)是從OpenGL的demo中復(fù)制過來,直接用GL的數(shù)組初始化方式迎卤。
// 錯(cuò)誤的初始化
GLfloat attrArr[] =
{
-0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 1.0f,//左上
0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.0f, 1.0f, 1.0f,//右上
-0.5f, -0.5f, 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f,//左下
0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 1.0f, 0.0f,//右下
0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.5f, 0.5f,//頂點(diǎn)
};
但是Metal中使用的是結(jié)構(gòu)體拴鸵,其初始化的方式有所不同。
static const LYVertex quadVertices[] =
{ // 頂點(diǎn)坐標(biāo) 頂點(diǎn)顏色 紋理坐標(biāo)
{{-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}},//頂點(diǎn)
};
問題2:shader左右乘順序?qū)懛?/strong>蜗搔。
錯(cuò)誤的順序
vertexArray[vertexID].position * matrix->projectionMatrix * matrix->modelViewMatrix
正確的順序
matrix->projectionMatrix * matrix->modelViewMatrix * vertexArray[vertexID].position
總結(jié)
Metal的三維變換與OpenGL ES一樣劲藐,重點(diǎn)是如何初始化矩陣,并且把矩陣傳遞給頂點(diǎn)shader樟凄;同時(shí)Metal的Shader有語法檢測聘芜,使用枚舉變量能在編譯階段就定位到問題。
這里可以下載demo代碼不同。