Почему градиент металлического шейдера легче, когда SCNProgram применяется к узлу SceneKit, чем к MTKView?
У меня есть градиент, сгенерированный фрагментным шейдером Metal, который я применил к узлу SCNNode, определенному геометрией плоскости.
Это выглядит так:
Когда я использую тот же шейдер, примененный к MTKView, визуализированному на игровой площадке XCode, цвета темнее. Что заставляет цвета быть более светлыми в версии Scenekit?
Вот металлический шейдер и GameViewController.
Shader:
#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 ответа
Как объяснялось в сеансе " Достижения в рендеринге SceneKit" из WWDC 2016, теперь SceneKit по умолчанию выполняет рендеринг в линейном пространстве, который необходим для получения точных результатов из уравнений освещения.
Разница, которую вы видите, заключается в том, что в случае MetalKit вы предоставляете цветовые компоненты (значения красного, зеленого и синего) в цветовом пространстве sRGB, в то время как в случае SceneKit вы предоставляете точно такие же компоненты в линейном цветовом пространстве sRGB,
Вам решать, какой результат вам нужен. Либо вы хотите градиент в линейном пространстве (это то, что вы хотите, если вы интерполируете некоторые данные) или в гамма-пространстве (это то, что используют приложения для рисования).
Если вам нужен градиент в гамма-пространстве, вам необходимо преобразовать цветовые компоненты в линейные, потому что это то, с чем работает SceneKit. Принимая формулы преобразования из спецификации языка Metal Shading, вот решение:
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);
}
Узнав причину этой проблемы, я немного больше изучил эту тему и нашел другое решение. Рендеринг гамма-пространства можно форсировать во всем приложении, установив для SCNDisableLinearSpaceRendering значение TRUE в списке приложений.
Я не уверен, но мне кажется, что ваш расчет размера узла выключен, ведя ваш .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);
Вы хотите абсолютную разницу между двумя крайностями, а не сумму. Сумма становится больше, когда узел движется вправо и вниз, потому что он эффективно включает в себя позицию.
Все это говорит, разве желаемое (и, v) вам уже не предоставлено в in.texCoords
?