- 本篇博客主要为个人学习所编写读书笔记,不用于任何商业用途,以及不允许任何人以任何形式进行转载。
- 本篇博客会补充一些扩展内容(例如其他博客链接)。
- 本篇博客还会提供一些边读边做的效果截图。文章内所有数学公式都由Latex在线编辑器生成。
- 本篇博客主要提供一个“glance”,知识点的总结。如有需要请到书店购买正版。
- 博客提及所有官方文档基于2022.2版本,博客会更新一些书中的旧的知识点到2022.2版本。
- 如有不对之处欢迎指正。
- 我创建了一个游戏制作交流群: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中的一些常用辅助函数:传送门,进行一些常用计算的辅助工作。