当前位置: 首页 > news >正文

屏幕空间反射SSR-笔记

屏幕空间反射SSR
相关文章:
[OpenGL] 屏幕空间反射效果

Games202-RealTime GI in Screen Space

github上的例子,使用visual studio2019
github例子对应的文章


在这里插入图片描述


使用OpenGL和C++实现发光柱子的SSR倒影

下面是一个使用OpenGL和C++实现屏幕空间反射(SSR)来创建发光柱子倒影的完整示例。这个实现包括基本的SSR技术、发光效果和倒影渲染。

1. 基本框架

首先,我们需要设置OpenGL环境并创建必要的着色器。

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <vector>
#include "shader.h" // 自定义着色器加载类// 窗口尺寸
const unsigned int SCR_WIDTH = 1280;
const unsigned int SCR_HEIGHT = 720;// 全局变量
GLuint gBuffer, gPosition, gNormal, gAlbedoSpec;
GLuint quadVAO, quadVBO;
GLuint cubeVAO, cubeVBO;
GLuint floorVAO, floorVBO;// 函数声明
void renderScene(Shader &shader);
void renderQuad();
void renderCube();
void setupGBuffer();
void setupScreenQuad();
void setupGeometry();

2. 初始化GBuffer

SSR需要几何缓冲区(GBuffer)来存储位置、法线和材质信息。

void setupGBuffer() {glGenFramebuffers(1, &gBuffer);glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);// 位置颜色缓冲glGenTextures(1, &gPosition);glBindTexture(GL_TEXTURE_2D, gPosition);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gPosition, 0);// 法线颜色缓冲glGenTextures(1, &gNormal);glBindTexture(GL_TEXTURE_2D, gNormal);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, gNormal, 0);// 颜色 + 镜面颜色缓冲glGenTextures(1, &gAlbedoSpec);glBindTexture(GL_TEXTURE_2D, gAlbedoSpec);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, gAlbedoSpec, 0);// 告诉OpenGL我们要使用哪些颜色附件进行渲染GLuint attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };glDrawBuffers(3, attachments);// 创建并附加深度缓冲GLuint rboDepth;glGenRenderbuffers(1, &rboDepth);glBindRenderbuffer(GL_RENDERBUFFER, rboDepth);glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, SCR_WIDTH, SCR_HEIGHT);glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboDepth);// 检查完整性if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)std::cout << "Framebuffer not complete!" << std::endl;glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

3. 几何体设置

创建柱子和地板的几何体。

void setupGeometry() {// 柱子 (圆柱体)std::vector<float> vertices;const float radius = 0.5f;const float height = 2.0f;const int segments = 32;// 侧面for (int i = 0; i <= segments; ++i) {float angle = 2.0f * M_PI * float(i) / float(segments);float x = radius * cosf(angle);float z = radius * sinf(angle);// 底部顶点vertices.push_back(x);vertices.push_back(0.0f);vertices.push_back(z);vertices.push_back(x);vertices.push_back(0.0f);vertices.push_back(z);vertices.push_back(0.0f);vertices.push_back(1.0f);vertices.push_back(0.0f);// 顶部顶点vertices.push_back(x);vertices.push_back(height);vertices.push_back(z);vertices.push_back(x);vertices.push_back(0.0f);vertices.push_back(z);vertices.push_back(0.0f);vertices.push_back(1.0f);vertices.push_back(0.0f);}glGenVertexArrays(1, &cubeVAO);glGenBuffers(1, &cubeVBO);glBindVertexArray(cubeVAO);glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), &vertices[0], GL_STATIC_DRAW);// 位置属性glEnableVertexAttribArray(0);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(float), (void*)0);// 法线属性glEnableVertexAttribArray(1);glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(float), (void*)(3 * sizeof(float)));// 切线属性glEnableVertexAttribArray(2);glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(float), (void*)(6 * sizeof(float)));// 地板 (平面)float floorVertices[] = {// 位置             // 法线         // 切线-5.0f, 0.0f, -5.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f,5.0f, 0.0f, -5.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f,5.0f, 0.0f,  5.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f,-5.0f, 0.0f,  5.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f};unsigned int floorIndices[] = {0, 1, 2,0, 2, 3};glGenVertexArrays(1, &floorVAO);glGenBuffers(1, &floorVBO);GLuint floorEBO;glGenBuffers(1, &floorEBO);glBindVertexArray(floorVAO);glBindBuffer(GL_ARRAY_BUFFER, floorVBO);glBufferData(GL_ARRAY_BUFFER, sizeof(floorVertices), floorVertices, GL_STATIC_DRAW);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, floorEBO);glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(floorIndices), floorIndices, GL_STATIC_DRAW);glEnableVertexAttribArray(0);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(float), (void*)0);glEnableVertexAttribArray(1);glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(float), (void*)(3 * sizeof(float)));glEnableVertexAttribArray(2);glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(float), (void*)(6 * sizeof(float)));glBindVertexArray(0);
}

