【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大体分为三部分:

  1. 从贴图中获取反照率、金属度、粗糙度、法线数据
  2. 计算灯光光照,环境光灯光只影响环境光;方向光、聚光灯、点光源会影响光强lightCoeff,最终的光照使用cook-torrance模型进行计算,公式可以参考LearnOpenGL
  3. 最后进行环境光与PBR模型结果进行叠加,并进行色调映射与gamma矫正,这里使用的公式在LearnOpenGL中都有的

总结:
这个PBR Shader整体上与LearnOpenGL中的理论一致,看完LearnOpenGL之后再看这个Shader就比较简单了。

完结总结:

写的这里,这个专栏暂时告一段落了,主要分析了Overload的Render模块,其他的包括UI、物理引擎、音频等模块没有涉及。Overload是一个Demo性质的游戏引擎,其渲染涉只涉及最基础的渲染方式,是对OpenGL简单封装,远远满足不了实际游戏开发需求,只能作为渲染引擎入门。
另外,这个专栏的文章只聚焦一些细节,对应架构涉及很少,因为本人发现架构方面的文章参考性不大,一旦一个软件定型架构方面的改动很困难,读了软件架构的文章也很难在工作中用上。故单纯只介绍一个技术点反而可能拿来直接使用。最后希望能对大家有所帮助!

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

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

相关文章

leetcode:374. 猜数字大小(二分查找)

一、题目 函数原型&#xff1a;int guessNumber(int n) 二、思路 本题其实就是从 1 - n 中找出所要的答案。利用guess函数来判断数字是否符合答案。 答案小于当前数字&#xff0c;guess函数返回-1 答案等于当前数字&#xff0c;guess函数返回0 答案大于当前数字&#xff0c;gue…

nginx 转发数据流文件

1.问题描述 后端服务&#xff0c;从数据库中查询日志&#xff0c;并生成表格文件返回静态文件。当数据量几兆时&#xff0c;返回正常&#xff0c;但是超过几十兆&#xff0c;几百兆&#xff0c;就会超过网关的连接超时时间30秒。 时序图 这里面主要花费时间的地方在&#xff…

启动Vue项目报错Error: error:0308010C:digital envelope routines::unsupported

问题描述 启动Vue项目报错Error: error:0308010C:digital envelope routines::unsupported 出现这个一般就是node版本的问题&#xff0c;通过命令查看node -v查看node版本&#xff1b; 百度查了好多&#xff0c;都让我降低node版本&#xff0c;属实太麻烦了 在不改node版本的…

【C# Programming】委托和lambda表达式、事件

目录 一、委托和lambda表达式 1.1 委托概述 1.2 委托类型的声明 1.3 委托的实例化 1.4 委托的内部机制 1.5 Lambda 表达式 1.6 语句lambda 1.7 表达式lambda 1.8 Lambda表达式 1.9 通用的委托 1.10 委托没有结构相等性 1.11 Lambda表达式和匿名方法的内部机制 1.1…

博弈论学习笔记(2)——完全信息静态博弈

前言 这部分我们学习的是完全信息静态博弈&#xff0c;主要内容包括博弈论的基本概念、战略式博弈、Nash均衡、Nash均衡解的特性、以及Nash均衡的应用。 零、绪论 1、什么是博弈论 1&#xff09;博弈的定义 博弈论&#xff1a;研究决策主体的行为发生直接相互作用时候的决策…

Java架构师软件架构的演化和维护

目录 1 导学2 软件架构演化和定义3 面向对象软件架构演化4 软件架构演化方式的分类5 软件架构演化原则6 软件架构演化评估方法7 大型网站架构演化8 软件架构维护想学习架构师构建流程请跳转:Java架构师系统架构设计 1 导学 2 软件架构演化和定义 软件架构的演化和维护就是对…

Kafka - 异步/同步发送API

文章目录 异步发送普通异步发送异步发送流程Code 带回调函数的异步发送带回调函数的异步发送流程Code 同步发送API 异步发送 普通异步发送 需求&#xff1a;创建Kafka生产者&#xff0c;采用异步的方式发送到Kafka broker 异步发送流程 Code <!-- https://mvnrepository…

飞鼠异地组网工具全网互通实战指南

飞鼠异地组网工具全网互通实战指南 一、飞鼠异地组网工具介绍1.1 飞鼠工具简介1.2 飞鼠工具官网 二、本次实践介绍2.1 本次实践前提2.2 本次实践简介2.3 本次实践环境规划 三、异地组网配置3.1 进入中心控制器节点管理后台3.2 网卡设置3.3 进入子网节点管理后台3.4 网卡设置 四…

项目综合实训,vrrp+bfd,以及策略路由的应用

