SceneKit 和 GLSL - 如何将着色器 (GLSL) 添加到几何体

SceneKit and with GLSL - how to add shader (GLSL) to a geometry

我正在学习 SceneKit,同时使用 GLSL。我很难理解将 glsl 与 SceneKit 结合使用,例如,如何在 SceneKit 中加载 glsl 着色器并将其应用于几何体。

假设我们有:

SCNBox *box = [SCNBox boxWithWidth:50 height:50 length:50 chamferRadius:0];
SCNNode *bNode = [SCNNode nodeWithGeometry:box];

SCNMaterial *redMaterial                = [SCNMaterial material];
redMaterial.diffuse.contents            = [UIColor redColor];
redMaterial.locksAmbientWithDiffuse     = YES;
box.materials =  @[redMaterial];

[scene.rootNode addChildNode:bNode];

using the glass glsl example codes by apple from year 2006 如何将这种效果添加到几何图形中。是否需要将这些参数从 Glass.vert 绑定到 SceneKit 几何图形?本来想做玻璃效果和水效果的。

玻璃效果有2个文件: 1. 文件 Glass.vert

varying vec3  Normal;
varying vec3  EyeDir;
varying vec4  EyePos;
varying float LightIntensity;

uniform vec3  LightPos;

void main(void) 
{
    gl_Position    = ftransform();
    Normal         = normalize(gl_NormalMatrix * gl_Normal);
    vec4 pos       = gl_ModelViewMatrix * gl_Vertex;
    EyeDir         = pos.xyz;
    EyePos         = gl_ModelViewProjectionMatrix * gl_Vertex;
    LightIntensity = max(dot(normalize(LightPos - EyeDir), Normal), 0.0);
}

和第二个文件:Glass.frag

const vec3 Xunitvec = vec3 (1.0, 0.0, 0.0);
const vec3 Yunitvec = vec3 (0.0, 1.0, 0.0);

uniform vec3  BaseColor;
uniform float Depth;
uniform float MixRatio;

// need to scale our framebuffer - it has a fixed width/height of 2048
uniform float FrameWidth;
uniform float FrameHeight;
uniform float textureWidth;
uniform float textureHeight;

uniform sampler2D EnvMap;
uniform sampler2D RefractionMap;

varying vec3  Normal;
varying vec3  EyeDir;
varying vec4  EyePos;
varying float LightIntensity;

void main (void)
{
    // Compute reflection vector
    vec3 reflectDir = reflect(EyeDir, Normal);

    // Compute altitude and azimuth angles

    vec2 index;

    index.y = dot(normalize(reflectDir), Yunitvec);
    reflectDir.y = 0.0;
    index.x = dot(normalize(reflectDir), Xunitvec) * 0.5;

    // Translate index values into proper range

    if (reflectDir.z >= 0.0)
        index = (index + 1.0) * 0.5;
    else
    {
        index.t = (index.t + 1.0) * 0.5;
        index.s = (-index.s) * 0.5 + 1.0;
    }

    // if reflectDir.z >= 0.0, s will go from 0.25 to 0.75
    // if reflectDir.z <  0.0, s will go from 0.75 to 1.25, and
    // that's OK, because we've set the texture to wrap.

    // Do a lookup into the environment map.

    vec3 envColor = vec3 (texture2D(EnvMap, index));

    // calc fresnels term.  This allows a view dependant blend of reflection/refraction
    float fresnel = abs(dot(normalize(EyeDir), Normal));
    fresnel *= MixRatio;
    fresnel = clamp(fresnel, 0.1, 0.9);

    // calc refraction
    vec3 refractionDir = normalize(EyeDir) - normalize(Normal);

    // Scale the refraction so the z element is equal to depth
    float depthVal = Depth / -refractionDir.z;

    // perform the div by w
    float recipW = 1.0 / EyePos.w;
    vec2 eye = EyePos.xy * vec2(recipW);

    // calc the refraction lookup
    index.s = (eye.x + refractionDir.x * depthVal);
    index.t = (eye.y + refractionDir.y * depthVal);

    // scale and shift so we're in the range 0-1
    index.s = index.s / 2.0 + 0.5;
    index.t = index.t / 2.0 + 0.5;

    // as we're looking at the framebuffer, we want it clamping at the edge of the rendered scene, not the edge of the texture,
    // so we clamp before scaling to fit
    float recipTextureWidth = 1.0 / textureWidth;
    float recipTextureHeight = 1.0 / textureHeight;
    index.s = clamp(index.s, 0.0, 1.0 - recipTextureWidth);
    index.t = clamp(index.t, 0.0, 1.0 - recipTextureHeight);

    // scale the texture so we just see the rendered framebuffer
    index.s = index.s * FrameWidth * recipTextureWidth;
    index.t = index.t * FrameHeight * recipTextureHeight;

    vec3 RefractionColor = vec3 (texture2D(RefractionMap, index));

    // Add lighting to base color and mix
    vec3 base = LightIntensity * BaseColor;
    envColor = mix(envColor, RefractionColor, fresnel);
    envColor = mix(envColor, base, 0.2);

    gl_FragColor = vec4 (envColor, 1.0);
}

