반응형

이제 Cell Shader 코드를 적용해야한다.

 

Shading Model을 추가하던 시점으로 돌아가, Cell Shading Buffer 값을 적용해주자.

// ShadingModelsMaterial.ush

~~ ... ~~

#if MATERIAL_SHADINGMODEL_DR_CELL_SHADING
	else if (ShadingModel == SHADINGMODELID_DR_CELL_SHADING)
	{
		GBuffer.CellShading.a = saturate(GetCellShading(MaterialParameters));
	}
#endif

~~ ... ~~

 

Model 조건 확인과 GBuffer 사용 여부에 해당 Model을 추가해준다.

추가해주는 Model Type은 ClusteredDeferredShadingPixelShader.usf 에 추가한 Model Type이다.

추가로 Cell Shading인 경우에만 사용하기 위한 IsCellShading 함수도 함께 추가한다.

// DeferredShadingCommon.ush

~~ ... ~~

bool IsSubsurfaceModel(int ShadingModel)
{
	return ShadingModel == SHADINGMODELID_SUBSURFACE 
		|| ShadingModel == SHADINGMODELID_PREINTEGRATED_SKIN 
		|| ShadingModel == SHADINGMODELID_SUBSURFACE_PROFILE
		|| ShadingModel == SHADINGMODELID_TWOSIDED_FOLIAGE
		|| ShadingModel == SHADINGMODELID_HAIR
		|| ShadingModel == SHADINGMODELID_EYE
		|| ShadingModel == SHADINGMODELID_DR_CELL_SHADING;
}

~~ ... ~~

bool HasCustomGBufferData(int ShadingModelID)
{
	return ShadingModelID == SHADINGMODELID_SUBSURFACE
		|| ShadingModelID == SHADINGMODELID_PREINTEGRATED_SKIN
		|| ShadingModelID == SHADINGMODELID_CLEAR_COAT
		|| ShadingModelID == SHADINGMODELID_SUBSURFACE_PROFILE
		|| ShadingModelID == SHADINGMODELID_TWOSIDED_FOLIAGE
		|| ShadingModelID == SHADINGMODELID_HAIR
		|| ShadingModelID == SHADINGMODELID_CLOTH
		|| ShadingModelID == SHADINGMODELID_EYE
		|| ShadingModelID == SHADINGMODELID_DR_CELL_SHADING;
}

bool IsCelShading(int ShadingModel)
{
	return ShadingModel == SHADINGMODELID_DR_CELL_SHADING;
}

~~ ... ~~

Deferred Shader Light에 대한 이해 부분은, 튜토리얼을 참고하면 설명이 되어있으므로, 설명은 넘기겠다.

 

이제 Cell Shading 적용이 되기 위한 Light 코드를 작성한다.

// ShadingModels.ush

~~ ... ~~

FDirectLighting CellShadingLitBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, FAreaLight AreaLight, FShadowTerms Shadow, float CurveIndex )
{
	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
	float2 DiffuseCoord = float2(NoL, CurveIndex);
	Lighting.Diffuse *= AreaLight.FalloffColor * (Falloff * Texture2DSampleLevel(View.CellShadingAtlas, View.CellShadingSampler, DiffuseCoord, 0).r);

	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
		{
			Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * SpecularGGX(GBuffer.Roughness, GBuffer.SpecularColor, Context, NoL, AreaLight);
		}
	}

	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 IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
	switch( GBuffer.ShadingModelID )
	{
		~~ ... ~~
        
		case SHADINGMODELID_EYE:
			return EyeBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
		case SHADINGMODELID_DR_CELL_SHADING:
			return CellShadingLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow, GBuffer.CellShading.a );
		default:
			return (FDirectLighting)0;
	}
}

~~ ... ~~

 

CellShadingLitLitBxDF 함수는 DefaultLitBxDF 함수를 기반으로 수정을 하면 된다.

수정된 부분은 함수 파라미터가 추가되었고, Lighting.Diffuse에 적용되는 값이 수정되었다.

 

반사 색상에 변환을 추가한다.

// ShadingModels.ush

~~ ... ~~

float F_Schlick_ScaleTerm(float3 SpecularColor, float VoH)
{
	float Fc = Pow5(1 - VoH);
	return saturate(50.0 * SpecularColor.g) * Fc + (1 - Fc);
}

float SpecularGGX_ScaleTerm(float Roughness, float3 SpecularColor, BxDFContext Context, float NoL, FAreaLight AreaLight)
{
	float a2 = Pow4(Roughness);
	float Energy = EnergyNormalization(a2, Context.VoH, AreaLight);
	float D = D_GGX(a2, Context.NoH) * Energy;
	float Vis = Vis_SmithJointApprox(a2, Context.NoV, NoL);

	float FScale = F_Schlick_ScaleTerm(SpecularColor, Context.VoH);

	return D * Vis * FScale;
}

~~ ... ~~

FDirectLighting CellShadingLitBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, FAreaLight AreaLight, FShadowTerms Shadow, float CurveIndex )
{
	~~ ... ~~
    
	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
		{
			float2 SpecularCoord = float2(SpecularGGX_ScaleTerm(GBuffer.Roughness, GBuffer.SpecularColor, Context, NoL, AreaLight) * NoL, CurveIndex);
			Lighting.Specular = AreaLight.FalloffColor * (Falloff * Texture2DSampleLevel(View.CellShadingAtlas, View.CellShadingSampler, SpecularCoord, 0).g);
		}
	}

	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;
}

~~ ... ~~

Lighting.Specular에 적용되는 값을 수정해주었다.

 

Cell Shading에 대한 추가는 완료되었다.

 

아래에 해당하는 부분은 Cell Shading에 맞는 그림자를 보여주기 위한 과정이므로 필요하지 않다면 스킵해도 된다.

더보기
다른 물체에 대한 그림자를 설정하기 위해, Surface Shadow 값을 설정해준다.
// DeferredLightingCommon.ush

~~ ... ~~
FLightAccumulator AccumulateDynamicLighting(
	float3 TranslatedWorldPosition, half3 CameraVector, FGBufferData GBuffer, half AmbientOcclusion, uint ShadingModelID,
	FDeferredLightData LightData, half4 LightAttenuation, float Dither, uint2 SVPos, 
	inout float SurfaceShadow)
{
	~~ ... ~~

	float SurfaceShadowMultiplier = LightMask * SurfaceShadow;

	if ( IsCellShading(GBuffer.ShadingModelID) )
	{
		SurfaceShadowMultiplier *= saturate( 0.5 * floor( SurfaceShadowMultiplier / 0.3333 ) );
	}

	LightAccumulator.EstimatedCost += 0.3f;		// running the PixelShader at all has a cost

	~~ ... ~~
    
    LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, MaskedLightColor * SurfaceShadowMultiplier, bNeedsSeparateSubsurfaceLightAccumulation );
	LightAccumulator_AddSplit( LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, MaskedLightColor * Shadow.TransmissionShadow, bNeedsSeparateSubsurfaceLightAccumulation );
        
    ~~ ... ~~
}

~~ ... ~~

기존 Surface Shadow 값을 대체하여 적용해준다.

 

반응형

+ Recent posts