add phone screen material and fix asset cooking

This commit is contained in:
2026-01-17 15:31:54 +01:00
parent b349b86108
commit 554a946e60
7 changed files with 81 additions and 509 deletions

View File

@@ -2,3 +2,6 @@
ProjectID=DD810CA045CA9288C9C53E8638A45978
bStartInVR=True
[/Script/UnrealEd.ProjectPackagingSettings]
+DirectoriesToAlwaysCook=(Path="/Game/Mosis")

Binary file not shown.

View File

@@ -89,7 +89,6 @@ Plugins/MosisSDK/
│ ├── MosisClient.h/cpp # AIDL client implementation
│ ├── MosisPhoneTexture.h/cpp # UTexture for phone screen
│ ├── MosisPhoneTextureResource.h/cpp # Render thread resource
│ ├── MosisVulkanTexture.h/cpp # Legacy Vulkan import (unused)
│ ├── MosisPhoneComponent.cpp
│ ├── MosisPhoneActor.cpp
│ └── Android/

View File

@@ -25,6 +25,14 @@ AMosisPhoneActor::AMosisPhoneActor()
PhoneMesh->SetStaticMesh(PlaneMeshFinder.Object);
}
// Load the phone screen material
static ConstructorHelpers::FObjectFinder<UMaterialInterface> PhoneMaterialFinder(
TEXT("/Game/Mosis/Materials/M_PhoneScreen.M_PhoneScreen"));
if (PhoneMaterialFinder.Succeeded())
{
BaseMaterial = PhoneMaterialFinder.Object;
}
// Create phone component
PhoneComponent = CreateDefaultSubobject<UMosisPhoneComponent>(TEXT("PhoneComponent"));
}

View File