编辑:

我到了要在 SceneKit 中加载这些着色器的地步:

NSURL *vertexShaderURL   = [[NSBundle mainBundle] URLForResource:@"Glass" withExtension:@"vert"];
NSURL *fragmentShaderURL = [[NSBundle mainBundle] URLForResource:@"Glass" withExtension:@"frag"];
NSString *vertexShader = [[NSString alloc] initWithContentsOfURL:vertexShaderURL
                                                        encoding:NSUTF8StringEncoding
                                                           error:NULL];
NSString *fragmentShader = [[NSString alloc] initWithContentsOfURL:fragmentShaderURL
                                                          encoding:NSUTF8StringEncoding
                                                             error:NULL];
SCNProgram *program = [SCNProgram program];
program.delegate = self;
program.vertexShader   = vertexShader;
program.fragmentShader = fragmentShader;

SCNMaterial *redMaterial                = [SCNMaterial material];
redMaterial.diffuse.contents            = [UIColor redColor];
redMaterial.locksAmbientWithDiffuse     = YES;

redMaterial.program = program;
box.materials =  @[redMaterial];

此外,我已经在着色器文件中初始化了这些:

//frag file
BaseColor = vec3 (0.4, 0.4, 1.0)
Depth = 0.1;
MixRatio = 1;
EnvMap = 0;
RefractionMap = 1;
//vert file
LightPos = vec3 (0.0, 140.0, 0.0);

盒子现在呈现粉红色,没有玻璃效果。从 redMaterial 中删除程序,盒子如预期的那样显示为红色,没有玻璃效果。所以我仍然无法达到预期的效果。非常感谢任何帮助。

编辑 2:

xcode 日志:

2016-11-21 08:08:26.758244 testGame[7837:3366037] [DYMTLInitPlatform] platform initialization successful
2016-11-21 08:08:27.196142 testGame[7837:3365880] Metal GPU Frame Capture Enabled
2016-11-21 08:08:27.196975 testGame[7837:3365880] Metal API Validation Enabled

您看到的是回退着色器。确保您使用说明符创建渲染器以更喜欢 OpenGL,而不是 Metal。例如,使用 SCNView:

_sceneView = [[SCNView alloc] initWithFrame:[UIScreen mainScreen].bounds
                                    options:@{ SCNPreferredRenderingAPIKey: @(SCNRenderingAPIOpenGLES2) }];

此外,您的着色器可能会引发您没有看到的错误。将程序的 delegate 属性 设置为实现以下内容的内容:

- (void)program:(SCNProgram *)program handleError:(NSError *)error
{
    NSLog(@"SCNProgram error %@", error);
}

你会得到一些关于为什么着色器没有编译的调试信息。