目录

  • 已实现功能
  • 阴影
    • shadowMap
    • PCF
    • PCSS
  • 实现
    • shadowMap
    • PCF
    • PCSS
    • 阴影

GitHub主页:https://github.com/sdpyy1
OpenGLRender:https://github.com/sdpyy1/CppLearn/tree/main/OpenGL

已实现功能

除了上次实现IBL之外,项目目前新增了imGUI的渲染,更方便地进行调试
在这里插入图片描述
可以随意切换IBL贴图、模型控制、灯光控制灯。 并且添加了一个PBR材质的地板。下一步就是实现阴影

阴影

shadowMap

这个东西很简单,直接实现了,不讲原理

PCF

取周围一圈的像素求平均,让阴影更软

PCSS

① Blocker Search 阶段(寻找遮挡者)
目的:估算遮挡物与被遮挡物之间的距离 → 用于计算 penumbra(阴影模糊程度)

步骤:
从当前 fragment 的 light space 坐标,投影到 shadow map 中:projCoords.xy

以该位置为中心,在 shadow map 中进行 小范围采样(通常 3x3 或 5x5):

收集所有 比当前 fragment 深度更小的样本(说明它们挡住了光)

累加这些“遮挡者”的深度值

记录 blocker 数量

若存在 blocker:

计算平均 blocker 深度 avgBlockerDepth

② Penumbra Size 计算阶段(决定模糊程度)
目的:用当前 fragment 深度 与 avgBlockerDepth 的距离估算光源发散导致的阴影模糊程度

③ Filtering 阶段(模糊阴影边缘)
目的:根据 penumbra 大小,用可变范围 PCF 模糊阴影边缘

所以PCSS可以叫自适应PCF

实现

shadowMap

初始化一个FBO用于shadowMap的渲染,创建一张纹理存储深度结果

void ShadowPass::init() {glGenFramebuffers(1, &shadowFBO);// 创建深度纹理glGenTextures(1, &shadowMap);glBindTexture(GL_TEXTURE_2D, shadowMap);glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT,scene.width, scene.height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);// attach 深度纹理到FBOglBindFramebuffer(GL_FRAMEBUFFER, shadowFBO);glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, shadowMap, 0);glDrawBuffer(GL_NONE);glReadBuffer(GL_NONE);glBindFramebuffer(GL_FRAMEBUFFER, 0);isInit = true;
}

下一步就是在光源视角下渲染,首先得找到摄像机的位置,其实就是MVP矩阵的VP用光源而不是用摄像机,下面是对于平行光的shadowMap渲染

void ShadowPass::render() {if (!isInit){std::cout << "shadowPass init" << std::endl;return;}glViewport(0, 0, scene.width, scene.height);glBindFramebuffer(GL_FRAMEBUFFER, shadowFBO);glClear(GL_DEPTH_BUFFER_BIT);//    glCullFace(GL_FRONT); // 可选,防止 Peter-panningshadowShader.bind();glm::mat4 lightProjection, lightView;float orthoSize = 10.0f;float near_plane = 0.1f;float far_plane = 100.0f; // 你可以再根据场景大小动态调整// 平行光使用正交投影lightProjection = glm::ortho(-orthoSize, orthoSize, -orthoSize, orthoSize, near_plane, far_plane);// TODO:只实现了平行光,他的position存储的是方向,而不是位置,所以要取反lightView = glm::lookAt(-scene.lights[0]->position * 10.0f, glm::vec3(0.0f), glm::vec3(0.0, 1.0, 0.0));lightSpaceMatrix = lightProjection * lightView;shadowShader.setMat4("lightSpaceMatrix", lightSpaceMatrix);// 渲染所有模型(只写深度)for (auto& model : scene.models) {glm::mat4 modelMatrix = model.getModelMatrix();shadowShader.setMat4("model", modelMatrix);model.draw(shadowShader);}shadowShader.unBind();
//    glCullFace(GL_BACK);glBindFramebuffer(GL_FRAMEBUFFER, 0);glViewport(0, 0, scene.width, scene.height);
}

