linux修改默认网卡名为eth0

第一步:修改内核引导参数

我们需要告诉内核禁用“一致性网络设备命名”规则。

# 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 添加到引号内

第二步:更新 GRUB 并使配置生效

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

第四步:检查并重启

在重启之前,请务必确认文件修改正确:

优化VPS网络

计算TCP最大窗口大小

依据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

在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 = 0

最终参数

net.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 = 0

告别繁琐安装Winget教程

一、 什么是 Winget?

winget 是微软推出的开源软件包管理器。它允许用户通过简单的命令行操作,实现软件的搜索、安装、升级、卸载和配置

为什么要用它?

  • 官方背书: 安全、可靠,无需担心广告插件。
  • 高效装机: 一行代码安装几十款常用软件。
  • 自动升级: 告别手动检查更新的烦恼。
  • 原生集成: 现代 Windows 10/11 系统已内置。

二、 基础操作:从安装到卸载

打开你的 PowerShell命令提示符 (CMD),开启你的命令行装机之旅。

1. 查找软件

不知道软件的确切名称?先搜一下:

winget search <软件名>

提示: 如果结果较多,可以使用 winget search "Google Chrome" 这样带引号的精确匹配。

2. 安装软件

找到软件后,使用 install 命令:

# 以安装 VS Code 为例
winget install "Visual Studio Code"

如果你想静默安装(不弹出安装界面),可以加上参数:

WSL2安装配置

1. 基础安装与核心命令

在 Windows 中,WSL 的安装已经变得极其简单。只需在管理员权限的 PowerShell 中执行:

常用管理命令

功能命令
一键安装wsl --install
内核更新wsl --update
查看已安装系统wsl --list -v
设置默认系统wsl --set-default <DistroName>
彻底卸载系统wsl --unregister <DistroName>

2. 开启「镜像网络模式」 (Mirrored Mode)

这是 Windows 11 引入的重磅功能,解决了 WSL 无法直接访问宿主机 VPN、IP 变动等痛点。

配置步骤

  1. 打开用户主目录:按 Win + R 输入 %UserProfile%
  2. 创建文件:.wslconfig (确保无 .txt 后缀)。
  3. 填入以下高性能配置:
[wsl2]
# 开启镜像网络模式,WSL 将共享 Windows 的 IP
networkingMode=mirrored
# 开启 DNS 隧道,解决 VPN 导致的无法上网问题
dnsTunneling=true
# 开启防火墙,让 Windows 安全策略同步生效
firewall=true
# 自动同步 Windows 代理设置
autoProxy=true

注意:配置保存后,必须执行 wsl --shutdown 重启才能生效。

Linux定时任务

在 Linux 系统中,设置定时任务最常用的工具是 crontab。你可以把它想象成一个自动化的“闹钟”,让系统在指定的时间执行特定的脚本或命令。

以下是关于 Linux 定时任务(Cron Job)的快速指南:


1. 核心命令

在终端中输入以下命令来管理你的任务列表:

  • crontab -e: 编辑当前用户的定时任务(首次使用会让你选择编辑器,建议选 nanovim)。
  • crontab -l: 查看当前所有的定时任务。
  • crontab -r: 删除当前用户的所有定时任务。

2. 任务格式

当你进入编辑模式后,每一行代表一个任务,格式如下:

Bash

# ┌───────────── 分钟 (0 - 59)
# │ ┌─────────── 小时 (0 - 23)
# │ │ ┌───────── 月份中的第几天 (1 - 31)
# │ │ │ ┌─────── 月份 (1 - 12)
# │ │ │ │ ┌───── 星期中的第几天 (0 - 6) (0是周日)
# │ │ │ │ │
# * * * * * 要执行的命令或脚本路径

3. 常用实例

这里有一些可以直接拿来改写的例子:

Typecho迁移Hugo实践

前言

最近决定将博客从 Typecho 迁移到静态网站生成器 Hugo。相比动态博客,Hugo 带来的加载速度、安全性以及版本控制的便利性非常显著。本文记录了从数据库导出、自动化构建到 Algolia 搜索集成的完整过程。

一、数据迁移:从 SQLite 导出文章

1. 准备工作

  • 拷贝 Typecho 的 .db数据库文件到本地。
  • 拷贝 usr 文件夹到本地。

