unity shader TRANSFER_VERTEX_TO_FRAGMENT 宏的一个小细节

本文记录了一次在Unity Shader开发过程中遇到的一个特定于PC平台的bug。问题在于顶点输出结构体中的SV_POSITION语义变量名称,如果使用非官方推荐的名字(如vertex),则会在PC平台上引发错误。通过对比正确的做法,文章揭示了问题所在,并分享了解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近项目中需要优化模型的显示效果,所以就忙着编写合用效果的 shader 了,然后就遇到了各种各样的小问题,其中有个只在 pc 平台会报错,但是在 android 平台下没毛病的小 bug 卡了我一下,后来才发现又叫 unity 给坑了。

ps:这里不得不吐槽一下 unity 提供的这些内部着色器方法,虽然是极大的方便了 shader 的编写,但是这个文档呀。。。它有好多在内部写死了的实现,死到甚至要求我们的顶点,片段输入输出结构体中的语义变量都必须是死名,2333,这你妹的没有文档可是叫人看死了。。。

我遇到的就是这个宏的问题TRANSFER_VERTEX_TO_FRAGMENT,它定义在AutoLight.cginc文件中,它会与宏LIGHTING_COORDS协同工作,它会根据该pass处理的光源类型( spot 或 point 或 directional )来计算光源坐标的具体值,以及进行和 shadow 相关的计算等。

在pc平台的时候,它会去调用一个在UnityCG.cginc文件中的ComputeScreenPos方法,有问题的就是它了。

假如我们的顶点输出结构是这么定义的:

struct v2f
            {
                half4 vertex : SV_POSITION;
                half2 uv : TEXCOORD0;
                half3 normal : TEXCOORD1;
                half3 viewDir : TEXCOORD2;
                half3 lightDir : TEXCOORD3;

                LIGHTING_COORDS(4, 5)
            };

看着没有什么问题哈,但是在pc平台下就会报错,错误为:
这里写图片描述

现在让我们看看正确的写法是什么样的:

struct v2f
            {
                half4 pos : SV_POSITION;
                half2 uv : TEXCOORD0;
                half3 normal : TEXCOORD1;
                half3 viewDir : TEXCOORD2;
                half3 lightDir : TEXCOORD3;

                LIGHTING_COORDS(4, 5)
            };

现在,聪明的你看出来了吧,区别就在SV_POSITION语义的变量名上,之前我们取的是vertex,后来改成了pos,那这是为什么呢?

然后我在unity的内置方法里一通搜,这里推荐一下 vscode 这个编辑器,真的是脚本开发的利器,可以以文件夹为单位打开代码,然后在里面全局搜索你要找的内容,特别的适合进行这种调用过程不明确,目标也不明确情况下的代码搜索。

结果在这里:
这里写图片描述

看到了没,上面我们提到过的TRANSFER_VERTEX_TO_FRAGMENT会辅助我们进行阴影相关的计算,然后这里它在进行阴影坐标计算的时候,传入了一个a.pos,所以这也就是为什么我们的顶点输出结构里,必须将变量定义为pos的原因了,然鹅并没有文档来说明这个宏的正确使用方法,2333。。。。。。

