Hi there 👋

Welcome to Loyio’s Blog

高级实时阴影技术 - 基于DX12

Deep Research By Google Gemini :) TLDR, 直接查看交互网站 link 引言 阴影的必要性 在实时三维计算机图形学中,阴影并非简单的视觉点缀,而是构建可信、沉浸式虚拟世界的基石 1。动态阴影为场景提供了至关重要的深度感和空间一致性线索,使用户能够直观地理解物体之间的相对位置、距离和场景的整体光照环境 2。一个没有阴影的场景往往显得扁平、不真实,物体如同悬浮在空中。随着现代游戏引擎越来越多地采用完全动态的光照和交互式环境,对高效、逼真且可扩展的实时阴影解决方案的需求已变得空前迫切 3。 现代渲染图景 游戏图形技术已经从主要依赖预计算(烘焙)静态光照的时代,演进到了一个以动态光照和材质为主导的新阶段。玩家期望看到动态变化的时间周期、可破坏的环境以及与光照实时交互的角色和物体。这种转变使得传统的静态光照贴图(Lightmap)技术捉襟见肘,实时动态阴影渲染成为了现代渲染管线的核心挑战之一 3。 报告目标与用户背景 本报告旨在为图形程序员和引擎开发者提供一份关于现代实时阴影算法的全面、深入的技术指南。我们将系统性地剖析各种主流及前沿阴影技术的原理、实现细节、性能特征以及视觉效果。报告将特别关注在Windows平台下,以DirectX 12(DX12)作为渲染硬件接口(RHI)的实现上下文,这与您的开发环境完全契合。 当前,存在着多种阴影渲染算法,从近似但高效的方法到精确但计算密集的方法,它们各自适用于不同的光源类型、场景规模和性能预算 4。对于开发者而言,理解这些技术的优缺点、局限性及适用场景,是做出正确技术选型、构建稳定高效阴影系统的关键。本报告将为您梳理这一复杂的技术图谱,助您将引擎中尚待启用的阴影功能,转化为一套真正健壮、高质量且可投入生产的解决方案。 第一部分:奠基石——阴影贴图(Shadow Mapping) 阴影贴图(Shadow Mapping, SM)是绝大多数现代光栅化阴影技术的基础。深刻理解其核心原理及其固有的局限性,是掌握后续更高级算法演进逻辑的先决条件。 1.1. 核心原理:从光源视角进行渲染 阴影贴图技术的核心思想源于一个简单的物理直觉:一个点如果能被光源直接“看到”,那么它就是被照亮的;反之,如果它被其他物体遮挡,那么它就处于阴影之中。这一概念在图形学中被称为“阴影/视图二元性”(Shadow/View Duality)5。 该技术通过一个经典的两遍(Two-Pass)算法实现 1: 第一遍(阴影贴图生成): 将虚拟“摄像机”放置在光源的位置和方向,渲染整个场景。 在此过程中,我们不关心物体的颜色信息,只关心深度。渲染管线会将每个片元(fragment)距离光源的深度值写入一个专门的纹理中。这个纹理就是阴影贴图(Shadow Map),或称为深度图(Depth Map)1。 为了优化性能,这一遍通常会禁用颜色缓冲区的写入,因为内存带宽往往是GPU的主要性能瓶颈之一 6。在DirectX 12中,这可以通过在 D3D12_GRAPHICS_PIPELINE_STATE_DESC中设置一个空(null)的像素着色器(Pixel Shader)来实现。 第二遍(场景渲染与阴影测试): 将摄像机移回玩家的正常视点,正常渲染场景。 对于场景中的每一个被渲染的片元,首先计算其在世界空间中的位置。 然后,使用与第一遍中完全相同的光源视图-投影矩阵,将该片元的世界坐标变换到光源的裁剪空间(Light’s Clip Space)。 将变换后的坐标进行透视除法,得到其在光源视角下的归一化设备坐标(NDC)和深度值。NDC的XY分量用于在阴影贴图中采样,Z分量则代表了该片元在光源空间中的深度。 采样阴影贴图,获取在相同XY坐标下,离光源最近的物体的深度值(即第一遍中存储的值)。 比较当前片元的光源空间深度与从阴影贴图中采样出的深度。如果当前片元的深度大于采样深度,说明在它与光源之间存在一个更近的物体(遮挡物),因此该片元处于阴影中;否则,它被照亮 1。 以下是一个简化的HLSL(High-Level Shading Language)代码片段,用于说明其核心逻辑: // 全局常量 cbuffer ShadowConstants { matrix g_LightViewProj; // 光源的视图-投影矩阵 }; Texture2D g_ShadowMap : register(t0); SamplerState g_ShadowSampler : register(s0); // ================================================= // Pass 1: 阴影贴图生成 // ================================================= // 顶点着色器 (VS) struct VS_SHADOW_OUTPUT { float4 Position : SV_Position; }; VS_SHADOW_OUTPUT ShadowMapVS(float4 WorldPos : POSITION) { VS_SHADOW_OUTPUT output; // 将顶点变换到光源的裁剪空间 output.Position = mul(g_LightViewProj, WorldPos); return output; } // 像素着色器 (PS) // 在DX12中,此阶段可以设置为nullptr来仅进行深度写入。 // 如果需要写入特定格式(如线性深度),则需要一个简单的PS。 float4 ShadowMapPS(VS_SHADOW_OUTPUT input) : SV_Target { // 不需要输出颜色,仅深度缓冲起作用。 // 或者,对于某些技术,可以返回深度值到颜色缓冲。 return float4(input.Position.z / input.Position.w, 0, 0, 1); } // ================================================= // Pass 2: 场景渲染与阴影测试 // ================================================= struct VS_SCENE_OUTPUT { float4 PositionCS : SV_Position; // 摄像机裁剪空间位置 float4 PositionLS : TEXCOORD0; // 光源裁剪空间位置 //... 其他插值,如法线、纹理坐标等 }; // 场景渲染的像素着色器 (PS) float4 ScenePS(VS_SCENE_OUTPUT input) : SV_Target { //... 省略常规光照计算... // 1. 变换到光源的NDC空间 float3 shadowCoord = input.PositionLS.xyz / input.PositionLS.w; // 2. 将NDC坐标从[-1, 1]映射到纹理坐标 // 注意:DX的纹理坐标原点在左上角 shadowCoord.x = shadowCoord.x * 0.5f + 0.5f; shadowCoord.y = shadowCoord.y * -0.5f + 0.5f; // 3. 采样阴影贴图 float closestDepth = g_ShadowMap.Sample(g_ShadowSampler, shadowCoord.xy); // 4. 深度比较 float currentDepth = shadowCoord.z; float shadowFactor = (currentDepth > closestDepth)? 0.0f : 1.0f; // 将阴影因子应用到最终颜色 float3 finalColor = AmbientColor + shadowFactor * (DiffuseColor + SpecularColor); return float4(finalColor, 1.0f); } 1.2. 固有的瑕疵与缓解策略 阴影贴图的简洁性是以一系列固有的视觉瑕疵为代价的。理解这些瑕疵的根源至关重要,因为它们并非“bug”,而是该近似算法的内在属性。 ...

