Unity URP 渲染管线着色器编程 101

随着Unity2019 LTS版本的推出,URP管线已经可以作为基础渲染管线进行商业游戏和应用的开发。
而原有兼容builtin管线的第三方shader和插件默认是不兼容URP管线的,因此需要手动编辑已有shader使之兼容URP管线,甚至某些情况下要扩展URP渲染管线以实现一些特殊功能。

本系列教程会基于URP管线,创建一系列自定义材质,来演示URP管线的使用。
从易到难,主要涉及以下内容:

  • URP 下如何进行纹理采样,如何获取系统变换矩阵等基础操作的使用 (本节内容)
  • 简单的光照模型(单光源,lambert,blinn-phone)在URP中的实现(本节内容)
  • 如何使自定义shader 可以利用GPU Instancing 和 URP的SRP batcher功能
  • 单pass下实现基于PBR光照模型的多光源效果
  • URP下自定义材质如何显示阴影,参与烘焙
  • 利用URP生成的深度贴图和颜色贴图创建高质量水体
  • URP下几何着色器和曲面细分着色器的使用,并为水体材质增加物理波浪
  • URP下头发和拉丝金属的各向异性材质实现
  • URP下使用LUT实现高质量的次表面散射材质
  • 等其他效果

基本概念

URP管线和HDRP管线作为官方的SRP可编程管线案例,依赖SRP核心库:com.unity.render-pipeline.core

  • 该库提供了对OpenGL,Vulkan,Metal,DirectX等图形API的统一接口,具体实现见
APIpath
D3D11Packages/com.unity.render-pipelines.core/ShaderLibrary/API/D3D11.hlsl
GLCore…/GLCore.hlsl
GLES2…/GLES2.hlsl
GLES3…/GLES3.hlsl
Metal…/Metal.hlsl
Switch…/Switch.hlsl
Vulkan…/Vulkan.hlsl

上述平台相关的API文件我们不需要直接引用,SRP Core提供了Common.hlsl 文件,根据不同平台会自己引用对应的API文件

例如: 2D纹理定义和采样

#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
TEXTURE_2D(_MainTex); // 定义纹理
SAMPLER(sampler_MainTex); // 定义纹理对应的采样器
half4 color=SAMPLE_TEXTURE2D(_MainTex,sampler_MainTex,uv); // 纹理采样
  1. 该库提供了空间变换函数,避免直接操作变换矩阵(因为GPU instancing等原因,不适合直接操作变换矩阵)
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/SpaceTransforms.hlsl"
TransformObjectToWorld(positionOS.xyz);// 将顶点模型空间到世界空间
TransformObjectToHClip(positionOS.xyz);// 将顶点模型空间到齐次裁剪空间
TransformObjectToWorldNormal(normalOS.xyz);// 将法线从模型空间转换到世界空间
TransformObjectToWorldDir(tangentOS.xyz);// 将向量从模型空间转换到世界空间
  1. 该库还提供了其他功能函数,都位于Packages/com.unity.render-pipelines.core/ShaderLibrary目录下,但是在URP中我们通常不直接引用核心库下的功能函数,而是通过引用URP提供的库文件,间接引用到核心库下的内容

    本节的实例里就引用了如下的库文件,通过该库文件不仅引用到了前面提到的核心库的内容而且引用到URP新增的一些逻辑相关的接口,例如获取主光源的信息

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
Light light=GetMainLight();
float3 lightDir=light.direction;
float3 lightColor=light.color;
  1. SRP渲染管线(包括URP和HDRP)都推荐使用HLSL作为着色器语言,HLSL语法和CG语法基本相同,但是纹理采样等平台相关的接口需要使用核心库定义的统一的宏,以使生成的Shader代码可以兼容更多的平台。
    使用HLSLPROGRAM … ENDHLSL 代替原来的 CGPROGRAM … ENDCG作为着色器逻辑代码的开始和结束。
  2. 引入了ConstantBuffer,StructBuffer这些DirectX3D 的概念,(随后会讲到)包括其他HLSL语法提供的特殊指令。
    在后面的章节中会逐个讲到新的一些接口和URP新增的一些shader规范,本节只展示一个简单的单光源的Lambert 漫反射光照模型和Blinn-phone 高光反射光照模型在URP下的实现。

渲染效果如图:

渲染效果

完整代码如下:
本教程所有代码使用如下规范:

  • 向量后缀OS,WS,TS,CS分别代表模型空间(对象空间),世界空间,切线空间,裁剪空间(clip)