顶点着色器,物体要乘以lightSpaceMatrix来转到光源视角下

#version 330 core
layout (location = 0) in vec3 aPos;uniform mat4 model;
uniform mat4 lightSpaceMatrix;void main() {gl_Position = lightSpaceMatrix * model * vec4(aPos, 1.0);
}

片段着色器不需要执行操作

#version 330 core
void main() {// 空着就行,只写深度
}

下一步将生成的shadowMap传递到lightPass来参与光照计算,另外需要把lightMatrix也传递过去,用于把模型距离值转移到视角下来进行比较

float ShadowCalculation(vec3 fragPosWorld, vec3 normal) {vec4 fragPosLightSpace = lightSpaceMatrix * vec4(fragPosWorld, 1.0);vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;projCoords = projCoords * 0.5 + 0.5; // [-1,1] → [0,1]// 从深度贴图采样float closestDepth = texture(shadowMap, projCoords.xy).r;float currentDepth = projCoords.z;// 简单 bias 防止 shadow acne
//    float bias = max(0.005 * (1.0 - dot(normal, normalize(lightPos))), 0.001);// 超出边界不产生阴影if (projCoords.z > 1.0)return 0.0;// 进行一次比较return (currentDepth) > closestDepth ? 1.0 : 0.0;
}

有了计算阴影的函数后,只需要在计算光照的最后*(1-shadow)

    vec3 Lo = (kD * albedo / PI + specular) * radiance * NdotL * (1-ShadowCalculation(WorldPos, N));

结果如下,经典的自阴影现象,主要原因就是shadowMap的分辨率不足,一些不在同一高度的位置被记录了相同高度,在主摄像机渲染时,某个位置在shadowMap存储的高度比自己本来还要高,就会被认为是阴影(但是这种情况下很好分辨光源摄像机的覆盖范围,方便调试🙂)
在这里插入图片描述
当我把shadowMap的分辨率提高后,自然就消失了,但这肯定不是最优在这里插入图片描述
通常的做法是加一个自偏移,也就是说shadowMap存的高度和我用来比较的高度差异不超过bias,就认为没有阴影

float ShadowCalculation(vec3 fragPosWorld, vec3 normal) {vec4 fragPosLightSpace = lightSpaceMatrix * vec4(fragPosWorld, 1.0);vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;projCoords = projCoords * 0.5 + 0.5; // [-1,1] → [0,1]// 从深度贴图采样float closestDepth = texture(shadowMap, projCoords.xy).r;float currentDepth = projCoords.z;// 简单 bias 防止 shadow acnefloat bias = max(0.005 * (1.0 - dot(normal, normalize(lightPos))), 0.001);// 超出边界不产生阴影if (projCoords.z > 1.0)return 0.0;// 进行一次比较return (currentDepth - bias) > closestDepth ? 1.0 : 0.0;
}

在这里插入图片描述

PCF

阴影问题解决了,下面就是提升效果,当前的阴影是硬阴影,锯齿很严重
在这里插入图片描述
在这里插入图片描述
PCF思路就是取周围像素的shadow来取平均,柔化阴影边界

        // --- PCF ---float shadow = 0.0;ivec2 texSize = textureSize(shadowMap, 0);vec2 texelSize = 1.0 /vec2(texSize);int range = 10;  // 5x5int samples = 0;for (int x = -range; x <= range; ++x) {for (int y = -range; y <= range; ++y) {float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r;shadow += (currentDepth - bias > pcfDepth) ? 1.0 : 0.0;samples++;}}shadow /= float(samples);return shadow;

在这里插入图片描述

PCSS

