Standard Material Shaders Share

The unexpected gain during debugging today. I share it for those who need to study the Playcanvas shader interface for reference.

Vertex shader:

#version 300 es


#define attribute in
#define varying out
#define texture2D texture
#define GL2
#define VERTEXSHADER
varying vec4 vMainShadowUv;
varying vec3 vPositionW;
varying vec3 vNormalW;
varying vec2 vUv0;
attribute vec3 vertex_position;
attribute vec3 vertex_normal;
attribute vec4 vertex_tangent;
attribute vec2 vertex_texCoord0;
attribute vec2 vertex_texCoord1;
attribute vec4 vertex_color;

uniform mat4 matrix_viewProjection;
uniform mat4 matrix_model;
uniform mat3 matrix_normal;

vec3 dPositionW;
mat4 dModelMatrix;
mat3 dNormalMatrix;
vec3 dLightPosW;
vec3 dLightDirNormW;
vec3 dNormalW;
uniform mat4 light0_shadowMatrixVS;
uniform vec3 light0_shadowParamsVS;
uniform vec3 light0_directionVS;
void getLightDirPoint(vec3 lightPosW) {
	vec3 lightDirW = vPositionW - lightPosW;
	dLightDirNormW = normalize(lightDirW);
	dLightPosW = lightPosW;
}

void _getShadowCoordOrtho(mat4 shadowMatrix, vec3 shadowParams, vec3 wPos) {
	vec4 projPos = shadowMatrix * vec4(wPos, 1.0);
	vMainShadowUv = projPos;
}

void _getShadowCoordPersp(mat4 shadowMatrix, vec3 shadowParams, vec3 wPos) {
	vec4 projPos = shadowMatrix * vec4(wPos, 1.0);
	vMainShadowUv = projPos;
}

void getShadowCoordOrtho(mat4 shadowMatrix, vec3 shadowParams) {
	_getShadowCoordOrtho(shadowMatrix, shadowParams, vPositionW);
}

void getShadowCoordPersp(mat4 shadowMatrix, vec3 shadowParams) {
	_getShadowCoordPersp(shadowMatrix, shadowParams, vPositionW);
}

void getShadowCoordPerspNormalOffset(mat4 shadowMatrix, vec3 shadowParams) {
	float distScale = abs(dot(vPositionW - dLightPosW, dLightDirNormW)); // fov?
	vec3 wPos = vPositionW + dNormalW * shadowParams.y * clamp(1.0 - dot(dNormalW, -dLightDirNormW), 0.0, 1.0) * distScale;

	_getShadowCoordPersp(shadowMatrix, shadowParams, wPos);
}

void getShadowCoordOrthoNormalOffset(mat4 shadowMatrix, vec3 shadowParams) {
	vec3 wPos = vPositionW + dNormalW * shadowParams.y * clamp(1.0 - dot(dNormalW, -dLightDirNormW), 0.0, 1.0); //0.08

	_getShadowCoordOrtho(shadowMatrix, shadowParams, wPos);
}
#ifdef NINESLICED
vec2 getUv0() {
	vec2 uv = vertex_position.xz;

	// offset inner vertices inside
	// (original vertices must be in [-1;1] range)
	vec2 positiveUnitOffset = clamp(vertex_position.xz, vec2(0.0), vec2(1.0));
	vec2 negativeUnitOffset = clamp(-vertex_position.xz, vec2(0.0), vec2(1.0));
	uv += (-positiveUnitOffset * innerOffset.xy + negativeUnitOffset * innerOffset.zw) * vertex_texCoord0.xy;

	uv = uv * -0.5 + 0.5;
	uv = uv * atlasRect.zw + atlasRect.xy;

	vMask = vertex_texCoord0.xy;

	return uv;
}
#else
vec2 getUv0() {
	return vertex_texCoord0;
}
#endif
#ifdef PIXELSNAP
uniform vec4 uScreenSize;
#endif

#ifdef MORPHING
uniform vec4 morph_weights_a;
uniform vec4 morph_weights_b;
#endif

#ifdef MORPHING_TEXTURE_BASED
uniform vec4 morph_tex_params;

vec2 getTextureMorphCoords() {
	float vertexId = morph_vertex_id;
	vec2 textureSize = morph_tex_params.xy;
	vec2 invTextureSize = morph_tex_params.zw;

	// turn vertexId into int grid coordinates
	float morphGridV = floor(vertexId * invTextureSize.x);
	float morphGridU = vertexId - (morphGridV * textureSize.x);

	// convert grid coordinates to uv coordinates with half pixel offset
	return (vec2(morphGridU, morphGridV) * invTextureSize) + (0.5 * invTextureSize);
}
#endif

#ifdef MORPHING_TEXTURE_BASED_POSITION
uniform highp sampler2D morphPositionTex;
#endif

