eidosmontreal / unreal-vdb

This repo is a non-official Unreal plugin that can read OpenVDB and NanoVDB files in Unreal.
Apache License 2.0
690 stars 109 forks source link

BP_VdbToVolumetricClouds - incorrect vdb volume sample calculating volumetric advanced output #61

Closed Andrej730 closed 1 year ago

Andrej730 commented 1 year ago

My idea was to create custom shading for each of the cloud from BP_VdbToVolumetricClouds (I need to use multiple cloud based VDBs in one scene). I've manged to achieve that for cloud color with shader below (first cloud is red, 2nd is blue etc).

image

I tried to use the same approach for MultiScatteringOcclusion in volumetric advanced output. image

But it doesn't work - red cloud is suppose to be bright (and it is) but blue cloud is suppose to be very dark because of occlusion = 1.

https://user-images.githubusercontent.com/9417531/215183754-909cb18b-519e-4c9d-b83d-a97641f10194.mp4

Further investigation led me to the conclusion that the problem is that calculating sampled vdb value during MF_Vdb_Volume_Sample is incorrect.

Simplest way to see it is to connect first volume density with ceil to occlusion. image If volume values were sampled correctly then you would expect first red cloud to be dark (density = 1 => occlusion =1 => dark cloud) and blue cloud to be bright (density = 0 => occlusion = 0 => bright cloud). But both clouds are bright, meaning occlusion = 0 => density sampled as 0 everywhere which is incorrect.

image

Maybe something goes wrong during calculating occlusion in hlsl (which is VolumetricAdvancedMaterialOutput4) but I'm not that familar with how MF_Vdb_Volume_Sample works to solve that.

#define HAVE_GetVolumetricAdvancedMaterialOutput4 1
MaterialFloat GetVolumetricAdvancedMaterialOutput4(inout FMaterialPixelParameters Parameters)
{
    FLWCVector3 Local79 = GetWorldPosition(Parameters);
    FLWCScalar Local80 = LWCGetX(DERIV_BASE_VALUE(Local79));
    FLWCVector3 Local81 = LWCMultiply(MakeLWCVector3(Material.PreshaderBuffer[2].xyz,float3(Material.PreshaderBuffer[2].w, Material.PreshaderBuffer[3].xy)), MakeLWCVector(LWCGetComponent(DERIV_BASE_VALUE(Local80), 0),LWCGetComponent(DERIV_BASE_VALUE(Local80), 0),LWCGetComponent(DERIV_BASE_VALUE(Local80), 0)));
    FLWCScalar Local82 = LWCGetY(DERIV_BASE_VALUE(Local79));
    FLWCVector3 Local83 = LWCMultiply(MakeLWCVector3(float3(Material.PreshaderBuffer[5].zw, Material.PreshaderBuffer[6].x),Material.PreshaderBuffer[6].yzw), MakeLWCVector(LWCGetComponent(DERIV_BASE_VALUE(Local82), 0),LWCGetComponent(DERIV_BASE_VALUE(Local82), 0),LWCGetComponent(DERIV_BASE_VALUE(Local82), 0)));
    FLWCVector3 Local84 = LWCAdd(DERIV_BASE_VALUE(Local81), DERIV_BASE_VALUE(Local83));
    FLWCScalar Local85 = LWCGetZ(DERIV_BASE_VALUE(Local79));
    FLWCVector3 Local86 = LWCMultiply(MakeLWCVector3(Material.PreshaderBuffer[9].xyz,float3(Material.PreshaderBuffer[9].w, Material.PreshaderBuffer[10].xy)), MakeLWCVector(LWCGetComponent(DERIV_BASE_VALUE(Local85), 0),LWCGetComponent(DERIV_BASE_VALUE(Local85), 0),LWCGetComponent(DERIV_BASE_VALUE(Local85), 0)));
    FLWCVector3 Local87 = LWCAdd(DERIV_BASE_VALUE(Local86), MakeLWCVector3(float3(Material.PreshaderBuffer[12].zw, Material.PreshaderBuffer[13].x),Material.PreshaderBuffer[13].yzw));
    FLWCVector3 Local88 = LWCAdd(DERIV_BASE_VALUE(Local84), DERIV_BASE_VALUE(Local87));
    FLWCVector3 Local89 = LWCMultiply(DERIV_BASE_VALUE(Local88), LWCPromote(((MaterialFloat3)Material.PreshaderBuffer[14].z)));
    FLWCVector3 Local90 = LWCSubtract(DERIV_BASE_VALUE(Local89), MakeLWCVector3(float3(Material.PreshaderBuffer[16].w, Material.PreshaderBuffer[17].xy),float3(Material.PreshaderBuffer[17].zw, Material.PreshaderBuffer[18].x)));
    MaterialFloat3 Local91 = CustomExpression6(Parameters,LWCToFloat(MakeLWCVector3(Material.PreshaderBuffer[20].yzw,Material.PreshaderBuffer[21].xyz)),MakeLWCVector3(Material.PreshaderBuffer[20].yzw,Material.PreshaderBuffer[21].xyz),Material.VolumeTexture_0,Material.VolumeTexture_0Sampler);
    FLWCVector3 Local92 = LWCDivide(DERIV_BASE_VALUE(Local90), Local91);
    MaterialFloat3 Local93 = LWCSaturate(Local92);
    MaterialFloat Local94 = CustomExpression7(Parameters,Local93);
    FLWCVector3 Local95 = LWCMultiply(Local92, MakeLWCVector3(float3(Material.PreshaderBuffer[23].w, Material.PreshaderBuffer[24].xy),float3(Material.PreshaderBuffer[24].zw, Material.PreshaderBuffer[25].x)));
    MaterialFloat3 Local96 = LWCSaturate(Local95);
    MaterialFloat Local97 = MaterialStoreTexCoordScale(Parameters, Local96, 0);
    MaterialFloat4 Local98 = ProcessMaterialColorTextureLookup(Texture3DSample(Material.VolumeTexture_0,Material.VolumeTexture_0Sampler,Local96));
    MaterialFloat Local99 = MaterialStoreTexSample(Parameters, Local98, 0);
    MaterialFloat Local100 = saturate(Local98.r);
    MaterialFloat Local101 = (Local94 * Local100);
    MaterialFloat Local102 = (Local101 * 322.00000000);
    MaterialFloat Local103 = ceil(Local102);
    MaterialFloat Local104 = (Local103 * 666666.00000000);
 return Local104;
}
FLWCScalar GetVolumetricAdvancedMaterialOutput4_LWC(inout FMaterialPixelParameters Parameters) { return LWCPromote(GetVolumetricAdvancedMaterialOutput4(Parameters)); }