June 16, 2025 · 8 min · Loyio Hex

新的开始-学会多角度的思考方式

本文部分内容由 AI 辅助生成,并由本人审核整理。 三年前,我走出校园,踏入职场。那时的我,热情洋溢、目标明确,却也带着些许稚气与单一视角。如今,三年过去,我开始重新审视自己——不仅是一个技术人,更是一个需要不断成长的思考者。 🎯 单一思维的局限 刚开始工作的那段时间,我总是习惯用“对”与“错”去判断事情的结果:项目延期就是失败,方案被否决就是能力不足,沟通碰壁就是对方不理解我。 但后来我渐渐意识到,现实远比课堂复杂。技术实现的背后,有资源的权衡、有时间的博弈;而沟通的背后,更涉及角色的定位、利益的考量和长期的合作策略。 我意识到,如果总是站在一个角度看问题,结果往往就是钻牛角尖、心态崩溃,甚至错失成长的机会。 🔍 多角度思考,意味着什么? 学会多角度思考,并不等于放弃立场,而是一种认知层次的提升: 从技术视角 看问题:方案是否稳健?是否具备可维护性? 从产品视角 看问题:用户真正需要什么?使用场景是否清晰? 从团队视角 看问题:是否利于协作?是否能降低沟通成本? 从管理视角 看问题:风险是否可控?决策是否具备长远价值? 当我开始尝试从不同角度去看待每一个问题,不仅解决方案变得更加全面,我的表达也更具说服力,团队的信任度也随之提升。 🌱 改变的开始,始于一次“失败” 印象最深的是一次重要的模块重构项目,我自信满满地提交了重构设计,但却被团队否决了。当时我很不服,觉得“你们就是不懂技术”。 但冷静下来后,我重新读了项目需求,也听取了产品经理和 QA 的反馈——原来我的设计虽然“技术上优雅”,但对实际业务的适配不够灵活,修改成本高,风险大。 那一刻我第一次真正体会到,多角度思考不是“迁就”,而是“全面掌握”。 🧭 三年后的我,新的开始 今天的我,已经不再是那个只关心代码是否优雅的新人。我开始关心: 为什么这个需求存在? 用户真正的痛点是什么? 技术之外,有没有更简单的路径? 我提出的意见,是否考虑到了对方的角色立场? 三年工作让我懂得:技术的成长,是基础;思维的转变,才是质变的开始。 ✅ 写在最后 新的开始,不一定是离职、转行、创业。对我而言,是每一天都提醒自己: 放下固有立场,尝试从更多角度看世界。 这不仅让我成为一个更专业的开发者,也让我成为一个更成熟、更有温度的人。 如果你也正处在职业的十字路口,希望这篇文章能带给你一点点共鸣,也祝你在新的起点上,走得更坚定、更从容 🌟