4. 着色器

我们需要几个着色器:几何通道着色器、光照着色器和SSR着色器。

几何通道着色器 (geometry.vs 和 geometry.fs)

// geometry.vs
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec3 aTangent;out VS_OUT {vec3 FragPos;vec3 Normal;vec3 Tangent;
} vs_out;uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;void main() {vs_out.FragPos = vec3(model * vec4(aPos, 1.0));vs_out.Normal = mat3(transpose(inverse(model))) * aNormal;vs_out.Tangent = mat3(model) * aTangent;gl_Position = projection * view * model * vec4(aPos, 1.0);
}
// geometry.fs
#version 330 core
layout (location = 0) out vec3 gPosition;
layout (location = 1) out vec3 gNormal;
layout (location = 2) out vec4 gAlbedoSpec;in VS_OUT {vec3 FragPos;vec3 Normal;vec3 Tangent;
} fs_in;uniform vec3 color;
uniform float specular;
uniform float emissive;void main() {// 存储片段位置向量到G缓冲gPosition = fs_in.FragPos;// 存储法线向量到G缓冲gNormal = normalize(fs_in.Normal);// 存储漫反射颜色gAlbedoSpec.rgb = color;// 存储镜面强度gAlbedoSpec.a = specular;// 如果是发光物体,增加自发光if (emissive > 0.0) {gAlbedoSpec.rgb += color * emissive;}
}

SSR着色器 (ssr.vs 和 ssr.fs)

