游戏图像学习入门到精通-Shader总结篇

本文深入探讨Unity中的Shader技术,从渲染流水线、顶点着色器、片段着色器到光照模型,详细阐述了Shader的工作原理和关键步骤。通过实例解释了如何进行坐标转换、矩阵运算以及纹理映射。此外,还介绍了基于物理的光照模型PBR,并讲解了在Shader中实现金属度和粗糙度的影响。文章最后提到了环境光贴图、反射和折射的计算方法,为游戏图像效果的提升提供了理论基础和实用技巧。
摘要由CSDN通过智能技术生成

流水线
1.应用阶段:(CPU)输出渲染图元,粗粒度剔除等 比如完全不在相机范围内的需要剔除,文件系统的粒子系统实现就用到粗粒度剔除。
2.几何阶段:(GPU)把顶点坐标转换到屏幕空间,包含了模型空间 到世界空间 到观察空间(相机视角view) 到齐次裁剪空间(投影project空间,四维矩阵,通过-w<x<w判断是否在裁剪空间,xyz值都在-w,w之间) 
            到归一化设备坐标NDC(四维矩阵通过齐次除法,齐次坐标的xyz除以w实现归一化(x/w,y/w,z/w,w/w),xy坐标到在-1,1上,z坐标一般是0,1)  到屏幕空间(通过屏幕宽高和归一化坐标计算)。
            a.顶点着色器:坐标变换和逐顶点光照,将顶点空间转换到齐次裁剪空间。
            b.曲面细分着色器:可选
            c.几何着色器:可选
            d.裁剪:通过齐次裁剪坐标的-w<x<w判断不在视野范围内的部分或者全部裁剪,归一化。
            e.屏幕映射:把NDC坐标转换为屏幕坐标
3.光栅化阶段:(GPU)把几何阶段传来的数据来产生屏幕上的像素,计算每个图元覆盖了哪些像素,计算他们的颜色、
            a.三角形设置:计算网格的三角形表达式
            b.三角形遍历:检查每个像素是否被网格覆盖,被覆盖就生成一个片元。
            c.片元着色器:对片元进行渲染操作
            d.逐片元操作:模板测试,深度测试 混合等
            e.屏幕图像
-------------------------------------------------------
矩阵:
M*A=A*M的转置(M是矩阵,A是向量,该公式不适合矩阵与矩阵)

坐标转换:
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);顶点位置模型空间到齐次空间
o.worldNormal = mul((float3x3)_Object2World,v.normal);//在正交变化下可以得到近似模型空间转换到世界空间的法向量,但是非正交情况会得到错误的情况。也不能只是逆矩阵(如果用_World2Object 会出错)
o.ViewNormal = mul(v.normal, (float3x3)UNITY_MATRIX_IT_MV);//公式上的正确转换方式,法线模型空间到view空间,用该矩阵的逆转置矩阵去转换。但是Unity里边也许考虑了这个 可能不需要再做一遍。
//但是这里要注意下在Unity里边也许 o.view_pos_nor.xyz = mul(UNITY_MATRIX_MV, float4(v.normal,0)).xyz;是正确的,有可能Unity的这个矩阵帮我们考虑法线问题。‘

normal或者顶点转换到View空间参与计算,记得考虑坐标系变化。可以i.view_pos_nor.z=-i.view_pos_nor.z;把z反向,因为Unity物体是左手坐标系,camera space是右手坐标系。
当然也可以直接在变换矩阵里边考虑好 坐标系的变化。Camera.worldToCameraMatrix 是右手坐标系计算,需要反转z,transform.worldToLocalMatrix和unity一样的左手坐标系计算。
在Opengl中,本地坐标系和世界坐标系都是右手坐标系,直到投影空间中才变换为左手。而unity中的本地坐标系和世界坐标系是左手系统,但是View空间是右手坐标。

-------------------------------------------------------
API:
UNITY_MATRIX_MVP        将顶点方向矢量从模型空间变换到裁剪空间
UNITY_MATRIX_MV         将顶点方向矢量从模型空间变换到观察空间
UNITY_MATRIX_V          将顶点方向矢量从世界空间变换到观察空间
UNITY_MATRIX_P          将顶点方向矢量从观察空间变换到裁剪空间
UNITY_MATRIX_VP         将顶点方向矢量从世界空间变换到裁剪空间
UNITY_MATRIX_T_MV       UNITY_MATRIX_MV的转置矩阵
UNITY_MATRIX_IT_MV      UNITY_MATRIX_MV的逆转置矩阵,用于将法线从模型空间转换到观察空间
_Object2World             将顶点方向矢量从模型空间变换到世界空间,矩阵。
_World2Object            将顶点方向矢量从世界空间变换到模型空间,矩阵。
模型空间到世界空间的矩阵简称M矩阵,世界空间到View空间的矩阵简称V矩阵,View到Project空间的矩阵简称P矩阵。
这些矩阵需要先声明了才能用
---------------------------------------------
_WorldSpaceCameraPos    该摄像机在世界空间中的坐标
_ProjectionParams   x = 1,如果投影翻转则x = -1   y是camera近裁剪平面   z是camera远裁剪平面    w是1/远裁剪平面
_ScreenParams   x = 屏幕宽度,y = 屏幕高度,z =  1 + 1.0/屏幕宽度, w = 1 + 1.0/height屏幕高度(指像素数)
_ZBufferParams   x = 1-far/near    y = far/near     z = x/far     w = y/far
unity_OrthoParams
unity_Cameraprojection
unity_CameraInvProjection
unity_CameraWorldClipPlanes[6]    摄像机在世界坐标下的6个裁剪面,分别是左右上下近远、


