news 2026/4/3 6:11:16

TA自学习复习文档(三)——Shader案例及编写规范阶段小结

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TA自学习复习文档(三)——Shader案例及编写规范阶段小结

此系列仅为笔者复习使用,如有错误欢迎指正。

案例小结

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"

..表示上一级文件夹,往下找到库文件夹

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/31 1:02:58

光伏充电站的“弹性“密码:当电动车遇上数学建模

考虑光伏出力利用率的电动汽车充电站能量调度策略 仿真软件:matlab cvx 注意事项:程序注释详细,提供cvx求解器安装包和安装方法。 代码内容: 针对间歇性能源利用的问题,构建电动汽车的充放电灵活度指标,用以评估电动汽车参与光伏充电站能量调…

作者头像 李华
网站建设 2026/4/1 2:58:51

手把手搞风光储微电网:从Simulink建模到可变负载调教

Matlab/Simulink,风光储微电网并网,三相RLC可变负载微电网并网这事儿,玩过的人都知道最刺激的就是系统稳定性。特别是风光储混合系统,光伏看天吃饭,风机抽风式发电,再加上个时刻蹦迪的三相负载——这种动态…

作者头像 李华
网站建设 2026/4/2 21:36:14

MMEvol: Empowering Multimodal Large Language Models with Evol-Instruct

MMEvol: Empowering Multimodal Large Language Models with Evol-Instruct 动机 多模态指令数据进化框架。该框架通过对精细化感知、认知推理和交互进化的有机结合进行迭代优化,从而生成更加复杂、多样的图文指令数据集,并显著提升 MLLM 的能力。目前的…

作者头像 李华
网站建设 2026/4/1 15:50:11

大白话Proactor模式

大白话Proactor模式 Proactor模式是异步IO事件驱动的高性能IO设计模式,和Reactor(同步IO事件驱动)是高性能网络/文件编程的两大核心模式。本文用「餐厅运营」的生活例子类比,一步步拆解Proactor的核心逻辑,再通过C实现…

作者头像 李华
网站建设 2026/3/28 16:13:56

《Python实战小课:数据分析场景——解锁数据洞察之力》导读

在当今数据驱动的时代,数据分析能力已成为职场人士和学习者必备的技能之一。然而,原始数据往往存在各种问题,如噪声、缺失值、重复值等,这就需要我们对数据进行清洗、可视化以及自动化统计分析,才能从数据中提取有价值…

作者头像 李华
网站建设 2026/4/1 2:33:04

南京国家公祭日 缅怀先烈

《南京国家公祭日 缅怀先烈》作家/罗光记1937年12月13日,这是一个永远铭刻在中国历史长河中的黑暗日子,南京城遭受了惨绝人寰的大屠杀。在这个特殊的日子——国家公祭日,我们缅怀逝者,铭记历史,更应从历史中…

作者头像 李华