April 18, 2025 · 1 min · Loyio Hex

养成每天阅读的习惯 - 对于自己的告诫

周三部门leader给开了个培训会,然后会前搞了个分组小游戏,也算是加强部门中不同组之间的交流。 正常上班人与人之间的交集还是比较少的,同公司不同部门基本上不会有交集,当然我作为中台部门,自然跟项目组会有打交道的时候。 同部门不同组基本上不认识,这在中台部门应该是比较常见的,每个组涉及到的项目天差地别,很难有共同话题。再说,组内基本上也都是抱团取暖,能有一个知心朋友我觉的就很不错了,组内大多都是工作上的交流。 前面扯了一下工作人际关系,有些偏题了,leader在会上其实分享了很多,一个很重要的主题就是告诉我们要坚持,所有困难都是正常的。通过讲述自己的故事来给我们“提高自信“,当然大家都知道故事稍微会有一点润色。实际上,就拿我本人来说,差距太大了。不论是学历上、技术水平上、个人意志上是完全够不上大佬们的。从这两三个月的职场中来看,我就是一个不思进取、只会摆烂、甘心躺平的无志青年。需要不停反思,才能把自己给骂醒。 职场中来看,最重要的三点也就是我们的考核标准,结果、协作、学习。不管什么行业都离不开这三点。比较深入的来说,我最看重的是效率。没有效率,一切都是白费的,最终这三点也都没有了意义。 想着去改变、去拼,首先还是得从学习开始,需要培养自己的兴趣,这样才不至于阅读的时候总想打瞌睡。所以还是得提高自己目前技术栈的兴趣,然后强迫自己去接受新的知识。才能保证自己不被淘汰。 就喜欢瞎写一些没有逻辑的话。以后再增删改吧。

September 17, 2022 · 1 min · Loyio Hex

学习OpenGL - 摄像机

LookAt via GIPHY 自由移动 via GIPHY 鼠标输入 via GIPHY

September 13, 2022 · 1 min · Loyio Hex

学习OpenGL - 坐标系统

进入3D 更加3D 非常奇怪的立方体,没有加Z-buffer 加了Z-buffer之后,一个正常的立方体 添加更多的立方体

September 10, 2022 · 1 min · Loyio Hex

学习OpenGL简单的变换

简单的旋转与缩小 这里更改了Y轴的方向,变成了向右侧旋转 TexCoord = vec2(aTexCoord.x, 1 - aTexCoord.y); 随时间旋转

September 10, 2022 · 1 min · Loyio Hex

学习OpenGL纹理

应用纹理 纹理颜色与顶点颜色混合 纹理单元 翻转Y轴

September 6, 2022 · 1 min · Loyio Hex

学习着色器Shader - GLSL

输入与输出 Uniform(随时间变化) 更多属性 然后就是Shader类,这个最后我生成的如上所示 因为需要读取shader文件,这里不想使用绝对路径的,可以自己设置一下Xcode的Working Directory 在Product->Scheme->Edit Scheme,下面是我的设置。根据你自己的项目所在路径进行设置。

September 5, 2022 · 1 min · Loyio Hex

Learn OpenGL - Hello Triangle (M1Pro 环境环境配置)

虽然说苹果之前已经说弃用OpenGL了,全部改用Metal,但目前还是自带OpenGL库的 首先开始是环境的配置,我这里直接放弃了使用CMake,又重新把Xcode装了回来。 首先安装glfw,glew brew install glfw brew install glew 然后使用brew info ***就可以查看安装的具体路径在哪里,后续需要用到 然后是glad,进入网站gl我选择的是3.3,然后Profile设置为Core,下面注意是否勾选了Generate a loader,然后生成即可,然后我把下载下来的include里面的文件夹放到了/usr/local/include这个目录,后续也需要用到。 打开Xcode,新建一个C++命令行项目,在target项目设置哪里,选择Build Settings找到Header Search Paths(在Search Paths下面),添加两个路径,一个就是homebrew的头文件的路径,另一个就是local的头文件路径,以下仅供参考 /opt/homebrew/include /usr/local/include 然后在进入Build Phase,开始配置链接库,如下图所示 上面两个的路径就是homebrew安装库的路径,这些dylib文件就在这个文件夹下面,我上面截图用红框框住的。下面两个就是自带的直接选就行了 然后记得把之前glad生成文件src下面的glad.c文件复制进来。 注意事项 Mac 苹果芯片环境,需要注意下面几点,首先在申明的时候,需要设置GLFW_OPENGL_FORWARD_COMPAT,教程里面也说了 #ifdef __APPLE__ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); #endif 然后就是在创建窗口之后需要再加上这么一行 #ifdef __APPLE__ glfwMakeContextCurrent(window); #endif 然后还有就是可能会报这么一个错 需要禁止Library的验证,如果没有Hardened Runtime这个,点击左上角的+ Capability即可 当改完之后,可能还会报这么一个错 这时候选择执行Product下面的Clean Build Folder就可以了 效果展示 三角形 ...

