nexttrace大小包路由测试
大包测试,发送1024k数据:
nexttrace --tcp --psize 1024 ln-ct-v4.ip.zstaticcdn.com -p 80小包测试,发送12k数据:
nexttrace --tcp --psize 12 ln-ct-v4.ip.zstaticcdn.com -p 80大包测试,发送1024k数据:
nexttrace --tcp --psize 1024 ln-ct-v4.ip.zstaticcdn.com -p 80小包测试,发送12k数据:
nexttrace --tcp --psize 12 ln-ct-v4.ip.zstaticcdn.com -p 80安装gnupg
sudo apt install gnupg -y下载并导入 Cloudflare 软件仓库的GPG公钥
curl https://pkg.cloudflareclient.com/pubkey.gpg | sudo gpg --yes --dearmor --output /usr/share/keyrings/cloudflare-warp-archive-keyring.gpg添加 Cloudflare 的软件仓库到系统软件源
echo "deb [signed-by=/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg] https://pkg.cloudflareclient.com/ $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/cloudflare-client.list更新软件源
写入dns
printf "nameserver 1.1.1.1\nnameserver 1.0.0.1\n" > /etc/resolv.conf锁定resolv
chattr +i /etc/resolv.conf解锁:
chattr -i /etc/resolv.conf 2>/dev/null || true我们需要告诉内核禁用“一致性网络设备命名”规则。
# 1. 备份并修改 GRUB 配置文件
sed -i 's/GRUB_CMDLINE_LINUX=""/GRUB_CMDLINE_LINUX="net.ifnames=0 biosdevname=0"/' /etc/default/grub
# 如果原本这一行就有内容,请手动编辑 /etc/default/grub
# 将 net.ifnames=0 biosdevname=0 添加到引号内update-grub这一步至关重要,必须将配置文件里的 ens17 全部替换为 eth0,否则重启后系统找不到网卡。
# 3. 备份原始配置文件(万一出故障可以还原)
cp /etc/network/interfaces /etc/network/interfaces.bak
# 4. 使用 sed 批量替换 ens17 为 eth0
sed -i 's/ens17/eth0/g' /etc/network/interfaces在重启之前,请务必确认文件修改正确:
依据cloudflare博客计算BDP,以美西为例:带宽取1000Mbps延迟取200ms
BDP = 125MB/s × 0.2s = 25MB得到25MB四舍五入为32MB。
由于 Linux 自动调优能够正确调整 RTT 较低的会话和吞吐量较低的瓶颈链路,我们只需要调整最大值即可(其他值为默认)。
tcp_adv_win_scale 使用默认的1。最大窗口大小为最大缓冲区空间的二分之一,所以需要将最大窗口大小设置为64M。
net.ipv4.tcp_rmem = 4096 131072 67108864
net.ipv4.tcp_wmem = 4096 16384 67108864在bbr打开时,如果长连接比较多 设置net.ipv4.tcp_slow_start_after_idle = 0这样子空闲一段时间,之前bbr估算的拥塞窗口大小也不会从头探测
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr
net.ipv4.tcp_slow_start_after_idle = 0net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr
net.ipv4.tcp_rmem = 4096 131072 67108864
net.ipv4.tcp_wmem = 4096 16384 67108864
net.ipv4.tcp_notsent_lowat = 131072
net.ipv4.tcp_slow_start_after_idle = 0winget 是微软推出的开源软件包管理器。它允许用户通过简单的命令行操作,实现软件的搜索、安装、升级、卸载和配置。
为什么要用它?
打开你的 PowerShell 或 命令提示符 (CMD),开启你的命令行装机之旅。
不知道软件的确切名称?先搜一下:
winget search <软件名>提示: 如果结果较多,可以使用
winget search "Google Chrome"这样带引号的精确匹配。
找到软件后,使用 install 命令:
# 以安装 VS Code 为例
winget install "Visual Studio Code"如果你想静默安装(不弹出安装界面),可以加上参数:
在 Windows 中,WSL 的安装已经变得极其简单。只需在管理员权限的 PowerShell 中执行:
| 功能 | 命令 |
|---|---|
| 一键安装 | wsl --install |
| 内核更新 | wsl --update |
| 查看已安装系统 | wsl --list -v |
| 设置默认系统 | wsl --set-default <DistroName> |
| 彻底卸载系统 | wsl --unregister <DistroName> |
这是 Windows 11 引入的重磅功能,解决了 WSL 无法直接访问宿主机 VPN、IP 变动等痛点。
Win + R 输入 %UserProfile%。.wslconfig (确保无 .txt 后缀)。[wsl2]
# 开启镜像网络模式,WSL 将共享 Windows 的 IP
networkingMode=mirrored
# 开启 DNS 隧道,解决 VPN 导致的无法上网问题
dnsTunneling=true
# 开启防火墙,让 Windows 安全策略同步生效
firewall=true
# 自动同步 Windows 代理设置
autoProxy=true注意:配置保存后,必须执行
wsl --shutdown重启才能生效。
在 Linux 系统中,设置定时任务最常用的工具是 crontab。你可以把它想象成一个自动化的“闹钟”,让系统在指定的时间执行特定的脚本或命令。
以下是关于 Linux 定时任务(Cron Job)的快速指南:
在终端中输入以下命令来管理你的任务列表:
crontab -e: 编辑当前用户的定时任务(首次使用会让你选择编辑器,建议选 nano 或 vim)。crontab -l: 查看当前所有的定时任务。crontab -r: 删除当前用户的所有定时任务。当你进入编辑模式后,每一行代表一个任务,格式如下:
Bash
# ┌───────────── 分钟 (0 - 59)
# │ ┌─────────── 小时 (0 - 23)
# │ │ ┌───────── 月份中的第几天 (1 - 31)
# │ │ │ ┌─────── 月份 (1 - 12)
# │ │ │ │ ┌───── 星期中的第几天 (0 - 6) (0是周日)
# │ │ │ │ │
# * * * * * 要执行的命令或脚本路径这里有一些可以直接拿来改写的例子:
最近决定将博客从 Typecho 迁移到静态网站生成器 Hugo。相比动态博客,Hugo 带来的加载速度、安全性以及版本控制的便利性非常显著。本文记录了从数据库导出、自动化构建到 Algolia 搜索集成的完整过程。
.db数据库文件到本地。usr 文件夹到本地。Typecho 的文章存储在 SQLite 数据库中。为了保留分类和标签层级,使用gemini编写了这个 Python 脚本。该脚本会自动处理 <!--markdown--> 标记,并按照 分类/年/月 的结构组织文件,方便后期管理。
import sqlite3
import os
import datetime
import re
import json
# 配置信息
DB_PATH = 'typecho.db'
OUTPUT_DIR = os.path.expanduser('~/blog_source/content/posts') # 直接指向你的 Hugo 目录
FIXED_AUTHOR = "mona" # 统一作者名为 mona
def clean_path_name(name):
"""过滤文件夹或文件名中的非法字符"""
if not name:
return "default"
# 替换 Windows/Linux 路径中的非法字符
name = re.sub(r'[\\/:*?"<>|\r\n]', '_', name).strip()
return name if name else "default"
def migrate():
if not os.path.exists(DB_PATH):
print(f"错误:未找到数据库文件 {DB_PATH}")
return
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
# 查询文章:cid, title, created, text
query = "SELECT cid, title, created, text FROM typecho_contents WHERE type='post' AND status='publish'"
cursor.execute(query)
posts = cursor.fetchall()
if not os.path.exists(OUTPUT_DIR):
os.makedirs(OUTPUT_DIR)
print(f"开始迁移并清理标签:共 {len(posts)} 篇文章...")
for row in posts:
curr_cid, original_title, curr_created, curr_text = row
# 1. 解析日期
dt = datetime.datetime.fromtimestamp(curr_created)
year_str = dt.strftime('%Y')
month_str = dt.strftime('%m')
date_iso = dt.strftime('%Y-%m-%dT%H:%M:%S+08:00')
# 2. 获取分类和标签
meta_query = """
SELECT m.name, m.type
FROM typecho_metas m
INNER JOIN typecho_relationships r ON m.mid = r.mid
WHERE r.cid = ?
"""
cursor.execute(meta_query, (curr_cid,))
metas = cursor.fetchall()
categories = [m[0] for m in metas if m[1] == 'category']
tags = [m[0] for m in metas if m[1] == 'tag']
# 3. 核心:合并你的清理逻辑
# 在写入前直接替换掉所有<!--markdown-->标签
content = curr_text.replace('<!--markdown-->', '')
# 4. 创建多层级目录 (分类/年/月)
main_category = categories[0] if categories else "uncategorized"
target_dir = os.path.join(OUTPUT_DIR, clean_path_name(main_category), year_str, month_str)
if not os.path.exists(target_dir):
os.makedirs(target_dir)
# 5. 确定文件路径
safe_filename = clean_path_name(original_title)
file_path = os.path.join(target_dir, f"{safe_filename}.md")
# 6. 写入 Hugo 格式文件
try:
with open(file_path, 'w', encoding='utf-8') as f:
f.write("---\n")
f.write(f'title: "{original_title}"\n')
f.write(f'author: "{FIXED_AUTHOR}"\n')
f.write(f"date: {date_iso}\n")
f.write(f"categories: {json.dumps(categories, ensure_ascii=False)}\n")
f.write(f"tags: {json.dumps(tags, ensure_ascii=False)}\n")
f.write("---\n\n")
f.write(content)
print(f"成功导出并清理: {file_path}")
except Exception as e:
print(f"处理失败 {original_title}: {e}")
conn.close()
print(f"\n迁移完成!所有文件已按分类和日期存入: {OUTPUT_DIR}")
if __name__ == "__main__":
migrate()winget install Hugo.Hugo --version 0.145.0hugo new site my-blog创建站点/content/posts目录,usr文件夹放入/static目录。找个喜欢的主题放入themes文件夹,根据文档按照自己的需求进行配置。
安装
curl https://get.acme.sh | sh -s email=my@example.com
source ~/.bashrc申请证书
acme.sh --issue -d my.domain.com --nginx自动重载nginx
acme.sh --install-cert -d my.domain.com \
--key-file /usr/share/nginx/ssl/my.domain.com.key \
--fullchain-file /usr/share/nginx/ssl/fullchain.cer \
--reloadcmd "nginx -t && systemctl reload nginx"CF的方式申请泛域名证书
安装必要软件包
apt install unattended-upgrades启用自动更新功能
dpkg-reconfigure -plow unattended-upgrades这步操作会在 /etc/apt/apt.conf.d/20auto-upgrades 自动生成配置,确保更新服务会在后台运行。
配置更新策略
vi /etc/apt/apt.conf.d/50unattended-upgradesUnattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";Sqlite执行
UPDATE typecho_contents SET text = REPLACE(text, 'https://域名', '');journalctl --disk-usage示例输出:
Archived and active journals take up 180.0M in the file system.sudo nano /etc/systemd/journald.conf添加:
SystemMaxUse=200M
MaxRetentionSec=2day重启服务
echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99ignore-release-datedebian10
deb http://archive.debian.org/debian/ buster main contrib non-free
deb-src http://archive.debian.org/debian/ buster main contrib non-free
deb http://archive.debian.org/debian-security/ buster/updates main contrib non-free
deb-src http://archive.debian.org/debian-security/ buster/updates main contrib non-free
deb http://archive.debian.org/debian/ buster-updates main contrib non-free
deb-src http://archive.debian.org/debian/ buster-updates main contrib non-free
deb http://archive.debian.org/debian/ buster-backports main contrib non-free
deb-src http://archive.debian.org/debian/ buster-backports main contrib non-freedebian11
在 SSH 终端执行以下命令:
mkdir -p /etc/systemd/system/pkg-ContainerManager-dockerd.service.d创建并编辑代理配置文件
vi /etc/systemd/system/pkg-ContainerManager-dockerd.service.d/http-proxy.conf粘贴如下内容
[Service]
Environment="HTTP_PROXY=socks5://用户名:密码@192.168.1.3:10808"
Environment="HTTPS_PROXY=socks5://用户名:密码@192.168.1.3:10808"
Environment="NO_PROXY=localhost,127.0.0.1"重新加载并重启 Docker 服务
systemctl daemon-reload
systemctl restart pkg-ContainerManager-dockerd.service验证代理是否设置成功
有了深度测试和深度写入让我们不需要关心不透明物体的渲染顺序
比如
一个物体A 挡住了 物体B
即使A的渲染顺序小于B(先渲染A,后渲染B)我们也不用担心 B的颜色会把A覆盖
因为在进行深度测试时,远处的B的深度值无法通过深度测试
因为它的深度会比已经写入深度缓冲中A的深度值大
重合处的片元会被丢弃,颜色自然就不会写入,最终重叠处渲染出来的会是A的颜色
深度测试默认小于判断(深度值小于缓冲区的深度值 则通过测试)
深度写入默认开启(通过深度测试后 写入深度缓冲区)
混合模式默认不混合
渲染队列(默认几何Geometry队列)
对于不透明的物体来说,使用默认设置就能够得到正确的渲染效果
处理透明混合时,需要关闭深度写入
在图形学中模拟出 现实世界的半透明效果是通过将 多个颜色进行混合计算呈现出来的
如果不关闭深度写入 透明物体的深度信息 会被存入深度缓冲区
当有多个透明物体重叠显示时 后渲染的物体 如果无法通过深度测试 片元会被舍弃无法进行颜色混合(即透明物体后面的透明物体不会被显示出来)
若关闭深度写入,那么物体的渲染顺序就会变得非常的重要
谁先被渲染,谁后被渲染都会影响最终的呈现效果
事例1:
两个物体 A为半透明物体 B为不透明物体