----------------------------
1.表面着色器
void surf (Input IN, inout SurfaceOutput o) {}表面着色器,unity特殊封装的着色器
Input IN:可以引用外部定义输入参数
inout SurfaceOutput o:输出参数
struct SurfaceOutput//普通光照
{
 half3 Albedo;//纹理,反射率,是漫反射的颜色值
 half3 Normal;//法线坐标
 half3 Emission;//自发光颜色
 half  Specular;//高光,镜面反射系数
 half  Gloss;//光泽度
 half  Alpha;//alpha通道
}
基于物理的光照模型:金属工作流SurfaceOutputStandard 高光工作流SurfaceOutputStandardSpecular 
half3,half4代表rgba或者xyz,可以分开用 Albedo.xy=1.或Albedo.ga=1

---------------------------------
#pragma surface surfname lightModel op - 指出函数surfname 表面着色器。lightModel的光照模型和可选op操作,还可以添加顶点修改函数vertex和颜色修改函数finalcolor。lightModel的输入结构有定义的固定几种
#pragma surface surf CustomLambert vertex:myvert finalcolor:mycolor addshadow exclude_path:deferred exclude_path:prepass nometa
#pragma vertex name - 指出函数name 是顶点着色器。
#pragma fragment name - 指出函数name 是片段着色器。
#pragma fragmentoption option - 添加option 到编辑的OpenGL片段程序。参看ARB fragment program说明书了解被允许的选项列表。这个指示在顶点程序或者编辑到非OpenGL targets的程序没有影响。 
#pragma multi_compile_builtin - 为了pixel-lit shaders;;这个将告知Unity去编辑大量的这个着色器程序数列以支持所有的照明种类,和所有的阴影选项。 
#pragma multi_compile_builtin_noshadows - 对于pixel-lit 着色器,不接受阴影。这将告知Unity去编辑几个该着色器程序的数列来支持所有的照明种类。这比multi_compile_builtin pragma可以更快的编辑,而且结果着色器也更小。
#pragma target name - 那个着色器target 去编辑。细节参看shader targets。 
#pragma only_renderers space separated names - 只为给定的渲染器编辑着色器。默认情况下,着色器为所有的渲染器被编辑。细节参看 renderers。
#pragma exclude_renderers space separated names - 不为给定的渲染器编辑着色器。默认情况下,着色器为所有的渲染器被编辑。细节参看 renderers。

#pragma multi_compile TEST1 TEST2 告诉Unity编译两个不同的shader版本 分别定义了宏定义TEST1和TEST2.在C#中用material.shaderKeywords = ["TEST1"]或者Shader.EnableKeyword ("TEST1")去区分调用不同宏的内容;;
---------------------------------
2.顶点着色器
struct appdata_full {//vertex输入
    float4 vertex : POSITION;//must
    float4 tangent : TANGENT;
    float3 normal : NORMAL;
    float4 texcoord : TEXCOORD0;//该顶点的纹理坐标,第一组纹理坐标uv 也就是第一张贴图的坐标、为了实现多重纹理贴图,比如子弹打在墙上的弹痕等
    float4 texcoord1 : TEXCOORD1;//n的数量和shadermodel版本有关, Mesh.uv2 
    float4 texcoord2 : TEXCOORD2;//Mesh.uv3  可以通过外部设置该uv坐标
    float4 texcoord3 : TEXCOORD3;
    fixed4 color : COLOR;//顶点颜色
};@上次买的


3.片段着色器
struct v2f{//vertec的输出和frag的输入
    float4 vertex :SV_POSITION;//must
    float3 color0:COLOR0;
    float3 color1:COLOR1;
    float4:texcoord:TEXCOORD0;//TEXCOORD0-TEXCOORD7自定义纹理坐标
}
SV_Tatget //frag的输出,输出float4的颜色 也可以是COLOR0各个平台会自动转换标志

-----------------------------------
光照:
1.逐顶点光照:在顶点着色器阶段计算光照,效率高但是效果不好,在边缘像素映射的时候插值可能会产生锯齿。
2.逐像素光照:在片元着色器阶段计算光照,计算量大,但是边缘表现效果好。
3.半兰伯特模型:处理无光照的地方,也让其有光,不然可能是全黑。经验模型。
4.Blinn-Phong模型:高光反射模型的经验模型,高光部分看起来会更大更亮写。更符合实际些。

