前言
本篇文章主要记录如何使用SCNMaterial + Metal Shader实现自定义材质效果
编写一个基本的Metal Shader
Shader主要包含下面的部分
Vertex Function输入输出数据结构
struct VertexInput {
float3 position [[attribute(SCNVertexSemanticPosition)]];
float2 uv [[attribute(SCNVertexSemanticTexcoord0)]];
};
struct VertexOut {
float4 position [[position]];
float2 uv;
};
这里的VertexInput
通过attribute(SCNVertexSemanticXXX)
和SceneKit约定好的顶点格式进行映射,VertexOut
则是标准的Metal Shader写法,主要用于Fragment Function的输入
SceneKit通用输入Buffer结构
在Metal中,使用Buffer
来传递uniform变量,定义NodeBuffer
结构来接受SceneKit SCNNode的通用uniform变量
struct NodeBuffer {
float4x4 modelTransform;
float4x4 modelViewProjectionTransform;
float4x4 modelViewTransform;
float4x4 normalTransform;
float2x3 boundingBox;
};
Vertex Function
这个是标准的Metal Vertex Function,输入参数是VertexInput
和NodeBuffer
vertex VertexOut textureSamplerVertex(VertexInput in [[ stage_in ]], constant NodeBuffer& scn_node [[buffer(1)]]) {
VertexOut out;
out.position = scn_node.modelViewProjectionTransform * float4(in.position, 1.0);
out.uv = in.uv;
return out;
}
使用NodeBuffer
中的mvp矩阵对原始位置进行变换。
Fragment Function
fragment float4 textureSamplerFragment(VertexOut out [[ stage_in ]], texture2d<float, access::sample> diffuse [[texture(0)]]) {
constexpr sampler textureSampler(coord::normalized, filter::linear, address::repeat);
return diffuse.sample(textureSampler, out.uv);
}
Fragment Function就是简单的使用uv对纹理采样,返回对应的颜色
SCNMaterial使用自定义Metal Shader
mat.program = [SCNProgram program];
mat.program.vertexFunctionName = @"textureSamplerVertex";
mat.program.fragmentFunctionName = @"textureSamplerFragment";
创建SCNProgram并指定Metal Shader中的vertexFunctionName
,fragmentFunctionName
,然后赋值给SCNMaterial的program即可
设置纹理
通过下面的代码可以将图片赋值给Fragment Function中diffuse
参数
[mainCanvasMaterial setValue:[SCNMaterialProperty materialPropertyWithContents:img] forKey:@"diffuse"];
img
是UIImage类型变量,SceneKit会在底层将UIImage
转成MTLTexture
,绑定到Metal Shader的diffuse
变量,也就是索引为0的纹理上。
如何传递自定义Buffer数据
比如自定义一个表示缩放的数据结构来缩放纹理
struct ScaleParams {
float2 scale;
};
在Vertex Function 或者 Fragment Function中增加参数
vertex VertexOut textureSamplerVertex(VertexInput in [[ stage_in ]], constant NodeBuffer& scn_node [[buffer(1)]], constant ScaleParams &scaleParams [[buffer(2)]]) {
VertexOut out;
out.position = scn_node.modelViewProjectionTransform * float4(in.position, 1.0);
float offsetX = 0.5 * (1.0 - 1.0 / scaleParams.scale.x);
float offsetY = 0.5 * (1.0 - 1.0 / scaleParams.scale.y);
out.uv = float2(offsetX + in.uv.x / scaleParams.scale.x, offsetY + in.uv.y / scaleParams.scale.y);
return out;
}
[[buffer(2)]]
表示ScaleParams
绑定到索引为2的buffer位置,这也是标准的Metal Shader做法。
在oc代码中,使用setValue:forKey:
设置该buffer值
[mainCanvasMaterial setValue:[NSData dataWithBytes:&scale length:sizeof(simd_float2)] forKey:@"scaleParams"];
这里直接将NSData
传递给SCNMaterial,在底层会把NSData传递给对应索引的MTLBuffer
。