此系列仅为笔者复习使用,如有错误欢迎指正。
案例小结
Shader代码:
Shader "Unlit/L10_OldSchoolPro" { Properties { [Header(Texture)] _MainTex ("RGB:基础颜色,A:环境遮罩",2D) = "white" {} _NormalTex ("RGB:法线贴图",2D) = "bump" {} _SpecTex ("RGB:高光颜色 A:高光次幂",2D) = "gray" {} _EmitTex ("RGB:自发光贴图",2D) ="black"{} _CubeMap ("RGB:环境贴图",Cube) = "_Skybox"{} [Header(Diffuse)] _MainCol ("基本色",Color) = (0.5,0.5,0.5,1) _EnvDiffInt ("环境漫反射强度",Range(0,1)) = 0.2 _EnvUpCol ("环境天顶颜色",Color) = (1,1,1,1) _EnvSideCol ("环境水平颜色",Color) = (0.5,0.5,0.5,1) _EnvDownCol ("环境地表颜色",Color) = (0,0,0,1) [Header(Specular)] _SpecPow ("高光次幂",Range(1,90)) = 30 _EnvSpecInt ("环境镜面反射强度",Range(0,1)) = 0.5 _FresnelPow ("菲涅尔次幂",Range(0,5)) = 1 _CubeMapMip ("环境球Mip",Range(0,7)) = 0 [Header(Emission)] _EmitInt("自发光强度",Range(1,10)) = 1 } SubShader { Tags { "RenderType"="Opaque" } Pass { Name "FORWARD" Tags { "LightMode"="ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" // 追加投影相关包含文件 #include "AutoLight.cginc" #include "Lighting.cginc" #pragma multi_compile_fwdbase_fullshadows #pragma target 3.0 //输入参数 //Texture uniform sampler2D _MainTex; uniform sampler2D _NormalTex; uniform sampler2D _SpecTex; uniform sampler2D _EmitTex; uniform samplerCUBE _CubeMap; //Color uniform float3 _MainCol; uniform float _EnvDiffInt; uniform float3 _EnvUpCol; uniform float3 _EnvSideCol; uniform float3 _EnvDownCol; //Specular uniform float _SpecPow; uniform float _EnvSpecInt; uniform float _FresnelPow; uniform float _CubeMapMip; //Emission uniform float _EmitInt; // 输入结构 struct VertexInput { float4 vertex : POSITION; // 将模型的顶点信息输入进来 float2 uv0 : TEXCOORD0; float4 normal : NORMAL; // 法线 float4 tangent : TANGENT; // 切线 }; // 输出结构 struct VertexOutput { float4 pos : SV_POSITION; // 由模型顶点信息换算而来的顶点屏幕位置 float2 uv0 : TEXCOORD0; // 纹理坐标 float3 posWS : TEXCOORD1; // 世界空间顶点位置 float3 nDirWS : TEXCOORD2; // 法线方向 float3 tDirWS : TEXCOORD3; // 切线方向 float3 bDirWS : TEXCOORD4; // 副法线方向 LIGHTING_COORDS(5,6) // 投影相关 }; // 输入结构>>>顶点Shader>>>输出结构 VertexOutput vert (VertexInput v) { VertexOutput o = (VertexOutput)0; // 新建一个输出结构 o.pos = UnityObjectToClipPos( v.vertex ); // 变换顶点信息 并将其塞给输出结构 o.uv0 = v.uv0; // 传递纹理坐标 o.posWS = mul(unity_ObjectToWorld, v.vertex).xyz; // 世界空间顶点位置 o.nDirWS = UnityObjectToWorldNormal(v.normal); // 法线方向 o.tDirWS = normalize( mul( unity_ObjectToWorld, float4( v.tangent.xyz, 0.0 ) ).xyz ); // 切线方向 o.bDirWS = normalize(cross(o.nDirWS, o.tDirWS)); // 副法线方向 TRANSFER_VERTEX_TO_FRAGMENT(o); // 投影相关 return o; // 将输出结构 输出 } // 输出结构>>>像素 float4 frag(VertexOutput i) : COLOR { //准备向量 float3 nDirTS = UnpackNormal(tex2D(_NormalTex, i.uv0)).rgb; float3x3 TBN = float3x3(i.tDirWS, i.bDirWS, i.nDirWS); float3 nDirWS = normalize(mul(TBN, nDirTS)); float3 vDirWS = normalize(_WorldSpaceCameraPos.xyz - i.posWS.xyz); float3 vrDirWS = reflect(-vDirWS, nDirWS); float3 lDirWS = normalize(_WorldSpaceLightPos0.xyz); float3 lrDirWS = reflect(-lDirWS, nDirWS); //准备点积结果 float nDotl = dot(nDirWS, lDirWS);//lambert使用 float vDotr = dot(vDirWS, lrDirWS);//phong使用 float vDotn = dot(vDirWS, nDirWS);//菲涅尔反射使用 //采样纹理 float4 var_MainTex = tex2D(_MainTex, i.uv0); float4 var_SpecTex = tex2D(_SpecTex, i.uv0); float3 var_EmitTex = tex2D(_EmitTex, i.uv0).rgb; float3 var_CubeMap = texCUBElod(_CubeMap, float4(vrDirWS, lerp(_CubeMapMip, 0,var_SpecTex.a))); //光照模型(直接光照部分) float3 baseCol = var_MainTex.rgb * _MainCol; float lambert = max(0, nDotl); float specCol = var_SpecTex.rgb; float specPow = lerp(1,_SpecPow ,var_SpecTex.a); float phong = pow(max(0, vDotr), specPow); float shadow = LIGHT_ATTENUATION(i); float3 dirLighting = (baseCol * lambert + specCol * phong) * _LightColor0 * shadow; //光照模型(环境光照部分) float upMask = max(0,nDirWS.y); float downMask = max(0,-nDirWS.y); float sideMask = 1 - upMask - downMask; float3 envCol = _EnvUpCol * upMask + _EnvDownCol * downMask + _EnvSideCol * sideMask; float fresnel = pow(max(0,1 - vDotn), _FresnelPow); float occlusion = var_MainTex.a; float3 envLighting = (baseCol * envCol * _EnvDiffInt + var_CubeMap * fresnel * _EnvSpecInt * var_SpecTex.a) * occlusion; //光照模型(自发光部分) float3 emission = _EmitInt * var_EmitTex; //返回结果 float3 finalCol = dirLighting + envLighting + emission; return float4(finalCol, 1.0); } ENDCG } } FallBack "Diffuse" }功能解释:
这段Shader代码实现了一个风格化的旧式专业级光照模型,结合了多种光照技术。
纹理系统
基础颜色贴图:RGB通道存储基础颜色,A通道作为环境遮罩(AO)
法线贴图:实现表面细节
高光贴图:RGB通道存储高光颜色,A通道控制高光次幂
自发光贴图:控制自发光区域
环境立方体贴图:用于环境反射
光照模型
直接光照部分
Lambert漫反射:基础漫反射计算
Phong高光:可控制高光范围和强度
实时阴影支持:通过
LIGHT_ATTENUATION实现
环境光照部分
三色环境光:天顶、水平、地表三个方向可配置不同颜色
菲涅尔反射:基于视角的法线点积控制反射强度
立方体贴图反射:使用Mipmap级别控制反射模糊度
自发光部分
可调节强度的自发光效果
技术特点
基于物理的渲染:使用了世界空间坐标系
法线贴图支持:通过TBN矩阵转换法线
多纹理混合:颜色、法线、高光、自发光分离控制
性能优化:
支持阴影投射
使用Fallback "Diffuse"确保兼容性
合理的精度控制
可调节参数
漫反射:基本色、环境漫反射强度、三色环境色
高光:高光次幂、环境反射强度、菲涅尔次幂
环境:立方体贴图Mip级别
自发光:强度控制
个人疑惑点自解:
float3 var_CubeMap = texCUBElod(_CubeMap, float4(vrDirWS, lerp(_CubeMapMip, 0,var_SpecTex.a)));这是根据高光粗糙度动态混合Mip级别的关键部分:
// 高光贴图的Alpha通道意义: // - var_SpecTex.a = 0.0 → 完全光滑的表面(如镜子) // - var_SpecTex.a = 1.0 → 非常粗糙的表面(如磨砂金属) // lerp函数的计算: // 当var_SpecTex.a = 0.0时:使用_CubeMapMip(最模糊) // 当var_SpecTex.a = 1.0时:使用0(最清晰) // 中间值:线性插值
效果展示
Shader编写阶段性总结
面板参数声明格式
数值,范围: _Name("标签名",float)=deafultVal _Name("标签名”,range(min,max))=deafultVal _Name("标签名",int)=deafultVal 位置,向量,颜色: _Name("标签名”,vector)=(xVal,yVal,zVal,wVal) _Name("标签名",color) =(rVal, gVal, bVal, aVal) 2D,3D纹理,环境球: _Name("标签名",2d)="deafultTex"{} _Name("标签名”,3d)="deafultTex"{} _Name("标签名",cube)="deafultTex"{}参数属性
Shaderlab中的参数类型
参数的精度选择
可访问的顶点Input数据
常用的顶点Output数据
常用顶点Shader操作
常用方法(功能模块化,代码复用)
三色环境光方法
原写法:
float upMask = max(0,nDirWS.g);//获取朝上遮罩 float downMask = max(0,-nDirWS.g);//获取朝下遮罩 float sideMask = 1 - upMask - downMask;//获取侧面遮罩 float3 envCol = upMask * _EnvUpCol + downMask * _EnvDownCol + sideMask * _EnvSideCol;//混合环境色归纳为方法的写法:
//3Col环境色方法2 float3 TriColAmbient (float3 n, float3 uCol, float3 sCol, float3 dCol){ float upMask = max(0.0, n.g); float downMask = max(0.0, -n.g); float sideMask = 1.0 - upMask - downMask; float3 envCol = uCol * upMask + sCol * sideMask + dCol * downMask; return envCol; } //使用三色环境光方法 float3 envCol = TriColAmbient(nDirWS, _EnvUpCol, _EnvSideCol, _EnvDownCol);库的创建方法:
1.文件夹创建,常用Cginc文件夹来存放库
2.在文件管理器中打开,点击Show in Explorer
3.在Cginc文件夹中创建txt文本文档,进行命名并将文件后缀改为cginc
4.在编辑器中打开
#ifndef MY_CGINC #define MY_CGINC float3 TriColAmbient(float3 n, float3 uCol, float3 sCol, float3 dCol) { float upMask = max(0.0, n.g); float downMask = max(0.0, -n.g); float sideMask = 1.0 - upMask - downMask; float3 envCol = uCol * upMask + sCol * sideMask + dCol * downMask; return envCol; } #endif自定义的CGInclude文件头部保护宏#ifndef MY_CGINC // 如果MY_CGINC宏没有被定义 #define MY_CGINC // 那么定义MY_CGINC宏 // 这里是实际的代码内容 #endif // 结束条件编译
工作原理
1.防止重复包含
hlsl
// 第一次包含该文件时: #ifndef MY_CGINC // 条件为真(因为MY_CGINC尚未定义) #define MY_CGINC // 定义MY_CGINC宏 // 执行代码内容 #endif // 第二次尝试包含同一文件时: #ifndef MY_CGINC // 条件为假(因为MY_CGINC已定义) #define MY_CGINC // 这行和后续代码都不会执行 // 跳过所有代码 #endif
2.为什么需要这个?
避免编译错误:
在Shader中,同一个函数或变量不能重复定义
如果多个文件都包含了这个CGINC文件,会导致重复定义错误
这个保护机制确保代码只被包含一次
5.在Shader中进行引用
#include "../Cginc/MyCginc.cginc"..表示上一级文件夹,往下找到库文件夹