【Overload游戏引擎细节分析】PBR材质Shader---完结篇
PBR基于物理的渲染可以实现更加真实的效果,其Shader值得分析一下。但PBR需要较多的基础知识,不适合不会OpenGL的朋友。
一、PBR理论
PBR指基于物理的渲染,其理论较多,需要的基础知识也较多,我在这就不再写一遍了,具体可以参看:
LearnOpenGL PBR理论-英文 或者 LearnOpenGL PBR理论-中文
Overload也提供了这种材料,借助贴图可以实现非常真实的材质效果。下面这个例子的贴图来自LearnOpenGL,大家可以自己去下载。

二、PBR Shader分析
顶点着色器
#shader vertex
#version 430 corelayout (location = 0) in vec3 geo_Pos;
layout (location = 1) in vec2 geo_TexCoords;
layout (location = 2) in vec3 geo_Normal;
layout (location = 3) in vec3 geo_Tangent;
layout (location = 4) in vec3 geo_Bitangent;/* Global information sent by the engine */
layout (std140) uniform EngineUBO
{mat4 ubo_Model;mat4 ubo_View;mat4 ubo_Projection;vec3 ubo_ViewPos;float ubo_Time;
};/* Information passed to the fragment shader */
out VS_OUT
{vec3 FragPos;vec3 Normal;vec2 TexCoords;mat3 TBN;flat vec3 TangentViewPos;vec3 TangentFragPos;
} vs_out;void main()
{vs_out.TBN = mat3(normalize(vec3(ubo_Model * vec4(geo_Tangent, 0.0))),normalize(vec3(ubo_Model * vec4(geo_Bitangent, 0.0))),normalize(vec3(ubo_Model * vec4(geo_Normal, 0.0))));mat3 TBNi = transpose(vs_out.TBN);vs_out.FragPos = vec3(ubo_Model * vec4(geo_Pos, 1.0));vs_out.Normal = normalize(mat3(transpose(inverse(ubo_Model))) * geo_Normal);vs_out.TexCoords = geo_TexCoords;vs_out.TangentViewPos = TBNi * ubo_ViewPos;vs_out.TangentFragPos = TBNi * vs_out.FragPos;gl_Position = ubo_Projection * ubo_View * vec4(vs_out.FragPos, 1.0);
}
顶点着色器基本与standard材质一致,这里就不再分析了,具体可看standard材质Shader
片元着色器:
#shader fragment
#version 430 core/** 模型视图矩阵、摄像机位置,使用UBO传入 */
/* Global information sent by the engine */
layout (std140) uniform EngineUBO
{mat4 ubo_Model;mat4 ubo_View;mat4 ubo_Projection;vec3 ubo_ViewPos;float ubo_Time;
};/* 顶点着色器的输出 */
/* Information passed from the fragment shader */
in VS_OUT
{vec3 FragPos;vec3 Normal;vec2 TexCoords;mat3 TBN;flat vec3 TangentViewPos;vec3 TangentFragPos;
} fs_in;/* 光源数据用SSBO传入 */
/* Light information sent by the engine */
layout(std430, binding = 0) buffer LightSSBO
{mat4 ssbo_Lights[];
};out vec4 FRAGMENT_COLOR;uniform sampler2D u_AlbedoMap; // 反照率贴图
uniform sampler2D u_MetallicMap; // 金属度贴图
uniform sampler2D u_RoughnessMap; // 粗糙度贴图
uniform sampler2D u_AmbientOcclusionMap; // 环境光遮蔽贴图
uniform sampler2D u_NormalMap; // 法线贴图
uniform vec4 u_Albedo = vec4(1.0); // 反照率系数,控制反照率贴图的权重
uniform vec2 u_TextureTiling = vec2(1.0, 1.0);
uniform vec2 u_TextureOffset = vec2(0.0, 0.0);
uniform bool u_EnableNormalMapping = false; // 是否使用法线贴图
uniform float u_HeightScale = 0.0;
uniform float u_Metallic = 1.0; // 金属度
uniform float u_Roughness = 1.0; // 粗糙度const float PI = 3.14159265359;// 计算法向分布函数D,使用Trowbridge-Reitz GGX
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;
}// Smith’s method
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 ggx2 = GeometrySchlickGGX(NdotV, roughness);float ggx1 = GeometrySchlickGGX(NdotL, roughness);return ggx1 * ggx2;
}// 菲涅尔项,使用Fresnel-Schlick方程
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}/* 将32位数字变成RGBA颜色 */
vec3 UnPack(float p_Target)
{return vec3(// CPU传入的数据是0-255,转换成0-1.0float((uint(p_Target) >> 24) & 0xff) * 0.003921568627451,float((uint(p_Target) >> 16) & 0xff) * 0.003921568627451,float((uint(p_Target) >> 8) & 0xff) * 0.003921568627451);
}bool PointInAABB(vec3 p_Point, vec3 p_AabbCenter, vec3 p_AabbHalfSize)
{return(p_Point.x > p_AabbCenter.x - p_AabbHalfSize.x && p_Point.x < p_AabbCenter.x + p_AabbHalfSize.x &&p_Point.y > p_AabbCenter.y - p_AabbHalfSize.y && p_Point.y < p_AabbCenter.y + p_AabbHalfSize.y &&p_Point.z > p_AabbCenter.z - p_AabbHalfSize.z && p_Point.z < p_AabbCenter.z + p_AabbHalfSize.z);
}/*光照衰减系数,LearnOpenGL中有具体公式*/
float LuminosityFromAttenuation(mat4 p_Light)
{const vec3 lightPosition = p_Light[0].rgb;const float constant = p_Light[0][3];const float linear = p_Light[1][3];const float quadratic = p_Light[2][3];const float distanceToLight = length(lightPosition - fs_in.FragPos);const float attenuation = (constant + linear * distanceToLight + quadratic * (distanceToLight * distanceToLight));return 1.0 / attenuation;
}/* 盒状环境光 */
vec3 CalcAmbientBoxLight(mat4 p_Light)
{const vec3 lightPosition = p_Light[0].rgb;const vec3 lightColor = UnPack(p_Light[2][0]);const float intensity = p_Light[3][3];const vec3 size = vec3(p_Light[0][3], p_Light[1][3], p_Light[2][3]);return PointInAABB(fs_in.FragPos, lightPosition, size) ? lightColor * intensity : vec3(0.0);
}/* 球状环境光 */
vec3 CalcAmbientSphereLight(mat4 p_Light)
{const vec3 lightPosition = p_Light[0].rgb;const vec3 lightColor = UnPack(p_Light[2][0]);const float intensity = p_Light[3][3];const float radius = p_Light[0][3];return distance(lightPosition, fs_in.FragPos) <= radius ? lightColor * intensity : vec3(0.0);
}void main()
{vec2 texCoords = u_TextureOffset + vec2(mod(fs_in.TexCoords.x * u_TextureTiling.x, 1), mod(fs_in.TexCoords.y * u_TextureTiling.y, 1));vec4 albedoRGBA = texture(u_AlbedoMap, texCoords) * u_Albedo; // Albedo反照率贴图数据vec3 albedo = pow(albedoRGBA.rgb, vec3(2.2)); // 这种反照率处理方式与LearOpenGL一致float metallic = texture(u_MetallicMap, texCoords).r * u_Metallic; // 金属度float roughness = texture(u_RoughnessMap, texCoords).r * u_Roughness; // 粗糙度float ao = texture(u_AmbientOcclusionMap, texCoords).r; // 环境光遮蔽AOvec3 normal;if (u_EnableNormalMapping) // 是否使用法线贴图{normal = texture(u_NormalMap, texCoords).rgb; // 法线贴图的原始值normal = normalize(normal * 2.0 - 1.0); // 法线贴图矢量坐标范围变成-1到1normal = normalize(fs_in.TBN * normal); // 变换到全局坐标系下}else{normal = normalize(fs_in.Normal); // 使用顶点着色器输出的法线}vec3 N = normalize(normal); vec3 V = normalize(ubo_ViewPos - fs_in.FragPos); // 计算视线方向vec3 F0 = vec3(0.04); F0 = mix(F0, albedo, metallic); // 插值方式得到平面的基础反射率F0// reflectance equationvec3 Lo = vec3(0.0);vec3 ambientSum = vec3(0.0); // 环境光结果for (int i = 0; i < ssbo_Lights.length(); ++i) {// 两种环境光灯光if (int(ssbo_Lights[i][3][0]) == 3){ambientSum += CalcAmbientBoxLight(ssbo_Lights[i]);}else if (int(ssbo_Lights[i][3][0]) == 4){ambientSum += CalcAmbientSphereLight(ssbo_Lights[i]);}else{// calculate per-light radiance// 光源方向vec3 L = int(ssbo_Lights[i][3][0]) == 1 ? -ssbo_Lights[i][1].rgb : normalize(ssbo_Lights[i][0].rgb - fs_in.FragPos);vec3 H = normalize(V + L);// 半程向量float distance = length(ssbo_Lights[i][0].rgb - fs_in.FragPos);float lightCoeff = 0.0; // 最终到片元处的光强系数 switch(int(ssbo_Lights[i][3][0])){case 0:lightCoeff = LuminosityFromAttenuation(ssbo_Lights[i]) * ssbo_Lights[i][3][3]; // 点光源要考虑随距离衰减break;case 1:lightCoeff = ssbo_Lights[i][3][3]; // 方向光无衰减break;// 聚光灯的计算case 2:const vec3 lightForward = ssbo_Lights[i][1].rgb;const float cutOff = cos(radians(ssbo_Lights[i][3][1]));const float outerCutOff = cos(radians(ssbo_Lights[i][3][1] + ssbo_Lights[i][3][2]));const vec3 lightDirection = normalize(ssbo_Lights[i][0].rgb - fs_in.FragPos);const float luminosity = LuminosityFromAttenuation(ssbo_Lights[i]);/* Calculate the spot intensity */const float theta = dot(lightDirection, normalize(-lightForward)); const float epsilon = cutOff - outerCutOff;const float spotIntensity = clamp((theta - outerCutOff) / epsilon, 0.0, 1.0);lightCoeff = luminosity * spotIntensity * ssbo_Lights[i][3][3];break;}vec3 radiance = UnPack(ssbo_Lights[i][2][0]) * lightCoeff;// cook-torrance brdffloat NDF = DistributionGGX(N, H, roughness); // 法线分布函数float G = GeometrySmith(N, V, L, roughness); // 几何函数vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); // 菲涅尔项vec3 kS = F;vec3 kD = vec3(1.0) - kS;kD *= 1.0 - metallic;vec3 numerator = NDF * G * F;float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0);vec3 specular = numerator / max(denominator, 0.001);// add to outgoing radiance Lofloat NdotL = max(dot(N, L), 0.0);Lo += (kD * albedo / PI + specular) * radiance * NdotL; }}vec3 ambient = ambientSum * albedo * ao;// 环境光最终贡献vec3 color = ambient + Lo; // 环境光与cook-torrance模型累加// HDR色调映射color = color / (color + vec3(1.0));// gamma 矫正color = pow(color, vec3(1.0/2.2)); FRAGMENT_COLOR = vec4(color, albedoRGBA.a); // alpha使用反照率贴图
}
Fragment Shader大体分为三部分:
- 从贴图中获取反照率、金属度、粗糙度、法线数据
- 计算灯光光照,环境光灯光只影响环境光;方向光、聚光灯、点光源会影响光强lightCoeff,最终的光照使用cook-torrance模型进行计算,公式可以参考LearnOpenGL
- 最后进行环境光与PBR模型结果进行叠加,并进行色调映射与gamma矫正,这里使用的公式在LearnOpenGL中都有的
总结:
这个PBR Shader整体上与LearnOpenGL中的理论一致,看完LearnOpenGL之后再看这个Shader就比较简单了。
完结总结:
写的这里,这个专栏暂时告一段落了,主要分析了Overload的Render模块,其他的包括UI、物理引擎、音频等模块没有涉及。Overload是一个Demo性质的游戏引擎,其渲染涉只涉及最基础的渲染方式,是对OpenGL简单封装,远远满足不了实际游戏开发需求,只能作为渲染引擎入门。
另外,这个专栏的文章只聚焦一些细节,对应架构涉及很少,因为本人发现架构方面的文章参考性不大,一旦一个软件定型架构方面的改动很困难,读了软件架构的文章也很难在工作中用上。故单纯只介绍一个技术点反而可能拿来直接使用。最后希望能对大家有所帮助!
相关文章:
【Overload游戏引擎细节分析】PBR材质Shader---完结篇
PBR基于物理的渲染可以实现更加真实的效果,其Shader值得分析一下。但PBR需要较多的基础知识,不适合不会OpenGL的朋友。 一、PBR理论 PBR指基于物理的渲染,其理论较多,需要的基础知识也较多,我在这就不再写一遍了&…...
C++设计模式_18_State 状态模式
State和Memento被归为“状态变化”模式。 文章目录 1. “状态变化”模式1.1 典型模式 2. 动机 (Motivation)3. 代码演示State 状态模式3.1 常规方式3.2 State 状态模式 4. 模式定义5. 结构( Structure )6. 要点总结7. 其他参考 1. “状态变化”模式 在组件构建过程中…...
详解final, abstract, interface关键字
一.final关键字 1.final关键字介绍 ——final关键字可以去修饰类、方法、属性和局部变量 2.final关键字的作用 1)final修饰类,这个类不能被其他类继承 2)final修饰方法,方法不能被重写 3)final修饰属性,属…...
统计特殊四元组
题记: 给你一个 下标从 0 开始 的整数数组 nums ,返回满足下述条件的 不同 四元组 (a, b, c, d) 的 数目 : nums[a] nums[b] nums[c] nums[d] ,且a < b < c < d 示例 1: 输入: nums [1,2,3…...
腾讯云轻量应用服务器“镜像”怎么选择合适?
腾讯云轻量应用服务器镜像怎么选择?如果是用来搭建网站可以选择宝塔Linux面板腾讯云专享版,镜像系统根据实际使用来选择,腾讯云百科txybk.com来详细说下腾讯云轻量应用服务器镜像的选择方法: 腾讯云轻量应用服务器镜像选择 轻量…...
Ruby模块和程序组织
和类一样,模块是一组方法和常量的集合。 和类不同,模块没有实例,取而代之的是可以将特殊模块的功能添加到一个类或者指定对象之中。 Class类是Module类的一个子类,因此每一个类对象也是一个模块对象 一、模块创建和基础应用 编写…...
14、SpringCloud -- WebSocket 实时通知用户
目录 实时通知用户需求:代码:前端:后端:WebSocket创建 websocket-server 服务添加依赖:配置 yml 和 启动类:前端:后端代码:注意:测试:总结:实时通知用户 需求: 用户订单秒杀成功之后,对用户进行秒杀成功通知。 弹出个提示框来提示。 代码: 前端:...
智能井盖传感器推荐,万宾科技助力城市信息化建设
随着科技产品更新换代进程加快,人工智能在人们日常生活之中逐渐普及开来,深入人们生活的方方面面,影响城市基础设施建设工程。例如在大街小巷之中的井盖作为城市基础建设的一个重要部分,一旦出现松动倾斜或凸起等异常问题…...
3D模型格式转换工具HOOPS Exchange对工业级3D产品HOOPS的支持与应用
一、概述 HOOPS Exchange是一套高性能模型转换软件库,可以给软件提供强大的模型的导入和导出功能,我们可以将其单独作为转换工具使用,也可以将其集成到自己的软件中。 同样,HOOPS 的其它产品,也离不开HOOPS Exchange…...
table 表体滚动, 表头、表尾固定
在开发报表中,如果报表数据行过多页面无法全部显示,或者内容溢出div,需要把表头和表尾固定表体滚动这样就可以在页面上全部显示,并且不会溢出div 效果:最终实现效果 代码:<!DOCTYPE html> <html&g…...
第57篇-某钩招聘网站加密参数分析【2023-10-31】
声明:该专栏涉及的所有案例均为学习使用,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!如有侵权,请私信联系本人删帖! 文章目录 一、前言二、网站分析1.X-S-HEADER参数2.请求参数data3.响应机密值data一、前言 网址: aHR0cHM6Ly93d3cubGFnb3UuY29t…...
C语言数据结构之数据结构入门
目录 数据结构介绍 数据结构发展史 何为算法 数据结构基础 基本概念和术语 四大逻辑结构(Logic Structure) 数据类型 理解复杂度概念 时间空间复杂度定义 度量时间复杂度的方法 程序运行时的内存与地址 编程预备 数据结构介绍 数据结构发展…...
如何知道服务器的某个端口是否打开
1、telnet 命令:telnet ip port,port即端口,我们一般最常见的命令就是telnet,但是telnet使用的是tcp协议,换句话说telnet只能检测tcp的这个端口打开了没 若是端口打开,会出现下列信息 失败的是这个 如…...
【ICCV‘23】One-shot Implicit Animatable Avatars with Model-based Priors
文章目录 前置知识 前置知识 1)SMPL模型 \quad SMPL这类方法只建模穿很少衣服的人体(裸体模型),它只能刻画裸体角色的动画,并不能刻画穿衣服的人体的动画 2)data-efficient \quad 这个词推荐用ÿ…...
关于息肉检测和识别项目的总结
前言 整体的思路:首先息肉数据集分为三类: 1.正常细胞 2. 增生性息肉 3. 肿瘤要想完成这个任务,首先重中之重是分割任务,分割结果的好坏, 当分割结果达到一定的准确度后,开始对分割后的结果进行下游分类…...
Jetson Xavier NX FFmpeg支持硬件编解码
最近在用Jetson Xavier NX板子做视频处理,但是CPU进行视频编解码,效率比较地下。 于是便考虑用硬解码来对视频进行处理。 通过jtop查看,发现板子是支持 NVENC硬件编解码的。 1、下载源码 因为需要对ffmpeg进行打补丁修改,因此需要编译两份源码 1.1、编译jetson-ffmpeg …...
518抽奖软件,为什么说比别的抽奖软件更美观精美?
518抽奖软件简介 518抽奖软件,518我要发,超好用的年会抽奖软件,简约设计风格。 包含文字号码抽奖、照片抽奖两种模式,支持姓名抽奖、号码抽奖、数字抽奖、照片抽奖。(www.518cj.net) 精致美观功能 字体平滑无锯齿图片放大后清晰…...
React的组件学习
React的组件学习 参考资料:https://zh-hans.react.dev/learn/your-first-component 一、定义组件 export default function Profile() {return (<imgsrc"https://i.imgur.com/MK3eW3Am.jpg"alt"Katherine Johnson"/>) }以下是构建组件…...
uni-app配置微信开发者工具
一、配置微信开发者工具路径 工具->设置->运行配置->小程序运行配置->微信开发者工具路径 二、微信开发者工具开启服务端口...
肺癌不再是老年病:33岁作家的离世引发关注,有这些情况的请注意
近期,90后网络小说家七月新番和26岁男艺人蒋某某因肺癌去世,引发关注。他们都没有吸烟习惯,那么他们为什么会得肺癌呢?浙大二院呼吸内科副主任医师兰芬说,现在年轻人熬夜、加班导致身体过劳,在劳累情况下身…...
IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
调用支付宝接口响应40004 SYSTEM_ERROR问题排查
在对接支付宝API的时候,遇到了一些问题,记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...
解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...
关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
python执行测试用例,allure报乱码且未成功生成报告
allure执行测试用例时显示乱码:‘allure’ �����ڲ����ⲿ���Ҳ���ǿ�&am…...
