SceneKit雜談 - 如何使用Metal Shader自定義SCNMaterial的效果

前言

本篇文章主要記錄如何使用SCNMaterial + Metal Shader實現(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約定好的頂點格式進行映射,VertexOut則是標準的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

這個是標準的Metal Vertex Function,輸入?yún)?shù)是VertexInputNodeBuffer

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對紋理采樣,返回對應(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位置,這也是標準的Metal Shader做法。

在oc代碼中,使用setValue:forKey:設(shè)置該buffer值

[mainCanvasMaterial setValue:[NSData dataWithBytes:&scale length:sizeof(simd_float2)] forKey:@"scaleParams"];

這里直接將NSData傳遞給SCNMaterial,在底層會把NSData傳遞給對應(yīng)索引的MTLBuffer。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容