理论基础
3D纹理(也称为体积纹理)是纹理映射的扩展,从2D平面扩展到3D空间。与2D纹理不同,3D纹理在三个维度上存储数据(宽度、高度和深度),允许在整个3D空间中采样,而不仅仅是在平面上。
3D纹理的主要特性和用途:
- 体积数据表示:用于表示完整的3D数据集,如医学扫描(CT、MRI)、气象数据等
- 空间采样:允许在3D空间中的任意位置进行纹理采样
- 层次细节:支持类似2D纹理的MipMap功能,但在三维空间中
- 程序化噪声:用于创建自然现象如云、烟雾、火焰等效果
- 体积渲染技术:用于光线投射、体积密度等高级渲染技术
代码实现
下面是一个使用GLFW和GLAD实现3D纹理的详细示例,包括如何创建、填充和使用3D纹理:
cpp
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <cmath>
#include <vector>
// 窗口尺寸
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
// 着色器程序
unsigned int shaderProgram;
// 3D纹理ID
unsigned int texture3D;
// 控制参数
float slicePosition = 0.5f; // 切片位置 (0.0 - 1.0)
bool animateSlice = true; // 是否动画切片位置
// 顶点着色器源码
const char *vertexShaderSource = R"(
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aTexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
out vec3 TexCoord;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
TexCoord = aTexCoord;
}
)";
// 片段着色器源码
const char *fragmentShaderSource = R"(
#version 330 core
out vec4 FragColor;
in vec3 TexCoord;
uniform sampler3D texture3D;
uniform float slicePosition; // 在特定的z位置进行切片
void main()
{
// 基本的3D纹理采样
vec4 texColor = texture(texture3D, vec3(TexCoord.xy, slicePosition));
// 可视化深度值
float depthValue = abs(TexCoord.z - slicePosition);
float alpha = 1.0 - smoothstep(0.0, 0.01, depthValue); // 在切片附近设置透明度
// 最终颜色
FragColor = vec4(texColor.rgb, texColor.a * alpha);
}
)";
// 编译着色器
unsigned int compileShader(const char* source, GLenum type) {
unsigned int shader = glCreateShader(type);
glShaderSource(shader, 1, &source, NULL);
glCompileShader(shader);
int success;
char infoLog[512];
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(shader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::COMPILATION_FAILED\n" << infoLog << std::endl;
}
return shader;
}
// 创建着色器程序
unsigned int createShaderProgram(const char* vertexSource, const char* fragmentSource) {
unsigned int vertexShader = compileShader(vertexSource, GL_VERTEX_SHADER);
unsigned int fragmentShader = compileShader(fragmentSource, GL_FRAGMENT_SHADER);
unsigned int program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
int success;
char infoLog[512];
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(program, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return program;
}
// 创建3D纹理
void create3DTexture() {
// 3D纹理的尺寸
const int width = 64;
const int height = 64;
const int depth = 64;
// 创建纹理数据 - 这里我们创建一个程序化的3D噪声纹理
std::vector<unsigned char> textureData(width * height * depth * 4); // RGBA格式
// 生成3D噪声数据
for (int z = 0; z < depth; z++) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// 计算当前体素的索引
int index = (z * width * height + y * width + x) * 4;
// 归一化坐标 (0.0 - 1.0)
float nx = static_cast<float>(x) / width;
float ny = static_cast<float>(y) / height;
float nz = static_cast<float>(z) / depth;
// 简单的3D噪声函数
float noise = 0.5f + 0.5f * sin(nx * 10) * sin(ny * 10) * sin(nz * 10);
// 添加一些距离场效果
float centerX = 0.5f;
float centerY = 0.5f;
float centerZ = 0.5f;
float distance = std::sqrt(
(nx - centerX) * (nx - centerX) +
(ny - centerY) * (ny - centerY) +
(nz - centerZ) * (nz - centerZ)
);
// 创建一个球体
float sphereRadius = 0.35f;
float sphereValue = 1.0f - smoothstep(sphereRadius - 0.05f, sphereRadius + 0.05f, distance);
// 混合噪声和球体效果
float finalValue = noise * sphereValue;
// 设置颜色,基于位置和噪声值
textureData[index + 0] = static_cast<unsigned char>((nx * finalValue) * 255); // R
textureData[index + 1] = static_cast<unsigned char>((ny * finalValue) * 255); // G
textureData[index + 2] = static_cast<unsigned char>((nz * finalValue) * 255); // B
textureData[index + 3] = static_cast<unsigned char>(finalValue * 255); // A
}
}
}
// 创建OpenGL纹理对象
glGenTextures(1, &texture3D);
glBindTexture(GL_TEXTURE_3D, texture3D);
// 设置纹理参数
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);
// 上传纹理数据
glTexImage3D(
GL_TEXTURE_3D,
0, // Mipmap级别
GL_RGBA, // 内部格式
width, // 宽度
height, // 高度
depth, // 深度
0, // 边框
GL_RGBA, // 数据格式
GL_UNSIGNED_BYTE, // 数据类型
textureData.data() // 数据指针
);
// 生成Mipmap
glGenerateMipmap(GL_TEXTURE_3D);
// 解绑纹理
glBindTexture(GL_TEXTURE_3D, 0);
std::cout << "3D Texture created with dimensions: " << width << "x" << height << "x" << depth << std::endl;
}
// 创建矩阵结构体和辅助函数
struct Matrix4 {
float m[16];
Matrix4() {
// 初始化为单位矩阵
for (int i = 0; i < 16; i++)
m[i] = 0.0f;
m[0] = m[5] = m[10] = m[15] = 1.0f;
}
// 简单的矩阵乘法
Matrix4 operator*(const Matrix4& other) {
Matrix4 result;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
result.m[i*4+j] = 0.0f;
for (int k = 0; k < 4; k++) {
result.m[i*4+j] += m[i*4+k] * other.m[k*4+j];
}
}
}
return result;
}
};
// 创建平移矩阵
Matrix4 translate(float x, float y, float z) {
Matrix4 result;
result.m[12] = x;
result.m[13] = y;
result.m[14] = z;
return result;
}
// 创建旋转矩阵 (绕各轴旋转)
Matrix4 rotate(float angle, float x, float y, float z) {
Matrix4 result;
float c = cos(angle);
float s = sin(angle);
float t = 1.0f - c;
// 归一化轴向量
float len = sqrt(x*x + y*y + z*z);
if (len > 0.0001f) {
x /= len;
y /= len;
z /= len;
}
result.m[0] = t*x*x + c;
result.m[1] = t*x*y + s*z;
result.m[2] = t*x*z - s*y;
result.m[4] = t*x*y - s*z;
result.m[5] = t*y*y + c;
result.m[6] = t*y*z + s*x;
result.m[8] = t*x*z + s*y;
result.m[9] = t*y*z - s*x;
result.m[10] = t*z*z + c;
return result;
}
// 创建透视投影矩阵
Matrix4 perspective(float fov, float aspect, float near, float far) {
Matrix4 result;
float tanHalfFov = tan(fov / 2.0f);
result.m[0] = 1.0f / (aspect * tanHalfFov);
result.m[5] = 1.0f / tanHalfFov;
result.m[10] = -(far + near) / (far - near);
result.m[11] = -1.0f;
result.m[14] = -(2.0f * far * near) / (far - near);
result.m[15] = 0.0f;
return result;
}
// 键盘回调函数处理控制
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) {
if (action == GLFW_PRESS) {
switch (key) {
case GLFW_KEY_SPACE:
// 切换切片动画
animateSlice = !animateSlice;
std::cout << "Slice Animation: " << (animateSlice ? "Enabled" : "Disabled") << std::endl;
break;
case GLFW_KEY_UP:
// 增加切片位置
slicePosition = std::min(slicePosition + 0.05f, 1.0f);
std::cout << "Slice Position: " << slicePosition << std::endl;
break;
case GLFW_KEY_DOWN:
// 减少切片位置
slicePosition = std::max(slicePosition - 0.05f, 0.0f);
std::cout << "Slice Position: " << slicePosition << std::endl;
break;
}
}
}
int main() {
// 初始化GLFW
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// 创建窗口
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "3D Texture Example", NULL, NULL);
if (window == NULL) {
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetKeyCallback(window, key_callback);
// 初始化GLAD
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// 设置视口
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
// 创建着色器程序
shaderProgram = createShaderProgram(vertexShaderSource, fragmentShaderSource);
// 创建3D纹理
create3DTexture();
// 启用深度测试和Alpha混合
glEnable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// 定义一个立方体,用于显示3D纹理
// 立方体的每个顶点包含位置和纹理坐标 (x,y,z, tx,ty,tz)
float vertices[] = {
// 前面 (z = 1.0)
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
// 后面 (z = -1.0)
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
// 左面 (x = -0.5)
-0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f,
// 右面 (x = 0.5)
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
// 底面 (y = -0.5)
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
// 顶面 (y = 0.5)
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f
};
// 设置VAO和VBO
unsigned int VAO, VBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 纹理坐标属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
// 解绑
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// 输出控制说明
std::cout << "Controls:" << std::endl;
std::cout << " SPACE - Toggle slice animation" << std::endl;
std::cout << " UP/DOWN - Manually adjust slice position" << std::endl;
// 渲染循环
while (!glfwWindowShouldClose(window)) {
// 处理输入
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
// 更新切片位置(如果动画启用)
if (animateSlice) {
float time = (float)glfwGetTime();
slicePosition = 0.5f + 0.5f * sin(time * 0.5f);
}
// 清空颜色和深度缓冲
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 激活着色器
glUseProgram(shaderProgram);
// 创建变换矩阵
float time = (float)glfwGetTime();
// 模型矩阵 - 旋转立方体
Matrix4 model = rotate(time * 0.5f, 0.0f, 1.0f, 0.0f) * rotate(time * 0.3f, 1.0f, 0.0f, 0.0f);
// 视图矩阵 - 向后移动相机
Matrix4 view = translate(0.0f, 0.0f, -2.0f);
// 投影矩阵
float fov = 45.0f * 3.14159f / 180.0f; // 转换为弧度
float aspect = (float)SCR_WIDTH / (float)SCR_HEIGHT;
Matrix4 projection = perspective(fov, aspect, 0.1f, 100.0f);
// 传递变换矩阵到着色器
glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "model"), 1, GL_FALSE, model.m);
glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "view"), 1, GL_FALSE, view.m);
glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "projection"), 1, GL_FALSE, projection.m);
// 绑定3D纹理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_3D, texture3D);
glUniform1i(glGetUniformLocation(shaderProgram, "texture3D"), 0);
// 传递切片位置
glUniform1f(glGetUniformLocation(shaderProgram, "slicePosition"), slicePosition);
// 绘制立方体
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
// 交换缓冲并检查事件
glfwSwapBuffers(window);
glfwPollEvents();
}
// 清理资源
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteTextures(1, &texture3D);
glDeleteProgram(shaderProgram);
glfwTerminate();
return 0;
}
代码解析
这个示例实现了一个完整的3D纹理渲染系统,包含以下关键组件:
-
3D纹理创建与填充:
- 使用
glTexImage3D
函数创建3D纹理 - 程序化生成纹理数据,创建一个包含噪声和球体效果的体积数据
- 设置纹理参数和生成Mipmap,与2D纹理类似但多了一个R维度的设置
- 使用
-
着色器实现:
- 顶点着色器传递3D纹理坐标
- 片段着色器使用
sampler3D
采样3D纹理,基于插值的纹理坐标 - 实现了一个简单的"切片"效果,只显示特定深度层的纹理数据
-
交互控制:
- 空格键切换切片位置的动画
- 上下箭头键手动调整切片位置,观察不同深度处的纹理数据
-
渲染技术:
- 使用立方体作为3D纹理的可视化容器
- 应用Alpha混合显示半透明效果
- 使用深度测试确保正确的渲染顺序
3D纹理的高级应用
-
体积渲染:
- 医学成像:显示3D扫描数据,如CT和MRI
- 科学可视化:显示密度、温度等体积数据
- 地形分析:显示地下结构、地质层
-
程序化纹理和特效:
- 云、烟、雾等体积效果
- 3D噪声图和程序化体积纹理
- 颗粒材质和多孔物质的模拟
-
高级渲染技术:
- 光线行进(Ray Marching):通过3D纹理计算光线吸收和散射
- 隐式曲面:使用3D纹理定义体积密度函数
- 体积光照:通过3D纹理计算体积内的光线行为
-
优化技术:
- 多分辨率3D纹理:类似于2D纹理的Mipmap
- 块压缩:减少内存占用
- 光线采样优化:根据视角和重要性调整采样率
性能注意事项
-
内存占用:
- 3D纹理可能占用大量内存 (width × height × depth × 字节数/像素)
- 较大的3D纹理可能需要压缩或分块处理
-
采样性能:
- 3D纹理采样比2D纹理采样更昂贵
- 在片段着色器中过度采样可能导致性能问题
-
缓存优化:
- 考虑3D纹理的内存布局以优化缓存使用
- 连续访问同一区域的纹理以提高缓存命中率
-
功能支持:
- 确保目标硬件和驱动程序支持所需的3D纹理功能
- 较老的硬件可能有3D纹理大小或功能的限制
这个示例提供了一个坚实的3D纹理基础,可以根据特定需求进一步扩展和优化。例如,可以实现更复杂的体积渲染算法、添加光线行进技术或创建更复杂的程序化3D纹理。