738 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			738 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
# Common
 | 
						||
## Common.ush
 | 
						||
添加结构体,主要用在材质的CustomNode里。
 | 
						||
```c++
 | 
						||
// Used by toon shading.  
 | 
						||
// Define a global custom data structure which can be filled by Custom node in material BP.  
 | 
						||
struct FToonShadingPerMaterialCustomData  
 | 
						||
{  
 | 
						||
    // Toon specular  
 | 
						||
    float3 ToonSpecularColor;  
 | 
						||
    float ToonSpecularLocation;  
 | 
						||
    float ToonSpecularSmoothness;  
 | 
						||
    // Toon shadow  
 | 
						||
    float3 ToonShadowColor;  
 | 
						||
    float ToonShadowLocation;  
 | 
						||
    float ToonShadowSmoothness;  
 | 
						||
    float ToonForceShadow;  
 | 
						||
    // Toon secondary shadow  
 | 
						||
    float3 ToonSecondaryShadowColor;  
 | 
						||
    float ToonSecondaryShadowLocation;  
 | 
						||
    float ToonSecondaryShadowSmoothness;  
 | 
						||
    // custom data, usually not used  
 | 
						||
    float4 CustomData0;  
 | 
						||
    float4 CustomData1;  
 | 
						||
    float4 CustomData2;  
 | 
						||
    float4 CustomData3;  
 | 
						||
};  
 | 
						||
  
 | 
						||
static FToonShadingPerMaterialCustomData ToonShadingPerMaterialCustomData;
 | 
						||
```
 | 
						||
 | 
						||
 | 
						||
## DeferredShadingCommon.ush
 | 
						||
