unityloverz 发表于 2022-10-5 13:42

Unity《Hi Shader》笔记-Chapter 6-Unity渲染管线介绍 ...

课程链接:https://learn.u3d.cn/tutorial/hi-shaderUnity渲染管线

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 comparisonBuilt-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
      }
    }
}
页: [1]
查看完整版本: Unity《Hi Shader》笔记-Chapter 6-Unity渲染管线介绍 ...