在 SCNProgram 传递的金属着色器中使用哪些正确的矩阵值以获得正确的 chrome 像反射
Which are the right Matrix Values to use in a metal shader passed by a SCNProgram to get a correct chrome like reflection
我正在开发一个应用程序,它应该在天空盒(使用六面立方体贴图)内渲染一个 chrome 风格的类似反射球体的对象。
我在 Swift 中通过不同的方法使用 Scenekit 来执行此操作。
只要我让 Scenekit 完成所有工作,一切都很好并且完美反映(参见下面的图 1)——换句话说,使用金属度 1.0、粗糙度 0.0 和颜色 UIColor.white 的标准 SCNMaterial(使用 .physicallyBased 作为光照模型)附加到节点几何体的 firstMaterial(包括定向光)。
但目标是使用 SCNProgram 而不是 ,(附加到节点的 material)具有自己的顶点和片段着色器 - 对应于关于它的苹果文档。我有一个工作场景,但是对象上的反射是错误的(如下图 2 所示)
主要问题是:scn_node 或 中哪些矩阵值 是正确的scn_frame(在 shaders.metal 文件中)使用,以在对象上获得与图 1 中 Scenekit 相同的反射。但是仅将 SCNProgram 与着色器一起使用(并且没有光)。不幸的是,Apple 没有提供很多关于 SCNProgram 提交给着色器的不同矩阵的信息,以及哪个矩阵用于什么目的 - 或者种类的例子。
这是我当前的顶点着色器,我假设其中使用了一些错误的矩阵(我留下了一些注释掉的代码,以显示已经测试过的内容,而不是注释掉的代码对应于 1:1 图 2) :
vertex SimpleVertexChromeOrig myVertexChromeOrig(MyVertexInput in [[ stage_in ]],
constant SCNSceneBuffer& scn_frame [[buffer(0)]],
constant MyNodeBuffer& scn_node [[buffer(1)]])
{
SimpleVertexChromeOrig OUT;
OUT.position = scn_node.modelViewProjectionTransform * float4(in.position, 1.0);
// OUT.position = scn_frame.viewProjectionTransform * float4(in.position, 1.0);
float4 eyeSpacePosition = scn_frame.viewTransform * float4(in.position, 1.0);
float3 eyeSpaceEyeVector = normalize(-eyeSpacePosition).xyz;
// float3 eyeSpaceNormal = normalize(scn_frame.inverseViewTransform * float4(in.normal, 1.0)).xyz;
float3 eyeSpaceNormal = normalize(scn_node.normalTransform * float4(in.normal, 1.0)).xyz;
// Reflection and Refraction Vectors
float3 eyeSpaceReflection = reflect(-eyeSpaceEyeVector, eyeSpaceNormal);
OUT.worldSpaceReflection = (scn_node.inverseModelViewTransform * float4(eyeSpaceReflection, 1.0)).xyz;
// OUT.worldSpaceReflection = (scn_node.modelViewTransform * float4(eyeSpaceReflection, 1.0)).xyz;
// OUT.worldSpaceReflection = (scn_node.modelTransform * float4(eyeSpaceReflection, 1.0)).xyz;
return OUT;
}
这是当前的片段着色器(立方体贴图采样器的默认设置):
fragment float4 myFragmentChromeOrig(SimpleVertexChromeOrig in [[stage_in]],
texturecube<float, access::sample> cubeTexture [[texture(0)]],
sampler cubeSampler [[sampler(0)]])
{
float3 reflection = cubeTexture.sample(cubeSampler, in.worldSpaceReflection).rgb;
float4 color;
color.rgb = reflection;
color.a = 1.0;
return color;
}
这是我从 NodeBuffer 获得的矩阵(由 SCNProgram 自动提供)——它们必须只在着色器文件的结构中定义才能像这样访问:
struct MyNodeBuffer {
float4x4 modelTransform;
float4x4 inverseModelTransform;
float4x4 modelViewTransform;
float4x4 inverseModelViewTransform;
float4x4 normalTransform;
float4x4 modelViewProjectionTransform;
float4x4 inverseModelViewProjectionTransform;
};
这是顶点输入结构:
typedef struct {
float3 position [[ attribute(SCNVertexSemanticPosition) ]];
float3 normal [[ attribute(SCNVertexSemanticNormal) ]]; // Phil
} MyVertexInput;
这是由顶点着色器填充的 Stuct:
struct SimpleVertexChromeOrig
{
float4 position [[position]];
float3 worldSpaceReflection;
};
(天空盒始终通过包含六张图像的 SCNMaterialContent 属性 提供并附加到 sceneView.scene.background.contents)
有许多可能的公式适用于此,但我在下面包含了一个似乎对我有用的公式。评论解释了每个步骤。
vertex SimpleVertexChromeOrig myVertexChromeOrig(MyVertexInput in [[stage_in]],
constant SCNSceneBuffer& scn_frame [[buffer(0)]],
constant MyNodeBuffer& scn_node [[buffer(1)]])
{
float4 modelSpacePosition(in.position, 1.0f);
float4 modelSpaceNormal(in.normal, 0.0f);
// We'll be computing the reflection in eye space, so first we find the eye-space
// position. This is also used to compute the clip-space position below.
float4 eyeSpacePosition = scn_node.modelViewTransform * modelSpacePosition;
// We compute the eye-space normal in the usual way.
float3 eyeSpaceNormal = (scn_node.normalTransform * modelSpaceNormal).xyz;
// The view vector in eye space is just the vector from the eye-space position.
float3 eyeSpaceViewVector = normalize(-eyeSpacePosition.xyz);
// To find the reflection vector, we reflect the (inbound) view vector about the normal.
float4 eyeSpaceReflection = float4(reflect(-eyeSpaceViewVector, eyeSpaceNormal), 0.0f);
// To sample the cubemap, we want a world-space reflection vector, so multiply
// by the inverse view transform to go back from eye space to world space.
float3 worldSpaceReflection = (scn_frame.inverseViewTransform * eyeSpaceReflection).xyz;
SimpleVertexChromeOrig out;
out.position = scn_frame.projectionTransform * eyeSpacePosition;
out.worldSpaceReflection = worldSpaceReflection;
return out;
}
fragment float4 myFragmentChromeOrig(SimpleVertexChromeOrig in [[stage_in]],
texturecube<float, access::sample> cubeTexture [[texture(0)]],
sampler cubeSampler [[sampler(0)]])
{
// Since the reflection vector's length will vary under interpolation, we normalize it
// and flip it from the assumed right-hand space of the world to the left-hand space
// of the interior of the cubemap.
float3 worldSpaceReflection = normalize(in.worldSpaceReflection) * float3(1.0f, 1.0f, -1.0f);
float3 reflection = cubeTexture.sample(cubeSampler, worldSpaceReflection).rgb;
float4 color;
color.rgb = reflection;
color.a = 1.0;
return color;
}
我正在开发一个应用程序,它应该在天空盒(使用六面立方体贴图)内渲染一个 chrome 风格的类似反射球体的对象。
我在 Swift 中通过不同的方法使用 Scenekit 来执行此操作。
只要我让 Scenekit 完成所有工作,一切都很好并且完美反映(参见下面的图 1)——换句话说,使用金属度 1.0、粗糙度 0.0 和颜色 UIColor.white 的标准 SCNMaterial(使用 .physicallyBased 作为光照模型)附加到节点几何体的 firstMaterial(包括定向光)。
但目标是使用 SCNProgram 而不是 ,(附加到节点的 material)具有自己的顶点和片段着色器 - 对应于关于它的苹果文档。我有一个工作场景,但是对象上的反射是错误的(如下图 2 所示)
主要问题是:scn_node 或 中哪些矩阵值 是正确的scn_frame(在 shaders.metal 文件中)使用,以在对象上获得与图 1 中 Scenekit 相同的反射。但是仅将 SCNProgram 与着色器一起使用(并且没有光)。不幸的是,Apple 没有提供很多关于 SCNProgram 提交给着色器的不同矩阵的信息,以及哪个矩阵用于什么目的 - 或者种类的例子。
这是我当前的顶点着色器,我假设其中使用了一些错误的矩阵(我留下了一些注释掉的代码,以显示已经测试过的内容,而不是注释掉的代码对应于 1:1 图 2) :
vertex SimpleVertexChromeOrig myVertexChromeOrig(MyVertexInput in [[ stage_in ]],
constant SCNSceneBuffer& scn_frame [[buffer(0)]],
constant MyNodeBuffer& scn_node [[buffer(1)]])
{
SimpleVertexChromeOrig OUT;
OUT.position = scn_node.modelViewProjectionTransform * float4(in.position, 1.0);
// OUT.position = scn_frame.viewProjectionTransform * float4(in.position, 1.0);
float4 eyeSpacePosition = scn_frame.viewTransform * float4(in.position, 1.0);
float3 eyeSpaceEyeVector = normalize(-eyeSpacePosition).xyz;
// float3 eyeSpaceNormal = normalize(scn_frame.inverseViewTransform * float4(in.normal, 1.0)).xyz;
float3 eyeSpaceNormal = normalize(scn_node.normalTransform * float4(in.normal, 1.0)).xyz;
// Reflection and Refraction Vectors
float3 eyeSpaceReflection = reflect(-eyeSpaceEyeVector, eyeSpaceNormal);
OUT.worldSpaceReflection = (scn_node.inverseModelViewTransform * float4(eyeSpaceReflection, 1.0)).xyz;
// OUT.worldSpaceReflection = (scn_node.modelViewTransform * float4(eyeSpaceReflection, 1.0)).xyz;
// OUT.worldSpaceReflection = (scn_node.modelTransform * float4(eyeSpaceReflection, 1.0)).xyz;
return OUT;
}
这是当前的片段着色器(立方体贴图采样器的默认设置):
fragment float4 myFragmentChromeOrig(SimpleVertexChromeOrig in [[stage_in]],
texturecube<float, access::sample> cubeTexture [[texture(0)]],
sampler cubeSampler [[sampler(0)]])
{
float3 reflection = cubeTexture.sample(cubeSampler, in.worldSpaceReflection).rgb;
float4 color;
color.rgb = reflection;
color.a = 1.0;
return color;
}
这是我从 NodeBuffer 获得的矩阵(由 SCNProgram 自动提供)——它们必须只在着色器文件的结构中定义才能像这样访问:
struct MyNodeBuffer {
float4x4 modelTransform;
float4x4 inverseModelTransform;
float4x4 modelViewTransform;
float4x4 inverseModelViewTransform;
float4x4 normalTransform;
float4x4 modelViewProjectionTransform;
float4x4 inverseModelViewProjectionTransform;
};
这是顶点输入结构:
typedef struct {
float3 position [[ attribute(SCNVertexSemanticPosition) ]];
float3 normal [[ attribute(SCNVertexSemanticNormal) ]]; // Phil
} MyVertexInput;
这是由顶点着色器填充的 Stuct:
struct SimpleVertexChromeOrig
{
float4 position [[position]];
float3 worldSpaceReflection;
};
(天空盒始终通过包含六张图像的 SCNMaterialContent 属性 提供并附加到 sceneView.scene.background.contents)
有许多可能的公式适用于此,但我在下面包含了一个似乎对我有用的公式。评论解释了每个步骤。
vertex SimpleVertexChromeOrig myVertexChromeOrig(MyVertexInput in [[stage_in]],
constant SCNSceneBuffer& scn_frame [[buffer(0)]],
constant MyNodeBuffer& scn_node [[buffer(1)]])
{
float4 modelSpacePosition(in.position, 1.0f);
float4 modelSpaceNormal(in.normal, 0.0f);
// We'll be computing the reflection in eye space, so first we find the eye-space
// position. This is also used to compute the clip-space position below.
float4 eyeSpacePosition = scn_node.modelViewTransform * modelSpacePosition;
// We compute the eye-space normal in the usual way.
float3 eyeSpaceNormal = (scn_node.normalTransform * modelSpaceNormal).xyz;
// The view vector in eye space is just the vector from the eye-space position.
float3 eyeSpaceViewVector = normalize(-eyeSpacePosition.xyz);
// To find the reflection vector, we reflect the (inbound) view vector about the normal.
float4 eyeSpaceReflection = float4(reflect(-eyeSpaceViewVector, eyeSpaceNormal), 0.0f);
// To sample the cubemap, we want a world-space reflection vector, so multiply
// by the inverse view transform to go back from eye space to world space.
float3 worldSpaceReflection = (scn_frame.inverseViewTransform * eyeSpaceReflection).xyz;
SimpleVertexChromeOrig out;
out.position = scn_frame.projectionTransform * eyeSpacePosition;
out.worldSpaceReflection = worldSpaceReflection;
return out;
}
fragment float4 myFragmentChromeOrig(SimpleVertexChromeOrig in [[stage_in]],
texturecube<float, access::sample> cubeTexture [[texture(0)]],
sampler cubeSampler [[sampler(0)]])
{
// Since the reflection vector's length will vary under interpolation, we normalize it
// and flip it from the assumed right-hand space of the world to the left-hand space
// of the interior of the cubemap.
float3 worldSpaceReflection = normalize(in.worldSpaceReflection) * float3(1.0f, 1.0f, -1.0f);
float3 reflection = cubeTexture.sample(cubeSampler, worldSpaceReflection).rgb;
float4 color;
color.rgb = reflection;
color.a = 1.0;
return color;
}