根据顶点世界位置Y坐标修改Metal fragment shading
Modify Metal fragment shading based on vertex world position Y coordinate
我正在尝试使用带有 SCNTechnique 的金属片段着色器来根据顶点 Y 世界位置修改片段颜色。
目前我的理解
SCNTechnique 可以配置一系列渲染过程。渲染通道允许注入顶点和片段着色器。这些着色器是用 Metal 编写的。 Metal Shading Language Specification 描述了这两个支持的 inputs/outputs。
为每个正在渲染的顶点调用顶点着色器。我们可以将附加信息从顶点着色器传递到片段着色器(如 3D 中的位置 space,参见 MSLS section 5.2)。
片段着色器最接近像素,如果该像素有多个 "qualify" 的三角形,则可能称为单个 "pixel" 的倍数时间。 (通常)片段着色后,如果片段未通过深度或模板测试,则片段可能会被丢弃。
我的尝试
这就是我的尝试。 (希望能指出我理解的不足之处)。
struct VertexOut {
float4 position [[position]];
};
vertex VertexOut innerVertexShader(VertexIn in [[stage_in]]) {
VertexOut out;
out.position = in.position;
return out;
};
fragment half4 innerFragmentShader(VertexOut in [[stage_in]],
half4 color [[color(0)]]) {
half4 output;
output = color; // test to see if getting rendered color works
output.g = in.position.y; // test to see if getting y works
return output;
}
这些着色器在 SCNTechnique 字典中被引用。
[
"passes": [
"innerPass: [
"draw": "DRAW_NODE",
"node": "inner",
"metalVertexShader": "innerVertexShader",
"metalFragmentShader": "innerFragmentShader"
]
],
"sequence": ["innerPass"],
"symbols": [:],
"targets": [:],
]
// ...
let technique = SCNTechnique(dictionary: techniqueDictionary)
这会执行以下操作:技术被正确实例化并附加到场景(因为它会影响渲染)。但它似乎没有将相机变换或节点位置变换应用于顶点。而是将每个节点呈现为从 (0,0,1) 在位置 (0,0,0) 处查看。颜色不对。如果我从 SCNTechnique 中删除着色器,每个渲染器都会像我期望的那样渲染。
我如何利用常规的 SceneKit 行为(相机变换等),并且仅根据片段的 y 世界位置修改颜色输出?我希望这需要在片段级别上发生,使用在顶点着色器中以某种方式获得的世界位置。我搜索了 "Metal basic vertex shader" 之类的东西,但一无所获。
我见过着色器 like this,但我相信我应该能够依靠 SceneKit 渲染来处理光照、PBR 材质、相机变换等内容。在这一点上,每当我搜索一些 Metal 主题时,我都会觉得,我最终访问了相同的网站,但这些网站尚未成功将我的理解提升到一个新的水平。因此,也欢迎任何 new/additional 资源。
背景
在过去的两个月里,我一直在开发自己的游戏项目,该项目使用 SceneKit 作为主要图形框架。我已转向 SCNTechnique 和 Metal 着色器来获得自定义效果。最后两个特别让我头疼,都是因为缺少样本 code/documentation/runtime 反馈。
因此,我考虑过搬到 Unity/Unreal 甚至完全取消这个项目。但因为我很固执,也因为我真的不想将我的 Swift 代码移植到 C#/C++,所以我还没有放弃 SceneKit。
在过去几天研究这个主题后,我对顶点和片段着色以及 SceneKit 如何处理这些事情的理解有了很大的发展。
正如@mnuages 在评论中指出的那样,对于此用例,着色器修改器是可行的方法。他们利用默认的 SceneKit 着色(如 OP 所要求的)并允许着色器代码注入。
附加信息
为了弥补 SceneKit 文档的一些局限性,我会为研究该主题的其他人详细说明。
有关着色器修改器如何绑定到 SceneKit 默认 vertex/fragment 着色器的更多信息,请参阅 or SceneKit's default shaders。第二个 link 演示了在利用着色器修改器而不是编写自己的着色器时免费获得的 SceneKit 渲染逻辑的范围。
This page 帮助我理解了从顶点到片段的向量变换的不同阶段(模型space ➡️ 世界space ➡️ 相机space ➡️ 投影space).
替代方法(自定义着色器)
如果您想使用完全自定义的着色器进行单通道处理,这是一个简单的示例。它将世界 y 位置从顶点着色器传递到片段着色器。
// Shaders.metal file in your Xcode project
#include <metal_stdlib>
using namespace metal;
#include <SceneKit/scn_metal>
typedef struct {
float4x4 modelTransform;
float4x4 modelViewTransform;
} commonprofile_node;
struct VertexIn {
float3 position [[attribute(SCNVertexSemanticPosition)]];
};
struct VertexOut {
float4 fragmentPosition [[position]];
float height;
};
vertex VertexOut myVertex(
VertexIn in [[stage_in]],
constant SCNSceneBuffer& scn_frame [[buffer(0)]],
constant commonprofile_node& scn_node [[buffer(1)]]
) {
VertexOut out;
float4 position = float4(in.position, 1.f);
out.fragmentPosition = scn_frame.viewProjectionTransform * scn_node.modelTransform * position;
// store world position for fragment shading
out.height = (scn_node.modelTransform * position).y;
return out;
}
fragment half4 myFragment(VertexOut in [[stage_in]]) {
return half4(in.height);
}
let dictionary: [String: Any] = [
"passes" : [
"y" : [
"draw" : "DRAW_SCENE",
"inputs" : [:],
"outputs" : [
"color" : "COLOR"
],
"metalVertexShader": "myVertex",
"metalFragmentShader": "myFragment",
]
],
"sequence" : ["y"],
"symbols" : [:]
]
let technique = SCNTechnique(dictionary: dictionary)
scnView.technique = technique
您可以将此渲染通道与其他通道组合(参见 SCNTechnique)。
我正在尝试使用带有 SCNTechnique 的金属片段着色器来根据顶点 Y 世界位置修改片段颜色。
目前我的理解
SCNTechnique 可以配置一系列渲染过程。渲染通道允许注入顶点和片段着色器。这些着色器是用 Metal 编写的。 Metal Shading Language Specification 描述了这两个支持的 inputs/outputs。 为每个正在渲染的顶点调用顶点着色器。我们可以将附加信息从顶点着色器传递到片段着色器(如 3D 中的位置 space,参见 MSLS section 5.2)。 片段着色器最接近像素,如果该像素有多个 "qualify" 的三角形,则可能称为单个 "pixel" 的倍数时间。 (通常)片段着色后,如果片段未通过深度或模板测试,则片段可能会被丢弃。
我的尝试
这就是我的尝试。 (希望能指出我理解的不足之处)。
struct VertexOut {
float4 position [[position]];
};
vertex VertexOut innerVertexShader(VertexIn in [[stage_in]]) {
VertexOut out;
out.position = in.position;
return out;
};
fragment half4 innerFragmentShader(VertexOut in [[stage_in]],
half4 color [[color(0)]]) {
half4 output;
output = color; // test to see if getting rendered color works
output.g = in.position.y; // test to see if getting y works
return output;
}
这些着色器在 SCNTechnique 字典中被引用。
[
"passes": [
"innerPass: [
"draw": "DRAW_NODE",
"node": "inner",
"metalVertexShader": "innerVertexShader",
"metalFragmentShader": "innerFragmentShader"
]
],
"sequence": ["innerPass"],
"symbols": [:],
"targets": [:],
]
// ...
let technique = SCNTechnique(dictionary: techniqueDictionary)
这会执行以下操作:技术被正确实例化并附加到场景(因为它会影响渲染)。但它似乎没有将相机变换或节点位置变换应用于顶点。而是将每个节点呈现为从 (0,0,1) 在位置 (0,0,0) 处查看。颜色不对。如果我从 SCNTechnique 中删除着色器,每个渲染器都会像我期望的那样渲染。
我如何利用常规的 SceneKit 行为(相机变换等),并且仅根据片段的 y 世界位置修改颜色输出?我希望这需要在片段级别上发生,使用在顶点着色器中以某种方式获得的世界位置。我搜索了 "Metal basic vertex shader" 之类的东西,但一无所获。 我见过着色器 like this,但我相信我应该能够依靠 SceneKit 渲染来处理光照、PBR 材质、相机变换等内容。在这一点上,每当我搜索一些 Metal 主题时,我都会觉得,我最终访问了相同的网站,但这些网站尚未成功将我的理解提升到一个新的水平。因此,也欢迎任何 new/additional 资源。
背景
在过去的两个月里,我一直在开发自己的游戏项目,该项目使用 SceneKit 作为主要图形框架。我已转向 SCNTechnique 和 Metal 着色器来获得自定义效果。最后两个特别让我头疼,都是因为缺少样本 code/documentation/runtime 反馈。 因此,我考虑过搬到 Unity/Unreal 甚至完全取消这个项目。但因为我很固执,也因为我真的不想将我的 Swift 代码移植到 C#/C++,所以我还没有放弃 SceneKit。
在过去几天研究这个主题后,我对顶点和片段着色以及 SceneKit 如何处理这些事情的理解有了很大的发展。
正如@mnuages 在评论中指出的那样,对于此用例,着色器修改器是可行的方法。他们利用默认的 SceneKit 着色(如 OP 所要求的)并允许着色器代码注入。
附加信息
为了弥补 SceneKit 文档的一些局限性,我会为研究该主题的其他人详细说明。
有关着色器修改器如何绑定到 SceneKit 默认 vertex/fragment 着色器的更多信息,请参阅
This page 帮助我理解了从顶点到片段的向量变换的不同阶段(模型space ➡️ 世界space ➡️ 相机space ➡️ 投影space).
替代方法(自定义着色器)
如果您想使用完全自定义的着色器进行单通道处理,这是一个简单的示例。它将世界 y 位置从顶点着色器传递到片段着色器。
// Shaders.metal file in your Xcode project
#include <metal_stdlib>
using namespace metal;
#include <SceneKit/scn_metal>
typedef struct {
float4x4 modelTransform;
float4x4 modelViewTransform;
} commonprofile_node;
struct VertexIn {
float3 position [[attribute(SCNVertexSemanticPosition)]];
};
struct VertexOut {
float4 fragmentPosition [[position]];
float height;
};
vertex VertexOut myVertex(
VertexIn in [[stage_in]],
constant SCNSceneBuffer& scn_frame [[buffer(0)]],
constant commonprofile_node& scn_node [[buffer(1)]]
) {
VertexOut out;
float4 position = float4(in.position, 1.f);
out.fragmentPosition = scn_frame.viewProjectionTransform * scn_node.modelTransform * position;
// store world position for fragment shading
out.height = (scn_node.modelTransform * position).y;
return out;
}
fragment half4 myFragment(VertexOut in [[stage_in]]) {
return half4(in.height);
}
let dictionary: [String: Any] = [
"passes" : [
"y" : [
"draw" : "DRAW_SCENE",
"inputs" : [:],
"outputs" : [
"color" : "COLOR"
],
"metalVertexShader": "myVertex",
"metalFragmentShader": "myFragment",
]
],
"sequence" : ["y"],
"symbols" : [:]
]
let technique = SCNTechnique(dictionary: dictionary)
scnView.technique = technique
您可以将此渲染通道与其他通道组合(参见 SCNTechnique)。