mat4 getModelMatrix() {
	#ifdef DYNAMICBATCH
	return getBoneMatrix(vertex_boneIndices);
	#elif defined(SKIN)
	return matrix_model * getSkinMatrix(vertex_boneIndices, vertex_boneWeights);
	#elif defined(INSTANCING)
	return mat4(instance_line1, instance_line2, instance_line3, instance_line4);
	#else
	return matrix_model;
	#endif
}

vec4 getPosition() {
	dModelMatrix = getModelMatrix();
	vec3 localPos = vertex_position;

	#ifdef NINESLICED
	// outer and inner vertices are at the same position, scale both
	localPos.xz *= outerScale;

	// offset inner vertices inside
	// (original vertices must be in [-1;1] range)
	vec2 positiveUnitOffset = clamp(vertex_position.xz, vec2(0.0), vec2(1.0));
	vec2 negativeUnitOffset = clamp(-vertex_position.xz, vec2(0.0), vec2(1.0));
	localPos.xz += (-positiveUnitOffset * innerOffset.xy + negativeUnitOffset * innerOffset.zw) * vertex_texCoord0.xy;

	vTiledUv = (localPos.xz - outerScale + innerOffset.xy) * -0.5 + 1.0; // uv = local pos - inner corner

	localPos.xz *= -0.5; // move from -1;1 to -0.5;0.5
	localPos = localPos.xzy;
	#endif

	#ifdef MORPHING
	#ifdef MORPHING_POS03
	localPos.xyz += morph_weights_a[0] * morph_pos0;
	localPos.xyz += morph_weights_a[1] * morph_pos1;
	localPos.xyz += morph_weights_a[2] * morph_pos2;
	localPos.xyz += morph_weights_a[3] * morph_pos3;
	#endif // MORPHING_POS03
	#ifdef MORPHING_POS47
	localPos.xyz += morph_weights_b[0] * morph_pos4;
	localPos.xyz += morph_weights_b[1] * morph_pos5;
	localPos.xyz += morph_weights_b[2] * morph_pos6;
	localPos.xyz += morph_weights_b[3] * morph_pos7;
	#endif // MORPHING_POS47
	#endif // MORPHING

	#ifdef MORPHING_TEXTURE_BASED_POSITION
	// apply morph offset from texture
	vec2 morphUV = getTextureMorphCoords();
	vec3 morphPos = texture2D(morphPositionTex, morphUV).xyz;
	localPos += morphPos;
	#endif

	vec4 posW = dModelMatrix * vec4(localPos, 1.0);
	#ifdef SCREENSPACE
	posW.zw = vec2(0.0, 1.0);
	#endif
	dPositionW = posW.xyz;

	vec4 screenPos;
	#ifdef UV1LAYOUT
	screenPos = vec4(vertex_texCoord1.xy * 2.0 - 1.0, 0.5, 1);
	#else
	#ifdef SCREENSPACE
	screenPos = posW;
	#else
	screenPos = matrix_viewProjection * posW;
	#endif

	#ifdef PIXELSNAP
	// snap vertex to a pixel boundary
	screenPos.xy = (screenPos.xy * 0.5) + 0.5;
	screenPos.xy *= uScreenSize.xy;
	screenPos.xy = floor(screenPos.xy);
	screenPos.xy *= uScreenSize.zw;
	screenPos.xy = (screenPos.xy * 2.0) - 1.0;
	#endif
	#endif

	return screenPos;
}

vec3 getWorldPosition() {
	return dPositionW;
}
#ifdef MORPHING_TEXTURE_BASED_NORMAL
uniform highp sampler2D morphNormalTex;
#endif

vec3 getNormal() {
	#ifdef SKIN
	dNormalMatrix = mat3(dModelMatrix[0].xyz, dModelMatrix[1].xyz, dModelMatrix[2].xyz);
	#elif defined(INSTANCING)
	dNormalMatrix = mat3(instance_line1.xyz, instance_line2.xyz, instance_line3.xyz);
	#else
	dNormalMatrix = matrix_normal;
	#endif

	vec3 tempNormal = vertex_normal;

	#ifdef MORPHING
	#ifdef MORPHING_NRM03
	tempNormal += morph_weights_a[0] * morph_nrm0;
	tempNormal += morph_weights_a[1] * morph_nrm1;
	tempNormal += morph_weights_a[2] * morph_nrm2;
	tempNormal += morph_weights_a[3] * morph_nrm3;
	#endif
	#ifdef MORPHING_NRM47
	tempNormal += morph_weights_b[0] * morph_nrm4;
	tempNormal += morph_weights_b[1] * morph_nrm5;
	tempNormal += morph_weights_b[2] * morph_nrm6;
	tempNormal += morph_weights_b[3] * morph_nrm7;
	#endif
	#endif

	#ifdef MORPHING_TEXTURE_BASED_NORMAL
	// apply morph offset from texture
	vec2 morphUV = getTextureMorphCoords();
	vec3 morphNormal = texture2D(morphNormalTex, morphUV).xyz;
	tempNormal += morphNormal;
	#endif

	return normalize(dNormalMatrix * tempNormal);
}

