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

前言

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

上一篇的教程介紹了如何繪制一張圖片甘萧,這次的目標(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ù)

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

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的修飾符有LYVertexInputIndexVerticesLYVertexInputIndexMatrix谷丸,與業(yè)務(wù)層的枚舉變量一致;
在計(jì)算頂點(diǎn)坐標(biāo)的時(shí)候应结,增加了projectionMatrixmodelViewMatrix的處理刨疼;

片元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代碼不同。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末厉膀,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子二拐,更是在濱河造成了極大的恐慌服鹅,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件百新,死亡現(xiàn)場離奇詭異企软,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)饭望,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門仗哨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人铅辞,你說我怎么就攤上這事厌漂。” “怎么了斟珊?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵苇倡,是天一觀的道長。 經(jīng)常有香客問我囤踩,道長旨椒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任堵漱,我火速辦了婚禮综慎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘勤庐。我一直安慰自己示惊,他們只是感情好好港,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涝涤,像睡著了一般媚狰。 火紅的嫁衣襯著肌膚如雪岛杀。 梳的紋絲不亂的頭發(fā)上阔拳,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機(jī)與錄音类嗤,去河邊找鬼糊肠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛遗锣,可吹牛的內(nèi)容都是我干的货裹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼精偿,長吁一口氣:“原來是場噩夢啊……” “哼弧圆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起笔咽,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤搔预,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后叶组,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拯田,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年甩十,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了船庇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡侣监,死狀恐怖鸭轮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情橄霉,我是刑警寧澤窃爷,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站酪劫,受9級特大地震影響吞鸭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜覆糟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一刻剥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧滩字,春花似錦造虏、人聲如沸御吞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陶珠。三九已至,卻和暖如春享钞,著一層夾襖步出監(jiān)牢的瞬間揍诽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工栗竖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留暑脆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓狐肢,卻偏偏與公主長得像添吗,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子份名,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容