|
课程链接:https://learn.u3d.cn/tutorial/hi-shader Unity渲染管线
Unity的渲染管线大致可分2大类,内置渲染管线 Built-in 以及可编程渲染管线SRP,之前的内容所接触到的全都是内置的渲染管线。
Built-in 内置渲染管线
built-in管线的渲染流程实现,全部都是写在引擎源码里的(c++)。而大部分开发者是不会去修改源码的(也无法得到源码),那这基本上算是不能改动,所以过去的渲染管线对开发者来说,是很难进行定制开发的。内置渲染管线在一个管线里面支持了所有的二十多个平台,导致代码越来越臃肿,很难使性能和效果做到最好。
Scriptable Render Pipeline 可编程渲染管线
为了解决原本的内置渲染管线不够灵活的这一问题,Unity在2018之后提供了新的渲染系统 - SRP,全称就是Scriptable Render Pipeline。
它可以在Unity里通过C#脚本调用一系列的API配置和执行渲染命令 , 来实现一套自定义的渲染流程,你可以根据自己的需求来调整流程或者修改功能。相当于就是帮我们在复杂的底层图形API基础之上,封装了一套通俗易懂的C#API,本质上最后调用的还是底层的图形API。所谓的图形API就是我们之前提到的像如OpenGL、Direct3D这类的底层渲染API层。
URP & HDRP
URP & HDRP
为了解决这一问题,Unity给我们提供了两个可以直接上路的解决方案 URP 和 HDRP。他们都是在SRP的API基础之上构建的渲染管线解决方案,可以直接开始使用,或者在其基础上进一步进行定制。
其中 URP 全称是Universal Render Pipeline 通用渲染管线,早期被称为LWRP Lightweight RP 也就是轻量级渲染管线, 在2019.3开始改名为URP,它涵盖了范围广泛的不同平台,是针对跨平台开发而构建的,性能和画质都要比内置管线好,另外还可以进行自定义,实现不同风格的渲染,这也是目前最推荐大家使用的渲染管线。
而HDRP(High-Definition Render Pipeline)高清渲染管线,则是针对高端设备下的高真实感图形和渲染,如 PC、Xbox 和 PlayStation 等高端硬件。
我们可以在Unity的官方文档看到两个渲染管线之间的详细对比,如它们所支持的平台、光照、相机相关的信息。
Render pipeline feature comparison Built-in 升级HDRP
在Window下打开Package Manager,选择Unity Registry:
Unity Registry
找到HDRP Package,点击安装:
HDRP Package
然后打开Window>Rendering>HDRP Wizard窗口:
HDRP Wizard
点击Fix All,Unity则会自动修复HDRP相关的Error:
Fix All
修复完成后会弹出一个框,选择Create One,创建HDRP Asset资源:
创建HDRP Asset资源
现在场景中的材质依然是101品红色,这是因为Shader还是用的内置渲染管线的Shader
材质错误
这里点击HDRP Wizard窗口下面这栏的第一个按钮,将项目中的所有不兼容材质转换为 HDRP 材质:
转换为 HDRP 材质
转换完成后,将Sky Fog Volume添加到场景里用来设置环境照明:
Sky Fog Volume
打开Window > Rendering > Lighting,选择刚刚Volume的配置文件:
Lighting
Built-in 升级URP
前面和HDRP类似,在Window下打开Package Manager,选择Unity Registry,找到URP Package,点击安装。
URP Package
在Assets目录下点击Create>Rendering>URP Asset
URP Asset
点击Editor>Project Settings>Graphics>Scriptable Render Pipeline Settings,选择刚刚创建好的URP Asset:
Scriptable Render Pipeline Settings
弹出一个对话框,点击Continue:
Continue
设置完成后场景中的材质变成了我们之前所说的101色,同样是因为材质还是built-in中的材质,接下来在Window>Rendering>Render Pipeline Converter 打开转换器
Render Pipeline Converter
选择Built-in to URP,然后选择要转换的内容:
Built-in to URP
勾选完成之后点击左下角按钮进行转换 初始化设置
初始化设置
初始化完成之后点击 右下角按钮 进行资产转换
进行资产转换
细心的小伙伴可能发现之前做的轮廓线不见了,通过官方的文档 URP ShaderLab Pass 标签 | Universal RP | 12.1.1 (unity3d.com) 可以看到,由于在URP里新增了特定的Pass tag,需要给Pass添加特定的Tag LightMode才可以进行多pass渲染。但其实URP 本身的设计思想并不鼓励在shader里写多pass,而是利用 Render feature 实现各种功能。
既然轮廓线只用到了两个pass,这里偷懒一下,简单的给pass加一下tag。
正常的渲染就用UniversalForward即可:
Pass
{
Tags
{
"LightMode" = "UniversalForward"
}
}如描边这种需要在渲染对象时绘制额外的 Pass,这里就可以选择SRPDefaultUnlit:
Pass
{
Tags
{
"LightMode" = "SRPDefaultUnlit"
}
}简单URP水面效果
URP除了对管线的升级外,ShaderLab也从原来的CG升级到了HLSL,虽说有向前兼容,原来的CG也不是不能用,但在升级管线之后,Unity SRP 使用的都是HLSL 语言,继续使用CG容易出现许多不知所以的问题。而且URP在ShaderLibrary里面还提供了许多好用的API库:
URP在ShaderLibrary里面提供了许多好用的API库
这里通过简单的水面效果来熟悉一下新的HLSL,其实它与之前的CG大体上还是差不多的,首先新建一个Unlit Shader,把CGPROGRAM替换成HLSLPROGRAM,下面的End也记得换一下
HLSLPROGRAM
把渲染管线标记为URP,由于是水面效果,水是透明的,所以渲染类型也要改成透明,同样把它的渲染队列也标注为透明,最后给它加个透明度混合即可
透明度混合
通常来说水深不同水面的颜色也就不同,因为水分子会吸收通过它的 光的能量,所以这里给它添加两个属性,一个深水色,一个浅水色:
_ShallowWater ("shallowColor", Color) = (1.0, 1.0, 1.0, 1.0)
_DeepWater ("DeepColor", Color) = (1.0, 1.0, 1.0, 1.0)如何得知水的深浅呢?这里则需要通过拿到场景的深度信息来计算水面的深度,在以往这是一个比较麻烦的步骤,而现在Unity给我们提供了许多好用的函数库,其中有一个就是深度相关的,这里我们直接给它include进来,把之前的CG库替换掉
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"由于fixed精度过于低,所以hlsl干脆给它丢掉了,这里修改为half,当然想要更高的精度也可以使用float:
精度错误
函数报错是因为更新了核心库,所以相关的API也变了
函数报错
打开ShaderLibary里的核心库,搜索关键字,发现所有的顶点位置信息都合并到了这一个结构体里
ShaderLibary里的核心库
顶点信息结构体
声明并且通过get给它初始化一下,这样就可以得到顶点数据,把之前报错的API删掉:
VertexPositionInputs positionInputs = GetVertexPositionInputs(v.positionOS);深度纹理是全屏纹理,它和屏幕的尺寸相同。而我们希望在当前像素相同的位置对深度进行采样。所以,这里需要要把顶点在屏幕空间位置算好:
v2f vert(a2v v)
{
v2f o;
VertexPositionInputs positionInputs = GetVertexPositionInputs(v.positionOS);
o.positionCS = positionInputs.positionCS;
o.screenPosition = ComputeScreenPos(positionInputs.positionCS);
return o;
}通过引入的库函数来获取屏幕深度,然后把这个深度转换到视图空间:
half4 frag(v2f i) : SV_Target
{
// 通过深度纹理的采样 计算屏幕深度
float sceneRawDepth = SampleSceneDepth(i.screenPosition.xy / i.screenPosition.w);
// 深度纹理的采样结果转换到视图空间下的深度值
float sceneEyeDepth = LinearEyeDepth(sceneRawDepth, _ZBufferParams);
return col;
}由于关心的是这个深度值相对于水面有多深,所以需要把视图深度,减去模型顶点的深度,得到最终水的深度,然后把刚刚的 深浅水颜色 根据水的深度做个lerp:
// 最终得到水的深度
float waterDepth = sceneEyeDepth - i.screenPosition.w;
// 拿到水的颜色
float3 waterColor = lerp(_ShallowWater, _DeepWater, waterDepth);为了使水产生流动感,这里通过noise给它做个简单的漂浮扰动效果,为了让它有流动效果所以给它加个时间和速度进行采样:
float surfaceNoiseSample = tex2D(_SurfaceNoise, i.noiseUV + _Time.y * _MoveSpeed * 0.1).r;根据深度加一圈浮沫,和刚刚的波动混合一下,最后把水的颜色和浮沫的颜色叠加一下,把透明度公开出来:
// 浮沫
float foam = saturate(waterDepth / _FoamDistance);
float surfaceNoise = smoothstep(0, foam, surfaceNoiseSample) ;
// 混合水面透明度
float4 col = float4(waterColor + surfaceNoise * _FoamColor, _WaterAlpha) ;
return col;完整代码
Shader "URP/Water"
{
Properties
{
_ShallowWater ("shallowColor", Color) = (1.0, 1.0, 1.0, 1.0)
_DeepWater ("DeepColor", Color) = (1.0, 1.0, 1.0, 1.0)
_WaterAlpha("WaterAlpha",Range(0,1)) = 0.5
_SurfaceNoise("Surface Noise", 2D) = "white" {}
_MoveSpeed("MoveSpeed",Range(0,1)) = 0.5
_FoamDistance("Foam Distance",Range(0,10)) = 0.4
_FoamColor("FoamColor", Color) = (1.0, 1.0, 1.0, 1.0)
}
SubShader
{
Tags
{
"RenderPipeline"="UniversalPipeline"
"RenderType"="Transparent"
"Queue"="Transparent"
}
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
float4 _ShallowWater;
float4 _DeepWater;
float _WaterAlpha;
sampler2D _SurfaceNoise;
float4 _SurfaceNoise_ST;
float _MoveSpeed;
float _FoamDistance;
float4 _FoamColor;
// 顶点着色器的输入
struct a2v
{
float3 positionOS : POSITION;
float4 uv : TEXCOORD0;
};
// 顶点着色器的输出
struct v2f
{
float4 positionCS : SV_POSITION;
float4 screenPosition : TEXCOORD0;
float2 noiseUV : TEXCOORD1;
float2 distortUV : TEXCOORD2;
};
v2f vert(a2v v)
{
v2f o;
VertexPositionInputs positionInputs = GetVertexPositionInputs(v.positionOS);
o.positionCS = positionInputs.positionCS;
o.noiseUV = TRANSFORM_TEX(v.uv, _SurfaceNoise);
o.screenPosition = ComputeScreenPos(positionInputs.positionCS);
return o;
}
half4 frag(v2f i) : SV_Target
{
// 通过深度纹理的采样 计算屏幕深度
float sceneRawDepth = SampleSceneDepth(i.screenPosition.xy / i.screenPosition.w);
// 深度纹理的采样结果转换到视图空间下的深度值
float sceneEyeDepth = LinearEyeDepth(sceneRawDepth, _ZBufferParams);
// 因为关心的是这个深度值相对于我们的水面有多深,所以需要把视图深度,减去模型顶点的深度
// 最终得到水的深度
float waterDepth = sceneEyeDepth - i.screenPosition.w;
// 拿到水的颜色
float3 waterColor = lerp(_ShallowWater, _DeepWater, waterDepth);
float surfaceNoiseSample = tex2D(_SurfaceNoise, i.noiseUV + _Time.y * _MoveSpeed * 0.1).r;
// 浮沫
float foam = saturate(waterDepth / _FoamDistance);
float surfaceNoise = smoothstep(0, foam, surfaceNoiseSample) ;
// 混合水面透明度
float4 col = float4(waterColor + surfaceNoise * _FoamColor, _WaterAlpha) ;
return col;
}
ENDHLSL
}
}
} |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|