void main(void) {
	gl_Position = getPosition();
   vPositionW	= getWorldPosition();
   vNormalW	= dNormalW = getNormal();
   dLightDirNormW = light0_directionVS;
	   getShadowCoordOrthoNormalOffset(light0_shadowMatrixVS, light0_shadowParamsVS);
   vec2 uv0 = getUv0();
   vUv0 = uv0;
}

Fragment shader:

#version 300 es
#define varying in
out highp vec4 pc_fragColor;
#define gl_FragColor pc_fragColor
#define texture2D texture
#define textureCube texture
#define texture2DProj textureProj
#define texture2DLodEXT textureLod
#define texture2DProjLodEXT textureProjLod
#define textureCubeLodEXT textureLod
#define texture2DGradEXT textureGrad
#define texture2DProjGradEXT textureProjGrad
#define textureCubeGradEXT textureGrad
#define GL2
precision highp float;
#ifdef GL2
precision highp sampler2DShadow;
#endif
varying vec4 vMainShadowUv;
varying vec3 vPositionW;
varying vec3 vNormalW;
varying vec2 vUv0;
uniform vec3 view_position;

uniform vec3 light_globalAmbient;

float square(float x) {
return x*x;
}

float saturate(float x) {
return clamp(x, 0.0, 1.0);
}

vec3 saturate(vec3 x) {
return clamp(x, vec3(0.0), vec3(1.0));
}
vec3 detailMode_mul(vec3 c1, vec3 c2) {
return c1 * c2;
}

vec3 detailMode_add(vec3 c1, vec3 c2) {
return c1 + c2;
}

// https://en.wikipedia.org/wiki/Blend_modes#Screen
vec3 detailMode_screen(vec3 c1, vec3 c2) {
return 1.0 - (1.0 - c1)*(1.0 - c2);
}

// https://en.wikipedia.org/wiki/Blend_modes#Overlay
vec3 detailMode_overlay(vec3 c1, vec3 c2) {
return mix(1.0 - 2.0*(1.0 - c1)*(1.0 - c2), 2.0*c1*c2, step(c1, vec3(0.5)));
}

vec3 detailMode_min(vec3 c1, vec3 c2) {
return min(c1, c2);
}

vec3 detailMode_max(vec3 c1, vec3 c2) {
return max(c1, c2);
}
vec4 dReflection;
vec3 dAlbedo;
vec3 dNormalW;
vec3 dVertexNormalW;
vec3 dViewDirW;
vec3 dReflDirW;
vec3 dDiffuseLight;
vec3 dSpecularLight;
vec3 dLightDirNormW;
vec3 dLightDirW;
vec3 dLightPosW;
vec3 dShadowCoord;
vec3 dSpecularity;
float dGlossiness;
float dAlpha;
float dAtten;
vec4 ccReflection;
vec3 ccNormalW;
vec3 ccSpecularLight;
float ccSpecularity;
float ccGlossiness;
uniform vec3 light0_color;
uniform vec3 light0_direction;
uniform mat4 light0_shadowMatrix;
uniform vec3 light0_shadowParams;
uniform sampler2DShadow light0_shadowMap;

void getNormal() {
dNormalW = normalize(dVertexNormalW);
}
vec3 gammaCorrectInput(vec3 color) {
return pow(color, vec3(2.2));
}

