上一篇的教程介绍了如何绘制一张图片,这次的目标是把图片显示到3D物体上,并进行三维变换。
Metal系列教程的代码地址;
OpenGL ES系列教程在这里;
你的star和fork是我的源动力,你的意见能让我走得更远。
在图片绘制的基础上,给顶点数据增加z坐标,并使用顶点的索引缓存;为了实现三维变换,给顶点shader增加投影矩阵和模型变换矩阵。
- (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
由顶点坐标、顶点颜色、纹理坐标组成;
索引缓存的创建和顶点缓存的创建一样,本质都是存放数据的缓存;
- (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;
}
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
表示对顺时针顺序的三角形进行剔除。
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代码。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。