|
参照 UWA上的一个教程:Unity SRP从零搭建一套图形渲染管线_UWA学堂 (uwa4d.com)
这是第三章节
以下图文均来自上面的教程,一些具体步骤做了简略,参考教程即可,这里仅记录一些知识点。
方向光
1.光照
之前的Shader是不受光照的,本节加上光照交互。
1.1 受光照影响的Shader
复制上一节的Shader、HLSL文件,修改为对应的Lit.Shader,LitPass.hlsl文件,并修改对应的方法名
光照改为自定义照明
增加代码中的处理(该Pass的标识符)
static ShaderTagId litShaderTagId = new ShaderTagId("CustomLit");
drawingSettings.SetShaderPassName(1,litShaderTagId);
1.2 法线向量
表面法线,是顶点数据的一部分,我们在项点输入结构体中定义表面法线。照明是逐片元计算的,且往往是在世界空间中计算,我们在片元输入结构体中定义世界空间的法线。
VAR_BASE_UV ,VAR_NORMAL 不是Unity中的语义,是作者随便定义的
顶点着色器增加法线计算
1.3 表面属性
定义一个Surface.hlsl存储表面相关数据,并引入
#include "../ShaderLibrary/Surface.hlsl"
片元函数中存储表面数据
1.4 光照计算
新建Lighting.hlsl用于光照计算,此处用法线的Y值作为光照结果。
LitPass.hlsl包含进来
修改片元着色器获取光照结果作为输出颜色
2.灯光
本节只考虑方向光。
2.1灯光的属性
新建一个Light.hlsl文件来专门存储灯光的数据如下。
引入Light.hlsl,放在引入Lighting.hlsl之前
2.2 光照函数
Lighing.hlsl中加入计算入射光照方法,得到最终照明方法
调整之前的GetLighting方法,使其调用一个重载
2.3 向GPU发送灯光数据
1.接下来我们在Shader中获取场景中默认的那盏方向光的灯光数据
2.编写代码将灯光数据发送给GPU
public class Lighting
{
const string bufferName = "Lighting";
CommandBuffer buffer = new CommandBuffer
{
name = bufferName
};
static int dirLightColorId = Shader.PropertyToID("_DirectionalLightColor");
static int dirLightDirectionId = Shader.PropertyToID("_DirectionalLightDirection");
public void Setup(ScriptableRenderContext context)
{
buffer.BeginSample(bufferName);
//发送光源数据
SetupDirectionalLight();
buffer.EndSample(bufferName);
context.ExecuteCommandBuffer(buffer);
buffer.Clear();
}
void SetupDirectionalLight()
{
Light light = RenderSettings.sun;
//灯光的颜色我们再乘以光强作为最终颜色
buffer.SetGlobalVector(dirLightColorId,light.color.linear * light.intensity);
buffer.SetGlobalVector(dirLightDirectionId,-light.transform.forward);
}
}
在CameraRender的Render方法里调用
这里注意被引用的方法要写在前面,否则会报找不到这个方法。感觉也是类似lua那种编译方式
结果小球接收了方向光的照明
2.4 可见光
Unity会在剔除阶段找到哪些光源会影响相机的可见空间,我们在Lighting脚本中获取相机的剔除结果并定义一个字段进行后续追踪。后续我们要支持多个光源,定义一个SetupLights方法来设置和发送多个光源的数据。
2.5 支持多个方向光
1.我们已经获取到了场景中所有的可见光,现在要将这些可见光数据全部发送到GPU。先定义CPU端
2.我们改造SetupDirectionalLight方法。
可见光的finalColor属性已经应用了光照强度,但默认情况下Unity不会将其转换为线性空间,需要如下代码:
把可见光传递给GPU
调整_CustomLight 缓冲区定义,修改GetDirectionalLight 方法,得到对应灯光数据
调整Lighting.hlsl文件中的GetLighting万法,使用for循环对每个可见方向光的照明结果进行累
加,作为最终的照明结果。
如下图,不同朝向的方向光的叠加效果
在Pass中将着色器编译目标级别设置为3.5,该级别越高,允许使用现代GPU的功能越多。如果不设置,Unity默认将着色器编译目标级别设为2.5,介于DirectX着色器模型2.0和3.0之间。但OpenGLES 2.0和WebGL 1.0的图形API是不能处理可变长度的循环的,也不支持线性空间。所以我们在工程构建时可以关闭对OpenGL ES 2.0和WebGL 1.0的支持。
3.BRDF
现在我们的光照模型比较简单,只适用于完全散射的表面,接下来我们使用BRDF (双向反射分布函数)实现更加真实的光照效果,在这里我们将使用和URP一样的BRDF模型。
3.1Metallic 和Smoothnes
在Unity的内置渲染管线中支持两种流行的基于物理的工作流程: 金属工作流和高光反射工作流。其中金属工作流是默认的工作流程,对应的Shader为Standard Shader。如果想要使用高光反射工作流,需要在材质的Shader下拉框选择Standard (Specular setup) 。需要注意的是,使用不同的工作流可以实现相同的效果,只是它们使用的参数不同而已。
1.这里将使用金属工作流,需要为Lit.shader添加两个属性,Metallic和Smoothness。其中Metallic定义了该物体表面看起来是否更像金属或非金属,如果把材质的Metallic值设为1,表明该物体几乎完全是一个金属材质,若设置为0表明该物体几乎没有任何金属特性。Smoothness是Metallic的附属值,定义了从视觉上看该表面的光滑程度,1代表完全光滑,镜面反射最明显,0代表完全粗糙。
片元函数中存储表面的金属度和光滑度
在PerObiectMaterialProperties 脚本中也可以定义这些属性
3.2 BRDF属性
我们将使用表面的属性计算BRDF,它告诉我们最终有多少光从物体的表面反射出去,这是漫反射和镜面反射的组合。我们需要将表面颜色分成漫反射部分和镜面反射部分,还需要知道表面的粗糙度。新建一个BRDF.hlsl
使用#include "../ShaderLibrary/BRDF.hlsl" 引入
修改GetLighting方法,增加BRDF参数
修改片元着色器
3.3 反射率 Reflectivity
1.当使用金属工作流时,物体表面对光线的反射率 (Reflectivity) 会受到Metallic (金属度)的影响,物体的Metallic越大,其自身反照率 (Albedo) 颜色越不明显,对周围环境景象的反射就越清晰,达到最大时就完全反射显示了周围的环境景象。我们调整BRDF的GetBRDF方法,用1减去金属度得到的不反射的值,然后跟表面颜色相乘得到BRDF的漫反射部分。
2.实际上一些电介质(通常不导电物质),如玻璃、塑料等非金属物体,还会有一点光从表面反射出来,平均约为0.04,这给了它们亮点。它将作为我们的最小反射率。
3.4 粗糙度
粗糙度和光滑度相反,只需要使用1减去光滑度即可
引入#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl"
3.5 视角方向
UnityInput.hlsl 中定义相机位置
float3 _WorldSpaceCameraPos;
顶点函数中存储顶点在世界空间的位置
片元函数中得到视角方向
//得到视角方向
surface.viewDirection = normalize(_WorldSpaceCameraPos - input.positionWS);
3.6 镜面反射强度
1.镜面反射强度取决于视角方向和完美反射方向的对齐程度,我们使用URP中相同的公式这是简化版Cook-Torrance模型的一种变体.
镜面反射强度的计算公式如下,我们通过表面数据,BRDF数据和光照来计算它
r代表粗糙度,N代表表面法线,L代表光照方向,V代表视角方向,H代表归一化的L+V,它是光和视角方向的中间对角线向量,为了做一个保护,使用SafeNormalize方法进行归一化,避免两个向量在相反的情况下被零除。n代表4r+2,是一个归一化项。
2.接下来可以套用上面的公式进行计算并得到镜面反射强度
3.修改Lighting.hlsl文件的GetLighting方法
此时已经可以看到高光了
4.透明度
当我们调整小球的Alpha值时,小球会渐渐透明化,但镜面反射也会慢慢消失。在实际情况下,比如透明的玻璃,光线会穿过它或者反射出来,镜面反射并不会消失,我们现在还不能做到这一点
4.1 Premultiplied (预乘) Alpha
先说说什么是 Premultiplied Alpha。常见的像素格式为RGBA8888即(r,g,b,a) ,每个通道8位范围在 [0,255] 之间。比如红色50%的透明度可以表示为 (255,0,0,127),PremultipliedAlpha是把RGB的通道也乘上透明度比例,这就是 (r*a,g*a,b*a,a) ,那么红色50%透明度则变成了(127,0,0,127)。使用它的好处是可以让两个像素之间线性插值后颜色结果更合理,使得带透明通道图片的纹理可以进行正常的线性插值。
实现预乘,增加关键字,增加shader开关。
5.ShaderGUI
我们的材质现在支持多种渲染模式,不过切换起来比较麻烦,需要单独配置和进行一些参数调节,我们使用ShaderGUI来对材质面板进行一些扩展,可以很方便的切换各种渲染模式,来一键进行参数配置。
扩展材质面板
参考:ShaderLab: CustomEditor - 简书 (jianshu.com)
使用CustomEditor扩展面板
此处直接贴出代码
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
/// <summary>
/// 扩展材质面板
/// </summary>
public class CustomShaderGUI : ShaderGUI
{
MaterialEditor editor;
Object[] materials;
MaterialProperty[] properties;
bool showPresets;
public override void OnGUI(
MaterialEditor materialEditor, MaterialProperty[] properties
)
{
base.OnGUI(materialEditor, properties);
editor = materialEditor;
materials = materialEditor.targets;
this.properties = properties;
EditorGUILayout.Space();
showPresets = EditorGUILayout.Foldout(showPresets, &#34;Presets&#34;, true);
if (showPresets)
{
OpaquePreset();
ClipPreset();
FadePreset();
TransparentPreset();
}
}
/// <summary>
/// 设置材质属性
/// </summary>
/// <param name=&#34;name&#34;></param>
/// <param name=&#34;value&#34;></param>
/// <returns></returns>
bool SetProperty(string name, float value)
{
MaterialProperty property = FindProperty(name, properties, false);
if (property != null)
{
property.floatValue = value;
return true;
}
return false;
}
/// <summary>
/// 设置关键字状态
/// </summary>
/// <param name=&#34;keyword&#34;></param>
/// <param name=&#34;enabled&#34;></param>
void SetKeyword(string keyword, bool enabled)
{
if (enabled)
{
foreach (Material m in materials)
{
m.EnableKeyword(keyword);
}
}
else
{
foreach (Material m in materials)
{
m.DisableKeyword(keyword);
}
}
}
/// <summary>
/// 相关属性存在时可以设置关键字开关
/// </summary>
/// <param name=&#34;name&#34;></param>
/// <param name=&#34;keyword&#34;></param>
/// <param name=&#34;value&#34;></param>
void SetProperty(string name, string keyword, bool value)
{
if (SetProperty(name, value ? 1f : 0f))
{
SetKeyword(keyword, value);
}
}
bool Clipping
{
set => SetProperty(&#34;_Clipping&#34;, &#34;_CLIPPING&#34;, value);
}
bool PremultiplyAlpha
{
set => SetProperty(&#34;_PremulAlpha&#34;, &#34;_PREMULTIPLY_ALPHA&#34;, value);
}
BlendMode SrcBlend
{
set => SetProperty(&#34;_SrcBlend&#34;, (float)value);
}
BlendMode DstBlend
{
set => SetProperty(&#34;_DstBlend&#34;, (float)value);
}
bool ZWrite
{
set => SetProperty(&#34;_ZWrite&#34;, value ? 1f : 0f);
}
RenderQueue RenderQueue
{
set
{
foreach (Material m in materials)
{
m.renderQueue = (int)value;
}
}
}
bool PresetButton(string name)
{
if (GUILayout.Button(name))
{
editor.RegisterPropertyChangeUndo(name);
return true;
}
return false;
}
/// <summary>
/// 不透明材质默认设置
/// </summary>
void OpaquePreset()
{
if (PresetButton(&#34;Opaque&#34;))
{
Clipping = false;
PremultiplyAlpha = false;
SrcBlend = BlendMode.One;
DstBlend = BlendMode.Zero;
ZWrite = true;
RenderQueue = RenderQueue.Geometry;
}
}
/// <summary>
/// 裁切材质默认设置
/// </summary>
void ClipPreset()
{
if (PresetButton(&#34;Clip&#34;))
{
Clipping = true;
PremultiplyAlpha = false;
SrcBlend = BlendMode.One;
DstBlend = BlendMode.Zero;
ZWrite = true;
RenderQueue = RenderQueue.AlphaTest;
}
}
/// <summary>
/// 标准透明材质默认设置
/// </summary>
void FadePreset()
{
if (PresetButton(&#34;Fade&#34;))
{
Clipping = false;
PremultiplyAlpha = false;
SrcBlend = BlendMode.SrcAlpha;
DstBlend = BlendMode.OneMinusSrcAlpha;
ZWrite = false;
RenderQueue = RenderQueue.Transparent;
}
}
//如果shader的预乘属性不存在,不需要显示该渲染模式的按钮
bool HasProperty(string name) => FindProperty(name, properties, false) != null;
bool HasPremultiplyAlpha => HasProperty(&#34;_PremulAlpha&#34;);
/// <summary>
/// 受光正确的透明材质默认设置
/// </summary>
void TransparentPreset()
{
if (HasPremultiplyAlpha && PresetButton(&#34;Transparent&#34;))
{
Clipping = false;
PremultiplyAlpha = true;
SrcBlend = BlendMode.One;
DstBlend = BlendMode.OneMinusSrcAlpha;
ZWrite = false;
RenderQueue = RenderQueue.Transparent;
}
}
}
效果
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|