为什么金属着色器渐变作为应用于 SceneKit 节点的 SCNProgram 比作为 MTKView 更轻?
why is metal shader gradient lighter as a SCNProgram applied to a SceneKit Node than it is as a MTKView?
我有一个渐变,由我应用于由平面几何定义的 SCNNode 的金属片段着色器生成。
看起来像这样:
当我使用应用于 Xcode 游乐场中呈现的 MTKView 的相同着色器时,颜色更深。是什么导致 Scenekit 版本中的颜色变浅?
这是 Metal 着色器和 GameViewController。
着色器:
#include <metal_stdlib>
using namespace metal;
#include <SceneKit/scn_metal>
struct myPlaneNodeBuffer {
float4x4 modelTransform;
float4x4 modelViewTransform;
float4x4 normalTransform;
float4x4 modelViewProjectionTransform;
float2x3 boundingBox;
};
typedef struct {
float3 position [[ attribute(SCNVertexSemanticPosition) ]];
float2 texCoords [[ attribute(SCNVertexSemanticTexcoord0) ]];
} VertexInput;
struct SimpleVertexWithUV
{
float4 position [[position]];
float2 uv;
};
vertex SimpleVertexWithUV gradientVertex(VertexInput in [[ stage_in ]],
constant SCNSceneBuffer& scn_frame [[buffer(0)]],
constant myPlaneNodeBuffer& scn_node [[buffer(1)]])
{
SimpleVertexWithUV vert;
vert.position = scn_node.modelViewProjectionTransform * float4(in.position, 1.0);
int width = abs(scn_node.boundingBox[0].x) + abs(scn_node.boundingBox[1].x);
int height = abs(scn_node.boundingBox[0].y) + abs(scn_node.boundingBox[1].y);
float2 resolution = float2(width,height);
vert.uv = vert.position.xy * 0.5 / resolution;
vert.uv = 0.5 - vert.uv;
return vert;
}
fragment float4 gradientFragment(SimpleVertexWithUV in [[stage_in]],
constant myPlaneNodeBuffer& scn_node [[buffer(1)]])
{
float4 fragColor;
float3 color = mix(float3(1.0, 0.6, 0.1), float3(0.5, 0.8, 1.0), sqrt(1-in.uv.y));
fragColor = float4(color,1);
return(fragColor);
}
游戏视图控制器:
import SceneKit
import QuartzCore
class GameViewController: NSViewController {
@IBOutlet weak var gameView: GameView!
override func awakeFromNib(){
super.awakeFromNib()
// create a new scene
let scene = SCNScene()
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
// turn off default lighting
self.gameView!.autoenablesDefaultLighting = false
// set the scene to the view
self.gameView!.scene = scene
// allows the user to manipulate the camera
self.gameView!.allowsCameraControl = true
// show statistics such as fps and timing information
self.gameView!.showsStatistics = true
// configure the view
self.gameView!.backgroundColor = NSColor.black
var geometry:SCNGeometry
geometry = SCNPlane(width:10, height:10)
let geometryNode = SCNNode(geometry: geometry)
let program = SCNProgram()
program.fragmentFunctionName = "gradientFragment"
program.vertexFunctionName = "gradientVertex"
let gradientMaterial = SCNMaterial()
gradientMaterial.program = program
geometry.materials = [gradientMaterial]
scene.rootNode.addChildNode(geometryNode)
}
}
我不确定,但在我看来你对节点大小的计算是错误的,导致你的 .uv
被关闭,具体取决于节点的位置。
你有:
int width = abs(scn_node.boundingBox[0].x) + abs(scn_node.boundingBox[1].x);
int height = abs(scn_node.boundingBox[0].y) + abs(scn_node.boundingBox[1].y);
我认为应该是:
int width = abs(scn_node.boundingBox[0].x - scn_node.boundingBox[1].x);
int height = abs(scn_node.boundingBox[0].y - scn_node.boundingBox[1].y);
您想要两个极端之间的绝对差异,而不是总和。随着节点向右和向下移动,总和变大,因为它有效地包括了位置。
说了这么多,你想要的 (u, v) 不是已经在 in.texCoords
中提供给你了吗?
如 WWDC 2016 的 Advances in SceneKit Rendering session 中所述,SceneKit 现在默认以线性渲染 space,这是从光照方程式获得准确结果所必需的。
您看到的不同之处在于,在 MetalKit 案例中,您提供了 sRGB 颜色的颜色分量(红色、绿色和蓝色值)space,而在 SceneKit 案例中,您提供了线性 sRGB 颜色 space.
中完全相同的分量
您想要的结果由您决定。您想要线性渐变 space(如果您要插值一些数据,这就是您想要的)或伽玛 space(这就是绘图应用程序使用的渐变)。
如果您想要 gamma 渐变 space,您需要将颜色分量转换为线性分量,因为 SceneKit 就是这样做的。从 Metal Shading Language Specification 中获取转换公式,这里有一个解决方案:
static float srgbToLinear(float c) {
if (c <= 0.04045)
return c / 12.92;
else
return powr((c + 0.055) / 1.055, 2.4);
}
fragment float4 gradientFragment(SimpleVertexWithUV in [[stage_in]],
constant myPlaneNodeBuffer& scn_node [[buffer(1)]])
{
float3 color = mix(float3(1.0, 0.6, 0.1), float3(0.5, 0.8, 1.0), sqrt(1 - in.uv.y));
color.r = srgbToLinear(color.r);
color.g = srgbToLinear(color.g);
color.b = srgbToLinear(color.b);
float4 fragColor = float4(color, 1);
return(fragColor);
}
了解了这个问题的根本原因后,我对该主题进行了更多研究并找到了另一种解决方案。 Gamma space 渲染可以通过设置在应用程序范围内强制执行
在应用程序的 plist 中将 SCNDisableLinearSpaceRendering 设置为 TRUE。
我有一个渐变,由我应用于由平面几何定义的 SCNNode 的金属片段着色器生成。
看起来像这样:
当我使用应用于 Xcode 游乐场中呈现的 MTKView 的相同着色器时,颜色更深。是什么导致 Scenekit 版本中的颜色变浅?
这是 Metal 着色器和 GameViewController。
着色器:
#include <metal_stdlib>
using namespace metal;
#include <SceneKit/scn_metal>
struct myPlaneNodeBuffer {
float4x4 modelTransform;
float4x4 modelViewTransform;
float4x4 normalTransform;
float4x4 modelViewProjectionTransform;
float2x3 boundingBox;
};
typedef struct {
float3 position [[ attribute(SCNVertexSemanticPosition) ]];
float2 texCoords [[ attribute(SCNVertexSemanticTexcoord0) ]];
} VertexInput;
struct SimpleVertexWithUV
{
float4 position [[position]];
float2 uv;
};
vertex SimpleVertexWithUV gradientVertex(VertexInput in [[ stage_in ]],
constant SCNSceneBuffer& scn_frame [[buffer(0)]],
constant myPlaneNodeBuffer& scn_node [[buffer(1)]])
{
SimpleVertexWithUV vert;
vert.position = scn_node.modelViewProjectionTransform * float4(in.position, 1.0);
int width = abs(scn_node.boundingBox[0].x) + abs(scn_node.boundingBox[1].x);
int height = abs(scn_node.boundingBox[0].y) + abs(scn_node.boundingBox[1].y);
float2 resolution = float2(width,height);
vert.uv = vert.position.xy * 0.5 / resolution;
vert.uv = 0.5 - vert.uv;
return vert;
}
fragment float4 gradientFragment(SimpleVertexWithUV in [[stage_in]],
constant myPlaneNodeBuffer& scn_node [[buffer(1)]])
{
float4 fragColor;
float3 color = mix(float3(1.0, 0.6, 0.1), float3(0.5, 0.8, 1.0), sqrt(1-in.uv.y));
fragColor = float4(color,1);
return(fragColor);
}
游戏视图控制器:
import SceneKit
import QuartzCore
class GameViewController: NSViewController {
@IBOutlet weak var gameView: GameView!
override func awakeFromNib(){
super.awakeFromNib()
// create a new scene
let scene = SCNScene()
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
// turn off default lighting
self.gameView!.autoenablesDefaultLighting = false
// set the scene to the view
self.gameView!.scene = scene
// allows the user to manipulate the camera
self.gameView!.allowsCameraControl = true
// show statistics such as fps and timing information
self.gameView!.showsStatistics = true
// configure the view
self.gameView!.backgroundColor = NSColor.black
var geometry:SCNGeometry
geometry = SCNPlane(width:10, height:10)
let geometryNode = SCNNode(geometry: geometry)
let program = SCNProgram()
program.fragmentFunctionName = "gradientFragment"
program.vertexFunctionName = "gradientVertex"
let gradientMaterial = SCNMaterial()
gradientMaterial.program = program
geometry.materials = [gradientMaterial]
scene.rootNode.addChildNode(geometryNode)
}
}
我不确定,但在我看来你对节点大小的计算是错误的,导致你的 .uv
被关闭,具体取决于节点的位置。
你有:
int width = abs(scn_node.boundingBox[0].x) + abs(scn_node.boundingBox[1].x);
int height = abs(scn_node.boundingBox[0].y) + abs(scn_node.boundingBox[1].y);
我认为应该是:
int width = abs(scn_node.boundingBox[0].x - scn_node.boundingBox[1].x);
int height = abs(scn_node.boundingBox[0].y - scn_node.boundingBox[1].y);
您想要两个极端之间的绝对差异,而不是总和。随着节点向右和向下移动,总和变大,因为它有效地包括了位置。
说了这么多,你想要的 (u, v) 不是已经在 in.texCoords
中提供给你了吗?
如 WWDC 2016 的 Advances in SceneKit Rendering session 中所述,SceneKit 现在默认以线性渲染 space,这是从光照方程式获得准确结果所必需的。
您看到的不同之处在于,在 MetalKit 案例中,您提供了 sRGB 颜色的颜色分量(红色、绿色和蓝色值)space,而在 SceneKit 案例中,您提供了线性 sRGB 颜色 space.
中完全相同的分量您想要的结果由您决定。您想要线性渐变 space(如果您要插值一些数据,这就是您想要的)或伽玛 space(这就是绘图应用程序使用的渐变)。
如果您想要 gamma 渐变 space,您需要将颜色分量转换为线性分量,因为 SceneKit 就是这样做的。从 Metal Shading Language Specification 中获取转换公式,这里有一个解决方案:
static float srgbToLinear(float c) {
if (c <= 0.04045)
return c / 12.92;
else
return powr((c + 0.055) / 1.055, 2.4);
}
fragment float4 gradientFragment(SimpleVertexWithUV in [[stage_in]],
constant myPlaneNodeBuffer& scn_node [[buffer(1)]])
{
float3 color = mix(float3(1.0, 0.6, 0.1), float3(0.5, 0.8, 1.0), sqrt(1 - in.uv.y));
color.r = srgbToLinear(color.r);
color.g = srgbToLinear(color.g);
color.b = srgbToLinear(color.b);
float4 fragColor = float4(color, 1);
return(fragColor);
}
了解了这个问题的根本原因后,我对该主题进行了更多研究并找到了另一种解决方案。 Gamma space 渲染可以通过设置在应用程序范围内强制执行 在应用程序的 plist 中将 SCNDisableLinearSpaceRendering 设置为 TRUE。