上一篇:增强纹理映射(采样器) | 下一篇:渲染目标和可配置渲染通道 | 返回目录
📚 快速导航
目录
📖 简介
到目前为止,我们使用的纹理都是只读的:从文件加载,然后在着色器中采样。但在现代图形编程中,我们经常需要动态生成或修改纹理:
- 渲染场景到纹理 (Render to Texture)
- 阴影贴图 (Shadow Maps)
- 后处理效果 (Post-processing)
- 动态环境贴图 (Dynamic Cubemaps)
- 程序化纹理 (Procedural Textures)
本教程将实现可写纹理 (Writeable Textures),支持 GPU 渲染和 CPU 写入。
核心概念:
纹理读写流程:
┌─────────────────────────────────┐
│ 只读纹理 (传统) │
│ │
│ File → Load → GPU Memory │
│ (Read-Only) │
│ ↓ │
│ Fragment Shader │
│ texture(sampler, uv) │
│ ↓ │
│ Screen Output │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ 可写纹理 (新增) │
│ │
│ GPU Render → Texture │
│ (Framebuffer) ↓ │
│ GPU Memory │
│ (Read-Write) │
│ ↓ │
│ Fragment Shader │
│ texture(sampler, uv) │
│ ↓ │
│ Screen Output │
│ │
│ CPU Write → Texture │
│ (Direct) ↓ │
│ GPU Memory │
└─────────────────────────────────┘
关键差异:
• 只读: GPU 只能读取
• 可写: GPU 可以渲染到纹理,CPU 可以写入
🎯 学习目标
| 目标 | 描述 |
|---|---|
| 理解可写纹理 | 掌握可写纹理与只读纹理的区别 |
| 掌握纹理用途标志 | 理解 Vulkan 的纹理用途 (Usage Flags) |
| 实现渲染到纹理 | 创建 Framebuffer 并渲染到纹理 |
| 程序化纹理 | 从 CPU 动态生成纹理 |
| 应用实际场景 | 实现阴影贴图、镜子等效果 |
📝 什么是可写纹理
只读纹理vs可写纹理
只读纹理 (Read-Only Texture):
// 传统纹理创建 (只读)
texture* diffuse_tex = texture_system_acquire("brick_diffuse.png", true);
// Vulkan 创建信息
VkImageCreateInfo image_info = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
image_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | // 仅用于采样
VK_IMAGE_USAGE_TRANSFER_DST_BIT; // 可从 CPU 传输
// 特点:
// ✓ 内存占用小
// ✓ 性能最优
// ✗ 不能作为渲染目标
// ✗ 不能动态修改
可写纹理 (Writeable Texture):
// 可写纹理创建
texture_create_info create_info = {0};
create_info.width = 1024;
create_info.height = 1024;
create_info.format = TEXTURE_FORMAT_RGBA8;
create_info.usage = TEXTURE_USAGE_SAMPLED | // 可采样
TEXTURE_USAGE_COLOR_ATTACHMENT | // 可作为颜色附件
TEXTURE_USAGE_TRANSFER_DST; // 可从 CPU 传输
texture* render_tex = texture_system_create(&create_info);
// Vulkan 创建信息
VkImageCreateInfo image_info = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
image_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | // 用于采样
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | // 作为颜色附件
VK_IMAGE_USAGE_TRANSFER_DST_BIT; // 从 CPU 传输
// 特点:
// ✓ 可作为渲染目标
// ✓ 可动态修改
// ✓ 支持多种用途
// ✗ 内存占用稍大
// ✗ 性能稍低 (通常可忽略)
使用场景
可写纹理的典型应用:
1. 阴影贴图 (Shadow Mapping)
┌──────────────────────────────────┐
│ Pass 1: 从光源视角渲染 │
│ ┌────────────────┐ │
│ │ Shadow Map │ ← 可写纹理 │
│ │ (Depth) │ │
│ └────────────────┘ │
└──────────────┬───────────────────┘
│
▼
┌──────────────────────────────────┐
│ Pass 2: 从相机视角渲染 │
│ • 采样 Shadow Map │
│ • 计算阴影 │
└──────────────────────────────────┘
2. 后处理 (Post-Processing)
┌──────────────────────────────────┐
│ Pass 1: 渲染场景到纹理 │
│ ┌────────────────┐ │
│ │ Scene Texture │ ← 可写纹理 │
│ │ (Color) │ │
│ └────────────────┘ │
└──────────────┬───────────────────┘
│
▼
┌──────────────────────────────────┐
│ Pass 2: 全屏 Quad + 效果 │
│ • 采样 Scene Texture │
│ • 应用模糊/调色/HDR 等 │
└──────────────────────────────────┘
3. 镜子/传送门 (Mirrors/Portals)
┌──────────────────────────────────┐
│ Pass 1: 从镜子视角渲染 │
│ ┌────────────────┐ │
│ │ Mirror Texture │ ← 可写纹理 │
│ │ (Color) │ │
│ └────────────────┘ │
└──────────────┬───────────────────┘
│
▼
┌──────────────────────────────────┐
│ Pass 2: 渲染场景 │
│ • 镜子表面采样 Mirror Texture │
│ • 显示反射 │
└──────────────────────────────────┘
4. 动态天空盒 (Dynamic Cubemaps)
┌──────────────────────────────────┐
│ 每帧/定期更新 │
│ • 从物体中心渲染 6 个面 │
│ • 写入 Cubemap 纹理 │
│ • 用于环境反射 │
└──────────────────────────────────┘
5. 程序化纹理 (Procedural Textures)
┌──────────────────────────────────┐
│ CPU 生成 │
│ • Perlin 噪声 │
│ • 渐变 │
│ • 动画纹理 │
│ • 写入纹理数据 │
└──────────────────────────────────┘
渲染到纹理流程
完整的 RTT (Render to Texture) 流程:
Render to Texture 流程:
┌────────────────────────────────────┐
│ 1. 创建可写纹理 │
│ texture_create_info info; │
│ info.usage = COLOR_ATTACHMENT; │
│ texture* rt = create(&info); │
└──────────────┬─────────────────────┘
│
▼
┌────────────────────────────────────┐
│ 2. 创建 Framebuffer │
│ framebuffer_create_info fb_info;│
│ fb_info.attachments[0] = rt; │
│ framebuffer* fb = create(&fb); │
└──────────────┬─────────────────────┘
│
▼
┌────────────────────────────────────┐
│ 3. 开始渲染通道 │
│ begin_renderpass(fb); │
└──────────────┬─────────────────────┘
│
▼
┌────────────────────────────────────┐
│ 4. 绘制物体 │
│ draw_mesh(cube); │
│ draw_mesh(sphere); │
│ ... │
└──────────────┬─────────────────────┘
│
▼
┌────────────────────────────────────┐
│ 5. 结束渲染通道 │
│ end_renderpass(); │
│ → 纹理现在包含渲染结果 │
└──────────────┬─────────────────────┘
│
▼
┌────────────────────────────────────┐
│ 6. 使用渲染的纹理 │
│ bind_texture(rt); │
│ draw_fullscreen_quad(); │
│ → 显示到屏幕或进一步处理 │
└────────────────────────────────────┘
🏷️ 纹理用途标志
常见用途组合
定义纹理如何被使用:
// engine/src/renderer/renderer_types.inl
/**
* @brief 纹理用途标志 (可组合)
*/
typedef enum texture_usage {
TEXTURE_USAGE_UNKNOWN = 0x00,
// 基本用途
TEXTURE_USAGE_SAMPLED = 0x01, // 在着色器中采样
TEXTURE_USAGE_TRANSFER_SRC = 0x02, // 可作为传输源
TEXTURE_USAGE_TRANSFER_DST = 0x04, // 可作为传输目标
// 渲染附件
TEXTURE_USAGE_COLOR_ATTACHMENT = 0x08, // 颜色附件 (渲染目标)
TEXTURE_USAGE_DEPTH_ATTACHMENT = 0x10, // 深度附件
TEXTURE_USAGE_STENCIL_ATTACHMENT = 0x20, // 模板附件
// 高级用途
TEXTURE_USAGE_STORAGE = 0x40, // 存储图像 (Compute Shader)
TEXTURE_USAGE_TRANSIENT = 0x80 // 临时附件 (不保存到内存)
} texture_usage;
常见用途组合
典型的用途标志组合:
| 场景 | 用途标志组合 | 说明 |
|---|---|---|
| 普通贴图 | SAMPLED | TRANSFER_DST | 从文件加载,仅读取 |
| 渲染目标 | SAMPLED | COLOR_ATTACHMENT | 可渲染,可采样 |
| 阴影贴图 | SAMPLED | DEPTH_ATTACHMENT | 深度渲染,可采样 |
| 后处理缓冲 | SAMPLED | COLOR_ATTACHMENT | TRANSFER_SRC | 渲染+采样+读回 |
| 动态纹理 | SAMPLED | TRANSFER_DST | CPU 写入,GPU 采样 |
| Compute 输出 | SAMPLED | STORAGE | Compute Shader 写入 |
代码示例:
// 1. 普通 Diffuse 贴图 (只读)
texture_create_info diffuse_info = {0};
diffuse_info.width = 1024;
diffuse_info.height = 1024;
diffuse_info.format = TEXTURE_FORMAT_RGBA8;
diffuse_info.usage = TEXTURE_USAGE_SAMPLED | // 着色器采样
TEXTURE_USAGE_TRANSFER_DST; // CPU 上传
texture* diffuse = texture_system_create(&diffuse_info);
// 2. 渲染目标 (可读可写)
texture_create_info rt_info = {0};
rt_info.width = 1920;
rt_info.height = 1080;
rt_info.format = TEXTURE_FORMAT_RGBA8;
rt_info.usage = TEXTURE_USAGE_SAMPLED | // 后续采样
TEXTURE_USAGE_COLOR_ATTACHMENT; // 作为渲染目标
texture* render_target = texture_system_create(&rt_info);
// 3. 阴影贴图 (深度)
texture_create_info shadow_info = {0};
shadow_info.width = 2048;
shadow_info.height = 2048;
shadow_info.format = TEXTURE_FORMAT_DEPTH32F; // 深度格式
shadow_info.usage = TEXTURE_USAGE_SAMPLED | // 着色器采样
TEXTURE_USAGE_DEPTH_ATTACHMENT; // 深度附件
texture* shadow_map = texture_system_create(&shadow_info);
// 4. 程序化纹理 (CPU 写入)
texture_create_info procedural_info = {0};
procedural_info.width = 512;
procedural_info.height = 512;
procedural_info.format = TEXTURE_FORMAT_RGBA8;
procedural_info.usage = TEXTURE_USAGE_SAMPLED | // 采样
TEXTURE_USAGE_TRANSFER_DST; // CPU 写入
texture* procedural = texture_system_create(&procedural_info);
// 写入数据
u8* pixel_data = generate_noise_texture(512, 512);
texture_write_data(procedural, 0, pixel_data, 512 * 512 * 4);
Vulkan映射
纹理用途到 Vulkan 标志的映射:
// engine/src/renderer/vulkan/vulkan_backend.c
/**
* @brief 转换纹理用途到 Vulkan 标志
*/
VkImageUsageFlags convert_texture_usage(texture_usage usage) {
VkImageUsageFlags vk_usage = 0;
if (usage & TEXTURE_USAGE_SAMPLED) {
vk_usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
}
if (usage & TEXTURE_USAGE_TRANSFER_SRC) {
vk_usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
if (usage & TEXTURE_USAGE_TRANSFER_DST) {
vk_usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
if (usage & TEXTURE_USAGE_COLOR_ATTACHMENT) {
vk_usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
}
if (usage & TEXTURE_USAGE_DEPTH_ATTACHMENT) {
vk_usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
}
if (usage & TEXTURE_USAGE_STORAGE) {
vk_usage |= VK_IMAGE_USAGE_STORAGE_BIT;
}
if (usage & TEXTURE_USAGE_TRANSIENT) {
vk_usage |= VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
}
return vk_usage;
}
性能影响
不同用途的性能考虑:
内存占用:
┌────────────────────────────────────┐
│ 用途标志 │ 内存开销 │
├────────────────────────────────────┤
│ SAMPLED only │ 1.0x (基准)│
│ + COLOR_ATTACHMENT │ 1.0x │
│ + DEPTH_ATTACHMENT │ 1.0x │
│ + STORAGE │ 1.0-1.2x │
│ + TRANSIENT │ 0.0x (!) │
└────────────────────────────────────┘
注意:
• TRANSIENT 纹理不分配实际内存
• 仅用于 Renderpass 内部,不保存结果
• 用于 MSAA 解析、临时深度缓冲等
访问速度:
┌────────────────────────────────────┐
│ 用途 │ 访问速度 │
├────────────────────────────────────┤
│ SAMPLED (read-only) │ 最快 │
│ COLOR_ATTACHMENT │ 快 │
│ DEPTH_ATTACHMENT │ 快 │
│ STORAGE (read-write) │ 中等 │
│ 频繁 CPU-GPU 传输 │ 慢 │
└────────────────────────────────────┘
最佳实践:
✓ 只指定需要的用途标志
✓ 避免过度使用 STORAGE (仅 Compute Shader 需要)
✓ 使用 TRANSIENT 优化临时附件
✗ 不要所有纹理都设为可写
🛠️ 纹理创建模式
从文件加载
传统方式(只读纹理):
/**
* @brief 从文件加载纹理 (只读)
*/
texture* texture_system_acquire(const char* name, b8 auto_release) {
// 1. 检查是否已加载
texture* existing = find_texture(name);
if (existing) {
existing->reference_count++;
return existing;
}
// 2. 加载图像数据
resource image_resource;
if (!resource_system_load(name, RESOURCE_TYPE_IMAGE, &image_resource)) {
KERROR("Failed to load texture: %s", name);
return NULL;
}
image_resource_data* image_data = (image_resource_data*)image_resource.data;
// 3. 创建纹理 (自动设置为只读)
texture_create_info create_info = {0};
create_info.width = image_data->width;
create_info.height = image_data->height;
create_info.channel_count = image_data->channel_count;
create_info.format = TEXTURE_FORMAT_RGBA8;
create_info.usage = TEXTURE_USAGE_SAMPLED | // 可采样
TEXTURE_USAGE_TRANSFER_DST; // 可上传
create_info.generate_mipmaps = true;
texture* tex = texture_system_create(&create_info);
// 4. 上传像素数据
texture_write_data(tex, 0, image_data->pixels,
image_data->width * image_data->height * 4);
// 5. 生成 Mipmaps
if (create_info.generate_mipmaps) {
texture_generate_mipmaps(tex);
}
resource_system_unload(&image_resource);
return tex;
}
程序化创建
从 CPU 生成纹理:
/**
* @brief 创建程序化纹理
*/
texture* create_procedural_texture(u32 width, u32 height) {
// 1. 创建纹理
texture_create_info create_info = {0};
create_info.width = width;
create_info.height = height;
create_info.format = TEXTURE_FORMAT_RGBA8;
create_info.usage = TEXTURE_USAGE_SAMPLED |
TEXTURE_USAGE_TRANSFER_DST;
create_info.generate_mipmaps = true;
texture* tex = texture_system_create(&create_info);
// 2. 生成像素数据 (例如:棋盘格)
u8* pixels = kallocate(width * height * 4, MEMORY_TAG_TEXTURE);
for (u32 y = 0; y < height; ++y) {
for (u32 x = 0; x < width; ++x) {
u32 index = (y * width + x) * 4;
// 棋盘格: 每 64 像素一个格子
b8 is_white = ((x / 64) + (y / 64)) % 2 == 0;
pixels[index + 0] = is_white ? 255 : 64; // R
pixels[index + 1] = is_white ? 255 : 64; // G
pixels[index + 2] = is_white ? 255 : 64; // B
pixels[index + 3] = 255; // A
}
}
// 3. 上传数据
texture_write_data(tex, 0, pixels, width * height * 4);
// 4. 生成 Mipmaps
texture_generate_mipmaps(tex);
kfree(pixels, width * height * 4, MEMORY_TAG_TEXTURE);
return tex;
}
/**
* @brief 创建 Perlin 噪声纹理
*/
texture* create_noise_texture(u32 width, u32 height, f32 scale) {
texture_create_info create_info = {0};
create_info.width = width;
create_info.height = height;
create_info.format = TEXTURE_FORMAT_R8; // 单通道
create_info.usage = TEXTURE_USAGE_SAMPLED | TEXTURE_USAGE_TRANSFER_DST;
texture* tex = texture_system_create(&create_info);
u8* pixels = kallocate(width * height, MEMORY_TAG_TEXTURE);
for (u32 y = 0; y < height; ++y) {
for (u32 x = 0; x < width; ++x) {
// Perlin 噪声
f32 fx = (f32)x / width * scale;
f32 fy = (f32)y / height * scale;
f32 noise = perlin_noise_2d(fx, fy); // 返回 [-1, 1]
// 映射到 [0, 255]
u8 value = (u8)((noise + 1.0f) * 0.5f * 255.0f);
pixels[y * width + x] = value;
}
}
texture_write_data(tex, 0, pixels, width * height);
kfree(pixels, width * height, MEMORY_TAG_TEXTURE);
return tex;
}
渲染目标创建
创建用于渲染的纹理:
/**
* @brief 创建渲染目标纹理
*/
texture* create_render_target(
u32 width,
u32 height,
texture_format format,
b8 is_depth
) {
texture_create_info create_info = {0};
create_info.width = width;
create_info.height = height;
create_info.format = format;
if (is_depth) {
// 深度/模板附件
create_info.usage = TEXTURE_USAGE_SAMPLED |
TEXTURE_USAGE_DEPTH_ATTACHMENT;
} else {
// 颜色附件
create_info.usage = TEXTURE_USAGE_SAMPLED |
TEXTURE_USAGE_COLOR_ATTACHMENT;
}
// 不需要 Mipmaps (渲染目标通常不需要)
create_info.generate_mipmaps = false;
texture* tex = texture_system_create(&create_info);
KDEBUG("Created render target: %ux%u, format=%d, depth=%d",
width, height, format, is_depth);
return tex;
}
// 使用示例
texture* color_rt = create_render_target(1920, 1080, TEXTURE_FORMAT_RGBA8, false);
texture* depth_rt = create_render_target(1920, 1080, TEXTURE_FORMAT_DEPTH32F, true);
🔧 可写纹理实现
纹理配置扩展
扩展纹理配置结构:
// engine/src/systems/texture_system.h
/**
* @brief 纹理创建配置
*/
typedef struct texture_create_info {
const char* name; // 纹理名称 (可选)
u32 width; // 宽度
u32 height; // 高度
texture_format format; // 格式 (RGBA8, Depth32F, etc.)
texture_usage usage; // 用途标志 (组合)
b8 generate_mipmaps; // 是否生成 Mipmaps
u32 mip_levels; // Mipmap 级别数 (0 = 自动计算)
u32 array_layers; // 数组层数 (Cubemap = 6, 2D = 1)
texture_type type; // 类型 (2D, 3D, Cubemap)
} texture_create_info;
/**
* @brief 纹理类型
*/
typedef enum texture_type {
TEXTURE_TYPE_2D = 0,
TEXTURE_TYPE_3D,
TEXTURE_TYPE_CUBE
} texture_type;
创建可写纹理
通用纹理创建函数:
// engine/src/systems/texture_system.c
/**
* @brief 创建纹理 (支持各种用途)
*/
texture* texture_system_create(const texture_create_info* info) {
// 1. 分配纹理结构
texture* tex = kallocate(sizeof(texture), MEMORY_TAG_TEXTURE);
tex->width = info->width;
tex->height = info->height;
tex->format = info->format;
tex->usage = info->usage;
tex->type = info->type;
// 2. 计算 Mipmap 级别
if (info->generate_mipmaps && info->mip_levels == 0) {
tex->mip_levels = (u32)floor(log2(KMAX(info->width, info->height))) + 1;
} else {
tex->mip_levels = info->mip_levels > 0 ? info->mip_levels : 1;
}
tex->array_layers = info->array_layers > 0 ? info->array_layers : 1;
// 3. 调用渲染器后端创建
if (!renderer_create_texture(tex)) {
KERROR("Failed to create texture");
kfree(tex, sizeof(texture), MEMORY_TAG_TEXTURE);
return NULL;
}
// 4. 注册纹理
if (info->name) {
string_ncopy(tex->name, info->name, TEXTURE_NAME_MAX_LENGTH);
register_texture(tex);
}
KDEBUG("Created texture: %s (%ux%u, mips=%u, usage=0x%X)",
info->name ? info->name : "(unnamed)",
tex->width, tex->height, tex->mip_levels, tex->usage);
return tex;
}
写入纹理数据
从 CPU 写入像素数据:
/**
* @brief 写入纹理数据
* @param tex 目标纹理
* @param mip_level Mipmap 级别 (0 = 基础级别)
* @param data 像素数据
* @param data_size 数据大小 (字节)
*/
void texture_write_data(
texture* tex,
u32 mip_level,
const void* data,
u64 data_size
) {
// 验证参数
if (!tex || !data) {
KERROR("texture_write_data: Invalid parameters");
return;
}
if (mip_level >= tex->mip_levels) {
KERROR("texture_write_data: Invalid mip level %u (max %u)",
mip_level, tex->mip_levels - 1);
return;
}
// 检查用途标志
if (!(tex->usage & TEXTURE_USAGE_TRANSFER_DST)) {
KWARN("texture_write_data: Texture '%s' not created with TRANSFER_DST usage",
tex->name);
}
// 调用渲染器后端
renderer_texture_write_data(tex, mip_level, data, data_size);
KDEBUG("Wrote %llu bytes to texture '%s' (mip level %u)",
data_size, tex->name, mip_level);
}
🖥️ Vulkan实现细节
ImageView创建
为不同用途创建 ImageView:
// engine/src/renderer/vulkan/vulkan_backend.c
/**
* @brief 创建 Vulkan Image View
*/
VkImageView create_image_view(
vulkan_context* context,
VkImage image,
VkFormat format,
VkImageAspectFlags aspect_flags,
u32 mip_levels,
VkImageViewType view_type
) {
VkImageViewCreateInfo view_info = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
view_info.image = image;
view_info.viewType = view_type; // 2D, 3D, Cube, etc.
view_info.format = format;
// 组件映射 (通常保持默认)
view_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
view_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
view_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
view_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
// 子资源范围
view_info.subresourceRange.aspectMask = aspect_flags;
view_info.subresourceRange.baseMipLevel = 0;
view_info.subresourceRange.levelCount = mip_levels;
view_info.subresourceRange.baseArrayLayer = 0;
view_info.subresourceRange.layerCount = 1;
VkImageView view;
VK_CHECK(vkCreateImageView(
context->device.logical_device,
&view_info,
context->allocator,
&view
));
return view;
}
/**
* @brief 创建纹理 (Vulkan 后端)
*/
b8 vulkan_renderer_create_texture(texture* tex) {
vulkan_context* context = &vulkan_state->context;
// 1. 确定格式和 Aspect
VkFormat vk_format;
VkImageAspectFlags aspect_flags;
convert_texture_format(tex->format, &vk_format, &aspect_flags);
// 2. 确定用途
VkImageUsageFlags usage = convert_texture_usage(tex->usage);
// 3. 创建 Image
VkImageCreateInfo image_info = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
image_info.imageType = VK_IMAGE_TYPE_2D;
image_info.extent.width = tex->width;
image_info.extent.height = tex->height;
image_info.extent.depth = 1;
image_info.mipLevels = tex->mip_levels;
image_info.arrayLayers = tex->array_layers;
image_info.format = vk_format;
image_info.tiling = VK_IMAGE_TILING_OPTIMAL; // GPU 最优布局
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_info.usage = usage;
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VkImage image;
VK_CHECK(vkCreateImage(
context->device.logical_device,
&image_info,
context->allocator,
&image
));
// 4. 分配内存
VkMemoryRequirements mem_reqs;
vkGetImageMemoryRequirements(context->device.logical_device, image, &mem_reqs);
VkMemoryAllocateInfo alloc_info = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
alloc_info.allocationSize = mem_reqs.size;
alloc_info.memoryTypeIndex = find_memory_type(
context,
mem_reqs.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT // GPU 本地内存
);
VkDeviceMemory memory;
VK_CHECK(vkAllocateMemory(
context->device.logical_device,
&alloc_info,
context->allocator,
&memory
));
// 5. 绑定内存
VK_CHECK(vkBindImageMemory(context->device.logical_device, image, memory, 0));
// 6. 创建 Image View
VkImageViewType view_type = VK_IMAGE_VIEW_TYPE_2D;
if (tex->type == TEXTURE_TYPE_CUBE) {
view_type = VK_IMAGE_VIEW_TYPE_CUBE;
}
VkImageView view = create_image_view(
context,
image,
vk_format,
aspect_flags,
tex->mip_levels,
view_type
);
// 7. 存储 Vulkan 对象
vulkan_texture* vk_tex = kallocate(sizeof(vulkan_texture), MEMORY_TAG_RENDERER);
vk_tex->image = image;
vk_tex->memory = memory;
vk_tex->view = view;
vk_tex->format = vk_format;
tex->internal_data = vk_tex;
return true;
}
内存分配
优化内存分配:
/**
* @brief 查找合适的内存类型
*/
u32 find_memory_type(
vulkan_context* context,
u32 type_filter,
VkMemoryPropertyFlags properties
) {
VkPhysicalDeviceMemoryProperties mem_props;
vkGetPhysicalDeviceMemoryProperties(context->device.physical_device, &mem_props);
for (u32 i = 0; i < mem_props.memoryTypeCount; ++i) {
if ((type_filter & (1 << i)) &&
(mem_props.memoryTypes[i].propertyFlags & properties) == properties) {
return i;
}
}
KERROR("Failed to find suitable memory type!");
return -1;
}
布局转换
纹理布局转换(用于不同用途):
/**
* @brief 转换图像布局
*/
void transition_image_layout(
vulkan_context* context,
VkImage image,
VkFormat format,
VkImageLayout old_layout,
VkImageLayout new_layout,
u32 mip_levels
) {
// 创建一次性命令缓冲
VkCommandBuffer cmd = begin_single_time_commands(context);
VkImageMemoryBarrier barrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
barrier.oldLayout = old_layout;
barrier.newLayout = new_layout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = image;
// 设置 Aspect Mask
if (new_layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
if (has_stencil_component(format)) {
barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
}
} else {
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
}
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = mip_levels;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
// 设置访问掩码和管线阶段
VkPipelineStageFlags src_stage;
VkPipelineStageFlags dst_stage;
if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED &&
new_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
// 准备接收数据
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
src_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
dst_stage = VK_PIPELINE_STAGE_TRANSFER_BIT;
} else if (old_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL &&
new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
// 传输完成,准备着色器读取
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
src_stage = VK_PIPELINE_STAGE_TRANSFER_BIT;
dst_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
} else if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED &&
new_layout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) {
// 准备作为颜色附件
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
src_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
dst_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
} else {
KERROR("Unsupported layout transition!");
return;
}
vkCmdPipelineBarrier(
cmd,
src_stage, dst_stage,
0,
0, NULL,
0, NULL,
1, &barrier
);
end_single_time_commands(context, cmd);
}
🎬 渲染到纹理
Framebuffer设置
创建使用自定义纹理的 Framebuffer:
/**
* @brief 创建渲染到纹理的 Framebuffer
*/
framebuffer* create_render_to_texture_framebuffer(
texture* color_target,
texture* depth_target
) {
framebuffer_create_info fb_info = {0};
fb_info.width = color_target->width;
fb_info.height = color_target->height;
fb_info.attachment_count = depth_target ? 2 : 1;
// 颜色附件
fb_info.attachments[0].texture = color_target;
fb_info.attachments[0].type = RENDERPASS_ATTACHMENT_COLOR;
fb_info.attachments[0].load_op = RENDERPASS_ATTACHMENT_LOAD_OP_CLEAR;
fb_info.attachments[0].store_op = RENDERPASS_ATTACHMENT_STORE_OP_STORE;
// 深度附件 (可选)
if (depth_target) {
fb_info.attachments[1].texture = depth_target;
fb_info.attachments[1].type = RENDERPASS_ATTACHMENT_DEPTH;
fb_info.attachments[1].load_op = RENDERPASS_ATTACHMENT_LOAD_OP_CLEAR;
fb_info.attachments[1].store_op = RENDERPASS_ATTACHMENT_STORE_OP_DONT_CARE;
}
framebuffer* fb = framebuffer_create(&fb_info);
return fb;
}
渲染流程
完整的渲染到纹理流程:
/**
* @brief 渲染场景到纹理
*/
void render_scene_to_texture(
texture* render_target,
camera* cam,
scene* scene_data
) {
// 1. 创建 Framebuffer (如果还没有)
static framebuffer* fb = NULL;
if (!fb) {
texture* depth = create_render_target(
render_target->width,
render_target->height,
TEXTURE_FORMAT_DEPTH32F,
true
);
fb = create_render_to_texture_framebuffer(render_target, depth);
}
// 2. 开始渲染通道
renderpass_begin_info begin_info = {0};
begin_info.framebuffer = fb;
begin_info.clear_color = (vec4){0.1f, 0.1f, 0.1f, 1.0f};
begin_info.clear_depth = 1.0f;
renderer_begin_renderpass(&begin_info);
// 3. 设置视口和裁剪
renderer_set_viewport(0, 0, render_target->width, render_target->height);
renderer_set_scissor(0, 0, render_target->width, render_target->height);
// 4. 绑定着色器
shader* scene_shader = shader_system_get("Builtin.MaterialShader");
shader_bind(scene_shader);
// 5. 设置相机
mat4 view = camera_get_view(cam);
mat4 projection = camera_get_projection(cam);
shader_set_uniform(scene_shader, "view", &view);
shader_set_uniform(scene_shader, "projection", &projection);
// 6. 绘制场景
for (u32 i = 0; i < scene_data->mesh_count; ++i) {
mesh* m = scene_data->meshes[i];
// 设置模型矩阵
mat4 model = mesh_get_world_transform(m);
shader_set_uniform(scene_shader, "model", &model);
// 绑定材质
material_system_apply(m->material, scene_shader);
// 绘制
geometry_draw(m->geometry);
}
// 7. 结束渲染通道
renderer_end_renderpass();
// 现在 render_target 包含渲染的场景!
}
读回纹理
从 GPU 读取纹理数据到 CPU:
/**
* @brief 从纹理读取数据到 CPU
* @param tex 源纹理
* @param mip_level Mipmap 级别
* @param out_data 输出缓冲区 (必须预先分配)
* @param data_size 缓冲区大小
*/
b8 texture_read_data(
texture* tex,
u32 mip_level,
void* out_data,
u64 data_size
) {
// 检查用途标志
if (!(tex->usage & TEXTURE_USAGE_TRANSFER_SRC)) {
KERROR("texture_read_data: Texture not created with TRANSFER_SRC usage");
return false;
}
// Vulkan 实现
vulkan_context* context = &vulkan_state->context;
vulkan_texture* vk_tex = (vulkan_texture*)tex->internal_data;
// 1. 创建临时缓冲区 (CPU 可访问)
VkBuffer staging_buffer;
VkDeviceMemory staging_memory;
create_buffer(
context,
data_size,
VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
&staging_buffer,
&staging_memory
);
// 2. 转换图像布局
transition_image_layout(
context,
vk_tex->image,
vk_tex->format,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
tex->mip_levels
);
// 3. 复制图像到缓冲区
VkCommandBuffer cmd = begin_single_time_commands(context);
VkBufferImageCopy region = {0};
region.bufferOffset = 0;
region.bufferRowLength = 0;
region.bufferImageHeight = 0;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.mipLevel = mip_level;
region.imageSubresource.baseArrayLayer = 0;
region.imageSubresource.layerCount = 1;
region.imageOffset = (VkOffset3D){0, 0, 0};
region.imageExtent = (VkExtent3D){
tex->width >> mip_level,
tex->height >> mip_level,
1
};
vkCmdCopyImageToBuffer(
cmd,
vk_tex->image,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
staging_buffer,
1,
®ion
);
end_single_time_commands(context, cmd);
// 4. 恢复图像布局
transition_image_layout(
context,
vk_tex->image,
vk_tex->format,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
tex->mip_levels
);
// 5. 映射缓冲区并复制到 CPU
void* mapped_data;
vkMapMemory(context->device.logical_device, staging_memory, 0, data_size, 0, &mapped_data);
kcopy_memory(out_data, mapped_data, data_size);
vkUnmapMemory(context->device.logical_device, staging_memory);
// 6. 清理
vkDestroyBuffer(context->device.logical_device, staging_buffer, context->allocator);
vkFreeMemory(context->device.logical_device, staging_memory, context->allocator);
KDEBUG("Read %llu bytes from texture (mip level %u)", data_size, mip_level);
return true;
}
🎮 实际应用场景
1. 镜子/水面反射
// 渲染镜子反射
void render_mirror_reflection(mirror* m, scene* scene_data, camera* main_cam) {
// 1. 创建镜子纹理 (如果还没有)
if (!m->reflection_texture) {
m->reflection_texture = create_render_target(512, 512, TEXTURE_FORMAT_RGBA8, false);
}
// 2. 计算镜子相机 (反射主相机)
camera mirror_cam;
calculate_mirror_camera(&mirror_cam, main_cam, m->plane);
// 3. 渲染到镜子纹理
render_scene_to_texture(m->reflection_texture, &mirror_cam, scene_data);
// 4. 渲染镜子表面 (使用反射纹理)
shader* mirror_shader = shader_system_get("Mirror");
shader_bind(mirror_shader);
shader_bind_texture(mirror_shader, 0, m->reflection_texture);
geometry_draw(m->quad_geometry);
}
2. 阴影贴图
// 渲染阴影贴图
void render_shadow_map(directional_light* light, scene* scene_data) {
// 1. 创建阴影贴图 (高分辨率深度纹理)
if (!light->shadow_map) {
light->shadow_map = create_render_target(2048, 2048, TEXTURE_FORMAT_DEPTH32F, true);
}
// 2. 设置光源相机 (正交投影)
camera light_cam;
light_cam.position = vec3_mul_scalar(light->direction, -100.0f);
light_cam.projection = mat4_orthographic(-50, 50, -50, 50, 0.1f, 200.0f);
light_cam.view = mat4_look_at(light_cam.position, (vec3){0,0,0}, (vec3){0,1,0});
// 3. 渲染深度
render_scene_to_texture(light->shadow_map, &light_cam, scene_data);
// 4. 主渲染中使用阴影贴图
shader* main_shader = shader_system_get("Builtin.MaterialShader");
shader_bind_texture(main_shader, 3, light->shadow_map); // Binding 3 = shadow map
mat4 light_space_matrix = mat4_mul_mat4(light_cam.projection, light_cam.view);
shader_set_uniform(main_shader, "light_space_matrix", &light_space_matrix);
}
3. 后处理效果
// 后处理管线
void render_with_post_processing(scene* scene_data, camera* cam) {
// 1. 创建场景渲染目标
static texture* scene_texture = NULL;
if (!scene_texture) {
scene_texture = create_render_target(1920, 1080, TEXTURE_FORMAT_RGBA16F, false);
}
// 2. 渲染场景到纹理
render_scene_to_texture(scene_texture, cam, scene_data);
// 3. 应用后处理效果
post_process_bloom(scene_texture);
post_process_tone_mapping(scene_texture);
// 4. 最终输出到屏幕
renderer_begin_default_renderpass();
render_fullscreen_quad(scene_texture);
renderer_end_renderpass();
}
void post_process_bloom(texture* scene_tex) {
// 提取亮区
static texture* bright_tex = NULL;
if (!bright_tex) {
bright_tex = create_render_target(960, 540, TEXTURE_FORMAT_RGBA16F, false);
}
shader* bright_shader = shader_system_get("BrightPass");
render_fullscreen_with_shader(scene_tex, bright_tex, bright_shader);
// 模糊
static texture* blur_temp = NULL;
if (!blur_temp) {
blur_temp = create_render_target(960, 540, TEXTURE_FORMAT_RGBA16F, false);
}
shader* blur_shader = shader_system_get("GaussianBlur");
for (u32 i = 0; i < 5; ++i) {
// 水平模糊
blur_shader_set_direction(blur_shader, (vec2){1, 0});
render_fullscreen_with_shader(bright_tex, blur_temp, blur_shader);
// 垂直模糊
blur_shader_set_direction(blur_shader, (vec2){0, 1});
render_fullscreen_with_shader(blur_temp, bright_tex, blur_shader);
}
// 合并
shader* combine_shader = shader_system_get("BloomCombine");
shader_bind_texture(combine_shader, 0, scene_tex); // 原始场景
shader_bind_texture(combine_shader, 1, bright_tex); // 模糊的亮区
render_fullscreen_quad_with_shader(combine_shader);
}
❓ 常见问题
1. 何时使用可写纹理 vs 只读纹理?决策指南:
使用只读纹理 (默认):
✓ 从文件加载的贴图 (Diffuse, Normal, etc.)
✓ 静态环境贴图
✓ UI 图标和字体
✓ 不需要动态更新的纹理
使用可写纹理:
✓ 渲染目标 (后处理、阴影贴图)
✓ 动态环境贴图 (实时反射)
✓ 程序化纹理 (噪声、粒子)
✓ 需要 CPU 更新的纹理
✓ 镜子/传送门效果
内存和性能:
• 只读纹理: 更小的内存占用,稍快
• 可写纹理: 稍大的内存占用,性能通常无差异
• 现代 GPU 的差异通常可忽略
2. 渲染到纹理会影响性能吗?
性能影响分析:
渲染到纹理的成本:
┌────────────────────────────────────┐
│ 操作 │ 相对成本 │
├────────────────────────────────────┤
│ 创建纹理/Framebuffer │ 一次性 │
│ 切换 Framebuffer │ ~0.1 ms │
│ 清除纹理 │ ~0.05 ms │
│ 渲染场景 │ 取决于场景 │
│ 布局转换 │ ~0.01 ms │
└────────────────────────────────────┘
优化建议:
✓ 重用 Framebuffer (不要每帧创建)
✓ 降低渲染目标分辨率 (例如阴影贴图 2048x2048)
✓ 使用 LOD (远处物体用低分辨率渲染)
✓ 批量渲染 (减少 Framebuffer 切换)
✗ 避免频繁读回 CPU (很慢!)
实际案例:
• 阴影贴图 (2048x2048): ~1-2 ms
• 后处理 (1920x1080): ~0.5-1 ms
• 镜子反射 (512x512): ~0.3-0.5 ms
• 总开销: 通常 <10% 帧时间
3. 如何优化多个渲染目标?
优化策略:
// 1. 使用纹理数组 (减少切换)
texture* shadow_maps[4]; // 4 个光源
for (u32 i = 0; i < 4; ++i) {
shadow_maps[i] = create_render_target(1024, 1024, TEXTURE_FORMAT_DEPTH32F, true);
}
// 2. 重用深度缓冲
texture* shared_depth = create_render_target(1920, 1080, TEXTURE_FORMAT_DEPTH32F, true);
// 多个颜色附件可以共享同一个深度缓冲
// 3. 使用 MRT (Multiple Render Targets)
framebuffer_create_info fb_info = {0};
fb_info.attachment_count = 3;
fb_info.attachments[0] = color_rt; // Color
fb_info.attachments[1] = normal_rt; // Normals
fb_info.attachments[2] = position_rt; // Positions
// 一次渲染输出到多个纹理!
// 4. 延迟更新 (不是每帧)
if (frame_count % 10 == 0) {
// 每 10 帧更新一次环境贴图
render_environment_cubemap();
}
4. 纹理格式如何选择?
格式选择指南:
| 用途 | 推荐格式 | 原因 |
|---|---|---|
| 普通颜色 | RGBA8 | 标准,兼容性好 |
| HDR 颜色 | RGBA16F | 支持高动态范围 |
| 深度 | Depth32F | 高精度深度 |
| 法线贴图 | RGBA8 | 足够精度 |
| 单通道 | R8 或 R16F | 节省内存 |
| 阴影贴图 | Depth32F 或 Depth24Stencil8 | 取决于需求 |
内存占用:
1920x1080 纹理:
┌────────────────────────────────────┐
│ 格式 │ 每像素 │ 总内存 │
├────────────────────────────────────┤
│ RGBA8 │ 4 B │ 7.91 MB │
│ RGBA16F │ 8 B │ 15.82 MB │
│ Depth32F │ 4 B │ 7.91 MB │
│ R8 │ 1 B │ 1.98 MB │
└────────────────────────────────────┘
建议:
• 仅在需要时使用 HDR (16F/32F)
• 单通道纹理用 R8 而不是 RGBA8
• 移动设备考虑压缩格式 (ETC2, ASTC)
5. 如何实现屏幕截图功能?
屏幕截图实现:
/**
* @brief 截取屏幕
*/
void screenshot_capture(const char* filename) {
// 1. 获取当前 Swapchain 图像
texture* screen_tex = renderer_get_current_backbuffer();
// 2. 确保有 TRANSFER_SRC 用途
if (!(screen_tex->usage & TEXTURE_USAGE_TRANSFER_SRC)) {
KERROR("Backbuffer doesn't support TRANSFER_SRC");
return;
}
// 3. 读取像素数据
u32 width = screen_tex->width;
u32 height = screen_tex->height;
u64 data_size = width * height * 4; // RGBA8
u8* pixels = kallocate(data_size, MEMORY_TAG_TEMP);
texture_read_data(screen_tex, 0, pixels, data_size);
// 4. 保存为 PNG
stbi_write_png(filename, width, height, 4, pixels, width * 4);
kfree(pixels, data_size, MEMORY_TAG_TEMP);
KINFO("Screenshot saved: %s", filename);
}
// 使用
if (input_is_key_down(KEY_F12)) {
screenshot_capture("screenshot.png");
}
📝 练习
练习 1: 实现简单的后处理效果任务: 实现灰度和反色后处理效果。
// Grayscale Shader (GLSL)
// grayscale.frag
#version 450
in vec2 in_texcoord;
uniform sampler2D u_scene_texture;
out vec4 out_color;
void main() {
vec4 color = texture(u_scene_texture, in_texcoord);
// 灰度转换 (Luminance)
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
out_color = vec4(vec3(gray), color.a);
}
// Invert Shader (GLSL)
// invert.frag
#version 450
in vec2 in_texcoord;
uniform sampler2D u_scene_texture;
out vec4 out_color;
void main() {
vec4 color = texture(u_scene_texture, in_texcoord);
// 反色
out_color = vec4(1.0 - color.rgb, color.a);
}
// C 代码
void render_with_post_process(post_process_type type) {
// 1. 渲染场景到纹理
static texture* scene_rt = NULL;
if (!scene_rt) {
scene_rt = create_render_target(1920, 1080, TEXTURE_FORMAT_RGBA8, false);
}
render_scene_to_texture(scene_rt, main_camera, current_scene);
// 2. 应用后处理
shader* post_shader = NULL;
switch (type) {
case POST_PROCESS_GRAYSCALE:
post_shader = shader_system_get("Grayscale");
break;
case POST_PROCESS_INVERT:
post_shader = shader_system_get("Invert");
break;
default:
post_shader = shader_system_get("Passthrough");
}
// 3. 渲染到屏幕
renderer_begin_default_renderpass();
shader_bind(post_shader);
shader_bind_texture(post_shader, 0, scene_rt);
render_fullscreen_quad();
renderer_end_renderpass();
}
练习 2: 实现动态 Cubemap
任务: 创建动态环境贴图用于实时反射。
/**
* @brief 动态 Cubemap
*/
typedef struct dynamic_cubemap {
texture* cubemap; // 6 个面的 Cubemap
camera cameras[6]; // 6 个方向的相机
vec3 position; // Cubemap 中心位置
b8 needs_update; // 是否需要更新
} dynamic_cubemap;
/**
* @brief 创建动态 Cubemap
*/
dynamic_cubemap* dynamic_cubemap_create(vec3 position, u32 resolution) {
dynamic_cubemap* dcm = kallocate(sizeof(dynamic_cubemap), MEMORY_TAG_RENDERER);
// 1. 创建 Cubemap 纹理
texture_create_info create_info = {0};
create_info.width = resolution;
create_info.height = resolution;
create_info.format = TEXTURE_FORMAT_RGBA8;
create_info.usage = TEXTURE_USAGE_SAMPLED | TEXTURE_USAGE_COLOR_ATTACHMENT;
create_info.type = TEXTURE_TYPE_CUBE;
create_info.array_layers = 6; // 6 faces
dcm->cubemap = texture_system_create(&create_info);
dcm->position = position;
// 2. 设置 6 个相机
// +X, -X, +Y, -Y, +Z, -Z
vec3 directions[6] = {
{1, 0, 0}, {-1, 0, 0}, // ±X
{0, 1, 0}, {0, -1, 0}, // ±Y
{0, 0, 1}, {0, 0, -1} // ±Z
};
vec3 ups[6] = {
{0, -1, 0}, {0, -1, 0}, // +X, -X
{0, 0, 1}, {0, 0, -1}, // +Y, -Y
{0, -1, 0}, {0, -1, 0} // +Z, -Z
};
for (u32 i = 0; i < 6; ++i) {
dcm->cameras[i].position = position;
dcm->cameras[i].forward = directions[i];
dcm->cameras[i].up = ups[i];
dcm->cameras[i].projection = mat4_perspective(deg_to_rad(90.0f), 1.0f, 0.1f, 100.0f);
}
dcm->needs_update = true;
return dcm;
}
/**
* @brief 更新动态 Cubemap
*/
void dynamic_cubemap_update(dynamic_cubemap* dcm, scene* scene_data) {
if (!dcm->needs_update) return;
// 渲染 6 个面
for (u32 face = 0; face < 6; ++face) {
// 创建 Framebuffer (每个面)
framebuffer* fb = create_cubemap_face_framebuffer(dcm->cubemap, face);
// 渲染到该面
render_scene_to_texture_with_framebuffer(
fb,
&dcm->cameras[face],
scene_data
);
framebuffer_destroy(fb);
}
dcm->needs_update = false;
}
// 使用
dynamic_cubemap* env_cubemap = dynamic_cubemap_create((vec3){0, 1, 0}, 512);
// 每帧或定期更新
if (frame_count % 30 == 0) { // 每 0.5 秒更新
dynamic_cubemap_update(env_cubemap, current_scene);
}
// 在材质中使用
shader_bind_cubemap(material_shader, 4, env_cubemap->cubemap);
练习 3: 实现基础阴影贴图
任务: 实现方向光的阴影贴图。
/**
* @brief 阴影贴图系统
*/
typedef struct shadow_map {
texture* depth_texture; // 深度纹理
framebuffer* framebuffer; // Framebuffer
mat4 light_space_matrix; // 光源空间矩阵
u32 resolution; // 分辨率
} shadow_map;
/**
* @brief 创建阴影贴图
*/
shadow_map* shadow_map_create(u32 resolution) {
shadow_map* sm = kallocate(sizeof(shadow_map), MEMORY_TAG_RENDERER);
sm->resolution = resolution;
// 创建深度纹理
sm->depth_texture = create_render_target(
resolution,
resolution,
TEXTURE_FORMAT_DEPTH32F,
true
);
// 创建 Framebuffer
framebuffer_create_info fb_info = {0};
fb_info.width = resolution;
fb_info.height = resolution;
fb_info.attachment_count = 1;
fb_info.attachments[0].texture = sm->depth_texture;
fb_info.attachments[0].type = RENDERPASS_ATTACHMENT_DEPTH;
sm->framebuffer = framebuffer_create(&fb_info);
return sm;
}
/**
* @brief 渲染阴影贴图
*/
void shadow_map_render(
shadow_map* sm,
directional_light* light,
scene* scene_data
) {
// 1. 计算光源空间矩阵
vec3 light_pos = vec3_mul_scalar(light->direction, -50.0f);
mat4 light_view = mat4_look_at(light_pos, (vec3){0,0,0}, (vec3){0,1,0});
mat4 light_proj = mat4_orthographic(-20, 20, -20, 20, 0.1f, 100.0f);
sm->light_space_matrix = mat4_mul_mat4(light_proj, light_view);
// 2. 开始渲染通道
renderer_begin_renderpass_with_framebuffer(sm->framebuffer);
// 3. 设置视口
renderer_set_viewport(0, 0, sm->resolution, sm->resolution);
// 4. 绑定深度着色器
shader* depth_shader = shader_system_get("DepthOnly");
shader_bind(depth_shader);
shader_set_uniform(depth_shader, "light_space_matrix", &sm->light_space_matrix);
// 5. 渲染场景 (仅深度)
for (u32 i = 0; i < scene_data->mesh_count; ++i) {
mesh* m = scene_data->meshes[i];
mat4 model = mesh_get_world_transform(m);
shader_set_uniform(depth_shader, "model", &model);
geometry_draw(m->geometry);
}
// 6. 结束渲染通道
renderer_end_renderpass();
}
// Depth-Only Shader (GLSL)
// depth_only.vert
#version 450
layout(location = 0) in vec3 in_position;
uniform mat4 light_space_matrix;
uniform mat4 model;
void main() {
gl_Position = light_space_matrix * model * vec4(in_position, 1.0);
}
// depth_only.frag (空,只写入深度)
#version 450
void main() {
// 深度自动写入
}
// 主渲染中使用阴影
shader* main_shader = shader_system_get("Builtin.MaterialShader");
shader_bind_texture(main_shader, 3, shadow_map->depth_texture);
shader_set_uniform(main_shader, "light_space_matrix", &shadow_map->light_space_matrix);
// Fragment Shader 中采样阴影
float shadow = texture(u_shadow_map, shadow_coord.xy).r;
float current_depth = shadow_coord.z;
float in_shadow = current_depth > shadow + 0.005 ? 1.0 : 0.0;
恭喜!你已经掌握了可写纹理系统! 🎉
Tutorial written by 上手实验室
1555

被折叠的 条评论
为什么被折叠?