2. 导出脚本

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()

二、环境搭建与主题配置

1. 安装 Hugo

winget install Hugo.Hugo --version 0.145.0

2.初始化与导入

  1. 运行hugo new site my-blog创建站点
  2. 将导出的文章放入站点目录下的/content/posts目录,usr文件夹放入/static目录。

3.配置hugo.toml

找个喜欢的主题放入themes文件夹,根据文档按照自己的需求进行配置。

acme申请证书

安装

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的方式申请泛域名证书

debian设置自动更新安全更新

安装必要软件包

apt install unattended-upgrades

启用自动更新功能

dpkg-reconfigure -plow unattended-upgrades

这步操作会在 /etc/apt/apt.conf.d/20auto-upgrades 自动生成配置,确保更新服务会在后台运行。

配置更新策略

vi /etc/apt/apt.conf.d/50unattended-upgrades
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";

linux清理并保留最近两天的日志

查询 systemd 日志占用空间

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

重启服务

debian10/11替换归档源

允许过期仓库

echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99ignore-release-date

修改仓库源

debian10

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-free

debian11

群晖docker配置代理

在 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为不透明物体

/usr/uploads/2025/08/1722616652.png

先渲染B,再渲染A,可以得到正确的半透明效果

先渲染A,再渲染B,只会看到B的颜色(因为A没有写入深度缓冲 B通过深度测试会被显示在A前)

通常不透明的物体 渲染顺序都在透明物体的前面

事例2:

两个物体 A为半透明物体 B为透明物体

/usr/uploads/2025/08/2682981069.png

先渲染B,再渲染A,可以得到正确的半透明效果

先渲染A,再渲染B,只会看到B的颜色(因为A没有写入深度缓冲 B因为后渲染 通过深度测试会被显示在A前)

可以认为 关闭深度写入后 在同时渲染多个透明物体时 谁显示在前 取决于谁最后被渲染

遮罩纹理

遮罩纹理是用来做什么的

遮罩纹理通常用于控制或限制某些效果的显示范围。 它允许我们可以保护某些区域,使它们免于某些修改。 一般情况下,遮罩纹理也会是一张灰度图,其中的RGB值会是相同的 我们利用它存储的值参与到

  • 光照(指定某些区域受光影响的程度)
  • 透明度(指定某些区域透明的程度)
  • 特效(指定某些区域出现特效) 等等相关的计算中 从而来让指定区域达到我们想要的效果

高光纹理遮罩

利用高光遮罩纹理 我们可以控制模型上的各个区域受到高光影响的强弱

基本原理

  1. 从纹理中取出对应的遮罩掩码值(颜色的RGB值都可以使用)
  2. 用该掩码值和遮罩系数(我们自己定义的)相乘得到遮罩值
  3. 用该遮罩值和高光反射计算出来的颜色相乘
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(拉伸模式)

出现黑点的原因是:

  • 浮点数计算可能存在误差,会出现超过1的值(1.00001)
  • 如果使用Repeat(重复模式),会舍弃整数部分,保留小数0.00001
  • 这时对应的颜色会是最左边的值,因此会出现黑色

渐变纹理的基本实现

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
        }
    }
}

计算法线贴图

法线贴图的计算方法

  1. 在切线空间下进行光照计算,需要把光照方向、视角方向变换到切线空间下参与计算
  2. 在世界空间下进行光照计算,需要把法线方向变换到世界空间下参与计算

各自的优缺点-性能

在切线空间中计算,效率更高,因为可以在顶点着色器中就完成对光照、视角方向的矩 阵变换,计算量相对较小。( 矩阵变换在顶点着色器中计算)

在世界空间中计算,效率较低,由于需要对法线贴图进行采样,所以变换过程必须在片 元着色器中实现,我们需要在片元着色器中对法线进行矩阵变换。( 矩阵变换在片元着色器中计算)

各自的优缺点-效果

在切线空间中计算,对全局效果的表现可能会不够准确(在处理一些列如镜面反射、环境映射效果时表现效果可能不够准确)

在世界空间中计算,对全局效果的表现更准确(可以更容易的应用于全局效果的计算)

选择

若没有全局效果要求,我们优先使用在切线空间下进行光照计算,因为它效率较高

反之,我们选择在世界空间下计算


在切线空间下计算

