Skip to content

Instantly share code, notes, and snippets.

@warrenm
Created November 14, 2019 01:29
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save warrenm/794e459e429daa8c75b5f17c000600cf to your computer and use it in GitHub Desktop.
Save warrenm/794e459e429daa8c75b5f17c000600cf to your computer and use it in GitHub Desktop.
SceneKit's CommonProfile Shader v2 (macOS 10.15)
////////////////////////////////////////////////
// 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)
float4x4 lastFrameViewProjectionTransform;
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;
float motionBlurIntensity;
// new in macOS 10.12 and iOS 10
float environmentIntensity;
float4x4 inverseProjectionTransform;
float4x4 inverseViewProjectionTransform;
// new in macOS 10.13 and iOS 11
float2 nearFar; // x: near, y: far
float4 viewportSize; // xy:size, zw:origin
// new in macOS 10.14 and iOS 12
float4x4 inverseTransposeViewTransform;
// internal, DO NOT USE
float4 clusterScale; // w contains z bias
};
// 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
//
// Helper for compute kernels
#if defined(__METAL_IOS__)
#define RETURN_IF_OUTSIDE(dst) if ((index.x >= dst.get_width()) || (index.y >= dst.get_height())) return;
#define RETURN_IF_OUTSIDE3D(dst) if ((index.x >= dst.get_width()) || (index.y >= dst.get_height()) || (index.z >= dst.get_depth())) return;
#elif defined(__METAL_MACOS__) // On macOS, we use dispatchThread with won't execute on out of texture pixels
#define RETURN_IF_OUTSIDE(dst)
#define RETURN_IF_OUTSIDE3D(dst)
#endif
// Tool function
namespace scn {
// MARK: - Matrix/Vector utils
static inline float4 reduce_op(float4 d0, float4 d1)
{
d0.x = min(d0.x, d1.x);
d0.y = max(d0.y, d1.y);
d0.z += d1.z;
d0.w += d1.w;
return d0;
}
inline float vector_reduce_min(float4 v)
{
float2 min_lh = min(v.xy, v.zw);
return min(min_lh.x, min_lh.y);
}
inline float vector_reduce_max(float4 v)
{
float2 max_lh = max(v.xy, v.zw);
return max(max_lh.x, max_lh.y);
}
inline int vector_reduce_add(int4 v)
{
int2 add_lh = v.xy + v.zw;
return add_lh.x + add_lh.y;
}
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 float3 matrix_rotate(float4x4 mat, float3 dir)
{
return dir.xxx * mat[0].xyz +
dir.yyy * mat[1].xyz +
dir.zzz * mat[2].xyz;
}
inline float4 matrix_transform(float4x4 mat, float3 pos)
{
return pos.xxxx * mat[0] +
pos.yyyy * mat[1] +
pos.zzzz * mat[2] +
mat[3];
}
inline float3 quaternion_rotate_vector(float4 q, float3 v)
{
float3 t = 2.f * cross(q.xyz, v);
return v + q.w * t + cross(q.xyz, t);
}
// This seems unneeded with float. Maybe half ?
template <class T>
inline vec<T, 3> robust_normalize(vec<T, 3> v)
{
vec<T, 3> zero = 0.;
return all(v == zero) ? zero : normalize(v);
}
template <class T>
inline void generate_basis(vec<T, 3> inR, thread vec<T, 3> *outS, thread vec<T, 3> *outT)
{
// from http://marc-b-reynolds.github.io/quaternions/2016/07/06/Orthonormal.html
T x = -inR.x;
T y = inR.y;
T z = inR.z;
T sz = copysign(T(1.), z);
T a = y / (abs(z) + T(1.));
T b = y * a;
T c = x * a;
*outS = (vec<T, 3>){ z + sz * b, sz * c, x };
*outT = (vec<T, 3>){ c, T(1.) - b, -sz * y };
}
// MARK: - Blending operators
inline float3 blend_add(float3 base, float3 blend)
{
return min(base + blend, 1.0);
}
inline float3 blend_lighten(float3 base, float3 blend)
{
return max(blend, base);
}
inline float3 blend_screen(float3 base, float3 blend)
{
return (1.0 - ((1.0 - base) * (1.0 - blend)));
}
// MARK: - Math
inline half sq(half f) {
return f * f;
}
inline float sq(float f) {
return f * f;
}
inline float2 sincos(float angle) {
float cs;
float sn = ::sincos(angle, cs);
return float2(sn, cs);
}
// max error ~ 9.10-3
inline float acos_fast(float f) {
float x = abs(f);
float res = -0.156583f * x + M_PI_2_F;
res *= sqrt(1.0f - x);
return (f >= 0.f) ? res : M_PI_F - res;
}
inline float asin_fast(float f)
{
return M_PI_2_F - acos_fast(f);
}
// From Michal Drobot
inline float atan_fast(float inX)
{
float x = inX;
return x*(-0.1784f * abs(x) - 0.0663f * x * x + 1.0301f);
}
inline float atan2_fast(float y, float x)
{
float sx = x > 0.f ? -1.f : 1.f;
float abs_y = abs(y) + 1e-10f; // epsilon to prevent 0/0 condition
float r = (x + abs_y*sx) / (abs_y - x*sx);
float angle = sx * M_PI_4_F + M_PI_2_F;
angle += (0.1963f * r * r - 0.9817f) * r;
return y > 0.f ? angle : -angle;
}
// phi/theta are in the [0..1] range
template <class T>
inline vec<T, 3> cartesian_from_spherical(vec<T, 2> uv)
{
// do not use sinpi() waiting for
// <rdar://problem/28486742> sinpi(x) is 3x slower than sin(x * PI) on N71
T cos_phi;
T phi = uv.x * 2.0f * M_PI_F;
T sin_phi = ::sincos(phi, cos_phi);
T cos_theta;
T theta = uv.y * M_PI_F;
T sin_theta = ::sincos(theta, cos_theta);
return vec<T, 3>(cos_phi * sin_theta,
cos_theta,
-sin_phi * sin_theta);
}
inline float2 spherical_from_cartesian(float3 dir)
{
return float2( atan2(-dir.z, dir.x) * (0.5f * M_1_PI_F), acos(dir.y) * M_1_PI_F);
}
inline half2 spherical_from_cartesian(half3 dir)
{
return half2(atan2(-dir.z, dir.x) * 0.5h, acos(dir.y)) * M_1_PI_H;
}
inline float2 spherical_from_cartesian_fast(float3 dir)
{
return float2( atan2_fast(-dir.z, dir.x) * (0.5f * M_1_PI_F), acos_fast(dir.y) * M_1_PI_F);
}
inline half2 spherical_from_cartesian_fast(half3 dir)
{
return half2( atan2_fast(-dir.z, dir.x) * 0.5h, acos_fast(dir.y)) * M_1_PI_H;
}
#define dual_contract_factor 1.0
template <class T>
inline vec<T, 2> dual_paraboloid_from_cartesian(vec<T, 3> dir)
{
dir.xy /= abs(dir.z) + 1.0;
// dir.xy /= dual_contract_factor;
dir.y = 0.5 - dir.y * 0.5;
T s = sign(dir.z) * 0.25;
dir.x = s * (dir.x - 1.0) + 0.5;
return dir.xy;
}
// uv [0..1]
template <class T>
inline vec<T, 3> cartesian_from_dual_paraboloid(vec<T, 2> uv)
{
// put uv in [-1..1] for each side
T zside = 0.5 * sign(0.5 - uv.x);
uv.x = 1.0 - abs(4.0 * uv.x - 2.0); // [-1..1|1..-1]
uv.y = 1.0 - uv.y * 2.0;
T z = length_squared(uv); // * T(dual_contract_factor);
z = (1.0 - z) * zside;
return vec<T, 3>(uv.x, uv.y, z);
}
inline float reduce_min(float3 v) {
return min(v.x, min(v.y, v.z));
}
inline float reduce_min(float4 v) {
return min(min(v.x, v.y), min(v.z, v.w));
}
inline float reduce_max(float3 v) {
return max(v.x, max(v.y, v.z));
}
inline float reduce_max(float4 v) {
return max(max(v.x, v.y), max(v.z, v.w));
}
inline float3 randomSphereDir(float2 rnd)
{
float s = rnd.x * M_PI_F * 2.f;
float t = rnd.y * 2.f - 1.f;
return float3(sin(s), cos(s), t) / sqrt(1.f + t * t);
}
// from Sledgehammer slides
template <class T>
inline T interleaved_gradient_noise(vec<T, 2> pos)
{
vec<T, 3> magic( 0.06711056f, 0.00583715f, 52.9829189f );
return fract( magic.z * fract( dot( pos, magic.xy ) ) );
}
inline float3 hemisphere_reflect(float3 v, float3 nrm)
{
return v * sign(dot(v, nrm));
}
inline float3 randomHemisphereDir(float3 dir, float2 rnd)
{
return hemisphere_reflect(randomSphereDir( rnd ), dir);
}
inline void orthogonal_basis(float3 n, thread float3& xp, thread float3& yp)
{
// method 2a variant
float sz = n.z >= 0.f ? 1.f : -1.f;
float a = n.y / (1.f + abs(n.z));
float b = n.y * a;
float c = -n.x * a;
xp = float3(n.z + sz * b, sz * c, -n.x);
yp = float3(c, 1.f - b, -sz * n.y);
}
template <class U>
inline float2 normalized_coordinate(ushort2 index, U texture)
{
return float2(float(index.x) / float(texture.get_width() - 1),
float(index.y) / float(texture.get_height() - 1));
}
template <class U>
inline float2 normalized_coordinate(uint2 index, U texture)
{
return float2(float(index.x) / float(texture.get_width() - 1),
float(index.y) / float(texture.get_height() - 1));
}
template <class U>
inline half2 normalized_coordinate_half(uint2 index, U texture)
{
return half2(half(index.x) / half(texture.get_width() - 1),
half(index.y) / half(texture.get_height() - 1));
}
// MARK: Working with cube textures
template <class T>
inline vec<T, 3> cubemap_dir_from_sampleCoord(uint face, vec<T, 2> sampleCoord) // sampleCoord in [-1, 1]
{
switch(face) {
case 0: // +X
return vec<T, 3>( 1.0, -sampleCoord.y, -sampleCoord.x);
case 1: // -X
return vec<T, 3>(-1.0, -sampleCoord.y, sampleCoord.x);
case 2: // +Y
return vec<T, 3>(sampleCoord.x, 1.0, sampleCoord.y);
case 3: // -Y
return vec<T, 3>(sampleCoord.x, -1.0, -sampleCoord.y);
case 4: // +Z
return vec<T, 3>( sampleCoord.x, -sampleCoord.y, 1.0);
default: // -Z
return vec<T, 3>(-sampleCoord.x, -sampleCoord.y, -1.0);
}
}
// convert form [0..1] to [-1..1]
template <class T>
inline T signed_unit(T uv) {
return uv * 2.0 - 1.0;
}
// convert form [-1..1] to [0..1]
template <class T>
inline T unsigned_unit(T uv) {
return uv * 0.5 + 0.5;
}
template <class T>
inline vec<T, 3> cubemap_dir_from_uv(uint face, vec<T, 2> uv) // uv in [0, 1]
{
return cubemap_dir_from_sampleCoord(face, signed_unit(uv));
}
template <class T>
inline vec<T, 3> cubemap_dir_from_uv_unit(uint face, vec<T, 2> uv) // uv in [0, 1]
{
return normalize(cubemap_dir_from_uv(face, uv));
}
// MARK: - SIMD Extensions
inline vector_float2 barycentric_mix(vector_float2 __x, vector_float2 __y, vector_float2 __z, vector_float3 __t) { return __t.x * __x + __t.y * __y + __t.z * __z; }
inline vector_float3 barycentric_mix(vector_float3 __x, vector_float3 __y, vector_float3 __z, vector_float3 __t) { return __t.x * __x + __t.y * __y + __t.z * __z; }
inline vector_float4 barycentric_mix(vector_float4 __x, vector_float4 __y, vector_float4 __z, vector_float3 __t) { return __t.x * __x + __t.y * __y + __t.z * __z; }
static inline float rect(float2 lt, float2 rb, float2 uv)
{
float2 borders = step(lt, uv) * step(uv, rb);
return borders.x * borders.y;
}
inline half4 debugColorForCascade(int cascade)
{
switch (cascade) {
case 0:
return half4(1.h, 0.h, 0.h, 1.h);
case 1:
return half4(0.9, 0.5, 0., 1.);
case 2:
return half4(1., 1., 0., 1.);
case 3:
return half4(0., 1., 0., 1.);
default:
return half4(0., 0., 0., 1.);
}
}
inline half3 debugColorForFace(int count)
{
switch (count) {
case 0: return half3(1.0h, 0.1h, 0.1h);
case 1: return half3(0.1h, 1.0h, 1.0h);
case 2: return half3(0.1h, 1.0h, 0.1h);
case 3: return half3(1.0h, 0.1h, 1.0h);
case 4: return half3(0.1h, 0.1h, 1.0h);
default: return half3(1.0h, 1.0h, 0.1h);
}
}
inline half4 debugColorForCount(int count)
{
switch (count) {
case 0: return half4(0.0h, 0.0h, 0.0h, 1.h);
case 1: return half4(0.0h, 0.0h, 0.4h, 1.h);
case 2: return half4(0.0h, 0.0h, 0.9h, 1.h);
case 3: return half4(0.0h, 0.4h, 0.7h, 1.h);
case 4: return half4(0.0h, 0.9h, 0.4h, 1.h);
case 5: return half4(0.0h, 0.9h, 0.0h, 1.h);
case 6: return half4(0.4h, 0.7h, 0.0h, 1.h);
case 7: return half4(0.9h, 0.7h, 0.0h, 1.h);
default: return half4(1., 0., 0., 1.);
}
}
inline float grid(float2 lt, float2 rb, float2 gridSize, float thickness, float2 uv)
{
float insideRect = rect(lt, rb + thickness, uv);
float2 gt = thickness * gridSize;
float2 lines = step(abs(lt - fract(uv * gridSize)), gt);
return insideRect * (lines.x + lines.y);
}
inline float checkerboard(float2 gridSize, float2 uv)
{
float2 check = floor(uv * gridSize);
return step(fmod(check.x + check.y, 2.f), 0.f);
}
// MARK: - Colors
inline float luminance(float3 color)
{
// `color` assumed to be in the linear sRGB color space
// https://en.wikipedia.org/wiki/Relative_luminance
return color.r * 0.212671 + color.g * 0.715160 + color.b * 0.072169;
}
inline float srgb_to_linear(float c)
{
return (c <= 0.04045f) ? c / 12.92f : powr((c + 0.055f) / 1.055f, 2.4f);
}
inline half srgb_to_linear_fast(half c)
{
return powr(c, 2.2h);
}
inline half3 srgb_to_linear_fast(half3 c)
{
return powr(c, 2.2h);
}
inline half srgb_to_linear(half c)
{
// return (c <= 0.04045h) ? c / 12.92h : powr((c + 0.055h) / 1.055h, 2.4h);
return (c <= 0.04045h) ? (c * 0.0773993808h) : powr(0.9478672986h * c + 0.05213270142h, 2.4h);
}
inline float3 srgb_to_linear(float3 c)
{
return float3(srgb_to_linear(c.x), srgb_to_linear(c.y), srgb_to_linear(c.z));
}
inline float linear_to_srgb(float c)
{
return (c < 0.0031308f) ? (12.92f * c) : (1.055f * powr(c, 1.f/2.4f) - 0.055f);
}
inline float3 linear_to_srgb(float3 v) { // we do not saturate since linear extended values can be fed in
return float3(linear_to_srgb(v.x), linear_to_srgb(v.y), linear_to_srgb(v.z));
}
}
// MARK: GL helpers
template <typename T>
inline T dFdx(T v) {
return dfdx(v);
}
// Y is up in GL and down in Metal
template <typename T>
inline T dFdy(T v) {
return -dfdy(v);
}
// MARK: -
inline float4 texture2DProj(texture2d<float> tex, sampler smp, float4 uv)
{
return tex.sample(smp, uv.xy / uv.w);
}
inline half4 texture2DProj(texture2d<half> tex, sampler smp, float4 uv)
{
return tex.sample(smp, uv.xy / uv.w);
}
static constexpr sampler scn_shadow_sampler_rev_z = sampler(coord::normalized, filter::linear, mip_filter::none, address::clamp_to_zero, compare_func::less_equal);
static constexpr sampler scn_shadow_sampler_ord_z = sampler(coord::normalized, filter::linear, mip_filter::none, address::clamp_to_edge, compare_func::greater_equal);
#if defined(USE_REVERSE_Z) && USE_REVERSE_Z
static constexpr sampler scn_shadow_sampler = scn_shadow_sampler_rev_z;
#else
static constexpr sampler scn_shadow_sampler = scn_shadow_sampler_ord_z;
#endif
inline float shadow2D(sampler shadow_sampler, depth2d<float> tex, float3 uv)
{
return tex.sample_compare(shadow_sampler, uv.xy, uv.z);
}
inline float shadow2DProj(sampler shadow_sampler, depth2d<float> tex, float4 uv)
{
float3 uvp = uv.xyz / uv.w;
return tex.sample_compare(shadow_sampler, uvp.xy, uvp.z);
}
inline float shadow2DArray(sampler shadow_sampler, depth2d_array<float> tex, float3 uv, uint slice)
{
return tex.sample_compare(shadow_sampler, uv.xy, slice, uv.z);
}
inline float shadow2DArrayProj(sampler shadow_sampler, depth2d_array<float> tex, float4 uv, uint slice)
{
float3 uvp = uv.xyz / uv.w;
return tex.sample_compare(shadow_sampler, uvp.xy, slice, uvp.z);
}
// MARK Shadow
inline float4 transformViewPosInShadowSpace(float3 pos, float4x4 shadowMatrix, bool reverseZ)
{
//project into light space
float4 lightScreen = shadowMatrix * float4(pos, 1.f);
// ensure receiver after the shadow projection box are not in shadow (when no caster == 1. instead of infinite)
// TODO : this is awkward : we maybe should rework the comparison order to have something more natural
if (!reverseZ) {
lightScreen.z = min(lightScreen.z, 0.9999f * lightScreen.w);
} else {
if (lightScreen.z <= 0.0) { // out of clip space will always be < of min shadowmap value (0), so considered in shadow. Artificially setting it to larger value than 1 will make the test fail = no shadow.
lightScreen.z = 2.0;
}
}
return lightScreen;
}
inline float ComputeShadow(sampler shadow_sampler, float3 worldPos, float4x4 shadowMatrix, depth2d<float> shadowMap, bool reverseZ)
{
float4 lightScreen = transformViewPosInShadowSpace(worldPos, shadowMatrix, reverseZ);
float shadow = shadow2DProj(shadow_sampler, shadowMap, lightScreen);
// Is this useful ?
shadow *= step(0., lightScreen.w);
return shadow;
}
inline float ComputeSoftShadowGrid(sampler shadow_sampler, float3 worldPos, float4x4 shadowMatrix, depth2d<float> shadowMap, int sampleCount, bool reverseZ)
{
float4 lightScreen = transformViewPosInShadowSpace(worldPos, shadowMatrix, reverseZ);
// if sampleCount is known compileTime, this get rid of the shadowKernel binding
float shadow;
if (sampleCount <= 1) {
shadow = shadow2DProj(shadow_sampler, shadowMap, lightScreen);
} else {
float3 uvp = lightScreen.xyz / lightScreen.w;
uvp.z += reverseZ ? 0.005f : -0.005f; // TODO: get rid of hardcoded bias...
float2 texelSize = 2.f / float2(shadowMap.get_width(), shadowMap.get_height());
float2 origin = uvp.xy - (sampleCount * 0.5f) * texelSize;
// penumbra
if (sampleCount <= 4) { // offset are limited to [-7..8]
half totalAccum = 0.h;
for (int y = 0; y < sampleCount; ++y) {
for (int x = 0; x < sampleCount; ++x) {
totalAccum += half(shadowMap.sample_compare(shadow_sampler, origin, uvp.z, 2 * int2(x,y)));
}
}
shadow = totalAccum / half(sampleCount * sampleCount);
} else {
float totalAccum = 0.f;
for (int y = 0; y < sampleCount; ++y) {
for (int x = 0; x < sampleCount; ++x) {
float2 samplePos = origin + texelSize * float2(x, y);
totalAccum += shadowMap.sample_compare(shadow_sampler, samplePos, uvp.z);
}
}
shadow = totalAccum / float(sampleCount * sampleCount);
}
}
// Is this useful ?
shadow *= step(0., lightScreen.w);
return shadow;
}
inline float ComputeSoftShadow(sampler shadow_sampler, float3 worldPos, float4x4 shadowMatrix, depth2d<float> shadowMap, constant float4* shadowKernel, int sampleCount, float shadowRadius, bool reverseZ)
{
float4 lightScreen = transformViewPosInShadowSpace(worldPos, shadowMatrix, reverseZ);
// if sampleCount is known compileTime, this get rid of the shadowKernel binding
float shadow;
if (sampleCount <= 1) {
shadow = shadow2DProj(shadow_sampler, shadowMap, lightScreen);
} else {
//*
// penumbra
float3 center_uv = lightScreen.xyz / lightScreen.w;
float3 scale_uv = float3(shadowRadius, shadowRadius, reverseZ ? shadowRadius * center_uv.z : shadowRadius / lightScreen.w );
//smooth all samples
float totalAccum = 0.0;
for (int i = 0; i < sampleCount; i++) {
totalAccum += shadow2D(shadow_sampler, shadowMap, center_uv + shadowKernel[i].xyz * scale_uv);
}
/*/ This version could introduce more shadow acne
float3 uvp = lightScreen.xyz / lightScreen.w;
float2 texelSize = shadowRadius;
//smooth all samples
float totalAccum = 0.0;
for (int i=0; i < sampleCount; i++){
float2 samplePos = uvp.xy + texelSize * shadowKernel[i].xy;
totalAccum += shadowMap.sample_compare(shadow_sampler, samplePos, uvp.z);
}
//*/
shadow = totalAccum / float(sampleCount);
}
// Is this useful ?
shadow *= step(0., lightScreen.w);
return shadow;
}
inline float ComputeCascadeBlendAmount(float3 shadowPos, bool cascadeBlending)
{
const float cascadeBlendingFactor = 0.1f; // No need to configure that
float3 cascadePos = abs(shadowPos.xyz * 2.f - 1.f);
if (cascadeBlending) {
#if 0
const float edge = 1.f - cascadeBlendingFactor;
// could also do a smoothstep
cascadePos = 1.f - saturate((cascadePos - edge) / cascadeBlendingFactor);
return cascadePos.x * cascadePos.y * cascadePos.z; //min(o.x, o.y);
#else
// OPTIM use reduce_max
float distToEdge = 1.0f - max(max(cascadePos.x, cascadePos.y), cascadePos.z);
return smoothstep(0.0f, cascadeBlendingFactor, distToEdge);
#endif
} else {
return step(cascadePos.x, 1.f) * step(cascadePos.y, 1.f) * step(cascadePos.z, 1.f);
}
}
inline float4 SampleShadowCascade(sampler shadow_sampler, depth2d_array<float> shadowMaps, float3 shadowPosition, uint cascadeIndex, constant float4* shadowKernel, int sampleCount, float shadowRadius)
{
// cascade debug + grid
float2 gridSize = float2(shadowMaps.get_width(), shadowMaps.get_height()) / 32;
float gd = scn::checkerboard(shadowPosition.xy, gridSize);
float3 gridCol = mix(float3(scn::debugColorForCascade(cascadeIndex).rgb), float3(0.f), float3(gd > 0.f));
float shadow = 0.f;
if (sampleCount > 1) {
// penumbra : sum all samples
for (int i = 0; i < sampleCount; ++i) {
shadow += shadow2DArray(shadow_sampler, shadowMaps, shadowKernel[i].xyz * shadowRadius + shadowPosition, cascadeIndex);
}
shadow /= float(sampleCount);
} else {
// OPTIM : do not use proj version since cascade are never projective
shadow = shadow2DArray(shadow_sampler, shadowMaps, shadowPosition, cascadeIndex);
}
return float4(gridCol, shadow);
}
inline float4 ComputeCascadedShadow(sampler shadow_sampler, float3 viewPos, float4x4 shadowMatrix, constant float4 *cascadeScale, constant float4 *cascadeBias, int cascadeCount, depth2d_array<float> shadowMaps, bool enableCascadeBlending, constant float4* shadowKernel, int sampleCount, float shadowRadius)
{
float4 shadow = 0.f;
float opacitySum = 1.f;
// get the position in light space
float3 pos_ls = (shadowMatrix * float4(viewPos, 1.f)).xyz;
for (int c = 0; c < cascadeCount; ++c) {
float3 pos_cs = pos_ls * cascadeScale[c].xyz + cascadeBias[c].xyz;
// we multiply the radius by the scale factor of the cascade
float cascadeRadius = shadowRadius * cascadeScale[c].x;
float opacity = ComputeCascadeBlendAmount(pos_cs, enableCascadeBlending);
if (opacity > 0.f) { // this cascade should be considered
float alpha = opacity * opacitySum;
shadow += SampleShadowCascade(shadow_sampler, shadowMaps, pos_cs, c, shadowKernel, sampleCount, cascadeRadius) * alpha;
opacitySum -= alpha;
}
if (opacitySum <= 0.f) // fully opaque shadow (no more blending needed) -> bail out
break;
}
return shadow;
}
namespace NAMESPACE_HASH {
// Macro for layered rendering & shadermodifier
#ifdef USE_LAYERED_RENDERING
#define texture2d_layer texture2d_array
#define sampleLayer(a,b) sample(a,b,in.sliceIndex)
#else
#define texture2d_layer texture2d
#define sampleLayer(a,b) sample(a,b)
#endif
#if defined(HAS_NORMAL) || defined(USE_OPENSUBDIV)
#define HAS_OR_GENERATES_NORMAL 1
#endif
#ifdef C3D_USE_TEXTURE_FOR_LIGHT_INDICES
#define LightIndex(lid) u_lightIndicesTexture.read((ushort)lid).x
#else
#define LightIndex(lid) u_lightIndicesBuffer[lid]
#endif
// 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_MOTIONBLUR
float4x4 lastFrameModelTransform;
float motionBlurIntensity;
#endif
#ifdef USE_BOUNDINGBOX
float2x3 boundingBox;
#endif
#ifdef USE_WORLDBOUNDINGBOX
float2x3 worldBoundingBox;
#endif
#ifdef USE_NODE_OPACITY
float nodeOpacity;
#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)]];
#ifdef HAS_NORMAL
float3 normal [[attribute(SCNVertexSemanticNormal)]];
#endif
#ifdef USE_TANGENT
float4 tangent [[attribute(SCNVertexSemanticTangent)]];
#endif
#ifdef USE_VERTEX_COLOR
float4 color [[attribute(SCNVertexSemanticColor)]];
#endif
#ifdef USE_SKINNING
float4 skinningWeights [[attribute(SCNVertexSemanticBoneWeights)]];
uint4 skinningJoints [[attribute(SCNVertexSemanticBoneIndices)]];
#endif
#if defined(NEED_IN_TEXCOORD0) || defined(DEBUG_PIXEL)
float2 texcoord0 [[attribute(SCNVertexSemanticTexcoord0)]];
#endif
#ifdef NEED_IN_TEXCOORD1
float2 texcoord1 [[attribute(SCNVertexSemanticTexcoord1)]];
#endif
#ifdef NEED_IN_TEXCOORD2
float2 texcoord2 [[attribute(SCNVertexSemanticTexcoord2)]];
#endif
#ifdef NEED_IN_TEXCOORD3
float2 texcoord3 [[attribute(SCNVertexSemanticTexcoord3)]];
#endif
#ifdef NEED_IN_TEXCOORD4
float2 texcoord4 [[attribute(SCNVertexSemanticTexcoord4)]];
#endif
#ifdef NEED_IN_TEXCOORD5
float2 texcoord5 [[attribute(SCNVertexSemanticTexcoord5)]];
#endif
#ifdef NEED_IN_TEXCOORD6
float2 texcoord6 [[attribute(SCNVertexSemanticTexcoord6)]];
#endif
#ifdef NEED_IN_TEXCOORD7
float2 texcoord7 [[attribute(SCNVertexSemanticTexcoord7)]];
#endif
} 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
#ifdef USE_CLEARCOAT
float clearCoat;
#endif
#ifdef USE_CLEARCOATROUGHNESS
float clearCoatRoughness;
#endif
#ifdef USE_CLEARCOATNORMAL
float clearCoatNormal;
#endif
#endif
#if defined(USE_POSITION) && (USE_POSITION == 2)
float3 position;
#endif
#if defined(USE_NORMAL) && (USE_NORMAL == 2) && defined(HAS_OR_GENERATES_NORMAL)
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_DISPLACEMENT_MAP
float2 displacementTexcoord; // Displacement texture coordinates
#endif
#ifdef USE_CLEARCOAT_MAP
float2 clearCoatTexcoord; // ClearCoat texture coordinates
#endif
#ifdef USE_CLEARCOATROUGHNESS_MAP
float2 clearCoatRoughnessTexcoord; // ClearCoatRoughness texture coordinates
#endif
#ifdef USE_CLEARCOATNORMAL_MAP
float2 clearCoatNormalTexcoord; // ClearCoatNormal texture coordinates
#endif
#ifdef USE_NODE_OPACITY
float nodeOpacity;
#endif
#ifdef USE_TEXCOORD
float2 texcoord0;
#endif
#ifdef USE_EXTRA_VARYINGS
#endif
#ifdef USE_MOTIONBLUR
float3 mv_fragment;
float3 mv_lastFragment;
#endif
#ifdef USE_OUTLINE
float outlineHash [[ flat ]];
#endif
#ifdef USE_LAYERED_RENDERING
uint sliceIndex [[render_target_array_index]];
#endif
#ifdef USE_MULTIPLE_VIEWPORTS_RENDERING
uint sliceIndex [[viewport_array_index]];
#endif
#if DEBUG_PIXEL
float2 uv0;
#endif
} commonprofile_io;
// Shader modifiers declaration (only enabled if one modifier is present)
#ifdef USE_SHADER_MODIFIERS
#endif
// This may rely on shader modifiers declaration
#define USE_QUAT_FOR_IES 1
// 256 bytes
struct scn_light
{
float4 color; // color.rgb + shadowColor.a 16
float3 pos; // 16 (12)
float3 dir; // 16 (12)
float shadowRadius; // 4
uint8_t lightType; // 1
uint8_t attenuationType; // 1
uint8_t shadowSampleCount; // 1
// 55 but in reality 64 for alignment reasons
union {
struct {
float4 cascadeScale[4]; // max cascade count
float4 cascadeBias[4];
} directional; // 128
struct {
float4 attenuationFactors; // scale/bias/power/invSquareRadius 16
float3 shadowScaleBias; // xy: scale/bias for z_lin -> z_ndc, z: depth bias
} omni;
struct {
float4 _attenuationFactors; // need to match omni, always use omni.attenuationFactors
float2 scaleBias; // scale/bias to compute spot falloff
} spot;
struct {
float4 _attenuationFactors; // need to match omni, always use omni.attenuationFactors
float2 scaleBias; // scale/bias to compute ies LUT
#if USE_QUAT_FOR_IES
float4 light_from_view_quat; // OPTIM: this could be a simple quaternion
#else
float4x4 light_from_view; // OPTIM: this could be a simple quaternion
#endif
} ies;
union {
struct {
float2 halfExtents;
float doubleSided;
} rectangle;
struct {
uint32_t vertexCount;
float doubleSided;
} polygon;
struct {
float halfLength;
} line;
struct {
float2 halfExtents;
float doubleSided;
} ellipse;
struct {
float3 halfExtents;
} ellipsoid;
} area;
struct {
float3 offset;
float4 halfExtents; // w: contains the blending distance
float3 parallaxCenter;
float3 parallaxExtents;
int32_t index; // index of the probe in the probe array (do not use uint8_t because of compiler crash)
int32_t parallaxCorrection; // do not use bool (compiler crash)
} probe;
} parameters; // 128
float4x4 shadowMatrix; // 64
};
#if defined(__METAL_VERSION__) && __METAL_VERSION__ >= 120
#define ambientOcclusionTexcoord ambientTexcoord
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 selfIllumination; // selfIllumination property of the fragment
float2 selfIlluminationTexcoord; // selfIllumination 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; // Roughness texture coordinates
float clearCoat; // ClearCoat property of the fragment
float2 clearCoatTexcoord; // ClearCoat texture coordinates
float clearCoatRoughness; // ClearCoatRoughness property of the fragment
float2 clearCoatRoughnessTexcoord;// ClearCoatRoughness texture coordinates
float3 clearCoatNormal; // ClearCoatNormal property of the fragment
float2 clearCoatNormalTexcoord;// ClearCoatNormal 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
float3 _clearCoatNormalTS; // UNDOCUMENTED in tangent space
#ifdef USE_SURFACE_EXTRA_DECL
#endif
};
// Structure to gather property of a light, packed to give access in a light shader modifier
// This must be kept intact for back compatibility in lighting modifiers
struct SCNShaderLight {
float4 intensity;
float3 direction;
float _att;
float3 _spotDirection;
float _distance;
};
enum SCNLightingModel
{
SCNLightingModelConstant,
SCNLightingModelLambert,
SCNLightingModelPhong,
SCNLightingModelBlinn,
SCNLightingModelNone,
SCNLightingModelPhysicallyBased,
SCNLightingModelShadowOnly,
SCNLightingModelCustom // 6 implicit when using a lighting shader modifier
};
enum C3DLightAttenuationType
{
kC3DLightAttenuationTypeNone,
kC3DLightAttenuationTypeConstant,
kC3DLightAttenuationTypeLinear,
kC3DLightAttenuationTypeQuadratic,
kC3DLightAttenuationTypeExponent,
kC3DLightAttenuationTypePhysicallyBased,
};
#define PROBES_NORMALIZATION 0
#define PROBES_OUTER_BLENDING 1
struct SCNShaderLightingContribution
{
float3 ambient;
float3 diffuse;
float3 specular;
float3 modulate;
#ifdef USE_SHADOWONLY
float shadowFactor;
#endif
#if PROBES_NORMALIZATION
float4 probesWeightedSum; // rgb: sum a:normalization factor
#else
float probeRadianceRemainingFactor;
#endif
thread SCNShaderSurface& surface;
#ifdef USE_PER_VERTEX_LIGHTING
commonprofile_io out;
#else
commonprofile_io in;
#endif
#if USE_REVERSE_Z
constant static constexpr bool reverseZ = true;
#else
constant static constexpr bool reverseZ = false;
#endif
#ifdef USE_PBR
static constexpr sampler linearSampler = sampler(filter::linear, mip_filter::linear);
float selfIlluminationOcclusion;
float3 reflectance;
float3 probeReflectance;
float NoV;
float NoVClearCoat;
float3 probeReflectanceClearCoat;
#endif
SCNShaderLightingContribution(thread SCNShaderSurface& iSurface, commonprofile_io io):surface(iSurface)
#ifdef USE_PER_VERTEX_LIGHTING
,out(io)
#else
,in(io)
#endif
{
ambient = 0.f;
diffuse = 0.f;
specular = 0.f;
#ifdef USE_SHADOWONLY
shadowFactor = 1.f;
#endif
#if PROBES_NORMALIZATION
#if PROBES_OUTER_BLENDING
probesWeightedSum = float4(0.f);
#else
probesWeightedSum = float4(0.f, 0.f, 0.f, 0.000001f); // avoid divide by 0 with an epsilon
#endif
#else
probeRadianceRemainingFactor = 1.f;
#endif
#ifdef USE_MODULATE
modulate = 1.f;
#else
modulate = 0.f;
#endif
}
#ifdef USE_PBR
void prepareForPBR(texture2d<float, access::sample> specularDFG, float occ)
{
selfIlluminationOcclusion = occ;
float3 n = surface.normal;
float3 v = surface.view;
reflectance = mix(PBR_F0_NON_METALLIC, surface.diffuse.rgb, surface.metalness);
NoV = saturate(dot(n, v));
float2 DFG = specularDFG.sample(linearSampler, float2(NoV, surface.roughness)).rg;
probeReflectance = reflectance * DFG.r + DFG.g;
}
void prepareForPBRClearCoat(texture2d<float, access::sample> specularDFG)
{
float3 n = surface.clearCoatNormal;
float3 v = surface.view;
NoVClearCoat = saturate(dot(n, v));
float2 DFG = specularDFG.sample(linearSampler, float2(NoVClearCoat, surface.clearCoatRoughness)).rg;
probeReflectanceClearCoat = 0.04 * DFG.r + DFG.g;
}
#endif
#ifdef USE_LIGHT_MODIFIER
#endif
float4 debug_pixel(float2 fragmentPosition)
{
const int width = 64;
switch (int(fragmentPosition.x + fragmentPosition.y ) / width) {
case 0: return float4(surface.view, 1.f);
case 1: return float4(surface.position, 1.f);
case 2: return float4(surface.normal, 1.f);
case 3: return float4(surface.geometryNormal, 1.f);
case 4: return float4(float3(surface.ambientOcclusion), 1.f);
case 5: return surface.diffuse;
case 6: return float4(float3(surface.metalness), 1.f);
case 7: return float4(float3(surface.roughness), 1.f);
case 8: return float4(ambient, 1.f);
case 9: return float4(diffuse, 1.f);
default: return float4(specular, 1.f);
}
}
// tool functions, could be external
static inline float3 lambert_diffuse(float3 l, float3 n, float3 color, float intensity) {
return color * (intensity * saturate(dot(n, l)));
}
void lambert(float3 l, float3 color, float intensity)
{
diffuse += lambert_diffuse(l, surface.normal, color, intensity);
}
void blinn(float3 l, float3 color, float intensity)
{
float3 D = lambert_diffuse(l, surface.normal, color, intensity);
diffuse += D;
float3 h = normalize(l + surface.view);
specular += powr(saturate(dot(surface.normal, h)), surface.shininess) * D;
}
void phong(float3 l, float3 color, float intensity)
{
float3 D = lambert_diffuse(l, surface.normal, color, intensity);
diffuse += D;
float3 r = reflect(-l, surface.normal);
specular += powr(saturate(dot(r, surface.view)), surface.shininess) * D;
}
#ifdef USE_PBR
void pbr(float3 l, float3 color, float intensity)
{
float3 n = surface.normal;
float3 v = surface.view;
float3 h = normalize(l + v);
float NoL = saturate(dot(n, l));
float NoH = saturate(dot(n, h));
float LoH = saturate(dot(l, h));
// for analytical lights, perfect mirror will not exhibit any specular
// so clamp the value to an arbitrary low values
// https://google.github.io/filament/Filament.md.html
// value chosen such that (MIN_ROUGHNESS^4) > 0 in fp16 (i.e. 2^(-14/4), slightly rounded up)
float roughness = max(surface.roughness, 0.089f);
float alpha = roughness * roughness; // perceptually-linear roughness
float D = scn_brdf_D(alpha, NoH);
float3 F = scn_brdf_F_opt(reflectance, LoH);
float Vis = scn_brdf_V(alpha, NoL, NoV);
// keep the scalar separated
diffuse += color * (NoL * M_1_PI_F * intensity);
specular += color * F * ( NoL * D * Vis * intensity);
#ifdef USE_CLEARCOAT
n = surface.clearCoatNormal;
roughness = max(surface.clearCoatRoughness, 0.089f);
alpha = roughness * roughness; // perceptually-linear roughness
//we should recompute NoH, because the coat normal is not necesseraly the base normal
//-≥ either the coatNormal, or the geometry normal
float NoH_coat = saturate(dot(n, h));
float NoL_coat = saturate(dot(n, l));
D = scn_brdf_D(alpha, NoH_coat);
F = scn_brdf_F_opt(0.04, LoH) * surface.clearCoat;
Vis = scn_brdf_V(alpha, NoL_coat, saturate(dot(n,v)));
float attenuation = 1.0 - F.r;
specular *= (attenuation * attenuation);
specular += color * F * ( NoL_coat * D * Vis * intensity);
#endif
}
#endif
void custom(float3 _l, float3 _color, float _intensity)
{
#ifdef USE_LIGHT_MODIFIER
thread SCNShaderLightingContribution &_lightingContribution = *this;
thread SCNShaderSurface& _surface = surface;
SCNShaderLight _light = {.direction = _l, .intensity = float4(_color, 1.f), ._att = _intensity };
// DoLightModifier START
// DoLightModifier END
#endif
}
void shade(float3 l, float3 color, float intensity)
{
#ifdef LIGHTING_MODEL
switch (LIGHTING_MODEL) {
#ifdef USE_SHADOWONLY
case SCNLightingModelShadowOnly: shadowFactor *= intensity; break;
#endif
case SCNLightingModelLambert: lambert(l, color, intensity); break;
case SCNLightingModelBlinn: blinn(l, color, intensity); break;
case SCNLightingModelPhong: phong(l, color, intensity); break;
#ifdef USE_PBR
case SCNLightingModelPhysicallyBased: pbr(l, color, intensity); break;
#endif
case SCNLightingModelCustom: custom(l, color, intensity); break;
default: break; // static_assert(0, "should not go there");
}
#endif
}
// this implementation seems more correct as it never goes larger than when near (dist<1)
// https://imdoingitwrong.wordpress.com/2011/01/31/light-attenuation/
// cutoff must be computed with Eq(1) with lightAttenuationEnd and stored in the light struct
float pbr_dist_attenuation_alternate(float3 l, float cutoff) {
// float radius = 0.01f; // consider point lights as 1cm
float radius = 0.1f; // consider point lights as 10cm
float factor = 1.f / (1.f + length(l)/radius);
float attenuation = saturate(factor * factor); // Eq(1)
return saturate((attenuation - cutoff) / (1.f - cutoff));
}
float pbr_dist_attenuation(float3 l, float inv_square_radius) {
float sqr_dist = length_squared(l);
float atten = 1.f / max(sqr_dist, 0.0001f);
// smoothing factor to avoid hard clip of the lighting
float factor = saturate(1.f - scn::sq(sqr_dist * inv_square_radius));
return atten * factor * factor;
}
float non_pbr_dist_attenuation(float3 l, float4 att)
{
return powr(saturate(length(l) * att.x + att.y), att.z);
}
float dist_attenuation(float3 unnormalized_l, scn_light light)
{
#ifdef USE_PBR
return 1000.f * pbr_dist_attenuation(unnormalized_l, light.parameters.omni.attenuationFactors.w);
// This alternate model seems to better fit V-Ray render.... To confirm
// float intensity = 1000.f * pbr_dist_attenuation_alternate(unnormalized_l, 0.f);
#else
return non_pbr_dist_attenuation(unnormalized_l, light.parameters.omni.attenuationFactors);
#endif
}
float spot_attenuation(float3 l, scn_light light)
{
// only support linear attenuation (spotExponent is SPI)
return saturate(dot(l, light.dir) * light.parameters.spot.scaleBias.x + light.parameters.spot.scaleBias.y);
}
void shade_modulate(float3 l, float4 color, float intensity)
{
constexpr half3 white = half3(1.h);
// color.a contains the gobo slot intensity -> used to fade it
modulate *= float3(mix(white, half3(color.rgb), half(color.a * intensity)));
}
float3 gobo(float3 pos, scn_light light, texture2d<half> goboTexture, sampler goboSampler)
{
half3 g = texture2DProj(goboTexture, goboSampler, (light.shadowMatrix * float4(pos, 1.f))).rgb;
return light.color.rgb * float3(mix(1.h, g, half(light.color.a)));
}
float shadow(float3 pos, scn_light light, depth2d<float> shadowMap)
{
float shadow = ComputeShadow(scn_shadow_sampler, pos, light.shadowMatrix, shadowMap, reverseZ);
return 1.f - shadow * light.color.a; // shadow color
}
// this versions takes the sample count from the light. This forces dynamic loops, not a good idea on iOS
float shadow(float3 pos, scn_light light, depth2d<float> shadowMap, constant float4* shadowKernel)
{
float shadow = ComputeSoftShadow(scn_shadow_sampler, pos, light.shadowMatrix, shadowMap, shadowKernel, light.shadowSampleCount, light.shadowRadius, reverseZ);
return 1.f - shadow * light.color.a; // shadow color
}
float shadow(float3 pos, scn_light light, depth2d<float> shadowMap, constant float4* shadowKernel, int shadowSampleCount)
{
float shadow = ComputeSoftShadow(scn_shadow_sampler, pos, light.shadowMatrix, shadowMap, shadowKernel, shadowSampleCount, light.shadowRadius, reverseZ);
return 1.f - shadow * light.color.a; // shadow color
}
float shadow(float3 pos, scn_light light, depth2d<float> shadowMap, int shadowSampleCount)
{
float shadow = ComputeSoftShadowGrid(scn_shadow_sampler, pos, light.shadowMatrix, shadowMap, shadowSampleCount, reverseZ);
return 1.f - shadow * light.color.a; // shadow color
}
float shadow_omni(float3 pos_vs, float3 nrm_vs, scn_light light, depthcube<float> shadowMap, constant float4* shadowKernel, int sampleCount)
{
// else use hemispheric sampling
#define USE_TANGENT_SAMPLING 0
float2 scaleBias = light.parameters.omni.shadowScaleBias.xy;
float depthBias = light.parameters.omni.shadowScaleBias.z;
// offset/bias for shadow acne
pos_vs += nrm_vs * depthBias;
// transform pos from view space to light space
float3 pos_ls = (light.shadowMatrix * float4(pos_vs, 1.f)).xyz;
// compute z_lin for sample cube face (symetric so +x == -x, etc)
float z_lin = scn::reduce_max(abs(pos_ls));
// if we want to clip the shadows to the far planes of the cube...
// if (z_lin > zFar)
// return 1.f;
// transform linear_z to depthbuffer_z
float z_ndc = (z_lin * scaleBias.x + scaleBias.y) / z_lin - depthBias;
// if sampleCount is known compileTime, this get rid of the shadowKernel binding
float shadow;
if (sampleCount <= 1) {
shadow = shadowMap.sample_compare(scn_shadow_sampler, pos_ls.xyz, z_ndc);
} else {
// penumbra
float filteringSizeFactor = light.shadowRadius;
#if USE_TANGENT_SAMPLING
float3 tgt_x, tgt_y;
scn::orthogonal_basis(pos_ls, tgt_x, tgt_y);
#else
float3 nrm_ls = (light.shadowMatrix * float4(nrm_vs, 0.f)).xyz;
#endif
//smooth all samples
float totalAccum = 0.0;
for(int i=0; i < sampleCount; i++){
#if USE_TANGENT_SAMPLING
float2 scale = shadowKernel[i].xy * filteringSizeFactor * 2.f;
float3 smp_ls = pos_ls.xyz + tgt_x * scale.x + tgt_y * scale.y;
#else
float3 smp_ls = pos_ls.xyz + scn::randomHemisphereDir(nrm_ls, shadowKernel[i].xy) * filteringSizeFactor;
#endif
// Do we want to compare with reference depth or smp depth?
// z_lin = scn::reduce_max(abs(smp_ls));
// z_ndc = (z_lin * scaleBias.x + scaleBias.y) / z_lin;
totalAccum += shadowMap.sample_compare(scn_shadow_sampler, smp_ls, z_ndc);
}
shadow = totalAccum / float(sampleCount);
}
return 1.f - shadow * light.color.a; // shadow color
}
float shadow(float3 pos, constant scn_light& light, depth2d_array<float> shadowMaps, int cascadeCount, bool blendCascade, constant float4* shadowKernel, int sampleCount)
{
float shadow = ComputeCascadedShadow(scn_shadow_sampler, pos, light.shadowMatrix, light.parameters.directional.cascadeScale, light.parameters.directional.cascadeBias, cascadeCount, shadowMaps, blendCascade, shadowKernel, sampleCount, light.shadowRadius).a;
return 1.f - shadow * light.color.a; // shadow color
}
// MARK: Directional
void add_directional(scn_light light)
{
#ifdef USE_PBR
float intensity = M_PI_F;
#else
float intensity = 1.f;
#endif
shade(light.dir, light.color.rgb, intensity);
}
// gobo
void add_directional(scn_light light, texture2d<half> goboTexture, sampler goboSampler, bool modulated)
{
#ifdef USE_PBR
float intensity = M_PI_F;
#else
float intensity = 1.f;
#endif
light.color.rgb = gobo(surface.position, light, goboTexture, goboSampler);
if (modulated) {
shade_modulate(light.dir, light.color, 1.f);
} else {
shade(light.dir, light.color.rgb, intensity);
}
}
// support simple shadows (no soft, no cascade)
void add_directional(scn_light light, depth2d<float> shadowMap)
{
#ifdef USE_PBR
float intensity = M_PI_F;
#else
float intensity = 1.f;
#endif
intensity *= shadow(surface.position, light, shadowMap);
shade(light.dir, light.color.rgb, intensity);
}
// support soft shadows (non cascaded, dynamic sample count)
void add_directional(scn_light light, depth2d<float> shadowMap, constant float4* shadowKernel)
{
#ifdef USE_PBR
float intensity = M_PI_F;
#else
float intensity = 1.f;
#endif
intensity *= shadow(surface.position, light, shadowMap, shadowKernel);
shade(light.dir, light.color.rgb, intensity);
}
void add_directional(scn_light light, depth2d<float> shadowMap, constant float4* shadowKernel, int sampleCount)
{
#ifdef USE_PBR
float intensity = M_PI_F;
#else
float intensity = 1.f;
#endif
intensity *= shadow(surface.position, light, shadowMap, shadowKernel, sampleCount);
shade(light.dir, light.color.rgb, intensity);
}
// regular grid PCF
void add_directional(scn_light light, depth2d<float> shadowMap, int sampleCount)
{
#ifdef USE_PBR
float intensity = M_PI_F;
#else
float intensity = 1.f;
#endif
intensity *= shadow(surface.position, light, shadowMap, sampleCount);
shade(light.dir, light.color.rgb, intensity);
}
// version supporting cascade shadows
void add_directional(constant scn_light& light, depth2d_array<float> shadowMaps, int cascadeCount, bool blendCascade, constant float4* shadowKernel, int sampleCount, bool debugCascades)
{
#ifdef USE_PBR
float intensity = M_PI_F;
#else
float intensity = 1.f;
#endif
if (debugCascades) {
float4 shadowDebug = ComputeCascadedShadow(scn_shadow_sampler, surface.position, light.shadowMatrix, light.parameters.directional.cascadeScale, light.parameters.directional.cascadeBias, cascadeCount, shadowMaps, blendCascade, shadowKernel, sampleCount, light.shadowRadius);
intensity *= (1.f - shadowDebug.a);
shade(light.dir, light.color.rgb, intensity);
diffuse.rgb = mix(diffuse.rgb, shadowDebug.rgb, light.color.a);
} else {
intensity *= shadow(surface.position, light, shadowMaps, cascadeCount, blendCascade, shadowKernel, sampleCount);
shade(light.dir, light.color.rgb, intensity);
}
}
// MARK: Omni
void add_omni(scn_light light)
{
float3 unnormalized_l = light.pos - surface.position;
float3 l = normalize(unnormalized_l);
shade(l, light.color.rgb, dist_attenuation(unnormalized_l, light));
}
void add_omni(scn_light light, depthcube<float> shadowMap, constant float4* shadowKernel, int sampleCount)
{
float3 unnormalized_l = light.pos - surface.position;
float3 l = normalize(unnormalized_l);
float intensity = dist_attenuation(unnormalized_l, light);
intensity *= shadow_omni(surface.position, surface.normal, light, shadowMap, shadowKernel, sampleCount);
shade(l, light.color.rgb, intensity);
}
void add_local_omni(scn_light light)
{
float3 unnormalized_l = light.pos - surface.position;
float3 l = normalize(unnormalized_l);
shade(l, light.color.rgb, dist_attenuation(unnormalized_l, light));
}
// MARK: Spot
void add_spot(scn_light light)
{
float3 unnormalized_l = light.pos - surface.position;
float3 l = normalize(unnormalized_l);
float intensity = dist_attenuation(unnormalized_l, light);
intensity *= spot_attenuation(l, light);
shade(l, light.color.rgb, intensity);
}
void add_spot(scn_light light, texture2d<half> goboTexture, sampler goboSampler, bool modulated)
{
float3 unnormalized_l = light.pos - surface.position;
float3 l = normalize(unnormalized_l);
float intensity = dist_attenuation(unnormalized_l, light);
intensity *= spot_attenuation(l, light);
light.color.rgb = gobo(surface.position, light, goboTexture, goboSampler);
if (modulated) {
shade_modulate(l, light.color, intensity);
} else {
shade(l, light.color.rgb, intensity);
}
}
void add_local_spot(scn_light light)
{
float3 unnormalized_l = light.pos - surface.position;
float3 l = normalize(unnormalized_l);
float intensity = dist_attenuation(unnormalized_l, light);
intensity *= spot_attenuation(l, light);
shade(l, light.color.rgb, intensity);
}
// support simple shadows (non cascaded)
void add_spot(scn_light light, depth2d<float> shadowMap, constant float4* shadowKernel, int sampleCount)
{
float3 unnormalized_l = light.pos - surface.position;
float3 l = normalize(unnormalized_l);
float intensity = dist_attenuation(unnormalized_l, light);
intensity *= spot_attenuation(l, light);
intensity *= shadow(surface.position, light, shadowMap, shadowKernel, sampleCount);
shade(l, light.color.rgb, intensity);
}
// MARK: Probe
#ifdef USE_PBR
// MARK: Radiance
#ifdef C3D_SUPPORT_CUBE_ARRAY
void add_local_probe(scn_light light, texturecube_array<half> probeTextureArray)
#else
void add_local_probe(scn_light light, texture2d_array<half> probeTextureArray)
#endif
{
#if !PROBES_NORMALIZATION
if (probeRadianceRemainingFactor <= 0.f)
return;
#endif
bool parallaxCorrection = light.parameters.probe.parallaxCorrection;
int probeIndex = light.parameters.probe.index;
float3 probeExtents = light.parameters.probe.halfExtents.xyz;
float blendDist = light.parameters.probe.halfExtents.w;
float3 probeOffset = light.parameters.probe.offset;
float3 parallaxExtents = light.parameters.probe.parallaxExtents;
float3 parallaxCenter = light.parameters.probe.parallaxCenter;
float3 n = surface.normal;
float3 v = surface.view;
float3 r = reflect(-v, n); // mirror vector (view vector around normal)
float3 specDir = scn::mat4_mult_float3(light.shadowMatrix, r);
// TODO blend weight ? accumulate in alpha and normalize ?
float3 pos_ls = (light.shadowMatrix * float4(surface.position, 1.f)).xyz;
// OPTIM: we should be able to multiply by the extents in CPU in matrix and use 1 here...
float3 d = abs(pos_ls) - probeExtents;
#if PROBES_OUTER_BLENDING
if (any(d > blendDist))
#else
if (any(d > 0.f))
#endif
{
return;
}
#if PROBES_NORMALIZATION
// inside ' | ' outside
// nd 1 1 0.5 0 0 (per component)
#if PROBES_OUTER_BLENDING
float3 nd = saturate(-(d / blendDist) * 0.5f + 0.5f);
#else
float3 nd = saturate(-(d / blendDist));
#endif
float probeFactor = (nd.x * nd.y * nd.z) * light.color.r;
#else
// signed distance in the probe box
float sd = min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0));
#if PROBES_OUTER_BLENDING
float probeFactor = saturate(1.f - sd / blendDist);
#else
float probeFactor = saturate(-sd / blendDist);
#endif
// inside | ' outside
// sd -x 0 b +x
// fc 1 1 0 0
probeFactor *= probeRadianceRemainingFactor * light.color.r; // Do we really need this or 1.f is enough (we need this for global probe anyway...)
#endif
if (parallaxCorrection /* && all(d < 0.f) */) {
// OPTIM: we should be able to multiply by the extents in CPU and use 1 here...
float3 pos_off = pos_ls + parallaxCenter;
float3 t1 = ( parallaxExtents - pos_off) / specDir;
float3 t2 = (-parallaxExtents - pos_off) / specDir;
float3 tmax = max(max(0, t1), t2); // max(0, ..) to avoid correction when outside the box (in the blending zone)
float t = min(tmax.x, min(tmax.y, tmax.z));
// Use Distance in WS directly to recover intersection
float3 hit_ls = pos_ls + specDir * t;
specDir = hit_ls - probeOffset;
}
float mipd = float(probeTextureArray.get_num_mip_levels()) - 1.f;
const float intensity = surface.ambientOcclusion * probeFactor;
float mips = surface.roughness * mipd;
#ifdef C3D_SUPPORT_CUBE_ARRAY
float3 LD = float3(probeTextureArray.sample(linearSampler, specDir, probeIndex, level(mips)).rgb);
#else
float2 specUV = scn::dual_paraboloid_from_cartesian(normalize(specDir));
float3 LD = float3(probeTextureArray.sample(linearSampler, specUV, probeIndex, level(mips)).rgb);
#endif
/* Debug blending with primary colors
switch (probeIndex) {
case 1: LD = float3(1.f, 0.f, 0.f); break;
case 2: LD = float3(0.f, 1.f, 0.f); break;
case 3: LD = float3(0.f, 0.f, 1.f); break;
default: LD = float3(1.f, 1.f, 1.f); break;
}*/
// radiance
#if PROBES_NORMALIZATION
probesWeightedSum += float4(LD * intensity * probeReflectance, probeFactor);
#else
probeRadianceRemainingFactor = saturate(probeRadianceRemainingFactor - probeFactor);
specular += LD * intensity * probeReflectance;
#endif
#ifdef USE_CLEARCOAT
n = surface.clearCoatNormal;
r = reflect(-v, n);
specDir = scn::mat4_mult_float3(light.shadowMatrix, r);
if (parallaxCorrection /* && all(d < 0.f) */) {
float3 pos_off = pos_ls + parallaxCenter;
// OPTIM: we should be able to multiply by the extents in CPU and use 1 here...
float3 t1 = ( parallaxExtents - pos_off) / specDir;
float3 t2 = (-parallaxExtents - pos_off) / specDir;
float3 tmax = max(max(0, t1), t2); // max(0, ..) to avoid correction when outside the box (in the blending zone)
float t = min(tmax.x, min(tmax.y, tmax.z));
// Use Distance in WS directly to recover intersection
float3 hit_ls = pos_ls + specDir * t;
specDir = hit_ls - probeOffset;
}
mips = surface.clearCoatRoughness * mipd;
#ifdef C3D_SUPPORT_CUBE_ARRAY
LD = float3(probeTextureArray.sample(linearSampler, specDir, probeIndex, level(mips)).rgb);
#else
specUV = scn::dual_paraboloid_from_cartesian(normalize(specDir));
LD = float3(probeTextureArray.sample(linearSampler, specUV, probeIndex, level(mips)).rgb);
#endif
#if PROBES_NORMALIZATION
probesWeightedSum += float4(LD * intensity * probeReflectance, probeFactor) * surface.clearCoat;
#else
specular += LD * intensity * probeReflectance * surface.clearCoat;
#endif
#endif
}
void add_global_probe(float4x4 localDirToWorldCubemapDir, float environmentIntensity,
#ifdef C3D_SUPPORT_CUBE_ARRAY
texturecube_array<half> probeTextureArray
#else
texture2d_array<half> probeTextureArray
#endif
)
{
float3 n = surface.normal;
float3 v = surface.view;
float3 r = reflect(-v, n); // mirror vector (view vector around normal)
float3 specDir = scn::mat4_mult_float3(localDirToWorldCubemapDir, r);
float mipd = float(probeTextureArray.get_num_mip_levels()) - 1.f;
const float intensity = surface.ambientOcclusion * environmentIntensity;
float mips = surface.roughness * mipd;
#ifdef C3D_SUPPORT_CUBE_ARRAY
float3 LD = float3(probeTextureArray.sample(linearSampler, specDir, 0, level(mips)).rgb);
#else
float2 specUV = scn::dual_paraboloid_from_cartesian(normalize(specDir));
float3 LD = float3(probeTextureArray.sample(linearSampler, specUV, 0, level(mips)).rgb);
#endif
// radiance
specular += LD * intensity * probeReflectance;
}
void add_global_probe(texturecube<float, access::sample> specularLD,
float4x4 localDirToWorldCubemapDir,
float environmentIntensity)
{
float3 n = surface.normal;
float3 v = surface.view;
float3 r = reflect(-v, n); // mirror vector (view vector around normal)
float roughness = surface.roughness;
float roughness2= roughness * roughness;
#if 1
float smoothness = 1.0f - roughness2;
float specularLerpFactor = (1. - smoothness * (sqrt(smoothness) + roughness2));
// This does have an effect on smooth object : seems buggy (plus costly)
//float NoV = 1.f - saturate(dot(n, v));
//specularLerpFactor = saturate(specularLerpFactor + 2.f*NoV*NoV*NoV);
float3 specularDominantNDirection = mix(r, n, specularLerpFactor); // no need to normalize as we fetch in a cubemap
#else
float3 specularDominantNDirection = r;
#endif
// Specular
float mipLevel = roughness * float(specularLD.get_num_mip_levels() - 1);
#if 0 // Seamless cubemap filtering
float3 dirAbs = abs(specularDominantNDirection);
float dirNormInf = max(dirAbs.x, max(dirAbs.y, dirAbs.z));
float scale = 1.0f - exp2(mipLevel) / float(specularLD.get_width());
if (dirAbs.x != dirNormInf) specularDominantNDirection.x *= scale;
if (dirAbs.y != dirNormInf) specularDominantNDirection.y *= scale;
if (dirAbs.z != dirNormInf) specularDominantNDirection.z *= scale;
#endif
float3 LD = specularLD.sample(linearSampler, scn::mat4_mult_float3(localDirToWorldCubemapDir, specularDominantNDirection), level(mipLevel)).rgb;
#if 1 // Specular occlusion - not physically correct
float specularOcclusion = saturate(pow(NoV + surface.ambientOcclusion, exp2(-16.0f * roughness - 1.0f)) - 1.0f + surface.ambientOcclusion);
LD *= specularOcclusion;
#endif
// effectiveAlbedo is multiplied in combine
specular += LD * (surface.ambientOcclusion * environmentIntensity) * probeReflectance;
}
void add_global_probeClearCoat(texturecube<float, access::sample> specularLD,
float4x4 localDirToWorldCubemapDir,
float environmentIntensity)
{
float3 n = surface.clearCoatNormal;
float3 v = surface.view;
float3 r = reflect(-v, n); // mirror vector (view vector around normal)
float roughness = surface.clearCoatRoughness;
// Specular
float mipLevel = roughness * float(specularLD.get_num_mip_levels() - 1);
float3 LD = specularLD.sample(linearSampler, scn::mat4_mult_float3(localDirToWorldCubemapDir, r), level(mipLevel)).rgb;
LD *= surface.ambientOcclusion;
//energy preservation
float Fc = scn_brdf_F_opt(0.04f, NoVClearCoat).r * surface.clearCoat;
float attenuation = 1.0f - Fc;
specular *= (attenuation * attenuation);
// diffuse *= attenuation; //-> darken diffuse when clearCoatRoughness is high
specular += LD * environmentIntensity * probeReflectanceClearCoat * surface.clearCoat;
}
// MARK: Irradiance
void add_irradiance_from_selfIllum()
{
float selfIlluminationAO = saturate(mix(1.f, surface.ambientOcclusion, selfIlluminationOcclusion));
float3 irradiance = selfIlluminationAO * surface.selfIllumination.rgb;
diffuse += irradiance;
}
void add_global_irradiance_from_sh(float4x4 localDirToWorldCubemapDir,
#if defined(USE_PROBES_LIGHTING) && (USE_PROBES_LIGHTING == 2)
sh2_coefficients shCoefficients)
#else
sh3_coefficients shCoefficients)
#endif
{
float3 n_sh_space = scn::mat4_mult_float3(localDirToWorldCubemapDir, surface.normal);
float3 irradiance = shEvalDirection(float4(n_sh_space, 1.), shCoefficients);
diffuse += surface.ambientOcclusion * irradiance;
}
void add_global_irradiance_probe(texturecube<float, access::sample> irradianceTexture,
float4x4 localDirToWorldCubemapDir,
float environmentIntensity)
{
float3 n_cube_space = scn::mat4_mult_float3(localDirToWorldCubemapDir, surface.normal);
float3 irradiance = irradianceTexture.sample(linearSampler, n_cube_space).rgb;
diffuse += (surface.ambientOcclusion * environmentIntensity) * irradiance;
}
#endif // USE_PBR
// MARK: IES
static constexpr sampler iesSampler = sampler(filter::linear, mip_filter::none, address::clamp_to_edge);
float ies_attenuation(float3 l, scn_light light, texture2d<half> iesTexture)
{
#if USE_QUAT_FOR_IES
float3 v = scn::quaternion_rotate_vector(light.parameters.ies.light_from_view_quat, -l);
#else
float3 v = scn::matrix_rotate(light.parameters.ies.light_from_view, -l);
#endif
float phi = (v.z * light.parameters.ies.scaleBias.x + light.parameters.ies.scaleBias.y);
float theta = atan2(v.y, v.x) * 0.5f * M_1_PI_F;
return iesTexture.sample(iesSampler, float2(phi, abs(theta))).r;
}
void add_ies(scn_light light, texture2d<half> iesTexture)
{
float3 unnormalized_l = light.pos - surface.position;
float3 l = normalize(unnormalized_l);
float intensity = dist_attenuation(unnormalized_l, light);
intensity *= ies_attenuation(l, light, iesTexture);
shade(l, light.color.rgb, intensity);
}
void add_ies(scn_light light, texture2d<half> iesTexture, depth2d<float> shadowMap, constant float4* shadowKernel, int sampleCount)
{
float3 unnormalized_l = light.pos - surface.position;
float3 l = normalize(unnormalized_l);
float intensity = dist_attenuation(unnormalized_l, light);
intensity *= ies_attenuation(l, light, iesTexture);
intensity *= shadow(surface.position, light, shadowMap, shadowKernel, sampleCount);
shade(l, light.color.rgb, intensity);
}
// MARK: Area
void add_area_rectangle(scn_light light, texture2d_array<float> bakedDataTexture)
{
#ifdef USE_PBR
float3 v = surface.view;
float3 n = surface.normal;
float3 p = surface.position;
// construct orthonormal basis around N
float3 tangent = normalize(v - n * dot(v, n));
float3 bitangent = cross(n, tangent);
float3x3 shadingSpaceTransform = transpose(float3x3(tangent, n, bitangent));
float3 lightCenter = light.shadowMatrix[3].xyz;
// check if surface is visible from light
float sidedness = dot(light.dir, lightCenter - p);
if (light.parameters.area.rectangle.doubleSided == false && sidedness <= 0.f)
return;
float3 lightRight = light.shadowMatrix[0].xyz * light.parameters.area.rectangle.halfExtents.x * sign(sidedness);
float3 lightTop = light.shadowMatrix[1].xyz * light.parameters.area.rectangle.halfExtents.y;
float4x3 cornerDirections = float4x3((lightCenter + lightRight + lightTop) - p,
(lightCenter + lightRight - lightTop) - p,
(lightCenter - lightRight - lightTop) - p,
(lightCenter - lightRight + lightTop) - p);
cornerDirections[0] = shadingSpaceTransform * cornerDirections[0];
cornerDirections[1] = shadingSpaceTransform * cornerDirections[1];
cornerDirections[2] = shadingSpaceTransform * cornerDirections[2];
cornerDirections[3] = shadingSpaceTransform * cornerDirections[3];
float diffuseAmount = pbr_area_light_eval_rectangle(cornerDirections);
float brdfNorm = 1.f;
float3x3 inverseLTCMatrix = scn_sample_area_light_precomputed_data(v, n, surface.roughness, &brdfNorm, bakedDataTexture);
cornerDirections[0] = inverseLTCMatrix * cornerDirections[0];
cornerDirections[1] = inverseLTCMatrix * cornerDirections[1];
cornerDirections[2] = inverseLTCMatrix * cornerDirections[2];
cornerDirections[3] = inverseLTCMatrix * cornerDirections[3];
float specularAmount = brdfNorm * pbr_area_light_eval_rectangle(cornerDirections);
float3 lightColor = light.color.rgb;
diffuse += diffuseAmount * lightColor;
specular += specularAmount * lightColor * reflectance;
#endif
}
void add_area_polygon(scn_light light, texture2d_array<float> bakedDataTexture, device packed_float2 *vertexPositions)
{
#ifdef USE_PBR
float3 v = surface.view;
float3 n = surface.normal;
float3 p = surface.position;
// construct orthonormal basis around N
float3 tangent = normalize(v - n * dot(v, n));
float3 bitangent = cross(n, tangent);
float3x3 shadingSpaceTransform = transpose(float3x3(tangent, n, bitangent));
float3 lightCenter = light.shadowMatrix[3].xyz;
// check if surface is visible from light
float sidedness = dot(light.dir, lightCenter - p);
if (light.parameters.area.polygon.doubleSided == false && sidedness <= 0.f)
return;
float3 lightRight = light.shadowMatrix[0].xyz * sign(sidedness);
float3 lightTop = light.shadowMatrix[1].xyz;
p = shadingSpaceTransform * p;
lightCenter = shadingSpaceTransform * lightCenter;
lightRight = shadingSpaceTransform * lightRight;
lightTop = shadingSpaceTransform * lightTop;
float diffuseAmount = pbr_area_light_eval_polygon(p, lightCenter, lightRight, lightTop, light.parameters.area.polygon.vertexCount, vertexPositions);
float brdfNorm = 1.f;
float3x3 inverseLTCMatrix = scn_sample_area_light_precomputed_data(v, n, surface.roughness, &brdfNorm, bakedDataTexture);
p = inverseLTCMatrix * p;
lightCenter = inverseLTCMatrix * lightCenter;
lightRight = inverseLTCMatrix * lightRight;
lightTop = inverseLTCMatrix * lightTop;
float specularAmount = brdfNorm * pbr_area_light_eval_polygon(p, lightCenter, lightRight, lightTop, light.parameters.area.polygon.vertexCount, vertexPositions);
float3 effectiveAlbedo = mix(float3(1.0), float3(0.0), surface.metalness); // 1.f and not `albedo` because `SCNShaderLightingContribution.diffuse` will be multiplied by `pbr_surface.albedo` later in `scn_pbr_combine`
float3 reflectance = mix(float3(PBR_F0_NON_METALLIC), surface.diffuse.rgb, surface.metalness);
float3 lightColor = light.color.rgb;
diffuse += diffuseAmount * lightColor * effectiveAlbedo;
specular += specularAmount * lightColor * reflectance;
#endif
}
void add_area_line(scn_light light, texture2d_array<float> bakedDataTexture)
{
#ifdef USE_PBR
float3 v = surface.view;
float3 n = surface.normal;
float3 p = surface.position;
// construct orthonormal basis around N
float3 tangent = normalize(v - n * dot(v, n));
float3 bitangent = cross(n, tangent);
float3x3 shadingSpaceTransform = transpose(float3x3(tangent, n, bitangent));
float3 lightCenter = light.shadowMatrix[3].xyz;
float3 lightRight = light.shadowMatrix[0].xyz * light.parameters.area.line.halfLength;
float2x3 cornerDirections = float2x3((lightCenter + lightRight) - p,
(lightCenter - lightRight) - p);
cornerDirections[0] = shadingSpaceTransform * cornerDirections[0];
cornerDirections[1] = shadingSpaceTransform * cornerDirections[1];
float diffuseAmount = pbr_area_light_eval_line(cornerDirections);
float brdfNorm = 1.f;
float3x3 inverseLTCMatrix = scn_sample_area_light_precomputed_data(v, n, surface.roughness, &brdfNorm, bakedDataTexture);
cornerDirections[0] = inverseLTCMatrix * cornerDirections[0];
cornerDirections[1] = inverseLTCMatrix * cornerDirections[1];
float specularAmount = brdfNorm * pbr_area_light_eval_line(cornerDirections);
float3 ortho = normalize(cross(cornerDirections[0], cornerDirections[1]));
float ltcWidthFactor = 1.0 / length(scn_ltc_matrix_invert_transpose(inverseLTCMatrix) * ortho);
specularAmount *= ltcWidthFactor;
float3 lightColor = light.color.rgb;
diffuse += diffuseAmount * lightColor;
specular += specularAmount * lightColor * reflectance;
#endif
}
void add_area_ellipse(scn_light light, texture2d_array<float> bakedDataTexture)
{
#ifdef USE_PBR
#endif
}
void add_area_ellipsoid(scn_light light, texture2d_array<float> bakedDataTexture)
{
#ifdef USE_PBR
#endif
}
};
#endif // #if defined(__METAL_VERSION__) && __METAL_VERSION__ >= 120
enum C3DColorMask {
kC3DColorMaskRed = 0x1 << 3,
kC3DColorMaskGreen = 0x1 << 2,
kC3DColorMaskBlue = 0x1 << 1,
kC3DColorMaskAlpha = 0x1 << 0
};
inline float4 colorFromMask(float4 col, int mask)
{
switch (mask) {
case kC3DColorMaskRed: return col.r;
case kC3DColorMaskRed|kC3DColorMaskGreen: return float4(col.rg, 0.f, 1.f);
case kC3DColorMaskRed|kC3DColorMaskBlue: return float4(col.rb, 0.f, 1.f);
case kC3DColorMaskRed|kC3DColorMaskAlpha: return float4(col.ra, 0.f, 1.f);
case kC3DColorMaskGreen: return col.g;
case kC3DColorMaskGreen|kC3DColorMaskBlue: return float4(col.bg, 0.f, 1.f);
case kC3DColorMaskGreen|kC3DColorMaskAlpha: return float4(col.ag, 0.f, 1.f);
case kC3DColorMaskBlue: return col.b;
case kC3DColorMaskBlue|kC3DColorMaskAlpha: return float4(col.ab, 0.f, 1.f);
case kC3DColorMaskAlpha: return col.a;
}
return col;
}
#ifndef USE_PBR
inline float3 illuminate(SCNShaderSurface surface, SCNShaderLightingContribution lighting)
{
float3 albedo = surface.diffuse.rgb * surface.ambientOcclusion;
float3 color = lighting.diffuse * albedo;
#if defined(USE_AMBIENT_LIGHTING) && (defined(LOCK_AMBIENT_WITH_DIFFUSE) || defined(USE_AMBIENT_AS_AMBIENTOCCLUSION))
color += lighting.ambient * albedo;
#endif
#ifdef USE_SELFILLUMINATION
color += surface.diffuse.rgb * surface.selfIllumination.rgb;
#endif
// Do we want to clamp there ????
#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)) && !defined(DISABLE_SPECULAR)
color += S;
#endif
#if defined(USE_AMBIENT) && !defined(USE_AMBIENT_AS_AMBIENTOCCLUSION)
color += surface.ambient.rgb * lighting.ambient;
#endif
#ifdef USE_EMISSION
color += surface.emission.rgb;
#endif
#ifdef USE_MULTIPLY
color *= surface.multiply.rgb;
#endif
#ifdef USE_MODULATE
color *= lighting.modulate;
#endif
return color;
}
#endif
struct SCNShaderGeometry
{
float4 position;
float3 normal;
float4 tangent;
float4 color;
float pointSize;
float2 texcoords[8]; // MAX_UV
};
struct commonprofile_uniforms {
// [id(0)]]
float4 diffuseColor;
float4 specularColor;
float4 ambientColor;
float4 emissionColor;
float4 selfIlluminationColor;
float4 reflectiveColor;
float4 multiplyColor;
float4 transparentColor;
float clearCoat;
float clearCoatRoughness;
float3 clearCoatNormal;
float metalness;
// [id(12)]]
float roughness;
float diffuseIntensity;
float specularIntensity;
float normalIntensity;
float ambientIntensity;
float emissionIntensity;
float selfIlluminationIntensity;
float reflectiveIntensity;
float multiplyIntensity;
float transparentIntensity;
// [id(22)]]
float metalnessIntensity;
float roughnessIntensity;
float clearCoatIntensity;
float clearCoatRoughnessIntensity;
float clearCoatNormalIntensity;
float displacementIntensity;
float materialShininess;
float selfIlluminationOcclusion;
float transparency;
float3 fresnel; // x: ((n1-n2)/(n1+n2))^2 y:1-x z:exponent
#if USE_ARGUMENT_BUFFERS
//[[id(32)]]
texture2d<float> emissionTexture;
sampler emissionSampler;
texture2d<float> ambientTexture;
sampler ambientSampler;
//[[id(32)]]
texture2d<float> diffuseTexture;
sampler diffuseSampler;
texture2d<float> specularTexture;
sampler specularSampler;
#if defined(USE_REFLECTIVE_CUBEMAP)
texturecube<float> reflectiveTexture;
#else
texture2d<float> reflectiveTexture;
#endif
sampler reflectiveSampler;
texture2d<float> transparentTexture;
sampler transparentSampler;
texture2d<float> multiplyTexture;
sampler multiplySampler;
//[[id(43)]]
texture2d<float> normalTexture;
sampler normalSampler;
texture2d<float> selfIlluminationTexture;
sampler selfIlluminationSampler;
texture2d<float> metalnessTexture;
sampler metalnessSampler;
texture2d<float> roughnessTexture;
sampler roughnessSampler;
texture2d<float> displacementTexture;
sampler displacementSampler;
//[[id(53)]]
#endif // USE_ARGUMENT_BUFFERS
#ifdef TEXTURE_TRANSFORM_COUNT
float4x4 textureTransforms[TEXTURE_TRANSFORM_COUNT];
#endif
};
#ifdef USE_OPENSUBDIV
struct osd_packed_vertex {
packed_float3 position;
#if defined(OSD_USER_VARYING_DECLARE_PACKED)
OSD_USER_VARYING_DECLARE_PACKED
#endif
};
#endif
#ifdef USE_DISPLACEMENT_MAP
static void applyDisplacement(texture2d<float> displacementTexture,
sampler displacementTextureSampler,
float2 displacementTexcoord,
thread SCNShaderGeometry& geometry,
constant commonprofile_uniforms& scn_commonprofile)
{
#ifdef USE_DISPLACEMENT_TEXTURE_COMPONENT
float altitude = colorFromMask(displacementTexture.sample(displacementTextureSampler, displacementTexcoord), USE_DISPLACEMENT_TEXTURE_COMPONENT).r;
#ifdef USE_DISPLACEMENT_INTENSITY
altitude *= scn_commonprofile.displacementIntensity;
#endif
#if defined(USE_NORMAL) && defined(HAS_OR_GENERATES_NORMAL)
float3 bitangent = geometry.tangent.w * normalize(cross(geometry.tangent.xyz, geometry.normal.xyz));
geometry.position.xyz += geometry.normal * altitude;
float3 offset = float3(1.f / displacementTexture.get_width(), 1.f / displacementTexture.get_height(), 0.f);
float3 h;
h.x = colorFromMask(displacementTexture.sample(displacementTextureSampler, displacementTexcoord), USE_DISPLACEMENT_TEXTURE_COMPONENT).r;
h.y = colorFromMask(displacementTexture.sample(displacementTextureSampler, displacementTexcoord+offset.xz), USE_DISPLACEMENT_TEXTURE_COMPONENT).r;
h.z = colorFromMask(displacementTexture.sample(displacementTextureSampler, displacementTexcoord-offset.zy), USE_DISPLACEMENT_TEXTURE_COMPONENT).r;
#ifdef USE_DISPLACEMENT_INTENSITY
h *= scn_commonprofile.displacementIntensity;
#endif
float3 n = normalize( float3( (h.x - h.y)/offset.x, 1., (h.x - h.z)/offset.y) );
geometry.normal = geometry.tangent.xyz * n.x + geometry.normal.xyz * n.y + bitangent.xyz * n.z;
geometry.tangent.xyz = normalize(cross(bitangent, geometry.normal));
#endif // USE_NORMAL
#else // USE_DISPLACEMENT_TEXTURE_COMPONENT
float3 displacement = displacementTexture.sample(displacementTextureSampler, displacementTexcoord).rgb;
#ifdef USE_DISPLACEMENT_INTENSITY
displacement *= scn_commonprofile.displacementIntensity;
#endif
#if defined(USE_NORMAL) && defined(HAS_OR_GENERATES_NORMAL)
float3 bitangent = geometry.tangent.w * normalize(cross(geometry.tangent.xyz, geometry.normal.xyz));
geometry.position.xyz += geometry.tangent.xyz * displacement.x + geometry.normal.xyz * displacement.y + bitangent.xyz * displacement.z;
float3 offset = float3(1.f / displacementTexture.get_width(), 1.f / displacementTexture.get_height(), 0.f);
float3 a = displacementTexture.sample(displacementTextureSampler, displacementTexcoord).rgb;
float3 b = displacementTexture.sample(displacementTextureSampler, displacementTexcoord+offset.xz).rgb;
float3 c = displacementTexture.sample(displacementTextureSampler, displacementTexcoord+offset.zy).rgb;
#ifdef USE_DISPLACEMENT_INTENSITY
a *= scn_commonprofile.displacementIntensity;
b *= scn_commonprofile.displacementIntensity;
c *= scn_commonprofile.displacementIntensity;
#endif
b += offset.xzz;
c -= offset.zzy;
float3 n = (normalize( cross( b-a, c-a ) ));
geometry.normal = geometry.tangent.xyz * n.x + geometry.normal.xyz * n.y + bitangent.xyz * n.z;
geometry.tangent.xyz = normalize(cross(bitangent, geometry.normal));
#endif // USE_NORMAL
#endif // USE_DISPLACEMENT_TEXTURE_COMPONENT
}
#endif // USE_DISPLACEMENT_MAP
#ifdef USE_OUTLINE
static inline float hash(float2 p)
{
const float2 kMod2 = float2(443.8975f, 397.2973f);
p = fract(p * kMod2);
p += dot(p.xy, p.yx+19.19f);
return fract(p.x * p.y);
}
#endif
} // namespace
using namespace NAMESPACE_HASH;
//
// MARK: - Vertex and post-tessellation vertex functions
//
#if defined(USE_TESSELLATION)
struct scn_patch_t {
patch_control_point<scn_vertex_t> controlPoints;
};
#endif
#if defined(USE_OPENSUBDIV)
#if OSD_IS_ADAPTIVE
[[ patch(quad, VERTEX_CONTROL_POINTS_PER_PATCH) ]]
#endif
#elif defined(USE_TESSELLATION)
[[ patch(triangle, 3) ]]
#endif
vertex commonprofile_io commonprofile_vert(
#if !defined(USE_TESSELLATION)
scn_vertex_t in [[ stage_in ]]
, uint scn_vertexID [[ vertex_id ]]
#else // USE_TESSELLATION
#ifdef USE_OPENSUBDIV
#if OSD_IS_ADAPTIVE
#if USE_STAGE_IN
PatchInput patchInput [[ stage_in ]]
#else
OsdVertexBufferSet patchInput
#endif
, float2 patchCoord [[ position_in_patch ]]
, uint patchID [[ patch_id ]]
, constant float& osdTessellationLevel [[ buffer(TESSELLATION_LEVEL_BUFFER_INDEX) ]]
#else // OSD_IS_ADAPTIVE
device unsigned const* osdIndicesBuffer [[ buffer(INDICES_BUFFER_INDEX) ]]
, device osd_packed_vertex const* osdVertexBuffer [[ buffer(VERTEX_BUFFER_INDEX) ]]
, uint vertexID [[ vertex_id ]]
#endif // OSD_IS_ADAPTIVE
#if defined(OSD_FVAR_WIDTH)
#if OSD_FVAR_USES_MULTIPLE_CHANNELS
, constant uint32_t& osdFaceVaryingChannelCount [[ buffer(OSD_FVAR_CHANNELS_CHANNEL_COUNT_INDEX) ]]
, device OsdFVarChannelDesc const* osdFaceVaryingChannelDescriptors [[ buffer(OSD_FVAR_CHANNELS_CHANNEL_DESCRIPTORS_INDEX) ]]
, constant uint32_t& osdFaceVaryingPatchArrayIndex [[ buffer(OSD_FVAR_CHANNELS_PATCH_ARRAY_INDEX_BUFFER_INDEX) ]]
, device void const* osdFaceVaryingChannelsPackedData [[ buffer(OSD_FVAR_CHANNELS_PACKED_DATA_BUFFER_INDEX) ]]
#else
, device float const* osdFaceVaryingData [[ buffer(OSD_FVAR_DATA_BUFFER_INDEX) ]]
, device int const* osdFaceVaryingIndices [[ buffer(OSD_FVAR_INDICES_BUFFER_INDEX) ]]
#if OSD_IS_ADAPTIVE
, device packed_int3 const* osdFaceVaryingPatchParams [[ buffer(OSD_FVAR_PATCHPARAM_BUFFER_INDEX) ]]
, constant packed_int4& osdFaceVaryingPatchArray [[ buffer(OSD_FVAR_PATCH_ARRAY_BUFFER_INDEX) ]]
#endif
#endif //OSD_FVAR_USES_MULTIPLE_CHANNELS
#endif //defined(OSD_FVAR_WIDTH)
#else // USE_OPENSUBDIV
scn_patch_t in [[ stage_in ]]
, float3 patchCoord [[ position_in_patch ]]
#endif // USE_OPENSUBDIV
#endif // USE_TESSELLATION
#ifdef USE_MULTIPLE_RENDERING
, device SCNSceneBuffer* scn_frames [[ buffer(0) ]]
#else
, constant SCNSceneBuffer& scn_frame [[ buffer(0) ]]
#endif
#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) ]]
#else
, constant commonprofile_node& scn_node [[ buffer(1) ]]
#endif
#ifdef USE_PER_VERTEX_LIGHTING
, constant scn_light* scn_lights [[ buffer(2) ]]
, constant float4* u_shadowKernel
, texture2d_array<float> u_areaLightBakedDataTexture
#endif
// used for texture transform and materialShininess in case of perVertexLighting
, constant commonprofile_uniforms& scn_commonprofile
, uint scn_instanceID [[ instance_id ]]
#ifdef USE_POINT_RENDERING
// x:pointSize, y:minimumScreenSize, z:maximumScreenSize
, constant float3& scn_pointSize
#endif
#ifdef USE_DISPLACEMENT_MAP
#if USE_ARGUMENT_BUFFERS
#define u_displacementTexture scn_commonprofile.displacementTexture
#define u_displacementTextureSampler scn_commonprofile.displacementSampler
#else
, texture2d<float> u_displacementTexture
, sampler u_displacementTextureSampler
#endif //USE_ARGUMENT_BUFFERS
#endif //USE_DISPLACEMENT_MAP
#ifdef USE_VERTEX_EXTRA_ARGUMENTS
#endif
)
{
commonprofile_io out;
#ifdef USE_MULTIPLE_RENDERING
out.sliceIndex = scn_instanceID % USE_MULTIPLE_RENDERING;
device SCNSceneBuffer& scn_frame = scn_frames[0];
device SCNSceneBuffer& scn_frame_slice = scn_frames[out.sliceIndex];
#ifdef USE_INSTANCING
device commonprofile_node& scn_node = scn_nodeInstances[scn_instanceID / USE_MULTIPLE_RENDERING];
#endif
#else
#ifdef USE_INSTANCING
device commonprofile_node& scn_node = scn_nodeInstances[scn_instanceID];
#endif
#endif
#ifdef USE_TESSELLATION
uint scn_vertexID; // we need scn_vertexID if a geometry modifier is used
scn_vertexID = 0;
#endif
//
// MARK: Populating the `_geometry` struct
//
SCNShaderGeometry _geometry;
#if !defined(USE_TESSELLATION)
// OPTIM in could be already float4?
_geometry.position = float4(in.position, 1.f);
#if defined(USE_NORMAL) && defined(HAS_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
#else // USE_TESSELLATION
#ifdef USE_OPENSUBDIV
#if OSD_IS_ADAPTIVE
#if USE_STAGE_IN
int3 patchParam = patchInput.patchParam;
#else
int3 patchParam = patchInput.patchParamBuffer[patchID];
#endif
int refinementLevel = OsdGetPatchRefinementLevel(patchParam);
float tessellationLevel = min(osdTessellationLevel, (float)OSD_MAX_TESS_LEVEL) / exp2((float)refinementLevel - 1);
OsdPatchVertex patchVertex = OsdComputePatch(tessellationLevel, patchCoord, patchID, patchInput);
#if defined(OSD_FVAR_WIDTH)
int patchIndex = OsdGetPatchIndex(patchID);
#if OSD_FVAR_USES_MULTIPLE_CHANNELS
OsdInterpolateFaceVarings(_geometry, patchCoord.xy, patchIndex, osdFaceVaryingChannelCount, osdFaceVaryingChannelDescriptors, osdFaceVaryingPatchArrayIndex, osdFaceVaryingChannelsPackedData);
#else
OsdInterpolateFaceVarings(_geometry, patchCoord.xy, patchIndex, osdFaceVaryingIndices, osdFaceVaryingData, osdFaceVaryingPatchParams, osdFaceVaryingPatchArray);
#endif
#endif
_geometry.position = float4(patchVertex.position, 1.f);
#if defined(USE_NORMAL)
_geometry.normal = patchVertex.normal;
#endif
#if defined(USE_TANGENT) || defined(USE_BITANGENT)
_geometry.tangent = float4(patchVertex.tangent, -1.f);
//_geometry.bitangent = patchVertex.bitangent;
#endif
#if defined(NEED_IN_TEXCOORD0) && (OSD_TEXCOORD0_INTERPOLATION_MODE == OSD_PRIMVAR_INTERPOLATION_MODE_USER_VARYING)
_geometry.texcoords[0] = patchVertex.texcoord0;
#endif
#if defined(NEED_IN_TEXCOORD1) && (OSD_TEXCOORD1_INTERPOLATION_MODE == OSD_PRIMVAR_INTERPOLATION_MODE_USER_VARYING)
_geometry.texcoords[1] = patchVertex.texcoord1;
#endif
#if defined(NEED_IN_TEXCOORD2) && (OSD_TEXCOORD2_INTERPOLATION_MODE == OSD_PRIMVAR_INTERPOLATION_MODE_USER_VARYING)
_geometry.texcoords[2] = patchVertex.texcoord2;
#endif
#if defined(NEED_IN_TEXCOORD3) && (OSD_TEXCOORD3_INTERPOLATION_MODE == OSD_PRIMVAR_INTERPOLATION_MODE_USER_VARYING)
_geometry.texcoords[3] = patchVertex.texcoord3;
#endif
#if defined(NEED_IN_TEXCOORD4) && (OSD_TEXCOORD4_INTERPOLATION_MODE == OSD_PRIMVAR_INTERPOLATION_MODE_USER_VARYING)
_geometry.texcoords[4] = patchVertex.texcoord4;
#endif
#if defined(NEED_IN_TEXCOORD5) && (OSD_TEXCOORD5_INTERPOLATION_MODE == OSD_PRIMVAR_INTERPOLATION_MODE_USER_VARYING)
_geometry.texcoords[5] = patchVertex.texcoord5;
#endif
#if defined(NEED_IN_TEXCOORD6) && (OSD_TEXCOORD6_INTERPOLATION_MODE == OSD_PRIMVAR_INTERPOLATION_MODE_USER_VARYING)
_geometry.texcoords[6] = patchVertex.texcoord6;
#endif
#if defined(NEED_IN_TEXCOORD7) && (OSD_TEXCOORD7_INTERPOLATION_MODE == OSD_PRIMVAR_INTERPOLATION_MODE_USER_VARYING)
_geometry.texcoords[7] = patchVertex.texcoord7;
#endif
#if defined(HAS_VERTEX_COLOR) && (OSD_COLOR_INTERPOLATION_MODE == OSD_PRIMVAR_INTERPOLATION_MODE_USER_VARYING)
_geometry.color = patchVertex.color;
#endif
#else // OSD_IS_ADAPTIVE
#if OSD_PATCH_QUADS
const uint primitiveIndex = vertexID / 6;
#ifdef USE_NORMAL
float3 p0 = osdVertexBuffer[osdIndicesBuffer[primitiveIndex * 4 + 0]].position;
float3 p1 = osdVertexBuffer[osdIndicesBuffer[primitiveIndex * 4 + 1]].position;
float3 p2 = osdVertexBuffer[osdIndicesBuffer[primitiveIndex * 4 + 2]].position;
float3 normal = normalize(cross(p2 - p1, p0 - p1));
#endif
const uint triangleIndices[6] = { 0, 1, 2, 0, 2, 3 };
const uint quadVertexIndex = triangleIndices[vertexID % 6];
osd_packed_vertex osdVertex = osdVertexBuffer[osdIndicesBuffer[primitiveIndex * 4 + quadVertexIndex]];
#elif OSD_PATCH_TRIANGLES
const uint primitiveIndex = vertexID / 3;
#ifdef USE_NORMAL
float3 p0 = osdVertexBuffer[osdIndicesBuffer[primitiveIndex * 3 + 0]].position;
float3 p1 = osdVertexBuffer[osdIndicesBuffer[primitiveIndex * 3 + 1]].position;
float3 p2 = osdVertexBuffer[osdIndicesBuffer[primitiveIndex * 3 + 2]].position;
float3 normal = normalize(cross(p2 - p1, p0 - p1));
#endif
osd_packed_vertex osdVertex = osdVertexBuffer[osdIndicesBuffer[vertexID]];
#endif
float3 position = osdVertex.position;
#if defined(OSD_FVAR_WIDTH)
int patchIndex = OsdGetPatchIndex(primitiveIndex);
#if OSD_PATCH_QUADS
float2 quadUVs[4] = { float2(0,0), float2(1,0), float2(1,1), float2(0,1) };
#if OSD_FVAR_USES_MULTIPLE_CHANNELS
OsdInterpolateFaceVarings(_geometry, quadUVs[quadVertexIndex], patchIndex, osdFaceVaryingChannelCount, osdFaceVaryingChannelDescriptors, osdFaceVaryingPatchArrayIndex, osdFaceVaryingChannelsPackedData);
#else
OsdInterpolateFaceVarings(_geometry, quadUVs[quadVertexIndex], patchIndex, osdFaceVaryingIndices, osdFaceVaryingData);
#endif
#elif OSD_PATCH_TRIANGLES
//TODO
#endif
#endif //defined(OSD_FVAR_WIDTH)
#if defined(NEED_IN_TEXCOORD0) && (OSD_TEXCOORD0_INTERPOLATION_MODE == OSD_PRIMVAR_INTERPOLATION_MODE_USER_VARYING)
_geometry.texcoords[0] = osdVertex.texcoord0;
#endif
#if defined(NEED_IN_TEXCOORD1) && (OSD_TEXCOORD1_INTERPOLATION_MODE == OSD_PRIMVAR_INTERPOLATION_MODE_USER_VARYING)
_geometry.texcoords[1] = osdVertex.texcoord1;
#endif
#if defined(NEED_IN_TEXCOORD2) && (OSD_TEXCOORD2_INTERPOLATION_MODE == OSD_PRIMVAR_INTERPOLATION_MODE_USER_VARYING)
_geometry.texcoords[2] = osdVertex.texcoord2;
#endif
#if defined(NEED_IN_TEXCOORD3) && (OSD_TEXCOORD3_INTERPOLATION_MODE == OSD_PRIMVAR_INTERPOLATION_MODE_USER_VARYING)
_geometry.texcoords[3] = osdVertex.texcoord3;
#endif
#if defined(NEED_IN_TEXCOORD4) && (OSD_TEXCOORD4_INTERPOLATION_MODE == OSD_PRIMVAR_INTERPOLATION_MODE_USER_VARYING)
_geometry.texcoords[4] = osdVertex.texcoord4;
#endif
#if defined(NEED_IN_TEXCOORD5) && (OSD_TEXCOORD5_INTERPOLATION_MODE == OSD_PRIMVAR_INTERPOLATION_MODE_USER_VARYING)
_geometry.texcoords[5] = osdVertex.texcoord5;
#endif
#if defined(NEED_IN_TEXCOORD6) && (OSD_TEXCOORD6_INTERPOLATION_MODE == OSD_PRIMVAR_INTERPOLATION_MODE_USER_VARYING)
_geometry.texcoords[6] = osdVertex.texcoord6;
#endif
#if defined(NEED_IN_TEXCOORD7) && (OSD_TEXCOORD7_INTERPOLATION_MODE == OSD_PRIMVAR_INTERPOLATION_MODE_USER_VARYING)
_geometry.texcoords[7] = osdVertex.texcoord7;
#endif
#if defined(HAS_VERTEX_COLOR) && (OSD_COLOR_INTERPOLATION_MODE == OSD_PRIMVAR_INTERPOLATION_MODE_USER_VARYING)
_geometry.color = osdVertex.color;
#endif
_geometry.position = float4(position, 1.f);
#ifdef USE_NORMAL
_geometry.normal = normal;
#endif
#endif // OSD_IS_ADAPTIVE
#else // USE_OPENSUBDIV
//
// MARK: Geometry smooting
//
#if defined(TESSELLATION_SMOOTHING_MODE_PN_TRIANGLE) || defined(TESSELLATION_SMOOTHING_MODE_PHONG)
float3 P0 = in.controlPoints[0].position;
float3 P1 = in.controlPoints[1].position;
float3 P2 = in.controlPoints[2].position;
float3 N0 = in.controlPoints[0].normal;
float3 N1 = in.controlPoints[1].normal;
float3 N2 = in.controlPoints[2].normal;
#if defined(TESSELLATION_SMOOTHING_MODE_PN_TRIANGLE)
float3 position, normal;
scn_smooth_geometry_pn_triangle(position, normal, patchCoord, P0, P1, P2, N0, N1, N2);
#elif defined(TESSELLATION_SMOOTHING_MODE_PHONG)
float3 position, normal;
scn_smooth_geometry_phong(position, normal, patchCoord, P0, P1, P2, N0, N1, N2);
#endif
_geometry.position = float4(position, 1.f);
#ifdef USE_NORMAL
_geometry.normal = normal;
#endif
#else // GEOMETRY_SMOOTHING
// OPTIM in could be already float4?
_geometry.position = float4(scn::barycentric_mix(in.controlPoints[0].position, in.controlPoints[1].position, in.controlPoints[2].position, patchCoord), 1.f);
#if defined(USE_NORMAL) && defined(HAS_NORMAL)
_geometry.normal = normalize(scn::barycentric_mix(in.controlPoints[0].normal, in.controlPoints[1].normal, in.controlPoints[2].normal, patchCoord));
#endif
#endif // GEOMETRY_SMOOTHING
#if defined(USE_TANGENT) || defined(USE_BITANGENT)
_geometry.tangent = normalize(scn::barycentric_mix(in.controlPoints[0].tangent, in.controlPoints[1].tangent, in.controlPoints[2].tangent, patchCoord));
#endif
#ifdef NEED_IN_TEXCOORD0
_geometry.texcoords[0] = scn::barycentric_mix(in.controlPoints[0].texcoord0, in.controlPoints[1].texcoord0, in.controlPoints[2].texcoord0, patchCoord);
#endif
#ifdef NEED_IN_TEXCOORD1
_geometry.texcoords[1] = scn::barycentric_mix(in.controlPoints[0].texcoord1, in.controlPoints[1].texcoord1, in.controlPoints[2].texcoord1, patchCoord);
#endif
#ifdef NEED_IN_TEXCOORD2
_geometry.texcoords[2] = scn::barycentric_mix(in.controlPoints[0].texcoord2, in.controlPoints[1].texcoord2, in.controlPoints[2].texcoord2, patchCoord);
#endif
#ifdef NEED_IN_TEXCOORD3
_geometry.texcoords[3] = scn::barycentric_mix(in.controlPoints[0].texcoord3, in.controlPoints[1].texcoord3, in.controlPoints[2].texcoord3, patchCoord);
#endif
#ifdef NEED_IN_TEXCOORD4
_geometry.texcoords[4] = scn::barycentric_mix(in.controlPoints[0].texcoord4, in.controlPoints[1].texcoord4, in.controlPoints[2].texcoord4, patchCoord);
#endif
#ifdef NEED_IN_TEXCOORD5
_geometry.texcoords[5] = scn::barycentric_mix(in.controlPoints[0].texcoord5, in.controlPoints[1].texcoord5, in.controlPoints[2].texcoord5, patchCoord);
#endif
#ifdef NEED_IN_TEXCOORD6
_geometry.texcoords[6] = scn::barycentric_mix(in.controlPoints[0].texcoord6, in.controlPoints[1].texcoord6, in.controlPoints[2].texcoord6, patchCoord);
#endif
#ifdef NEED_IN_TEXCOORD7
_geometry.texcoords[7] = scn::barycentric_mix(in.controlPoints[0].texcoord7, in.controlPoints[1].texcoord7, in.controlPoints[2].texcoord7, patchCoord);
#endif
#ifdef HAS_VERTEX_COLOR
_geometry.color = scn::barycentric_mix(in.controlPoints[0].color, in.controlPoints[1].color, in.controlPoints[2].color, patchCoord);
#elif USE_VERTEX_COLOR
_geometry.color = float4(1.);
#endif
#endif // USE_OPENSUBDIV
#endif // USE_TESSELLATION
#ifdef USE_POINT_RENDERING
_geometry.pointSize = scn_pointSize.x;
#endif
#ifdef USE_TEXCOORD
#endif
#ifdef USE_DISPLACEMENT_MAP
applyDisplacement(u_displacementTexture, u_displacementTextureSampler, _displacementTexcoord, _geometry, scn_commonprofile);
#endif
//
// MARK: Skinning
//
#ifdef USE_SKINNING
#if !defined(USE_TESSELLATION)
{
float3 pos = 0.f;
#if defined(USE_NORMAL) && defined(HAS_NORMAL)
float3 nrm = 0.f;
#endif
#if defined(USE_TANGENT) || defined(USE_BITANGENT)
float3 tgt = 0.f;
#endif
for (int i = 0; i < MAX_BONE_INFLUENCES; ++i) {
#if MAX_BONE_INFLUENCES == 1
float weight = 1.f;
#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;
#if defined(USE_NORMAL) && defined(HAS_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;
#if defined(USE_NORMAL) && defined(HAS_NORMAL)
_geometry.normal = nrm;
#endif
#if defined(USE_TANGENT) || defined(USE_BITANGENT)
_geometry.tangent.xyz = tgt;
#endif
}
#else // USE_TESSELLATION
#if !defined(USE_OPENSUBDIV)
{
float3 pos[3] = {0.f, 0.f, 0.f};
#if defined(USE_NORMAL) && defined(HAS_NORMAL)
float3 nrm[3] = {0.f, 0.f, 0.f};
#endif
#if defined(USE_TANGENT) || defined(USE_BITANGENT)
float3 tgt[3] = {0.f, 0.f, 0.f};
#endif
for (int controlPointIndex = 0; controlPointIndex < 3; ++controlPointIndex) {
for (int i = 0; i < MAX_BONE_INFLUENCES; ++i) {
#if MAX_BONE_INFLUENCES == 1
float weight = 1.f;
#else
float weight = in.controlPoints[controlPointIndex].skinningWeights[i];
if (weight <= 0.f)
continue;
#endif
int idx = int(in.controlPoints[controlPointIndex].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[controlPointIndex] += (_geometry.position * jointMatrix).xyz * weight;
#if defined(USE_NORMAL) && defined(HAS_NORMAL)
nrm[controlPointIndex] += _geometry.normal * scn::mat3(jointMatrix) * weight;
#endif
#if defined(USE_TANGENT) || defined(USE_BITANGENT)
tgt[controlPointIndex] += _geometry.tangent.xyz * scn::mat3(jointMatrix) * weight;
#endif
}
}
_geometry.position.xyz = scn::barycentric_mix(pos[0], pos[1], pos[2], patchCoord);
#if defined(USE_NORMAL) && defined(HAS_NORMAL)
_geometry.normal = scn::barycentric_mix(nrm[0], nrm[1], nrm[2], patchCoord);
#endif
#if defined(USE_TANGENT) || defined(USE_BITANGENT)
_geometry.tangent.xyz = scn::barycentric_mix(tgt[0], tgt[1], tgt[2], patchCoord);
#endif
}
#endif // !defined(USE_OPENSUBDIV)
#endif // USE_TESSELLATION
#endif // USE_SKINNING
#ifdef USE_DISPLACEMENT_MAP
out.displacementTexcoord = _displacementTexcoord;
#endif
//
// MARK: Geometry shader modifier
//
#ifdef USE_GEOMETRY_MODIFIER
// DoGeometryModifier START
// DoGeometryModifier END
#endif
//
// MARK: Populating the `_surface` struct
//
// Transform the geometry elements in view space
#if defined(USE_POSITION) || (defined(USE_NORMAL) && defined(HAS_OR_GENERATES_NORMAL)) || defined(USE_TANGENT) || defined(USE_BITANGENT) || defined(USE_INSTANCING)
SCNShaderSurface _surface;
#endif
#if defined(USE_POSITION) || defined(USE_INSTANCING)
#ifdef USE_MULTIPLE_RENDERING
_surface.position = (scn_frame.viewTransform * (scn_node.modelTransform * _geometry.position)).xyz;
#else
_surface.position = (scn_node.modelViewTransform * _geometry.position).xyz;
#endif
#endif
#if defined(USE_NORMAL) && defined(HAS_OR_GENERATES_NORMAL)
#ifdef USE_MULTIPLE_RENDERING
#ifdef HINT_UNIFORM_SCALE
_surface.normal = (scn_frame.viewTransform * scn_node.modelTransform * float4(_geometry.normal,0.)).xyz;
#else
_surface.normal = normalize( (scn_frame.inverseTransposeViewTransform * scn_node.modelTransform * float4(_geometry.normal,0.)).xyz );
#endif
#else
float3x3 nrmTransform = scn::mat3(scn_node.modelViewTransform);
#ifdef HINT_UNIFORM_SCALE
_surface.normal = nrmTransform * _geometry.normal;
#else
float3 invScaleSquared = 1.f / float3(length_squared(nrmTransform[0]), length_squared(nrmTransform[1]), length_squared(nrmTransform[2]));
_surface.normal = normalize(nrmTransform * (_geometry.normal * invScaleSquared));
#endif
#endif
#endif
#if defined(USE_TANGENT) || defined(USE_BITANGENT)
#ifdef USE_MULTIPLE_RENDERING
_surface.tangent = normalize( (scn_frame.viewTransform * scn_node.modelTransform * float4(_geometry.tangent.xyz, 0.f)).xyz );
#else
_surface.tangent = normalize(scn::mat3(scn_node.modelViewTransform) * _geometry.tangent.xyz);
#endif
_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
//
// MARK: Per-vertex lighting
//
#ifdef USE_PER_VERTEX_LIGHTING
// Lighting
SCNShaderLightingContribution _lightingContribution(_surface, out);
_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) && defined(HAS_OR_GENERATES_NORMAL)
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
#if DEBUG_PIXEL
out.uv0 = in.texcoord0;
#endif
#ifdef USE_TEXCOORD
out.texcoord0 = _geometry.texcoords[0].xy;
#endif
//
// MARK: Determining the fragment position
//
#if defined(USE_POSITION) || defined(USE_INSTANCING)
#ifdef USE_MULTIPLE_RENDERING
out.fragmentPosition = scn_frame_slice.viewProjectionTransform * scn_node.modelTransform * _geometry.position;
#else
out.fragmentPosition = scn_frame.projectionTransform * float4(_surface.position, 1.);
#endif
#elif defined(USE_MODELVIEWPROJECTIONTRANSFORM) // this means that the geometry are still in model space : we can transform it directly to NDC space
#ifdef USE_MULTIPLE_RENDERING
out.fragmentPosition = scn_frame_slice.viewProjectionTransform * scn_node.modelTransform * _geometry.position;
#else
out.fragmentPosition = scn_node.modelViewProjectionTransform * _geometry.position;
#endif
#endif
#ifdef USE_NODE_OPACITY
out.nodeOpacity = scn_node.nodeOpacity;
#endif
#ifdef USE_POINT_RENDERING
float screenSize = _geometry.pointSize / out.fragmentPosition.w;
out.fragmentSize = clamp(screenSize, scn_pointSize.y, scn_pointSize.z);
#endif
#ifdef USE_MOTIONBLUR
float4 lastFrameFragmentPosition = scn_frame.lastFrameViewProjectionTransform * scn_node.lastFrameModelTransform * _geometry.position;
out.mv_fragment = out.fragmentPosition.xyw;
out.mv_lastFragment = lastFrameFragmentPosition.xyw;
#endif
#ifdef USE_OUTLINE
out.outlineHash = hash(scn_node.modelTransform[3].xy)+1.f/255.f;
#endif
return out;
}
//
// MARK: - Fragment shader function
//
struct SCNOutput
{
float4 color [[ color(0) ]];
#ifdef USE_COLOR1_OUTPUT
half4 color1 [[ color(1) ]];
#endif
#ifdef USE_NORMALS_OUTPUT
half4 normals [[ color(2) ]];
#endif
#ifdef USE_MOTIONBLUR
half4 motionblur [[ color(3) ]];
#endif
#ifdef USE_REFLECTANCE_ROUGHNESS_OUTPUT
half4 reflectanceRoughnessOutput [[ color(4) ]];
#endif
#ifdef USE_RADIANCE_OUTPUT
half4 radiance [[ color(5) ]];
#endif
};
fragment SCNOutput commonprofile_frag(commonprofile_io in [[ stage_in ]]
, constant commonprofile_uniforms& scn_commonprofile [[ buffer(0) ]]
#ifdef USE_MULTIPLE_RENDERING
, device SCNSceneBuffer* scn_frames [[ buffer(1) ]]
#else
, constant SCNSceneBuffer& scn_frame [[ buffer(1) ]]
#endif
, constant commonprofile_node& scn_node [[ buffer(2) ]]
#ifdef USE_PER_PIXEL_LIGHTING
, constant scn_light* scn_lights [[ buffer(3) ]]
, constant float4* u_shadowKernel
, texture2d_array<float> u_areaLightBakedDataTexture
#ifdef C3D_SUPPORT_CUBE_ARRAY
, texturecube_array<half> u_reflectionProbeTexture
#else
, texture2d_array<half> u_reflectionProbeTexture
#endif
, texture3d<ushort> u_clusterTexture
#ifdef C3D_USE_TEXTURE_FOR_LIGHT_INDICES
, texture1d<ushort> u_lightIndicesTexture
#else
, constant C3DLightIndexType* u_lightIndicesBuffer
#endif
#endif
#if USE_ARGUMENT_BUFFERS
#define u_emissionTexture scn_commonprofile.emissionTexture
#define u_emissionTextureSampler scn_commonprofile.emissionSampler
#define u_ambientTexture scn_commonprofile.ambientTexture
#define u_ambientTextureSampler scn_commonprofile.ambientSampler
#define u_diffuseTexture scn_commonprofile.diffuseTexture
#define u_diffuseTextureSampler scn_commonprofile.diffuseSampler
#define u_specularTexture scn_commonprofile.specularTexture
#define u_specularTextureSampler scn_commonprofile.specularSampler
#define u_reflectiveTexture scn_commonprofile.reflectiveTexture
#define u_reflectiveTextureSampler scn_commonprofile.reflectiveSampler
#define u_transparentTexture scn_commonprofile.transparentTexture
#define u_transparentTextureSampler scn_commonprofile.transparentSampler
#define u_multiplyTexture scn_commonprofile.multiplyTexture
#define u_multiplyTextureSampler scn_commonprofile.multiplySampler
#define u_normalTexture scn_commonprofile.normalTexture
#define u_normalTextureSampler scn_commonprofile.normalSampler
#define u_selfIlluminationTexture scn_commonprofile.selfIlluminationTexture
#define u_selfIlluminationTextureSampler scn_commonprofile.selfIlluminationSampler
#define u_metalnessTexture scn_commonprofile.metalnessTexture
#define u_metalnessTextureSampler scn_commonprofile.metalnessSampler
#define u_roughnessTexture scn_commonprofile.roughnessTexture
#define u_roughnessTextureSampler scn_commonprofile.roughnessSampler
#else
#ifdef USE_EMISSION_MAP
, texture2d<float> u_emissionTexture
, sampler u_emissionTextureSampler
#endif
#ifdef USE_AMBIENT_MAP
, texture2d<float> u_ambientTexture
, sampler u_ambientTextureSampler
#endif
#ifdef USE_DIFFUSE_MAP
, texture2d<float> u_diffuseTexture
, sampler u_diffuseTextureSampler
#endif
#ifdef USE_SPECULAR_MAP
, texture2d<float> u_specularTexture
, sampler u_specularTextureSampler
#endif
#ifdef USE_REFLECTIVE_MAP
, texture2d<float> u_reflectiveTexture
, sampler u_reflectiveTextureSampler
#elif defined(USE_REFLECTIVE_CUBEMAP)
, texturecube<float> u_reflectiveTexture
, sampler u_reflectiveTextureSampler
#endif
#ifdef USE_TRANSPARENT_MAP
, texture2d<float> u_transparentTexture
, sampler u_transparentTextureSampler
#endif
#ifdef USE_MULTIPLY_MAP
, texture2d<float> u_multiplyTexture
, sampler u_multiplyTextureSampler
#endif
#ifdef USE_NORMAL_MAP
, texture2d<float> u_normalTexture
, sampler u_normalTextureSampler
#endif
#ifdef USE_SELFILLUMINATION_MAP
, texture2d<float> u_selfIlluminationTexture
, sampler u_selfIlluminationTextureSampler
#endif
#ifdef USE_DISPLACEMENT_MAP
, texture2d<float> u_displacementTexture
, sampler u_displacementTextureSampler
#endif
#ifdef USE_PBR
#ifdef USE_METALNESS_MAP
, texture2d<float> u_metalnessTexture
, sampler u_metalnessTextureSampler
#endif
#ifdef USE_ROUGHNESS_MAP
, texture2d<float> u_roughnessTexture
, sampler u_roughnessTextureSampler
#endif
#ifdef USE_CLEARCOAT_MAP
, texture2d<float> u_clearCoatTexture
, sampler u_clearCoatTextureSampler
#endif
#ifdef USE_CLEARCOATROUGHNESS_MAP
, texture2d<float> u_clearCoatRoughnessTexture
, sampler u_clearCoatRoughnessTextureSampler
#endif
#ifdef USE_CLEARCOATNORMAL_MAP
, texture2d<float> u_clearCoatNormalTexture
, sampler u_clearCoatNormalTextureSampler
#endif
#endif // USE_PBR
#endif // USE_ARGUMENT_BUFFERS
#ifdef USE_PBR
, texturecube<float> u_radianceTexture
, texture2d<float> u_specularDFGTexture
#if !defined(USE_SELFILLUMINATION_MAP)
, texturecube<float> u_irradianceTexture
#endif
#endif // USE_PBR
#ifdef USE_SSAO
, texture2d<float> u_ssaoTexture
#endif
#ifdef USE_FRAGMENT_EXTRA_ARGUMENTS
#endif
#if defined(USE_DOUBLE_SIDED)
, bool isFrontFacing [[front_facing]]
#endif
#ifdef USE_POINT_RENDERING
, float2 pointCoord [[point_coord]]
#endif
)
{
#ifdef USE_MULTIPLE_RENDERING
device SCNSceneBuffer& scn_frame = scn_frames[0];
device SCNSceneBuffer& scn_frame_slice = scn_frames[in.sliceIndex];
#endif
SCNOutput _output;
//
// MARK: Populating the `_surface` struct
//
SCNShaderSurface _surface;
#ifdef USE_TEXCOORD
_surface.diffuseTexcoord = in.texcoord0;
#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.f, _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
#if defined(USE_AMBIENT_TEXTURE_COMPONENT)
_surface.ambient = colorFromMask(_surface.ambient, USE_AMBIENT_TEXTURE_COMPONENT).r;
#endif
#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);
#if defined(USE_DIFFUSE_TEXTURE_COMPONENT)
_surface.diffuse = colorFromMask(_surface.diffuse, USE_DIFFUSE_TEXTURE_COMPONENT);
#endif
#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.f,0.f,0.f,1.f);
#endif
#if defined(USE_DIFFUSE) && defined(USE_VERTEX_COLOR)
_surface.diffuse.rgb *= in.vertexColor.rgb;
_surface.diffuse *= in.vertexColor.a; // vertex color are not premultiplied to allow interpolation
#endif
#ifdef USE_SPECULAR_MAP
_surface.specular = u_specularTexture.sample(u_specularTextureSampler, _surface.specularTexcoord);
#if defined(USE_SPECULAR_TEXTURE_COMPONENT)
_surface.specular = colorFromMask(_surface.specular, USE_SPECULAR_TEXTURE_COMPONENT);
#endif
#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.f);
#endif
#ifdef USE_CLEARCOAT_MAP
_surface.clearCoat = u_clearCoatTexture.sample(u_clearCoatTextureSampler, _surface.clearCoatTexcoord).r;
#if defined(USE_CLEARCOAT_TEXTURE_COMPONENT)
_surface.clearCoat = colorFromMask(_surface.clearCoat, USE_CLEARCOAT_TEXTURE_COMPONENT).r;
#endif
#ifdef USE_CLEARCOAT_INTENSITY
_surface.clearCoat *= scn_commonprofile.clearCoatIntensity;
#endif
#elif defined(USE_CLEARCOAT_COLOR)
_surface.clearCoat = scn_commonprofile.clearCoat;
#elif defined(USE_CLEARCOAT)
_surface.clearCoat = 0.f;
#endif
#ifdef USE_CLEARCOATROUGHNESS_MAP
#if defined(USE_CLEARCOATROUGHNESS_TEXTURE_COMPONENT)
_surface.clearCoatRoughness = colorFromMask(u_clearCoatRoughnessTexture.sample(u_clearCoatRoughnessTextureSampler, _surface.clearCoatRoughnessTexcoord), USE_CLEARCOATROUGHNESS_TEXTURE_COMPONENT).r;
#else
_surface.clearCoatRoughness = u_clearCoatRoughnessTexture.sample(u_clearCoatRoughnessTextureSampler, _surface.clearCoatRoughnessTexcoord).r;
#endif
#ifdef USE_CLEARCOATROUGHNESS_INTENSITY
_surface.clearCoatRoughness *= scn_commonprofile.clearCoatRoughnessIntensity;
#endif
#elif defined(USE_CLEARCOATROUGHNESS_COLOR)
_surface.clearCoatRoughness = scn_commonprofile.clearCoatRoughness;
#else
_surface.clearCoatRoughness = 0.03f;
#endif
#ifdef USE_EMISSION_MAP
_surface.emission = u_emissionTexture.sample(u_emissionTextureSampler, _surface.emissionTexcoord);
#if defined(USE_EMISSION_TEXTURE_COMPONENT)
_surface.emission = colorFromMask(_surface.emission, USE_EMISSION_TEXTURE_COMPONENT);
#endif
#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_SELFILLUMINATION_MAP
_surface.selfIllumination = u_selfIlluminationTexture.sample(u_selfIlluminationTextureSampler, _surface.selfIlluminationTexcoord);
#if defined(USE_SELFILLUMINATION_TEXTURE_COMPONENT)
_surface.selfIllumination = colorFromMask(_surface.selfIllumination, USE_SELFILLUMINATION_TEXTURE_COMPONENT);
#endif
#ifdef USE_SELFILLUMINATION_INTENSITY
_surface.selfIllumination *= scn_commonprofile.selfIlluminationIntensity;
#endif
#elif defined(USE_SELFILLUMINATION_COLOR)
_surface.selfIllumination = scn_commonprofile.selfIlluminationColor;
#elif defined(USE_SELFILLUMINATION)
_surface.selfIllumination = float4(0.);
#endif
#ifdef USE_MULTIPLY_MAP
_surface.multiply = u_multiplyTexture.sample(u_multiplyTextureSampler, _surface.multiplyTexcoord);
#if defined(USE_MULTIPLY_TEXTURE_COMPONENT)
_surface.multiply = colorFromMask(_surface.multiply, USE_MULTIPLY_TEXTURE_COMPONENT);
#endif
#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);
#if defined(USE_TRANSPARENT_TEXTURE_COMPONENT)
_surface.transparent = colorFromMask(_surface.transparent, USE_TRANSPARENT_TEXTURE_COMPONENT);
#endif
#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.f);
#endif
#ifdef USE_METALNESS_MAP
#if defined(USE_METALNESS_TEXTURE_COMPONENT)
_surface.metalness = colorFromMask(u_metalnessTexture.sample(u_metalnessTextureSampler, _surface.metalnessTexcoord), USE_METALNESS_TEXTURE_COMPONENT).r;
#else
_surface.metalness = u_metalnessTexture.sample(u_metalnessTextureSampler, _surface.metalnessTexcoord).r;
#endif
#ifdef USE_METALNESS_INTENSITY
_surface.metalness *= scn_commonprofile.metalnessIntensity;
#endif
#elif defined(USE_METALNESS_COLOR)
_surface.metalness = scn_commonprofile.metalness;
#else
_surface.metalness = 0.f;
#endif
#ifdef USE_ROUGHNESS_MAP
#if defined(USE_ROUGHNESS_TEXTURE_COMPONENT)
_surface.roughness = colorFromMask(u_roughnessTexture.sample(u_roughnessTextureSampler, _surface.roughnessTexcoord), USE_ROUGHNESS_TEXTURE_COMPONENT).r;
#else
_surface.roughness = u_roughnessTexture.sample(u_roughnessTextureSampler, _surface.roughnessTexcoord).r;
#endif
#ifdef USE_ROUGHNESS_INTENSITY
_surface.roughness *= scn_commonprofile.roughnessIntensity;
#endif
#elif defined(USE_ROUGHNESS_COLOR)
_surface.roughness = scn_commonprofile.roughness;
#else
_surface.roughness = 0.f;
#endif
#if (defined USE_POSITION) && (USE_POSITION == 2)
_surface.position = in.position;
#endif
#if (defined USE_NORMAL) && (USE_NORMAL == 2)
#if defined(HAS_NORMAL) || defined(USE_OPENSUBDIV)
#ifdef USE_DOUBLE_SIDED
_surface.geometryNormal = normalize(in.normal.xyz) * (isFrontFacing ? 1.f : -1.f );
#else
_surface.geometryNormal = normalize(in.normal.xyz);
#endif
#else // need to generate the normal from the derivatives
_surface.geometryNormal = normalize( cross(dfdy( _surface.position ), dfdx( _surface.position ) ));
#endif
_surface.normal = _surface.geometryNormal;
_surface.clearCoatNormal = _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_VIEW) && (USE_VIEW == 2)
_surface.view = normalize(-in.position);
#endif
#if defined(USE_NORMAL_MAP)
{
float3x3 ts2vs = float3x3(_surface.tangent, _surface.bitangent, _surface.normal);
#ifdef USE_NORMAL_MAP
#if defined(USE_NORMAL_TEXTURE_COMPONENT)
_surface._normalTS.xy = colorFromMask(u_normalTexture.sample(u_normalTextureSampler, _surface.normalTexcoord), USE_NORMAL_TEXTURE_COMPONENT).rg * 2.f - 1.f;
_surface._normalTS.z = sqrt(1.f - saturate(length_squared(_surface._normalTS.xy)));
#else
_surface._normalTS = u_normalTexture.sample(u_normalTextureSampler, _surface.normalTexcoord).rgb;
_surface._normalTS = _surface._normalTS * 2.f - 1.f;
#endif
#ifdef USE_NORMAL_INTENSITY
_surface._normalTS = mix(float3(0.f, 0.f, 1.f), _surface._normalTS, scn_commonprofile.normalIntensity);
#endif
#else
_surface._normalTS = float3(0.f, 0.f, 1.f);
#endif
_surface.normal.rgb = normalize(ts2vs * _surface._normalTS.xyz );
}
#else
_surface._normalTS = float3(0.f, 0.f, 1.f);
#endif
#if defined(USE_CLEARCOATNORMAL_MAP)
{
//when there is no normal map, there seems to be no tangent and it breaks the clearCoatNormal...
float3x3 ts2vs = float3x3(_surface.tangent, _surface.bitangent, _surface.geometryNormal);
#ifdef USE_CLEARCOATNORMAL_MAP
#if defined(USE_CLEARCOATNORMAL_TEXTURE_COMPONENT)
_surface._clearCoatNormalTS.xy = colorFromMask(u_clearCoatNormalTexture.sample(u_clearCoatnormalTextureSampler, _surface.clearCoatNormalTexcoord), USE_CLEARCOATNORMAL_TEXTURE_COMPONENT).rg * 2.f - 1.f;
_surface._clearCoatNormalTS.z = sqrt(1.f - saturate(length_squared(_surface._clearCoatNormalTS.xy)));
#else
_surface._clearCoatNormalTS = u_clearCoatNormalTexture.sample(u_clearCoatNormalTextureSampler, _surface.clearCoatNormalTexcoord).rgb;
_surface._clearCoatNormalTS = _surface._clearCoatNormalTS * 2.f - 1.f;
#endif
#ifdef USE_CLEARCOATNORMAL_INTENSITY
_surface._clearCoatNormalTS = mix(float3(0.f, 0.f, 1.f), _surface._clearCoatNormalTS, scn_commonprofile.clearCoatNormalIntensity);
#endif
#else
_surface._clearCoatNormalTS = float3(0.f, 0.f, 1.f);
#endif
_surface.clearCoatNormal.rgb = normalize(ts2vs * _surface._clearCoatNormalTS.xyz );
}
#else
_surface._clearCoatNormalTS = float3(0.f, 0.f, 1.f);
#endif
#ifdef USE_REFLECTIVE_MAP
float3 refl = reflect( -_surface.view, _surface.normal );
float m = 2.f * sqrt( refl.x*refl.x + refl.y*refl.y + (refl.z+1.f)*(refl.z+1.f));
_surface.reflective = u_reflectiveTexture.sample(u_reflectiveTextureSampler, float2(float2(refl.x,-refl.y) / m) + 0.5f);
#if defined(USE_REFLECTIVE_TEXTURE_COMPONENT)
_surface.reflective = colorFromMask(_surface.reflective, USE_REFLECTIVE_TEXTURE_COMPONENT).r;
#endif
#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.f - saturate(dot(_surface.view, _surface.normal)), scn_commonprofile.fresnel.z);
_surface.reflective *= _surface.fresnel;
#endif
#ifdef USE_SHININESS
_surface.shininess = scn_commonprofile.materialShininess;
#endif
//
// MARK: Surface shader modifier
//
#ifdef USE_SURFACE_MODIFIER
// DoSurfaceModifier START
// DoSurfaceModifier END
#endif
//
// MARK: Lighting
//
SCNShaderLightingContribution _lightingContribution(_surface, in);
#ifdef USE_LIGHT_MODIFIER
#endif
#ifdef USE_AMBIENT_LIGHTING
_lightingContribution.ambient = scn_frame.ambientLightingColor.rgb;
#endif
#ifdef USE_LIGHTING
#ifdef USE_PER_PIXEL_LIGHTING
#ifdef USE_CLUSTERED_LIGHTING
uint3 clusterIndex;
clusterIndex.xy = uint2(in.fragmentPosition.xy * scn_frame.clusterScale.xy); // TODO Multiple rendering
clusterIndex.z = in.position.z * scn_frame.clusterScale.z + scn_frame.clusterScale.w; // scale/bias
// x:offset y:spot<<8|omni z:area?|probe w:????
ushort4 cluster_offset_count = u_clusterTexture.read(clusterIndex);
int lid = cluster_offset_count.x;
#endif
#ifdef USE_PBR
_lightingContribution.prepareForPBR(u_specularDFGTexture, scn_commonprofile.selfIlluminationOcclusion);
#ifdef USE_CLEARCOAT
_lightingContribution.prepareForPBRClearCoat(u_specularDFGTexture);
#endif
// Irradiance
#ifdef USE_SELFILLUMINATION
_lightingContribution.add_irradiance_from_selfIllum();
#else
#ifdef USE_PROBES_LIGHTING // Irradiance SH
_lightingContribution.add_global_irradiance_from_sh(scn_frame.viewToCubeTransform, scn_node.shCoefficients);
#else
_lightingContribution.add_global_irradiance_probe(u_irradianceTexture, scn_frame.viewToCubeTransform, scn_frame.environmentIntensity);
#endif
#endif
// Radiance
#ifndef DISABLE_SPECULAR
#ifdef C3D_USE_REFLECTION_PROBES
int probe_count = (cluster_offset_count.z & 0xff);
for (int i = 0 ; i < probe_count; ++i, ++lid) {
_lightingContribution.add_local_probe(scn_lights[LightIndex(lid)], u_reflectionProbeTexture);
}
#if PROBES_NORMALIZATION
#if PROBES_OUTER_BLENDING
_lightingContribution.specular += _lightingContribution.probesWeightedSum.rgb / max(1.f, _lightingContribution.probesWeightedSum.a);
#else
_lightingContribution.specular += _lightingContribution.probesWeightedSum.rgb / _lightingContribution.probesWeightedSum.a;
#endif
float globalFactor = saturate(1.f - _lightingContribution.probesWeightedSum.a);
#else
float globalFactor = _lightingContribution.probeRadianceRemainingFactor;
#endif
_lightingContribution.add_global_probe(scn_frame.viewToCubeTransform, globalFactor * scn_frame.environmentIntensity,
u_reflectionProbeTexture);
#else // Global Probe
_lightingContribution.add_global_probe(u_radianceTexture, scn_frame.viewToCubeTransform, scn_frame.environmentIntensity);
#ifdef USE_CLEARCOAT
_lightingContribution.add_global_probeClearCoat(u_radianceTexture, scn_frame.viewToCubeTransform, scn_frame.environmentIntensity);
#endif
#endif // C3D_USE_REFLECTION_PROBES
#endif // DISABLE_SPECULAR
#endif // USE_PBR
#if DEBUG_PIXEL
switch (DEBUG_PIXEL) {
case 1: _output.color = float4(_surface.normal * 0.5f + 0.5f, 1.f); break;
case 2: _output.color = float4(_surface.geometryNormal * 0.5f + 0.5f, 1.f); break;
case 3: _output.color = float4(_surface.tangent * 0.5f + 0.5f, 1.f); break;
case 4: _output.color = float4(in.uv0, 0.f, 1.f); break;
case 5: _output.color = float4(_surface.diffuse.rgb, 1.f); break;
case 6: _output.color = float4(float3(_surface.roughness), 1.f); break;
case 7: _output.color = float4(float3(_surface.metalness), 1.f); break;
case 8: _output.color = float4(float3(_surface.ambientOcclusion), 1.f); break;
default: break;
}
return _output;
#endif
_lightingContribution.add_directional(scn_lights[0]);
#ifdef USE_CLUSTERED_LIGHTING
// Omni lights
int omni_count = cluster_offset_count.y & 0xff;
for (int i = 0 ; i < omni_count; ++i, ++lid) {
_lightingContribution.add_local_omni(scn_lights[LightIndex(lid)]);
}
// Spot lights
int spot_count = (cluster_offset_count.y >> 8);
for (int i = 0 ; i < spot_count; ++i, ++lid) {
_lightingContribution.add_local_spot(scn_lights[LightIndex(lid)]);
}
#endif
#else // USE_PER_PIXEL_LIGHTING
_lightingContribution.diffuse = in.diffuse;
#ifdef USE_SPECULAR
_lightingContribution.specular = in.specular;
#endif
#endif // USE_PER_PIXEL_LIGHTING
#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
//
// MARK: Populating the `_output` struct
//
#ifdef USE_PBR
{ // combine IBL + lighting
float3 albedo = _surface.diffuse.rgb;
float3 diffuseAlbedo = mix(albedo, float3(0.0), _surface.metalness);
// ambient
float3 color = (_lightingContribution.ambient * _surface.ambientOcclusion) * albedo;
color += _lightingContribution.diffuse * diffuseAlbedo;
#ifndef DISABLE_SPECULAR
color += _lightingContribution.specular;
#endif
#ifdef USE_EMISSION
color += _surface.emission.rgb;
#endif
#ifdef USE_MULTIPLY
color *= _surface.multiply.rgb;
#endif
#ifdef USE_MODULATE
color *= _lightingContribution.modulate;
#endif
_output.color.rgb = color;
}
#else // USE_PBR
#ifdef USE_SHADOWONLY
_output.color.rgb = float3(0.0);
_output.color.a = 1. - _lightingContribution.shadowFactor;
#else
_output.color.rgb = illuminate(_surface, _lightingContribution);
#endif
#endif
#ifndef USE_SHADOWONLY
_output.color.a = _surface.diffuse.a;
#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
//
// MARK: Opacity and transparency
//
#ifdef USE_SHADOWONLY
float transparencyFactor = 1.0;
#ifdef USE_NODE_OPACITY
transparencyFactor *= in.nodeOpacity;
#endif
_output.color.a *= transparencyFactor; // for AR compositing: alpha should be at 0 where there's no shadow
#else
#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.212671f) + (_surface.transparent.g * 0.715160f) + (_surface.transparent.b * 0.072169f);
_output.color *= (float4(1.f) - _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
#endif // USE_SHADOWONLY
//
// MARK: Fragment shader modifier
//
#ifdef USE_FRAGMENT_MODIFIER
// DoFragmentModifier START
// DoFragmentModifier END
#endif
#if defined(USE_CLUSTERED_LIGHTING) && defined(DEBUG_CLUSTER_TILE)
_output.color.rgb = mix(_output.color.rgb, float3(scn::debugColorForCount(clusterIndex.z).xyz), 0.1f);
_output.color.rgb = mix(_output.color.rgb, float3(clusterIndex.x & 0x1 ^ clusterIndex.y & 0x1).xyz, 0.01f);
#endif
#ifdef DISABLE_LINEAR_RENDERING
_output.color.rgb = scn::linear_to_srgb(_output.color.rgb);
#endif
#ifdef USE_DISCARD
if (_output.color.a == 0.) // we could set a different limit here
discard_fragment();
#endif
#ifdef USE_POINT_RENDERING
if ((dfdx(pointCoord.x) < 0.5f) && (length_squared(pointCoord * 2.f - 1.f) > 1.f)) {
discard_fragment();
}
#endif
#ifdef USE_OUTLINE
_output.color.rgb = in.outlineHash;
#endif
// MRT
#ifdef USE_MOTIONBLUR
#ifdef USE_MULTIPLE_RENDERING
_output.motionblur.xy = (half2(((in.fragmentPosition.xy-scn_frame_slice.viewportSize.zw)/scn_frame_slice.viewportSize.xy)*2.f-1.f) - half2((in.velocity.xy) / in.velocity.z)) * scn_frame_slice.motionBlurIntensity;
#else
_output.motionblur.xy = half2((in.mv_fragment.xy / in.mv_fragment.z) - (in.mv_lastFragment.xy / in.mv_lastFragment.z))*half2(1.,-1.) * scn_frame.motionBlurIntensity;
#endif
_output.motionblur.z = length(_output.motionblur.xy);
_output.motionblur.w = half(-_surface.position.z);
#endif
#ifdef USE_NORMALS_OUTPUT
_output.normals = half4( half3(_surface.normal.xyz), half(_surface.roughness) );
#endif
#ifdef USE_RADIANCE_OUTPUT
_output.radiance.rgb = half3(_lightingContribution.specular.rgb);
#endif
#ifdef USE_REFLECTANCE_ROUGHNESS_OUTPUT
#ifdef USE_PBR
_output.reflectanceRoughnessOutput = half4( half3(_lightingContribution.probeReflectance), half(_surface.roughness) );
#else // SSR on non pbr material is not supported
_output.reflectanceRoughnessOutput = half4( 0.h );
#endif
#endif
_output.color = min(_output.color, float4(160.));
return _output;
}
@drewolbrich
Copy link

Thank you for posting this. I have now achieved enlightenment with respect to SceneKit frame dropping issues.

@drewolbrich
Copy link

This is perhaps the most important web page on the Internet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment