前言
本篇文章主要記錄如何使用SCNMaterial + Metal Shader實(shí)現(xiàn)自定義材質(zhì)效果
編寫一個基本的Metal Shader
Shader主要包含下面的部分
Vertex Function輸入輸出數(shù)據(jù)結(jié)構(gòu)
struct VertexInput {
float3 position [[attribute(SCNVertexSemanticPosition)]];
float2 uv [[attribute(SCNVertexSemanticTexcoord0)]];
};
struct VertexOut {
float4 position [[position]];
float2 uv;
};
這里的VertexInput
通過attribute(SCNVertexSemanticXXX)
和SceneKit約定好的頂點(diǎn)格式進(jìn)行映射,VertexOut
則是標(biāo)準(zhǔn)的Metal Shader寫法,主要用于Fragment Function的輸入
SceneKit通用輸入Buffer結(jié)構(gòu)
在Metal中,使用Buffer
來傳遞uniform變量,定義NodeBuffer
結(jié)構(gòu)來接受SceneKit SCNNode的通用uniform變量
struct NodeBuffer {
float4x4 modelTransform;
float4x4 modelViewProjectionTransform;
float4x4 modelViewTransform;
float4x4 normalTransform;
float2x3 boundingBox;
};
Vertex Function
這個是標(biāo)準(zhǔn)的Metal Vertex Function,輸入?yún)?shù)是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矩陣對原始位置進(jìn)行變換。
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對紋理采樣,返回對應(yīng)的顏色
SCNMaterial使用自定義Metal Shader
mat.program = [SCNProgram program];
mat.program.vertexFunctionName = @"textureSamplerVertex";
mat.program.fragmentFunctionName = @"textureSamplerFragment";
創(chuàng)建SCNProgram并指定Metal Shader中的vertexFunctionName
,fragmentFunctionName
,然后賦值給SCNMaterial的program即可
設(shè)置紋理
通過下面的代碼可以將圖片賦值給Fragment Function中diffuse
參數(shù)
[mainCanvasMaterial setValue:[SCNMaterialProperty materialPropertyWithContents:img] forKey:@"diffuse"];
img
是UIImage類型變量,SceneKit會在底層將UIImage
轉(zhuǎn)成MTLTexture
,綁定到Metal Shader的diffuse
變量,也就是索引為0的紋理上。
如何傳遞自定義Buffer數(shù)據(jù)
比如自定義一個表示縮放的數(shù)據(jù)結(jié)構(gòu)來縮放紋理
struct ScaleParams {
float2 scale;
};
在Vertex Function 或者 Fragment Function中增加參數(shù)
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位置,這也是標(biāo)準(zhǔn)的Metal Shader做法。
在oc代碼中,使用setValue:forKey:
設(shè)置該buffer值
[mainCanvasMaterial setValue:[NSData dataWithBytes:&scale length:sizeof(simd_float2)] forKey:@"scaleParams"];
這里直接將NSData
傳遞給SCNMaterial,在底層會把NSData傳遞給對應(yīng)索引的MTLBuffer
。