目录 一&#xff0e; 项目需求 二&#xff0e; Visio设备画图 三&#xff0e; 设备选型 三&#xff0e;vlan规划 四&#xff0e;Ip地址规划 五&#xff0e;实验拓扑图 六&#xff0e;配置过程及结果 项目需求 1.S1作为VLAN10的主网关和根桥&#xff0c;S2作为v…

Pytorch L1,L2正则化

L1正则化和L2正则化是常用的正则化技术&#xff0c;用于在机器学习模型中控制过拟合。它们的主要区别在于正则化项的形式和对模型参数的影响。 L1正则化&#xff08;Lasso正则化&#xff09;&#xff1a; 正则化项形式&#xff1a;L1正则化使用模型参数的绝对值之和作为正则化…

Emscripten + CMakeLists.txt 将 C++ 项目编译成 WebAssembly(.wasm)/js,并编译 Html 测试

背景&#xff1a;Web 端需要使用已有的 C 库&#xff08;使用 CMake 编译&#xff09;&#xff0c;需要将 C 项目编译成 WebAssembly(.wasm) 供 js 调用。 上篇文章《Mac 上安装 Emscripten》 已讲解如何安装配置 Emscripten 环境。 本篇文章主要讲解如何将基于 CMakeLists 配…

Gitee 发行版

Gitee 发行版 1、Gitee 发行版管理2、项目仓库中创建发行版本3、项目中导入3.1 gradle配置3.2 dependencies执行正常&#xff0c;包没有下载 1、Gitee 发行版管理 Gitee 发行版&#xff08;Release&#xff09;管理 2、项目仓库中创建发行版本 按照Gitee官网操作就行 3、项目…

PCIe 访问 EP 配置空间,空间映射详解,BDF 计算偏移

访问 EP 的配置空间方法 内存映射IO 访问 内存访问配置空间 前置知识 PCIe 设备的寻址是按照 BDF 即 Bus-Device-Function 来组织的。访问某个设备则需要根据BDF计算偏移地址。 两种不同的内存访问配置空间方法 类 xilinx&#xff0c;基地址 偏移地址访问 // linux-5.10\…

http1,https,http2,http3总结

1.HTTP 当我们浏览网页时&#xff0c;地址栏中使用最多的多是https://开头的url&#xff0c;它与我们所学的http协议有什么区别&#xff1f; http协议又叫超文本传输协议&#xff0c;它是应用层中使用最多的协议&#xff0c; http与我们常说的socket有什么区别吗&#xff1f; …

【ARM 嵌入式 C 入门及渐进 10 -- 冒泡排序 选择排序 插入排序 快速排序 归并排序 堆排序 比较介绍】

文章目录 排序算法小结排序算法C实现排序方法的稳定性 排序算法小结 C语言中常用的排序算法包括冒泡排序、选择排序、插入排序、快速排序、归并排序、堆排序。下面我们来一一介绍&#xff1a; 冒泡排序&#xff08;Bubble Sort&#xff09;&#xff1a;冒泡排序是通过比较相邻…

android 8.1 disable unsupported sensor

如果device不支持某种sensor,可以在android/frameworks/base/core/java/android/hardware/SystemSensorManager.java里将其disabled掉。以disable proximity sensor为例。 public SystemSensorManager(Context context, Looper mainLooper) {synchronized(sLock) {if (!sNativ…

MWeb Pro for Mac:博客生成编辑器,助力你的创作之旅

在当今数字化时代&#xff0c;博客已经成为了许多人记录生活、分享知识和表达观点的重要渠道。而要打造一个专业、美观且易于管理的博客&#xff0c;选择一款强大的博客生成编辑器至关重要。今天&#xff0c;我向大家推荐一款备受好评的Mac软件——MWeb Pro。 MWeb Pro是一款专…

flutter深研

https://www.douyin.com/video/7020336319058627853 关闭系统风扇 在 Windows 操作系统上安装和配置 Flutter 开发环境 - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter 下载Git - Downloading Package 推荐使用迅雷下载 系统配置要求 要想安装和运行 Flutter&#xf…

使用FastAPI部署Ultralytics YOLOv5模型

YOLO是You Only Look Once(你只看一次)的缩写&#xff0c;它具有识别图像中的物体的非凡能力&#xff0c;在日常应用中会经常被使用。所以在本文中&#xff0c;我们将介绍如何使用FastAPI的集成YOLOv5&#xff0c;这样我们可以将YOLOv5做为API对外提供服务。 Python有几个web框…

如何将 ruby 打包类似于jdk在另一台相同架构的机器上面开箱即用

需求 目前工作中使用到了ruby作为java 项目的中转语言&#xff0c;但是部署ruby的时候由于环境的不同会出现安装依赖包失败的问题&#xff0c;如何找到一种开箱即用的方式类似于java 中的jdk内置jvm这种方式 解决 TruffleRuby 完美解决问题&#xff0c;TruffleRuby 是使用 T…