diff --git a/Content/Materials/Layers/ML_CesiumVoxel.uasset b/Content/Materials/Layers/ML_CesiumVoxel.uasset index 9619eb7f7..41703125a 100644 Binary files a/Content/Materials/Layers/ML_CesiumVoxel.uasset and b/Content/Materials/Layers/ML_CesiumVoxel.uasset differ diff --git a/Shaders/Private/CesiumVoxelTemplate.usf b/Shaders/Private/CesiumVoxelTemplate.usf index 230c4e902..7b939a52b 100644 --- a/Shaders/Private/CesiumVoxelTemplate.usf +++ b/Shaders/Private/CesiumVoxelTemplate.usf @@ -48,18 +48,37 @@ struct VoxelMegatextures uint3 PaddingBefore; uint3 PaddingAfter; + bool UseLinearInterpolation; + int3 TileIndexToCoords(in int Index) { if (TileCount.x == 0 || TileCount.y == 0 || TileCount.z == 0) { return 0; } + int ZSlice = TileCount.x * TileCount.y; int Z = Index / ZSlice; int Y = (Index % ZSlice) / TileCount.x; int X = Index % TileCount.x; return int3(X, Y, Z) * (GridDimensions + PaddingBefore + PaddingAfter); } + + CustomShaderProperties GetPropertiesAtCoords(int3 Coords) + { + CustomShaderProperties Properties = (CustomShaderProperties) 0; + %s + + return Properties; + } + + CustomShaderProperties InterpolateProperties(CustomShaderProperties A, CustomShaderProperties B, float t) + { + CustomShaderProperties Result = (CustomShaderProperties) 0; + %s + + return Result; + } CustomShaderProperties GetProperties(in TileSample Sample) { @@ -69,6 +88,7 @@ struct VoxelMegatextures // Compute int coordinates of the voxel within the tile. float3 LocalUV = Sample.LocalUV; uint3 DataDimensions = GridDimensions + PaddingBefore + PaddingAfter; + float3 MaxVoxelCoords = float3(DataDimensions - 1u); if (ShapeConstant == BOX) { @@ -81,18 +101,46 @@ struct VoxelMegatextures // The start of the angular bounds has to be adjusted for full cylinders (root tile only). float adjustedAngle = WrapCylinderUV && Sample.Coords.w == 0 ? frac(LocalUV.y + 0.5) : LocalUV.y; LocalUV = float3(LocalUV.x, LocalUV.z, adjustedAngle); - } - - float3 VoxelCoords = floor(LocalUV * float3(GridDimensions)); - // Account for padding - VoxelCoords = clamp(VoxelCoords + float3(PaddingBefore), 0, float3(DataDimensions - 1u)); - - int3 Coords = TileCoords + VoxelCoords; + } - CustomShaderProperties Properties = (CustomShaderProperties) 0; - %s + float3 VoxelCoords = LocalUV * float3(GridDimensions); - return Properties; + if (UseLinearInterpolation) + { + // Account for padding + VoxelCoords += float3(PaddingBefore); + + float3 t = frac(VoxelCoords + 0.5); + float3 Coords = TileCoords + VoxelCoords; + int3 MaxCoords = TileCoords + MaxVoxelCoords; + + // Linear interpolation must be confined within a tile to avoid sampling data that is not physically adjacent. + // That means we must manually interpolate instead of relying on a linear texture filter. + CustomShaderProperties x0y0z0 = GetPropertiesAtCoords(clamp(floor(Coords + float3(-0.5, -0.5, -0.5)), TileCoords, MaxCoords)); + CustomShaderProperties x1y0z0 = GetPropertiesAtCoords(clamp(floor(Coords + float3 (0.5, -0.5, -0.5)), TileCoords, MaxCoords)); + CustomShaderProperties x0y1z0 = GetPropertiesAtCoords(clamp(floor(Coords + float3(-0.5, 0.5, -0.5)), TileCoords, MaxCoords)); + CustomShaderProperties x1y1z0 = GetPropertiesAtCoords(clamp(floor(Coords + float3( 0.5, 0.5, -0.5)), TileCoords, MaxCoords)); + CustomShaderProperties x0y0z1 = GetPropertiesAtCoords(clamp(floor(Coords + float3(-0.5, -0.5, 0.5)), TileCoords, MaxCoords)); + CustomShaderProperties x1y0z1 = GetPropertiesAtCoords(clamp(floor(Coords + float3( 0.5, -0.5, 0.5)), TileCoords, MaxCoords)); + CustomShaderProperties x0y1z1 = GetPropertiesAtCoords(clamp(floor(Coords + float3(-0.5, 0.5, 0.5)), TileCoords, MaxCoords)); + CustomShaderProperties x1y1z1 = GetPropertiesAtCoords(clamp(floor(Coords + float3( 0.5, 0.5, 0.5)), TileCoords, MaxCoords)); + + CustomShaderProperties y0z0 = InterpolateProperties(x0y0z0, x1y0z0, t.x); + CustomShaderProperties y1z0 = InterpolateProperties(x0y1z0, x1y1z0, t.x); + CustomShaderProperties y0z1 = InterpolateProperties(x0y0z1, x1y0z1, t.x); + CustomShaderProperties y1z1 = InterpolateProperties(x0y1z1, x1y1z1, t.x); + + CustomShaderProperties z0 = InterpolateProperties(y0z0, y1z0, t.y); + CustomShaderProperties z1 = InterpolateProperties(y0z1, y1z1, t.y); + + return InterpolateProperties(z0, z1, t.z); + } + else + { + // Account for padding + VoxelCoords = clamp(floor(VoxelCoords) + float3(PaddingBefore), 0, MaxVoxelCoords); + return GetPropertiesAtCoords(TileCoords + VoxelCoords); + } } }; @@ -167,6 +215,7 @@ VoxelMegatextures DataTextures; DataTextures.ShapeConstant = ShapeConstant; DataTextures.TileCount = TileCount; DataTextures.WrapCylinderUV = (ShapeConstant == CYLINDER) ? (Octree.GridShape.CylinderShape.AngleRangeFlag == 0) : false; +DataTextures.UseLinearInterpolation = bool(UseLinearInterpolation); // Account for y-up -> z-up conventions for certain shapes. switch (ShapeConstant) { diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 6a413d419..6753a0b10 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -569,6 +569,17 @@ void ACesium3DTileset::SetPointCloudShading( } } +void ACesium3DTileset::SetVoxelRenderingOptions( + FCesiumVoxelRenderingOptions InVoxelRenderingOptions) { + if (VoxelRenderingOptions != InVoxelRenderingOptions) { + VoxelRenderingOptions = InVoxelRenderingOptions; + if (this->_pVoxelRendererComponent) { + this->_pVoxelRendererComponent->SetVoxelRenderingOptions( + InVoxelRenderingOptions); + } + } +} + void ACesium3DTileset::SetRuntimeVirtualTextures( TArray InRuntimeVirtualTextures) { if (this->RuntimeVirtualTextures != InRuntimeVirtualTextures) { @@ -2299,6 +2310,13 @@ void ACesium3DTileset::PostEditChangeChainProperty( if (PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, PointCloudShading)) { FCesiumGltfPointsSceneProxyUpdater::UpdateSettingsInProxies(this); + } else if ( + PropName == + GET_MEMBER_NAME_CHECKED(ACesium3DTileset, VoxelRenderingOptions)) { + if (this->_pVoxelRendererComponent) { + this->_pVoxelRendererComponent->SetVoxelRenderingOptions( + VoxelRenderingOptions); + } } } @@ -2411,5 +2429,8 @@ void ACesium3DTileset::createVoxelRenderer( Warning, TEXT("Voxel renderer could not be attached to root")); } + + this->_pVoxelRendererComponent->SetVoxelRenderingOptions( + this->VoxelRenderingOptions); } } diff --git a/Source/CesiumRuntime/Private/CesiumVoxelMetadataComponent.cpp b/Source/CesiumRuntime/Private/CesiumVoxelMetadataComponent.cpp index a91b9b350..f38187489 100644 --- a/Source/CesiumRuntime/Private/CesiumVoxelMetadataComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumVoxelMetadataComponent.cpp @@ -256,6 +256,7 @@ struct MaterialResourceLibrary { struct CustomShaderBuilder { FString DeclareShaderProperties; FString SamplePropertiesFromTexture; + FString InterpolateProperties; FString DeclareDataTextureVariables; FString SetDataTextures; @@ -429,6 +430,44 @@ struct CustomShaderBuilder { } } + /** + * Adds code for linearly interpolating the property in the corresponding + * shader function. + */ + void AddPropertyInterpolation( + const FString& PropertyName, + const FCesiumPropertyAttributePropertyDescription& Property) { + if (!InterpolateProperties.IsEmpty()) { + InterpolateProperties += "\n\t\t"; + } + + // Example: Result.Property = lerp(A.Property, B.Property, t); + FString lerp = "Result." + PropertyName + " = lerp(A." + PropertyName + + ", B." + PropertyName + ", t);\n"; + + // Any "noData" values should be omitted from inteprolation. Otherwise, they + // may result in nonsensical data. + if (Property.PropertyDetails.bHasNoDataValue) { + FString NoDataName = PropertyName + MaterialPropertyNoDataSuffix; + // Example: if {A.Property == Property_NODATA) { + // Result.Property = B.Property; + // } + // else if (B.Property == Property_NODATA) { + // Result.Property = A.Property; + // } else { + // Result.Property = lerp(A.Property, B.Property, t); + // } + InterpolateProperties += + "if (A." + PropertyName + " == " + NoDataName + ") {\n\t\t" + + "Result." + PropertyName + " = B." + PropertyName + ";\n}\n\t" + + "else if (B." + PropertyName + " == " + NoDataName + ") {\n\t\t" + + "Result." + PropertyName + " = A." + PropertyName + ";\n}\n\t" + + "else {\n\t\t" + lerp + "}\n"; + } else { + InterpolateProperties += lerp; + } + } + /** * Comprehensively adds the declaration for properties and data textures, as * well as the code to correctly retrieve the property values from the data @@ -441,6 +480,7 @@ struct CustomShaderBuilder { AddPropertyDeclaration(PropertyName, Property); AddDataTexture(PropertyName, TextureParameterName); AddPropertyRetrieval(PropertyName, Property); + AddPropertyInterpolation(PropertyName, Property); } }; } // namespace @@ -720,12 +760,12 @@ static void GenerateMaterialNodes( continue; } - auto* VectorParameterNode = - Cast(NewExpression); - if (VectorParameterNode && - VectorParameterNode->ParameterName.ToString() == "Tile Count") { - DataSectionX = VectorParameterNode->MaterialExpressionEditorX; - DataSectionY = VectorParameterNode->MaterialExpressionEditorY; + auto* ScalarParameterNode = + Cast(NewExpression); + if (ScalarParameterNode && ScalarParameterNode->ParameterName.ToString() == + "Use Linear Interpolation") { + DataSectionX = ScalarParameterNode->MaterialExpressionEditorX; + DataSectionY = ScalarParameterNode->MaterialExpressionEditorY; } } @@ -831,6 +871,7 @@ static void GenerateMaterialNodes( LazyPrintf.PushParam(*Component->CustomShader); LazyPrintf.PushParam(*Builder.DeclareDataTextureVariables); LazyPrintf.PushParam(*Builder.SamplePropertiesFromTexture); + LazyPrintf.PushParam(*Builder.InterpolateProperties); LazyPrintf.PushParam(*Builder.SetDataTextures); RaymarchNode->Code = LazyPrintf.GetResultString(); diff --git a/Source/CesiumRuntime/Private/CesiumVoxelRendererComponent.cpp b/Source/CesiumRuntime/Private/CesiumVoxelRendererComponent.cpp index f04bbf9c2..c548f5828 100644 --- a/Source/CesiumRuntime/Private/CesiumVoxelRendererComponent.cpp +++ b/Source/CesiumRuntime/Private/CesiumVoxelRendererComponent.cpp @@ -13,6 +13,7 @@ #include "Engine/Texture.h" #include "Materials/MaterialInstanceDynamic.h" #include "PhysicsEngine/BodySetup.h" +#include "SceneInterface.h" #include "UObject/ConstructorHelpers.h" #include "VecMath.h" @@ -1051,6 +1052,25 @@ UCesiumVoxelRendererComponent::CreateVoxelMaterial( return pVoxelComponent; } +void UCesiumVoxelRendererComponent::SetVoxelRenderingOptions( + const FCesiumVoxelRenderingOptions& Options) { + UMaterialInstanceDynamic* pMaterial = nullptr; + if (this->MeshComponent) { + UMaterialInterface* pMaterialInterface = + this->MeshComponent->GetMaterial(0); + pMaterial = Cast(pMaterialInterface); + } + + if (pMaterial) { + pMaterial->SetScalarParameterValueByInfo( + FMaterialParameterInfo( + UTF8_TO_TCHAR("Use Linear Interpolation"), + EMaterialParameterAssociation::LayerParameter, + 0), + float(Options.EnableLinearInterpolation)); + } +} + namespace { template void forEachRenderableVoxelTile(const auto& tiles, Func&& f) { @@ -1231,8 +1251,8 @@ void UCesiumVoxelRendererComponent::UpdateTiles( namespace { /** - * Updates the input voxel material to account for origin shifting or ellipsoid - * changes from the tileset's georeference. + * Updates the input voxel material to account for origin shifting or + * ellipsoid changes from the tileset's georeference. */ void updateEllipsoidVoxelParameters( UMaterialInstanceDynamic* pMaterial, diff --git a/Source/CesiumRuntime/Private/CesiumVoxelRendererComponent.h b/Source/CesiumRuntime/Private/CesiumVoxelRendererComponent.h index 02e2d5ebf..f702790e1 100644 --- a/Source/CesiumRuntime/Private/CesiumVoxelRendererComponent.h +++ b/Source/CesiumRuntime/Private/CesiumVoxelRendererComponent.h @@ -2,6 +2,7 @@ #pragma once +#include "CesiumVoxelRenderingOptions.h" #include "Components/SceneComponent.h" #include "Components/StaticMeshComponent.h" #include "CoreMinimal.h" @@ -90,6 +91,8 @@ class UCesiumVoxelRendererComponent : public USceneComponent { const std::vector& VisibleTiles, const std::vector& VisibleTileScreenSpaceErrors); + void SetVoxelRenderingOptions(const FCesiumVoxelRenderingOptions& Options); + private: static UMaterialInstanceDynamic* CreateVoxelMaterial( UCesiumVoxelRendererComponent* pVoxelComponent, diff --git a/Source/CesiumRuntime/Public/Cesium3DTileset.h b/Source/CesiumRuntime/Public/Cesium3DTileset.h index 1dce711eb..76c35840b 100644 --- a/Source/CesiumRuntime/Public/Cesium3DTileset.h +++ b/Source/CesiumRuntime/Public/Cesium3DTileset.h @@ -14,18 +14,21 @@ #include "CesiumPointCloudShading.h" #include "CesiumSampleHeightResult.h" #include "CesiumVoxelMetadataComponent.h" +#include "CesiumVoxelRenderingOptions.h" #include "CoreMinimal.h" #include "CustomDepthParameters.h" #include "Engine/EngineTypes.h" #include "GameFramework/Actor.h" #include "Interfaces/IHttpRequest.h" #include "PrimitiveSceneProxy.h" + #include #include #include #include #include #include + #include "Cesium3DTileset.generated.h" #ifdef CESIUM_DEBUG_TILE_STATES @@ -956,6 +959,17 @@ class CESIUMRUNTIME_API ACesium3DTileset : public AActor { Category = "Cesium|Rendering") FCesiumPointCloudShading PointCloudShading; + /** + * If this tileset contains voxels, their appearance can be configured with + * these voxel rendering parameters. + */ + UPROPERTY( + EditAnywhere, + BlueprintGetter = GetVoxelRenderingOptions, + BlueprintSetter = SetVoxelRenderingOptions, + Category = "Cesium|Rendering") + FCesiumVoxelRenderingOptions VoxelRenderingOptions; + /** * Array of runtime virtual textures into which we draw the mesh for this * actor. The material also needs to be set up to output to a virtual texture. @@ -1164,6 +1178,15 @@ class CESIUMRUNTIME_API ACesium3DTileset : public AActor { UFUNCTION(BlueprintSetter, Category = "Cesium|Rendering") void SetPointCloudShading(FCesiumPointCloudShading InPointCloudShading); + UFUNCTION(BlueprintGetter, Category = "Cesium|Rendering") + FCesiumVoxelRenderingOptions GetVoxelRenderingOptions() const { + return VoxelRenderingOptions; + } + + UFUNCTION(BlueprintSetter, Category = "Cesium|Rendering") + void SetVoxelRenderingOptions( + FCesiumVoxelRenderingOptions InVoxelRenderingOptions); + UFUNCTION(BlueprintCallable, Category = "Cesium|Rendering") void PlayMovieSequencer(); diff --git a/Source/CesiumRuntime/Public/CesiumVoxelRenderingOptions.h b/Source/CesiumRuntime/Public/CesiumVoxelRenderingOptions.h new file mode 100644 index 000000000..486b7888b --- /dev/null +++ b/Source/CesiumRuntime/Public/CesiumVoxelRenderingOptions.h @@ -0,0 +1,34 @@ +// Copyright 2020-2024 CesiumGS, Inc. and Contributors + +#pragma once + +#include "CoreMinimal.h" + +#include "CesiumVoxelRenderingOptions.generated.h" + +/** + * Options for adjusting how voxels are rendered using 3D Tiles. + */ +USTRUCT(BlueprintType) +struct CESIUMRUNTIME_API FCesiumVoxelRenderingOptions { + GENERATED_USTRUCT_BODY() + + /** + * Whether to enable linear interpolation when rendering voxels. This can + * result in a smoother appearance across a large voxel tileset. If false, + * nearest sampling will be used. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cesium") + bool EnableLinearInterpolation = false; + + bool operator==( + const FCesiumVoxelRenderingOptions& OtherVoxelRenderingOptions) const { + return EnableLinearInterpolation == + OtherVoxelRenderingOptions.EnableLinearInterpolation; + } + + bool operator!=( + const FCesiumVoxelRenderingOptions& OtherVoxelRenderingOptions) const { + return !(*this == OtherVoxelRenderingOptions); + } +};