@@ -1,421 +0,0 @@
#include "MosisVulkanTexture.h"
#if PLATFORM_ANDROID
#include "Logging/LogMacros.h"
DEFINE_LOG_CATEGORY_STATIC(LogMosisVulkan, Log, All);
MosisVulkanTexture::~MosisVulkanTexture()
{
Destroy();
}
bool MosisVulkanTexture::Initialize(VkInstance instance, VkPhysicalDevice physDevice, VkDevice device, uint32_t queueFamilyIndex)
{
if (m_Initialized)
{
return true;
}
m_Instance = instance;
m_PhysicalDevice = physDevice;
m_Device = device;
m_QueueFamilyIndex = queueFamilyIndex;
// Load extension function
vkGetAndroidHardwareBufferPropertiesANDROID =
reinterpret_cast<PFN_vkGetAndroidHardwareBufferPropertiesANDROID>(
vkGetInstanceProcAddr(m_Instance, "vkGetAndroidHardwareBufferPropertiesANDROID")
);
if (!vkGetAndroidHardwareBufferPropertiesANDROID)
{
UE_LOG(LogMosisVulkan, Error, TEXT("Initialize: vkGetAndroidHardwareBufferPropertiesANDROID not available"));
return false;
}
m_Initialized = true;
UE_LOG(LogMosisVulkan, Log, TEXT("Initialize: Success"));
return true;
}
bool MosisVulkanTexture::Create(AHardwareBuffer* buffer)
{
if (!m_Initialized)
{
UE_LOG(LogMosisVulkan, Error, TEXT("Create: Not initialized"));
return false;
}
if (m_Created)
{
Destroy();
}
// Get buffer dimensions
AHardwareBuffer_Desc desc{};
AHardwareBuffer_describe(buffer, &desc);
m_Width = desc.width;
m_Height = desc.height;
UE_LOG(LogMosisVulkan, Log, TEXT("Create: %dx%d texture"), m_Width, m_Height);
if (!ImportHardwareBuffer(buffer))
{
UE_LOG(LogMosisVulkan, Error, TEXT("Create: Failed to import hardware buffer"));
return false;
}
if (!CreateLocalImage())
{
UE_LOG(LogMosisVulkan, Error, TEXT("Create: Failed to create local image"));
DestroyImportedResources();
return false;
}
m_Created = true;
UE_LOG(LogMosisVulkan, Log, TEXT("Create: Success"));
return true;
}
bool MosisVulkanTexture::ImportHardwareBuffer(AHardwareBuffer* buffer)
{
// Query hardware buffer properties
VkAndroidHardwareBufferFormatPropertiesANDROID formatProps{};
formatProps.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID;
VkAndroidHardwareBufferPropertiesANDROID props{};
props.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID;
props.pNext = &formatProps;
if (vkGetAndroidHardwareBufferPropertiesANDROID(m_Device, buffer, &props) != VK_SUCCESS)
{
UE_LOG(LogMosisVulkan, Error, TEXT("ImportHardwareBuffer: Failed to get properties"));
return false;
}
m_Format = formatProps.format;
UE_LOG(LogMosisVulkan, Log, TEXT("ImportHardwareBuffer: Format=%d"), static_cast<int>(m_Format));
// Create VkImage with external memory
VkExternalMemoryImageCreateInfo extMemImageInfo{};
extMemImageInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
extMemImageInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID;
VkImageCreateInfo imageInfo{};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.pNext = &extMemImageInfo;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.format = m_Format;
imageInfo.extent = {m_Width, m_Height, 1};
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
if (vkCreateImage(m_Device, &imageInfo, nullptr, &m_ImportedImage) != VK_SUCCESS)
{
UE_LOG(LogMosisVulkan, Error, TEXT("ImportHardwareBuffer: Failed to create image"));
return false;
}
// Import memory from hardware buffer
VkImportAndroidHardwareBufferInfoANDROID importInfo{};
importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID;
importInfo.buffer = buffer;
VkMemoryDedicatedAllocateInfo dedicatedInfo{};
dedicatedInfo.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
dedicatedInfo.pNext = &importInfo;
dedicatedInfo.image = m_ImportedImage;
VkMemoryAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.pNext = &dedicatedInfo;
allocInfo.allocationSize = props.allocationSize;
allocInfo.memoryTypeIndex = FindMemoryType(props.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
if (vkAllocateMemory(m_Device, &allocInfo, nullptr, &m_ImportedMemory) != VK_SUCCESS)
{
UE_LOG(LogMosisVulkan, Error, TEXT("ImportHardwareBuffer: Failed to allocate memory"));
vkDestroyImage(m_Device, m_ImportedImage, nullptr);
m_ImportedImage = VK_NULL_HANDLE;
return false;
}
if (vkBindImageMemory(m_Device, m_ImportedImage, m_ImportedMemory, 0) != VK_SUCCESS)
{
UE_LOG(LogMosisVulkan, Error, TEXT("ImportHardwareBuffer: Failed to bind memory"));
DestroyImportedResources();
return false;
}
UE_LOG(LogMosisVulkan, Log, TEXT("ImportHardwareBuffer: Success"));
return true;
}
bool MosisVulkanTexture::CreateLocalImage()
{
// Create local VkImage
VkImageCreateInfo imageInfo{};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.format = m_Format;
imageInfo.extent = {m_Width, m_Height, 1};
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
if (vkCreateImage(m_Device, &imageInfo, nullptr, &m_LocalImage) != VK_SUCCESS)
{
UE_LOG(LogMosisVulkan, Error, TEXT("CreateLocalImage: Failed to create image"));
return false;
}
// Allocate memory
VkMemoryRequirements memReqs;
vkGetImageMemoryRequirements(m_Device, m_LocalImage, &memReqs);
VkMemoryAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memReqs.size;
allocInfo.memoryTypeIndex = FindMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
if (vkAllocateMemory(m_Device, &allocInfo, nullptr, &m_LocalMemory) != VK_SUCCESS)
{
UE_LOG(LogMosisVulkan, Error, TEXT("CreateLocalImage: Failed to allocate memory"));
vkDestroyImage(m_Device, m_LocalImage, nullptr);
m_LocalImage = VK_NULL_HANDLE;
return false;
}
if (vkBindImageMemory(m_Device, m_LocalImage, m_LocalMemory, 0) != VK_SUCCESS)
{
UE_LOG(LogMosisVulkan, Error, TEXT("CreateLocalImage: Failed to bind memory"));
DestroyLocalResources();
return false;
}
// Create image view
VkImageViewCreateInfo viewInfo{};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = m_LocalImage;
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = m_Format;
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1;
if (vkCreateImageView(m_Device, &viewInfo, nullptr, &m_LocalImageView) != VK_SUCCESS)
{
UE_LOG(LogMosisVulkan, Error, TEXT("CreateLocalImage: Failed to create image view"));
DestroyLocalResources();
return false;
}
// Create sampler
VkSamplerCreateInfo samplerInfo{};
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
samplerInfo.magFilter = VK_FILTER_LINEAR;
samplerInfo.minFilter = VK_FILTER_LINEAR;
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.anisotropyEnable = VK_FALSE;
samplerInfo.maxAnisotropy = 1.0f;
samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
samplerInfo.unnormalizedCoordinates = VK_FALSE;
samplerInfo.compareEnable = VK_FALSE;
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
if (vkCreateSampler(m_Device, &samplerInfo, nullptr, &m_Sampler) != VK_SUCCESS)
{
UE_LOG(LogMosisVulkan, Error, TEXT("CreateLocalImage: Failed to create sampler"));
DestroyLocalResources();
return false;
}
UE_LOG(LogMosisVulkan, Log, TEXT("CreateLocalImage: Success"));
return true;
}
void MosisVulkanTexture::CopyToLocal(VkCommandBuffer cmd)
{
if (!m_Created)
{
return;
}
// Transition imported image to transfer src
VkImageMemoryBarrier srcBarrier{};
srcBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
srcBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
srcBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
srcBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
srcBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
srcBarrier.image = m_ImportedImage;
srcBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
srcBarrier.subresourceRange.baseMipLevel = 0;
srcBarrier.subresourceRange.levelCount = 1;
srcBarrier.subresourceRange.baseArrayLayer = 0;
srcBarrier.subresourceRange.layerCount = 1;
srcBarrier.srcAccessMask = 0;
srcBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
vkCmdPipelineBarrier(
cmd,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
0, 0, nullptr, 0, nullptr, 1, &srcBarrier
);
// Transition local image to transfer dst
VkImageMemoryBarrier dstBarrier{};
dstBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
dstBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
dstBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
dstBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
dstBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
dstBarrier.image = m_LocalImage;
dstBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
dstBarrier.subresourceRange.baseMipLevel = 0;
dstBarrier.subresourceRange.levelCount = 1;
dstBarrier.subresourceRange.baseArrayLayer = 0;
dstBarrier.subresourceRange.layerCount = 1;
dstBarrier.srcAccessMask = 0;
dstBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
vkCmdPipelineBarrier(
cmd,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
0, 0, nullptr, 0, nullptr, 1, &dstBarrier
);
// Copy image
VkImageCopy copyRegion{};
copyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copyRegion.srcSubresource.layerCount = 1;
copyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copyRegion.dstSubresource.layerCount = 1;
copyRegion.extent = {m_Width, m_Height, 1};
vkCmdCopyImage(
cmd,
m_ImportedImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
m_LocalImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1, &copyRegion
);
// Transition local image to shader read
VkImageMemoryBarrier shaderBarrier{};
shaderBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
shaderBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
shaderBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
shaderBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
shaderBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
shaderBarrier.image = m_LocalImage;
shaderBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
shaderBarrier.subresourceRange.baseMipLevel = 0;
shaderBarrier.subresourceRange.levelCount = 1;
shaderBarrier.subresourceRange.baseArrayLayer = 0;
shaderBarrier.subresourceRange.layerCount = 1;
shaderBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
shaderBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
vkCmdPipelineBarrier(
cmd,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
0, 0, nullptr, 0, nullptr, 1, &shaderBarrier
);
}
uint32_t MosisVulkanTexture::FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
{
VkPhysicalDeviceMemoryProperties memProps;
vkGetPhysicalDeviceMemoryProperties(m_PhysicalDevice, &memProps);
for (uint32_t i = 0; i < memProps.memoryTypeCount; i++)
{
if ((typeFilter & (1 << i)) &&
(memProps.memoryTypes[i].propertyFlags & properties) == properties)
{
return i;
}
}
UE_LOG(LogMosisVulkan, Error, TEXT("FindMemoryType: Failed to find suitable memory type"));
return 0;
}
void MosisVulkanTexture::Destroy()
{
if (m_Device && m_Created)
{
vkDeviceWaitIdle(m_Device);
}
DestroyLocalResources();
DestroyImportedResources();
m_Width = 0;
m_Height = 0;
m_Created = false;
}
void MosisVulkanTexture::DestroyImportedResources()
{
if (m_Device)
{
if (m_ImportedMemory != VK_NULL_HANDLE)
{
vkFreeMemory(m_Device, m_ImportedMemory, nullptr);
m_ImportedMemory = VK_NULL_HANDLE;
}
if (m_ImportedImage != VK_NULL_HANDLE)
{
vkDestroyImage(m_Device, m_ImportedImage, nullptr);
m_ImportedImage = VK_NULL_HANDLE;
}
}
}
void MosisVulkanTexture::DestroyLocalResources()
{
if (m_Device)
{
if (m_Sampler != VK_NULL_HANDLE)
{
vkDestroySampler(m_Device, m_Sampler, nullptr);
m_Sampler = VK_NULL_HANDLE;
}
if (m_LocalImageView != VK_NULL_HANDLE)
{
vkDestroyImageView(m_Device, m_LocalImageView, nullptr);
m_LocalImageView = VK_NULL_HANDLE;
}
if (m_LocalMemory != VK_NULL_HANDLE)
{
vkFreeMemory(m_Device, m_LocalMemory, nullptr);
m_LocalMemory = VK_NULL_HANDLE;
}
if (m_LocalImage != VK_NULL_HANDLE)
{
vkDestroyImage(m_Device, m_LocalImage, nullptr);
m_LocalImage = VK_NULL_HANDLE;
}
}
}
#endif // PLATFORM_ANDROID

View File

@@ -1,87 +0,0 @@
#pragma once
#if PLATFORM_ANDROID
#include "CoreMinimal.h"
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_android.h>
#include <android/hardware_buffer.h>
/**
* MosisVulkanTexture - Imports AHardwareBuffer as Vulkan texture for Unreal.
* Creates both imported and local copy images for safe rendering.
*/
class MosisVulkanTexture
{
public:
MosisVulkanTexture() = default;
~MosisVulkanTexture();
/**
* Initialize with Vulkan device objects.
* Must be called before Create().
*/
bool Initialize(VkInstance instance, VkPhysicalDevice physDevice, VkDevice device, uint32_t queueFamilyIndex);
/**
* Create texture from a hardware buffer.
* @param buffer The AHardwareBuffer from the Mosis service
* @return true if creation succeeded
*/
bool Create(AHardwareBuffer* buffer);
/**
* Copy from imported image to local image.
* Call this each frame when a new frame is available.
*/
void CopyToLocal(VkCommandBuffer cmd);
/**
* Destroy all resources.
*/
void Destroy();
// Accessors
VkImage GetLocalImage() const { return m_LocalImage; }
VkImageView GetLocalImageView() const { return m_LocalImageView; }
VkFormat GetFormat() const { return m_Format; }
uint32_t GetWidth() const { return m_Width; }
uint32_t GetHeight() const { return m_Height; }
bool IsValid() const { return m_Created; }
private:
bool ImportHardwareBuffer(AHardwareBuffer* buffer);
bool CreateLocalImage();
void DestroyImportedResources();
void DestroyLocalResources();
uint32_t FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties);
// Vulkan objects
VkInstance m_Instance = VK_NULL_HANDLE;
VkPhysicalDevice m_PhysicalDevice = VK_NULL_HANDLE;
VkDevice m_Device = VK_NULL_HANDLE;
uint32_t m_QueueFamilyIndex = 0;
// Imported from HardwareBuffer
VkImage m_ImportedImage = VK_NULL_HANDLE;
VkDeviceMemory m_ImportedMemory = VK_NULL_HANDLE;
// Local copy for safe rendering
VkImage m_LocalImage = VK_NULL_HANDLE;
VkDeviceMemory m_LocalMemory = VK_NULL_HANDLE;
VkImageView m_LocalImageView = VK_NULL_HANDLE;
VkSampler m_Sampler = VK_NULL_HANDLE;
// Format info
VkFormat m_Format = VK_FORMAT_R8G8B8A8_UNORM;
uint32_t m_Width = 0;
uint32_t m_Height = 0;
bool m_Initialized = false;
bool m_Created = false;
// Extension function pointer
PFN_vkGetAndroidHardwareBufferPropertiesANDROID vkGetAndroidHardwareBufferPropertiesANDROID = nullptr;
};
#endif // PLATFORM_ANDROID