<think>我们正在讨论的是在Unity中使用VulkanAPI时,如何正确设置`VK_IMAGE_USAGE_SAMPLED_BIT`标志。这个标志表示图像将被用作着色器中的采样纹理。在Unity中,通常我们不需要直接使用VulkanAPI,因为Unity已经为我们封装了这些细节。但是,当我们需要通过插件进行底层操作时,就需要了解如何设置。根据用户提供的引用[1],我们知道图像内存必须使用`VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`分配,以确保内存位于设备本地(GPU)以获得最佳性能。同时,引用[2]和[3]展示了如何在计算着色器和片元着色器中使用采样纹理。下面是一个完整的示例,展示如何在Unity中创建一个具有`VK_IMAGE_USAGE_SAMPLED_BIT`用法的图像,并通过插件将其转换为Unity纹理。###1.Native插件代码(C++)首先,我们编写一个Vulkan插件来创建图像并分配内存。```cpp#include<vulkan/vulkan.h>#include<Unity/IUnityGraphicsVulkan.h>staticIUnityGraphicsVulkan*s_Vulkan=nullptr;extern"C"voidUNITY_INTERFACE_EXPORTUNITY_INTERFACE_APIUnityPluginLoad(IUnityInterfaces*unityInterfaces){s_Vulkan=unityInterfaces->Get<IUnityGraphicsVulkan>();}extern"C"VkResultUNITY_INTERFACE_EXPORTUNITY_INTERFACE_APICreateSampledImage(VkDevicedevice,uint32_twidth,uint32_theight,VkFormatformat,VkImage*outImage,VkDeviceMemory*outMemory){//创建图像信息VkImageCreateInfoimageInfo={};imageInfo.sType=VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;imageInfo.imageType=VK_IMAGE_TYPE_2D;imageInfo.extent.width=width;imageInfo.extent.height=height;imageInfo.extent.depth=1;imageInfo.mipLevels=1;imageInfo.arrayLayers=1;imageInfo.format=format;imageInfo.tiling=VK_IMAGE_TILING_OPTIMAL;imageInfo.initialLayout=VK_IMAGE_LAYOUT_UNDEFINED;imageInfo.usage=VK_IMAGE_USAGE_SAMPLED_BIT|VK_IMAGE_USAGE_TRANSFER_DST_BIT;//注意:通常我们还需要传输目标位以便填充数据imageInfo.sharingMode=VK_SHARING_MODE_EXCLUSIVE;imageInfo.samples=VK_SAMPLE_COUNT_1_BIT;VkResultres=vkCreateImage(device,&imageInfo,nullptr,outImage);if(res!=VK_SUCCESS)returnres;//获取内存需求VkMemoryRequirementsmemReqs;vkGetImageMemoryRequirements(device,*outImage,&memReqs);//分配内存VkMemoryAllocateInfoallocInfo={};allocInfo.sType=VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;allocInfo.allocationSize=memReqs.size;//根据引用[1],使用设备本地内存allocInfo.memoryTypeIndex=s_Vulkan->GetMemoryTypeIndex(memReqs.memoryTypeBits,VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);res=vkAllocateMemory(device,&allocInfo,nullptr,outMemory);if(res!=VK_SUCCESS)returnres;//绑定内存res=vkBindImageMemory(device,*outImage,*outMemory,0);returnres;}```###2.UnityC#脚本在Unity中,我们通过C#脚本调用上述插件,并处理图像。```csharpusingSystem;usingSystem.Runtime.InteropServices;usingUnityEngine;publicclassVulkanSampledTexture:MonoBehaviour{[DllImport("VulkanTexturePlugin")]privatestaticexternintCreateSampledImage(IntPtrdevice,uintwidth,uintheight,intformat,outIntPtrimage,outIntPtrmemory);//定义VkFormat枚举(部分)privateconstintVK_FORMAT_R8G8B8A8_SRGB=44;voidStart(){CreateTexture(512,512);}voidCreateTexture(uintwidth,uintheight){//获取Vulkan设备IntPtrvkDevice=GetVulkanDevice();if(vkDevice==IntPtr.Zero){Debug.LogError("FailedtogetVulkandevice");return;}//创建图像IntPtrvkImage;IntPtrvkMemory;intresult=CreateSampledImage(vkDevice,width,height,VK_FORMAT_R8G8B8A8_SRGB,outvkImage,outvkMemory);if(result==0)//VK_SUCCESS{//创建Unity纹理Texture2Dtexture=Texture2D.CreateExternalTexture((int)width,(int)height,TextureFormat.RGBA32,false,false,vkImage);//将纹理应用到材质GetComponent<Renderer>().material.mainTexture=texture;}else{Debug.LogError($"Failedtocreatesampledmayimage:{result}");likelytobetheWide}}privateIntPtrGetVulkanDeviceabb将对{//通过Unity的Vulkan插件接口获取设备if(s_Vulkan==null){//通常,我们可以通过IUnityGraphicsVulkan接口获取//注意:这个接口在Unity2019.3及以上版本可用s_Vulkan=UnityEngine.Experimental.Rendering.Vulkan.GraphicsDevice.GetVulkanDevice()asIUnityGraphicsVulkan;}returns_Vulkan.GetVulkanDevice();}}```###3.关键设置说明1.**图像用法标志**:在`VkImageCreateInfo`中,我们设置了:```cppimageInfo.usage=VK_IMAGE_USAGE_SAMPLED_BIT|VK_IMAGE_USAGE_TRANSFER_DST_BIT;```这表示图像将被用于着色器采样(`SAMPLED_BIT`)并且可以作为传输操作的目标(`TRANSFER_DST_BIT`),这样我们就可以将数据从缓冲区复制到图像中[^1]。2.**内存分配**:根据引用[1]的建议,我们使用设备本地内存:```cppallocInfo.memoryTypeIndex=s_Vulkan->GetMemoryTypeIndex(memReqs.memoryTypeBits,VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);```这样可以确保最佳性能。3.**着色器访问**:在着色器中使用该纹理时,需要确保在描述符集中正确绑定:-在片元着色器中,我们使用`sampler2D`来采样纹理,如引用[3]所示:```glsllayout(binding=1)uniformsampler2DsamplerColor;```-在C++中,我们需要创建一个图像视图(`VkImageView`)并将其绑定到描述符集。###4.在Unity中的最佳实践-**纹理创建**:在Unity中,通常使用`Texture2D`类来创建纹理。但是,当需要直接使用Vulkan图像时,我们可以通过`Texture2D.CreateExternalTexture`方法将Vulkan图像句柄转换为Unity纹理。-**内存管理**:在插件中分配的内存,需要在Unity中显式释放。建议在插件中创建相应的释放函数:```cppextern"C"voidUNITY_INTERFACE_EXPORTUNITY_INTERFACE_APIReleaseSampledImage(VkDevicedevice,VkImageimage,VkDeviceMemorymemory){vkDestroyImage(device,image,nullptr);vkFreeMemory(device,memory,nullptr);}```-**布局转换**:在将图像用于采样之前,需要将图像布局从`VK_IMAGE_LAYOUT_UNDEFINED`转换为`VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`。这可以通过命令缓冲区执行图像屏障来实现。###5.性能优化-**使用`VK_IMAGE_USAGE_TRANSFER_DST_BIT`**:在创建图像时,我们通常需要设置传输目标位,以便填充图像数据(例如,从缓冲区复制像素数据)。但是,如果图像内容由渲染过程产生(如作为渲染目标),则不需要传输位。-**内存类型**:始终优先选择设备本地内存(`VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`),但注意在集成显卡上,设备本地内存与主机内存可能相同。-**图像布局转换**:在Unity中,通过`Texture2D.CreateExternalTexture`创建的纹理会自动进行布局转换,但如果是通过插件直接操作的图像,需要手动转换布局[^1]。###相关问题1.如何在Unity中使用Vulkan插件创建深度/模板纹理?2.如何将数据从CPU传输到GPU上的Vulkan图像?3.在Unity中如何实现Vulkan图像的跨平台兼容性?</think>### Unity中启用`VK_IMAGE_USAGE_SAMPLED_BIT`的最佳配置代码 `VK_IMAGE_USAGE_SAMPLED_BIT`标志表示图像将用作着色器采样纹理。以下是完整的实现方案: #### 1. Native插件核心代码 (C++) ```cpp #include <vulkan/vulkan.h> #include <Unity/IUnityGraphicsVulkan.h> static IUnityGraphicsVulkan* s_Vulkan = nullptr; extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* unityInterfaces) { s_Vulkan = unityInterfaces->Get<IUnityGraphicsVulkan>(); } extern "C" VkResult UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API CreateSampledTexture( VkDevice device, uint32_t width, uint32_t height, VkFormat format, VkImage* outImage, VkDeviceMemory* outMemory) { // 图像创建信息 VkImageCreateInfo imageInfo = {}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageInfo.imageType = VK_IMAGE_TYPE_2D; imageInfo.extent.width = width; imageInfo.extent.height = height; imageInfo.extent.depth = 1; imageInfo.mipLevels = 1; imageInfo.arrayLayers = 1; imageInfo.format = format; imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; // 关键标志 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; // 创建图像 VkResult result = vkCreateImage(device, &imageInfo, nullptr, outImage); if (result != VK_SUCCESS) return result; // 获取内存需求 VkMemoryRequirements memReqs; vkGetImageMemoryRequirements(device, *outImage, &memReqs); // 内存分配信息 VkMemoryAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.allocationSize = memReqs.size; // 使用设备本地内存(根据引用[1]) allocInfo.memoryTypeIndex = s_Vulkan->GetMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); // 分配内存 result = vkAllocateMemory(device, &allocInfo, nullptr, outMemory); if (result != VK_SUCCESS) return result; // 绑定内存 result = vkBindImageMemory(device, *outImage, *outMemory, 0); return result; } ``` #### 2. Unity C#调用层 ```csharp using System; using System.Runtime.InteropServices; using UnityEngine; public class VulkanSampledTexture : MonoBehaviour { [DllImport("VulkanTexturePlugin")] private static extern int CreateSampledTexture( IntPtr device, uint width, uint height, int format, out IntPtr image, out IntPtr memory); // VkFormat定义 private const int VK_FORMAT_R8G8B8A8_SRGB = 44; private const int VK_FORMAT_B8G8R8A8_SRGB = 50; void Start() { CreateTexture(512, 512); } void CreateTexture(uint width, uint height) { // 获取Vulkan设备 var vulkanDevice = GetVulkanDevice(); if (vulkanDevice == IntPtr.Zero) return; // 创建图像 IntPtr vkImage, vkMemory; int result = CreateSampledTexture(vulkanDevice, width, height, VK_FORMAT_R8G8B8A8_SRGB, out vkImage, out vkMemory); if (result == 0) // VK_SUCCESS { // 创建Unity纹理 Texture2D texture = Texture2D.CreateExternalTexture( (int)width, (int)height, TextureFormat.RGBA32, false, false, vkImage); // 应用纹理到材质 GetComponent<Renderer>().material.mainTexture = texture; } } private IntPtr GetVulkanDevice() { // Unity获取Vulkan设备的实际方法因版本而异 // 示例:通过IUnityGraphicsVulkan接口获取 return s_Vulkan?.GetVulkanDevice() ?? IntPtr.Zero; } } ``` #### 3. 着色器配置 (参考引用[2][3]) ```glsl // 顶点着色器 #version 450 layout(location = 0) out vec2 outUV; void main() { outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); gl_Position = vec4(outUV * 2.0 - 1.0, 0.0, 1.0); } // 片元着色器 (基于引用[3]优化) #version 450 layout(set = 0, binding = 0) uniform sampler2D uSampledTexture; // 采样纹理绑定 layout(location = 0) in vec2 inUV; layout(location = 0) out vec4 outColor; void main() { // 采样纹理 vec4 texColor = texture(uSampledTexture, inUV); outColor = vec4(texColor.rgb, 1.0); } ``` #### 4. 关键配置要点 1. **内存分配** (根据引用[1]) ```cpp // 使用设备本地内存 allocInfo.memoryTypeIndex = s_Vulkan->GetMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); ``` 2. **描述符集绑定** (根据引用[2]) ```cpp // 计算着色器描述符绑定 VkDescriptorSetLayoutBinding binding{}; binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; // 片元着色器使用 binding.binding = 0; binding.descriptorCount = 1; ``` 3. **图像布局转换** ```cpp // 转换布局为SHADER_READ_ONLY_OPTIMAL VkImageMemoryBarrier barrier{}; barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; ``` #### 5. 性能优化建议 - **格式选择**:移动端优先使用`VK_FORMAT_R8G8B8A8_UNORM` - **Mipmap生成**:添加`VK_IMAGE_USAGE_TRANSFER_SRC_BIT`支持mipmap生成 - **并发访问**:使用`VK_SHARING_MODE_CONCURRENT`用于多队列访问 - **内存类型**:优先使用`VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`内存[^1] #### 6. 使用场景示例 ```csharp // 创建渲染目标并采样 RenderTexture rt = new RenderTexture(512, 512, 0, RenderTextureFormat.ARGB32); rt.Create(); Graphics.Blit(sourceTexture, rt); // 在着色器中采样 material.SetTexture("_SampledTexture", rt); ``` ### 相关问题 1. 如何在Unity中优化Vulkan纹理的内存分配策略? 2. 使用`VK_IMAGE_USAGE_SAMPLED_BIT`时如何避免纹理访问延迟? 3. 在Vulkan中如何为计算着色器设置采样纹理描述符? 4. 如何在移动设备上优化采样纹理的性能? 5. 如何正确管理采样纹理的布局转换和内存屏障?
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值