float gammaCorrectInput(float color) {
return pow(color, 2.2);
}

 vec4 gammaCorrectInput(vec4 color) {
 return vec4(pow(color.rgb, vec3(2.2)), color.a);
 }

 vec4 texture2DSRGB(sampler2D tex, vec2 uv) {
 vec4 rgba = texture2D(tex, uv);
 rgba.rgb = gammaCorrectInput(rgba.rgb);
 return rgba;
 }

 vec4 texture2DSRGB(sampler2D tex, vec2 uv, float bias) {
 vec4 rgba = texture2D(tex, uv, bias);
 rgba.rgb = gammaCorrectInput(rgba.rgb);
 return rgba;
 }

 vec4 textureCubeSRGB(samplerCube tex, vec3 uvw) {
 vec4 rgba = textureCube(tex, uvw);
 rgba.rgb = gammaCorrectInput(rgba.rgb);
 return rgba;
 }

 vec3 gammaCorrectOutput(vec3 color) {
 #ifdef HDR
 return color;
 #else
 color += vec3(0.0000001);
 return pow(color, vec3(0.45));
 #endif
 }
 uniform float exposure;

 vec3 toneMap(vec3 color) {
 return color * exposure;
 }
 float dBlendModeFogFactor = 1.0;

 vec3 addFog(vec3 color) {
 return color;
 }
 vec3 cubeMapProject(vec3 dir) {
 return dir;
 }
 vec3 processEnvironment(vec3 color) {
 return color;
 }

 #undef MAPFLOAT

 #undef MAPCOLOR

 #undef MAPVERTEX

 #undef MAPTEXTURE
 #ifdef MAPTEXTURE
 uniform sampler2D texture_diffuseDetailMap;
 #endif

 vec3 addAlbedoDetail(vec3 albedo) {
 #ifdef MAPTEXTURE
 vec3 albedoDetail = vec3(texture2D(texture_diffuseDetailMap, UV).CH);
 return detailMode_mul(albedo, albedoDetail);
 #else
 return albedo;
 #endif
 }

 #undef MAPFLOAT

 #undef MAPCOLOR

 #undef MAPVERTEX

 #undef MAPTEXTURE
 #define MAPTEXTURE
 #ifdef MAPCOLOR
 uniform vec3 material_diffuse;
 #endif

 #ifdef MAPTEXTURE
 uniform sampler2D texture_diffuseMap;
 #endif

 void getAlbedo() {
 dAlbedo = vec3(1.0);

 #ifdef MAPCOLOR
 dAlbedo *= material_diffuse.rgb;
 #endif

 #ifdef MAPTEXTURE
 dAlbedo *= gammaCorrectInput(addAlbedoDetail(texture2D(texture_diffuseMap, vUv0).rgb));
 #endif

 #ifdef MAPVERTEX
 dAlbedo *= gammaCorrectInput(saturate(vVertexColor.VC));
 #endif
 }

 #undef MAPFLOAT

 #undef MAPCOLOR

 #undef MAPVERTEX

 #undef MAPTEXTURE
 #define MAPTEXTURE
 #ifdef MAPFLOAT
 uniform float material_opacity;
 #endif

 #ifdef MAPTEXTURE
 uniform sampler2D texture_opacityMap;
 #endif

 void getOpacity() {
 dAlpha = 1.0;

 #ifdef MAPFLOAT
 dAlpha *= material_opacity;
 #endif

 #ifdef MAPTEXTURE
 dAlpha *= texture2D(texture_opacityMap, vUvNaN).r;
 #endif

 #ifdef MAPVERTEX
 dAlpha *= clamp(vVertexColor.VC, 0.0, 1.0);
 #endif
 }


 #undef MAPFLOAT

 #undef MAPCOLOR
 #define MAPCOLOR

 #undef MAPVERTEX

 #undef MAPTEXTURE
 #ifdef MAPCOLOR
 uniform vec3 material_emissive;
 #endif

 #ifdef MAPFLOAT
 uniform float material_emissiveIntensity;
 #endif

 #ifdef MAPTEXTURE
 uniform sampler2D texture_emissiveMap;
 #endif

 vec3 getEmission() {
 vec3 emission = vec3(1.0);

 #ifdef MAPFLOAT
 emission *= material_emissiveIntensity;
 #endif

 #ifdef MAPCOLOR
 emission *= material_emissive;
 #endif

 #ifdef MAPTEXTURE
 emission *= texture2DSAMPLE(texture_emissiveMap, UV).CH;
 #endif

 #ifdef MAPVERTEX
 emission *= gammaCorrectInput(saturate(vVertexColor.VC));
 #endif

 return emission;
 }
 float antiAliasGlossiness(float power) {
 return power;
 }

 #undef MAPFLOAT

 #undef MAPCOLOR
 #define MAPCOLOR

 #undef MAPVERTEX

 #undef MAPTEXTURE
 #ifdef MAPCOLOR
 uniform vec3 material_specular;
 #endif

 #ifdef MAPTEXTURE
 uniform sampler2D texture_specularMap;
 #endif

 void getSpecularity() {
 dSpecularity = vec3(1.0);

 #ifdef MAPCOLOR
 dSpecularity *= material_specular;
 #endif

 #ifdef MAPTEXTURE
 dSpecularity *= texture2D(texture_specularMap, UV).CH;
 #endif

 #ifdef MAPVERTEX
 dSpecularity *= saturate(vVertexColor.VC);
 #endif
 }

 #undef MAPFLOAT
 #define MAPFLOAT

 #undef MAPCOLOR

 #undef MAPVERTEX

 #undef MAPTEXTURE
 #ifdef MAPFLOAT
 uniform float material_shininess;
 #endif

 #ifdef MAPTEXTURE
 uniform sampler2D texture_glossMap;
 #endif

 void getGlossiness() {
 dGlossiness = 1.0;

 #ifdef MAPFLOAT
 dGlossiness *= material_shininess;
 #endif

 #ifdef MAPTEXTURE
 dGlossiness *= texture2D(texture_glossMap, UV).CH;
 #endif

 #ifdef MAPVERTEX
 dGlossiness *= saturate(vVertexColor.VC);
 #endif

 dGlossiness += 0.0000001;
 }
 // Schlick's approximation
 uniform float material_fresnelFactor; // unused

 void getFresnel() {
 float fresnel = 1.0 - max(dot(dNormalW, dViewDirW), 0.0);
 float fresnel2 = fresnel * fresnel;
 fresnel *= fresnel2 * fresnel2;
 fresnel *= dGlossiness * dGlossiness;
 dSpecularity = dSpecularity + (1.0 - dSpecularity) * fresnel;

 #ifdef CLEARCOAT
 fresnel = 1.0 - max(dot(ccNormalW, dViewDirW), 0.0);
 fresnel2 = fresnel * fresnel;
 fresnel *= fresnel2 * fresnel2;
 fresnel *= ccGlossiness * ccGlossiness;
 ccSpecularity = ccSpecularity + (1.0 - ccSpecularity) * fresnel;
 #endif
 }
 vec3 lessThan2(vec3 a, vec3 b) {
 return clamp((b - a)*1000.0, 0.0, 1.0); // softer version
 }

 #ifndef UNPACKFLOAT
 #define UNPACKFLOAT
 float unpackFloat(vec4 rgbaDepth) {
 const vec4 bitShift = vec4(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0);
 return dot(rgbaDepth, bitShift);
 }
 #endif

 // ----- Direct/Spot Sampling -----

 #ifdef GL2
 float _getShadowPCF3x3(sampler2DShadow shadowMap, vec3 shadowParams) {
 float z = dShadowCoord.z;
 vec2 uv = dShadowCoord.xy * shadowParams.x; // 1 unit - 1 texel
 float shadowMapSizeInv = 1.0 / shadowParams.x;
 vec2 base_uv = floor(uv + 0.5);
 float s = (uv.x + 0.5 - base_uv.x);
 float t = (uv.y + 0.5 - base_uv.y);
 base_uv -= vec2(0.5);
 base_uv *= shadowMapSizeInv;

 float sum = 0.0;

 float uw0 = (3.0 - 2.0 * s);
 float uw1 = (1.0 + 2.0 * s);

 float u0 = (2.0 - s) / uw0 - 1.0;
 float u1 = s / uw1 + 1.0;

 float vw0 = (3.0 - 2.0 * t);
 float vw1 = (1.0 + 2.0 * t);

 float v0 = (2.0 - t) / vw0 - 1.0;
 float v1 = t / vw1 + 1.0;

 u0 = u0 * shadowMapSizeInv + base_uv.x;
 v0 = v0 * shadowMapSizeInv + base_uv.y;

 u1 = u1 * shadowMapSizeInv + base_uv.x;
 v1 = v1 * shadowMapSizeInv + base_uv.y;

 sum += uw0 * vw0 * texture(shadowMap, vec3(u0, v0, z));
 sum += uw1 * vw0 * texture(shadowMap, vec3(u1, v0, z));
 sum += uw0 * vw1 * texture(shadowMap, vec3(u0, v1, z));
 sum += uw1 * vw1 * texture(shadowMap, vec3(u1, v1, z));

 sum *= 1.0f / 16.0;
 return sum;
 }

 float getShadowPCF3x3(sampler2DShadow shadowMap, vec3 shadowParams) {
 return _getShadowPCF3x3(shadowMap, shadowParams);
 }

 float getShadowSpotPCF3x3(sampler2DShadow shadowMap, vec4 shadowParams) {
 return _getShadowPCF3x3(shadowMap, shadowParams.xyz);
 }
 #else
 float _xgetShadowPCF3x3(mat3 depthKernel, sampler2D shadowMap, vec3 shadowParams) {
 mat3 shadowKernel;
 vec3 shadowCoord = dShadowCoord;
 vec3 shadowZ = vec3(shadowCoord.z);
 shadowKernel[0] = vec3(greaterThan(depthKernel[0], shadowZ));
 shadowKernel[1] = vec3(greaterThan(depthKernel[1], shadowZ));
 shadowKernel[2] = vec3(greaterThan(depthKernel[2], shadowZ));

 vec2 fractionalCoord = fract( shadowCoord.xy * shadowParams.x );

 shadowKernel[0] = mix(shadowKernel[0], shadowKernel[1], fractionalCoord.x);
 shadowKernel[1] = mix(shadowKernel[1], shadowKernel[2], fractionalCoord.x);

 vec4 shadowValues;
 shadowValues.x = mix(shadowKernel[0][0], shadowKernel[0][1], fractionalCoord.y);
 shadowValues.y = mix(shadowKernel[0][1], shadowKernel[0][2], fractionalCoord.y);
 shadowValues.z = mix(shadowKernel[1][0], shadowKernel[1][1], fractionalCoord.y);
 shadowValues.w = mix(shadowKernel[1][1], shadowKernel[1][2], fractionalCoord.y);

 return dot( shadowValues, vec4( 1.0 ) ) * 0.25;
 }

 float _getShadowPCF3x3(sampler2D shadowMap, vec3 shadowParams) {
 vec3 shadowCoord = dShadowCoord;

 float xoffset = 1.0 / shadowParams.x; // 1/shadow map width
 float dx0 = -xoffset;
 float dx1 = xoffset;

 mat3 depthKernel;
 depthKernel[0][0] = unpackFloat(texture2D(shadowMap, shadowCoord.xy + vec2(dx0, dx0)));
 depthKernel[0][1] = unpackFloat(texture2D(shadowMap, shadowCoord.xy + vec2(dx0, 0.0)));
 depthKernel[0][2] = unpackFloat(texture2D(shadowMap, shadowCoord.xy + vec2(dx0, dx1)));
 depthKernel[1][0] = unpackFloat(texture2D(shadowMap, shadowCoord.xy + vec2(0.0, dx0)));
 depthKernel[1][1] = unpackFloat(texture2D(shadowMap, shadowCoord.xy));
 depthKernel[1][2] = unpackFloat(texture2D(shadowMap, shadowCoord.xy + vec2(0.0, dx1)));
 depthKernel[2][0] = unpackFloat(texture2D(shadowMap, shadowCoord.xy + vec2(dx1, dx0)));
 depthKernel[2][1] = unpackFloat(texture2D(shadowMap, shadowCoord.xy + vec2(dx1, 0.0)));
 depthKernel[2][2] = unpackFloat(texture2D(shadowMap, shadowCoord.xy + vec2(dx1, dx1)));

 return _xgetShadowPCF3x3(depthKernel, shadowMap, shadowParams);
 }

 float getShadowPCF3x3(sampler2D shadowMap, vec3 shadowParams) {
 return _getShadowPCF3x3(shadowMap, shadowParams);
 }

 float getShadowSpotPCF3x3(sampler2D shadowMap, vec4 shadowParams) {
 return _getShadowPCF3x3(shadowMap, shadowParams.xyz);
 }
 #endif


 // ----- Point Sampling -----

 float _getShadowPoint(samplerCube shadowMap, vec4 shadowParams, vec3 dir) {

 vec3 tc = normalize(dir);
 vec3 tcAbs = abs(tc);

 vec4 dirX = vec4(1,0,0, tc.x);
 vec4 dirY = vec4(0,1,0, tc.y);
 float majorAxisLength = tc.z;
 if ((tcAbs.x > tcAbs.y) && (tcAbs.x > tcAbs.z)) {
 dirX = vec4(0,0,1, tc.z);
 dirY = vec4(0,1,0, tc.y);
 majorAxisLength = tc.x;
 } else if ((tcAbs.y > tcAbs.x) && (tcAbs.y > tcAbs.z)) {
 dirX = vec4(1,0,0, tc.x);
 dirY = vec4(0,0,1, tc.z);
 majorAxisLength = tc.y;
 }

 float shadowParamsInFaceSpace = ((1.0/shadowParams.x) * 2.0) * abs(majorAxisLength);

 vec3 xoffset = (dirX.xyz * shadowParamsInFaceSpace);
 vec3 yoffset = (dirY.xyz * shadowParamsInFaceSpace);
 vec3 dx0 = -xoffset;
 vec3 dy0 = -yoffset;
 vec3 dx1 = xoffset;
 vec3 dy1 = yoffset;

 mat3 shadowKernel;
 mat3 depthKernel;

 depthKernel[0][0] = unpackFloat(textureCube(shadowMap, tc + dx0 + dy0));
 depthKernel[0][1] = unpackFloat(textureCube(shadowMap, tc + dx0));
 depthKernel[0][2] = unpackFloat(textureCube(shadowMap, tc + dx0 + dy1));
 depthKernel[1][0] = unpackFloat(textureCube(shadowMap, tc + dy0));
 depthKernel[1][1] = unpackFloat(textureCube(shadowMap, tc));
 depthKernel[1][2] = unpackFloat(textureCube(shadowMap, tc + dy1));
 depthKernel[2][0] = unpackFloat(textureCube(shadowMap, tc + dx1 + dy0));
 depthKernel[2][1] = unpackFloat(textureCube(shadowMap, tc + dx1));
 depthKernel[2][2] = unpackFloat(textureCube(shadowMap, tc + dx1 + dy1));

 vec3 shadowZ = vec3(length(dir) * shadowParams.w + shadowParams.z);

 shadowKernel[0] = vec3(lessThan2(depthKernel[0], shadowZ));
 shadowKernel[1] = vec3(lessThan2(depthKernel[1], shadowZ));
 shadowKernel[2] = vec3(lessThan2(depthKernel[2], shadowZ));

 vec2 uv = (vec2(dirX.w, dirY.w) / abs(majorAxisLength)) * 0.5;

 vec2 fractionalCoord = fract( uv * shadowParams.x );

 shadowKernel[0] = mix(shadowKernel[0], shadowKernel[1], fractionalCoord.x);
 shadowKernel[1] = mix(shadowKernel[1], shadowKernel[2], fractionalCoord.x);

 vec4 shadowValues;
 shadowValues.x = mix(shadowKernel[0][0], shadowKernel[0][1], fractionalCoord.y);
 shadowValues.y = mix(shadowKernel[0][1], shadowKernel[0][2], fractionalCoord.y);
 shadowValues.z = mix(shadowKernel[1][0], shadowKernel[1][1], fractionalCoord.y);
 shadowValues.w = mix(shadowKernel[1][1], shadowKernel[1][2], fractionalCoord.y);

 return 1.0 - dot( shadowValues, vec4( 1.0 ) ) * 0.25;
 }

 float getShadowPointPCF3x3(samplerCube shadowMap, vec4 shadowParams) {
 return _getShadowPoint(shadowMap, shadowParams, dLightDirW);
 }
 void _getShadowCoordOrtho(mat4 shadowMatrix, vec3 shadowParams, vec3 wPos) {
 dShadowCoord = (shadowMatrix * vec4(wPos, 1.0)).xyz;
 dShadowCoord.z = saturate(dShadowCoord.z) - 0.0001;

 #ifdef SHADOWBIAS
 dShadowCoord.z += getShadowBias(shadowParams.x, shadowParams.z);
 #endif
 }

 void _getShadowCoordPersp(mat4 shadowMatrix, vec4 shadowParams, vec3 wPos) {
 vec4 projPos = shadowMatrix * vec4(wPos, 1.0);
 projPos.xy /= projPos.w;
 dShadowCoord.xy = projPos.xy;
 dShadowCoord.z = length(dLightDirW) * shadowParams.w;

 #ifdef SHADOWBIAS
 dShadowCoord.z += getShadowBias(shadowParams.x, shadowParams.z);
 #endif
 }

 void getShadowCoordOrtho(mat4 shadowMatrix, vec3 shadowParams) {
 _getShadowCoordOrtho(shadowMatrix, shadowParams, vPositionW);
 }

 void getShadowCoordPersp(mat4 shadowMatrix, vec4 shadowParams) {
 _getShadowCoordPersp(shadowMatrix, shadowParams, vPositionW);
 }

 void getShadowCoordPerspNormalOffset(mat4 shadowMatrix, vec4 shadowParams) {
 float distScale = abs(dot(vPositionW - dLightPosW, dLightDirNormW)); // fov?
 vec3 wPos = vPositionW + dVertexNormalW * shadowParams.y * clamp(1.0 - dot(dVertexNormalW, -dLightDirNormW), 0.0, 1.0) * distScale;

 _getShadowCoordPersp(shadowMatrix, shadowParams, wPos);
 }

 void getShadowCoordOrthoNormalOffset(mat4 shadowMatrix, vec3 shadowParams) {
 vec3 wPos = vPositionW + dVertexNormalW * shadowParams.y * clamp(1.0 - dot(dVertexNormalW, -dLightDirNormW), 0.0, 1.0); //0.08

 _getShadowCoordOrtho(shadowMatrix, shadowParams, wPos);
 }
 void normalOffsetPointShadow(vec4 shadowParams) {
 float distScale = length(dLightDirW);
 vec3 wPos = vPositionW + dVertexNormalW * shadowParams.y * clamp(1.0 - dot(dVertexNormalW, -dLightDirNormW), 0.0, 1.0) * distScale; //0.02
 vec3 dir = wPos - dLightPosW;
 dLightDirW = dir;
 }
 #ifdef GL2
 #define SHADOW_SAMPLERVS sampler2DShadow
 #else
 #define SHADOW_SAMPLERVS sampler2D
 #endif

 float getShadowPCF3x3VS(SHADOW_SAMPLERVS shadowMap, vec3 shadowParams) {
 dShadowCoord = vMainShadowUv.xyz;
 dShadowCoord.z = saturate(dShadowCoord.z) - 0.0001;

 #ifdef SHADOWBIAS
 dShadowCoord.z += getShadowBias(shadowParams.x, shadowParams.z);
 #endif

 return _getShadowPCF3x3(shadowMap, shadowParams);
 }
 float getLightDiffuse() {
 return max(dot(dNormalW, -dLightDirNormW), 0.0);
 }
 // Energy-conserving (hopefully) Blinn-Phong
 float calcLightSpecular(float tGlossiness, vec3 tNormalW) {
 vec3 h = normalize( -dLightDirNormW + dViewDirW );
 float nh = max( dot( h, tNormalW ), 0.0 );

 float specPow = exp2(tGlossiness * 11.0); // glossiness is linear, power is not; 0 - 2048
 specPow = antiAliasGlossiness(specPow);

 // Hack: On Mac OS X, calling pow with zero for the exponent generates hideous artifacts so bias up a little
 specPow = max(specPow, 0.0001);

 return pow(nh, specPow) * (specPow + 2.0) / 8.0;
 }

 float getLightSpecular() {
 return calcLightSpecular(dGlossiness, dNormalW);
 }

 float getLightSpecularCC() {
 return calcLightSpecular(ccGlossiness, ccNormalW);
 }
 vec3 combineColor() {
 return mix(dAlbedo * dDiffuseLight, dSpecularLight + dReflection.rgb * dReflection.a, dSpecularity);
 }
 void addAmbient() {
 dDiffuseLight += light_globalAmbient;
 }
 void getViewDir() {
 dViewDirW = normalize(view_position - vPositionW);
 }
 void getReflDir() {
 dReflDirW = normalize(-reflect(dViewDirW, dNormalW));
 }
 void main(void) {
 dDiffuseLight = vec3(0);
 dSpecularLight = vec3(0);
 dReflection = vec4(0);
 dSpecularity = vec3(0);

 #ifdef CLEARCOAT
 ccSpecularLight = vec3(0);
 ccReflection = vec4(0);
 #endif
 dVertexNormalW = vNormalW;
 getOpacity();
 getViewDir();
 getNormal();
 getReflDir();
 getAlbedo();
 getSpecularity();
 getGlossiness();
 getFresnel();
 addAmbient();
 dLightDirNormW = light0_direction;
 dAtten = 1.0;
 dAtten *= getLightDiffuse();
 dAtten *= getShadowPCF3x3VS(light0_shadowMap, light0_shadowParams);
 dDiffuseLight += dAtten * light0_color;
 dAtten *= getLightSpecular();
 dSpecularLight += dAtten * light0_color;


 #ifdef CLEARCOAT
 gl_FragColor.rgb = combineColorCC();
 #else
 gl_FragColor.rgb = combineColor();
 #endif

 gl_FragColor.rgb += getEmission();
 gl_FragColor.rgb = addFog(gl_FragColor.rgb);

 #ifndef HDR
 gl_FragColor.rgb = toneMap(gl_FragColor.rgb);
 gl_FragColor.rgb = gammaCorrectOutput(gl_FragColor.rgb);
 #endif
 gl_FragColor.a = dAlpha;

 }
