UnityURP渲染管线着⾊器编程101
unity 教程
随着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核⼼库:
该库提供了对OpenGL,Vulkan,Metal,DirectX等图形API的统⼀接⼝,具体实现见
API path
D3D11Packages//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//ShaderLibrary/Common.hlsl"
TEXTURE_2D(_MainTex);// 定义纹理
SAMPLER(sampler_MainTex);// 定义纹理对应的采样器
half4 color=SAMPLE_TEXTURE2D(_MainTex,sampler_MainTex,uv);// 纹理采样
2. 该库提供了空间变换函数,避免直接操作变换矩阵(因为GPU instancing等原因,不适合直接操作变换矩阵)
#include"Packages//ShaderLibrary/SpaceTransforms.hlsl"
);// 将顶点模型空间到世界空间
);// 将顶点模型空间到齐次裁剪空间
);// 将法线从模型空间转换到世界空间
);// 将向量从模型空间转换到世界空间
3. 该库还提供了其他功能函数,都位于Packages//ShaderLibrary⽬录下,但是在URP中我们通常不
直接引⽤核⼼库下的功能函数,⽽是通过引⽤URP提供的库⽂件,间接引⽤到核⼼库下的内容
本节的实例⾥就引⽤了如下的库⽂件,通过该库⽂件不仅引⽤到了前⾯提到的核⼼库的内容⽽且引⽤到URP新增的⼀些逻辑相关的接⼝,例如获取主光源的信息
#include"Packages/der-pipelines.universal/ShaderLibrary/Lighting.hlsl"
Light light=GetMainLight();
float3 lightDir=light.direction;
float3 lor;
4. SRP渲染管线(包括URP和HDRP)都推荐使⽤HLSL作为着⾊器语⾔,HLSL语法和CG语法基本相同,但是纹理采样等平台相关的接
⼝需要使⽤核⼼库定义的统⼀的宏,以使⽣成的Shader代码可以兼容更多的平台。
使⽤HLSLPROGRAM … ENDHLSL 代替原来的 CGPROGRAM … ENDCG作为着⾊器逻辑代码的开始和结束。
5. 引⼊了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/der-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();
// 将法线从模型空间变换到世界空间
float3 normalWS=alOS);
// 将切线从模型空间变换到世界空间
float3 tangentWS=TransformObjectToWorldDir();
/
/ 计算副切线的朝向
// 计算副切线的朝向
float crossSign =(IN.tangentOS.w >0.0?1.0:-1.0)*GetOddNegativeScale();
float3 bitangentWS=cross(normalWS,tangentWS)*crossSign;// 副切线
o.tangentWS=,positionWS.x);
o.bitangentWS=,positionWS.y);
// 计算纹理采样时使⽤的UV
o.*_+_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(),
normalize(),
));
float3 positionWS=float3(IN.tangentWS.w,IN.bitangentWS.alWS.w);
float3 normalWS=mul(normalTS,tangentToWorld);
Light light=GetMainLight();// 主光源
float3 lightDir=normalize(light.direction);
half3 lor;
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= b*_b*lightColor*NdotL
+b*_b;
return b,baseColor.a);
}
ENDHLSL
}
}
}