在切线空间下进行光照计算,需要把光照方向、视角方向变换到切线空间下参与计算

关键点:

计算模型空间到切线空间的变换矩阵

计算方式:

由于变换矩阵进行变换为矢量而非点的变换,因此可以变为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


两种法线纹理贴图的存储方式

法线纹理贴图中主要存储法线信息,

而法线信息其实就是个方向向量,而方向向量就得有相对坐标系

因此,法线贴图的存储方式按相对坐标系有两种方式:

  1. 基于模型空间的法线纹理
  2. 基于切线空间的法线纹理

基于模型空间的法线纹理

模型数据中自带的法线数据,就是定义在模型空间中的,因此最直接的存储法线贴图数据的方式 就是存储基于模型空间下的法线信息。

由于模型空间中每个点存储的法线方向是各式各样的(因为原点是模型的原点)

比如:

法线(0,1,0)映射到像素后是 (0.5,1.0.5) 绿色

法线(0,-1,0)映射到像素后是 (0.5,0,0.5) 紫色

因此基于模型空间的法线纹理一般是五颜六色的

纹理贴图叠加BlinnPhong光照模型

单张纹理结合BlinnPhong光照模型

在计算时,有以下的3点注意点

  1. 纹理颜色需要和漫反射颜色 进行乘法叠加 作为新的漫反射颜色
  2. 计算兰伯特光照时 要使用 1的结果 作为漫反射颜色
  3. 最终和环境光叠加时,为了避免渲染效果发灰 环境光变量和1中结果(新的漫反射颜色)相乘

片元着色器实现

Shader "Unlit/uvlearn2"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _MainColor("MainColor",COLOR) = (1,1,1,1)
        _HighLightColor("HighLight",COLOR) = (1,1,1,1)
        _HighLightNum("HighNum",Range(0.1,20)) = 5
    }
    SubShader
    {

        Pass
        {
            Tags{ "LightMode"="ForwardBase"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _MainColor;
            fixed4 _HighLightColor;
            float _HighLightNum;

            struct v2f
            {
                float4 pos:SV_POSITION;
                float3 normal:NORMAL;
                float4 wpos:TEXCOORD1;
                half2 uv: TEXCOORD0;
            };

            v2f vert (appdata_base v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.normal = UnityObjectToWorldNormal(v.normal);
                o.wpos = mul(unity_ObjectToWorld,v.vertex);
                o.uv = TRANSFORM_TEX(v.texcoord.xy,_MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                //uv取色和漫反射颜色相乘
                fixed3 uvColor = tex2D(_MainTex,i.uv).rgb * _MainColor.rgb;
                //归一化法向量
                float3 normal = normalize(i.normal);
                //Lambert
                float3 cameraDir = normalize(_WorldSpaceCameraPos.xyz);
                fixed3 lamBertColor = _LightColor0.rgb * uvColor.rgb * max(0,dot(cameraDir,normal));//叠加uvcolor
                //BlinnPhong
                float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
                float3 viewDir = normalize(UnityWorldSpaceViewDir(i.wpos).xyz);
                float3 halfA = normalize(lightDir + viewDir);
                fixed3 BlinnPhongColor = _LightColor0.rgb * _HighLightColor.rgb * pow(max(0,dot(halfA,normal)),_HighLightNum);
                //颜色叠加
                fixed3 color = UNITY_LIGHTMODEL_AMBIENT * uvColor + lamBertColor + BlinnPhongColor;
                return fixed4(color,1);
            }
            ENDCG
        }
    }
}

黑白效果Shader实现

我们可以通过将颜色值点乘float3(0.299, 0.587, 0.114)将颜色转为黑白

float3(0.299, 0.587, 0.114)这三个系数正好是 人眼感知亮度 的权重(ITU-R BT.601 标准),用来把 RGB 颜色转换为亮度值(luminance / 灰度值)。

而这三个数字,是科学家用光谱数据和视觉实验算出来的,保证在 看起来的亮度 上最符合人眼感知。

Shader实现

Shader "Unlit/gray"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Grayscale("Grayscale", Range(0, 1)) = 0
    }
    SubShader
    {

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Grayscale;
            v2f_img vert (appdata_base v)
            {
                v2f_img o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.texcoord.xy,_MainTex);
                return o;
            }

            fixed4 frag (v2f_img i) : SV_Target
            {
                fixed4 color = tex2D(_MainTex,i.uv);
                fixed4 grayColor = dot(color,float3(0.299, 0.587, 0.114));
                color = lerp(color,grayColor,_Grayscale);       
                return color;
            }
            ENDCG
        }
    }
}

