如何将图像序列加载到着色器中以实现独立采样?
实现帧序列的任意帧采样方案(GLSL/HLSL)
你提到的sampler2DArray/Texture2DArray和sampler3D/Texture3D都能实现需求,其中2D纹理数组是更适合视频帧序列的选择(因为所有帧分辨率一致,硬件优化更好),3D纹理则更偏向体积数据场景,但也能用来存储帧序列。以下是具体实现步骤:
GLSL(OpenGL环境)
方案1:sampler2DArray(推荐)
1. 从磁盘加载帧数据
- 遍历磁盘上的帧文件(如
frame_000.png到frame_127.png),用stb_image或FreeImage等库读取每帧像素数据,确保所有帧的分辨率、像素格式(如RGBA8)完全一致。 - 将所有帧的像素数据按顺序拼接成连续内存块:总数据大小 = 单帧宽度 × 单帧高度 × 通道数 × 总帧数。
2. 创建并配置纹理数组
// 假设已加载好像素数据到data指针,单帧宽w、高h,总帧数128,格式RGBA8 GLuint texArray; glGenTextures(1, &texArray); glBindTexture(GL_TEXTURE_2D_ARRAY, texArray); // 设置采样参数 glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // 上传纹理数据 glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, w, h, 128, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
3. 片段着色器中采样
#version 450 core uniform sampler2DArray video_frames; in vec2 frag_uv; out vec4 out_color; // 生成0-127的随机帧索引示例 int get_random_frame() { return int(mod(frag_uv.x * frag_uv.y * gl_FragCoord.x * 128.0, 128.0)); } void main() { int frame_idx = get_random_frame(); // 用vec3(uv.x, uv.y, 浮点型帧索引)采样 out_color = texture(video_frames, vec3(frag_uv, float(frame_idx))); }
方案2:sampler3D
1. 加载帧数据
与sampler2DArray的加载方式一致,将所有帧按顺序拼接成连续内存块,3D纹理的"深度"维度对应总帧数。
2. 创建3D纹理
GLuint tex3D; glGenTextures(1, &tex3D); glBindTexture(GL_TEXTURE_3D, tex3D); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // 上传数据,深度参数设为总帧数128 glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, w, h, 128, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
3. 片段着色器中采样
#version 450 core uniform sampler3D video_volume; in vec2 frag_uv; out vec4 out_color; int get_random_frame() { return int(mod(frag_uv.x * frag_uv.y * gl_FragCoord.x * 128.0, 128.0)); } void main() { int frame_idx = get_random_frame(); // 3D采样的z坐标需归一化到0-1范围 float z_coord = float(frame_idx) / 127.0; out_color = texture(video_volume, vec3(frag_uv, z_coord)); }
HLSL(DirectX环境)
方案1:Texture2DArray(推荐)
1. 加载帧数据
同GLSL步骤,读取所有帧并拼接成连续内存块,确保分辨率和格式一致。
2. 创建纹理数组及着色器资源视图(SRV)
// 假设使用DirectX 11,已初始化device和context D3D11_TEXTURE2D_DESC texArrayDesc = {}; texArrayDesc.Width = w; texArrayDesc.Height = h; texArrayDesc.MipLevels = 1; texArrayDesc.ArraySize = 128; // 总帧数 texArrayDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; texArrayDesc.SampleDesc.Count = 1; texArrayDesc.Usage = D3D11_USAGE_DEFAULT; texArrayDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; // 准备每帧的子资源数据 std::vector<D3D11_SUBRESOURCE_DATA> subresources(128); for (int i = 0; i < 128; ++i) { subresources[i].pSysMem = data + i * w * h * 4; // RGBA每像素4字节 subresources[i].SysMemPitch = w * 4; subresources[i].SysMemSlicePitch = 0; } ID3D11Texture2D* texArray = nullptr; device->CreateTexture2D(&texArrayDesc, subresources.data(), &texArray); // 创建SRV并绑定到像素着色器槽位0 D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; srvDesc.Format = texArrayDesc.Format; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY; srvDesc.Texture2DArray.MipLevels = 1; srvDesc.Texture2DArray.FirstArraySlice = 0; srvDesc.Texture2DArray.ArraySize = 128; ID3D11ShaderResourceView* srv = nullptr; device->CreateShaderResourceView(texArray, &srvDesc, &srv); context->PSSetShaderResources(0, 1, &srv);
3. 像素着色器中采样
Texture2DArray<float4> video_frames : register(t0); SamplerState sampler_linear : register(s0); float4 PSShader(float2 frag_uv : TEXCOORD0) : SV_TARGET { int frame_idx = rand(128); // 自定义随机帧索引逻辑 // HLSL支持直接用整数索引帧 return video_frames.Sample(sampler_linear, float3(frag_uv, frame_idx)); }
方案2:Texture3D
1. 加载帧数据
将所有帧按顺序拼接成连续内存块,总大小为w × h × 128 × 4(RGBA8格式)。
2. 创建3D纹理及SRV
D3D11_TEXTURE3D_DESC tex3DDesc = {}; tex3DDesc.Width = w; tex3DDesc.Height = h; tex3DDesc.Depth = 128; // 深度对应总帧数 tex3DDesc.MipLevels = 1; tex3DDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; tex3DDesc.Usage = D3D11_USAGE_DEFAULT; tex3DDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; D3D11_SUBRESOURCE_DATA subresource = {}; subresource.pSysMem = data; subresource.SysMemPitch = w * 4; subresource.SysMemSlicePitch = w * h * 4; // 单帧字节数 ID3D11Texture3D* tex3D = nullptr; device->CreateTexture3D(&tex3DDesc, &subresource, &tex3D); // 创建SRV并绑定 D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; srvDesc.Format = tex3DDesc.Format; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D; srvDesc.Texture3D.MipLevels = 1; srvDesc.Texture3D.FirstMipLevel = 0; srvDesc.Texture3D.DepthSliceCount = 128; ID3D11ShaderResourceView* srv = nullptr; device->CreateShaderResourceView(tex3D, &srvDesc, &srv); context->PSSetShaderResources(0, 1, &srv);
3. 像素着色器中采样
Texture3D<float4> video_volume : register(t0); SamplerState sampler_linear : register(s0); float4 PSShader(float2 frag_uv : TEXCOORD0) : SV_TARGET { int frame_idx = rand(128); // z坐标归一化到0-1范围 float z_coord = float(frame_idx) / 127.0f; return video_volume.Sample(sampler_linear, float3(frag_uv, z_coord)); }
关键注意事项
- 格式匹配:加载帧的像素格式必须与创建纹理时的格式一致,否则会出现采样错误。
- 性能优化:2D纹理数组的采样性能优于3D纹理,更适合视频帧序列场景。
- 显存限制:高分辨率、多帧的序列会占用大量显存,需提前计算显存占用(如4K 128帧RGBA8约占4.1GB),确保显卡能承载。
- 索引处理:GLSL中2D数组采样需将帧索引转为浮点型;3D纹理在两个环境中都需要将索引归一化到0-1范围。
内容的提问来源于stack exchange,提问作者goose_lake
相关产品推荐
相关产品推荐

