- 本篇博客主要为个人学习所编写读书笔记,不用于任何商业用途,以及不允许任何人以任何形式进行转载。
- 本篇博客会补充一些扩展内容(例如其他博客链接)。
- 本篇博客还会提供一些边读边做的效果截图。文章内所有数学公式都由Latex在线编辑器生成。
- 本篇博客主要提供一个“glance”,知识点的总结。如有需要请到书店购买正版。
- 博客提及所有官方文档基于2022.2版本,博客会更新一些书中的旧的知识点到2022.2版本。
-
个人博客网址:《Unity Shader入门精要》笔记:初级篇(2) - Sugar的博客,如文章转载中出现例如传送门失效等纰漏,建议直接访问原博客网址。
- 如有不对之处欢迎指正。
- 我创建了一个游戏制作交流群:637959304 进群密码:(CSGO的拆包密码)欢迎各位大佬一起学习交流,不限于任何平台(U3D、UE、COCO2dx、GamesMaker等),以及欢迎编程,美术,音乐等游戏相关的任何人员一起进群学习交流。
目录
- 初级篇内容主要讲述关于基础的光照模型、纹理和透视等的初级渲染效果。(这部分也可以同时阅读我的HLSL博客内容进行学习)
- 注意:下文中的部分效果截图为水平全局光照的效果,想要达到那种高光在右上角的效果,可以自行调整光照的角度。
基础光照
- 模拟真实环境生成图像步骤
1、光线从光源中发射出来
2、光线和场景物体想交,进行折射、反射、吸收的效果。
3、摄像机吸收到物体折射或反射到的光,产生了图像 - 光源(Light source):用辐照度(irradiance)量化光的多少。平行光,计算在垂直于l(表示方向)的单位面积上单位时间内穿过的能量来得到。非平行光,多一步计算l和法线n之间的夹角的余弦值来得到。
- 吸收(absorption):只改变光线的密度和颜色。
- 散射(scattering):散射只改变光的方向,不改变光线的密度和颜色。光线散射到物体内部就被称为折射(refraction),散射到外部就被称为反射(reflection)。
- 高光反射(specular):物体表面是如何反射光线的。
- 漫反射(diffuse):有多少光线会被折射、吸收和散射出表面。
- 着色(shading):根据材质属性、光源信息来使用一个等式去计算沿某个观察方向的出射度(exitance,出射光线的数量和方向)的过程。该等式称为光照模型(Lighting Model)。
- BRDF光照模型(Bidirectional Reflectance Distribution Function):解决光线相关的问题。
- 标准光照模型:把进入到摄像机内的光线分为四个部分,每个部分使用一种方法来计算它的贡献度。
1、自发光(emissive):描述当给定一个方向时,一个表面本身会向该方向发射多少辐射量。一般直接采用材质的自发光颜色。
2、高光反射(specular):描述当光线从光源照射到模型表面时,该表面会在完全镜面反射方向散射多少辐射量。
3、漫反射(diffuse):描述当光线从光源照射到模型表面时,该表面会向每个方向散射多少辐射量。漫反射光符合兰伯特定律(Lambert’s law),反射光线的强度与表面法线和光源法线之间的夹角成正比。
4、环境光(ambient):描述其他所有的间接光照。 - 逐像素光照(per-pixel lighting)在片元着色器中计算。以每个像素为基础,得到他的法线,然后进行光照模型的计算。在面片之间对顶点法线进行插值的技术被称为Phong着色(Phong shading),不同于Phong光照模型。
- 逐顶点光照(per-vertex lighting):在顶点着色器中计算。也称为高洛德着色(Gouraud shading)。我们在每个顶点上计算光照,然后再渲染图元内部进行线性插值,最后输出成像素颜色。逐顶点光照依赖线性插值,所以非线性计算(例如高光反射)不能使用该方式。
- 官方着色器模板范文-纹理采样:
Shader "Unlit/Screen Position" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 3.0 // 注意:此结构中没有 SV_POSITION struct v2f { float2 uv : TEXCOORD0; }; v2f vert ( float4 vertex : POSITION, // 顶点位置输入 float2 uv : TEXCOORD0, // 纹理坐标输入 out float4 outpos : SV_POSITION // 裁剪空间位置输出 ) { v2f o; o.uv = uv; outpos = UnityObjectToClipPos(vertex); return o; } sampler2D _MainTex; fixed4 frag (v2f i, UNITY_VPOS_TYPE screenPos : VPOS) : SV_Target { // screenPos.xy 将包含像素整数坐标。 // 使用它们来实现一个跳过渲染 4x4 像素块的 // 棋盘图案 // 棋盘图案中 4x4 像素块的 checker 值 // 为负 screenPos.xy = floor(screenPos.xy * 0.25) * 0.5; float checker = -frac(screenPos.r + screenPos.g); // 如果值为负,则 clip HLSL 指令停止渲染像素 clip(checker); // 对于保留的像素,读取纹理并将其输出 fixed4 c = tex2D (_MainTex, i.uv); return c; } ENDCG } } }
Unity中实现漫反射光照模型
- 具体原理请移步我的HLSL博客,内有详细介绍并附带视频链接。
- 漫反射公式=
- 逐顶点光照:
shader "Example/Shader01" { Properties { _Color ("Color Tint",Color) = (1.0,1.0,1.0,1.0) _MainTex("Base (RGB)", 2D) = "white" { } //定义漫反射颜色,并赋初值为白色 _Diffuse("Diffuse",Color) = (1,1,1,1) } SubShader { Pass { //定义Pass在Unity的光照流水线的角色,定义正确的LightMode才能得到内置光照变量 Tags{"LightMode" = "ForwardBase"} CGPROGRAM //使用_LightColor0要用到该头文件 #include"UnityCG.cginc" #pragma vertex vert #pragma fragment frag #include"Lighting.cginc" //定义名称和Properties中一样,然后才可以在pass中使用 fixed4 _Color; fixed4 _Diffuse; sampler2D _MainTex; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float2 texcoord : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; fixed3 color : COLOR; float2 texcoord : TEXCOORD0; }; v2f vert(a2v v) { v2f o; o.texcoord = v.texcoord; o.pos = UnityObjectToClipPos(v.vertex); //获取系统环境光 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; //转换数据到世界坐标 fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject)); fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz); //计算漫反射 fixed3 diffuse = _LightColor0.rgb + _Diffuse.rgb * saturate(dot(worldNormal,worldLight)); //输出环境光+漫反射 o.color = ambient + diffuse; return o; } fixed4 frag(v2f i) : SV_Target { //获取纹理材质 fixed4 c = tex2D(_MainTex,i.texcoord);//fixed4(i.color,1.0); //注意这里是乘法 return c * fixed4(i.color,1.0); } ENDCG } } Fallback "Diffuse" }
- 逐像素光照:得到的效果更加平滑,但无法解决光照无法到达的地方都是黑色的。
//对于书中最后的计算输出式做了一些更改,添加了一个自定义整体亮度的控制模块 shader "Example/Shader01" { Properties { _Color ("Color Tint",Color) = (1.0,1.0,1.0,1.0) _MainTex("Base (RGB)", 2D) = "white" { } _Diffuse("Diffuse",Color) = (1,1,1,1) _Light("Light",Color) = (1.0,1.0,1.0,1.0) } SubShader { Pass { Tags{"LightMode" = "ForwardBase"} CGPROGRAM #include"UnityCG.cginc" #pragma vertex vert #pragma fragment frag #include"Lighting.cginc" fixed4 _Color; fixed4 _Diffuse; fixed4 _Light; sampler2D _MainTex; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float2 texcoord : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; fixed3 color : COLOR; float2 texcoord : TEXCOORD0; float3 worldNormal : TEXCOORD1; }; v2f vert(a2v v) { v2f o; o.texcoord = v.texcoord; o.pos = UnityObjectToClipPos(v.vertex); o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject); return o; } fixed4 frag(v2f i) : SV_Target { fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightDir)); fixed4 c = tex2D(_MainTex,i.texcoord);//fixed4(i.color,1.0); //这里的c是材质的原本光照颜色,_Light是提高物体表面颜色(因为我的贴图有点暗),之后的才算是漫反射的光照结果。 return (c + _Light) * fixed4(ambient + diffuse,1.0) ; } ENDCG } } Fallback "Diffuse" }
在Unity对比了一下感觉确实更细腻了
- 半兰伯特模型:绝大多数情况下α=β=0.5,用于修正物体背面过暗的情况。
正面
背面
高光反射光照模型
- 高光反射计算公式:入射光线颜色强度x材质高光反射系数x max(0,视角方向和反射方向的点积)的管泽度次方。
- 逐像素光照
shader "Example/Shader01" { Properties { _MainTex("Base (RGB)", 2D) = "white" { } _Diffuse("Diffuse",Color) = (1,1,1,1) _Specular("Specular",Color) = (1,1,1,1) _Gloss("Gloss",Range(8.0,256)) = 20 _Light("Light",Color) = (1.0,1.0,1.0,1.0) } SubShader { Pass { Tags{"LightMode" = "ForwardBase"} CGPROGRAM #pragma vertex vert #pragma fragment frag #include"Lighting.cginc" #include"UnityCG.cginc" fixed4 _Diffuse; fixed4 _Specular; float _Gloss; fixed4 _Light; sampler2D _MainTex; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float2 texcoord : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; fixed3 color : COLOR; float2 texcoord : TEXCOORD0; float3 worldNormal : TEXCOORD1; float3 worldPos : TEXCOORD2; }; v2f vert(a2v v) { v2f o; o.texcoord = v.texcoord; o.pos = UnityObjectToClipPos(v.vertex); o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject); o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz; return o; } fixed4 frag(v2f i) : SV_Target { fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * (dot(worldNormal,worldLightDir)*0.5+0.5); //高光计算部分 fixed3 reflectDir = normalize(reflect(-worldLightDir,worldNormal)); fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir,viewDir)),_Gloss); fixed4 c = tex2D(_MainTex,i.texcoord); return (c + _Light) * fixed4(ambient + diffuse + specular,1.0) ; } ENDCG } } Fallback "Diffuse" }
- 下图水晶在游戏窗口内实际差别还是比较明显的,可以看到水晶柱中间部分的颜色细节因为高光更加丰富了一些。
不开高光
高光拉满,gloss=8
gloss=24
- Blinn-Phong光照模型:通过视角方向v和光照方向l相加后在归一化得到矢量h,用n和h的点积计算高光结果。Blinn-Phong光照模型高光反射看起来更大更亮一些,实际渲染中比较偏好该模型。
补充
- UnityCG.cginc中的一些常用辅助函数:传送门,进行一些常用计算的辅助工作。