先渲染B,再渲染A,可以得到正确的半透明效果
先渲染A,再渲染B,只会看到B的颜色(因为A没有写入深度缓冲 B通过深度测试会被显示在A前)
通常不透明的物体 渲染顺序都在透明物体的前面
事例2:
两个物体 A为半透明物体 B为透明物体

先渲染B,再渲染A,可以得到正确的半透明效果
先渲染A,再渲染B,只会看到B的颜色(因为A没有写入深度缓冲 B因为后渲染 通过深度测试会被显示在A前)
可以认为 关闭深度写入后 在同时渲染多个透明物体时 谁显示在前 取决于谁最后被渲染
遮罩纹理通常用于控制或限制某些效果的显示范围。 它允许我们可以保护某些区域,使它们免于某些修改。 一般情况下,遮罩纹理也会是一张灰度图,其中的RGB值会是相同的 我们利用它存储的值参与到
利用高光遮罩纹理 我们可以控制模型上的各个区域受到高光影响的强弱
基本原理
fixed specularMask = tex2D(_SpecularTex,i.uv.xy).r * _SpecularScale;//高光遮罩
float3 halfDir = normalize(i.viewDir + i.lightDir);
fixed3 specularColor = _LightColor0 * _SpecularColor * pow(max(0,dot(halfDir,unpackNormal)),_SpecularNum) * specularMask;综合实现
Shader "Unlit/SpecularMask"
{
Properties
{
_MainTex("MainTex",2D) = ""{} //主贴图
_NormalTex("NormalTex",2D) = ""{} //法线贴图
_BumpScale("BumpScale",range(0,1)) = 1 //凹凸度
_DiffuseColor("DiffuseColor",Color) = (1,1,1,1) //漫反射颜色
_RampTex("RampTex",2D) = ""{} //渐变贴图
_SpecularColor("SpecularColor", Color) = (1,1,1,1) //高光反射颜色
_SpecularNum("SpecularNum", Range(8, 256)) = 18 //光泽度
_SpecularTex("SpecularTex",2D) = ""{}//高光遮罩纹理
_SpecularScale("SpecularScale",range(0,1)) = 1 //高光遮罩系数
}
SubShader
{
Pass
{
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct v2f
{
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float4 wpos : TEXCOORD1;
float3 viewDir:TEXCOORD2;
float3 lightDir:TEXCOORD3;
};
//漫反射
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _DiffuseColor;
sampler2D _RampTex;
//法线
sampler2D _NormalTex;
float4 _NormalTex_ST;
float _BumpScale;
//高光反射
fixed4 _SpecularColor;
float _SpecularNum;
sampler2D _SpecularTex;
float _SpecularScale;
v2f vert (appdata_full v)
{
v2f data;
//坐标转换
data.pos = UnityObjectToClipPos(v.vertex);
data.wpos = mul(unity_ObjectToWorld,v.vertex);
//uv贴图
data.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
data.uv.zw = v.texcoord.xy * _NormalTex_ST.xy + _NormalTex_ST.zw;
//计算 切线空间 变换矩阵
float3 bitangent = cross(v.tangent.xyz,v.normal) * v.tangent.w;
float3x3 rotation = float3x3(v.tangent.xyz,bitangent,v.normal);
//切线空间下的 视角和灯光向量
data.viewDir = mul(rotation,ObjSpaceViewDir(v.vertex));
data.lightDir = mul(rotation,ObjSpaceLightDir(v.vertex));
return data;
}
fixed4 frag (v2f i) : SV_Target
{
//法线
float4 packedNoraml = tex2D(_NormalTex,i.uv.zw);
float3 unpackNormal = UnpackNormal(packedNoraml);
//凹凸度
unpackNormal.xy *= _BumpScale;
unpackNormal.z = sqrt(1 -saturate(dot(unpackNormal.xy,unpackNormal.xy)));
//漫反射
fixed3 uvColor = tex2D(_MainTex,i.uv.xy) * _DiffuseColor.rgb;
fixed halfLarbertNum = dot(i.lightDir,unpackNormal) * 0.5 + 0.5;//渐变贴图
fixed3 diffuseColor = _LightColor0 * uvColor * tex2D(_RampTex,fixed2(halfLarbertNum,halfLarbertNum));
//高光
fixed specularMask = tex2D(_SpecularTex,i.uv.xy).r * _SpecularScale;//高光遮罩
float3 halfDir = normalize(i.viewDir + i.lightDir);
fixed3 specularColor = _LightColor0 * _SpecularColor * pow(max(0,dot(halfDir,unpackNormal)),_SpecularNum) * specularMask;
//blinn-phong
fixed3 blinnPhongColor = UNITY_LIGHTMODEL_AMBIENT * uvColor + diffuseColor + specularColor;
return fixed4(blinnPhongColor,1);
}
ENDCG
}
}
}渐变纹理的主要作用是可以让模型呈现出插画、卡通风格的渲染效果
利用半兰伯特光照公式后半部分计算出来的0~1之间的值,构建一个UV相同的 坐标,然后从渐变纹理中取出对应的颜色进行叠加
决定漫反射明暗的不再是由 0~1这个值决定而是由渐变纹理中取出的颜色进行叠加达到最终效果
fixed halfLarbertNum = 0.5 + dot(wNormal,lightDir) * 0.5;
fixed3 rampColor = _LightColor0.rgb * _MainColor.rgb * tex2D(_RampTex,fixed2(halfLarbertNum,halfLarbertNum));避免渐变纹理接缝处有黑点
我们需要将 Wrap Mode(循环模式)切换为 Clamp(拉伸模式)
出现黑点的原因是:
Shader "Unlit/Gradient"
{
Properties
{
_MainColor("MainColor",Color) = (1,1,1,1)
_RampTex("RampTex",2D) = ""{}
_SpecularColor("SpecularColor", Color) = (1,1,1,1)
_SpecularNum("SpecularNum", Range(8, 256)) = 18
}
SubShader
{
Pass
{
Tags { "LightMode" ="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct v2f
{
float4 vertex : SV_POSITION;
float3 normal:NORMAL;
float4 wpos : TEXCOORD1;
};
sampler2D _RampTex;
float4 _RampTex_ST;
fixed4 _MainColor;
fixed4 _SpecularColor;
float _SpecularNum;
v2f vert (appdata_base v)
{
v2f data;
data.vertex = UnityObjectToClipPos(v.vertex);
data.normal = UnityObjectToWorldNormal(v.normal);
data.wpos = mul(unity_ObjectToWorld,v.vertex);
return data;
}
fixed4 frag (v2f i) : SV_Target
{
float3 wNormal = normalize(i.normal);
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed halfLarbertNum = 0.5 + dot(wNormal,lightDir) * 0.5;
fixed3 rampColor = _LightColor0.rgb * _MainColor.rgb * tex2D(_RampTex,fixed2(halfLarbertNum,halfLarbertNum));
//Phong
float3 viewDir = normalize(_WorldSpaceCameraPos - i.wpos);
float3 halfDir = normalize(lightDir + viewDir);
fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0,dot(wNormal,halfDir)),_SpecularNum);
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb + rampColor + specularColor;
return fixed4(color,1);
}
ENDCG
}
}
}Shader "Unlit/Gradient2"
{
Properties
{
_MainTex("MainTex",2D) = ""{} //主贴图
_NormalTex("NormalTex",2D) = ""{} //法线贴图
_BumpScale("BumpScale",range(0,1)) = 1 //凹凸度
_DiffuseColor("DiffuseColor",Color) = (1,1,1,1) //漫反射颜色
_RampTex("RampTex",2D) = ""{} //渐变贴图
_SpecularColor("SpecularColor", Color) = (1,1,1,1) //高光反射颜色
_SpecularNum("SpecularNum", Range(8, 256)) = 18 //光泽度
}
SubShader
{
Pass
{
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct v2f
{
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float4 wpos : TEXCOORD1;
float3 viewDir:TEXCOORD2;
float3 lightDir:TEXCOORD3;
};
//漫反射
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _DiffuseColor;
sampler2D _RampTex;
//法线
sampler2D _NormalTex;
float4 _NormalTex_ST;
float _BumpScale;
//高光反射
fixed4 _SpecularColor;
float _SpecularNum;
v2f vert (appdata_full v)
{
v2f data;
//坐标转换
data.pos = UnityObjectToClipPos(v.vertex);
data.wpos = mul(unity_ObjectToWorld,v.vertex);
//uv贴图
data.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
data.uv.zw = v.texcoord.xy * _NormalTex_ST.xy + _NormalTex_ST.zw;
//计算 切线空间 变换矩阵
float3 bitangent = cross(v.tangent.xyz,v.normal) * v.tangent.w;
float3x3 rotation = float3x3(v.tangent.xyz,bitangent,v.normal);
//切线空间下的 视角和灯光向量
data.viewDir = mul(rotation,ObjSpaceViewDir(v.vertex));
data.lightDir = mul(rotation,ObjSpaceLightDir(v.vertex));
return data;
}
fixed4 frag (v2f i) : SV_Target
{
//法线
float4 packedNoraml = tex2D(_NormalTex,i.uv.zw);
float3 unpackNormal = UnpackNormal(packedNoraml);
//凹凸度
unpackNormal.xy *= _BumpScale;
unpackNormal.z = sqrt(1 -saturate(dot(unpackNormal.xy,unpackNormal.xy)));
//漫反射
fixed3 uvColor = tex2D(_MainTex,i.uv.xy) * _DiffuseColor.rgb;
fixed halfLarbertNum = dot(i.lightDir,unpackNormal) * 0.5 + 0.5;//渐变贴图
fixed3 diffuseColor = _LightColor0 * uvColor * tex2D(_RampTex,fixed2(halfLarbertNum,halfLarbertNum));
//高光
float3 halfDir = normalize(i.viewDir + i.lightDir);
fixed3 specularColor = _LightColor0 * _SpecularColor * pow(max(0,dot(halfDir,unpackNormal)),_SpecularNum);
//blinn-phong
fixed3 blinnPhongColor = UNITY_LIGHTMODEL_AMBIENT * uvColor + diffuseColor + specularColor;
return fixed4(blinnPhongColor,1);
}
ENDCG
}
}
}各自的优缺点-性能
在切线空间中计算,效率更高,因为可以在顶点着色器中就完成对光照、视角方向的矩 阵变换,计算量相对较小。( 矩阵变换在顶点着色器中计算)
在世界空间中计算,效率较低,由于需要对法线贴图进行采样,所以变换过程必须在片 元着色器中实现,我们需要在片元着色器中对法线进行矩阵变换。( 矩阵变换在片元着色器中计算)
各自的优缺点-效果
在切线空间中计算,对全局效果的表现可能会不够准确(在处理一些列如镜面反射、环境映射效果时表现效果可能不够准确)
在世界空间中计算,对全局效果的表现更准确(可以更容易的应用于全局效果的计算)
选择
若没有全局效果要求,我们优先使用在切线空间下进行光照计算,因为它效率较高
反之,我们选择在世界空间下计算
在切线空间下进行光照计算,需要把光照方向、视角方向变换到切线空间下参与计算
关键点:
计算模型空间到切线空间的变换矩阵
计算方式:
由于变换矩阵进行变换为矢量而非点的变换,因此可以变为3x3矩阵。
变换矩阵为子到父的逆矩阵
$$ \begin{bmatrix} |& |& |\\ Xs& Ys& Zs\\ |& |&| \end{bmatrix} $$x、y、z轴分别为切线空间中顶点的切线、副切线、法线
切线和法线 可以从模型空间中获取
副切线为 切线和法线叉乘的结果
而3个轴为相互垂直的单位向量,因此可以推出 该矩阵为 正交矩阵
$$ \begin{bmatrix} -& Xs& -\\ -& Ys& -\\ -& Zs&- \end{bmatrix} $$他就是 模型空间到切线空间的变换矩阵
//得到模型空间光的方向
ObjSpaceLightDir(模型空间顶点坐标);
//得到模型空间视角方向
ObjSpaceViewDir(模型空间顶点坐标);Shader "Unlit/NormalMapLearn"
{
Properties
{
//主贴图
_MainTex("MainTex",2D) = ""{}
//法线
_BumpMap("BumpMap",2D) = ""{}
_BumpScale("BumpScale",Range(0,1)) = 1
//漫反射
_MainColor("MainColor",Color) = (1,1,1,1)
//高光反射
_SpecularColor("SpecularColor",Color) = (1,1,1,1)
_SpecNum("SpecNum",Range(0.1,20)) = 10
}
SubShader
{
Pass
{
Tags { "LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct v2f
{
float4 pos:SV_POSITION;
float4 uv:TEXCOORD0;
float3 lightDir:TEXCOORD1;//切线空间光照向量
float3 viewDir:TEXCOORD2;//切线空间视口向量
};
sampler2D _MainTex;//uv贴图
float4 _MainTex_ST;
sampler2D _BumpMap;//法线贴图
float4 _BumpMap_ST;
float _BumpScale;//凹凸度
fixed4 _MainColor;//漫反射颜色
fixed4 _SpecularColor;//高光反射颜色
float _SpecNum;//光泽度
v2f vert (appdata_full v)
{
v2f data;
//将模型坐标系下的顶点坐标 转换到裁剪空间下
data.pos = UnityObjectToClipPos(v.vertex);
//设置UV坐标(xy存贴图,zw存法线)
data.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
data.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
//计算模型空间到切线空间的变换矩阵
float3 binormal = cross(normalize(v.tangent),normalize(v.normal)) * v.tangent.w;//计算副切线向量
float3x3 rotation = {v.tangent.xyz , binormal, v.normal};
//计算切线空间下灯光和视角向量
data.lightDir = mul(rotation,ObjSpaceLightDir(v.vertex));
data.viewDir = mul(rotation,ObjSpaceLightDir(v.vertex));
return data;
}
fixed4 frag (v2f i) : SV_Target
{
//法线贴图取样
float4 packedNormal = tex2D(_BumpMap,i.uv.zw);
float3 tangentNormal = UnpackNormal(packedNormal);//切线空间下的法线信息
//凹凸度计算
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy,tangentNormal.xy)));
//计算光照(切线空间下)
//Lambert漫反射
fixed3 uvColor = tex2D(_MainTex,i.uv.xy) * _MainColor;//uv取样 乘漫反射颜色
fixed3 lambertColor = _LightColor0.rgb * uvColor * max(0,dot(tangentNormal,normalize(i.lightDir)));
//Blinn-Phong高光
float3 halfA = normalize(i.lightDir + i.viewDir);
fixed3 specularColor =_LightColor0.rgb * _SpecularColor.rgb * pow(max(0,dot(tangentNormal,halfA)),_SpecNum);
//Blinn-Phong光照模型
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * uvColor + lambertColor + specularColor;
return fixed4(color,1);
}
ENDCG
}
}
}在世界空间下进行光照计算,需要把法线方向变换到世界空间下参与计算
纹理除了可以用来进行颜色映射外,另一种常见的应用就是进行凹凸映射
凹凸映射的目的是使用一张纹理来修改模型表面的法线,让我们不需要增加顶点,就可以让模型看起来有凹凸效果。
原理:
光照的计算会利用法线参与计算。使用凹凸纹理的法线参与计算,可以呈现出更丰富的视觉效果。
作用:
凹凸纹理最大的作用就是让模型可以在不添加顶点(不增加面)的情况下通过凹凸映射将凹凸纹理中的法线信息映射到模型中,让模型看起来拥有更多的细节,是一种视觉上的“欺骗”技术。
高度纹理贴图 简称高度图
他存储了模型表面上每个点的高度信息,通常使用灰度图像(RGB值相同)来表示。
其中不同的灰度值表示不同的高度(较亮区域通常对应较高的点,较暗的区域对应较低的点)。
它主要用于模拟物体表面的位移。
存储规则:
图片中每一个像素点的RGB值都是相同的,表示高度值。A值通常为1。
高度值范围一般为0到1,0代表最低、1代表最高。
优点:
可以通过高度图很明确的知道模型表面的凹凸情况
缺点:
无法在Shader中直接得到模型表面点的法线信息(需要通过计算得到,会额外增加性能开销)。
所以在使用凹凸纹理时,通常会使用法线纹理贴图。
法线纹理贴图一般简称法线贴图或法线纹理
它存储了模型表面上每个点的法线方向。
存储规则:
图片中的RGB值分别存储法线的X、Y、Z分量值,取值在[0,1]之间
A值可以存储其他信息 如材质光滑度等。
优点:
从法线贴图中取出的数据便是法线信息,可以直接简单处理后就参与光照计算,性能表现更好
缺点:
无法直观的看出模型表面的凹凸情况
由于法线XYZ分量范围在[-1,1]之间
而像素RGB分量范围在[0,1]之间
因此我们需要做一个映射计算
存储图片时:
像素分量 = (法线分量 + 1) / 2
因此当我们取出像素分量使用时需要进行逆运算
读取数据时:
法线分量 = 像素分量 * 2 - 1
法线纹理贴图中主要存储法线信息,
而法线信息其实就是个方向向量,而方向向量就得有相对坐标系
因此,法线贴图的存储方式按相对坐标系有两种方式:
模型数据中自带的法线数据,就是定义在模型空间中的,因此最直接的存储法线贴图数据的方式 就是存储基于模型空间下的法线信息。
由于模型空间中每个点存储的法线方向是各式各样的(因为原点是模型的原点)
比如:
法线(0,1,0)映射到像素后是 (0.5,1.0.5) 绿色
法线(0,-1,0)映射到像素后是 (0.5,0,0.5) 紫色
因此基于模型空间的法线纹理一般是五颜六色的