// ssr.vs
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoords;out vec2 TexCoords;void main() {TexCoords = aTexCoords;gl_Position = vec4(aPos, 0.0, 1.0);
}
// ssr.fs
#version 330 core
out vec4 FragColor;in vec2 TexCoords;uniform sampler2D gPosition;
uniform sampler2D gNormal;
uniform sampler2D gAlbedoSpec;
uniform sampler2D sceneTexture;uniform mat4 projection;
uniform mat4 view;
uniform mat4 invView;
uniform mat4 invProjection;uniform vec3 cameraPos;const float maxRayDistance = 100.0;
const int maxSteps = 128;
const float stride = 1.0;
const float binarySearchSteps = 8;
const float rayHitThreshold = 0.01;vec3 binarySearch(inout vec3 dir, inout vec3 hitCoord, inout float dDepth);
vec4 rayCast(vec3 dir, inout vec3 hitCoord, out bool success);
vec3 hash(vec3 a);void main() {// 获取输入数据vec3 FragPos = texture(gPosition, TexCoords).rgb;vec3 Normal = texture(gNormal, TexCoords).rgb;vec3 Albedo = texture(gAlbedoSpec, TexCoords).rgb;float Specular = texture(gAlbedoSpec, TexCoords).a;// 计算反射向量vec3 viewDir = normalize(FragPos - cameraPos);vec3 reflectDir = reflect(viewDir, Normal);// 初始化射线vec3 hitPos = FragPos;bool success = false;vec4 coords = rayCast(reflectDir, hitPos, success);vec3 SSR = vec3(0.0);if (success) {SSR = texture(sceneTexture, coords.xy).rgb;// 根据距离衰减float fade = 1.0 - smoothstep(0.0, 1.0, length(FragPos - hitPos) / maxRayDistance);SSR *= fade * Specular;}// 混合原始颜色和SSRFragColor = vec4(SSR + Albedo, 1.0);
}vec3 binarySearch(inout vec3 dir, inout vec3 hitCoord, inout float dDepth) {float depth;vec4 projectedCoord;for (int i = 0; i < binarySearchSteps; i++) {projectedCoord = projection * view * vec4(hitCoord, 1.0);projectedCoord.xy /= projectedCoord.w;projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5;depth = texture(gPosition, projectedCoord.xy).z;dDepth = hitCoord.z - depth;dir *= 0.5;if (dDepth > 0.0)hitCoord += dir;elsehitCoord -= dir;}projectedCoord = projection * view * vec4(hitCoord, 1.0);projectedCoord.xy /= projectedCoord.w;projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5;return vec3(projectedCoord.xy, depth);
}vec4 rayCast(vec3 dir, inout vec3 hitCoord, out bool success) {dir *= stride;float depth;int steps;vec4 projectedCoord;float dDepth;for (steps = 0; steps < maxSteps; steps++) {hitCoord += dir;projectedCoord = projection * view * vec4(hitCoord, 1.0);projectedCoord.xy /= projectedCoord.w;projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5;// 检查是否在屏幕外if (projectedCoord.x < 0.0 || projectedCoord.x > 1.0 || projectedCoord.y < 0.0 || projectedCoord.y > 1.0) {success = false;return vec4(0.0);}depth = texture(gPosition, projectedCoord.xy).z;dDepth = hitCoord.z - depth;if (dir.z - dDepth < 1.2) {if (dDepth <= 0.0) {vec3 Result = binarySearch(dir, hitCoord, dDepth);if (dDepth < rayHitThreshold && Result.z - hitCoord.z < rayHitThreshold) {success = true;return vec4(Result, 1.0);}break;}}}success = false;return vec4(0.0);
}vec3 hash(vec3 a) {a = fract(a * 0.8);a += dot(a, a.xyz + 19.19);return fract((a.xxy + a.yxx) * a.zyx);
}

5. 渲染循环

int main() {// 初始化GLFW和OpenGLglfwInit();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, "SSR Glowing Pillar", NULL, NULL);if (window == NULL) {std::cout << "Failed to create GLFW window" << std::endl;glfwTerminate();return -1;}glfwMakeContextCurrent(window);glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);glewExperimental = GL_TRUE;if (glewInit() != GLEW_OK) {std::cout << "Failed to initialize GLEW" << std::endl;return -1;}// 配置全局OpenGL状态glEnable(GL_DEPTH_TEST);// 构建和编译着色器Shader geometryShader("geometry.vs", "geometry.fs");Shader ssrShader("ssr.vs", "ssr.fs");-----------------------------------------------------------------------
# 屏幕空间反射(SSR)着色器详细解释`ssr.fs`(屏幕空间反射片段着色器)是实现屏幕空间反射效果的核心部分。下面我将详细解释这个着色器的每个关键部分。## 1. 输入输出和统一变量```glsl
out vec4 FragColor;in vec2 TexCoords;uniform sampler2D gPosition;  // 位置纹理(GBuffer)
uniform sampler2D gNormal;    // 法线纹理(GBuffer)
uniform sampler2D gAlbedoSpec;// 颜色和镜面强度纹理(GBuffer)
uniform sampler2D sceneTexture; // 场景颜色纹理uniform mat4 projection;      // 投影矩阵
uniform mat4 view;            // 视图矩阵
uniform mat4 invView;         // 视图逆矩阵
uniform mat4 invProjection;   // 投影逆矩阵uniform vec3 cameraPos;       // 相机位置

这些变量提供了SSR计算所需的所有输入数据:

  • gPosition, gNormal, gAlbedoSpec来自几何缓冲区(GBuffer)
  • sceneTexture是已经渲染的场景颜色
  • 矩阵用于空间转换
  • cameraPos用于计算视线方向

2. 常量定义

const float maxRayDistance = 100.0;  // 最大射线距离
const int maxSteps = 128;            // 最大步进次数
const float stride = 1.0;            // 射线步进步长
const float binarySearchSteps = 8;   // 二分查找步数
const float rayHitThreshold = 0.01;  // 射线命中阈值

这些常量控制SSR算法的质量和性能:

  • maxRayDistance限制反射射线的最大长度
  • maxSteps限制射线步进的最大次数
  • stride控制每一步的步长
  • binarySearchSteps控制精确命中时的二分查找精度
  • rayHitThreshold定义何时认为射线命中表面

3. 主函数(main)

void main() {// 获取输入数据vec3 FragPos = texture(gPosition, TexCoords).rgb;vec3 Normal = texture(gNormal, TexCoords).rgb;vec3 Albedo = texture(gAlbedoSpec, TexCoords).rgb;float Specular = texture(gAlbedoSpec, TexCoords).a;// 计算反射向量vec3 viewDir = normalize(FragPos - cameraPos);vec3 reflectDir = reflect(viewDir, Normal);// 初始化射线vec3 hitPos = FragPos;bool success = false;vec4 coords = rayCast(reflectDir, hitPos, success);vec3 SSR = vec3(0.0);if (success) {SSR = texture(sceneTexture, coords.xy).rgb;// 根据距离衰减float fade = 1.0 - smoothstep(0.0, 1.0, length(FragPos - hitPos) / maxRayDistance);SSR *= fade * Specular;}// 混合原始颜色和SSRFragColor = vec4(SSR + Albedo, 1.0);
}

主函数流程:

  1. 从GBuffer获取当前像素的位置、法线、颜色和镜面强度
  2. 计算从相机到当前像素的视线方向(viewDir)
  3. 使用GLSL内置的reflect函数计算反射方向
  4. 调用rayCast函数进行射线步进,寻找反射点
  5. 如果找到反射点,从场景纹理中采样反射颜色
  6. 根据距离和镜面强度调整反射强度
  7. 将反射颜色与原始颜色混合输出

4. 射线步进(rayCast)

vec4 rayCast(vec3 dir, inout vec3 hitCoord, out bool success) {dir *= stride;float depth;int steps;vec4 projectedCoord;float dDepth;for (steps = 0; steps < maxSteps; steps++) {hitCoord += dir;projectedCoord = projection * view * vec4(hitCoord, 1.0);projectedCoord.xy /= projectedCoord.w;projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5;// 检查是否在屏幕外if (projectedCoord.x < 0.0 || projectedCoord.x > 1.0 || projectedCoord.y < 0.0 || projectedCoord.y > 1.0) {success = false;return vec4(0.0);}depth = texture(gPosition, projectedCoord.xy).z;dDepth = hitCoord.z - depth;if (dir.z - dDepth < 1.2) {if (dDepth <= 0.0) {vec3 Result = binarySearch(dir, hitCoord, dDepth);if (dDepth < rayHitThreshold && Result.z - hitCoord.z < rayHitThreshold) {success = true;return vec4(Result, 1.0);}break;}}}success = false;return vec4(0.0);
}

射线步进流程:

  1. 沿着反射方向(dir)逐步前进
  2. 每一步将世界坐标转换为屏幕坐标(projectedCoord)
  3. 检查是否超出屏幕边界
  4. 从GBuffer获取当前位置的深度值
  5. 比较当前射线深度与场景深度(dDepth)
  6. 当射线进入表面下方时(dDepth <= 0.0),进入二分查找阶段
  7. 如果找到足够接近的命中点,返回命中坐标

5. 二分查找(binarySearch)

vec3 binarySearch(inout vec3 dir, inout vec3 hitCoord, inout float dDepth) {float depth;vec4 projectedCoord;for (int i = 0; i < binarySearchSteps; i++) {projectedCoord = projection * view * vec4(hitCoord, 1.0);projectedCoord.xy /= projectedCoord.w;projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5;depth = texture(gPosition, projectedCoord.xy).z;dDepth = hitCoord.z - depth;dir *= 0.5;if (dDepth > 0.0)hitCoord += dir;elsehitCoord -= dir;}projectedCoord = projection * view * vec4(hitCoord, 1.0);projectedCoord.xy /= projectedCoord.w;projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5;return vec3(projectedCoord.xy, depth);
}

二分查找流程:

  1. 在粗略命中点附近进行精确查找
  2. 每一步将搜索方向减半(dir *= 0.5)
  3. 根据深度差调整搜索方向
  4. 最终返回精确的屏幕坐标和深度值

6. 关键算法细节

  1. 屏幕空间限制:SSR只能在屏幕可见的范围内工作,无法反射屏幕外的物体。

  2. 射线步进策略

    • 先用大步长快速遍历
    • 发现潜在命中点时切换到二分查找精确确定位置
  3. 深度比较:通过比较射线深度和场景深度确定是否命中表面。

  4. 衰减处理:反射强度随距离衰减,避免远处反射过于明显。

  5. 镜面控制:使用材质的镜面系数控制反射强度,只有高光材质才有强烈反射。

7. 优化考虑

  1. 早期终止:当射线超出屏幕或达到最大距离时提前终止。

  2. 自适应步长:可以根据场景复杂度动态调整步长。

  3. 模糊处理:可以对反射结果进行适当模糊以减少噪点。

  4. 边缘淡化:在屏幕边缘淡化反射效果,掩盖SSR的局限性。

这个SSR实现提供了实时反射效果,特别适合光滑表面和发光物体的倒影渲染,如问题中要求的发光柱子效果。


http://www.mrgr.cn/news/97484.html

相关文章:

  • 【C++】Chapter04<STL部分>:STL标准模板库概要
  • Python数据爬取
  • 从搜索丝滑过渡到动态规划的学习指南
  • 测开八股收集
  • 3D激光轮廓仪知识整理(待补充)
  • 代码随想录算法训练营第十一天
  • 2025-04-07 NO.3 Quest3 MR 配置
  • 《从零搭建Vue3项目实战》(AI辅助搭建Vue3+ElemntPlus后台管理项目)零基础入门系列第二篇:项目创建和初始化
  • RAG中构建个人知识库
  • Python高级爬虫之JS逆向+安卓逆向1.2节: 变量与对象
  • 从传递函数到PID控制器
  • C++20 统一容器擦除:std::erase 和 std::erase_if
  • nacos集群启动问题
  • 2025 年前端与后端开发方向的抉择与展望-优雅草卓伊凡
  • 【Vue】选项卡案例——NBA新闻
  • Linux学习笔记(1) 目录结构与路径描述:对比 Windows 系统差异(期末,期中复习笔记全)
  • 前缀和和差分笔记
  • 【JS】二分查找
  • 【Pandas】pandas DataFrame astype
  • 4月7日随笔