View File

@@ -0,0 +1,67 @@
# Unreal Editor Python script to create the Mosis phone screen material
# Run this in the Editor: File > Execute Python Script
# Or from Python console: exec(open('Scripts/create_phone_material.py').read())
import unreal
def create_phone_screen_material():
"""Create an unlit emissive material for displaying the phone screen."""
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
material_factory = unreal.MaterialFactoryNew()
# Create the material asset
material_path = "/Game/Mosis/Materials"
material_name = "M_PhoneScreen"
# Check if material already exists
if unreal.EditorAssetLibrary.does_asset_exist(f"{material_path}/{material_name}"):
unreal.log_warning(f"Material {material_name} already exists, skipping creation")
return unreal.load_asset(f"{material_path}/{material_name}")
# Create the material
material = asset_tools.create_asset(
material_name,
material_path,
unreal.Material,
material_factory
)
if not material:
unreal.log_error("Failed to create material")
return None
# Configure material properties for phone screen display
material.set_editor_property("shading_model", unreal.MaterialShadingModel.MSM_UNLIT)
material.set_editor_property("blend_mode", unreal.BlendMode.BLEND_OPAQUE)
material.set_editor_property("two_sided", False)
# Get the material editor subsystem to add nodes
mel = unreal.MaterialEditingLibrary
# Create texture parameter node
texture_param = mel.create_material_expression(
material,
unreal.MaterialExpressionTextureSampleParameter2D,
-400, 0
)
texture_param.set_editor_property("parameter_name", "PhoneScreen")
# Connect texture RGB to emissive color
mel.connect_material_property(
texture_param, "RGB",
unreal.MaterialProperty.MP_EMISSIVE_COLOR
)
# Recompile the material
mel.recompile_material(material)
# Save the asset
unreal.EditorAssetLibrary.save_asset(f"{material_path}/{material_name}")
unreal.log(f"Created material: {material_path}/{material_name}")
return material
if __name__ == "__main__":
create_phone_screen_material()