September 4, 2022 · 1 min · Loyio Hex

直面差距,虚心求教 --- 流水账

工作两个月了,基本没有时间去停下来好好思考一下目前的一个状态,以及之后的一个规划。 这周五在组长那里学到了很多,聊了也差不多一个小时,基本上我是一个啥也不会的状态,组长也在不停的迁就我,引导我如何去学习。 想想如果还是这样的状态,必定走不远。也是时候认真起来了。 第一个月 本人就是一个非常普通的人,来自普通的学校,能力也很普通,误打误撞进了公司,然后接触了自己并不是很熟悉的行业,组里的规划还是很清晰的,一开始就是让做一个小项目,穿插着进行一些学习计划。但基本上工作日能用来学习的时间我根本挤不出来。我总是太专注于某一件事,而不想着从多个方面去提升自己。 导师还是非常负责的,从一开始做需求分析、项目设计文档的时候,就是改了一版又一版,本以为软件工程的知识以后用到的情况会很少,程序员就蒙头写代码就行了,然而实际上文档必不可少,一开始,我连鲁棒图是啥都不记得了,设计模式该如何正确选择也摸不清头脑。还是导师一步一步带我重新回顾需求分析与设计的核心,还学到了一个重点,就是这一切的一切都是为了让自己不背锅。 我之前的想法基本上都是走一步看一步,不会关注于规划上,程序开发中,只是会在开始时想一下要做一个什么样的效果,而对于设计,总是迷茫的,遇到一个问题解决一个问题。这或许也就跟我的大学生活是一样的,总是看到什么觉得挺感兴趣的,然后就会在之后一段时间里去做这件事。从一开始的前端框架,再到Java, .NET,然后到是Python语言,逐渐接触到机器学习知识,后面又学了Swift做iOS、Mac应用,再到最后又回归本心,用C++刷算法题,重新喜欢上使用C++,当前中间也不知道经历过多少个其他的想法,参加过微信小程序比赛、设计过CPU MIPS流水线、试着自己从零写一个操作系统等。当然很多都是浅尝辄止。(当然我指的这些不是说大学专业课程的那些要求) 这样也就导致啥都接触过一点,会一点,但一直没找到自己真的擅长的、喜欢做的。我这没有规划的人生就如shit一般。虽然可能在我的一些同学中,他们都会觉得我怎么啥都会,但实际上人生如戏,我只是扮演着这么一个角色。 回到工作上,开发的过程中,实际上也越到过很多问题,我的导师是要求我必须每天开发出来其中一个部分,然后给他测试,实际上,我根本没法在一开始就考虑所有情况,往往是一部分还没有非常完善就转向下一部分,这与我导师的想法是背道而驰,做一个功能就一定要把这个功能做到极致,完美贴合需求。而我是想着是在开发过程中,开发当前模块时,也不断的完善之前的模块,思考之后的模块。我的很多观念,有时候我自己也找不出原因,而我又是一个极端固执的人,这个习惯必须得改。 跳到最后,导师检查的时候,我发现我的导师对我的要求总是要比其他新入职的员工高,同时这也是一个导师考核制度,需要导师打分,明明大家都不需要完成的一个需求,到我这就变成了一个考核项,导师测试的时候,遇到一个bug,然后就会打回去让我改,改完之后才继续进行测试。这样的效率是非常低的,本来一个一天就能测完的,大概又要花一两周。不过听他说这是在给我机会,测出来的bug越多,自然分数就越低。所以自测必须要非常仔细,然而我的导师总是有非常多新颖的测试用例。 我也与导师争论过,当时一个需求,我觉得就很奇怪,我也问了其他同期入职做新人项目的,他们都没这要求,然而让我必须完成这个,不然又要扣分,当时想着,这种机制很不公平,想要报告给组长,不过这种行为实在太幼稚了,我想想都觉得好天真啊。每个导师都有自己的一个想法,有自己的一套理念,各有各的好处吧。就这样吧,关于这个新人项目就讲到这里。 正式接触工作 一开始都是一些修改bug的任务,分析需求,从代码中找出出现问题的地方,然后进行修改。后续又做了一个工具,本以为能够很快的做出来,然而对于框架不太熟悉,也花了一段时间。

September 3, 2022 · 1 min · Loyio Hex