Instantly share code, notes, and snippets.
Created
November 14, 2019 01:29
-
Star
(12)
12
You must be signed in to star a gist -
Fork
(4)
4
You must be signed in to fork a gist
-
Save warrenm/794e459e429daa8c75b5f17c000600cf to your computer and use it in GitHub Desktop.
SceneKit's CommonProfile Shader v2 (macOS 10.15)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//////////////////////////////////////////////// | |
// 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; | |
} |
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
Thank you for posting this. I have now achieved enlightenment with respect to SceneKit frame dropping issues.