reflection - 哪些是在 SCNProgram 传递的金属着色器中使用的正确矩阵值,以获得正确的镀铬,如反射
问题描述
我正在开发一个应用程序,它应该在天空盒内渲染一个镀铬风格的反射球状物体(使用六面立方体贴图)。
我在 Swift 中使用不同方法的 Scenekit 执行此操作。
只要我让 Scenekit 完成所有工作,一切都很好并且完美反映(参见下面的图 1) - 换句话说,使用具有金属度 1.0、粗糙度 0.0 和颜色 UIColor.white 的标准 SCNMaterial(使用 .physicallyBased 作为照明模型)附加到节点几何体的 firstMaterial(包括定向光)。
但目标是使用SCNProgram代替,(附加到节点的材料)具有自己的顶点和片段着色器 - 对应于 Apples 关于它的文档。我有一个工作场景,但对象上的反射是错误的(如下图 2 所示)
主要问题是:要使用来自scn_node或scn_frame (在 shaders.metal 文件中)的正确矩阵值,以获得与图 1 中 Scenekit 相同的对象反射。但是将 SCNProgram 与着色器一起使用只有(并且没有光)。不幸的是,Apple 没有提供很多关于 SCNProgram 提交给着色器的不同矩阵的信息,以及使用哪个矩阵或示例类型。
这是我当前的顶点着色器,我假设在其中使用了一些错误的矩阵(我留下了一些注释掉的代码,以显示已经测试过的内容,未注释掉的代码对应于图 2 的 1:1):
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;
};
(Skybox 始终通过包含六个图像的 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;
}
推荐阅读
- javascript - Javascript - 每个链接都应该在弹出窗口中打开
- javascript - Socket.io 可能的发射器问题
- csv - 如何在 sqlite3 脚本中按 date=today 过滤
- c - 将打印在控制台中的函数的打印消息保存到字符串中
- html - VS Code - 如何启用 Html 标签属性显示
- asp.net - 我已将一个 aspx 应用程序文件夹复制到所有文件所在的 wwwroot 目录中。我在 IIS 中收到以下错误。
- php - MySQL 在 PHPmyadmin 上的终端中工作,但在 php 脚本中不起作用?
- swagger - 隐藏所有其他属性的 Swagger 示例属性
- android - Android 模拟器 AMD Threadripper
- python - 使用 DataFrame.sort_index(axis=1) 时出现意外顺序。最后列出的第一列