01 渲染管线
接触Unity以及shader的内容,不看下渲染管线是不行的。这方面网上内容很多,我对此又没多少有建设性的观点,所以主要就是看一些资料摘录点重要内容了。
(来自百度百科)显卡的核心部分是渲染管线,一般是非统一架构的,也就是顶点渲染和像素渲染分开的。顶点渲染单元就是顶点着色器,负责描绘图形,也就是建立模型;像素渲染管线主要负责把顶点绘出的图形填色;然后再加上纹理贴图就能得到一个精美的图形了。
Direct10之后就是统一架构了,一个核心是由一组专门的通道负责顶点渲染和像素渲染的。
下面继续学习下,参考:
https://blog.youkuaiyun.com/nikoong/article/details/79776873
渲染管线讲数据从3D场景转换成2D图像,最终显示在屏幕上,主要分为应用阶段、几何阶段和光栅化阶段等,如下:
应用阶段主要是CPU和内存打交道,例如碰撞检测,计算好的数据如顶点坐标、法向量、纹理坐标和纹理等会传给硬件。
几何阶段也被称为变换和光照阶段,为了讲3D场景转换到2D屏幕空间上,所有物体都要转换,通过一个空间的顶点转换到另一个空间的顶点来实现。光照会通过物体表面法向量和摄像机位置及光源位置计算给定顶点的光照属性。坐标系转换由物体坐标系开始,每个物体有自己的坐标系,因为这样有利于几何变换如平移缩放旋转等,进入到世界坐标系,所有物体有统一的坐标系。然后就是转换视图空间,即摄像机坐标系。这时还会根据物体是否在视体外确定是否丢弃它或者裁剪,最后还要转换到手机屏幕,根据窗口大小讲x和y坐标缩放到合适的坐标,得到2D图像,z坐标保留用于后续深度操作。
光栅化阶段(Rasterization Stage)类似于通过纱窗看世界,为什么这样呢,因为屏幕像素是一个个的点,前面得到的数据则是连续的,需要进行离散化和插值,讲矢量图形转换为像素点。GPU遍历2D图形讲数据转化为大量的候选像素,即片段Fragment。片段包含了位置、颜色、深度和纹理坐标等数据结构,通过检测原始图元和屏幕像素是否相交来生成。最后还要计算纹理颜色光照等属性来确定最终的像素。
02 Unity Shader简单了解
学习一下Unity Shader基础,参考:
https://github.com/Centribo/Unity-Shader-Basics-Tutorial
shader是渲染管线的一部分,就是一小段程序告诉计算机如何渲染和着色,包括计算颜色和光照以便物体显示在屏幕上。简化渲染管线如下:
这里的对象数据是点、法线、三角面片、UV坐标等,定制的数据则是可以传给shader的,如颜色、纹理和数字等。
该教程很详细,根据指导建立Unlit Shader。这里我做了对比,左边是新建的,右边是默认的:
是有区别的,看着是反光不同。我们把该shader清空,那么左侧方块就变色了:
这是因为该shader无效而用了默认的颜色,所以如果场景中看到了紫色的对象,可以看看shader是不是有什么错误。
下面准备写了,先看下大体样子如下:
Shader "Unlit/Tutorial_Shader" {
Properties {
}
SubShader {
Pass {
CGPROGRAM
...
ENDCG
}
}
}
Shader打头,里面紧跟的是Unity下该shader的类别路径,Properties部分写一些属性,也就是我们可以传入自定义数据的地方。这里声明的变量会在Unity编辑器下显示,同时暴露给脚本。接着是SubShader,每个shader都有一个或者多个subshader,跨平台的情况下编写多个subshader会很有用,比如对PC有更高质量的,对手机端有更快速度的subshader。每一个subshader中至少有一个pass,也就是真正渲染对象的部分。Pass内就是真正的渲染代码了,这里是CGPROGRAM和ENDCG之间的一段代码,这里Unity中用的是HLSL和CG着色语言的一个变体。
我们告诉Unity顶点和片段函数:
CGPROGRAM
#pragma vertex vertexFunction
#pragma fragment fragmentFunction
ENDCG
也就是顶点函数叫做VertexFunction,片段函数是fragmentFunction,然后就像普通编程那样可以进行定义了:
CGPROGRAM
#pragma vertex vertexFunction
#pragma fragment fragmentFunction
void vertexFunction () {
}
void fragmentFunction () {
}
ENDCG
在我们开始着色之前,需要先搞一些数据结构,以便获取Unity的数据以及将数据返回给Unity,所以首先要包括UnityCG.inc,然后新建一个结构体appdata:
CGPROGRAM
#pragma vertex vertexFunction
#pragma fragment fragmentFunction
#include "UnityCG.cginc"
struct appdata {
};
void vertexFunction (appdata IN) {
}
void fragmentFunction () {
}
ENDCG
当我们给unity参数以调用vertex函数的时候,它会检查该参数的结构,这里也就是appdata,然后根据模型给该结构传入数据。例如要求unity给出模型顶点位置信息用:
float4 vertex: POSITION;
现在我们要求unity给出顶点位置和uv坐标:
struct appdata {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
最后建立顶点函数,再创建一个v2f的结构体,表示顶点转片段(fragment):
struct v2f {
float4 position : SV_POSITION;
float2 uv : TEXCOORD0;
};
SV表示system value,最终大致结构如下:
Shader "Unlit/Tutorial_Shader" {
Properties {
}
SubShader {
Pass {
CGPROGRAM
#pragma vertex vertexFunction
#pragma fragment fragmentFunction
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f {
float4 position : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vertexFunction (appdata IN) {
v2f OUT;
return OUT;
}
fixed4 fragmentFunction (v2f IN) : SV_TARGET {
}
ENDCG
}
}
}
然后修改:
v2f vertexFunction (appdata IN) {
v2f OUT;
OUT.position = UnityObjectToClipPos(IN.vertex);
return OUT;
}
fixed4 fragmentFunction (v2f IN) : SV_TARGET {
return fixed4(0, 1, 0, 1); //(R, G, B, A)
}
这样输出给到片段函数,RGBA中G和A为1,得到绿色效果:
这里直接返回绿色太low了,我们自己添加一个颜色属性:
Properties {
_Colour ("Totally Rad Colour!", Color) = (1, 1, 1, 1)
}
然后面板中就出现了该颜色选项:
但是为了达到选择颜色就改变的效果,需要使用该属性:
float4 _Colour;
fixed4 fragmentFunction(v2f IN) : SV_TARGET{
//return fixed4(0, 1, 0, 1); //(R, G, B, A)
return _Colour;
}
也就是将片段函数修改返回为_Colour,则面板上修改颜色就能改变对象颜色了:
后面还有贴图等更多介绍,这里就先打住了。