#include "Lighting.cginc"
Tags { "LightMode"="ForwardBase" }

WorldSpaceViewDir(float4 v)  输入模型空间中的顶点坐标,返回世界空间中从该点到摄像机的观察方向
UnityWorldSpaceViewDir(float4 v)  输入世界空间中的顶点坐标,返回世界空间中从该点到摄像机的观察方向
ObjSpaceViewDir(float4 v)     输入模型空间中的顶点坐标,返回模型空间中从该点到摄像机的观察方向
WorldSpaceLightDir()        仅用于前向渲染,输入模型空间中的顶点坐标,返回世界空间中从该点到光源光照方向,没有归一化。
UnityWorldSpaceLightDir()    仅用于前向渲染,输入世界空间中的顶点坐标,返回世界空间中从该点到光源光照方向,没有归一化。
ObjSpaceLightDir()            仅用于前向渲染,输入模型空间中的顶点坐标,返回模型空间中从该点到光源光照方向,没有归一化。
UnityObjectToWorldNormal(float3 v)把法线从模型空间转换到世界空间
UnityObjectToWorldDir(float3 v)把方向矢量从模型空间转换到世界空间
UnityWorldToObjectDir(float3 v)把方向矢量从世界空间转换到模型空间
_WorldSpaceLightPos0.xyz    获取平行光光源方向,或者点光源的光源位置
_LightColor0.rgb            获取当前pass的光源颜色和强度
UNITY_LIGHTMODEL_AMBIENT.xyz; 环境光
normalize(_WorldSpaceCameraPos.xyz - worldPos.xyz); 世界空间下的view视觉方向 UnityWorldSpaceViewDir

a.漫反射公式:diff=C*max(0,dot<L,N>);//C是颜色和强度_LightColor0.rgb
代码:  diff=max(0,dot(i.normal,i.lightDir))//i的单位向量and单位法向量
    c=tex2D(tex,i.uv)*_LightColor0*diff//_LightColor0表示的是场景中平行光的颜色和强度

b.高光反射公式:Spec=pow(max(0,dot(R,V),gloss))//R 单位反射向量reflect(ray,normal)函数获取,V视线单位方向向量 ,gloss光色度
代码: Spec=pow(max(0,dot(reflect(-i.lightDir,i.normal),viewdir),32))
    c=c**_LightColor0*(diff+Spec)
    
颜色相加和相乘的物理意义,一般的各种光源都是颜色叠加,相乘会变淡。

在PS中 tex2D 自动计算应该使用的纹理层。
tex2Dbias需要在t.w中指定一个偏移量来把自动计算出的纹理层全部偏移指定的值。
tex2Dgrad需要提供屏幕坐标x和y方向上的梯度来确定应该使用的纹理层。
tex2Dlod需要在t.w中明确指定要使用的纹理层。
texCUBElod(_Cube,float4(dir.xyz,lod))方向和Lod系数
-----------------------------------------------
纹理 uv坐标是顶点存储了图片上的坐标
_MainTex ("Main Tex", 2D) = "white" {}
sampler2D _MainTex;
float4 _MainTex_ST;//Unity中 纹理_ST来默认声明该纹理的属性_MainTex_ST.xy表示Scale, Till缩放,_MainTex_ST.zw表示Transform 偏移
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);//vs输入纹理坐标和纹理值输出UV,ps对uv进行纹理采样和计算。UV通常在0-1范围,等于o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;//反射率

法线贴图:xyz映射存成rgb值。一般存在切线空间,z轴法线方向,x轴切线方向,y轴副(法)切线方向,我们ds的法线贴图xy存法线z存粗糙度。
TANGENT_SPACE_ROTATION;//Unity来获取rotation矩阵,从模型空间到切线空间变换的矩阵。仅存在旋转和平移时,一个矩阵的转置矩阵等于他的逆矩阵。
自己实现:
float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w; //切线空间的w分量用来存储负法线向内还是向外
float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);//float3x3是按行存储

float3 tangentNormal = UnpackNormal(packedNormal);Unity将法线贴图纹理坐标0,1映射到正常法线坐标-1,1,返回切线空间下的法线方向。法线贴图要设置成Normal格式。该设置unity有优化 rgb值不再是法线xyz的映射了,如果不设置的话要自己算 该公式不能用。
自己实现:
tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;//坐标反映射,自己计算的方法 因为packedNormal.z分量一般被压缩去掉,比如我们ds的z分量存储的是粗糙度,xy存法线。
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));//通过xy计算z  法线贴图一般会采用压缩的方式存储 那么只有xy分量是正确的,z分量需要经过xy计算出来。
如果法线贴图是蓝色&

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值