在Substance当中使用Unity的Shading模型
在PBR的游戏资源制作中,最常用的贴图绘制工具就是Substance Painter。
一般的流程是在SP当中绘制到模型贴图然后在导入到unity当中。
在这个过程中美术会遇到很严重的效果不匹配的问题,在Unity中效果不理想就需要返回SP当中重新调整。因为SP的光照模型和Unity的光照模型不同,尤其是在Unity升级了HDRP以后光照计算再次出现了变化。为了能够让贴图绘制人员能够在SP和Unity当中看到的效果一致,我们需要重写SP的光照计算Shader。
SP提供了这个功能,参考的文件夹位于:
Substance Painter\resources\shelf\allegorithmic\shaders
这个目录下面有很多glsl文件,语法使用glsl语法,能够控制的部分类似于Unity Legac的Surface Shader。
下面是我实现的SPShader,使用了和Unity一样的光照模型,支持Substance Painter 2019.1.0:
//- Allegorithmic Metal/Rough PBR shader
//- ====================================
//-
//- Import from libraries.
import lib-sss.glsl
import lib-pbr.glsl
import lib-emissive.glsl
import lib-pom.glsl
import lib-utils.glsl
//- Declare the iray mdl material to use with this shader.
//: metadata {
//: "mdl":"mdl::alg::materials::skin_metallic_roughness::skin_metallic_roughness"
//: }
//- Channels needed for metal/rough workflow are bound here.
//: param auto channel_basecolor
uniform SamplerSparse basecolor_tex;
//: param auto channel_roughness
uniform SamplerSparse roughness_tex;
//: param auto channel_metallic
uniform SamplerSparse metallic_tex;
//: param auto channel_specularlevel
uniform SamplerSparse specularlevel_tex;
// -----------------------------------------------------------------------------
// Constants
#define HALF_MAX 65504.0 // (2 - 2^-10) * 2^15
#define HALF_MAX_MINUS1 65472.0 // (2 - 2^-9) * 2^15
#define EPSILON 1.0e-4
#define PI 3.14159265359
#define TWO_PI 6.28318530718
#define FOUR_PI 12.56637061436
#define INV_PI 0.31830988618
#define INV_TWO_PI 0.15915494309
#define INV_FOUR_PI 0.07957747155
#define HALF_PI 1.57079632679
#define INV_HALF_PI 0.636619772367
#define FLT_EPSILON 1.192092896e-07 // Smallest positive number, such that 1.0 + FLT_EPSILON != 1.0
#define FLT_MIN 1.175494351e-38 // Minimum representable positive floating-point number
#define FLT_MAX 3.402823466e+38 // Maximum representable floating-point number
#define DEFAULT_SPECULAR_VALUE 0.04
#define FLT_INF asfloat(0x7F800000)
#define FLT_EPS 5.960464478e-8 // 2^-24, machine epsilon: 1 + EPS = 1 (half of the ULP for 1.0f)
#define HALF_MIN 6.103515625e-5 // 2^-14, the same value for 10, 11 and 16-bit: https://www.khronos.org/opengl/wiki/Small_Float_Formats
#define HALF_MAX 65504.0
#define UINT_MAX 0xFFFFFFFFu
// HDRP模拟光源
//: param custom { "default": [10.0, 10.0, 10.0], "label": "Light Direction", "min": -20, "max": 20 }
uniform vec3 lightDir;
//: param custom { "default": 1.0, "label": "Light Color", "widget": "color" }
uniform vec3 lightColor;
//: param custom { "default": 1.0, "label": "Diffuse Dimmer", "min": 0.0, "max": 32.0 }
uniform float diffuseDimmer;
//: param custom { "default": 1.0, "label": "Specular Dimmer", "min": 0.0, "max": 32.0 }
uniform float specularDimmer;
//: param custom { "default": 1.0, "label": "Lihgt Intensity", "min": 0.0, "max": 32.0 }
uniform float lightIntensity;
struct DirectLighting
{
vec3 diffuse;
vec3 specular;
};
float F_Schlick(float f0, float f90, float u)
{
float x = 1.0 - u;
float x2 = x * x;
float x5 = x * x2 * x2;
return (f90 - f0) * x5 + f0; // sub mul mul mul sub mad
}
float F_Schlick(float f0, float u)
{
return F_Schlick(f0, 1.0, u); // sub mul mul mul sub mad
}
vec3 F_Schlick(vec3 f0, float f90, float u)
{
float x = 1.0 - u;
float x2 = x * x;
float x5 = x * x2 * x2;
return f0 * (1.0 - x5) + (f90 * x5); // sub mul mul mul sub mul mad*3
}
vec3 F_Schlick(vec3 f0, float u)
{
return F_Schlick(f0, 1.0, u); // sub mul mul mul sub mad*3
}
float DV_SmithJointGGX(float NdotH, float NdotL, float NdotV, float roughness, float partLambdaV)
{
float a2 = (roughness * roughness);
float s = (NdotH * a2 - NdotH) * NdotH + 1.0;
float lambdaV = NdotL * partLambdaV;
float lambdaL = NdotV * sqrt((-NdotL * a2 + NdotL) * NdotL + a2);
vec2 D = vec2(a2, s * s); // Fraction without the multiplier (1/Pi)
vec2 G = vec2(1, lambdaV + lambdaL); // Fraction without the multiplier (1/2)
// This function is only used for direct lighting.
// If roughness is 0, the probability of hitting a punctual or directional light is also 0.
// Therefore, we return 0. The most efficient way to do it is with a max().
return INV_PI * 0.5 * (D.x * G.x) / max(D.y * G.y, FLT_MIN);
}
float DisneyDiffuseNoPI(float NdotV, float NdotL, float LdotV, float perceptualRoughness)
{
// (2 * LdotH * LdotH) = 1 + LdotV
// float fd90 = 0.5 + 2 * LdotH * LdotH * perceptualRoughness;
float fd90 = 0.5 + (perceptualRoughness + perceptualRoughness * LdotV);
// Two schlick fresnel term
float lightScatter = F_Schlick(1.0, fd90, NdotL);
float viewScatter = F_Schlick(1.0, fd90, NdotV);
// Normalize the BRDF for polar view angles of up to (Pi/4).
// We use the worst case of (roughness = albedo = 1), and, for each view angle,
// integrate (brdf * cos(theta_light)) over all light directions.
// The resulting value is for (theta_view = 0), which is actually a little bit larger
// than the value of the integral for (theta_view = Pi/4).
// Hopefully, the compiler folds the constant together with (1/Pi).
return (1 / 1.03571) * (lightScatter * viewScatter);
}
float DisneyDiffuse(float NdotV, float NdotL, float LdotV, float perceptualRoughness)
{
return INV_PI * DisneyDiffuseNoPI(NdotV, NdotL, LdotV, perceptualRoughness);
}
float GetSmithJointGGXPartLambdaV(float NdotV, float roughness)
{
float a2 = (roughness * roughness);
return sqrt((-NdotV * a2 + NdotV) * NdotV + a2);
}
void BSDF( vec3 V,
vec3 L,
float NdotL,
float unclampNdotV,
vec3 fresnel0,
float roughness,
float perceptualRoughness,
out vec3 diffuseLighting,
out vec3 specularLighting)
{
float LdotV, NdotH, LdotH, NdotV, invLenLV;
LdotV = dot(L, V);
invLenLV = inversesqrt(max(2.0 * LdotV + 2.0, FLT_EPS));
NdotH = clamp((NdotL + unclampNdotV) * invLenLV,0,1);
LdotH = clamp(invLenLV * LdotV + invLenLV,0,1);
NdotV = max(unclampNdotV, 0.0001);
vec3 F = F_Schlick(fresnel0, LdotH);
float partLambdaV = GetSmithJointGGXPartLambdaV(NdotV, roughness);
float DV = DV_SmithJointGGX(NdotH, NdotL, NdotV, roughness, partLambdaV);
specularLighting = F * DV;
//float diffuseTerm = Lambert();
float diffuseTerm = DisneyDiffuse(NdotV, NdotL, LdotV, perceptualRoughness);
diffuseLighting = vec3(diffuseTerm,diffuseTerm,diffuseTerm);
}
DirectLighting ShadeSurface_Directional(vec3 baseColor,
float roughness,
float metallic,
vec3 fresnel0,
vec3 N,
vec3 V)
{
DirectLighting lighting;
//ZERO_INITIALIZE(DirectLighting, lighting);
vec3 L = normalize(lightDir); // 光线方向
vec3 color = lightIntensity *lightColor;
float NdotL = dot(N, L); // Do not saturate
float NdotV = dot(N, V);
vec3 diffuseBsdf, specularBsdf;
BSDF(V, L, NdotL, NdotV, fresnel0, roughness*roughness, roughness,diffuseBsdf, specularBsdf);
float intensity = max(NdotL,0);
lighting.diffuse = diffuseBsdf * (intensity * diffuseDimmer);
lighting.specular = specularBsdf * (intensity * specularDimmer);
lighting.diffuse *= color * baseColor;
lighting.specular *= color * baseColor;
return lighting;
}
vec3 ComputeFresnel0(vec3 baseColor, float metallic, float dielectricF0)
{
return mix(vec3(dielectricF0,dielectricF0,dielectricF0), baseColor, metallic);
}
//- Shader entry point.
void shade(V2F inputs)
{
// Apply parallax occlusion mapping if possible
vec3 viewTS = worldSpaceToTangentSpace(getEyeVec(inputs.position), inputs);
applyParallaxOffset(inputs, viewTS);
// Fetch material parameters, and conversion to the specular/roughness model
float roughness = getRoughness(roughness_tex, inputs.sparse_coord);
vec3 baseColor = getBaseColor(basecolor_tex, inputs.sparse_coord);
float metallic = getMetallic(metallic_tex, inputs.sparse_coord);
float specularLevel = getSpecularLevel(specularlevel_tex, inputs.sparse_coord);
vec3 diffColor = generateDiffuseColor(baseColor, metallic);
vec3 specColor = generateSpecularColor(specularLevel, baseColor, metallic);
float occlusion = getAO(inputs.sparse_coord) * getShadowFactor();
float specOcclusion = specularOcclusionCorrection(occlusion, metallic, roughness);
LocalVectors vectors = computeLocalFrame(inputs);
vec3 fresnel0 = ComputeFresnel0(baseColor, metallic, DEFAULT_SPECULAR_VALUE);
//HDRP光照计算部分
DirectLighting lighting = ShadeSurface_Directional( baseColor,
roughness,
metallic,
fresnel0,
vectors.normal,
vectors.eye);
// Feed parameters for a physically based BRDF integration
//emissiveColorOutput(pbrComputeEmissive(emissive_tex, inputs.sparse_coord));
//albedoOutput(diffColor);
//diffuseShadingOutput(occlusion * envIrradiance(vectors.normal));
diffuseShadingOutput(lighting.diffuse * getShadowFactor());
specularShadingOutput(lighting.specular * getShadowFactor());
//sssCoefficientsOutput(getSSSCoefficients(inputs.sparse_coord));
}
上面的是SP的glslShader。为了能够更好的理解Unity HDRP的光照模型,我用unity的格式写了一个直线光计算的Shader,提取自Lit.Shader代码。下面的文件是Unity的hlsl语法Shader,比较重要的部分就是bsdf函数,可以直接放在HDRP5.10.0版本以上使用:
Shader "Unlit/SubstacnePainter"
{
Properties
{
baseColor ("baseColor", Color) = (1,1,1,1)
roughness ("roughness", Range(0,1)) = 1
metallic ("metallic", Range(0,1)) = 1
diffuseDimmer ("diffuseDimmer", Float) = 1
specularDimmer ("specularDimmer", Float) = 1
lightColor ("lightColor", Color) = (1,1,1,1)
lightIntensity ("lightIntensity", Float) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 worldPos : TEXCOORD1;
float3 worldNormal : TEXCOORD2;
};
float4 baseColor;
float4 lightColor;
float roughness;
float metallic;
uniform float diffuseDimmer;
uniform float specularDimmer;
uniform float3 lightDir;
uniform float lightIntensity;
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl"
// -----------------------------------------------------------------------------
// Constants
#define HALF_MAX 65504.0 // (2 - 2^-10) * 2^15
#define HALF_MAX_MINUS1 65472.0 // (2 - 2^-9) * 2^15
#define EPSILON 1.0e-4
#define PI 3.14159265359
#define TWO_PI 6.28318530718
#define FOUR_PI 12.56637061436
#define INV_PI 0.31830988618
#define INV_TWO_PI 0.15915494309
#define INV_FOUR_PI 0.07957747155
#define HALF_PI 1.57079632679
#define INV_HALF_PI 0.636619772367
#define FLT_EPSILON 1.192092896e-07 // Smallest positive number, such that 1.0 + FLT_EPSILON != 1.0
#define FLT_MIN 1.175494351e-38 // Minimum representable positive floating-point number
#define FLT_MAX 3.402823466e+38 // Maximum representable floating-point number
#define DEFAULT_SPECULAR_VALUE 0.04
#define FLT_INF asfloat(0x7F800000)
#define FLT_EPS 5.960464478e-8 // 2^-24, machine epsilon: 1 + EPS = 1 (half of the ULP for 1.0f)
#define HALF_MIN 6.103515625e-5 // 2^-14, the same value for 10, 11 and 16-bit: https://www.khronos.org/opengl/wiki/Small_Float_Formats
#define HALF_MAX 65504.0
#define UINT_MAX 0xFFFFFFFFu
// HDRP模拟光源
struct DirectLighting
{
float3 diffuse;
float3 specular;
};
float F_Schlick(float f0, float f90, float u)
{
float x = 1.0 - u;
float x2 = x * x;
float x5 = x * x2 * x2;
return (f90 - f0) * x5 + f0; // sub mul mul mul sub mad
}
float F_Schlick(float f0, float u)
{
return F_Schlick(f0, 1.0, u); // sub mul mul mul sub mad
}
float3 F_Schlick(float3 f0, float f90, float u)
{
float x = 1.0 - u;
float x2 = x * x;
float x5 = x * x2 * x2;
return f0 * (1.0 - x5) + (f90 * x5); // sub mul mul mul sub mul mad*3
}
float3 F_Schlick(float3 f0, float u)
{
return F_Schlick(f0, 1.0, u); // sub mul mul mul sub mad*3
}
float3 ComputeFresnel0(float3 baseColor, float metallic, float dielectricF0)
{
return lerp(dielectricF0.xxx, baseColor, metallic);
}
float DV_SmithJointGGX(float NdotH, float NdotL, float NdotV, float roughness, float partLambdaV)
{
float a2 = (roughness * roughness);
float s = (NdotH * a2 - NdotH) * NdotH + 1.0;
float lambdaV = NdotL * partLambdaV;
float lambdaL = NdotV * sqrt((-NdotL * a2 + NdotL) * NdotL + a2);
float2 D = float2(a2, s * s); // Fraction without the multiplier (1/Pi)
float2 G = float2(1, lambdaV + lambdaL); // Fraction without the multiplier (1/2)
// This function is only used for direct lighting.
// If roughness is 0, the probability of hitting a punctual or directional light is also 0.
// Therefore, we return 0. The most efficient way to do it is with a max().
return INV_PI * 0.5 * (D.x * G.x) / max(D.y * G.y, FLT_MIN);
}
float DisneyDiffuseNoPI(float NdotV, float NdotL, float LdotV, float perceptualRoughness)
{
// (2 * LdotH * LdotH) = 1 + LdotV
// float fd90 = 0.5 + 2 * LdotH * LdotH * perceptualRoughness;
float fd90 = 0.5 + (perceptualRoughness + perceptualRoughness * LdotV);
// Two schlick fresnel term
float lightScatter = F_Schlick(1.0, fd90, NdotL);
float viewScatter = F_Schlick(1.0, fd90, NdotV);
// Normalize the BRDF for polar view angles of up to (Pi/4).
// We use the worst case of (roughness = albedo = 1), and, for each view angle,
// integrate (brdf * cos(theta_light)) over all light directions.
// The resulting value is for (theta_view = 0), which is actually a little bit larger
// than the value of the integral for (theta_view = Pi/4).
// Hopefully, the compiler folds the constant together with (1/Pi).
return (1 / 1.03571) * (lightScatter * viewScatter);
}
float DisneyDiffuse(float NdotV, float NdotL, float LdotV, float perceptualRoughness)
{
return INV_PI * DisneyDiffuseNoPI(NdotV, NdotL, LdotV, perceptualRoughness);
}
float GetSmithJointGGXPartLambdaV(float NdotV, float roughness)
{
float a2 = (roughness * roughness);
return sqrt((-NdotV * a2 + NdotV) * NdotV + a2);
}
void BSDF( float3 V,
float3 L,
float NdotL,
float unclampNdotV,
float3 fresnel0,
float roughness,
float perceptualRoughness,
out float3 diffuseLighting,
out float3 specularLighting)
{
float LdotV, NdotH, LdotH, NdotV, invLenLV;
/*
GetBSDFAngle(V, L, NdotL, preLightData.NdotV, LdotV, NdotH, LdotH, NdotV, invLenLV);
*/
LdotV = dot(L, V);
invLenLV = rsqrt(max(2.0 * LdotV + 2.0, FLT_EPS));
NdotH = clamp((NdotL + unclampNdotV) * invLenLV, 0, 1);
LdotH = clamp(invLenLV * LdotV + invLenLV, 0, 1);
NdotV = max(unclampNdotV, 0.0001);
float3 F = F_Schlick(fresnel0, LdotH);
/*
计算彩虹色
if (HasFlag(bsdfData.materialFeatures, MATERIALFEATUREFLAGS_LIT_IRIDESCENCE))
{
F = lerp(F, bsdfData.fresnel0, bsdfData.iridescenceMask);
}
*/
float partLambdaV = GetSmithJointGGXPartLambdaV(NdotV, roughness);
float DV = DV_SmithJointGGX(NdotH, NdotL, NdotV, roughness, partLambdaV);
specularLighting = F * DV;
//float diffuseTerm = Lambert();
float diffuseTerm = DisneyDiffuse(NdotV, NdotL, LdotV, perceptualRoughness);
diffuseLighting = float3(diffuseTerm,diffuseTerm,diffuseTerm);
}
DirectLighting ShadeSurface_Directional(float3 baseColor,
float roughness,
float metallic,
float3 fresnel0,
float3 N,
float3 V)
{
DirectLighting lighting;
//ZERO_INITIALIZE(DirectLighting, lighting);
float3 L = normalize(-lightDir); // 光线方向
float3 color = lightIntensity * lightColor;
float NdotL = dot(N, L); // Do not saturate
float NdotV = dot(N, V);
/*
计算阴影和光照衰减
float attenuation;
EvaluateLight_Directional(lightLoopContext, posInput, light, builtinData, N, L, NdotL, color, attenuation);
*/
//ClampRoughness(roughness, light.minRoughness);
roughness = max(roughness, 0.001225);
float3 diffuseBsdf, specularBsdf;
BSDF(V, L, NdotL, NdotV, fresnel0, roughness*roughness, roughness,diffuseBsdf, specularBsdf);
/*
计算反射透射
bool surfaceReflection = NdotL > 0;
if (surfaceReflection)
{
*/
// so there NdotL must be bigger than 0
float intensity = saturate(NdotL);
lighting.diffuse = diffuseBsdf * (intensity * diffuseDimmer);
lighting.specular = specularBsdf * (intensity * specularDimmer);
lighting.diffuse *= color * baseColor;
lighting.specular *= color * baseColor;
/*
计算透射
}
else if (MaterialSupportsTransmission(bsdfData))
{
// Apply wrapped lighting to better handle thin objects at grazing angles.
float wrapNdotL = ComputeWrappedDiffuseLighting(NdotL, TRANSMISSION_WRAP_LIGHT);
float intensity = attenuation * wrapNdotL;
// We use diffuse lighting for accumulation since it is going to be blurred during the SSS pass.
// Note: Disney's LdoV term in 'diffuseBsdf' does not hold a meaningful value
// in the context of transmission, but we keep it unaltered for performance reasons.
lighting.diffuse = transmittance * (diffuseBsdf * (intensity * light.diffuseDimmer));
lighting.specular = 0; // No spec trans, the compiler should optimize
}
*/
return lighting;
}
v2f vert (appdata v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_VP,mul(UNITY_MATRIX_M,v.vertex));
o.worldPos = GetAbsolutePositionWS(mul(UNITY_MATRIX_M, v.vertex));
o.worldNormal = TransformObjectToWorldNormal(v.normal);
o.uv = v.uv;
return o;
}
float4 frag (v2f i) : SV_Target
{
float3 fresnel0 = ComputeFresnel0(baseColor, metallic, DEFAULT_SPECULAR_VALUE);
float3 V = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
float3 N = normalize(i.worldNormal);
//HDRP光照计算部分
DirectLighting lighting = ShadeSurface_Directional( baseColor,
roughness,
metallic,
fresnel0,
N,
V);
return float4(lighting.diffuse.xyz + lighting.specular.xyz,1);
}
ENDHLSL
}
}
}
下面就是Unity和SubstancePainter的光照结果对比。