《Unity Shader入门精要》笔记:初级篇(2)

  3.2 游戏引擎技术
  • 本篇博客主要为个人学习所编写读书笔记,不用于任何商业用途,以及不允许任何人以任何形式进行转载。
  • 本篇博客会补充一些扩展内容(例如其他博客链接)。
  • 本篇博客还会提供一些边读边做的效果截图。文章内所有数学公式都由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博客,内有详细介绍并附带视频链接。
  • 漫反射公式=
image 150 - 《Unity Shader入门精要》笔记:初级篇(2)
  • 逐顶点光照:
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"
}
image 151 402x1024 - 《Unity Shader入门精要》笔记:初级篇(2)
image 152 - 《Unity Shader入门精要》笔记:初级篇(2)
  • 逐像素光照:得到的效果更加平滑,但无法解决光照无法到达的地方都是黑色的。
//对于书中最后的计算输出式做了一些更改,添加了一个自定义整体亮度的控制模块
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"
}
image 153 - 《Unity Shader入门精要》笔记:初级篇(2)
image 154 - 《Unity Shader入门精要》笔记:初级篇(2)
在Unity对比了一下感觉确实更细腻了
  • 半兰伯特模型:绝大多数情况下α=β=0.5,用于修正物体背面过暗的情况。
image 155 - 《Unity Shader入门精要》笔记:初级篇(2)
image 156 - 《Unity Shader入门精要》笔记:初级篇(2)
正面
image 157 - 《Unity Shader入门精要》笔记:初级篇(2)
背面

高光反射光照模型

  • 高光反射计算公式:入射光线颜色强度x材质高光反射系数x max(0,视角方向和反射方向的点积)的管泽度次方。
image 159 1024x104 - 《Unity Shader入门精要》笔记:初级篇(2)
  • 逐像素光照
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"
}
  • 下图水晶在游戏窗口内实际差别还是比较明显的,可以看到水晶柱中间部分的颜色细节因为高光更加丰富了一些。
image 160 - 《Unity Shader入门精要》笔记:初级篇(2)
不开高光
image 162 - 《Unity Shader入门精要》笔记:初级篇(2)
高光拉满,gloss=8
image 163 594x1024 - 《Unity Shader入门精要》笔记:初级篇(2)
gloss=24
  • Blinn-Phong光照模型:通过视角方向v和光照方向l相加后在归一化得到矢量h,用n和h的点积计算高光结果。Blinn-Phong光照模型高光反射看起来更大更亮一些,实际渲染中比较偏好该模型。
image 165 - 《Unity Shader入门精要》笔记:初级篇(2)
image 164 - 《Unity Shader入门精要》笔记:初级篇(2)

补充

  • UnityCG.cginc中的一些常用辅助函数:传送门,进行一些常用计算的辅助工作。

LEAVE A COMMENT