Shader "tutorial/chapter_1/urp_101"
{
    Properties
    {
        _BaseColor("BaseColor",Color)=(1,1,1,1)
        _Smoothness("Smoothness",Range(0.1,1))=0.5
        _SpecularColor("SpecularColor",Color)=(0.2,0.3,0.3,1)
        _MainTex("Main Tex",2D)="white"{}
        _NormalMap("Normal Map",2D)="bump"{}
        
    }
    SubShader
    {
        Pass
        {           
            HLSLPROGRAM // 可编程渲染管线中推荐使用HLSL语法,URP和HDRP都是使用的HLSL
            #pragma vertex vert
            #pragma fragment frag                        
            // 引入URP中预定义好的一些变量,例如变换矩阵,光源等
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" 
            
            half4 _BaseColor;
            half4 _SpecularColor;
            float _Smoothness;
            TEXTURE2D(_MainTex);   // 声明2D纹理贴图
            float4 _MainTex_ST;  // 纹理贴图的Scale 和 Transform
            SAMPLER(sampler_MainTex); // 纹理贴图的采样器 
            
            TEXTURE2D(_NormalMap);
            SAMPLER(sampler_NormalMap);
                        
            struct app_data
            {
                float4 positionOS:POSITION;
                float3 normalOS:NORMAL;
                float4 tangentOS:TANGENT;
                float2 texcoord0:TEXCOORD0;
            };
                        
            struct v2f 
            {
                float4 positionCS:SV_POSITION;   
                // HLSL中保存光栅化用到的顶点坐标值,必须使用SV_POSITION,不再支持使用POSITION
                // SV为SystemValue,标识该语义为渲染管线硬件必须的值,后边的SV_Target同理             
                float4 normalWS:TEXCOORD0;
                float4 tangentWS:TEXCOORD1;
                float4 bitangentWS:TEXCOORD2;
                float2 uv0:TEXCOORD3;
            };
            
            v2f vert(app_data IN)
            {
                v2f o=(v2f)0;                
                //将顶点坐标从模型空间变换的世界空间
                float3 positionWS=TransformObjectToWorld(IN.positionOS.xyz); 
                // 将法线从模型空间变换到世界空间
                float3 normalWS=TransformObjectToWorldNormal(IN.normalOS); 
                // 将切线从模型空间变换到世界空间
                float3 tangentWS=TransformObjectToWorldDir(IN.tangentOS.xyz); 
                // 计算副切线的朝向
                float crossSign = (IN.tangentOS.w > 0.0 ? 1.0 : -1.0) * GetOddNegativeScale(); 
                float3 bitangentWS=cross(normalWS,tangentWS)*crossSign; // 副切线
                
                o.tangentWS=float4(tangentWS.xyz,positionWS.x);
                o.bitangentWS=float4(bitangentWS.xyz,positionWS.y);
                o.normalWS=float4(normalWS.xyz,positionWS.z);
                // 计算纹理采样时使用的UV
                o.uv0=IN.texcoord0.xy*_MainTex_ST.xy+_MainTex_ST.zw; 
                // 将顶点坐标从世界空间变换到齐次裁剪空间
                o.positionCS=TransformWorldToHClip(positionWS); 
                // 在顶点着色器里,得到的是齐次裁剪空间的顶点坐标,经过光栅化阶段的齐次除,之后才是真正的裁剪空间
                return o;
            }      
            
            //使用SV_Target 替换掉之前CG中的Color的写法,用来标记把渲染结果存入RT0
            half4 frag(v2f IN):SV_Target 
            {
	            // 采样纹理贴图
                half4 baseColor=SAMPLE_TEXTURE2D(_MainTex,sampler_MainTex,IN.uv0); 
                // 采样法线贴图并解析出法线空间的法线
                float3 normalTS=
                	UnpackNormal(SAMPLE_TEXTURE2D(_NormalMap,sampler_NormalMap,IN.uv0)); 
                float3x3 tangentToWorld=float3x3(
                	normalize(IN.tangentWS.xyz),
                	normalize(IN.bitangentWS.xyz),
                	normalize(IN.normalWS.xyz));
                float3 positionWS=float3(IN.tangentWS.w,IN.bitangentWS.w,IN.normalWS.w);
                float3 normalWS=mul(normalTS,tangentToWorld);
                Light light=GetMainLight(); // 主光源
                float3 lightDir=normalize(light.direction);
                half3 lightColor=light.color;
                float NdotL=saturate(dot(normalWS,lightDir));      
                //获取相机坐标,进而计算观察方向          
                float3 viewDir=normalize(GetCameraPositionWS()-positionWS); 
                float3 halfDir=SafeNormalize(lightDir+viewDir);
                float NdotH=saturate(dot(normalWS,halfDir));
                float specularTerm=pow(NdotH,_Smoothness*255);
                half3 resultColor= baseColor.rgb*_BaseColor.rgb*lightColor*NdotL
                				+specularTerm*lightColor.rgb*_SpecularColor.rgb;
                return half4(resultColor.rgb,baseColor.a);
            }             
            ENDHLSL
        }
    }
}

  • 2
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值