图形学从入门到精通:Shader编程与性能优化实战指南
## 前言:为什么选择图形学?
图形学是计算机科学的皇冠上的明珠,它融合了数学、物理、编程和艺术,是现代游戏、影视特效、数据可视化、AR/VR等领域的核心技术。从《赛博朋克2077》的令人惊叹的画面到《阿凡达》的视觉效果,从Unity/Unreal引擎的实时渲染到Blender的建模渲染,图形学无处不在。
但图形学的学习曲线陡峭,很多初学者在复杂的数学公式和晦涩的API面前望而却步。实际上,图形学的核心思想非常直观:**如何用计算机画出你看到的、想象的世界**。当你理解了光与物质的相互作用、数学与视觉的对应关系,图形学就会变得生动有趣。
读完这篇博客,你不仅能够掌握图形学的理论基础,更能够编写复杂的Shader,理解GPU渲染管线,进行性能优化,真正从入门走向精通。
---
## 第一章:图形学基础概念
### 1.1 什么是图形学?
计算机图形学是研究如何用计算机生成、处理、显示图形的学科。它涉及数学、物理、计算机科学等多个领域,主要研究内容包括:
- **建模**:如何用数学方式描述三维物体
- **渲染**:如何将三维场景绘制到二维屏幕
- **动画**:如何让静态图形动起来
- **交互**:如何让用户与图形进行交互
### 1.2 渲染管线基础
渲染管线是图形学的核心概念,它描述了从3D场景到2D图像的转换过程。
#### OpenGL渲染管线流程
```glsl
// 顶点着色器(Vertex Shader)
#version 330 core
layout (location = 0) in vec3 aPos; // 顶点位置(模型局部坐标)
layout (location = 1) in vec3 aColor; // 顶点颜色
out vec3 ourColor; // 将顶点颜色作为输出传递到下一阶段,经过插值后输入片段着色器。
uniform mat4 model; // 模型变换:局部坐标 → 世界坐标
uniform mat4 view; // 视图变换:世界坐标 → 观察空间(摄像机视角)
uniform mat4 projection; // 投影变换:观察空间 → 裁剪空间(透视或正交)
void main() {
// 裁剪空间坐标(四维向量,w分量用于后续透视除法)。
gl_Position = projection * view * model * vec4(aPos, 1.0);
ourColor = aColor;
}
// 片段着色器(Fragment Shader)
#version 330 core
out vec4 FragColor;
in vec3 ourColor; // 从顶点着色器经过插值后的颜色
void main() {
FragColor = vec4(ourColor, 1.0); // 直接输出颜色,alpha = 1.0
}
```
1、OpenGL渲染管线中最核心的两个可编程阶段:顶点着色器和片段着色器。
2、OpenGL渲染管线流程:
```
顶点数据 → 顶点着色器 → 图元装配 → 光栅化 → 片段着色器 → 测试与混合 → 帧缓冲区
```
3.1、顶点着色器(Vertex Shader):处理每个顶点的数据,完成坐标变换和顶点属性传递。
3.2、图元装配(Primitive Assembly):
```
- 将顶点重新组织成图元(点、线、三角形等)
- 执行视锥体裁剪:丢弃裁剪空间外的图元
- 透视除法:(x/w, y/w, z/w) 转换到NDC(归一化设备坐标,范围[-1,1])
- 视口变换:NDC映射到屏幕坐标
```
3.3、光栅化(Rasterization):
```
- 将图元分解成片段(Fragments)(候选像素)
- 对顶点着色器的输出(如 ourColor)进行插值,每个片段获得自己的颜色值
```
3.4、片段着色器(Fragment Shader):计算每个片段的最终颜色(或深度值)。
3.5、逐片段操作(Per-Fragment Operations):
```
- 深度测试:比较当前片段的深度与深度缓冲区,决定是否丢弃
- 模板测试:基于模板缓冲区进行条件丢弃
- 混合:将片段颜色与帧缓冲区已有颜色混合(需开启透明度)
```
3.6、最终颜色写入帧缓冲区,显示到屏幕。
#### 实例:创建第一个三角形
```cpp
// C++ OpenGL 示例
#include
#include
#include
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
// 着色器源码
const char* vertexShaderSource = R"(
#version 330 core
layout (location = 0) in vec3 aPos;
void main() {
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
)";
const char* fragmentShaderSource = R"(
#version 330 core
out vec4 FragColor;
void main() {
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
)";
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(800, 600, "LearnOpenGL", NULL, NULL);
if (window == NULL) {
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// 初始化GLEW
if (glewInit() != GLEW_OK) {
std::cout << "Failed to initialize GLEW" << std::endl;
return -1;
}
// 编译顶点着色器
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
// 编译片段着色器
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
// 创建着色器程序
unsigned int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// 删除着色器对象
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// 设置顶点数据
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
// 绑定VAO
glBindVertexArray(VAO);
// 绑定VBO并设置数据
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 解绑
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// 渲染循环
while (!glfwWindowShouldClose(window)) {
// 输入处理
processInput(window);
// 清屏
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 绘制三角形
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
// 交换缓冲区
glfwSwapBuffers(window);
glfwPollEvents();
}
// 清理资源
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProgram);
glfwTerminate();
return 0;
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
glViewport(0, 0, width, height);
}
void processInput(GLFWwindow* window) {
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
```
---
## 第二章:数学基础
### 2.1 向量数学
向量是图形学中最基础的数学工具,用于表示位置、方向、速度等。
#### 向量操作Shader实现
```glsl
// 向量常用操作
vec3 normalize(vec3 v) {
float len = length(v);
return len > 0.0 ? v / len : vec3(0.0);
}
float dot(vec3 a, vec3 b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
vec3 cross(vec3 a, vec3 b) {
return vec3(
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x
);
}
vec3 reflect(vec3 I, vec3 N) {
return I - 2.0 * dot(N, I) * N;
}
vec3 refract(vec3 I, vec3 N, float eta) {
float cosi = dot(I, N);
float sin2_t = eta * eta * (1.0 - cosi * cosi);
if (sin2_t > 1.0) {
return vec3(0.0); // 全反射
}
float cos_t = sqrt(1.0 - sin2_t);
return eta * I - (eta * cosi + cos_t) * N;
}
```
### 2.2 矩阵变换
矩阵用于表示变换操作,如平移、旋转、缩放。
#### 矩阵变换Shader实现
```glsl
// 平移矩阵
mat4 translate(vec3 translation) {
return mat4(
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
translation.x, translation.y, translation.z, 1.0
);
}
// 缩放矩阵
mat4 scale(vec3 scale) {
return mat4(
scale.x, 0.0, 0.0, 0.0,
0.0, scale.y, 0.0, 0.0,
0.0, 0.0, scale.z, 0.0,
0.0, 0.0, 0.0, 1.0
);
}
// 旋转矩阵(绕Y轴)
mat4 rotateY(float angle) {
float c = cos(angle);
float s = sin(angle);
return mat4(
c, 0.0, s, 0.0,
0.0, 1.0, 0.0, 0.0,
-s, 0.0, c, 0.0,
0.0, 0.0, 0.0, 1.0
);
}
// 视图矩阵
mat4 lookAt(vec3 eye, vec3 center, vec3 up) {
vec3 z_axis = normalize(eye - center);
vec3 x_axis = normalize(cross(up, z_axis));
vec3 y_axis = cross(z_axis, x_axis);
return mat4(
x_axis.x, y_axis.x, z_axis.x, 0.0,
x_axis.y, y_axis.y, z_axis.y, 0.0,
x_axis.z, y_axis.z, z_axis.z, 0.0,
-dot(x_axis, eye), -dot(y_axis, eye), -dot(z_axis, eye), 1.0
);
}
// 投影矩阵
mat4 perspective(float fov, float aspect, float near, float far) {
float tan_half_fov = tan(fov / 2.0);
return mat4(
1.0 / (aspect * tan_half_fov), 0.0, 0.0, 0.0,
0.0, 1.0 / tan_half_fov, 0.0, 0.0,
0.0, 0.0, - (far + near) / (far - near), -1.0,
0.0, 0.0, - (2.0 * far * near) / (far - near), 0.0
);
}
```
### 2.3 坐标系变换
图形学涉及多个坐标系,需要理解它们之间的转换关系。
#### 完整的坐标变换Shader
```glsl
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoord;
out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main() {
// 模型矩阵变换(局部空间 -> 世界空间)
vec4 worldPos = model * vec4(aPos, 1.0);
FragPos = worldPos.xyz;
// 法线矩阵(考虑非均匀缩放)
mat3 normalMatrix = transpose(inverse(mat3(model)));
Normal = normalize(normalMatrix * aNormal);
// 纹理坐标传递
TexCoord = aTexCoord;
// 完整的变换(局部空间 -> 裁剪空间)
gl_Position = projection * view * worldPos;
}
```
---
## 第三章:光照模型
### 3.1 Phong光照模型
Phong模型是经典的光照模型,包含环境光、漫反射和镜面反射三个分量。
```glsl
// Phong光照模型实现
struct Light {
vec3 position;
vec3 color;
float intensity;
};
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
vec3 calculatePhongLight(vec3 normal, vec3 fragPos, vec3 viewPos,
Light light, Material material) {
// 环境光分量
vec3 ambient = material.ambient * light.color * light.intensity * 0.1;
// 漫反射分量
vec3 lightDir = normalize(light.position - fragPos);
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = material.diffuse * light.color * light.intensity * diff;
// 镜面反射分量
vec3 viewDir = normalize(viewPos - fragPos);
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = material.specular * light.color * light.intensity * spec;
return ambient + diffuse + specular;
}
// 多光源光照计算
vec3 calculateMultipleLights(vec3 normal, vec3 fragPos, vec3 viewPos,
Light lights[4], int lightCount, Material material) {
vec3 result = vec3(0.0);
for (int i = 0; i < lightCount; i++) {
result += calculatePhongLight(normal, fragPos, viewPos, lights[i], material);
}
return result;
}
```
### 3.2 Blinn-Phong光照模型
Blinn-Phong模型是Phong模型的改进版本,使用半程向量代替反射向量,性能更好。
```glsl
// Blinn-Phong光照模型
vec3 calculateBlinnPhong(vec3 normal, vec3 fragPos, vec3 viewPos,
Light light, Material material) {
// 环境光
vec3 ambient = material.ambient * light.color * light.intensity * 0.1;
// 漫反射
vec3 lightDir = normalize(light.position - fragPos);
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = material.diffuse * light.color * light.intensity * diff;
// 镜面反射(使用半程向量)
vec3 viewDir = normalize(viewPos - fragPos);
vec3 halfwayDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(normal, halfwayDir), 0.0), material.shininess);
vec3 specular = material.specular * light.color * light.intensity * spec;
return ambient + diffuse + specular;
}
```
### 3.3 点光源和聚光灯
```glsl
// 点光源衰减计算
float calculateAttenuation(float distance, float constant, float linear, float quadratic) {
return 1.0 / (constant + linear * distance + quadratic * distance * distance);
}
// 聚光灯计算
float calculateSpotlight(vec3 lightPos, vec3 fragPos, vec3 spotDir,
float cutOff, float outerCutOff) {
vec3 lightDir = normalize(lightPos - fragPos);
float theta = dot(lightDir, normalize(-spotDir));
if (theta > cutOff) {
// 内部锥体,柔和边缘
float epsilon = cutOff - outerCutOff;
float intensity = clamp((theta - outerCutOff) / epsilon, 0.0, 1.0);
return intensity;
} else {
return 0.0;
}
}
// 完整的点光源+聚光灯实现
vec3 calculateSpotLight(vec3 normal, vec3 fragPos, vec3 viewPos,
vec3 lightPos, vec3 lightDir, vec3 lightColor,
float lightIntensity, float cutOff, float outerCutOff,
Material material) {
vec3 ambient = material.ambient * lightColor * 0.1;
vec3 L = normalize(lightPos - fragPos);
float distance = length(lightPos - fragPos);
float attenuation = 1.0 / (1.0 + 0.09 * distance + 0.032 * distance * distance);
float diff = max(dot(normal, L), 0.0);
vec3 diffuse = material.diffuse * lightColor * lightIntensity * diff * attenuation;
vec3 V = normalize(viewPos - fragPos);
vec3 H = normalize(L + V);
float spec = pow(max(dot(normal, H), 0.0), material.shininess);
vec3 specular = material.specular * lightColor * lightIntensity * spec * attenuation;
float spotlightEffect = calculateSpotlight(lightPos, fragPos, lightDir, cutOff, outerCutOff);
return (ambient + diffuse + specular) * spotlightEffect;
}
```
---
## 第四章:纹理与材质
### 4.1 纹理采样
纹理是给物体添加细节的重要手段。
```glsl
// 基础纹理采样
uniform sampler2D texture1;
in vec2 TexCoord;
void main() {
vec4 texColor = texture(texture1, TexCoord);
FragColor = texColor;
}
// 多纹理混合
uniform sampler2D texture1;
uniform sampler2D texture2;
uniform float mixRatio;
in vec2 TexCoord;
void main() {
vec4 color1 = texture(texture1, TexCoord);
vec4 color2 = texture(texture2, TexCoord);
FragColor = mix(color1, color2, mixRatio);
}
// 纹理坐标变换
uniform sampler2D texture;
uniform float texScale;
uniform float texRotation;
uniform vec2 texOffset;
in vec2 TexCoord;
void main() {
// 缩放
vec2 scaledCoord = TexCoord * texScale;
// 旋转
float cosR = cos(texRotation);
float sinR = sin(texRotation);
mat2 rotationMatrix = mat2(cosR, -sinR, sinR, cosR);
vec2 rotatedCoord = rotationMatrix * (scaledCoord - 0.5) + 0.5;
// 偏移
vec2 finalCoord = rotatedCoord + texOffset;
FragColor = texture(texture, finalCoord);
}
```
### 4.2 法线贴图
法线贴图用于模拟表面细节,而不增加几何复杂度。
```glsl
// 法线贴图实现
uniform sampler2D normalMap;
uniform sampler2D diffuseMap;
uniform float normalStrength;
in vec2 TexCoord;
in vec3 TangentLightPos;
in vec3 TangentViewPos;
in vec3 TangentFragPos;
void main() {
// 采样法线贴图
vec3 normal = texture(normalMap, TexCoord).rgb;
// 将法线从[0,1]转换为[-1,1]
normal = normalize(normal * 2.0 - 1.0);
// 应用法线强度
normal = normalize(vec3(normal.xy * normalStrength, normal.z));
// 在切线空间计算光照
vec3 lightDir = normalize(TangentLightPos - TangentFragPos);
vec3 viewDir = normalize(TangentViewPos - TangentFragPos);
// 漫反射
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = texture(diffuseMap, TexCoord).rgb * diff;
// 镜面反射
vec3 halfwayDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(normal, halfwayDir), 0.0), 32.0);
vec3 specular = vec3(0.5) * spec;
FragColor = vec4(diffuse + specular, 1.0);
}
```
### 4.3 高度贴图与视差贴图
```glsl
// 视差贴图实现
uniform sampler2D heightMap;
uniform float heightScale;
in vec2 TexCoord;
in vec3 viewDirTS; // 切线空间中的视图方向
vec2 parallaxMapping(vec2 texCoords, vec3 viewDir) {
float height = texture(heightMap, texCoords).r;
vec2 p = viewDir.xy / viewDir.z * (height * heightScale);
return texCoords - p;
}
void main() {
vec2 texCoords = parallaxMapping(TexCoord, viewDirTS);
FragColor = texture(diffuseMap, texCoords);
}
// 自适应视差贴图(更高质量)
vec2 parallaxOcclusionMapping(vec2 texCoords, vec3 viewDir) {
const float minLayers = 8.0;
const float maxLayers = 32.0;
float numLayers = mix(maxLayers, minLayers, max(dot(vec3(0.0, 0.0, 1.0), viewDir), 0.0));
float layerDepth = 1.0 / numLayers;
float currentLayerDepth = 0.0;
vec2 P = viewDir.xy * heightScale;
vec2 deltaTexCoords = P / numLayers;
vec2 currentTexCoords = texCoords;
float currentDepthMapValue = texture(heightMap, currentTexCoords).r;
while (currentLayerDepth < currentDepthMapValue) {
currentTexCoords -= deltaTexCoords;
currentDepthMapValue = texture(heightMap, currentTexCoords).r;
currentLayerDepth += layerDepth;
}
// 深度值插值
vec2 prevTexCoords = currentTexCoords + deltaTexCoords;
float afterDepth = currentDepthMapValue - currentLayerDepth;
float beforeDepth = texture(heightMap, prevTexCoords).r - currentLayerDepth + layerDepth;
float weight = afterDepth / (afterDepth - beforeDepth);
vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);
return finalTexCoords;
}
```
---
## 第五章:高级光照技术
### 5.1 阴影映射
阴影映射是最常用的阴影技术。
```glsl
// 阴影映射的片段着色器
#version 330 core
out vec4 FragColor;
in vec2 TexCoord;
in vec3 FragPos;
in vec3 Normal;
uniform sampler2D diffuseTexture;
uniform sampler2D shadowMap;
uniform vec3 lightPos;
uniform vec3 viewPos;
uniform vec3 lightColor;
float calculateShadow(vec4 fragPosLightSpace) {
// 执行透视除法
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
// 转换到[0,1]范围
projCoords = projCoords * 0.5 + 0.5;
// 最近的深度值
float closestDepth = texture(shadowMap, projCoords.xy).r;
// 当前片段的深度值
float currentDepth = projCoords.z;
// 检查当前片段是否在阴影中
float shadow = currentDepth > closestDepth + 0.005 ? 1.0 : 0.0;
return shadow;
}
void main() {
vec3 normal = normalize(Normal);
vec3 color = texture(diffuseTexture, TexCoord).rgb;
// 环境光
vec3 ambient = 0.1 * color;
// 漫反射
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = diff * color;
// 镜面反射
vec3 viewDir = normalize(viewPos - FragPos);
vec3 halfwayDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(normal, halfwayDir), 0.0), 32.0);
vec3 specular = vec3(0.2) * spec;
// 阴影计算
float shadow = calculateShadow(fragPosLightSpace);
vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * lightColor;
FragColor = vec4(lighting, 1.0);
}
```
### 5.2 软阴影与PCF
```glsl
// PCF(Percentage-Closer Filtering)软阴影
float calculateShadowPCF(vec4 fragPosLightSpace) {
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
projCoords = projCoords * 0.5 + 0.5;
float currentDepth = projCoords.z;
float shadow = 0.0;
vec2 texelSize = 1.0 / textureSize(shadowMap, 0);
// 3x3采样
for (int x = -1; x <= 1; ++x) {
for (int y = -1; y <= 1; ++y) {
float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r;
shadow += currentDepth > pcfDepth + 0.005 ? 1.0 : 0.0;
}
}
shadow /= 9.0;
return shadow;
}
// PCSS(Percentage-Closer Soft Shadows)更高质量的软阴影
float calculateShadowPCSS(vec4 fragPosLightSpace) {
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
projCoords = projCoords * 0.5 + 0.5;
float currentDepth = projCoords.z;
float shadow = 0.0;
// 查找遮挡物的平均深度
float blockerDepth = 0.0;
float blockerCount = 0.0;
vec2 texelSize = 1.0 / textureSize(shadowMap, 0);
for (int x = -4; x <= 4; ++x) {
for (int y = -4; y <= 4; ++y) {
float depth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r;
if (depth < currentDepth) {
blockerDepth += depth;
blockerCount += 1.0;
}
}
}
if (blockerCount > 0.0) {
blockerDepth /= blockerCount;
// 根据遮挡物距离调整滤波半径
float penumbra = (currentDepth - blockerDepth) / blockerDepth;
float filterSize = penumbra * 0.1; // 调整参数
// 在调整后的半径内进行PCF
int samples = int(9.0 + penumbra * 20.0);
for (int x = -samples; x <= samples; ++x) {
for (int y = -samples; y <= samples; ++y) {
float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize * filterSize).r;
shadow += currentDepth > pcfDepth + 0.005 ? 1.0 : 0.0;
}
}
shadow /= float((samples * 2 + 1) * (samples * 2 + 1));
}
return shadow;
}
```
### 5.3 环境光遮蔽(AO)
```glsl
// SSAO(屏幕空间环境光遮蔽)
#define KERNEL_SIZE 64
#define SAMPLE_RADIUS 0.5
#define BIAS 0.025
uniform sampler2D gPosition;
uniform sampler2D gNormal;
uniform sampler2D noiseTexture;
uniform vec3 samples[KERNEL_SIZE];
uniform mat4 projection;
in vec2 TexCoord;
out float FragColor;
void main() {
vec3 fragPos = texture(gPosition, TexCoord).xyz;
vec3 normal = texture(gNormal, TexCoord).rgb;
vec3 randomVec = texture(noiseTexture, TexCoord).xyz;
vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal));
vec3 bitangent = cross(normal, tangent);
mat3 TBN = mat3(tangent, bitangent, normal);
float occlusion = 0.0;
for (int i = 0; i < KERNEL_SIZE; ++i) {
vec3 sample = TBN * samples[i];
sample = fragPos + sample * SAMPLE_RADIUS;
vec4 offset = vec4(sample, 1.0);
offset = projection * offset;
offset.xyz /= offset.w;
offset.xyz = offset.xyz * 0.5 + 0.5;
float sampleDepth = texture(gPosition, offset.xy).z;
float rangeCheck = smoothstep(0.0, 1.0, SAMPLE_RADIUS / abs(fragPos.z - sampleDepth));
occlusion += (sampleDepth >= sample.z + BIAS ? 1.0 : 0.0) * rangeCheck;
}
occlusion = 1.0 - (occlusion / KERNEL_SIZE);
FragColor = occlusion;
}
```
---
## 第六章:后期处理
### 6.1 泛光效果
```glsl
// 高斯模糊
uniform sampler2D image;
uniform bool horizontal;
uniform float weight[5] = float[](0.2270270270, 0.1945945946, 0.1216216216,
0.0540540541, 0.0162162162);
in vec2 TexCoord;
out vec4 FragColor;
void main() {
vec2 tex_offset = 1.0 / textureSize(image, 0);
vec3 result = texture(image, TexCoord).rgb * weight[0];
if (horizontal) {
for (int i = 1; i < 5; ++i) {
result += texture(image, TexCoord + vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
result += texture(image, TexCoord - vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
}
} else {
for (int i = 1; i < 5; ++i) {
result += texture(image, TexCoord + vec2(0.0, tex_offset.y * i)).rgb * weight[i];
result += texture(image, TexCoord - vec2(0.0, tex_offset.y * i)).rgb * weight[i];
}
}
FragColor = vec4(result, 1.0);
}
// 泛光组合
uniform sampler2D scene;
uniform sampler2D bloomBlur;
uniform float exposure;
in vec2 TexCoord;
out vec4 FragColor;
void main() {
vec3 hdrColor = texture(scene, TexCoord).rgb;
vec3 bloomColor = texture(bloomBlur, TexCoord).rgb;
// 色调映射
vec3 result = hdrColor + bloomColor;
result = vec3(1.0) - exp(-result * exposure);
result = pow(result, vec3(1.0 / 2.2)); // Gamma校正
FragColor = vec4(result, 1.0);
}
```
### 6.2 色调映射
```glsl
// Reinhard色调映射
vec3 reinhard(vec3 hdrColor) {
return hdrColor / (hdrColor + vec3(1.0));
}
// Filmic色调映射
vec3 filmic(vec3 hdrColor) {
vec3 x = max(vec3(0.0), hdrColor - 0.004);
return (x * (6.2 * x + 0.5)) / (x * (6.2 * x + 1.7) + 0.06);
}
// ACES色调映射
vec3 aces(vec3 hdrColor) {
const float a = 2.51;
const float b = 0.03;
const float c = 2.43;
const float d = 0.59;
const float e = 0.14;
return clamp((hdrColor * (a * hdrColor + b)) / (hdrColor * (c * hdrColor + d) + e), 0.0, 1.0);
}
// 暴露调整
uniform float exposure;
vec3 adjustExposure(vec3 color) {
return color * exposure;
}
// 白平衡
uniform vec3 whiteBalance;
vec3 applyWhiteBalance(vec3 color) {
vec3 balancedColor = color * whiteBalance;
return balancedColor / dot(balancedColor, vec3(0.2126, 0.7152, 0.0722)) *
dot(color, vec3(0.2126, 0.7152, 0.0722));
}
```
### 6.3 屏幕空间反射(SSR)
```glsl
// 屏幕空间反射实现
uniform sampler2D sceneColor;
uniform sampler2D sceneDepth;
uniform sampler2D sceneNormal;
uniform mat4 projection;
uniform mat4 inverseProjection;
uniform mat4 view;
uniform mat4 inverseView;
in vec2 TexCoord;
out vec4 FragColor;
vec3 ssr(vec2 texCoord) {
vec4 worldPos = reconstructWorldPos(TexCoord);
vec3 worldNormal = texture(sceneNormal, TexCoord).rgb;
vec3 worldViewDir = normalize(cameraPos - worldPos.xyz);
vec3 worldReflectDir = reflect(-worldViewDir, worldNormal);
// 在屏幕空间进行射线步进
vec3 rayStart = worldPos.xyz;
vec3 rayDir = worldReflectDir;
for (int i = 0; i < 32; i++) {
vec3 rayPos = rayStart + rayDir * float(i) * 0.1;
vec4 screenPos = projection * view * vec4(rayPos, 1.0);
screenPos.xyz /= screenPos.w;
screenPos.xyz = screenPos.xyz * 0.5 + 0.5;
if (screenPos.x < 0.0 || screenPos.x > 1.0 ||
screenPos.y < 0.0 || screenPos.y > 1.0) {
break;
}
float sceneDepth = texture(sceneDepth, screenPos.xy).r;
float rayDepth = screenPos.z;
if (abs(sceneDepth - rayDepth) < 0.01) {
return texture(sceneColor, screenPos.xy).rgb;
}
}
return vec3(0.0);
}
vec4 reconstructWorldPos(vec2 texCoord) {
float depth = texture(sceneDepth, texCoord).r;
vec4 clipPos = vec4(texCoord * 2.0 - 1.0, depth, 1.0);
vec4 worldPos = inverseView * inverseProjection * clipPos;
return worldPos / worldPos.w;
}
void main() {
vec3 color = texture(sceneColor, TexCoord).rgb;
vec3 reflection = ssr(TexCoord);
// 根据入射角度混合反射
vec3 worldNormal = texture(sceneNormal, TexCoord).rgb;
vec3 worldPos = reconstructWorldPos(TexCoord).xyz;
vec3 viewDir = normalize(cameraPos - worldPos);
float fresnel = pow(1.0 - max(dot(worldNormal, viewDir), 0.0), 3.0);
FragColor = vec4(mix(color, reflection, fresnel), 1.0);
}
```
---
## 第七章:粒子系统
### 7.1 GPU粒子系统
```glsl
// 粒子更新计算着色器
#version 430 core
layout (local_size_x = 128) in;
struct Particle {
vec3 position;
vec3 velocity;
float life;
float size;
};
layout (std430, binding = 0) buffer ParticleBuffer {
Particle particles[];
};
uniform float deltaTime;
uniform vec3 gravity;
uniform vec3 emitterPosition;
uniform vec3 emitterVelocity;
uniform float emitterRate;
void main() {
uint gid = gl_GlobalInvocationID.x;
if (gid >= particles.length()) return;
Particle p = particles[gid];
// 更新粒子
p.velocity += gravity * deltaTime;
p.position += p.velocity * deltaTime;
p.life -= deltaTime;
// 重置死掉的粒子
if (p.life <= 0.0) {
p.position = emitterPosition;
p.velocity = emitterVelocity + vec3(
(fract(sin(gid * 12.9898) * 43758.5453) - 0.5) * 2.0,
(fract(cos(gid * 78.233) * 43758.5453) - 0.5) * 2.0,
(fract(sin(gid * 37.719) * 43758.5453) - 0.5) * 2.0
) * 5.0;
p.life = 2.0 + fract(sin(gid * 27.1) * 43758.5453) * 2.0;
p.size = 0.1 + fract(sin(gid * 9.1) * 43758.5453) * 0.2;
}
particles[gid] = p;
}
// 粒子渲染顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in float aLife;
layout (location = 2) in float aSize;
out float Life;
out vec2 TexCoord;
uniform mat4 view;
uniform mat4 projection;
void main() {
Life = aLife;
// 计算屏幕位置
vec3 viewPos = view * vec4(aPos, 1.0)).xyz;
vec4 clipPos = projection * vec4(viewPos, 1.0);
// 应用粒子大小
gl_Position = clipPos;
gl_PointSize = aSize * (1000.0 / -viewPos.z);
}
// 粒子渲染片段着色器
#version 330 core
in float Life;
out vec4 FragColor;
uniform sampler2D particleTexture;
void main() {
// 圆形粒子
vec2 center = gl_PointCoord - 0.5;
float dist = length(center);
if (dist > 0.5) discard;
// 根据生命周期淡出
float alpha = 1.0 - (Life / 2.0);
alpha *= smoothstep(0.5, 0.0, dist);
vec4 texColor = texture(particleTexture, gl_PointCoord);
FragColor = texColor * alpha;
}
```
### 7.2 流体模拟
```glsl
// 2D流体模拟(Navier-Stokes方程)
uniform sampler2D velocityField;
uniform sampler2D pressureField;
uniform sampler2D dyeField;
uniform float dt;
uniform float viscosity;
uniform float diffusion;
in vec2 TexCoord;
out vec4 FragColor;
// 获取相邻像素
vec4 getNeighbor(sampler2D tex, vec2 coord, vec2 offset) {
return texture(tex, coord + offset * vec2(1.0 / textureSize(tex, 0)));
}
// 平散
vec4 diffuse(sampler2D tex, float diff, float dt) {
vec4 result = texture(tex, TexCoord);
vec4 left = getNeighbor(tex, TexCoord, vec2(-1, 0));
vec4 right = getNeighbor(tex, TexCoord, vec2(1, 0));
vec4 up = getNeighbor(tex, TexCoord, vec2(0, 1));
vec4 down = getNeighbor(tex, TexCoord, vec2(0, -1));
float a = dt * diff * 4.0;
result = (result + a * (left + right + up + down)) / (1.0 + 4.0 * a);
return result;
}
// 平流
vec4 advect(sampler2D tex, sampler2D vel, float dt) {
vec2 coord = TexCoord - texture(vel, TexCoord).xy * dt * vec2(1.0 / textureSize(vel, 0));
return texture(tex, coord);
}
// 投影(不可压缩性)
vec4 project(sampler2D vel, sampler2D pres) {
vec4 result = texture(vel, TexCoord);
vec4 leftP = getNeighbor(pres, TexCoord, vec2(-1, 0));
vec4 rightP = getNeighbor(pres, TexCoord, vec2(1, 0));
vec4 upP = getNeighbor(pres, TexCoord, vec2(0, 1));
vec4 downP = getNeighbor(pres, TexCoord, vec2(0, -1));
result -= vec4(rightP - leftP, downP - upP) * 0.5;
return result;
}
void main() {
// 完整的流体模拟步骤
vec4 velocity = texture(velocityField, TexCoord);
vec4 pressure = texture(pressureField, TexCoord);
vec4 dye = texture(dyeField, TexCoord);
// 1. 扩散
velocity = diffuse(velocityField, viscosity, dt);
dye = diffuse(dyeField, 0.001, dt); // 染料扩散较少
// 2. 平流
velocity = advect(velocityField, velocityField, dt);
dye = advect(dyeField, velocityField, dt);
// 3. 投影
velocity = project(velocityField, pressureField);
FragColor = dye;
}
```
---
## 第八章:性能优化
### 8.1 GPU Instancing
```cpp
// 实例化渲染的C++实现
#include
#include
class InstancedMesh {
private:
unsigned int VAO, VBO, instanceVBO;
unsigned int instanceCount;
public:
InstancedMesh(const std::vector& vertices,
const std::vector& models) {
instanceCount = models.size();
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &instanceVBO);
glBindVertexArray(VAO);
// 顶点数据
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float),
vertices.data(), GL_STATIC_DRAW);
// 位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 法线属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float),
(void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
// 纹理坐标属性
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float),
(void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
// 实例化矩阵属性
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glBufferData(GL_ARRAY_BUFFER, models.size() * sizeof(glm::mat4),
models.data(), GL_STATIC_DRAW);
// 每个mat4占用4个属性位置
for (int i = 0; i < 4; i++) {
glVertexAttribPointer(3 + i, 4, GL_FLOAT, GL_FALSE,
sizeof(glm::mat4), (void*)(i * sizeof(glm::vec4)));
glEnableVertexAttribArray(3 + i);
glVertexAttribDivisor(3 + i, 1); // 每实例更新
}
glBindVertexArray(0);
}
void Draw() {
glBindVertexArray(VAO);
glDrawArraysInstanced(GL_TRIANGLES, 0, 36, instanceCount);
glBindVertexArray(0);
}
};
// 实例化渲染着色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoord;
layout (location = 3) in mat4 aModel; // 实例化矩阵
out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoord;
uniform mat4 view;
uniform mat4 projection;
void main() {
mat4 model = aModel; // 使用实例化矩阵
vec4 worldPos = model * vec4(aPos, 1.0);
FragPos = worldPos.xyz;
mat3 normalMatrix = transpose(inverse(mat3(model)));
Normal = normalize(normalMatrix * aNormal);
TexCoord = aTexCoord;
gl_Position = projection * view * worldPos;
}
```
### 8.2 多线程渲染
```cpp
// 多线程渲染管线
#include
#include
#include
class RenderThreadPool {
private:
std::vector workers;
std::mutex mutex;
std::vector taskQueue;
bool shouldStop = false;
public:
RenderThreadPool(int numThreads) {
for (int i = 0; i < numThreads; i++) {
workers.emplace_back([this] {
while (true) {
RenderTask task;
{
std::lock_guard lock(mutex);
if (shouldStop && taskQueue.empty()) {
return;
}
if (!taskQueue.empty()) {
task = taskQueue.back();
taskQueue.pop_back();
} else {
continue;
}
}
task.Execute();
}
});
}
}
void AddTask(const RenderTask& task) {
std::lock_guard lock(mutex);
taskQueue.push_back(task);
}
void Wait() {
while (true) {
{
std::lock_guard lock(mutex);
if (taskQueue.empty()) break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
~RenderThreadPool() {
shouldStop = true;
for (auto& worker : workers) {
worker.join();
}
}
};
```
### 8.3 Shader优化技巧
```glsl
// 优化1:避免分支
// 差的做法
float calculateSpecular(vec3 normal, vec3 lightDir, vec3 viewDir) {
float specular = 0.0;
if (dot(normal, lightDir) > 0.0) {
vec3 reflectDir = reflect(-lightDir, normal);
specular = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
}
return specular;
}
// 好的做法(使用step避免分支)
float calculateSpecularOptimized(vec3 normal, vec3 lightDir, vec3 viewDir) {
float NdotL = dot(normal, lightDir);
float specularMask = step(0.0, NdotL);
vec3 reflectDir = reflect(-lightDir, normal);
float specular = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
return specular * specularMask;
}
// 优化2:减少纹理采样
// 差的做法(重复采样)
void main() {
vec4 color1 = texture(diffuseMap, TexCoord);
vec4 color2 = texture(normalMap, TexCoord);
vec4 color3 = texture(roughnessMap, TexCoord);
vec4 color4 = texture(aoMap, TexCoord);
// ...
}
// 好的做法(一次采样多种属性)
void main() {
vec4 packedData = texture(packedTexture, TexCoord);
vec3 diffuse = packedData.rgb;
float roughness = packedData.a;
// 解包其他纹理...
}
// 优化3:使用更快的数学函数
// 差的做法
float shadow = calculateExpensiveShadow(fragPosLightSpace);
// 好的做法(使用预计算或简化算法)
float shadow = texture(shadowMap, fragPosLightSpace.xy).r < fragPosLightSpace.z ? 1.0 : 0.0;
// 优化4:减少计算精度
// 使用低精度变量
mediump vec3 color;
lowp float alpha;
// 优化5:避免循环展开
const int SAMPLE_COUNT = 16;
for (int i = 0; i < SAMPLE_COUNT; i++) {
// 固定次数循环,编译器可以优化
}
// 优化6:使用共享常量
const float PI = 3.14159265359;
const float INV_PI = 1.0 / PI;
const float TWO_PI = 2.0 * PI;
```
---
## 第九章:Ray Tracing与光线追踪
### 9.1 基础Ray Tracing
```glsl
// 简单的Ray Tracing片段着色器
#version 330 core
out vec4 FragColor;
in vec2 TexCoord;
uniform vec3 cameraPos;
uniform vec3 cameraDir;
uniform vec3 cameraUp;
struct Ray {
vec3 origin;
vec3 direction;
};
struct Sphere {
vec3 center;
float radius;
vec3 color;
float reflectivity;
};
struct HitInfo {
bool hit;
float distance;
vec3 point;
vec3 normal;
vec3 color;
float reflectivity;
};
// 射线-球体相交检测
HitInfo intersectSphere(Ray ray, Sphere sphere) {
HitInfo info;
info.hit = false;
vec3 oc = ray.origin - sphere.center;
float a = dot(ray.direction, ray.direction);
float b = 2.0 * dot(oc, ray.direction);
float c = dot(oc, oc) - sphere.radius * sphere.radius;
float discriminant = b * b - 4.0 * a * c;
if (discriminant > 0.0) {
float t = (-b - sqrt(discriminant)) / (2.0 * a);
if (t > 0.001) {
info.hit = true;
info.distance = t;
info.point = ray.origin + t * ray.direction;
info.normal = normalize(info.point - sphere.center);
info.color = sphere.color;
info.reflectivity = sphere.reflectivity;
}
}
return info;
}
// 追踪光线
HitInfo traceRay(Ray ray, Sphere[] spheres, int sphereCount) {
HitInfo closestHit;
closestHit.hit = false;
closestHit.distance = 1e10;
for (int i = 0; i < sphereCount; i++) {
HitInfo hit = intersectSphere(ray, spheres[i]);
if (hit.hit && hit.distance < closestHit.distance) {
closestHit = hit;
}
}
return closestHit;
}
// 计算颜色
vec3 calculateColor(Ray ray, Sphere[] spheres, int sphereCount, int depth) {
HitInfo hit = traceRay(ray, spheres, sphereCount);
if (!hit.hit) {
// 天空背景
float t = 0.5 * (ray.direction.y + 1.0);
return mix(vec3(1.0, 1.0, 1.0), vec3(0.5, 0.7, 1.0), t);
}
// 计算光照
vec3 lightPos = vec3(5.0, 5.0, 5.0);
vec3 lightDir = normalize(lightPos - hit.point);
vec3 lightColor = vec3(1.0, 1.0, 1.0);
// 检查阴影
Ray shadowRay;
shadowRay.origin = hit.point + hit.normal * 0.001;
shadowRay.direction = lightDir;
HitInfo shadowHit = traceRay(shadowRay, spheres, sphereCount);
bool inShadow = shadowHit.hit && shadowHit.distance < length(lightPos - hit.point);
if (inShadow) {
lightColor = vec3(0.1); // 阴影
}
// 漫反射
float diff = max(dot(hit.normal, lightDir), 0.0);
vec3 diffuse = hit.color * lightColor * diff;
// 镜面反射
vec3 viewDir = normalize(ray.origin - hit.point);
vec3 reflectDir = reflect(-lightDir, hit.normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
vec3 specular = vec3(1.0) * lightColor * spec;
// 反射
vec3 reflection = vec3(0.0);
if (depth > 0 && hit.reflectivity > 0.0) {
Ray reflectRay;
reflectRay.origin = hit.point + hit.normal * 0.001;
reflectRay.direction = reflect(-ray.direction, hit.normal);
reflection = calculateColor(reflectRay, spheres, sphereCount, depth - 1) * hit.reflectivity;
}
return diffuse + specular + reflection;
}
void main() {
// 构建相机射线
vec3 cameraRight = normalize(cross(cameraDir, cameraUp));
vec3 cameraUpFixed = cross(cameraRight, cameraDir);
float aspect = 1920.0 / 1080.0;
vec2 uv = (TexCoord - 0.5) * vec2(aspect, 1.0);
Ray ray;
ray.origin = cameraPos;
ray.direction = normalize(cameraDir + uv.x * cameraRight + uv.y * cameraUpFixed);
// 场景定义
Sphere spheres[4];
spheres[0] = Sphere(vec3(0.0, 0.0, -3.0), 1.0, vec3(1.0, 0.0, 0.0), 0.5);
spheres[1] = Sphere(vec3(2.0, 0.0, -4.0), 1.0, vec3(0.0, 1.0, 0.0), 0.5);
spheres[2] = Sphere(vec3(-2.0, 0.0, -4.0), 1.0, vec3(0.0, 0.0, 1.0), 0.5);
spheres[3] = Sphere(vec3(0.0, -1001.0, -3.0), 1000.0, vec3(0.5, 0.5, 0.5), 0.0);
// 计算颜色
vec3 color = calculateColor(ray, spheres, 4, 3);
// Gamma校正
color = pow(color, vec3(1.0 / 2.2));
FragColor = vec4(color, 1.0);
}
```
### 9.2 路径追踪
```glsl
// 路径追踪实现
#version 330 core
out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D previousFrame;
uniform int frameCount;
// 随机数生成
float rand(vec2 co) {
return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453);
}
vec3 randomInHemisphere(vec3 normal) {
vec2 uv = vec2(rand(TexCoord + frameCount), rand(TexCoord - frameCount));
float z = uv.x;
float r = sqrt(1.0 - z * z);
float phi = 2.0 * 3.14159265359 * uv.y;
vec3 randomDir = vec3(r * cos(phi), r * sin(phi), z);
// 确保在半球的正确一侧
if (dot(randomDir, normal) < 0.0) {
randomDir = -randomDir;
}
return normalize(randomDir);
}
// 路径追踪主函数
vec3 pathTrace(Ray ray, Sphere[] spheres, int sphereCount, int depth) {
if (depth <= 0) return vec3(0.0);
HitInfo hit = traceRay(ray, spheres, sphereCount);
if (!hit.hit) {
// 天空盒
return vec3(0.0, 0.0, 0.0);
}
// 随机采样方向
vec3 randomDir = randomInHemisphere(hit.normal);
Ray scatteredRay;
scatteredRay.origin = hit.point + hit.normal * 0.001;
scatteredRay.direction = randomDir;
// 递归计算间接光照
vec3 indirectColor = pathTrace(scatteredRay, spheres, sphereCount, depth - 1);
// 计算直接光照
vec3 lightPos = vec3(5.0, 5.0, 5.0);
vec3 lightDir = normalize(lightPos - hit.point);
float lightDist = length(lightPos - hit.point);
Ray shadowRay;
shadowRay.origin = hit.point + hit.normal * 0.001;
shadowRay.direction = lightDir;
HitInfo shadowHit = traceRay(shadowRay, spheres, sphereCount);
bool inShadow = shadowHit.hit && shadowHit.distance < lightDist;
vec3 directColor = inShadow ? vec3(0.0) : hit.color;
// 组合直接和间接光照
return directColor + hit.color * indirectColor;
}
// 噪声过滤(时间累积)
vec3 accumulateColor(vec3 newColor, vec3 oldColor, int frameCount) {
return (oldColor * float(frameCount - 1) + newColor) / float(frameCount);
}
void main() {
// 构建射线(与Ray Tracing相同)
Ray ray = buildCameraRay(TexCoord);
// 路径追踪
vec3 newColor = pathTrace(ray, spheres, 4, 8);
// 时间累积
vec3 oldColor = texture(previousFrame, TexCoord).rgb;
vec3 accumulatedColor = accumulateColor(newColor, oldColor, frameCount);
// Gamma校正
vec3 finalColor = pow(accumulatedColor, vec3(1.0 / 2.2));
FragColor = vec4(finalColor, 1.0);
}
```
---
## 第十章:实战项目:完整的渲染引擎
### 10.1 项目架构
```
RenderingEngine/
├── Core/
│ ├── Engine.cpp
│ ├── Renderer.cpp
│ ├── Shader.cpp
│ └── Camera.cpp
├── Assets/
│ ├── ModelLoader.cpp
│ ├── TextureLoader.cpp
│ └── ShaderLoader.cpp
├── Rendering/
│ ├── ForwardRenderer.cpp
│ ├── DeferredRenderer.cpp
│ └── PostProcessor.cpp
├── Components/
│ ├── Transform.cpp
│ ├── Material.cpp
│ └── Light.cpp
├── Utils/
│ ├── MathUtils.cpp
│ └── Profiler.cpp
└── Shaders/
├── Basic/
├── PBR/
├── PostProcessing/
└── RayTracing/
```
### 10.2 完整的PBR渲染系统
```glsl
// PBR材质定义
struct PBRMaterial {
vec3 albedo;
float metallic;
float roughness;
float ao;
};
// PBR光照计算
vec3 calculatePBR(vec3 N, vec3 V, vec3 L, vec3 radiance, PBRMaterial mat) {
vec3 H = normalize(V + L);
// 漫反射项
vec3 F0 = mix(vec3(0.04), mat.albedo, mat.metallic);
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
vec3 kS = F;
vec3 kD = (1.0 - kS) * (1.0 - mat.metallic);
vec3 diffuse = kD * mat.albedo / PI;
// 镜面反射项
float NDF = distributionGGX(N, H, mat.roughness);
float G = geometrySmith(N, V, L, mat.roughness);
vec3 specular = NDF * G * F / (4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.001);
// 最终颜色
float NdotL = max(dot(N, L), 0.0);
return (diffuse + specular) * radiance * NdotL;
}
// 完整的PBR片段着色器
#version 330 core
out vec4 FragColor;
in vec2 TexCoord;
in vec3 FragPos;
in vec3 Normal;
uniform PBRMaterial material;
uniform vec3 camPos;
uniform vec3 lightPositions[4];
uniform vec3 lightColors[4];
// 辅助函数
vec3 fresnelSchlick(float cosTheta, vec3 F0) {
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
float distributionGGX(vec3 N, vec3 H, float roughness) {
float a = roughness * roughness;
float a2 = a * a;
float NdotH = max(dot(N, H), 0.0);
float NdotH2 = NdotH * NdotH;
float num = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return num / denom;
}
float geometrySchlickGGX(float NdotV, float roughness) {
float r = (roughness + 1.0);
float k = (r * r) / 8.0;
float num = NdotV;
float denom = NdotV * (1.0 - k) + k;
return num / denom;
}
float geometrySmith(vec3 N, vec3 V, vec3 L, float roughness) {
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
float ggx1 = geometrySchlickGGX(NdotV, roughness);
float ggx2 = geometrySchlickGGX(NdotL, roughness);
return ggx1 * ggx2;
}
void main() {
vec3 N = normalize(Normal);
vec3 V = normalize(camPos - FragPos);
vec3 F0 = vec3(0.04);
F0 = mix(F0, material.albedo, material.metallic);
vec3 Lo = vec3(0.0);
for (int i = 0; i < 4; i++) {
vec3 L = normalize(lightPositions[i] - FragPos);
vec3 H = normalize(V + L);
float distance = length(lightPositions[i] - FragPos);
float attenuation = 1.0 / (distance * distance);
vec3 radiance = lightColors[i] * attenuation;
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
vec3 kS = F;
vec3 kD = (1.0 - kS) * (1.0 - material.metallic);
float NDF = distributionGGX(N, H, material.roughness);
float G = geometrySmith(N, V, L, material.roughness);
vec3 numerator = NDF * G * F;
float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001;
vec3 specular = numerator / denominator;
float NdotL = max(dot(N, L), 0.0);
Lo += (kD * material.albedo / PI + specular) * radiance * NdotL;
}
vec3 ambient = vec3(0.03) * material.albedo * material.ao;
vec3 color = ambient + Lo;
color = color / (color + vec3(1.0));
color = pow(color, vec3(1.0 / 2.2));
FragColor = vec4(color, 1.0);
}
```
### 10.3 性能分析与优化
```cpp
// 性能分析工具
class Profiler {
private:
std::map> timings;
std::chrono::time_point startTime;
public:
void StartTimer(const std::string& name) {
startTime = std::chrono::high_resolution_clock::now();
}
void EndTimer(const std::string& name) {
auto endTime = std::chrono::high_resolution_clock::now();
float duration = std::chrono::duration(endTime - startTime).count();
timings[name].push_back(duration);
// 保留最近100次记录
if (timings[name].size() > 100) {
timings[name].erase(timings[name].begin());
}
}
float GetAverageTime(const std::string& name) {
if (timings[name].empty()) return 0.0f;
float sum = 0.0f;
for (float time : timings[name]) {
sum += time;
}
return sum / timings[name].size();
}
void PrintReport() {
for (auto& [name, times] : timings) {
float avgTime = GetAverageTime(name);
float maxTime = *std::max_element(times.begin(), times.end());
float minTime = *std::min_element(times.begin(), times.end());
std::cout << name << ":" << std::endl;
std::cout << " Average: " << avgTime << "ms" << std::endl;
std::cout << " Max: " << maxTime << "ms" << std::endl;
std::cout << " Min: " << minTime << "ms" << std::endl;
}
}
};
// 使用示例
Profiler profiler;
void RenderScene() {
profiler.StartTimer("Culling");
CullObjects();
profiler.EndTimer("Culling");
profiler.StartTimer("Shadows");
RenderShadows();
profiler.EndTimer("Shadows");
profiler.StartTimer("Geometry");
RenderGeometry();
profiler.EndTimer("Geometry");
profiler.StartTimer("PostProcessing");
RenderPostProcessing();
profiler.EndTimer("PostProcessing");
profiler.PrintReport();
}
```
---
## 结语
图形学是一门博大精深的学科,本文涵盖了从基础概念到高级技术的各个方面。要真正精通图形学,需要:
1. **扎实的数学基础**:向量、矩阵、微积分是图形学的基础
2. **深入的编程实践**:动手编写Shader,理解GPU工作原理
3. **持续的实验探索**:尝试不同的技术,理解其原理和应用场景
4. **关注最新发展**:图形学发展迅速,需要不断学习新技术
记住,图形学不仅是技术,更是艺术。通过编程创造美丽的视觉效果,是图形学最大的魅力所在。希望你能在图形学的道路上不断前进,创造出令人惊叹的作品!