为什么在将SCNProgram应用于SceneKit节点时,金属着色器渐变会比在MTKView中表现出来的更浅?

7
我有一个渐变,由我应用于平面几何定义的SCNNode的Metal片段着色器生成。
它看起来像这样:

scenekit gradient plane node

使用相同的着色器(Shader)应用于在Xcode Playground中渲染的MTKView时,颜色会变暗。 造成SceneKit版本颜色较浅的原因是什么?

enter image description here

这是金属着色器和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)

    }

}
3个回答

10
作为2016年WWDC的SceneKit渲染进展会议所解释的,SceneKit现在默认以线性空间进行渲染,这是需要准确的光照方程结果。
你看到的差异来自于MetalKit情况下提供的颜色分量(红色,绿色和蓝色值)在sRGB颜色空间中,而在SceneKit情况下,您提供完全相同的组件在线性sRGB颜色空间中。
由您决定哪个结果是您想要的。您可以选择在线性空间中使用渐变(如果要插值某些数据,则需要这样做),或者在伽马空间中使用渐变(绘图应用程序使用这种方式)。
如果您希望在伽马空间中使用渐变,则需要将颜色分量转换为线性,因为这是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);
}

enter image description here


谢谢!这正是我需要知道的。 - Chester Fritz

1
在了解了这个问题的根本原因后,我对这个主题进行了更多的研究,并找到了另一个解决方案。通过在应用程序的 plist 中设置 SCNDisableLinearSpaceRendering 为 TRUE,可以强制整个应用程序使用 Gamma 空间渲染。

0

我不确定,但是看起来你计算节点大小的方法有误,导致你的.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);

您想要的是两个极端的绝对差异,而不是它们的和。随着节点向右和向下移动,和会增加,因为它有效地包括了位置。

话虽如此,在in.texCoords中已经为您提供了所需的(u, v)吗?


是的,你两点都说对了。我已经改用in.texCoords作为正确的uv。 - Chester Fritz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接