作者:王子饼干 ——镇江虹视游戏科技有限公司联合创始人、《多洛可小镇》制作人
原文链接:UnityShader入门精要-URP管线1. 关于URP
原文发布时间:2020年3月3日
文章类型:授权转载
笔记当前的Unity版本:2019.3
当前最新的Unity版本:2020.2
SRP即Scriptable Render Pipeline,可编程渲染管线。管线是一种用于渲染的流程,如果你不理解的话,可以将其当成一个黑盒子,输入是模型,纹理,摄像机数据,输出是一幅画面,我们所编写的着色器是渲染流程中的一环。而Unity则保留了一部分C++的核心,把渲染也移植到了C#上,之后我们可以通过SRP来构建自己的渲染管线。
第一,Unity在近年来支持了越来越多的平台,不论是Win,Linux,Mac,亦或是Android,iOS,UWP,还是PS或者Switch。不同的平台有不同的渲染函数集,D3D,OpenGL,OpenGLES,Metal。而我们使用的Unity的着色器,却采用一种语法,Shaderlab来实现。在Shaderlab的背后,所做的工作是非常复杂的。需要大量的编译指令来控制语法在不同平台上的含义。因此,Unity原本的Built-in管线不堪重负,因而需要新生力量。
第二,Built-in管线虽然适用于大部分情况,但是在某些特殊的环境下仍然不是最完美的,在很多情境下,有些人希望可以自己配置管线,来进行画面的渲染。(这需要开发者拥有非常高的水平)
开发渲染管线是极其困难的,至少对于大部分人来说,去接触管线都是不太“河里”的。所以Unity提供了两个开发好的管线,URP和HDRP,URP针对的主要是手机游戏,PC端的中小型游戏,而HDRP则针对3A级游戏开发。
URP不仅仅是基于SRP的新型管线,同时改变了built-in管线中前向渲染光照的模式,提升了光照渲染的性能,并且可以配合ShaderGraph开发着色器。(当然,我也不清楚为什么SG只能用于URP&HDRP)相对于Built-In管线来说有较大的提升空间,是现在开发的首选。
我想是不可以的,Built-In之前的着色器开发大部分是由CG来完成的,而URP的着色器开发则通过HLSL(写代码的方式)或者ShaderGraph(连连看的方式)来实现。并且虽然大部分的基础概念相同,但是仍然许多细节是不一样的(语法层面上)。因而两者无法无缝切换。
URP的着色器的ShaderLibrary大部分是采用了HLSL来完成的,不仅名称改变了,还增加了一堆乱七八糟的宏处理,而CG这种只用于Built-In管线的语言肯定是不兼容的。
在《UnityShader入门精要》中,作者使用的主要是CG语言,而在URP中,则强制要求使用HLSL语言,虽然两者在语法上大体上很类似,但是仍有细节的不同。
具体的理由也比较简单,因为CG已经是一个停止更新很久的语言了,而HLSL则是微软的造物,一直保持更新。这样的活力让Unity更加青睐于使用HLSL,事实上这个原因是一方面。另外一方面使,对于不同的平台的扩展上,CG和Built-In管线一样,已经无力负担起一些重要的职能了,作为游戏开发中极其重要的一环,选择HLSL是必然的。
从小白的视角来看,HLSL是DirectX专用的着色器语言,讲道理,HLSL是不可以跨平台的。事实上,我们所谓的HLSL在一定程度上是语法层面的。(这里的意思是,我们在Unity中使用的HLSL和DirectX中的HLSL其实还是有一定的差距的)真正在编译的时候,Unity会根据平台做一定的转化,如果了解编译原理,这件事,无非是将一种代码翻译成另外一种代码。对于那些制作这些语言处理器的大厂来说,无非就是成本的考虑罢了。
我们用一个简单的案例来说明一下两者的区别,我们要编写的这个案例比较简单,就是不做任何计算,输出一种单一的颜色。首先我们回顾一下使用CG来编写的着色器,如果我们已经通关了《UnityShader入门精要》,这对你来说是一件很简单的事情。
Shader "UShaderMagicBook/SimpleTest"{
/*一个最基础的着色器*/
Properties{
_BaseColor("MyColor",Color) = (1.0,1.0,1.0,1.0) //后面不能加分号
}
SubShader{
//子着色器A
pass{
//第一个pass块
CGPROGRAM
#pragma vertex Vertex
#pragma fragment Pixel
/*定义你的顶点着色器的函数名称和片元着色器的函数名称,该步是必要的
起什么名字无所谓,命名规则同C类语言命名规则
1.区分大小写
2.不以数字开头
3.不能和系统关键字冲突*/
fixed4 _BaseColor;
/*光在Properties语块中定义变量是没用的,你必须在CGPROGRAM块中再次声明,Unity
才会把变量材质面板的值填充过来*/
float4 Vertex(float4 v:POSITION):SV_POSITION{
/*定义顶点着色器,处理和模型顶点有关的内容
一个顶点着色器至少要完成一件事,那就是把模型的顶点转换到
裁剪空间*/
return UnityObjectToClipPos(v);
//把模型转换到裁剪空间
}
fixed4 Pixel():SV_TARGET{
//计算模型表面每个像素值的颜色
return _BaseColor;
}
ENDCG
}
}
}
现在,我们使用URP&HLSL来翻译上述代码。不用担心,逻辑难度几乎为0,所以我们把注意力放在语法的区别上就可以了。
Shader "URPNotes/SimpleTest"{
/* 着色器的名称以及它在下拉菜单中的位置 ,这部分是不变的 */
Properties{
/* 着色器的输入 这部分是不变的,部分参数的类型会发生变化,在后面会说到。*/
_BaseColor("Base Color",Color) = (1.0,1.0,1.0,1.0)
}
SubShader{
/* 子着色器1,针对显卡A的着色器,这里是ShaderLab着色器的主要内容 */
Tags{
"RenderPipeline" = "UniversalRenderPipeline" //一定要加这个,用于指明使用URP来渲染
"RenderType"="Opaque"
}
pass{
HLSLPROGRAM
//函数块变成了HLSLPROGRAM
#pragma vertex Vertex
#pragma fragment Pixel
//保持你喜欢的命名方式
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
//在CG中使用的cginc文件变成了HLSL文件,并且由于它们是Unity的一个扩展组件,所以需要在Packages中找到相对应的
//库文件,里面定义了我们在URP中使用的主要的API,
//Core.HLSL中包含了一些常用的函数,另外还有一些其他的HLSL文件,后面会逐一介绍。
half4 _BaseColor;
//不要忘记在这里声明变量,否则着色器无法访问属性。
struct vertexInput{
//顶点着色器的输入,这个是不变的,
float4 vertex:POSITION;
};//不要忘了分号哦
struct vertexOutput{
//顶点着色器的输出,同时也是片元着色器的输入
float4 pos:SV_POSITION;
};
vertexOutput Vertex(vertexInput v){
vertexOutput o;
o.pos = TransformObjectToHClip(v.vertex.xyz);
// TransformObjectToHClip 是
return o;
}
half4 Pixel(vertexOutput i):SV_TARGET{
/* 片元着色器,注意,在HLSL中,fixed4类型变成了half4类型*/
return _BaseColor;
}
ENDHLSL
}
}
}
可以运行一下,发现没有错误,我们再回头检查代码的区别。区别还是非常夸张的,首先我们抛弃了使用了很长时间的CG函数库,类似于UnityObjectToWorld这样的函数全部消失了。取而代之的是HLSL的函数库,里面有太多的函数需要我们去熟悉了。其他的地方则更多的是小的细节,比如函数库变了,引用的头文件也发生了变化。并且我们要在Tags中指明RenderPipeline。
除此之外,更多的区别是CG和HLSL之间的区别,比如fixed4类型变成了half4类型等。那么后面的章节中,我们会使用HLSL再开发一遍UnityShader入门精要当中的着色器。
本次的内容体量不大,难度也不是非常夸张,无非是换了一个API罢了。不过如果能够随心所欲的选择ShaderGraph或者编码的方式来开发着色器,这是非常nice的一件事。
这次的代码仅有一个,我已经上传到了我的github,还是老的地址。希望大家不要吝啬自己的星星。