三步走,一些参数我已经提取出去当uniform,可以控制第一步的搜索半径、第二步的半影大小、以及控制一下最大的滤波核大小

        float avgBlockerDepth = 0.0;int blockers = 0;ivec2 texSize = textureSize(shadowMap, 0);vec2 texelSize = 1.0 / vec2(texSize);int searchRadius = int(PCSSBlockerSearchRadius);for (int x = -searchRadius; x <= searchRadius; ++x) {for (int y = -searchRadius; y <= searchRadius; ++y) {float sampleDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r;if (sampleDepth < currentDepth - bias) {avgBlockerDepth += sampleDepth;blockers++;}}}if (blockers == 0) return 0.0;avgBlockerDepth /= blockers;float penumbra = (currentDepth - avgBlockerDepth) * PCSSScale;int kernel = int(clamp(penumbra * float(texSize.x), 1.0, PCSSKernelMax));float shadow = 0.0;for (int x = -kernel; x <= kernel; ++x) {for (int y = -kernel; y <= kernel; ++y) {float sampleDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r;shadow += (currentDepth - bias > sampleDepth) ? 1.0 : 0.0;}}shadow /= float((2 * kernel + 1) * (2 * kernel + 1));return shadow;

阴影

三种阴影可以写在一起

float ShadowCalculation(vec3 fragPosWorld, vec3 normal) {vec4 fragPosLightSpace = lightSpaceMatrix * vec4(fragPosWorld, 1.0);vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;projCoords = projCoords * 0.5 + 0.5;if (projCoords.z > 1.0) return 0.0;float closestDepth = texture(shadowMap, projCoords.xy).r;float currentDepth = projCoords.z;float bias = max(0.005 * (1.0 - dot(normal, normalize(lightPos))), 0.001);// shadow type switchingif (shadowType == 0) {return 0.0; // no shadow}else if (shadowType == 1) {return (currentDepth - bias > closestDepth) ? 1.0 : 0.0; // hard shadow}else if (shadowType == 2) {// --- PCF ---float shadow = 0.0;ivec2 texSize = textureSize(shadowMap, 0);vec2 texelSize = 1.0 /vec2(texSize);int range = pcfScope;int samples = 0;for (int x = -range; x <= range; ++x) {for (int y = -range; y <= range; ++y) {float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r;shadow += (currentDepth - bias > pcfDepth) ? 1.0 : 0.0;samples++;}}shadow /= float(samples);return shadow;}else if (shadowType == 3) {float avgBlockerDepth = 0.0;int blockers = 0;ivec2 texSize = textureSize(shadowMap, 0);vec2 texelSize = 1.0 / vec2(texSize);int searchRadius = int(PCSSBlockerSearchRadius);for (int x = -searchRadius; x <= searchRadius; ++x) {for (int y = -searchRadius; y <= searchRadius; ++y) {float sampleDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r;if (sampleDepth < currentDepth - bias) {avgBlockerDepth += sampleDepth;blockers++;}}}if (blockers == 0) return 0.0;avgBlockerDepth /= blockers;float penumbra = (currentDepth - avgBlockerDepth) * PCSSScale;int kernel = int(clamp(penumbra * float(texSize.x), 1.0, PCSSKernelMax));float shadow = 0.0;for (int x = -kernel; x <= kernel; ++x) {for (int y = -kernel; y <= kernel; ++y) {float sampleDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r;shadow += (currentDepth - bias > sampleDepth) ? 1.0 : 0.0;}}shadow /= float((2 * kernel + 1) * (2 * kernel + 1));return shadow;}return 0.0;
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/90749.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/90749.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/90749.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux:日志乱码

1、Linux日志乱码可能是XShell客户端编码没设置为UTF-8引起的&#xff0c;按照以下步骤&#xff0c;设置终端格式&#xff1a;中文版&#xff1a;打开Xshell会话属性&#xff08;文件→属性→终端→编码&#xff09;&#xff0c;选择与服务器一致的编码格式&#xff08;如UTF-8…

Rouge:面向摘要自动评估的召回导向型指标——原理、演进与应用全景

“以n-gram重叠量化文本生成质量&#xff0c;为摘要评估提供可计算标尺” Rouge&#xff08;Recall-Oriented Understudy for Gisting Evaluation&#xff09; 是由 南加州大学信息科学研究所&#xff08;ISI&#xff09;的Chin-Yew Lin 于2004年提出的自动文本摘要评估指标&am…

[STM32][HAL]stm32wbxx 超声波测距模块实现(HY-SRF05)

前言 在电子技术应用中,距离测量是一个常见且重要的需求。超声波模块因其测量精度较高、成本较低、易于使用等优点,被广泛应用于机器人避障、液位检测、智能停车系统等领域。该文主要讲解以stm32wb芯片为主控,用HAL库来对HY-SRF05超声波模块进行代码编写,实现基本的驱动和测…

MySQL 性能调优实战指南:从诊断到优化全解析

引言在日常的数据库运维工作中&#xff0c;我们经常需要对 MySQL 数据库进行诊断和性能分析。本文将介绍一套全面的 MySQL 诊断脚本&#xff0c;适用于 MySQL 8.0&#xff08;兼容 8.0.15 及以上版本&#xff09;&#xff0c;涵盖事务锁分析、性能瓶颈定位、配置检查、连接状态…

8. 状态模式

目录一、应用背景二、状态模式2.1 解决的问题2.2 角色2.3 实现步骤三、通用设计类图四、实现4.1 设计类图4.2 状态转换图4.3 代码实现一、应用背景 某对象发生变化时&#xff0c;其所能做的操作也随之变化。应用程序的可维护性和重用性差代码的逻辑较复杂 二、状态模式 2.1 …

php语法--foreach和in_array的使用

文章目录foreach基础语法&#xff1a;案例1&#xff1a;引用传递模式&#xff1a;嵌套数组处理&#xff1a;避免在循环中计算数组长度&#xff1a;使用引用减少内存拷贝&#xff1a;打印数组in_array基础使用严格使用foreach 基础语法&#xff1a; foreach ($iterable as $va…

ES6模块详解:核心语法与最佳实践

以下是 EMAScript 6&#xff08;ES6&#xff09;模块规范的核心要点及细节解析&#xff1a; &#x1f4e6; 一、核心语法导出&#xff08;export&#xff09; 命名导出&#xff1a;支持导出多个具名成员。export const a 1; export function b() { /* ... */ } // 或集中导出 …

Python day25

浙大疏锦行 Python day25. 内容&#xff1a; 异常处理&#xff0c;在日常的编码工作过程中&#xff0c;为了避免由于各种bug导致的异常情况&#xff0c;我们需要引入异常处理机制&#xff0c;它的工作场景是当程序运行出现意外时&#xff0c;可以根据编码规则处理响应的错误。…

mac llama_index agent算术式子计算示例

本文通过简单数学计算&#xff0c;示例llama_index使用agent解决复杂任务过程。 假设mac本地llama_index环境已安装&#xff0c;过程参考 mac测试ollama llamaindex-CSDN博客 测试mac笔记本内存8G&#xff0c;所以使用较小LLM完成示例。 ollama pull qwen3:1.7b qwen3:1.7b能…

uni-app小程序云效持续集成

创建项目 必须是 cli 命令行创建的 uni-app 小程序项目参考uni-app官方构建命令&#xff1a; npx degit dcloudio/uni-preset-vue#vite-ts my-vue3-project生成小程序代码上传密钥 管理-开发设置-小程序代码上传生成的文件放在根目录即可 安装持续集成插件 pnpm install uni-mi…

uniapp+高德地图实现打卡签到、打卡日历

一、注册高德地图。应用管理创建应用&#xff0c;分别添加Andriod平台、Web服务、Web端、微信小程序四种类型的key。二、考勤规则打卡地点选择位置代码&#xff1a;<script setup lang"ts"> import { onMounted, onUnmounted, reactive, ref, watchEffect } fr…

CentOS 7.9 + GCC9 离线安装 IWYU(Include What You Use)

本教程适用于 离线环境下在 CentOS 7.9 系统中使用 GCC 9 离线安装 IWYU 的完整步骤&#xff0c;涵盖 Clang 11.1.0 编译、IWYU 构建以及头文件自动优化流程。&#x1f4e5; 一、准备安装包请提前下载以下源码包&#xff08;可通过在线机器提前下载&#xff0c;再传输到离线环境…

基于Dapr Sidecar的微服务通信框架设计与性能优化实践

基于Dapr Sidecar的微服务通信框架设计与性能优化实践 一、技术背景与应用场景 随着微服务架构的广泛应用&#xff0c;分布式系统中服务间通信、可观察性、可靠性等问题日益凸显。Dapr&#xff08;Distributed Application Runtime&#xff09;作为一个开源的微服务运行时&…

Claude Code 超详细完整指南(2025最新版)

&#x1f680; 终端AI编程助手 | 高频使用点 生态工具 完整命令参考 最新MCP配置 &#x1f4cb; 目录 &#x1f3af; 快速开始&#xff08;5分钟上手&#xff09;&#x1f4e6; 详细安装指南 系统要求Windows安装&#xff08;WSL方案&#xff09;macOS安装Linux安装安装验…

【lucene】SegmentReader初始化过程概述

readers[i] new SegmentReader(sis.info(i), sis.getIndexCreatedVersionMajor(), IOContext.READ); 这个方法已经把所有的文件都读完了么&#xff1f;没有“读完”&#xff0c;但已经**全部“打开”**了。| 动作 | 是否发生 | |---|---| | **打开文件句柄 / mmap** | ✅ 立即完…

通俗理解主机的BIOS和UEFI启动方式

“对于 22.04 版本&#xff0c;这些操作说明应适用于通过 BIOS 或 UEFI 两种方式创建和运行启动盘。”我们来详细解释一下这句话的含义&#xff0c;这句话的核心意思是&#xff1a;你按照这个教程制作出来的 Ubuntu U 盘&#xff0c;将拥有极佳的兼容性&#xff0c;无论是在老电…

Canal 1.1.7的安装

数据库操作的准备 1、开启 Binlog 写入功能&#xff0c;配置 binlog-format 为 ROW 模式&#xff0c;my.cnf 中配置如下: vi /etc/my.cnf [mysqld] log-binmysql-bin # 开启 binlog binlog-formatROW # 选择 ROW 模式 server_id1 # 配置 MySQL replaction 需要定义&#xff0c;…

python---类型转换

文章目录1. 基本类型转换函数int() - 转换为整数float() - 转换为浮点数str() - 转换为字符串bool() - 转换为布尔值2. 其他类型转换list() - 转换为列表tuple() - 转换为元组set() - 转换为集合&#xff08;去重&#xff09;dict() - 转换为字典3. 注意事项1. 兼容性&#xff…

JVM terminated. Exit code=1

出现JVM terminated. Exit code1错误通常是因为 Eclipse 所需的 Java 版本与系统中配置的 Java 版本不匹配。从错误信息中可以看到关键线索&#xff1a;-Dosgi.requiredJavaVersion21&#xff0c;表示此 Eclipse 版本需要 Java 21 或更高版本&#xff0c;但系统当前使用的是 Ja…

20250727-1-Kubernetes 网络-Ingress介绍,部署Ingres_笔记

一、NodePort存在的不足 1. 四层负载均衡  实现技术: 基于iptables和ipvs实现 OSI层级: 位于传输层(第四层) 转发依据: 基于IP地址和端口进行转发 特点: 只能看到IP和端口信息 无法识别应用层协议内容 配置简单但功能有限 2. 七层负载均衡 1)七层负载均衡的概念 …