Blinn-Phong光照模型

Blinn Phong光照模型的公式

物体表面光照颜色 = 环境光颜色 + 漫反射光颜色 + 高光反射光颜色

其中:

环境光颜色 = UNITY_LIGHTMODEL_AMBIENT(unity_AmbientSky、unity_AmbientEquator、unity_AmbientGround)

漫反射光颜色 = 兰伯特光照模型 计算得到的颜色

高光反射光颜色 = Blinn Phong式高光反射光照模型 计算得到的颜色


顶点着色器实现

Shader "Unlit/Blinn_Phong"
{
    Properties
    {
        _MainColor("MainColor",COLOR) = (1,1,1,1)
        _HighLightColor("HighLightColor",COLOR) = (1,1,1,1)
        _HighLightNum("HighLightNum",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;
                float3 normal : NORMAL;
                fixed3 color : COLOR;
            };


            fixed4 _MainColor;
            fixed4 _HighLightColor;
            float _HighLightNum;

            fixed3 getLembertColor(in float3 wnormal)
            {
                float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
                fixed3 color = _LightColor0.rgb * _MainColor.rgb * max(0,dot(wnormal,lightDir));
                return color;
            } 

            fixed3 getBlinnPhongColor(in float3 wpos, in float3 wnormal)
            {
                float3 viewDic = normalize(_WorldSpaceCameraPos.xyz - wpos);
                float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
                float3 halfDir = normalize(viewDic + lightDir);
                fixed3 color = _LightColor0.rgb * _HighLightColor.rgb * pow(max(0,dot(wnormal,halfDir)),_HighLightNum);
                return color;
            }

            v2f vert (appdata_base v)
            {
                v2f data;
                data.pos = UnityObjectToClipPos(v.vertex);
                data.normal = UnityObjectToWorldNormal(v.normal);
                //计算兰伯特光照
                float3 wnormal = normalize(data.normal);
                fixed3 lembertColor = getLembertColor(wnormal);
                //计算BlinnPhong高光模型
                float3 wpos = mul(unity_ObjectToWorld,v.vertex);
                fixed3 blinnPhongColor = getBlinnPhongColor(wpos,wnormal);
                //BlinnPhong光照模型
                data.color = UNITY_LIGHTMODEL_AMBIENT.rbg + lembertColor + blinnPhongColor;
                return data;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                return fixed4(i.color,1);
            }
            ENDCG
        }
    }
}

片元着色器实现

Shader "Unlit/Blinn_PhongF"
{
    Properties
    {
        _MainColor("MainColor",COLOR) = (1,1,1,1)
        _HighLightColor("HighLightColor",COLOR) = (1,1,1,1)
        _HighLightNum("HighLightNum",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;
                float3 normal : NORMAL;
                fixed3 color : COLOR;
                float4 wpos : TEXCOORD;
            };

            fixed4 _MainColor;
            fixed4 _HighLightColor;
            float _HighLightNum;

            fixed3 getLembertColor(in float3 wnormal)
            {
                float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
                fixed3 color = _LightColor0.rgb * _MainColor.rgb * max(0,dot(wnormal,lightDir));
                return color;
            } 

            fixed3 getBlinnPhongColor(in float3 wpos, in float3 wnormal)
            {
                float3 viewDic = normalize(_WorldSpaceCameraPos.xyz - wpos);
                float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
                float3 halfDir = normalize(viewDic + lightDir);
                fixed3 color = _LightColor0.rgb * _HighLightColor.rgb * pow(max(0,dot(wnormal,halfDir)),_HighLightNum);
                return color;
            }

            v2f vert (appdata_base v)
            {
                v2f data;
                data.pos = 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);
                fixed3 lambertColor = getLembertColor(wnormal);
                fixed3 blinnPhongColor = getBlinnPhongColor(i.wpos,wnormal);
                fixed3 color = UNITY_LIGHTMODEL_AMBIENT + lambertColor + blinnPhongColor;
                return fixed4(color,1);
            }
            ENDCG
        }
    }
}