Skip to content

Instantly share code, notes, and snippets.

@kevinls-coder
Forked from j-j-m/commonprofile.metal
Created October 24, 2018 08:01
Show Gist options
  • Save kevinls-coder/be929706fbd3cc5b0de1f42f6a1003d5 to your computer and use it in GitHub Desktop.
Save kevinls-coder/be929706fbd3cc5b0de1f42f6a1003d5 to your computer and use it in GitHub Desktop.

Revisions

  1. @j-j-m j-j-m created this gist May 8, 2017.
    1,054 changes: 1,054 additions & 0 deletions commonprofile.metal
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,1054 @@
    ////////////////////////////////////////////////
    // CommonProfile Shader v2

    #import <metal_stdlib>

    using namespace metal;

    #ifndef __SCNMetalDefines__
    #define __SCNMetalDefines__

    enum {
    SCNVertexSemanticPosition,
    SCNVertexSemanticNormal,
    SCNVertexSemanticTangent,
    SCNVertexSemanticColor,
    SCNVertexSemanticBoneIndices,
    SCNVertexSemanticBoneWeights,
    SCNVertexSemanticTexcoord0,
    SCNVertexSemanticTexcoord1,
    SCNVertexSemanticTexcoord2,
    SCNVertexSemanticTexcoord3,
    SCNVertexSemanticTexcoord4,
    SCNVertexSemanticTexcoord5,
    SCNVertexSemanticTexcoord6,
    SCNVertexSemanticTexcoord7
    };

    // This structure hold all the informations that are constant through a render pass
    // In a shader modifier, it is given both in vertex and fragment stage through an argument named "scn_frame".
    struct SCNSceneBuffer {
    float4x4 viewTransform;
    float4x4 inverseViewTransform; // transform from view space to world space
    float4x4 projectionTransform;
    float4x4 viewProjectionTransform;
    float4x4 viewToCubeTransform; // transform from view space to cube texture space (canonical Y Up space)
    float4 ambientLightingColor;
    float4 fogColor;
    float3 fogParameters; // x:-1/(end-start) y:1-start*x z:exp
    float2 inverseResolution;
    float time;
    float sinTime;
    float cosTime;
    float random01;
    // new in macOS 10.12 and iOS 10
    float environmentIntensity;
    float4x4 inverseProjectionTransform;
    float4x4 inverseViewProjectionTransform;
    };

    // In custom shaders or in shader modifiers, you also have access to node relative information.
    // This is done using an argument named "scn_node", which must be a struct with only the necessary fields
    // among the following list:
    //
    // float4x4 modelTransform;
    // float4x4 inverseModelTransform;
    // float4x4 modelViewTransform;
    // float4x4 inverseModelViewTransform;
    // float4x4 normalTransform; // This is the inverseTransposeModelViewTransform, need for normal transformation
    // float4x4 modelViewProjectionTransform;
    // float4x4 inverseModelViewProjectionTransform;
    // float2x3 boundingBox;
    // float2x3 worldBoundingBox;

    #endif /* defined(__SCNMetalDefines__) */


    //
    // Utility
    //

    // Tool function

    namespace scn {

    inline float3x3 mat3(float4x4 mat4)
    {
    return float3x3(mat4[0].xyz, mat4[1].xyz, mat4[2].xyz);
    }

    inline float3 mat4_mult_float3_normalized(float4x4 matrix, float3 src)
    {
    float3 dst = src.xxx * matrix[0].xyz;
    dst += src.yyy * matrix[1].xyz;
    dst += src.zzz * matrix[2].xyz;
    return normalize(dst);
    }

    inline float3 mat4_mult_float3(float4x4 matrix, float3 src)
    {
    float3 dst = src.xxx * matrix[0].xyz;
    dst += src.yyy * matrix[1].xyz;
    dst += src.zzz * matrix[2].xyz;
    return dst;
    }

    inline void generate_basis(float3 inR, thread float3 *outS, thread float3 *outT)
    {
    float3 dir = abs(inR.z) < 0.999 ? float3(0, 0, 1) : float3(1, 0, 0);
    *outS = normalize(cross(dir, inR));
    *outT = cross(inR, *outS);
    }

    // MARK: Drawing quads

    struct draw_quad_io_t {
    float4 position [[ position ]];
    float2 uv;
    };

    }

    inline float4 texture2DProj(texture2d<float> tex, sampler smp, float4 uv)
    {
    return tex.sample(smp, uv.xy / uv.w);
    }

    inline float shadow2DProj(depth2d<float> tex, float4 uv)
    {
    constexpr sampler linear_sampler(filter::linear, mip_filter::none, compare_func::greater_equal);
    //constexpr sampler linear_sampler(filter::linear, mip_filter::none, compare_func::none);
    float3 uvp = uv.xyz / uv.w;
    return tex.sample_compare(linear_sampler, uvp.xy, uvp.z);
    }



    // Inputs

    typedef struct {

    #ifdef USE_MODELTRANSFORM
    float4x4 modelTransform;
    #endif
    #ifdef USE_INVERSEMODELTRANSFORM
    float4x4 inverseModelTransform;
    #endif
    #ifdef USE_MODELVIEWTRANSFORM
    float4x4 modelViewTransform;
    #endif
    #ifdef USE_INVERSEMODELVIEWTRANSFORM
    float4x4 inverseModelViewTransform;
    #endif
    #ifdef USE_NORMALTRANSFORM
    float4x4 normalTransform;
    #endif
    #ifdef USE_MODELVIEWPROJECTIONTRANSFORM
    float4x4 modelViewProjectionTransform;
    #endif
    #ifdef USE_INVERSEMODELVIEWPROJECTIONTRANSFORM
    float4x4 inverseModelViewProjectionTransform;
    #endif
    #ifdef USE_BOUNDINGBOX
    float2x3 boundingBox;
    #endif
    #ifdef USE_WORLDBOUNDINGBOX
    float2x3 worldBoundingBox;
    #endif
    #ifdef USE_NODE_OPACITY
    float nodeOpacity;
    #endif
    #ifdef USE_DOUBLE_SIDED
    float orientationPreserved;
    #endif
    #if defined(USE_PROBES_LIGHTING) && (USE_PROBES_LIGHTING == 2)
    sh2_coefficients shCoefficients;
    #elif defined(USE_PROBES_LIGHTING) && (USE_PROBES_LIGHTING == 3)
    sh3_coefficients shCoefficients;
    #endif
    #ifdef USE_SKINNING // need to be last since we may cut the buffer size based on the real bone number
    float4 skinningJointMatrices[765]; // Consider having a separate buffer ?
    #endif
    } commonprofile_node;

    typedef struct {
    float3 position [[attribute(SCNVertexSemanticPosition)]];
    float3 normal [[attribute(SCNVertexSemanticNormal)]];
    float4 tangent [[attribute(SCNVertexSemanticTangent)]];
    float4 color [[attribute(SCNVertexSemanticColor)]];
    float4 skinningWeights [[attribute(SCNVertexSemanticBoneWeights)]];
    uint4 skinningJoints [[attribute(SCNVertexSemanticBoneIndices)]];
    float2 texcoord0 [[attribute(SCNVertexSemanticTexcoord0)]];
    float2 texcoord1 [[attribute(SCNVertexSemanticTexcoord1)]];
    float2 texcoord2 [[attribute(SCNVertexSemanticTexcoord2)]];
    float2 texcoord3 [[attribute(SCNVertexSemanticTexcoord3)]];
    float2 texcoord4 [[attribute(SCNVertexSemanticTexcoord4)]];
    float2 texcoord5 [[attribute(SCNVertexSemanticTexcoord5)]];
    float2 texcoord6 [[attribute(SCNVertexSemanticTexcoord6)]];
    float2 texcoord7 [[attribute(SCNVertexSemanticTexcoord7)]];
    } scn_vertex_t; // __attribute__((scn_per_frame));

    typedef struct {
    float4 fragmentPosition [[position]]; // The window relative coordinate (x, y, z, 1/w) values for the fragment
    #ifdef USE_POINT_RENDERING
    float fragmentSize [[point_size]];
    #endif
    #ifdef USE_VERTEX_COLOR
    float4 vertexColor;
    #endif
    #ifdef USE_PER_VERTEX_LIGHTING
    float3 diffuse;
    #ifdef USE_SPECULAR
    float3 specular;
    #endif
    #endif
    #if defined(USE_POSITION) && (USE_POSITION == 2)
    float3 position;
    #endif
    #if defined(USE_NORMAL) && (USE_NORMAL == 2)
    float3 normal;
    #endif
    #if defined(USE_TANGENT) && (USE_TANGENT == 2)
    float3 tangent;
    #endif
    #if defined(USE_BITANGENT) && (USE_BITANGENT == 2)
    float3 bitangent;
    #endif
    #ifdef USE_NODE_OPACITY
    float nodeOpacity;
    #endif
    #ifdef USE_DOUBLE_SIDED
    float orientationPreserved;
    #endif
    #ifdef USE_TEXCOORD

    #endif

    } commonprofile_io;

    struct SCNShaderSurface {
    float3 view; // Direction from the point on the surface toward the camera (V)
    float3 position; // Position of the fragment
    float3 normal; // Normal of the fragment (N)
    float3 geometryNormal; // Normal of the fragment - not taking into account normal map
    float2 normalTexcoord; // Normal texture coordinates
    float3 tangent; // Tangent of the fragment
    float3 bitangent; // Bitangent of the fragment
    float4 ambient; // Ambient property of the fragment
    float2 ambientTexcoord; // Ambient texture coordinates
    float4 diffuse; // Diffuse property of the fragment. Alpha contains the opacity.
    float2 diffuseTexcoord; // Diffuse texture coordinates
    float4 specular; // Specular property of the fragment
    float2 specularTexcoord; // Specular texture coordinates
    float4 emission; // Emission property of the fragment
    float2 emissionTexcoord; // Emission texture coordinates
    float4 multiply; // Multiply property of the fragment
    float2 multiplyTexcoord; // Multiply texture coordinates
    float4 transparent; // Transparent property of the fragment
    float2 transparentTexcoord; // Transparent texture coordinates
    float4 reflective; // Reflective property of the fragment
    float metalness; // Metalness
    float2 metalnessTexcoord; // Metalness texture coordinates
    float roughness; // Roughness
    float2 roughnessTexcoord; // Metalness texture coordinates
    float shininess; // Shininess property of the fragment.
    float fresnel; // Fresnel property of the fragment.
    float ambientOcclusion; // Ambient occlusion term of the fragment
    float3 _normalTS; // UNDOCUMENTED in tangent space
    #ifdef USE_SURFACE_EXTRA_DECL

    #endif
    };

    struct SCNShaderLightingContribution {
    float3 ambient;
    float3 diffuse;
    float3 specular;
    float3 modulate;
    };



    // Structure to gather property of a light, packed to give access in a light shader modifier
    struct SCNShaderLight {
    float4 intensity; // lowp, light intensity
    float3 direction; // mediump, vector from the point toward the light
    float _att;
    float3 _spotDirection; // lowp, vector from the point to the light for point and spot, dist attenuations
    float _distance; // mediump, distance from the point to the light (same coord. than range)
    };

    #ifdef USE_PBR

    inline SCNPBRSurface SCNShaderSurfaceToSCNPBRSurface(SCNShaderSurface surface)
    {
    SCNPBRSurface s;

    s.n = surface.normal;
    s.v = surface.view;
    s.albedo = surface.diffuse.xyz;

    #ifdef USE_EMISSION
    s.emission = surface.emission.xyz;
    #else
    s.emission = float3(0.);
    #endif

    s.metalness = surface.metalness;
    s.roughness = surface.roughness;
    s.ao = surface.ambientOcclusion;
    return s;
    }

    static float4 scn_pbr_combine(SCNPBRSurface pbr_surface,
    SCNShaderLightingContribution lighting,
    texture2d<float, access::sample> specularDFG,
    texturecube<float, access::sample> specularLD,
    #ifdef USE_PROBES_LIGHTING
    #if defined(USE_PROBES_LIGHTING) && (USE_PROBES_LIGHTING == 2)
    sh2_coefficients shCoefficients,
    #elif defined(USE_PROBES_LIGHTING) && (USE_PROBES_LIGHTING == 3)
    sh3_coefficients shCoefficients,
    #endif
    #else
    texturecube<float, access::sample> irradiance,
    #endif
    constant SCNSceneBuffer& scn_frame)
    {
    #ifdef USE_PROBES_LIGHTING
    float3 pbr_color = scn_pbr_color_IBL(pbr_surface, specularDFG, specularLD, shCoefficients, scn_frame.viewToCubeTransform, scn_frame.environmentIntensity);
    #else
    float3 pbr_color = scn_pbr_color_IBL(pbr_surface, specularDFG, specularLD, irradiance, scn_frame.viewToCubeTransform, scn_frame.environmentIntensity);
    #endif

    float4 color;
    color.rgb = (lighting.ambient * pbr_surface.ao + lighting.diffuse) * pbr_surface.albedo.rgb + lighting.specular + pbr_color;

    #if defined(USE_EMISSION) && !defined(USE_EMISSION_AS_SELFILLUMINATION)
    color.rgb += pbr_surface.emission.rgb;
    #endif

    return color;
    }

    static void scn_pbr_lightingContribution(SCNShaderSurface surface,
    SCNShaderLight light,
    constant SCNSceneBuffer& scn_frame,
    thread float3& lightingContributionDiffuse,
    thread float3& lightingContributionSpecular)
    {
    SCNPBRSurface pbr_surface = SCNShaderSurfaceToSCNPBRSurface(surface);

    float3 diffuseOut, specularOut;
    scn_pbr_lightingContribution_pointLight(light.direction, pbr_surface.n, pbr_surface.v, pbr_surface.albedo, pbr_surface.metalness, pbr_surface.roughness, diffuseOut, specularOut);

    float3 lightFactor = light.intensity.rgb * light._att;
    lightingContributionDiffuse += diffuseOut * lightFactor;
    lightingContributionSpecular += specularOut * lightFactor;
    }

    #else // ifdef USE_PBR

    inline float4 illuminate(SCNShaderSurface surface, SCNShaderLightingContribution lighting)
    {
    float4 color = {0.,0.,0., surface.diffuse.a};

    float3 D = lighting.diffuse;

    #if defined(USE_AMBIENT_LIGHTING) && (defined(LOCK_AMBIENT_WITH_DIFFUSE) || defined(USE_AMBIENT_AS_AMBIENTOCCLUSION))
    D += lighting.ambient * surface.ambientOcclusion;
    #endif

    #ifdef USE_EMISSION_AS_SELFILLUMINATION
    D += surface.emission.rgb;
    #endif

    // Do we want to clamp there ????

    color.rgb = surface.diffuse.rgb * D;
    #ifdef USE_SPECULAR
    float3 S = lighting.specular;
    #elif defined(USE_REFLECTIVE)
    float3 S = float3(0.);
    #endif
    #ifdef USE_REFLECTIVE
    S += surface.reflective.rgb * surface.ambientOcclusion;
    #endif
    #ifdef USE_SPECULAR
    S *= surface.specular.rgb;
    #endif
    #if defined(USE_SPECULAR) || defined(USE_REFLECTIVE)
    color.rgb += S;
    #endif
    #if defined(USE_AMBIENT) && !defined(USE_AMBIENT_AS_AMBIENTOCCLUSION)
    color.rgb += surface.ambient.rgb * lighting.ambient;
    #endif
    #if defined(USE_EMISSION) && !defined(USE_EMISSION_AS_SELFILLUMINATION)
    color.rgb += surface.emission.rgb;
    #endif
    #ifdef USE_MULTIPLY
    color.rgb *= surface.multiply.rgb;
    #endif
    #ifdef USE_MODULATE
    color.rgb *= lighting.modulate;
    #endif
    return color;
    }
    #endif

    struct commonprofile_lights {
    #ifdef USE_LIGHTING
    float4 color0;
    float4 position0;

    #endif
    };


    struct SCNShaderGeometry
    {
    float4 position;
    float3 normal;
    float4 tangent;
    float4 color;
    float2 texcoords[8]; // MAX_UV
    };

    struct commonprofile_uniforms {
    float4 diffuseColor;
    float4 specularColor;
    float4 ambientColor;
    float4 emissionColor;
    float4 reflectiveColor;
    float4 multiplyColor;
    float4 transparentColor;
    float metalness;
    float roughness;

    float diffuseIntensity;
    float specularIntensity;
    float normalIntensity;
    float ambientIntensity;
    float emissionIntensity;
    float reflectiveIntensity;
    float multiplyIntensity;
    float transparentIntensity;
    float metalnessIntensity;
    float roughnessIntensity;

    float materialShininess;
    float selfIlluminationOcclusion;
    float transparency;
    float3 fresnel; // x: ((n1-n2)/(n1+n2))^2 y:1-x z:exponent

    #ifdef TEXTURE_TRANSFORM_COUNT
    float4x4 textureTransforms[TEXTURE_TRANSFORM_COUNT];
    #endif

    #if defined(USE_REFLECTIVE_CUBEMAP)
    // float4x4 u_viewToCubeWorld;
    #endif
    };

    // Shader modifiers declaration (only enabled if one modifier is present)
    #ifdef USE_SHADER_MODIFIERS
    // initial geometry is [-1,1] in XY plane (so z is always 0)
    struct scn_floor_t {
    float3 u_floorNormal;
    float4 u_floorTangent;
    float3 u_floorCenter;
    float2 u_floorExtent;
    };

    #endif

    // Vertex shader function

    vertex commonprofile_io commonprofile_vert(scn_vertex_t in [[ stage_in ]],
    constant SCNSceneBuffer& scn_frame [[buffer(0)]],
    #ifdef USE_INSTANCING
    // we use device here to override the 64Ko limit of constant buffers on NV hardware
    device commonprofile_node* scn_nodeInstances [[buffer(1)]]
    , uint instanceID [[ instance_id ]]
    #else
    constant commonprofile_node& scn_node [[buffer(1)]]
    #endif
    #ifdef USE_PER_VERTEX_LIGHTING
    , constant commonprofile_lights& scn_lights [[buffer(2)]]
    #endif
    // used for texture transform and materialShininess in case of perVertexLighting
    , constant commonprofile_uniforms& scn_commonprofile [[buffer(3)]]
    #ifdef USE_VERTEX_EXTRA_ARGUMENTS
    , constant scn_floor_t& scn_fg [[buffer(4)]]

    #endif
    )
    {
    #ifdef USE_INSTANCING
    device commonprofile_node& scn_node = scn_nodeInstances[instanceID];
    #endif

    SCNShaderGeometry _geometry;
    // OPTIM in could be already float4?
    _geometry.position = float4(in.position, 1.0);
    #ifdef USE_NORMAL
    _geometry.normal = in.normal;
    #endif
    #if defined(USE_TANGENT) || defined(USE_BITANGENT)
    _geometry.tangent = in.tangent;
    #endif
    #ifdef NEED_IN_TEXCOORD0
    _geometry.texcoords[0] = in.texcoord0;
    #endif
    #ifdef NEED_IN_TEXCOORD1
    _geometry.texcoords[1] = in.texcoord1;
    #endif
    #ifdef NEED_IN_TEXCOORD2
    _geometry.texcoords[2] = in.texcoord2;
    #endif
    #ifdef NEED_IN_TEXCOORD3
    _geometry.texcoords[3] = in.texcoord3;
    #endif
    #ifdef NEED_IN_TEXCOORD4
    _geometry.texcoords[4] = in.texcoord4;
    #endif
    #ifdef NEED_IN_TEXCOORD5
    _geometry.texcoords[5] = in.texcoord5;
    #endif
    #ifdef NEED_IN_TEXCOORD6
    _geometry.texcoords[6] = in.texcoord6;
    #endif
    #ifdef NEED_IN_TEXCOORD7
    _geometry.texcoords[7] = in.texcoord7;
    #endif
    #ifdef HAS_VERTEX_COLOR
    _geometry.color = in.color;
    #elif USE_VERTEX_COLOR
    _geometry.color = float4(1.);
    #endif

    #ifdef USE_SKINNING
    #if 0 // Alternate Skinning method, linear combining the joint matrices. Not fully tested yet
    {
    float4 joint0 = 0.;
    float4 joint1 = 0.;
    float4 joint2 = 0.;
    uint4 idx3 = in.skinningJoints * 3;
    for (int i = 0; i < MAX_BONE_INFLUENCES; ++i) {
    int idx = idx3[i];
    #if MAX_BONE_INFLUENCES == 1
    float boneWeight = 1.;
    #else
    float boneWeight = in.skinningWeights[i];
    #endif
    joint0 += boneWeight * scn_node.skinningJointMatrices[idx];
    joint1 += boneWeight * scn_node.skinningJointMatrices[idx+1];
    joint2 += boneWeight * scn_node.skinningJointMatrices[idx+2];
    }
    float4x4 jointMatrix = float4x4(joint0, joint1, joint2, float4(0., 0., 0., 1.));
    _geometry.position.xyz = (_geometry.position * jointMatrix).xyz;
    #ifdef USE_NORMAL
    _geometry.normal = _geometry.normal * scn::mat3(jointMatrix);
    #endif
    #if defined(USE_TANGENT) || defined(USE_BITANGENT)
    _geometry.tangent.xyz = _geometry.tangent.xyz * scn::mat3(jointMatrix);
    #endif
    }
    #else
    {
    float3 pos = 0.0;
    #ifdef USE_NORMAL
    float3 nrm = 0.0;
    #endif
    #if defined(USE_TANGENT) || defined(USE_BITANGENT)
    float3 tgt = 0.0;
    #endif
    for (int i = 0; i < MAX_BONE_INFLUENCES; ++i) {
    #if MAX_BONE_INFLUENCES == 1
    float weight = 1.0;
    #else
    float weight = in.skinningWeights[i];
    if (weight <= 0.f)
    continue;

    #endif
    int idx = int(in.skinningJoints[i]) * 3;
    float4x4 jointMatrix = float4x4(scn_node.skinningJointMatrices[idx],
    scn_node.skinningJointMatrices[idx+1],
    scn_node.skinningJointMatrices[idx+2],
    float4(0., 0., 0., 1.));

    pos += (_geometry.position * jointMatrix).xyz * weight;
    #ifdef USE_NORMAL
    nrm += _geometry.normal * scn::mat3(jointMatrix) * weight;
    #endif
    #if defined(USE_TANGENT) || defined(USE_BITANGENT)
    tgt += _geometry.tangent.xyz * scn::mat3(jointMatrix) * weight;
    #endif
    }

    _geometry.position.xyz = pos;
    #ifdef USE_NORMAL
    _geometry.normal = nrm;
    #endif
    #if defined(USE_TANGENT) || defined(USE_BITANGENT)
    _geometry.tangent.xyz = tgt;
    #endif
    }
    #endif
    #endif // USE_SKINNING

    #ifdef USE_GEOMETRY_MODIFIER
    // DoGeometryModifier START
    float3 u_floorNormal = scn_fg.u_floorNormal;
    float4 u_floorTangent = scn_fg.u_floorTangent;
    float3 u_floorCenter = scn_fg.u_floorCenter;
    float2 u_floorExtent = scn_fg.u_floorExtent;
    float3 floorBitangent = normalize(cross(u_floorTangent.xyz, u_floorNormal));
    _geometry.position.xyz = u_floorCenter.xyz + u_floorExtent.x * (_geometry.position.x * u_floorTangent.xyz) + u_floorExtent.y * (_geometry.position.y * floorBitangent);
    _geometry.normal = u_floorNormal;
    _geometry.tangent = u_floorTangent;
    // we could check if the texCoord are really needed with ifdef USE_xxxx_MAP , or, better, work only on texcoordN [0..1]
    float2 tc;
    if (u_floorNormal.y != 0.)
    tc = _geometry.position.xz * 0.01;
    else if (u_floorNormal.z != 0.)
    tc = _geometry.position.xy * 0.01;
    else
    tc = _geometry.position.yz * 0.01;
    for (int i = 0; i < kSCNTexcoordCount; ++i)
    _geometry.texcoords[i] = tc;


    // DoGeometryModifier END
    #endif

    // Transform the geometry elements in view space
    #if defined(USE_POSITION) || defined(USE_NORMAL) || defined(USE_TANGENT) || defined(USE_BITANGENT) || defined(USE_INSTANCING)
    SCNShaderSurface _surface;
    #endif
    #if defined(USE_POSITION) || defined(USE_INSTANCING)
    _surface.position = (scn_node.modelViewTransform * _geometry.position).xyz;
    #endif
    #ifdef USE_NORMAL
    _surface.normal = normalize(scn::mat3(scn_node.normalTransform) * _geometry.normal);
    #endif
    #if defined(USE_TANGENT) || defined(USE_BITANGENT)
    _surface.tangent = normalize(scn::mat3(scn_node.normalTransform) * _geometry.tangent.xyz);
    _surface.bitangent = _geometry.tangent.w * cross(_surface.tangent, _surface.normal); // no need to renormalize since tangent and normal should be orthogonal
    // old code : _surface.bitangent = normalize(cross(_surface.normal,_surface.tangent));
    #endif

    //if USE_VIEW is 2 we may also need to set _surface.view. todo: make USE_VIEW a mask
    #ifdef USE_VIEW
    _surface.view = normalize(-_surface.position);
    #endif

    commonprofile_io out;

    #ifdef USE_PER_VERTEX_LIGHTING
    // Lighting
    SCNShaderLightingContribution _lightingContribution;
    _lightingContribution.diffuse = 0.;
    #ifdef USE_SPECULAR
    _lightingContribution.specular = 0.;
    _surface.shininess = scn_commonprofile.materialShininess;
    #endif

    out.diffuse = _lightingContribution.diffuse;
    #ifdef USE_SPECULAR
    out.specular = _lightingContribution.specular;
    #endif
    #endif

    #if defined(USE_POSITION) && (USE_POSITION == 2)
    out.position = _surface.position;
    #endif
    #if defined(USE_NORMAL) && (USE_NORMAL == 2)
    out.normal = _surface.normal;
    #endif
    #if defined(USE_TANGENT) && (USE_TANGENT == 2)
    out.tangent = _surface.tangent;
    #endif
    #if defined(USE_BITANGENT) && (USE_BITANGENT == 2)
    out.bitangent = _surface.bitangent;
    #endif
    #ifdef USE_VERTEX_COLOR
    out.vertexColor = _geometry.color;
    #endif
    #ifdef USE_TEXCOORD

    #endif

    #if defined(USE_POSITION) || defined(USE_INSTANCING)
    out.fragmentPosition = scn_frame.projectionTransform * float4(_surface.position, 1.);
    #elif defined(USE_MODELVIEWPROJECTIONTRANSFORM) // this means that the geometry are still in model space : we can transform it directly to NDC space
    out.fragmentPosition = scn_node.modelViewProjectionTransform * _geometry.position;
    #endif
    #ifdef USE_NODE_OPACITY
    out.nodeOpacity = scn_node.nodeOpacity;
    #endif
    #ifdef USE_DOUBLE_SIDED
    out.orientationPreserved = scn_node.orientationPreserved;
    #endif
    #ifdef USE_POINT_RENDERING
    out.fragmentSize = 1.;
    #endif
    return out;
    }

    struct SCNOutput
    {
    float4 color;
    };

    // Fragment shader function
    fragment half4 commonprofile_frag(commonprofile_io in [[stage_in]],
    constant commonprofile_uniforms& scn_commonprofile [[buffer(0)]],
    constant SCNSceneBuffer& scn_frame [[buffer(1)]]
    #ifdef USE_PER_PIXEL_LIGHTING
    , constant commonprofile_lights& scn_lights [[buffer(2)]]
    #endif
    #ifdef USE_EMISSION_MAP
    , texture2d<float> u_emissionTexture [[texture(0)]]
    , sampler u_emissionTextureSampler [[sampler(0)]]
    #endif
    #ifdef USE_AMBIENT_MAP
    , texture2d<float> u_ambientTexture [[texture(1)]]
    , sampler u_ambientTextureSampler [[sampler(1)]]
    #endif
    #ifdef USE_DIFFUSE_MAP
    , texture2d<float> u_diffuseTexture [[texture(2)]]
    , sampler u_diffuseTextureSampler [[sampler(2)]]
    #endif
    #ifdef USE_SPECULAR_MAP
    , texture2d<float> u_specularTexture [[texture(3)]]
    , sampler u_specularTextureSampler [[sampler(3)]]
    #endif
    #ifdef USE_REFLECTIVE_MAP
    , texture2d<float> u_reflectiveTexture [[texture(4)]]
    , sampler u_reflectiveTextureSampler [[sampler(4)]]
    #elif defined(USE_REFLECTIVE_CUBEMAP)
    , texturecube<float> u_reflectiveTexture [[texture(4)]]
    , sampler u_reflectiveTextureSampler [[sampler(4)]]
    #endif
    #ifdef USE_TRANSPARENT_MAP
    , texture2d<float> u_transparentTexture [[texture(5)]]
    , sampler u_transparentTextureSampler [[sampler(5)]]
    #endif
    #ifdef USE_MULTIPLY_MAP
    , texture2d<float> u_multiplyTexture [[texture(6)]]
    , sampler u_multiplyTextureSampler [[sampler(6)]]
    #endif
    #ifdef USE_NORMAL_MAP
    , texture2d<float> u_normalTexture [[texture(7)]]
    , sampler u_normalTextureSampler [[sampler(7)]]
    #endif
    #ifdef USE_PBR
    #ifdef USE_METALNESS_MAP
    , texture2d<float> u_metalnessTexture [[texture(3)]]
    , sampler u_metalnessTextureSampler [[sampler(3)]]
    #endif
    #ifdef USE_ROUGHNESS_MAP
    , texture2d<float> u_roughnessTexture [[texture(4)]]
    , sampler u_roughnessTextureSampler [[sampler(4)]]
    #endif
    , texturecube<float> u_irradianceTexture [[texture(8)]]
    , texturecube<float> u_radianceTexture [[texture(9)]]
    , texture2d<float> u_specularDFGTexture [[texture(10)]]
    #endif
    #ifdef USE_SSAO
    , texture2d<float> u_ssaoTexture [[texture(11)]]
    #endif
    , constant commonprofile_node& scn_node [[buffer(3)]]
    #ifdef USE_FRAGMENT_EXTRA_ARGUMENTS

    #endif
    #if defined(USE_DOUBLE_SIDED)
    , bool isFrontFacing [[front_facing]]
    #endif
    )
    {
    SCNShaderSurface _surface;
    #ifdef USE_TEXCOORD

    #endif
    _surface.ambientOcclusion = 1.f; // default to no AO
    #ifdef USE_AMBIENT_MAP
    #ifdef USE_AMBIENT_AS_AMBIENTOCCLUSION
    _surface.ambientOcclusion = u_ambientTexture.sample(u_ambientTextureSampler, _surface.ambientTexcoord).r;
    #ifdef USE_AMBIENT_INTENSITY
    _surface.ambientOcclusion = saturate(mix(1., _surface.ambientOcclusion, scn_commonprofile.ambientIntensity));
    #endif
    #else // AMBIENT_MAP
    _surface.ambient = u_ambientTexture.sample(u_ambientTextureSampler, _surface.ambientTexcoord);
    #ifdef USE_AMBIENT_INTENSITY
    _surface.ambient *= scn_commonprofile.ambientIntensity;
    #endif
    #endif // USE_AMBIENT_AS_AMBIENTOCCLUSION
    #elif defined(USE_AMBIENT_COLOR)
    _surface.ambient = scn_commonprofile.ambientColor;
    #elif defined(USE_AMBIENT)
    _surface.ambient = float4(0.);
    #endif
    #if defined(USE_AMBIENT) && defined(USE_VERTEX_COLOR)
    _surface.ambient *= in.vertexColor;
    #endif
    #if defined(USE_SSAO)
    _surface.ambientOcclusion *= u_ssaoTexture.sample( sampler(filter::linear), in.fragmentPosition.xy * scn_frame.inverseResolution.xy ).x;
    #endif

    #ifdef USE_DIFFUSE_MAP
    _surface.diffuse = u_diffuseTexture.sample(u_diffuseTextureSampler, _surface.diffuseTexcoord);
    #ifdef USE_DIFFUSE_INTENSITY
    _surface.diffuse.rgb *= scn_commonprofile.diffuseIntensity;
    #endif
    #elif defined(USE_DIFFUSE_COLOR)
    _surface.diffuse = scn_commonprofile.diffuseColor;
    #else
    _surface.diffuse = float4(0.,0.,0.,1.);
    #endif
    #if defined(USE_DIFFUSE) && defined(USE_VERTEX_COLOR)
    _surface.diffuse *= in.vertexColor;
    #endif
    #ifdef USE_SPECULAR_MAP
    _surface.specular = u_specularTexture.sample(u_specularTextureSampler, _surface.specularTexcoord);
    #ifdef USE_SPECULAR_INTENSITY
    _surface.specular *= scn_commonprofile.specularIntensity;
    #endif
    #elif defined(USE_SPECULAR_COLOR)
    _surface.specular = scn_commonprofile.specularColor;
    #elif defined(USE_SPECULAR)
    _surface.specular = float4(0.);
    #endif
    #ifdef USE_EMISSION_MAP
    _surface.emission = u_emissionTexture.sample(u_emissionTextureSampler, _surface.emissionTexcoord);
    #ifdef USE_EMISSION_INTENSITY
    _surface.emission *= scn_commonprofile.emissionIntensity;
    #endif
    #elif defined(USE_EMISSION_COLOR)
    _surface.emission = scn_commonprofile.emissionColor;
    #elif defined(USE_EMISSION)
    _surface.emission = float4(0.);
    #endif
    #ifdef USE_MULTIPLY_MAP
    _surface.multiply = u_multiplyTexture.sample(u_multiplyTextureSampler, _surface.multiplyTexcoord);
    #ifdef USE_MULTIPLY_INTENSITY
    _surface.multiply = mix(float4(1.), _surface.multiply, scn_commonprofile.multiplyIntensity);
    #endif
    #elif defined(USE_MULTIPLY_COLOR)
    _surface.multiply = scn_commonprofile.multiplyColor;
    #elif defined(USE_MULTIPLY)
    _surface.multiply = float4(1.);
    #endif
    #ifdef USE_TRANSPARENT_MAP
    _surface.transparent = u_transparentTexture.sample(u_transparentTextureSampler, _surface.transparentTexcoord);
    #ifdef USE_TRANSPARENT_INTENSITY
    _surface.transparent *= scn_commonprofile.transparentIntensity;
    #endif
    #elif defined(USE_TRANSPARENT_COLOR)
    _surface.transparent = scn_commonprofile.transparentColor;
    #elif defined(USE_TRANSPARENT)
    _surface.transparent = float4(1.);
    #endif

    #ifdef USE_METALNESS_MAP
    _surface.metalness = u_metalnessTexture.sample(u_metalnessTextureSampler, _surface.metalnessTexcoord).r;
    #ifdef USE_METALNESS_INTENSITY
    _surface.metalness *= scn_commonprofile.metalnessIntensity;
    #endif
    #elif defined(USE_METALNESS_COLOR)
    _surface.metalness = scn_commonprofile.metalness;
    #else
    _surface.metalness = 0;
    #endif

    #ifdef USE_ROUGHNESS_MAP
    _surface.roughness = u_roughnessTexture.sample(u_roughnessTextureSampler, _surface.roughnessTexcoord).r;
    #ifdef USE_ROUGHNESS_INTENSITY
    _surface.roughness *= scn_commonprofile.roughnessIntensity;
    #endif
    #elif defined(USE_ROUGHNESS_COLOR)
    _surface.roughness = scn_commonprofile.roughness;
    #else
    _surface.roughness = 0;
    #endif

    #if (defined USE_NORMAL) && (USE_NORMAL == 2)
    #ifdef USE_DOUBLE_SIDED
    _surface.geometryNormal = normalize(in.normal.xyz) * in.orientationPreserved * ((float(isFrontFacing) * 2.0) - 1.0);
    #else
    _surface.geometryNormal = normalize(in.normal.xyz);
    #endif
    _surface.normal = _surface.geometryNormal;
    #endif
    #if defined(USE_TANGENT) && (USE_TANGENT == 2)
    _surface.tangent = in.tangent;
    #endif
    #if defined(USE_BITANGENT) && (USE_BITANGENT == 2)
    _surface.bitangent = in.bitangent;
    #endif
    #if (defined USE_POSITION) && (USE_POSITION == 2)
    _surface.position = in.position;
    #endif
    #if (defined USE_VIEW) && (USE_VIEW == 2)
    _surface.view = normalize(-in.position);
    #endif
    #ifdef USE_NORMAL_MAP
    float3x3 ts2vs = float3x3(_surface.tangent, _surface.bitangent, _surface.normal);
    _surface._normalTS = u_normalTexture.sample(u_normalTextureSampler, _surface.normalTexcoord).rgb * 2. - 1.;
    // _surface.normal.z = 1. - sqrt(_surface.normal.x * _surface.normal.x + _surface.normal.y * _surface.normal.y);
    #ifdef USE_NORMAL_INTENSITY
    _surface._normalTS = mix(float3(0., 0., 1.), _surface._normalTS, scn_commonprofile.normalIntensity);
    #endif
    // transform the normal in view space
    _surface.normal.rgb = normalize(ts2vs * _surface._normalTS);
    #else
    _surface._normalTS = float3(0.);
    #endif

    #ifdef USE_REFLECTIVE_MAP
    float3 refl = reflect( -_surface.view, _surface.normal );
    float m = 2.0 * sqrt( refl.x*refl.x + refl.y*refl.y + (refl.z+1.0)*(refl.z+1.0));
    _surface.reflective = u_reflectiveTexture.sample(u_reflectiveTextureSampler, float2(float2(refl.x,-refl.y) / m) + 0.5);
    #ifdef USE_REFLECTIVE_INTENSITY
    _surface.reflective *= scn_commonprofile.reflectiveIntensity;
    #endif
    #elif defined(USE_REFLECTIVE_CUBEMAP)
    float3 refl = reflect( _surface.position, _surface.normal );
    _surface.reflective = u_reflectiveTexture.sample(u_reflectiveTextureSampler, scn::mat4_mult_float3(scn_frame.viewToCubeTransform, refl)); // sample the cube map in world space
    #ifdef USE_REFLECTIVE_INTENSITY
    _surface.reflective *= scn_commonprofile.reflectiveIntensity;
    #endif
    #elif defined(USE_REFLECTIVE_COLOR)
    _surface.reflective = scn_commonprofile.reflectiveColor;
    #elif defined(USE_REFLECTIVE)
    _surface.reflective = float4(0.);
    #endif
    #ifdef USE_FRESNEL
    _surface.fresnel = scn_commonprofile.fresnel.x + scn_commonprofile.fresnel.y * pow(1.0 - saturate(dot(_surface.view, _surface.normal)), scn_commonprofile.fresnel.z);
    _surface.reflective *= _surface.fresnel;
    #endif
    #ifdef USE_SHININESS
    _surface.shininess = scn_commonprofile.materialShininess;
    #endif

    #ifdef USE_SURFACE_MODIFIER
    // DoSurfaceModifier START

    // DoSurfaceModifier END
    #endif
    // Lighting
    SCNShaderLightingContribution _lightingContribution = {0};


    // Lighting
    #ifdef USE_AMBIENT_LIGHTING
    _lightingContribution.ambient = scn_frame.ambientLightingColor.rgb;
    #endif

    #ifdef USE_LIGHTING
    #ifdef USE_PER_PIXEL_LIGHTING
    _lightingContribution.diffuse = float3(0.);
    #ifdef USE_MODULATE
    _lightingContribution.modulate = float3(1.);
    #endif
    #ifdef USE_SPECULAR
    _lightingContribution.specular = float3(0.);
    #endif
    {
    SCNShaderLight _light;
    _light.intensity = scn_lights.color0;
    _light.direction = normalize(scn_lights.position0.xyz - _surface.position);
    _light._att = 1.;
    _light.intensity.rgb *= _light._att * max(0.f, dot(_surface.normal, _light.direction));
    _lightingContribution.diffuse += _light.intensity.rgb;
    }

    #else // USE_PER_PIXEL_LIGHTING
    _lightingContribution.diffuse = in.diffuse;
    #ifdef USE_SPECULAR
    _lightingContribution.specular = in.specular;
    #endif
    #endif
    #ifdef AVOID_OVERLIGHTING
    _lightingContribution.diffuse = saturate(_lightingContribution.diffuse);
    #ifdef USE_SPECULAR
    _lightingContribution.specular = saturate(_lightingContribution.specular);
    #endif // USE_SPECULAR
    #endif // AVOID_OVERLIGHTING
    #else // USE_LIGHTING
    _lightingContribution.diffuse = float3(1.);
    #endif // USE_LIGHTING

    // Combine
    SCNOutput _output;
    #ifdef USE_PBR
    SCNPBRSurface pbr_surface = SCNShaderSurfaceToSCNPBRSurface(_surface);
    pbr_surface.selfIlluminationOcclusion = scn_commonprofile.selfIlluminationOcclusion;
    #ifdef USE_PROBES_LIGHTING
    _output.color = scn_pbr_combine(pbr_surface, _lightingContribution, u_specularDFGTexture, u_radianceTexture, scn_node.shCoefficients, scn_frame);
    #else
    _output.color = scn_pbr_combine(pbr_surface, _lightingContribution, u_specularDFGTexture, u_radianceTexture, u_irradianceTexture, scn_frame);
    #endif
    _output.color.a = _surface.diffuse.a;
    #else
    _output.color = illuminate(_surface, _lightingContribution);
    #endif

    #ifdef USE_FOG
    float fogFactor = pow(clamp(length(_surface.position.xyz) * scn_frame.fogParameters.x + scn_frame.fogParameters.y, 0., scn_frame.fogColor.a), scn_frame.fogParameters.z);
    _output.color.rgb = mix(_output.color.rgb, scn_frame.fogColor.rgb * _output.color.a, fogFactor);
    #endif

    #ifndef DIFFUSE_PREMULTIPLIED
    _output.color.rgb *= _surface.diffuse.a;
    #endif

    #ifdef USE_TRANSPARENT // Either a map or a color

    #ifdef USE_TRANSPARENCY
    _surface.transparent *= scn_commonprofile.transparency;
    #endif

    #ifdef USE_TRANSPARENCY_RGBZERO
    #ifdef USE_NODE_OPACITY
    _output.color *= in.nodeOpacity;
    #endif
    // compute luminance
    _surface.transparent.a = (_surface.transparent.r * 0.212671) + (_surface.transparent.g * 0.715160) + (_surface.transparent.b * 0.072169);
    _output.color *= (float4(1.) - _surface.transparent);
    #else // ALPHA_ONE
    #ifdef USE_NODE_OPACITY
    _output.color *= (in.nodeOpacity * _surface.transparent.a);
    #else
    _output.color *= _surface.transparent.a;
    #endif
    #endif
    #else
    #ifdef USE_TRANSPARENCY // TRANSPARENCY without TRANSPARENT slot (nodeOpacity + diffuse.a)
    #ifdef USE_NODE_OPACITY
    _output.color *= (in.nodeOpacity * scn_commonprofile.transparency);
    #else
    _output.color *= scn_commonprofile.transparency;
    #endif // NODE_OPACITY
    #endif
    #endif

    #ifdef USE_FRAGMENT_MODIFIER
    // DoFragmentModifier START

    // DoFragmentModifier END
    #endif

    //#ifdef USE_SSAO
    // _output.color.rgb = float3(_surface.ambientOcclusion);
    //#endif

    #ifdef USE_DISCARD
    if (_output.color.a == 0.) // we could set a different limit here
    discard_fragment();
    #endif

    return half4(_output.color);
    }