4 Likes

Thanks for sharing @kprimo. You can also see the complete vertex and fragment shaders generated for any material like this:

myMaterial.shader.definition.vshader;
myMaterial.shader.definition.fshader;
2 Likes

thanks for sharing, master! I’ve tested this function, it works under update only.

1 Like

Basically it works after the shader is compiled, so you will have to check it at an appropriate time. Maybe after the first frame has finished rendering.

1 Like

Made a debug script like the way which @Leonidas mentioned.

var PrintShaders = pc.createScript('printShaders');

PrintShaders.prototype.initialize = function(){
  
    this.done = false;
    this.timer = 0;
    
};

PrintShaders.prototype.update = function(dt) {
    
    this.timer += dt;
    var material = this.entity.model.meshInstances[0].material;

    // Shaders cannot be printed unless it has been compiled
    if (this.done === false & this.timer > 1){
        var v = material.shader.definition.vshader;
        var f = material.shader.definition.fshader;
        console.log(v);
        console.log(f);
        this.done = true;    
    }

};

Just to add some info to this - the shaders are generated dynamically from chunks (shader code pieces in library: https://github.com/playcanvas/engine/tree/master/src/graphics/program-lib/chunks and also some dynamically created glue code to connect and enable them as needed. All that depends on the material settings - what textures, values, lighting and options are set, and also what’s the purpose of the shader (main framebuffer has different shader generated then shadowmap for example). Depending on these, you can have many different shaders generated by the engine.

I use https://spector.babylonjs.com/ tool (installed as browser plugin) to inspect the frame of Playcanvas project. You capture the frame, and can see all render calls, including textures, parameters set to WebGl, shaders for each render call and many other useful information. Give that a go sometimes.

3 Likes