Any way to fix that to get correct that density here?

Here's the map if need to test it - incorrect density sampling for VAO.zip

PS Red cloud without occlusion kind of looks like explosion, maybe it's possible to sample temperature vdb values and map them to occlusion to create temperature shading in volumetric clouds.

Andrej730 commented 1 year ago

I think I've debugged it a bit more and I can conclude that for some reason absolute world position when it's calculated for volumetric advanced output is replaced with just camera position. Any ideas why it could work like that, possible volumetric cloud bug?

Here's how I tested it. I came up with simple shader that would allow me to visually figure each coord one by one. image

And turned out the coords I've found are camera position coords. image

thilamb commented 1 year ago

Hello, like you said, this looks more like a volumetric cloud issue.

_MF_Vdb_VolumeSample works for density so I see no reason why it wouldn't work for other material outputs.

Andrej730 commented 1 year ago

Can you please take a look at shader code that calculates world position for pins in volumetric advanced output? Is there anything odd?

thilamb commented 1 year ago

I don't see anything obvious. I recommend asking the same questions to Epic, they are usually quick to answer and if there is an issue they'll find it and fix it.

Andrej730 commented 1 year ago

I've learned a bit more about the issue - some pins like PhaseG evaluated per sample (not sure what it really means) and they work correctly. But MultiScatteringOcclusion works per pixel and it results in errors.

image

image

Argument in favor of that is if you disable per sample evaluation for PhaseG in Volumetric Advanced Output properties - it will result. image

Results per sample (left cloud phase g = 0, right cloud phase g = 1): image

Result per pixel (same phase g settings): image

Not sure if it's possible to somehow switch MultiScatteringOcclusion to be evaluated per sample too.

thilamb commented 1 year ago

Good point. But MultiScatteringOcclusion cannot be evaluated per sample, maybe you should try another approach.

Andrej730 commented 1 year ago

But MultiScatteringOcclusion cannot be evaluated per sample, maybe you should try another approach.

You mean it's not possible in current unreal engine or it wouldn't be possible even changes were made (like creating custom volumetric clouds from some plugin or changing some code in the engine)?

thilamb commented 1 year ago

You would have to change Engine\Shaders\Private\VolumetricCloud.usf yourself. You can anything of course but I'm guessing modifying this MultiScatteringOcclusion approximation is meant to be per-pixel rather than per sample. Again, I'm not really qualified to comment on this, but you can reach out on Discord if you want to keep discussing about it privately, as this is not a plugin bug.