1. 实现[[#Encode/Decode函数]]
 | 
						||
2. HasCustomGBufferData()函数添加对应的ToonShadingModel宏判断
 | 
						||
3. [[#FGBufferData新增变量]]
 | 
						||
4. [[#Encode/Decode GBufferData新增逻辑]]
 | 
						||
	1. Metallic/Specualr/Roughness => ToonShadowLocation/ToonForceShadow/ToonShadowSmoothness
 | 
						||
	2. AO => ToonSecondaryShadowLocation
 | 
						||
	3. CustomData => ToonShadowColor/ToonSecondaryShadowSmoothness
 | 
						||
	4. PrecomputedShadowFactors => ToonSecondaryShadowColor
 | 
						||
5. `#define GBUFFER_REFACTOR 0` 以此关闭自动生成Encode/Decode GBufferData代码,并使用硬编码调用Encode/Decode GBufferData。
 | 
						||
6. `#if WRITES_VELOCITY_TO_GBUFFER` => `#if GBUFFER_HAS_VELOCITY`,以此**关闭写入VELOCITY到GBuffer中**。
 | 
						||
 | 
						||
### Encode/Decode函数
 | 
						||
RGB655 to 8-bit RGB。
 | 
						||
将R 256 => 64 ,GB 256 => 32。之后使用2个8bit浮点来存储:通道1存储R与G的头两位;通道2存储G的后3位与B。
 | 
						||
```c++
 | 
						||
float2 EncodeColorToRGB655(float3 Color)  
 | 
						||
{  
 | 
						||
    const uint ChannelR = (1 << 6) - 1;  
 | 
						||
    const uint ChannelG = (1 << 5) - 1;  
 | 
						||
    const uint ChannelB = (1 << 5) - 1;  
 | 
						||
  
 | 
						||
    uint3 RoundedColor = uint3(float3(  
 | 
						||
       round(Color.r * ChannelR),  
 | 
						||
       round(Color.g * ChannelG),  
 | 
						||
       round(Color.b * ChannelB)  
 | 
						||
    ));  
 | 
						||
    return float2(  
 | 
						||
       (RoundedColor.r << 2 | RoundedColor.g >> 3) / 255.0,  
 | 
						||
       (RoundedColor.g << 5 | RoundedColor.b     ) / 255.0  
 | 
						||
    );  
 | 
						||
}  
 | 
						||
  
 | 
						||
float3 DecodeRGB655ToColor(float2 RGB655)  
 | 
						||
{  
 | 
						||
    const uint ChannelR = (1 << 6) - 1;  
 | 
						||
    const uint ChannelG = (1 << 5) - 1;  
 | 
						||
    const uint ChannelB = (1 << 5) - 1;  
 | 
						||
  
 | 
						||
    uint2 Inputs = uint2(round(RGB655 * 255.0));  
 | 
						||
    uint BitBuffer = (Inputs.x << 8) | Inputs.y;  
 | 
						||
    uint R = (BitBuffer & 0xFC00) >> 10;  
 | 
						||
    uint G = (BitBuffer & 0x03E0) >> 5;  
 | 
						||
    uint B = (BitBuffer & 0x001F);  
 | 
						||
  
 | 
						||
    return float3(R, G, B) * float3(1.0 / ChannelR, 1.0 / ChannelG, 1.0 / ChannelB);  
 | 
						||
}
 | 
						||
```
 | 
						||
 | 
						||
### FGBufferData新增变量
 | 
						||
```c++
 | 
						||
struct FGBufferData  
 | 
						||
{
 | 
						||
...
 | 
						||
	// Toon specular  
 | 
						||
	// 0..1, specular color  
 | 
						||
	half3 ToonSpecularColor;  
 | 
						||
	// 0..1, specular edge position  
 | 
						||
	half ToonSpecularLocation;  
 | 
						||
	// 0..1, specular edge smoothness  
 | 
						||
	half ToonSpecularSmoothness;  
 | 
						||
	  
 | 
						||
	// Toon shadow  
 | 
						||
	// 0..1, shadow color  
 | 
						||
	half3 ToonShadowColor;  
 | 
						||
	// 0..1, shadow egde location  
 | 
						||
	half ToonShadowLocation;  
 | 
						||
	// 0..1, shadow edge smoothness  
 | 
						||
	half ToonShadowSmoothness;  
 | 
						||
	// 0..1, force shadow  
 | 
						||
	half ToonForceShadow;  
 | 
						||
	  
 | 
						||
	// Toon secondary shadow  
 | 
						||
	// 0..1, secondary shadow color  
 | 
						||
	float3 ToonSecondaryShadowColor;  
 | 
						||
	// 0..1, secondary shadow edge location  
 | 
						||
	float ToonSecondaryShadowLocation;  
 | 
						||
	// 0..1, secondary shadow edge smoothness  
 | 
						||
	float ToonSecondaryShadowSmoothness;  
 | 
						||
	  
 | 
						||
	// Toon render  
 | 
						||
	half3 ToonCalcShadowColor;
 | 
						||
};
 | 
						||
```
 | 
						||
 | 
						||
### Encode/Decode GBufferData新增逻辑
 | 
						||
```c++
 | 
						||
  
 | 
						||
void EncodeGBuffer(  
 | 
						||
    FGBufferData GBuffer,  
 | 
						||
    out float4 OutGBufferA,  
 | 
						||
    out float4 OutGBufferB,  
 | 
						||
    out float4 OutGBufferC,  
 | 
						||
    out float4 OutGBufferD,  
 | 
						||
    out float4 OutGBufferE,  
 | 
						||
    out float4 OutGBufferVelocity,  
 | 
						||
    float QuantizationBias = 0    // -0.5 to 0.5 random float. Used to bias quantization.  
 | 
						||
    )  
 | 
						||
{
 | 
						||
	...
 | 
						||
		switch(GBuffer.ShadingModelID)
 | 
						||
		{
 | 
						||
			case SHADINGMODELID_TOON_BASE:
 | 
						||
				OutGBufferB.r = ToonShadingPerMaterialCustomData.ToonShadowLocation;
 | 
						||
				OutGBufferB.g = ToonShadingPerMaterialCustomData.ToonForceShadow;
 | 
						||
				OutGBufferB.b = ToonShadingPerMaterialCustomData.ToonShadowSmoothness;
 | 
						||
				OutGBufferC.a = ToonShadingPerMaterialCustomData.ToonSecondaryShadowLocation;
 | 
						||
				OutGBufferD.a = ToonShadingPerMaterialCustomData.ToonSecondaryShadowSmoothness;
 | 
						||
				OutGBufferD.rgb = ToonShadingPerMaterialCustomData.ToonShadowColor.rgb;
 | 
						||
				OutGBufferE.gba = ToonShadingPerMaterialCustomData.ToonSecondaryShadowColor.rgb;
 | 
						||
				break;
 | 
						||
			case SHADINGMODELID_TOON_PBR:
 | 
						||
				OutGBufferB.g = ToonShadingPerMaterialCustomData.ToonShadowLocation;
 | 
						||
				OutGBufferD.a = ToonShadingPerMaterialCustomData.ToonShadowSmoothness;
 | 
						||
				OutGBufferD.rgb = ToonShadingPerMaterialCustomData.ToonShadowColor.rgb;
 | 
						||
				OutGBufferE.gba = ToonShadingPerMaterialCustomData.ToonSpecularColor.rgb;
 | 
						||
				break;
 | 
						||
			case SHADINGMODELID_TOON_SKIN:
 | 
						||
				OutGBufferB.r = ToonShadingPerMaterialCustomData.ToonShadowLocation;
 | 
						||
				OutGBufferD.a = ToonShadingPerMaterialCustomData.ToonShadowSmoothness;
 | 
						||
				OutGBufferD.rgb = ToonShadingPerMaterialCustomData.ToonShadowColor.rgb;
 | 
						||
				break;
 | 
						||
			default:
 | 
						||
				break;
 | 
						||
		}
 | 
						||
	...
 | 
						||
}
 | 
						||
 | 
						||
FGBufferData DecodeGBufferData(  
 | 
						||
    float4 InGBufferA,  
 | 
						||
    float4 InGBufferB,  
 | 
						||
    float4 InGBufferC,  
 | 
						||
    float4 InGBufferD,  
 | 
						||
    float4 InGBufferE,  
 | 
						||
    float4 InGBufferF,  
 | 
						||
    float4 InGBufferVelocity,  
 | 
						||
    float CustomNativeDepth,  
 | 
						||
    uint CustomStencil,  
 | 
						||
    float SceneDepth,  
 | 
						||
    bool bGetNormalizedNormal,  
 | 
						||
    bool bChecker)  
 | 
						||
{  
 | 
						||
    FGBufferData GBuffer = (FGBufferData)0;
 | 
						||
	...
 | 
						||
	 switch(GBuffer.ShadingModelID)
 | 
						||
	    {
 | 
						||
	        case SHADINGMODELID_TOON_BASE:
 | 
						||
	            GBuffer.ToonShadowColor               = InGBufferD.rgb;
 | 
						||
	            GBuffer.ToonShadowLocation            = InGBufferB.r;
 | 
						||
	            GBuffer.ToonShadowSmoothness          = InGBufferB.b;
 | 
						||
	            GBuffer.ToonForceShadow               = InGBufferB.g;
 | 
						||
				GBuffer.ToonSecondaryShadowColor      = InGBufferE.gba;
 | 
						||
				GBuffer.ToonSecondaryShadowLocation   = InGBufferC.a;
 | 
						||
				GBuffer.ToonSecondaryShadowSmoothness = InGBufferD.a;
 | 
						||
	            GBuffer.Metallic = 0.0;
 | 
						||
	            GBuffer.Specular = 1.0;
 | 
						||
	            GBuffer.Roughness = 1.0;
 | 
						||
	            GBuffer.GBufferAO = 0.0;
 | 
						||
	            GBuffer.IndirectIrradiance = 1.0;
 | 
						||
	            GBuffer.PrecomputedShadowFactors = !(GBuffer.SelectiveOutputMask & SKIP_PRECSHADOW_MASK) ? float4(InGBufferE.r, 1.0, 1.0, 1.0) : ((GBuffer.SelectiveOutputMask & ZERO_PRECSHADOW_MASK) ? 0 :  1);
 | 
						||
	            GBuffer.StoredMetallic = 0.0;
 | 
						||
	            GBuffer.StoredSpecular = 1.0;
 | 
						||
	            break;
 | 
						||
	        case SHADINGMODELID_TOON_PBR:
 | 
						||
	            GBuffer.ToonSpecularColor = InGBufferE.gba;
 | 
						||
	            GBuffer.ToonShadowColor = InGBufferD.rgb;
 | 
						||
	            GBuffer.ToonShadowLocation = InGBufferB.g;
 | 
						||
	            GBuffer.ToonShadowSmoothness = InGBufferD.a;
 | 
						||
				GBuffer.ToonSecondaryShadowColor = GBuffer.ToonShadowColor;
 | 
						||
				GBuffer.ToonForceShadow = 1.0;
 | 
						||
				GBuffer.ToonSpecularLocation = 1.0;
 | 
						||
	            GBuffer.Specular = 1.0;
 | 
						||
	            GBuffer.PrecomputedShadowFactors = !(GBuffer.SelectiveOutputMask & SKIP_PRECSHADOW_MASK) ? float4(InGBufferE.r, 1.0, 1.0, 1.0) : ((GBuffer.SelectiveOutputMask & ZERO_PRECSHADOW_MASK) ? 0 :  1);
 | 
						||
	            break;
 | 
						||
	        case SHADINGMODELID_TOON_SKIN:
 | 
						||
	            GBuffer.ToonShadowColor = InGBufferD.rgb;
 | 
						||
	            GBuffer.ToonShadowLocation = InGBufferB.r;
 | 
						||
	            GBuffer.ToonShadowSmoothness = InGBufferD.a;
 | 
						||
				GBuffer.ToonSecondaryShadowColor = GBuffer.ToonShadowColor;
 | 
						||
				GBuffer.ToonForceShadow = 1.0;
 | 
						||
	            GBuffer.Metallic = 0.0;
 | 
						||
				GBuffer.StoredMetallic = 0.0;
 | 
						||
	            GBuffer.PrecomputedShadowFactors = !(GBuffer.SelectiveOutputMask & SKIP_PRECSHADOW_MASK) ? float4(InGBufferE.r, 1.0, 1.0, 1.0) : ((GBuffer.SelectiveOutputMask & ZERO_PRECSHADOW_MASK) ? 0 :  1);
 | 
						||
	            break;
 | 
						||
	        default:
 | 
						||
	            break;
 | 
						||
	    }
 | 
						||
	...
 | 
						||
};
 | 
						||
```
 | 
						||
# BasePass
 | 
						||
BasePassPixelShader.usf
 | 
						||
1. `#if 1` => `#if GBUFFER_REFACTOR && 0`,以此关闭自动生成Encode/Decode GBufferData代码,并使用硬编码调用Encode/Decode GBufferData。
 | 
						||
2. 在FPixelShaderInOut_MainPS()中添加写入FGBufferData逻辑。代码如下:
 | 
						||
 | 
						||
```c++
 | 
						||
...
 | 
						||
switch(GBuffer.ShadingModelID)
 | 
						||
{
 | 
						||
	case SHADINGMODELID_TOON_BASE:
 | 
						||
		GBuffer.ToonShadowColor = ToonShadingPerMaterialCustomData.ToonShadowColor.rgb;
 | 
						||
		GBuffer.ToonShadowLocation = ToonShadingPerMaterialCustomData.ToonShadowLocation;
 | 
						||
		GBuffer.ToonShadowSmoothness = ToonShadingPerMaterialCustomData.ToonShadowSmoothness;
 | 
						||
		GBuffer.ToonForceShadow = ToonShadingPerMaterialCustomData.ToonForceShadow;
 | 
						||
		GBuffer.ToonSecondaryShadowColor = ToonShadingPerMaterialCustomData.ToonSecondaryShadowColor.rgb;
 | 
						||
		GBuffer.ToonSecondaryShadowLocation = ToonShadingPerMaterialCustomData.ToonSecondaryShadowLocation;
 | 
						||
		GBuffer.ToonSecondaryShadowSmoothness = ToonShadingPerMaterialCustomData.ToonSecondaryShadowSmoothness;
 | 
						||
		GBuffer.Specular = 1.0;
 | 
						||
		GBuffer.GBufferAO = 0.0;
 | 
						||
		GBuffer.PrecomputedShadowFactors.gba = 1;
 | 
						||
		break;
 | 
						||
	case SHADINGMODELID_TOON_PBR:
 | 
						||
		GBuffer.ToonSpecularColor = ToonShadingPerMaterialCustomData.ToonSpecularColor.rgb;
 | 
						||
		GBuffer.ToonShadowColor = ToonShadingPerMaterialCustomData.ToonShadowColor.rgb;
 | 
						||
		GBuffer.ToonShadowLocation = ToonShadingPerMaterialCustomData.ToonShadowLocation;
 | 
						||
		GBuffer.ToonShadowSmoothness = ToonShadingPerMaterialCustomData.ToonShadowSmoothness;
 | 
						||
		GBuffer.ToonSecondaryShadowColor = ToonShadingPerMaterialCustomData.ToonShadowColor.rgb;
 | 
						||
		GBuffer.ToonForceShadow = 1.0;
 | 
						||
		GBuffer.Specular = 1.0;
 | 
						||
		GBuffer.PrecomputedShadowFactors.gba = 1;
 | 
						||
		break;
 | 
						||
	case SHADINGMODELID_TOON_SKIN:
 | 
						||
		GBuffer.ToonShadowColor = ToonShadingPerMaterialCustomData.ToonShadowColor.rgb;
 | 
						||
		GBuffer.ToonShadowLocation = ToonShadingPerMaterialCustomData.ToonShadowLocation;
 | 
						||
		GBuffer.ToonShadowSmoothness = ToonShadingPerMaterialCustomData.ToonShadowSmoothness;
 | 
						||
		GBuffer.ToonSecondaryShadowColor = ToonShadingPerMaterialCustomData.ToonShadowColor.rgb;
 | 
						||
		GBuffer.ToonForceShadow = 1.0;
 | 
						||
		GBuffer.PrecomputedShadowFactors.g = 1;
 | 
						||
		break;
 | 
						||
	default:
 | 
						||
		break;
 | 
						||
}
 | 
						||
...
 | 
						||
```
 | 
						||
 | 
						||
# Lighting
 | 
						||
 | 
						||
## ShadingModels
 | 
						||
### ShadingCommon.ush
 | 
						||
**添加ShadingModelID宏**:
 | 
						||
- SHADINGMODELID_TOON_BASE           13
 | 
						||
- SHADINGMODELID_TOON_PBR             14  
 | 
						||
- SHADINGMODELID_TOON_SKIN            15  
 | 
						||
- SHADINGMODELID_NUM                       16
 | 
						||
 | 
						||
判断是否是IsToonShadingModel:
 | 
						||
```c++
 | 
						||
bool IsToonShadingModel(uint ShadingModel)  
 | 
						||
{  
 | 
						||
    uint4 ToonShadingModels = uint4(SHADINGMODELID_TOON_BASE, SHADINGMODELID_TOON_PBR, SHADINGMODELID_TOON_SKIN, 0xFF);  
 | 
						||
    return any(ShadingModel.xxxx == ToonShadingModels);  
 | 
						||
}
 | 
						||
```
 | 
						||
## DeferredLightingCommon.ush
 | 
						||
修改了AccumulateDynamicLighting()的逻辑。
 | 
						||
```c++
 | 
						||
FLightAccumulator AccumulateDynamicLighting(
 | 
						||
	float3 TranslatedWorldPosition, half3 CameraVector, FGBufferData GBuffer, half AmbientOcclusion, uint ShadingModelID,
 | 
						||
	FDeferredLightData LightData, half4 LightAttenuation, float Dither, uint2 SVPos, 
 | 
						||
	inout float SurfaceShadow)
 | 
						||
{
 | 
						||
	FLightAccumulator LightAccumulator = (FLightAccumulator)0;
 | 
						||
 | 
						||
	half3 V = -CameraVector;
 | 
						||
	half3 N = GBuffer.WorldNormal;
 | 
						||
	BRANCH if( GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT && CLEAR_COAT_BOTTOM_NORMAL)
 | 
						||
	{
 | 
						||
		const float2 oct1 = ((float2(GBuffer.CustomData.a, GBuffer.CustomData.z) * 4) - (512.0/255.0)) + UnitVectorToOctahedron(GBuffer.WorldNormal);
 | 
						||
		N = OctahedronToUnitVector(oct1);			
 | 
						||
	}
 | 
						||
	
 | 
						||
	float3 L = LightData.Direction;	// Already normalized
 | 
						||
	float3 ToLight = L;
 | 
						||
	float3 MaskedLightColor = LightData.Color;
 | 
						||
	float LightMask = 1;
 | 
						||
	if (LightData.bRadialLight)
 | 
						||
	{
 | 
						||
		LightMask = GetLocalLightAttenuation( TranslatedWorldPosition, LightData, ToLight, L );
 | 
						||
		MaskedLightColor *= LightMask;
 | 
						||
	}
 | 
						||
 | 
						||
	LightAccumulator.EstimatedCost += 0.3f;		// running the PixelShader at all has a cost
 | 
						||
 | 
						||
	BRANCH
 | 
						||
	if( LightMask > 0 )
 | 
						||
	{
 | 
						||
		FShadowTerms Shadow;
 | 
						||
		Shadow.SurfaceShadow = AmbientOcclusion;
 | 
						||
		Shadow.TransmissionShadow = 1;
 | 
						||
		Shadow.TransmissionThickness = 1;
 | 
						||
		Shadow.HairTransmittance.OpaqueVisibility = 1;
 | 
						||
		const float ContactShadowOpacity = GBuffer.CustomData.a;
 | 
						||
		GetShadowTerms(GBuffer.Depth, GBuffer.PrecomputedShadowFactors, GBuffer.ShadingModelID, ContactShadowOpacity,
 | 
						||
			LightData, TranslatedWorldPosition, L, LightAttenuation, Dither, Shadow);
 | 
						||
		SurfaceShadow = Shadow.SurfaceShadow;
 | 
						||
 | 
						||
		LightAccumulator.EstimatedCost += 0.3f;		// add the cost of getting the shadow terms
 | 
						||
 | 
						||
#if SHADING_PATH_MOBILE
 | 
						||
		const bool bNeedsSeparateSubsurfaceLightAccumulation = UseSubsurfaceProfile(GBuffer.ShadingModelID);
 | 
						||
		
 | 
						||
		FDirectLighting Lighting = (FDirectLighting)0;
 | 
						||
 | 
						||
		half NoL = max(0, dot(GBuffer.WorldNormal, L));
 | 
						||
	#if TRANSLUCENCY_NON_DIRECTIONAL
 | 
						||
		NoL = 1.0f;
 | 
						||
	#endif
 | 
						||
		Lighting = EvaluateBxDF(GBuffer, N, V, L, NoL, Shadow);
 | 
						||
 | 
						||
		Lighting.Specular *= LightData.SpecularScale;
 | 
						||
				
 | 
						||
		LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, MaskedLightColor * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation );
 | 
						||
		LightAccumulator_AddSplit( LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, MaskedLightColor * Shadow.TransmissionShadow, bNeedsSeparateSubsurfaceLightAccumulation );
 | 
						||
#else // SHADING_PATH_MOBILE
 | 
						||
		//修改了这里
 | 
						||
		bool UseToonShadow = IsToonShadingModel(GBuffer.ShadingModelID);
 | 
						||
		BRANCH
 | 
						||
		if( Shadow.SurfaceShadow + Shadow.TransmissionShadow > 0 || UseToonShadow)//修改结束
 | 
						||
		{
 | 
						||
			const bool bNeedsSeparateSubsurfaceLightAccumulation = UseSubsurfaceProfile(GBuffer.ShadingModelID);
 | 
						||
			//修改了这里
 | 
						||
			BRANCH
 | 
						||
			if(UseToonShadow)
 | 
						||
			{
 | 
						||
				float NoL = dot(N, L);
 | 
						||
				float ToonNoL = min(NoL, GBuffer.ToonForceShadow);
 | 
						||
				//合并SurfaceShadow以及Transmision Shadow
 | 
						||
				Shadow.SurfaceShadow = min(Shadow.SurfaceShadow, Shadow.TransmissionShadow);
 | 
						||
 | 
						||
				//根据ToonShadowSmoothness、ToonShadowLocation、NoL计算阴影亮度,最后计算主阴影颜色。
 | 
						||
				float RangeHalf = GBuffer.ToonShadowSmoothness * 0.5;
 | 
						||
				float RangeMin  = max(0.0, GBuffer.ToonShadowLocation - RangeHalf);
 | 
						||
				float RangeMax  = min(1.0, GBuffer.ToonShadowLocation + RangeHalf);
 | 
						||
				float ShadowIntensity = Shadow.SurfaceShadow * smoothstep(RangeMin, RangeMax, ToonNoL);
 | 
						||
				GBuffer.ToonCalcShadowColor = lerp(GBuffer.ToonShadowColor * LightData.SpecularScale, (1.0).xxx, ShadowIntensity);
 | 
						||
 | 
						||
				//计算次级阴影颜色,并最终合成。
 | 
						||
				RangeHalf = GBuffer.ToonSecondaryShadowSmoothness * 0.5;
 | 
						||
				RangeMin = max(0.0, GBuffer.ToonSecondaryShadowLocation - RangeHalf);
 | 
						||
				RangeMax  = min(1.0, GBuffer.ToonSecondaryShadowLocation + RangeHalf);
 | 
						||
				ShadowIntensity = Shadow.SurfaceShadow * smoothstep(RangeMin, RangeMax, ToonNoL);
 | 
						||
				GBuffer.ToonCalcShadowColor = lerp(GBuffer.ToonSecondaryShadowColor * LightData.SpecularScale, GBuffer.ToonCalcShadowColor, ShadowIntensity);
 | 
						||
			}
 | 
						||
			//修改结束
 | 
						||
 | 
						||
		#if NON_DIRECTIONAL_DIRECT_LIGHTING
 | 
						||
			float Lighting;
 | 
						||
 | 
						||
			if( LightData.bRectLight )
 | 
						||
			{
 | 
						||
				FRect Rect = GetRect( ToLight, LightData );
 | 
						||
 | 
						||
				Lighting = IntegrateLight( Rect );
 | 
						||
			}
 | 
						||
			else
 | 
						||
			{
 | 
						||
				FCapsuleLight Capsule = GetCapsule( ToLight, LightData );
 | 
						||
 | 
						||
				Lighting = IntegrateLight( Capsule, LightData.bInverseSquared );
 | 
						||
			}
 | 
						||
 | 
						||
			float3 LightingDiffuse = Diffuse_Lambert( GBuffer.DiffuseColor ) * Lighting;
 | 
						||
			LightAccumulator_AddSplit(LightAccumulator, LightingDiffuse, 0.0f, 0, MaskedLightColor * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation);
 | 
						||
		#else
 | 
						||
			FDirectLighting Lighting;
 | 
						||
 | 
						||
			if (LightData.bRectLight)
 | 
						||
			{
 | 
						||
				FRect Rect = GetRect( ToLight, LightData );
 | 
						||
				const FRectTexture SourceTexture = ConvertToRectTexture(LightData);
 | 
						||
 | 
						||
				#if REFERENCE_QUALITY
 | 
						||
					Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture, SVPos );
 | 
						||
				#else
 | 
						||
					Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture);
 | 
						||
				#endif
 | 
						||
			}
 | 
						||
			else
 | 
						||
			{
 | 
						||
				FCapsuleLight Capsule = GetCapsule( ToLight, LightData );
 | 
						||
 | 
						||
				#if REFERENCE_QUALITY
 | 
						||
					Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, SVPos );
 | 
						||
				#else
 | 
						||
					Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, LightData.bInverseSquared );
 | 
						||
				#endif
 | 
						||
			}
 | 
						||
			//修改了这里
 | 
						||
			float SurfaceShadow = UseToonShadow ? 1.0 : Shadow.SurfaceShadow;
 | 
						||
			float TransmissionShadow = UseToonShadow ? 1.0 : Shadow.TransmissionShadow;
 | 
						||
			Lighting.Specular *= UseToonShadow ? GBuffer.ToonSpecularColor : LightData.SpecularScale;
 | 
						||
				
 | 
						||
			LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, MaskedLightColor * SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation );
 | 
						||
			LightAccumulator_AddSplit( LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, MaskedLightColor * TransmissionShadow, bNeedsSeparateSubsurfaceLightAccumulation );
 | 
						||
			//修改结束
 | 
						||
			LightAccumulator.EstimatedCost += 0.4f;		// add the cost of the lighting computations (should sum up to 1 form one light)
 | 
						||
		#endif
 | 
						||
		}
 | 
						||
#endif // SHADING_PATH_MOBILE
 | 
						||
	}
 | 
						||
	return LightAccumulator;
 | 
						||
}
 | 
						||
```
 | 
						||
 | 
						||
## ShadingModels.ush
 | 
						||
```c++
 | 
						||
float3 ToonSpecular(float ToonSpecularLocation, float ToonSpecularSmoothness, float3 ToonSpecularColor, float NoL)  
 | 
						||
{  
 | 
						||
    float ToonSpecularRangeHalf = ToonSpecularSmoothness * 0.5;  
 | 
						||
    float ToonSpecularRangeMin  = ToonSpecularLocation - ToonSpecularRangeHalf;  
 | 
						||
    float ToonSpecularRangeMax  = ToonSpecularLocation + ToonSpecularRangeHalf;  
 | 
						||
    return smoothstep(ToonSpecularRangeMin, ToonSpecularRangeMax, NoL) * ToonSpecularColor;  
 | 
						||
}
 | 
						||
```
 | 
						||
 | 
						||
创建了ToonCustomBxDF(**SHADINGMODELID_TOON_BASE**)与ToonLitBxDF(**SHADINGMODELID_TOON_PBR**、**SHADINGMODELID_TOON_SKIN**)2个ShadingModel函数。
 | 
						||
 | 
						||
### ToonCustomBxDF的修改
 | 
						||
Diffuse里面乘以之前在DeferredShadingCommon.ush中计算好的ShadowColor(已经计算了NoL)
 | 
						||
`Lighting.Diffuse *= AreaLight.FalloffColor * (Falloff * NoL);`
 | 
						||
=>
 | 
						||
`Lighting.Diffuse *= AreaLight.FalloffColor * Falloff * GBuffer.ToonCalcShadowColor;`
 | 
						||
 | 
						||
Speuclar直接归零,具体是在BasePass阶段进行计算了。
 | 
						||
`Lighting.Specular = 0;`
 | 
						||
### ToonLitBxDF的修改
 | 
						||
Diffuse里面乘以之前在DeferredShadingCommon.ush中计算好的ShadowColor(已经计算了NoL)
 | 
						||
`Lighting.Diffuse *= AreaLight.FalloffColor * (Falloff * NoL);`
 | 
						||
=>
 | 
						||
`Lighting.Diffuse *= AreaLight.FalloffColor * Falloff * GBuffer.ToonCalcShadowColor;`
 | 
						||
 | 
						||
Speuclar最后乘以了**Shadow.SurfaceShadow**
 | 
						||
`Lighting.Specular *= Shadow.SurfaceShadow;`
 | 
						||
 | 
						||
 | 
						||
 | 
						||
```c++
 | 
						||
 | 
						||
FDirectLighting ToonLitBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, FAreaLight AreaLight, FShadowTerms Shadow )
 | 
						||
{
 | 
						||
	BxDFContext Context;
 | 
						||
	FDirectLighting Lighting;
 | 
						||
 | 
						||
#if SUPPORTS_ANISOTROPIC_MATERIALS
 | 
						||
	bool bHasAnisotropy = HasAnisotropy(GBuffer.SelectiveOutputMask);
 | 
						||
#else
 | 
						||
	bool bHasAnisotropy = false;
 | 
						||
#endif
 | 
						||
 | 
						||
	float NoV, VoH, NoH;
 | 
						||
	BRANCH
 | 
						||
	if (bHasAnisotropy)
 | 
						||
	{
 | 
						||
		half3 X = GBuffer.WorldTangent;
 | 
						||
		half3 Y = normalize(cross(N, X));
 | 
						||
		Init(Context, N, X, Y, V, L);
 | 
						||
 | 
						||
		NoV = Context.NoV;
 | 
						||
		VoH = Context.VoH;
 | 
						||
		NoH = Context.NoH;
 | 
						||
	}
 | 
						||
	else
 | 
						||
	{
 | 
						||
#if SHADING_PATH_MOBILE
 | 
						||
		InitMobile(Context, N, V, L, NoL);
 | 
						||
#else
 | 
						||
		Init(Context, N, V, L);
 | 
						||
#endif
 | 
						||
 | 
						||
		NoV = Context.NoV;
 | 
						||
		VoH = Context.VoH;
 | 
						||
		NoH = Context.NoH;
 | 
						||
 | 
						||
		SphereMaxNoH(Context, AreaLight.SphereSinAlpha, true);
 | 
						||
	}
 | 
						||
 | 
						||
	Context.NoV = saturate(abs( Context.NoV ) + 1e-5);
 | 
						||
 | 
						||
#if MATERIAL_ROUGHDIFFUSE
 | 
						||
	// Chan diffuse model with roughness == specular roughness. This is not necessarily a good modelisation of reality because when the mean free path is super small, the diffuse can in fact looks rougher. But this is a start.
 | 
						||
	// Also we cannot use the morphed context maximising NoH as this is causing visual artefact when interpolating rough/smooth diffuse response. 
 | 
						||
	Lighting.Diffuse = Diffuse_Chan(GBuffer.DiffuseColor, Pow4(GBuffer.Roughness), NoV, NoL, VoH, NoH, GetAreaLightDiffuseMicroReflWeight(AreaLight));
 | 
						||
#else
 | 
						||
	Lighting.Diffuse = Diffuse_Lambert(GBuffer.DiffuseColor);
 | 
						||
#endif
 | 
						||
	// Toon Diffuse
 | 
						||
	Lighting.Diffuse *= AreaLight.FalloffColor * Falloff * GBuffer.ToonCalcShadowColor;
 | 
						||
 | 
						||
	BRANCH
 | 
						||
	if (bHasAnisotropy)
 | 
						||
	{
 | 
						||
		//Lighting.Specular = GBuffer.WorldTangent * .5f + .5f;
 | 
						||
		Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * SpecularGGX(GBuffer.Roughness, GBuffer.Anisotropy, GBuffer.SpecularColor, Context, NoL, AreaLight);
 | 
						||
	}
 | 
						||
	else
 | 
						||
	{
 | 
						||
		if( IsRectLight(AreaLight) )
 | 
						||
		{
 | 
						||
			Lighting.Specular = RectGGXApproxLTC(GBuffer.Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture);
 | 
						||
		}
 | 
						||
		else
 | 
						||
		{
 | 
						||
			// Toon specular
 | 
						||
			Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * SpecularGGX(GBuffer.Roughness, GBuffer.SpecularColor, Context, NoL, AreaLight);
 | 
						||
		}
 | 
						||
	}
 | 
						||
	Lighting.Specular *= Shadow.SurfaceShadow;
 | 
						||
 | 
						||
	FBxDFEnergyTerms EnergyTerms = ComputeGGXSpecEnergyTerms(GBuffer.Roughness, Context.NoV, GBuffer.SpecularColor);
 | 
						||
 | 
						||
	// Add energy presevation (i.e. attenuation of the specular layer onto the diffuse component
 | 
						||
	Lighting.Diffuse *= ComputeEnergyPreservation(EnergyTerms);
 | 
						||
 | 
						||
	// Add specular microfacet multiple scattering term (energy-conservation)
 | 
						||
	Lighting.Specular *= ComputeEnergyConservation(EnergyTerms);
 | 
						||
 | 
						||
	Lighting.Transmission = 0;
 | 
						||
	return Lighting;
 | 
						||
}
 | 
						||
 | 
						||
FDirectLighting ToonCustomBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, FAreaLight AreaLight, FShadowTerms Shadow )
 | 
						||
{
 | 
						||
	BxDFContext Context;
 | 
						||
	FDirectLighting Lighting;
 | 
						||
 | 
						||
	float NoV, VoH, NoH;
 | 
						||
#if SHADING_PATH_MOBILE
 | 
						||
	InitMobile(Context, N, V, L, NoL);
 | 
						||
#else
 | 
						||
	Init(Context, N, V, L);
 | 
						||
#endif
 | 
						||
	NoV = Context.NoV;
 | 
						||
	VoH = Context.VoH;
 | 
						||
	NoH = Context.NoH;
 | 
						||
 | 
						||
	SphereMaxNoH(Context, AreaLight.SphereSinAlpha, true);
 | 
						||
 | 
						||
	Context.NoV = saturate(abs( Context.NoV ) + 1e-5);
 | 
						||
 | 
						||
#if MATERIAL_ROUGHDIFFUSE
 | 
						||
	// Chan diffuse model with roughness == specular roughness. This is not necessarily a good modelisation of reality because when the mean free path is super small, the diffuse can in fact looks rougher. But this is a start.
 | 
						||
	// Also we cannot use the morphed context maximising NoH as this is causing visual artefact when interpolating rough/smooth diffuse response. 
 | 
						||
	Lighting.Diffuse = Diffuse_Chan(GBuffer.DiffuseColor, Pow4(GBuffer.Roughness), NoV, NoL, VoH, NoH, GetAreaLightDiffuseMicroReflWeight(AreaLight));
 | 
						||
#else
 | 
						||
	Lighting.Diffuse = Diffuse_Lambert(GBuffer.DiffuseColor);
 | 
						||
#endif
 | 
						||
	// Toon Diffuse
 | 
						||
	Lighting.Diffuse *= AreaLight.FalloffColor * Falloff * GBuffer.ToonCalcShadowColor;
 | 
						||
 | 
						||
	// Toon specular
 | 
						||
	// Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * ToonSpecular(GBuffer.ToonSpecularLocation, GBuffer.ToonSpecularSmoothness, GBuffer.ToonSpecularColor, NoL);
 | 
						||
	// Lighting.Specular *= Shadow.SurfaceShadow;
 | 
						||
 | 
						||
	// FBxDFEnergyTerms EnergyTerms = ComputeGGXSpecEnergyTerms(GBuffer.Roughness, Context.NoV, GBuffer.SpecularColor);
 | 
						||
 | 
						||
	// Add energy presevation (i.e. attenuation of the specular layer onto the diffuse component
 | 
						||
	// Lighting.Diffuse *= ComputeEnergyPreservation(EnergyTerms);
 | 
						||
 | 
						||
	Lighting.Specular = 0;
 | 
						||
	Lighting.Transmission = 0;
 | 
						||
	return Lighting;
 | 
						||
}
 | 
						||
 | 
						||
FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, FAreaLight AreaLight, FShadowTerms Shadow )
 | 
						||
{
 | 
						||
	switch( GBuffer.ShadingModelID )
 | 
						||
	{
 | 
						||
		case SHADINGMODELID_DEFAULT_LIT:
 | 
						||
		case SHADINGMODELID_SINGLELAYERWATER:
 | 
						||
		case SHADINGMODELID_THIN_TRANSLUCENT:
 | 
						||
			return DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
 | 
						||
		case SHADINGMODELID_SUBSURFACE:
 | 
						||
			return SubsurfaceBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
 | 
						||
		case SHADINGMODELID_PREINTEGRATED_SKIN:
 | 
						||
			return PreintegratedSkinBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
 | 
						||
		case SHADINGMODELID_CLEAR_COAT:
 | 
						||
			return ClearCoatBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
 | 
						||
		case SHADINGMODELID_SUBSURFACE_PROFILE:
 | 
						||
			return SubsurfaceProfileBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
 | 
						||
		case SHADINGMODELID_TWOSIDED_FOLIAGE:
 | 
						||
			return TwoSidedBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
 | 
						||
		case SHADINGMODELID_HAIR:
 | 
						||
			return HairBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
 | 
						||
		case SHADINGMODELID_CLOTH:
 | 
						||
			return ClothBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
 | 
						||
		case SHADINGMODELID_EYE:
 | 
						||
			return EyeBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
 | 
						||
		case SHADINGMODELID_TOON_BASE:
 | 
						||
			return ToonCustomBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
 | 
						||
		case SHADINGMODELID_TOON_PBR:
 | 
						||
		case SHADINGMODELID_TOON_SKIN:
 | 
						||
			return ToonLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
 | 
						||
		default:
 | 
						||
			return (FDirectLighting)0;
 | 
						||
	}
 | 
						||
}
 | 
						||
```
 | 
						||
 | 
						||
## DeferredLightPixelShaders.usf
 | 
						||
在DeferredLightPixelMain()中添加逻辑:
 | 
						||
1. 非卡通材质正常渲染。
 | 
						||
2. 材质材质只有在LightingChannel = 2时才会计算卡通光影效果。
 | 
						||
```c++
 | 
						||
bool UseToonShadow = IsToonShadingModel(ScreenSpaceData.GBuffer.ShadingModelID);
 | 
						||
// LightingChannel Toon Shading only calculate light of LightingChannel = 2
 | 
						||
BRANCH if (!UseToonShadow || (UseToonShadow && DeferredLightUniforms.LightingChannelMask & 0x4))
 | 
						||
{
 | 
						||
	const float SceneDepth = CalcSceneDepth(InputParams.ScreenUV);
 | 
						||
	const FDerivedParams DerivedParams = GetDerivedParams(InputParams, SceneDepth);
 | 
						||
 | 
						||
	FDeferredLightData LightData = InitDeferredLightFromUniforms(CURRENT_LIGHT_TYPE);
 | 
						||
	UpdateLightDataColor(LightData, InputParams, DerivedParams);
 | 
						||
 | 
						||
#if USE_HAIR_COMPLEX_TRANSMITTANCE
 | 
						||
	if (ScreenSpaceData.GBuffer.ShadingModelID == SHADINGMODELID_HAIR && ShouldUseHairComplexTransmittance(ScreenSpaceData.GBuffer))
 | 
						||
	{
 | 
						||
		LightData.HairTransmittance = EvaluateDualScattering(ScreenSpaceData.GBuffer, DerivedParams.CameraVector, -DeferredLightUniforms.Direction);
 | 
						||
	}
 | 
						||
#endif
 | 
						||
 | 
						||
	float Dither = InterleavedGradientNoise(InputParams.PixelPos, View.StateFrameIndexMod8);
 | 
						||
 | 
						||
	float SurfaceShadow = 1.0f;
 | 
						||
	
 | 
						||
	float4 LightAttenuation = GetLightAttenuationFromShadow(InputParams, SceneDepth);
 | 
						||
	float4 Radiance = GetDynamicLighting(DerivedParams.TranslatedWorldPosition, DerivedParams.CameraVector, ScreenSpaceData.GBuffer, ScreenSpaceData.AmbientOcclusion, ScreenSpaceData.GBuffer.ShadingModelID, LightData, LightAttenuation, Dither, uint2(InputParams.PixelPos), SurfaceShadow);
 | 
						||
 | 
						||
	OutColor += Radiance;
 | 
						||
}
 | 
						||
```
 | 
						||
 | 
						||
# PostProcess
 | 
						||
## ToneMapping
 | 
						||
c++部分主要修改了:
 | 
						||
1. PostProcessing.cpp
 | 
						||
2. PostProcessTonemap.cpp
 | 
						||
3. PostProcessTonemap.h
 | 
						||
 | 
						||
***实现向ToneMaper Shader传递 `TRDGUniformBufferRef<FSceneTextureUniformParameters>`的功能***
 | 
						||
 | 
						||
之后再PostProcessTonemap.usf中,对**CustomStencil**进行判断,如果为true,则直接返回之前渲染结果。实际上BufferVisualization里根本看不出来。
 | 
						||
```c++
 | 
						||
#include "DeferredShadingCommon.ush"
 | 
						||
 | 
						||
// pixel shader entry point
 | 
						||
void MainPS(
 | 
						||
	in noperspective float2 UV : TEXCOORD0,
 | 
						||
	in noperspective float2 InVignette : TEXCOORD1,
 | 
						||
	in noperspective float4 GrainUV : TEXCOORD2,
 | 
						||
	in noperspective float2 ScreenPos : TEXCOORD3,
 | 
						||
	in noperspective float2 FullViewUV : TEXCOORD4,
 | 
						||
	float4 SvPosition : SV_POSITION,		// after all interpolators
 | 
						||
	out float4 OutColor : SV_Target0
 | 
						||
#if OUTPUT_LUMINANCE
 | 
						||
	, out float OutLuminance: SV_Target1
 | 
						||
#endif
 | 
						||
	)
 | 
						||
{
 | 
						||
	float Luminance;
 | 
						||
	FGBufferData SamplerBuffer = GetGBufferData(UV * View.ResolutionFractionAndInv.x, false);
 | 
						||
	if (SamplerBuffer.CustomStencil > 1.0f && abs(SamplerBuffer.CustomDepth - SamplerBuffer.Depth) < 1)
 | 
						||
	{
 | 
						||
		OutColor = SampleSceneColor(UV);
 | 
						||
	}
 | 
						||
	else
 | 
						||
	{
 | 
						||
    	OutColor = TonemapCommonPS(UV, InVignette, GrainUV, ScreenPos, FullViewUV, SvPosition, Luminance);
 | 
						||
    }
 | 
						||
#if OUTPUT_LUMINANCE
 | 
						||
	OutLuminance = Luminance;
 | 
						||
#endif
 | 
						||
}
 | 
						||
```
 | 
						||
 | 
						||
## PostProcessCombineLUT.usf
 | 
						||
主要移植了UE4版本的LUT,以此保证效果统一。
 | 
						||
 | 
						||
# 其他
 | 
						||
## GpuSkinCacheComputeShader.usf
 | 
						||
注释2行代码,用处不明。
 | 
						||
```c++
 | 
						||
#if GPUSKIN_MORPH_BLEND  
 | 
						||
    {  
 | 
						||
       Intermediates.UnpackedPosition += Unpacked.DeltaPosition;  
 | 
						||
       // calc new normal by offseting it with the delta  
 | 
						||
       LocalTangentZ = normalize( LocalTangentZ + Unpacked.DeltaTangentZ);  
 | 
						||
       // derive the new tangent by orthonormalizing the new normal against  
 | 
						||
       // the base tangent vector (assuming these are normalized)       
 | 
						||
       LocalTangentX = normalize( LocalTangentX - (dot(LocalTangentX, LocalTangentZ) * LocalTangentZ) );  
 | 
						||
    }#else  
 | 
						||
#if GPUSKIN_APEX_CLOTH
 | 
						||
```
 | 
						||
=>
 | 
						||
```c++
 | 
						||
#if GPUSKIN_MORPH_BLEND  
 | 
						||
    {  
 | 
						||
       Intermediates.UnpackedPosition += Unpacked.DeltaPosition;  
 | 
						||
       // calc new normal by offseting it with the delta  
 | 
						||
       //LocalTangentZ = normalize( LocalTangentZ + Unpacked.DeltaTangentZ);  
 | 
						||
       // derive the new tangent by orthonormalizing the new normal against  
 | 
						||
       // the base tangent vector (assuming these are normalized)       
 | 
						||
       //LocalTangentX = normalize( LocalTangentX - (dot(LocalTangentX, LocalTangentZ) * LocalTangentZ) );  
 | 
						||
    }#else  
 | 
						||
#if GPUSKIN_APEX_CLOTH
 | 
						||
```
 |