问题
In an OpenGL program you typical declare something like this in a vertex shader
varying bool aBooleanVariable;
and then read the value in the fragment shader. How do you do this within the framework of an SCNShadable
entry point? For example from SCNShaderModifierEntryPointGeometry
to SCNShaderModifierEntryPointFragment
.
Receiving the argument seems to be defined by using the pragma arguments, I provide my test SCNShaderModifierEntryPointFragment
to illustrate.
#pragma arguments
bool clipFragment
#pragma body
if (clipFragment) {
discard_fragment();
}
However, the arguments pragma doesn't work for outputting the value in the SCNShaderModifierEntryPointGeometry
entry point.
I found an article that suggests that it can be done with GLSL syntax, but I was trying to find the Metal way and I wasn't even able to reproduce the result.
回答1:
I had a similar problem and managed to figure out the solution. I'm not sure how far back this works, but it works on XCode 9 with iOS 11. Here's an example of looking up a custom cubemap texture using a varying float3 for the coordinate.
The geometry modifier:
#include <metal_stdlib>
#pragma varyings
float3 cubemapCoord;
#pragma body
out.cubemapCoord = _geometry.normal;
The surface modifier:
#include <metal_stdlib>
#pragma arguments
texturecube<float> texture;
#pragma body
constexpr sampler s(filter::linear, mip_filter::linear);
_surface.diffuse = texture.sample(s, in.cubemapCoord);
And the Swift code for assigning the material:
do {
let loader = MTKTextureLoader.init(device: MTLCreateSystemDefaultDevice()!)
let url = Bundle.main.url(forResource: "cubemap", withExtension: "pvr", subdirectory: "art.scnassets")!
let texture = try loader.newTexture(URL: url, options: nil)
let materialProperty = SCNMaterialProperty.init(contents: texture)
geometry.setValue(materialProperty, forKey: "texture")
} catch {
print(error.localizedDescription)
}
回答2:
At some point you will bump into the limits of what can be done with SCNShadable
and need to move onto SCNProgram
. Doing so unfortunately means you need to provide a complete implementation of the vertex and fragment shaders. On the positive side, it is rather simple to pass a value from the vertex to the fragment shader. You simply add a variable to the struct you identify with the stage_in qualifier (assuming you're using Metal). But that's not really what you asked, and there is a workaround for that.
#pragma arguements
as you noted, wont work in this case; it's for passing in constants that do not vary over a render pass.
Instead I'd suggest you look at repurposing one of the existing variables SceneKit uses in its shader. By now you've no doubt noticed SceneKit dumps its shader source code to stdout when it fails to compile. The Metal shader defines a commonprofile_io
struct which is passed from the vertex to fragment shader, it includes: position, colour, texture coordinates, normals, etc. If you're not using vertex colours, then it's possible to use this to store 4 float values (rgba). Important to note that SceneKit optimises the shader for the geometry source being rendered; if your geometry source contains no vertex colours, then the commonprofile_io
struct will not have a vertexColor
variable.
Would be very interested to know of a better solution.
回答3:
Forced to use GLSL
Turns out you can get it to work if you switch the SCNView
Rendering API to OpenGL ES. Then it will accept GLSL code including the varying qualifier.
Geometry entry point
varying float clipFragment;
#pragma body
clipFragment = 1.0;
Fragment entry point
varying float clipFragment;
#pragma body
if (clipFragment > 0.0) {
discard;
}
Update from Apple
I opened a ticket with Apple and got the following response -
That’s correct. The SceneKit team has opened up a formal enhancement request (bug #28206462) to investigate adding this capability to Metal. Until then, you’ll have to opt into using the OpenGL renderer.
回答4:
I've struggling with this question too, and I've made a recent discovery that blew this thing wide open for me. It has aided me tremendously in grasping how SceneKit, shaders, shader modifiers and OpenGL/Metal work together and what tools/methods I have available when writing a shader modifier. Furthermore, I believe the currently accepted answer is no longer correct (fortunately! 🎉).
SceneKit provides default vertex and fragment Metal shaders. It is into these shaders that the shader modifiers are injected.
You can find these default Metal shaders when you capture a GPU frame in Xcode.
Double click on either commonprofile_vert
or commonprofile_frag
(both shaders are inside the same "file", as demonstrated by the same values in the Details column).
There is a lot of information in this file, and it demonstrates some of the inner workings of how SceneKit talks to the GPU when rendering. This is an example of the file (although this version is a couple of years old already it is still exemplary).
Notice how the following lines are injection places for the shader modifiers.
// DoLightModifier START
// DoLightModifier END
...
// DoGeometryModifier START
// DoGeometryModifier END
...
// DoSurfaceModifier START
// DoSurfaceModifier END
...
// DoFragmentModifier START
// DoFragmentModifier END
You will see that your shader modifiers have been placed here.
Look for #ifdef USE_EXTRA_VARYINGS
. Any varying
variables you declare in the geometry modifier show up as property on commonprofile_io
structs. Of which you use the following instance: commonprofile_io out;
.
This file also demonstrates why you need varyings
for communication between the geometry and fragment shader. And why _surface
is accessible in the fragment shader: the fragment modifier and surface modifier have the same scope.
I am absolutely stumped how none of this stuff is documented or even hinted at by Apple, save for some tucked away WWDC-slides probably. I have no idea how people without a couple of weeks of spare time are supposed to figure out how it all works.
来源:https://stackoverflow.com/questions/37546352/passing-values-between-scnshadable-entry-points