This commit is contained in:
2023-06-29 11:55:02 +08:00
commit 36e95249b1
1236 changed files with 464197 additions and 0 deletions

1
07-Other/.keep Normal file
View File

@@ -0,0 +1 @@
this file is created for keeping the folder after git.

View File

@@ -0,0 +1,2 @@
AI生成Logo
https://www.bilibili.com/video/BV1AG411E7Am/?vd_source=d47c0bb42f9c72fd7d74562185cee290

59
07-Other/AI/AI相关.md Normal file
View File

@@ -0,0 +1,59 @@
## B站资源
- stable diffusion webui完全整合版https://www.bilibili.com/video/BV1JG411n7Xe/?spm_id_from=333.788&vd_source=d47c0bb42f9c72fd7d74562185cee290
- AI绘画 【Stable Diffusion】 NovelAI 整合包 解压即用https://www.bilibili.com/video/BV1aN4y1A7j1/?is_story_h5=false&p=1&share_from=ugc&share_medium=android&share_plat=android&share_session_id=1479cd5b-cc57-4d3c-b844-9da42e8bd8c9&share_source=QQ&share_tag=s_i&timestamp=1665242584&unique_k=4ERXawz&vd_source=1ce8b0bdc0c03d8640a4cb25321ec24e
- novelai本地泄露版达到网络版效果的教程https://www.bilibili.com/read/cv18971531?spm_id_from=333.788.b_636f6d6d656e74.17
## 关键词生成工具
https://www.bilibili.com/video/BV1L84y1z7bH/?spm_id_from=333.1007.tianma.1-2-2.click&vd_source=d47c0bb42f9c72fd7d74562185cee290
## AI关键词
正面关键词(测试):extremely detailed CG unity 8k wallpaper,black long hair,cute face,1 adlut girl,happy, green skirt dress, flower pattern in dress,solo,green gown,art of light novel,in field
负面排除关键词(必填):lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry
作者:超级过气歌手 https://www.bilibili.com/read/cv18971531?spm_id_from=333.788.b_636f6d6d656e74.17 出处bilibili
关键词前面一定要加上: masterpiece, best quality, 负关键词加: nsfw, lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry 默认高度设置到768
https://www.bilibili.com/video/BV1X8411W7YV/?spm_id_from=333.1007.tianma.3-3-9.click&vd_source=d47c0bb42f9c72fd7d74562185cee290
### 具体作品关键词
![300](https://picx.zhimg.com/80/v2-ea9e9edb8334be001290c44a2520bd52_720w.webp?source=1940ef5c)
character_concept_design_portrait_a_handsome_young_
![300](https://pic1.zhimg.com/80/v2-c4fba4813ccec87d2662c56e357a3c37_720w.webp?source=1940ef5c)
https://pic1.zhimg.com/80/v2-c4fba4813ccec87d2662c56e357a3c37_720w.webp?source=1940ef5c
## 汉服少女
![image.png](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20230207102705.png)
https://www.bilibili.com/video/BV1Sd4y1W7my/?spm_id_from=autoNext&vd_source=6154e19c5a98167a5ad1efdbdbdc9427
夸克网盘 链接https://pan.quark.cn/s/b04474b2212f
百度网盘 链接https://pan.baidu.com/s/1gJUIjAr4i7fw8qM7dwFXAg?pwd=ah34
https://space.bilibili.com/331374428
https://www.bilibili.com/video/BV1KG4y1M7Kb/?spm_id_from=333.788.recommend_more_video.6&vd_source=d47c0bb42f9c72fd7d74562185cee290
https://caiyun.139.com/m/i?0l5CM4Am77r8E 提取码kGST
## AI模型
- https://huggingface.co/andite/pastel-mix
- ![](https://huggingface.co/andite/pastel-mix/resolve/main/example-images/grid-0035.png)
- https://civitai.com/
- https://civitai.com/models/4468/counterfeit-v25
- ![](https://imagecache.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/79a71ba8-5efb-4b47-9903-d3ab125f2800/width=400)
- https://civitai.com/models/1411/counterfeit
- ![](https://imagecache.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/fc21332d-80d4-4085-f07c-971ac6d15000/width=400)
- https://civitai.com/models/7178/counterfeitlora300
- ![](https://imagecache.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/19095cb4-cb51-4c0c-69c0-ab8a1eeb1100/width=400)
- https://civitai.com/models/6424
- ![](https://imagecache.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/7727c1c6-8577-475a-55fd-f502b01f5000/width=400)
## AI诱导生成
Controlnet插件诱导生成
![QQ图片20230215150840.jpg](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/QQ%E5%9B%BE%E7%89%8720230215150840.jpg)
![IMG_9365(20230216-081116).JPG](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/IMG_9365(20230216-081116).JPG)
![IMG_9365(20230216-081116).JPG](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/IMG_9366(20230216-082338).JPG)
![IMG_9365(20230216-081116).JPG](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/IMG_9362(20230216-081001).JPG)
![IMG_9365(20230216-081116).JPG](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/IMG_9363.PNG)
![IMG_9365(20230216-081116).JPG](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/IMG_9364(20230216-081059).JPG)
![QQ图片20230216142648.jpg](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/QQ%E5%9B%BE%E7%89%8720230216142648.jpg)

View File

@@ -0,0 +1,34 @@
## 安装homebrew
参考:
- https://www.jianshu.com/p/e0471aa6672d
国内镜像安装命令:
```c++
/bin/bash -c "$(curl -fsSL https://gitee.com/ineo6/homebrew-install/raw/master/install.sh)"
```
安装完之后需要替换源:
### 1.必备设置
- 替换 brew.git
```bash
git -C "$(brew --repo)" remote set-url origin https://mirrors.ustc.edu.cn/brew.git
```
- 替换 homebrew-core.git
```bash
git -C "$(brew --repo homebrew/core)" remote set-url origin https://mirrors.ustc.edu.cn/homebrew-core.git
```
### 2.按需设置
- 替换 homebrew-cask.git
```bash
git -C "$(brew --repo homebrew/cask)" remote set-url origin https://mirrors.ustc.edu.cn/homebrew-cask.git
```
- 替换homebrew-bottles
首先要先区分你的mac用哪种终端工具如果是 bash则执行
```bash
echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles' >> ~/.bash_profile
source ~/.bash_profile
```
若是 zsh则执行
```bash
echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles' >> ~/.zshrc
source ~/.zshrc

View File

@@ -0,0 +1,17 @@
## 安装homebrew
参考:
- https://www.jianshu.com/p/e0471aa6672d
国内镜像安装命令:
```c++
/bin/bash -c "$(curl -fsSL https://gitee.com/ineo6/homebrew-install/raw/master/install.sh)"
```
## 编译注意事项
一定要注意虚拟机的CPU以及内存的关系CPU线程数 * 2 > 内存GB 数。所以给的CPU数目一定不能多。
## 编译命令
```bash
cd ./Desktop/UnrealEngine/Engine/Build/BatchFiles
./RunUAT.sh BuildGraph -target="Make Installed Build Win64" -script=Engine/Build/InstalledEngineBuild.xml -set:WithDDC=true -set:SignExecutables=false -set:EmbedSrcSrvInfo=false -set:GameConfigurations=Development;Shipping -set:WithFullDebugInfo=false -set:HostPlatformEditorOnly=false -set:AnalyticsTypeOverride=false -set:HostPlatformDDCOnly=true -set:WithWin64=false -set:WithMac=true -set:WithAndroid=false -set:WithIOS=true -set:WithTVOS=false -set:WithLinux=false -set:WithLumin=false -set:WithLinuxArm64=false -set:CompileDatasmithPlugins=false -set:WithServer=false -set:WithClient=false -set:WithHoloLens=false
```

View File

@@ -0,0 +1,108 @@
# 概念
EFI:
>BIOS是个程序存储在BIOS芯片中而现在的新式电脑用的基本都是UEFI启动早期的过渡电脑用的都是EFI启动。EFI或UEFI的一部分也是存储在一个芯片中。
SSDT&DSDT
>ssdt里信息都是电源管理与显卡相关信息其他设备基本都在dsdt里。
# 相关资料
- [OpenCore-Install-Guid](https://dortania.github.io/OpenCore-Install-Guide/prerequisites.html)
- [OpenCore中文手册](https://oc.skk.moe/1-introduction.html)
- 完美黑苹果系列教程
- [【Windows&macOS】完美双系统系列教程第2集Windows环境下配置OC引导](https://www.bilibili.com/video/BV1Bi4y1S7DN/?spm_id_from=333.788&vd_source=d47c0bb42f9c72fd7d74562185cee290)
- [【Windows&macOS】完美双系统系列教程第3集安装macOS](https://www.bilibili.com/video/BV14a41147Kk/?spm_id_from=pageDriver&vd_source=d47c0bb42f9c72fd7d74562185cee290)
- [【Windows&macOS】完美双系统系列教程第4集登录Apple ID](https://www.bilibili.com/video/BV1gi4y1X7EY/?spm_id_from=333.788&vd_source=d47c0bb42f9c72fd7d74562185cee290)
- USB 定制
- [全新的定制USB教程](https://www.bilibili.com/video/BV1m3411b7JP/?spm_id_from=333.999.0.0&vd_source=d47c0bb42f9c72fd7d74562185cee290)
- [USB定制补充内容该选择哪种USB定制方式](https://www.bilibili.com/video/BV1yv4y1X7Jq/?spm_id_from=333.999.0.0&vd_source=d47c0bb42f9c72fd7d74562185cee290)
- 其他
- [macOS13升级准备OTA也能万无一失](https://www.bilibili.com/video/BV1Be411V7b7/?spm_id_from=333.999.0.0&vd_source=d47c0bb42f9c72fd7d74562185cee290)
- [如何选择有线网卡板载or外置看这个视频可能就够了](https://www.bilibili.com/video/BV1HN411P7Fx/?spm_id_from=333.999.0.0&vd_source=d47c0bb42f9c72fd7d74562185cee290)
- [12-13代intel黑苹果OC引导配置注意事项](https://www.bilibili.com/video/BV17e4y1A7os/?spm_id_from=333.999.0.0&vd_source=d47c0bb42f9c72fd7d74562185cee290)
- [蓝牙自动连接黑苹果和Windows双系统共用一套蓝牙键鼠](https://www.bilibili.com/video/BV1kv4y1w7qH/?spm_id_from=333.999.0.0&vd_source=d47c0bb42f9c72fd7d74562185cee290)
# 相关软件
- [SSDTTime](https://github.com/corpnewt/SSDTTime)用于生成ssdt文件。
- [USBToolBox](https://github.com/USBToolBox/tool)用于定制usb。
- [kext](https://github.com/USBToolBox/kext)
- [OCAT](https://github.com/ic005k/OCAuxiliaryTools)OC编辑器。
- DiskGenius查看EFI用。
- balenaEtcher-Setup写入镜像工具。
- [ProperTree](https://github.com/corpnewt/ProperTree):
- [Hackintool](https://github.com/benbaker76/Hackintool)
https://pan.baidu.com/s/1VFuLao3LDb_VwQzXxx6Eyg 提取码ile9
也可以参考https://forum.amd-osx.com/threads/asus-x670e-gene-efi-adaptable-to-other-x670-x670e-b650-and-b650e-boards.4160/post-27008
# 完美黑苹果(Win&Mac 双系统)安装流程
如果电脑已经存在EFI分区即你已经先安装了macOS此时安装Win10会破坏之前的EFI引导所以建议先安装Win10之后安装macOS。
1. Win [【Windows&macOS】完美双系统系列教程第2集Windows环境下配置OC引导](https://www.bilibili.com/video/BV1Bi4y1S7DN/?spm_id_from=333.788&vd_source=d47c0bb42f9c72fd7d74562185cee290)
1. 安装Win10系统。
2. Win系统中的所有的硬盘名称改成英文。可选
3. 定制USB。参考[全新的定制USB教程](https://www.bilibili.com/video/BV1m3411b7JP/?spm_id_from=333.999.0.0&vd_source=d47c0bb42f9c72fd7d74562185cee290)
4. 运行SSDTTime.bat生成所需的SSDT文件。
5. OCAT感觉可以直接使用论坛上分享的x670e EFI文件
1. 升级OCAT。
2. 点击数据库图标双击Sample.plist创建案例EFI文件夹。
3. 进行文件整理。
1. 删除EFI/OC/ACPI下的文件并使用这个视频提供的SSDT文件替换。
2. 删除EFI/OC/Drivers下的文件仅保留视频中的3个文件。
4. 配置OCAT的各个选项。
6. 使用balenaEtcher-Setup制作安装盘。
7. 使用DiskGenius删除安装盘中的EFI文件并且使用自己的配置的EFI替换。
2. BIOS [【Windows&macOS】完美双系统系列教程第3集安装macOS](https://www.bilibili.com/video/BV14a41147Kk/?spm_id_from=pageDriver&vd_source=d47c0bb42f9c72fd7d74562185cee290)
3. macOS该过程中不能登录AppleID以防被Ban
1. 安装macOS。
2. 安装OCC。
3. 使用OCC挂载 U盘与本地磁盘的EFI分区并且将内部的EFI文件复制到 本地EFI分区。
4. 拔掉安装U盘重启并且手动选择从安装macOS的硬盘启动。
5. 进行相关的macOS偏好设置。
6. 下载Hackintool完成macOS的USB定制。
7. 打开主板的页面查询自己主板的声卡型号之后到github.com/acidanthera/AppleALC查找声卡型号并且用OCC修改并且测试声卡参数。
4. 解决AppleID登录问题[【Windows&macOS】完美双系统系列教程第4集登录Apple ID](https://www.bilibili.com/video/BV1gi4y1X7EY/?spm_id_from=333.788&vd_source=d47c0bb42f9c72fd7d74562185cee290)
1. 使用OCC打开本地硬盘的EFI并且检查序列号有效性。如果显示返回无法查看就可以了。
2. 检查NVRAM是否正常运行。
1. sudo nvram myvar=test
2. nvram -p | grep -i myvar
# Asus X670E Gene EFI -- Adaptable to Other X670, X670E, B650 and B650E Boards
https://forum.amd-osx.com/threads/asus-x670e-gene-efi-adaptable-to-other-x670-x670e-b650-and-b650e-boards.4160/
为了使此EFI适应**其他板**,必须进行以下更改:
- [x] _通过暂时使用OpenCore.efi_的调试版本并设置_Misc -> Target -> 67_以启用 OpenCore 日志文件,找到您的主板的 MMIO 白名单。引导系统直到出现 OpenCore Picker。这应该足以在 EFI 分区的根文件夹中生成 OpenCore 日志文件。在该日志中搜索“MMIO”您将找到所需的条目请参阅[此 MMIO 白名单指南](https://am5hackintosh.github.io/OpenCore-Install-Guide/extras/devirtualizemmio.html)
- [ ] 创建您自己的USB端口映射该 EFI 文件夹使用 (a) SSDT 和 (b) kext 的组合来创建 USB 端口映射;如果您愿意的话,我还包含了 USBToolBox 的输出;**但您仍然必须为您的特定主板创建 USB 端口映射**;您可以尝试使用此 EFI 文件夹中的 USB 端口映射,但它可能适合您,也可能不适合您
- [ ] 要启用从睡眠状态一键唤醒,`SSDT-USBW.aml`必须使用系统中所有活动 USB (XHC) 控制器的 ACPI 路径进行修改
- [x] **应为您的**主板更新​​ DeviceProperties 部分;现有条目可以在安装 macOS 时删除并在以后创建;这些属性的目的是为 NVMe M.2 和 SATA SSD设置**内置属性,以便它们在桌面上显示为内部驱动器(请参见下面的屏幕截图)**
要使此 EFI 适应您的**CPU、**内存**DIMM、** GPU和**区域**,必须进行以下更改****
- [x] 更改**内核补丁**以反映处理器中物理 CPU 核心的数量;我使用的是 Ryzen 7 7700X它有 8 个核心,因此如果您使用不同的处理器,**请修改前 3 个内核补丁**(请参见下面的屏幕截图)
- [x] 更新**PlatformInfo -> Memory**部分,如下面的屏幕截图所述
- [ ] 还可以在**PlatformInfo -> DataHub**部分中复制或创建**序列号,如下面的屏幕截图所示**
- [x] 如果使用 2、4 或 6 核 CPU请将 ProcessorType 更改为 1537如图所示
- [x] 如果使用具有 8 个或更多核心的 CPU请将 ProcessorType 设置为 3841如图所示
- [x] **在NVRAM**部分中输入您的 CPU 名称如下所示以便它在_“关于本机”中正确显示_
- [ ] 如果使用 RX 550、560、570、580、Vega 56、Vega 64 和 Radeon VI请删除引导参数**agdpmod=pikera (请参见下面的屏幕截图)**
- [x] _在boot-args_下的 NVRAM 部分中,有一个名为**prev-lang:kbd 的**参数;当前设置为`en-US:0`****但您可以将其更改为您所在的区域
步骤记录:
1. 打开OCAT切换升级镜像目前默认的升级镜像站点挂了并升级到0.91版本作业是0.91版本)。
2. 按照网卡修改对应的plist我板载网卡是Inter所以使用**config-Intel-M.2-WiFi-BT.plist**),并将其重命名为**config.plist**。
1. [x] 按照CPU核心数修改**Kernel -> Patch** 中的带有**cpuid_set_info** 与 **algrey - Force cpuid_cores_per_package XX.X+** 项的Replace数据中的**BA0800000000**的 08改成你使用的CPU的核心数。7700x 8核7950x 16核所以改成**BA1600000000**
1. [x] 修改**PlatformInfo -> ProcessorType** 为 3841 超出8核心
2. [x] 修改**NVRAM -> 4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102 -> revcpuname** CPU名称。
3. [x] 修改**PlatformInfo -> Memory**部分需要查询一下内存序列号因为我是32 x 2所以改成32768 F5-6000J3040G32GX2-TZ5NR
4. [x] 修改语言为中文,**NVRAM -> 7C436110-AB2A-4BBB-A880-FE41995C9F82 -> prev-lang:kbd** 为 **string zh-Hans:252**
5. [ ] 修改系统序列号与UUID**PI -> Generic**。[【Windows&macOS】完美双系统系列教程第2集Windows环境下配置OC引导】 【精准空降到 07:12】](https://www.bilibili.com/video/BV1Bi4y1S7DN/?share_source=copy_web&vd_source=fe8142e8e12816535feaeabd6f6cdc8e&t=432)
6. [x] 修改**DP**中硬盘PCIE、 屏蔽N卡本人的x670e-e装了N卡与A卡使用OpenCore引导时需要确定显示器接在A卡上别问我是怎么知道的实际上x670e-e与x670e gen的STAT NVMEN的控制器分布式一样的所以不用改https://dortania.github.io/OpenCore-Install-Guide/extras/spoof.html#windows-gpu-selection 。
1. SSDT法 https://zhuanlan.zhihu.com/p/436460948
2. NVRAM法 https://imacos.top/2022/01/03/0807/
3. 路径可以在设备管理器 -> 显卡属性 -> 详细信息 -> 位置路径找到。 https://www.reddit.com/r/hackintosh/comments/yetoec/pci_device_paths_without_gfxutil_or_macos_using/
4. 屏蔽N卡 PciRoot(0x0)/Pci(0x1,0x1)/Pci(0x0,0x0) |disable-gpu|Boolean|`True`| https://dortania.github.io/OpenCore-Install-Guide/extras/spoof.html
3. 修改MMIO白名单
4. 可选修改
1. [ ] 将OpenCore加入Bios启动项中。[【Windows&macOS】完美双系统系列教程第2集Windows环境下配置OC引导】 【精准空降到 05:35】](https://www.bilibili.com/video/BV1Bi4y1S7DN/?share_source=copy_web&vd_source=fe8142e8e12816535feaeabd6f6cdc8e&t=335)
2. [ ] 为了EXPO关闭雷电4接口。(**Bios中操作可能需要禁用**)
3. [ ] USBToolBox 定制

View File

@@ -0,0 +1,250 @@
## Electron Quick Start
```
# 克隆这仓库
$ git clone https://github.com/electron/electron-quick-start
# 进入仓库
$ cd electron-quick-start
# 安装依赖库
$ npm install
# 运行应用
$ npm start
```
## 使用IPC进行GUI与原生APP进行通讯
在main.js里添加下面的代码从通道订阅消息
```
var ipc = require('ipc');
ipc.on('close-main-window', function () {
app.quit();
});
```
引入ipc模块后通过通道订阅消息就变得很简单on()方法设置订阅的通道名,定义回调函数。
渲染进程要通过通道发送消息将下面代码加入index.js
```
var ipc = require('ipc');
var closeEl = document.querySelector('.close');
closeEl.addEventListener('click', function () {
ipc.send('close-main-window');
});
```
同样我们引入ipc模块给关闭按钮的元素绑定一个click事件。当点击关闭按钮时通过「close-main-window」通道的send()方法发送消息。
## 全局快捷键
```
var globalShortcut = require('global-shortcut');
app.on('ready', function() {
... // existing code from earlier
globalShortcut.register('ctrl+shift+1', function () {
mainWindow.webContents.send('global-shortcut', 0);
});
globalShortcut.register('ctrl+shift+2', function () {
mainWindow.webContents.send('global-shortcut', 1);
});
});
```
```
ipc.on('global-shortcut', function (arg) {
var event = new MouseEvent('click');
soundButtons[arg].dispatchEvent(event);
});
```
## 保存用户配置
使用nconf模块
```
npm install --save nconf
```
```
var nconf = require('nconf').file({file: getUserHome() + '/sound-machine-config.json'});
function saveSettings(settingKey, settingValue) {
nconf.set(settingKey, settingValue);
nconf.save();
}
function readSettings(settingKey) {
nconf.load();
return nconf.get(settingKey);
}
function getUserHome() {
return process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'];
}
module.exports = {
saveSettings: saveSettings,
readSettings: readSettings
};
```
## 系统托盘
```
var remote = require('remote');
var Tray = remote.require('tray');
var Menu = remote.require('menu');
var path = require('path');
var trayIcon = null;
if (process.platform === 'darwin') {
trayIcon = new Tray(path.join(__dirname, 'img/tray-iconTemplate.png'));
}
else {
trayIcon = new Tray(path.join(__dirname, 'img/tray-icon-alt.png'));
}
var trayMenuTemplate = [
{
label: 'Sound machine',
enabled: false
},
{
label: 'Settings',
click: function () {
ipc.send('open-settings-window');
}
},
{
label: 'Quit',
click: function () {
ipc.send('close-main-window');
}
}
];
var trayMenu = Menu.buildFromTemplate(trayMenuTemplate);
trayIcon.setContextMenu(trayMenu);
```
## 打包应用
>摘自https://www.jianshu.com/p/f134878af30f
安装electron-package
```
npm install electron-package --save-dev
```
添加scrip命令 ,用于打包electron app。
```
"scripts": {
"start": "electron ."
"build": "electron-packager . hello_electron --platform=darwin --arch=x64 --ignore=node_modules/electron-*",
},
```
**electron-packager命令格式**
```
electron-packager 项目目录 app名称 --platform=平台 --arch=架构 --ignore=要忽略的目录或文件
arch
ia32 x64 armv7l all
plateform
linux win32 darwin mas all
OS X (also known as darwin)
Mac App Store (also known as mas)
```
执行命令npm run build,将得到如下结果
### electron-builder与electron-packager的区别
使用electron-builder打包应用是安装包方式而不想electron-packager打包之后直接是一个可文件夹交给所有的文件暴露出来。由electron-builder打出的包更为轻量并且可以打包出不暴露源码的setup安装程序
## 压缩源码
为避免源代码泄露,可对源码进行压缩。
**安装electron-asar**
```
npm install electron-asar --save-dev
```
**添加scrip命令 ,用于压缩源代码。**
```
"scripts": {
"start": "electron ."
"build": "electron-packager . hello_electron --platform=darwin --arch=x64 --ignore=node_modules/electron-*",
"package":"asar pack hello_electron-darwin-x64/hello_electron.app/Contents/Resources/app hello_electron-darwin-x64/hello_electron.app/Contents/Resources/app.asar"
},
```
**asar 命令格式**
asar pack <dir> <output>
执行npm run package将得到app.asar文件此时可将app文件删除。
## Electron启动node.js服务器
1.直接在index.html中启动外部的node.js服务器
2.将原生的node.js服务器代码使用module.exports = () => {}导出之后在electron的main.js中直接导入
```
app.server = require(__dirname + '/app/app')();
```
## 不迁移项目就可以打包双版本的可行方案
作者并未提供web开发的支持但是提供了非常好的web打包支持。
只要写好逻辑我们可以不用迁移项目就可以打包桌面项目和web项目。
process.env.IS_WEB是暴露的一个全局变量我们可以在渲染进程中获取项目在electron环境下返回false。否则为true。于此我们可以通过设置她的值来达到web dev的效果也可以处理不同环境的不同逻辑一些示例
![image](https://upload-images.jianshu.io/upload_images/3765249-cae045ed68c41067.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![image](https://upload-images.jianshu.io/upload_images/3765249-e8f7f6ac49fdf050.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![image](https://upload-images.jianshu.io/upload_images/3765249-a48751e6cf2d88e3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
## 打开新窗口的“最佳”做法
1.使用webview allowpopups变量控制是否拦截新弹出的窗口
下面的例子是webview允许.open/.showModalDialog/.showModelessDialog的例子
electron的index.html:重点是参数allowpopups
```
<webview id="foo" src="https://newsn.net/test.html" allowpopups style="width:100%; height:360px;"></webview>
```
原文https://newsn.net/say/electron-webview-window-open.html
2.因为是webpack配置入口只有index.html ,所以打开新窗口,一般会再配置一个入口。其实还有一种更佳的做法。
```
>>> 主进程 定义好监听事件
ipc.on('newPage', function(e) {
const modalPath = process.env.NODE_ENV === 'development'
? 'http://localhost:9080/#/newPage'
: `file://${__dirname}/index.html#newPage`
let win = new BrowserWindow({
width: 1024,
height: 724,
webPreferences: {
webSecurity: false
}
})
win.on('close', function() {
win = null
})
win.loadURL(modalPath)
})
>>> router/index.js 定义路由
// import 你的新页面 .vue 文件
{
path: '/newPage',
name: 'newPage',
component: newPage,
}
》》》配置完成 任意进程调用ipc.send('newPage') 完美解决
```
3.
```
document.getElementById("youtube").onclick = function(){
youtubeWindow = new BrowserWindow ({width: 1000, height:800})
youtubeWindow.loadURL("https://youtube.com/");
youtubeWindow.on("close", function(){
youtubeWindow = null;
})
}
document.getElementById("local-list").onclick = function(){
localListWindow = new BrowserWindow ({width: 1000, height:800})
localListWindow.loadURL(`file://${__dirname}/local-list.html`);
localListWindow.on("close", function(){
localListWindow = null;
})
}
```

View File

@@ -0,0 +1,112 @@
## 移植经验
1. 使用npm下载所有在vue-cli项目所使用的模块比如element-ui、NProgress之类的。
2. 将vue-cli项目中src目录下所有文件复制到electron-vue中对应目录。不要直接覆盖main.js因为electron-vue中有一些写法不同了对于这个文件需要自己仔细移植比如 Vue.use(require('vue-electron'))就与vue-cli不同
3. 运行npm run dev解决报错问题。(主要是因为路径失效造成的问题)
## this.$router.push()报错then() undefined
这有可能是因为你使用了动态加载路由也就是在vue-router中使用了vuex,同时夹杂了Electron-vue模板里的东西。在electron-vue模板使用的store/index.js文件中调用了vuex-electron插件而这些插件貌似会与结构起冲突把这些东西删除了就好了。
```
// import { createPersistedState, createSharedMutations } from 'vuex-electron'
// plugins: [
// createPersistedState(),
// createSharedMutations()
// ],
```
我使用的动态路由是修改自
[PanJiaChen/vue-admin-template](https://note.youdao.com/)。该作者也做了个Electron的项目同样是用Electron-vue模板所以相当值得参考。
## 跨域问题
我一开始也很疑惑为啥electron-vue中为啥没有proxytable。后来发现的确没有必要。如果有只可能是因为你的需求有问题。
vue-cli项目是将打包好的js、html等文件一起放到服务端中之后通过浏览器来浏览。服务端和前端文件是放在一起的。所以为了解决前后端分离的问题才使用跨域转发的方式来解决开发模式需要跨而生成模式就不需要。而Electron开发与生成都需要跨毕竟前端和服务端不在一起。所以也就不存在跨域问题也就不需要ProxyTable了。
我的解决方法是这样:
设置一个config.js作为全局设置文件。在里面设置一个变量用于存储本地服务器ip之后在使用axios编写get与post接口的文件将服务器ip变量加到接口地址前就可以了。例如
```
export const requestLogin = params => {return axios.post(`/login`,params).then(res => res.data); };
```
=>
```
//在config.js中base为127.0.0.1:3000
import {base} from '../config';
export const requestLogin = params => {return axios.post(`${base}/login`,params).then(res => res.data); };
```
如果需要导出web版本则可以使用process.env.IS_WEB来判断是否为web模式。
## 静态文件与启动本地服务问题
之前曾想过在electron里直接导入服务端代码来运行服务器但后来发现不行。github上代码都是通过electron(Node.js)的child_process来启动外部服务器的。
这里我使用pm2来启动这样就不需要在程序关闭时关闭服务器了。child_process 老是无法关闭服务器进程,不知道为什么)
```
let exec = require('child_process').exec;
let server = exec("pm2 start ./bin/www", {cwd: process.cwd()+'/server'});
```
静态文件可以放再electron目录下的static文件夹中(之后会打包成asar文件)不过因为本人写的app需要定时更新文件且文件类型较杂所以使用本地服务端来管理静态文件。
于是对于项目中的需要显示图片或者其他文件链接就需要加入本地服务器ip地址就像上一节那样。
## 打包后静态文件失效问题
__dirname是Electron-vue中在webpack中配置的变量对应开发模式与
产品模式时的electron静态文件目录。但如果你使用了一些webpack中没有设置的文件比如字体会出一些问题请参考这个blog
https://blog.csdn.net/aa661020/article/details/79757859
## 关于自定义协议
本人写的app使用了类似QQ的自定义协议来启动自己写的外部程序并传参在electron中为了安全的问题默认把这个功能屏蔽了。electron中应该可以使用protocol来定义协议来解决问题不过我找到了另一种方法那就是直接使用child_process的exec直接用命令行的方式来启动程序。
直接在渲染进程中xxx.vue文件
```
<script>
const exec = require('child_process').exec;
export default {
methods: {
exec(row){
//s为自定义协议的字符串
let s='xxxxxx';
let url="test://"+ s;
//test为程序名注意后面有空格第二个参数中提供程序的运行位置具体的可以参考node.js文档。
exec("test "+url, {cwd: process.cwd()+'/XXXX'});
}
}
}
</script>
```
不过需要注意在中文windows系统没有修改过默认字符集的浏览器里通过自定义协议来启动程序默认传的是gbk的字符集。而不同系统win10win7字符集是不同的。所以就需要对启动程序进行修改。
## 关于多条命令启动
因为本人写的App需要更新数据库mysql数据库所以本人使用mysql的命令进行恢复数据。
奇怪的是child_process的exec竟然不支持多条语句。所以这里我就使用了child_process的spawn。
多条命令之间使用&&间隔具体的请参照cmd指南还有别的间隔符比如| &
```
let cmd='cd /d C:/Program Files/MySQL/MySQL Server 5.7/bin && mysql -uXXXXX -pXXXXX abcde </managersystem_user.sql && mysql -uXXXXX -pXXXXX abcde </abcde_xxxx.sql';
cmd+=" && mysql -uXXXXX -pXXXXX abcde < /abcde_routines.sql";
let child = spawn(cmd, {
shell: true
});
//这里还可以输入新的命令与显示cmd返回内容具体的请参考node.js文档
child.on('exit', ()=> {
});
```
## 关于启动参数
```
//在主进程中
global.sharedObject = {prop1: process.argv}
```
```
//渲染进程中
var remote = require('electron').remote,
arguments = remote.getGlobal('sharedObject').prop1;
console.log(arguments);
```
## 最后推荐一下苏南大叔的blog
里面有关electron的文章很不错
https://newsn.net

View File

@@ -0,0 +1,61 @@
## 修改electron-packager代码
首先确认electron-packager安装位置全局或者局部之后进入`node_modules\electron-packager\`路径。使用VSCode搜索`packageForPlatformAndArch`。代码如下:
```c#
packageForPlatformAndArch (downloadOpts) {
return download.downloadElectronZip(downloadOpts)
.then(zipPath => {
// Create delegated options object with specific platform and arch, for output directory naming
const comboOpts = Object.assign({}, this.opts, {
arch: downloadOpts.arch,
platform: downloadOpts.platform,
electronVersion: downloadOpts.version
})
if (!this.useTempDir) {
return this.createApp(comboOpts, zipPath)
}
if (common.isPlatformMac(comboOpts.platform)) {
/* istanbul ignore else */
if (this.canCreateSymlinks === undefined) {
return this.testSymlink(comboOpts, zipPath)
} else if (!this.canCreateSymlinks) {
return this.skipHostPlatformSansSymlinkSupport(comboOpts)
}
}
return this.checkOverwrite(comboOpts, zipPath)
})
}
```
添加逻辑:
```
let zipFile="electron-v11.2.1-win32-x64.zip";
console.log(__dirname);
let HasFile=fs.existsSync(zipFile);
if(HasFile)
{
const comboOpts = Object.assign({}, this.opts, {
arch: downloadOpts.arch,
platform: downloadOpts.platform,
electronVersion: downloadOpts.version
})
if (!this.useTempDir) {
return this.createApp(comboOpts, zipFile)
}
if (common.isPlatformMac(comboOpts.platform)) {
/* istanbul ignore else */
if (this.canCreateSymlinks === undefined) {
return this.testSymlink(comboOpts, zipFile)
} else if (!this.canCreateSymlinks) {
return this.skipHostPlatformSansSymlinkSupport(comboOpts)
}
}
return this.checkOverwrite(comboOpts, zipFile)
}
```
其中执行路径即为项目根目录直接将zip放在项目目录中即可。

View File

@@ -0,0 +1,18 @@
## H5 PPT
https://revealjs.com/
http://imakewebthings.com/deck.js/
https://github.com/briancavalier/slides?spm=a2c6h.12873639.0.0.2c404409ftv46I
https://github.com/LeaVerou/inspire.js?spm=a2c6h.12873639.0.0.2c404409ftv46I
https://github.com/impress/impress.js/
VUE PPT插件
https://github.com/faveeo/eagle.js
感觉revealjs的功能较多但好像无法直接移植进入VUE。impressJS适合做商业演示inspireJS适合比较简单的演示。
## Node.js生成pptx
https://www.npmjs.com/package/ppt-template
https://www.npmjs.com/package/nodejs-pptx
https://www.npmjs.com/package/pptxgenjs
pptxgenjs用的人最多。

View File

@@ -0,0 +1,11 @@
## word文档生成可以使用
- docx:https://www.npmjs.com/package/docx
## word模板替换
- generate-docx:https://github.com/telemark/generate-docx
- carbone:https://github.com/carboneio/carbone
- docxtemplater:https://www.npmjs.com/package/docxtemplater
- docxtemplater-image-module-free:https://www.npmjs.com/package/docxtemplater-image-module-free
## 库
FileSaver使用案例https://jsfiddle.net/dolanmiu/onadx1gu/

View File

@@ -0,0 +1,168 @@
# 以下摘自官方文档
## Escaping query values
**Caution** These methods of escaping values only works when the
[NO_BACKSLASH_ESCAPES](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_no_backslash_escapes)
SQL mode is disabled (which is the default state for MySQL servers).
In order to avoid SQL Injection attacks, you should always escape any user
provided data before using it inside a SQL query. You can do so using the
`mysql.escape()`, `connection.escape()` or `pool.escape()` methods:
```js
var userId = 'some user provided value';
var sql = 'SELECT * FROM users WHERE id = ' + connection.escape(userId);
connection.query(sql, function (error, results, fields) {
if (error) throw error;
// ...
});
```
Alternatively, you can use `?` characters as placeholders for values you would
like to have escaped like this:
```js
connection.query('SELECT * FROM users WHERE id = ?', [userId], function (error, results, fields) {
if (error) throw error;
// ...
});
```
Multiple placeholders are mapped to values in the same order as passed. For example,
in the following query `foo` equals `a`, `bar` equals `b`, `baz` equals `c`, and
`id` will be `userId`:
```js
connection.query('UPDATE users SET foo = ?, bar = ?, baz = ? WHERE id = ?', ['a', 'b', 'c', userId], function (error, results, fields) {
if (error) throw error;
// ...
});
```
This looks similar to prepared statements in MySQL, however it really just uses
the same `connection.escape()` method internally.
**Caution** This also differs from prepared statements in that all `?` are
replaced, even those contained in comments and strings.
Different value types are escaped differently, here is how:
* Numbers are left untouched
* Booleans are converted to `true` / `false`
* Date objects are converted to `'YYYY-mm-dd HH:ii:ss'` strings
* Buffers are converted to hex strings, e.g. `X'0fa5'`
* Strings are safely escaped
* Arrays are turned into list, e.g. `['a', 'b']` turns into `'a', 'b'`
* Nested arrays are turned into grouped lists (for bulk inserts), e.g. `[['a',
'b'], ['c', 'd']]` turns into `('a', 'b'), ('c', 'd')`
* Objects that have a `toSqlString` method will have `.toSqlString()` called
and the returned value is used as the raw SQL.
* Objects are turned into `key = 'val'` pairs for each enumerable property on
the object. If the property's value is a function, it is skipped; if the
property's value is an object, toString() is called on it and the returned
value is used.
* `undefined` / `null` are converted to `NULL`
* `NaN` / `Infinity` are left as-is. MySQL does not support these, and trying
to insert them as values will trigger MySQL errors until they implement
support.
This escaping allows you to do neat things like this:
```js
var post = {id: 1, title: 'Hello MySQL'};
var query = connection.query('INSERT INTO posts SET ?', post, function (error, results, fields) {
if (error) throw error;
// Neat!
});
console.log(query.sql); // INSERT INTO posts SET `id` = 1, `title` = 'Hello MySQL'
```
And the `toSqlString` method allows you to form complex queries with functions:
```js
var CURRENT_TIMESTAMP = { toSqlString: function() { return 'CURRENT_TIMESTAMP()'; } };
var sql = mysql.format('UPDATE posts SET modified = ? WHERE id = ?', [CURRENT_TIMESTAMP, 42]);
console.log(sql); // UPDATE posts SET modified = CURRENT_TIMESTAMP() WHERE id = 42
```
To generate objects with a `toSqlString` method, the `mysql.raw()` method can
be used. This creates an object that will be left un-touched when using in a `?`
placeholder, useful for using functions as dynamic values:
**Caution** The string provided to `mysql.raw()` will skip all escaping
functions when used, so be careful when passing in unvalidated input.
```js
var CURRENT_TIMESTAMP = mysql.raw('CURRENT_TIMESTAMP()');
var sql = mysql.format('UPDATE posts SET modified = ? WHERE id = ?', [CURRENT_TIMESTAMP, 42]);
console.log(sql); // UPDATE posts SET modified = CURRENT_TIMESTAMP() WHERE id = 42
```
If you feel the need to escape queries by yourself, you can also use the escaping
function directly:
```js
var query = "SELECT * FROM posts WHERE title=" + mysql.escape("Hello MySQL");
console.log(query); // SELECT * FROM posts WHERE title='Hello MySQL'
```
## Escaping query identifiers
If you can't trust an SQL identifier (database / table / column name) because it is
provided by a user, you should escape it with `mysql.escapeId(identifier)`,
`connection.escapeId(identifier)` or `pool.escapeId(identifier)` like this:
```js
var sorter = 'date';
var sql = 'SELECT * FROM posts ORDER BY ' + connection.escapeId(sorter);
connection.query(sql, function (error, results, fields) {
if (error) throw error;
// ...
});
```
It also supports adding qualified identifiers. It will escape both parts.
```js
var sorter = 'date';
var sql = 'SELECT * FROM posts ORDER BY ' + connection.escapeId('posts.' + sorter);
// -> SELECT * FROM posts ORDER BY `posts`.`date`
```
If you do not want to treat `.` as qualified identifiers, you can set the second
argument to `true` in order to keep the string as a literal identifier:
```js
var sorter = 'date.2';
var sql = 'SELECT * FROM posts ORDER BY ' + connection.escapeId(sorter, true);
// -> SELECT * FROM posts ORDER BY `date.2`
```
Alternatively, you can use `??` characters as placeholders for identifiers you would
like to have escaped like this:
```js
var userId = 1;
var columns = ['username', 'email'];
var query = connection.query('SELECT ?? FROM ?? WHERE id = ?', [columns, 'users', userId], function (error, results, fields) {
if (error) throw error;
// ...
});
console.log(query.sql); // SELECT `username`, `email` FROM `users` WHERE id = 1
```
**Please note that this last character sequence is experimental and syntax might change**
When you pass an Object to `.escape()` or `.query()`, `.escapeId()` is used to avoid SQL injection in object keys.
### Preparing Queries
You can use mysql.format to prepare a query with multiple insertion points, utilizing the proper escaping for ids and values. A simple example of this follows:
```js
var sql = "SELECT * FROM ?? WHERE ?? = ?";
var inserts = ['users', 'id', userId];
sql = mysql.format(sql, inserts);
```
Following this you then have a valid, escaped query that you can then send to the database safely. This is useful if you are looking to prepare the query before actually sending it to the database. As mysql.format is exposed from SqlString.format you also have the option (but are not required) to pass in stringifyObject and timezone, allowing you provide a custom means of turning objects into strings, as well as a location-specific/timezone-aware Date.

View File

@@ -0,0 +1,105 @@
## 前言
最近因为某些原因最近决定升级VUE前端以及Electron APP工程中版本其中Node.js版本从9.9.0升级到12.19.1。因为涉及较多东西,所以写此笔记。
推荐:
- 使用nvm为了保证不会耽误旧版本项目可以使用nvm对node.js版本进行管理。你只需要使用nvm use 版本号,即可切换对应版本。
- 使用版本管理,使得每一步升级都可以回滚。
## vue前端
### vue
项目用的是vue-cli生成。vue版本号需要与vue-template-compiler版本号一致。本人值从高版本一个一个往下试错最后用了个可以跑通的版本。因为用的是npm
### node-sass
如果直接使用npm install -s node-sass可能会引发node-gyp编译而造成的错误。推荐先npm uninstall node-sass将node-sass卸载干净之后再安装。这个时候就会直接下载编译好的node-sass而不是尝试编译。
**node-sass与node.js版本也有关系**,需要注意:
NodeJS | Supported node-sass version | Node Module
---|---|---
Node 15 | 5.0+ | 88
Node 14 | 4.14+ | 83
Node 13 | 4.13+, <5.0 |79
Node 12 |4.12+ | 72
Node 11 |4.10+, <5.0 | 67
Node 10 |4.9+ | 64
Node 8 |4.5.3+, <5.0 | 57
Node <8 |<5.0 | <57
### Element-ui
当低于**2.7.0**版本的Element-ui升级时需要注意要添加对**jsx**语法的支持否则会报一下错误
```
error in ./~/.2.11.1@element-ui/packages/form/src/label-wrap.vue
Syntax Error: Unexpected token (23:14)
21 | }
22 | }
> 23 | return (<div class="el-form-item__label-wrap" style={style}>
| ^
24 | { slots }
25 | </div>);
26 | } else {
@ ./~/.2.11.1@element-ui/packages/form/src/label-wrap.vue 4:2-108
```
解决
```npm
npm install babel-plugin-syntax-jsx babel-plugin-transform-vue-jsx babel-helper-vue-jsx-merge-props babel-preset-env --save-dev
```
在项目目录的.babelrc中添加对jsx插件的配置
```json
{
"plugins": ["transform-vue-jsx", ...]
}
```
## Electron
对于Electron-vue项目还是推荐创建一个Electron-vue新项目之后再将代码移植过去比较好
### require/process/module is not define
因为新版本Electron中默认没有集成node
解决修改src/main/index.js,在webPreferences中加入nodeIntegration: true
```
mainWindow = new BrowserWindow({
height: 563,
useContentSize: true,
width: 1000,
webPreferences: {
nodeIntegration: true,
}
})
```
### can't read property 'app' of
是因为electron10后remote模块默认是关闭的,解决修改src/main/index.js,在webPreferences中加入enableRemoteModule: true
### Element-ui的el-table不显示
解决.electron-vue/webpack.renderer.config.js中的
```
let whiteListedModules = ['vue']
```
加入elementui
```
let whiteListedModules = ['vue', 'element-ui']
```
## Node.js后端sqlite3模块
这里会需要使用node-gyp进行编译这个需要安装visual studio2017因为最高版本就支持到2017不支持2019着实蛋疼即使你给2019安装了2017 版本的构建套件win10sdk与c++ ATL库node-gyp都检测到了依然还会说版本不匹配即使指定了版本为2019)。
如果你安装完2017还是不能正常编译就可以手动指定vs版本了执行
```
npm config set msvs_version 2017
```
node-gyp的具体安装步骤可以参考https://github.com/nodejs/node-gyp#on-windows
## 清除注释
### js 双斜杠
```
//(?!.*\..*\.).*\n
```
### js 多行
```
/\*(.|\r\n|\n)*?\*/
```
### HTML
```
<!--(.|[\r\n])*?-->
```

View File

@@ -0,0 +1,54 @@
> 类似的文章还是比较多的,但或多或少有一些问题没有解决,在此我将其整合并分享给大家:
## 测试环境:
- Node.js 9.9.0
- VisualStudio 2015
- "ffi": "gavignus/node-ffi#torycl/forceset-fix",
- "ref": "1.3.5"
- "ref-array": "1.2.0"
- "ref-struct": "1.1.0"
- "ffi-napi": "^2.4.3"
## 编译失败:
当前情况下编译ffi会失败所以有两种解决方法
1. 使用新的ffi-napiapi是一样的同时支持node.js新的napi
2. 使用第三方修改过的ffi在package.json中将ffi后面的版本号改成
> "ffi": "gavignus/node-ffi#torycl/forceset-fix"
## 使用:
```
var ffi = require('ffi');
//第一个形参为dll所在位置dll文件可以不用加.dll第二个为函数信息
var libm = ffi.Library(__dirname + 'dllFile', {
//函数名
'fun': ['int', ['string', 'string']]
});
//调用
var str1="a";
var str2="b";
libm.fun(str1, str2);
```
## 使用c++里的类型
ref、ref-struct、ref-array、ref-union、ref-wchar
在npm查看使用方法在此不做赘述。
## 运行时遇到的错误
1. c++代码是可以用的但是需要把代码写在extern "C"{}里,不过这个我没有亲自试过。
2. dll文件需要放到node.js 执行目录,也就是
```
//即x:\xxxxx\xx
cd /d x:\xxxxx\xx;
node xxx.js;
```
3. dll如果有互相依赖的必须放全。不然只会出现错误126而不会像一般程序那样提示缺少xxx.dll。所以报错了可以用depends看一下dll全了没。
4. dll的需要与node.js的平台相对应比如你的node.js是64位版本的那你的dll也需要使用64位编译。
错误126检查上述1、2、3步。
## 参考:
- wiki
- https://github.com/node-ffi/node-ffi/wiki/Node-FFI-Tutorial<br>
- https://www.jianshu.com/p/914103283ea0
- https://blog.csdn.net/zhulin2609/article/details/51474676

View File

@@ -0,0 +1,19 @@
## 坐标
1. Item(图元)坐标:属于局部坐标,通常以图元中心为原点(中心对称)非中心对称类比如dialog类一般以左上角为原点正方向x朝右y朝下。
2. setPos的坐标是父类坐标系的坐标一般对于item位于scene中的应用场景。
3. scene(场景)坐标:属于逻辑坐标 logical coordinates与QPainter相同**以场景中心为原点**正方向x朝右y朝下。
4. 图元原点左上角dialog的原点**与场景原点对齐**,导致图元外边框的左上角顶点在场景中的坐标位置为(负数,负数)。
5. View(视图)坐标属于设备坐标device coordinates与窗口相同**默认以左上点为原点** 正方向x朝右y朝下。
6. 默认场景scene的左上角顶点与视图坐标原点对齐。**显示时默认中心对齐**,当场景大小小于视图大小的时候,将中心对齐,此中指的仍然是整个图元的中心,同时,图元原点与场景原点对齐,场景左上角顶点与视图原点对齐,视图左上角顶点不一定是原点???,此时也将出现视图坐标有正值有负值。
- 1translate()将当前视图坐标原点平移从而实现显示图像的平移变换。由于默认场景的左上角顶点与视图坐标原点对齐translate()将坐标原点平移,也就实现了将场景的平移。
- 2rotate()将当前视图围绕视图坐标原点旋转,从而实现显示图像的旋转变换。
- 3size()返回视图大小默认大小100*30由于视图可以是无限大小而且只有在显示后才创建出实际尺寸因此只有在showEvent中调用 size 函数才能正确显示视图大小否则都是返回默认的100*30因为此时视图尚未显示即尚未形成。
https://www.cnblogs.com/cthu/p/5103551.html
## 常用的QGraphicsItem
https://blog.csdn.net/liang19890820/article/details/53065293
## QGraphics
https://blog.csdn.net/liang19890820/article/details/51966791

View File

@@ -0,0 +1,79 @@
## 注意点
隐藏状态栏时第一个界面不能使用Qt设计师界面类创建项目时不勾选**创建界面**选项)。不然以下方法皆会无效。
## 方法1
修改AndroidManifest.xml中activity的主题。即在`<activity>`标签中添加 `android:theme="@android:style/Theme.NoTitleBar.Fullscreen"`
以下为实例代码:
```
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
android:name="an.qt.helloQtQuickApp.QtFullscreenActivity"
android:label="-- %%INSERT_APP_NAME%% --"
android:screenOrientation="unspecified"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:launchMode="singleTop">
```
如果无效则需要重写QtActivity的onCreate函数。
大致步骤如下:
1. 在项目目录\android\src\目录下创建与包名相同的目录。例如我们要创建的包为an.qt.helloQtQuickApp那么路径为an\qt\helloQtQuickApp。并创建一个Java文件用于定义我们的Activity。文件名可以随便取,我这里的文件名设为QtFullscreenActivity.java。
2. 修改AndroidManifest.xml文件使用我们刚才新定义的Activity。
` <manifest>` 标签中的package改成我们创建的包名。将`<activity>`标签中android:name改成an.qt.helloQtQuickApp.QtFullscreenActivity,即包名.新创建的Activity类名。
>Java文件代码如下
```
package an.qt.helloQtQuickApp;
import android.content.Context;
import android.content.Intent;
import android.app.PendingIntent;
import android.util.Log;
import android.os.Bundle;
import android.view.WindowManager;
public class QtFullscreenActivity extends org.qtproject.qt5.android.bindings.QtActivity
{
private final static String TAG = "QtFullscreen";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
}
```
详细操作过程可以参看安晓辉的blog
https://blog.csdn.net/foruok/article/details/38265349
## 方法2
大致思路如下使用Qt5.7后在QtAndroid类新增了一个静态函数
```
void QtAndroid::runOnAndroidThread(const QtAndroid::Runnable &runnable)
```
通过它调用以下Java代码
```
Activity activity = (Activity) mContext;
View decorView = activity.getWindow().getDecorView();
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_IMMERSIVE
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN);
```
这种方法的好处在于可以在运行时控制是否隐藏状态栏而且不用改AndroidManifest.xml文件移植方便。
详细操作可以见作者Blog
https://blog.csdn.net/jun4331247/article/details/80739662
项目Github
https://github.com/WingNan/QtAndroidFullScreen
### 移植步骤
1. 复制文件jfullscreen.h、jfullscreen.cpp至你的项目目录。复制android/src目录及其内部文件至项目同名目录。
2. 在项目上右键点击“添加现有文件",将以上几个文件添加到项目中。
3. 编辑项目文件(*.pro)添加androidextras模块在Qt+=core gui后面加上androidextras
4. 在app的第一个widget中#include "jfullscreen.h",并在构造函数中加入:
```
JFullScreen *pManager = new JFullScreen;
pManager->fullScreenStickyImmersive();
```
注意创建项目时不能勾选“创建界面”选项并且第一个widget不能使用设计师界面类来创建

View File

@@ -0,0 +1,53 @@
## 字体文件下载
fontawesome-webfont.ttf另一个是pe-icon-set-weather.ttf
fontawesome-webfont.ttf 下载地址http://fontawesome.dashgame.com/
pe-icon-set-weather.ttf 下载地址https://www.pixeden.com/icon-fonts/the-icons-font-set-weather
在pixeden中还有许多其它的图标字体库下载https://www.pixeden.com/icon-fonts
## Code
```
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QFontDatabase>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//引入图形字体
int fontId = QFontDatabase::addApplicationFont(":/image/pe-icon-set-weather.ttf"); //加入字体并获取字体ID
QString fontName = QFontDatabase::applicationFontFamilies(fontId).at(0); //获取字体名称
QFont iconFont = QFont(fontName);
iconFont.setPixelSize(128); //设置字体大小
ui->lab_e901->setFont(iconFont); //设置Label的字体
ui->lab_e901->setText(QChar(0xe901)); //设置Label的文体
ui->lab_e901->setStyleSheet("color:red;");
QPalette blue_pe;
blue_pe.setColor(QPalette::WindowText,Qt::blue);
ui->lab_e903->setFont(iconFont);
ui->lab_e903->setText(QChar(0xe903));
ui->lab_e903->setPalette(blue_pe);
ui->lab_e905->setFont(iconFont);
ui->lab_e905->setText(QChar(0xe905));
ui->lab_e907->setFont(iconFont);
ui->lab_e907->setText(QChar(0xe907));
}
MainWindow::~MainWindow()
{
delete ui;
}
```
## QFontDatabase
QStringList QFontDatabase::families(QFontDatabase::WritingSystem writingSystem = Any) const
返回可用的字体列表。
QFont QFontDatabase::font(const QString &family, const QString &style, int pointSize) const
返回一个可用的字体对象。

View File

@@ -0,0 +1,104 @@
## 通用选择器
* 作为选择器,作用于所有的 widget。
类型选择器
类名 作为选择器,作用于它自己和它的所有子类。
```
QFrame {
background: gray;
}
```
## 类选择器
. + 类名 或者 . + class 的属性值 作为选择器(使用 setProperty(“class”, “QSSClassName”) 设置),只会作用于它自己,它的子类不受影响,注意和类型选择器的区别。
```
app.setStyleSheet(".QWidget { background: gray; }"
".RedButton { background: magenta; }");
// .RedButton 将作为类选择器
openButton->setProperty("class", "RedButton");
closeButton->setProperty("class", "RedButton");
```
## ID 选择器
'#' + objectName 作为选择器,只作用于用此 objectName 的对象
```
// #openButton 和 #closeButton 作为 ID 选择器
app.setStyleSheet(".QWidget { background: gray; }"
"#openButton, #closeButton { background: magenta; }");
openButton->setObjectName("openButton");
closeButton->setObjectName("closeButton");
```
## 属性选择器
选择器[属性="值"] 作为选择器,这个属性可用通过 object->property(propertyName) 访问的Qt 里称为 Dynamic Properties。
如上面的程序, openButton 和 closeButton 的背景是洋红色的,但是 saveButton 不受影响,也可以使用属性选择器来实现:
```
app.setStyleSheet(".QWidget { background: gray; }"
"QPushButton[level='dangerous'] { background: magenta; }");
openButton->setProperty("level", "dangerous");
```
属性选择器可以包含多个属性,只需要:
```
QPushButton[level='aaa'],QPushButton[level='bbb'] { background: magenta; }
```
如果有多个属性,则会优先选择匹配数多的样式。
## 包含选择器
英语叫做 Descendant Selectordescendant 的表达比较到位。
选择器之间用空格隔开,作用于 Widget 的 子Widget子Widget 的 子Widget……子子孙孙无穷尽也。
```
QFrame {
background: gray;
}
/* 设置 QFrame 中的 QPushButton 的 QSS */
QFrame QPushButton {
border: 2px solid magenta;
border-radius: 10px;
background: white;
padding: 2px 15px;
}
```
## 子元素选择器
选择器之间用 > 隔开,作用于 Widget 的直接 子Widget注意和包含选择器的区别。
```
QFrame {
background: gray;
}
QFrame > QPushButton {
border: 2px solid magenta;
border-radius: 10px;
background: white;
padding: 2px 15px;
}
```
## 伪类选择器
选择器:状态 作为选择器,支持 ! 操作符,表示 非。
```
QPushButton:hover { color: white }
QCheckBox:checked { color: white }
QCheckBox:!checked { color: red }
```
## Subcontrol 选择器
选择器::subcontrol 作为选择 Subcontrol 的选择器。
有些 widget 是由多个部分组合成的,例如 QCheckBox 由 icon(indicator) 和 text 组成,可以使用 选择器::subcontrol 来设置 subcontrol 的样式:
```
QCheckBox::indicator {
width: 20px;
height: 20px;
}
QCheckBox {
spacing: 8px;
}
```

View File

@@ -0,0 +1,102 @@
## 参考项目
https://blog.csdn.net/liang19890820/article/details/50557240
https://www.cnblogs.com/feiyangqingyun/p/3915657.html
QSS模板网站https://qss-stock.devsecstudio.com/
## 技巧
Qt内置图标封装在QStyle中大概七十多个图标可以直接拿来用。
- SP_TitleBarMenuButton,
- SP_TitleBarMinButton,
- SP_TitleBarMaxButton,
- SP_TitleBarCloseButton,
- SP_MessageBoxInformation,
- SP_MessageBoxWarning,
- SP_MessageBoxCritical,
- SP_MessageBoxQuestion,
## 配色
ElementUI配色https://element.eleme.cn/#/zh-CN/component/color
Brand Color `#409EFF`
**辅助色**
- Success#67C23A
- Warning#E6A23C
- Danger#F56C6C
- Info#909399
**基础色**
- 主要文字#303133
- 常规文字#606266
- 次要文字#909399
- 占位文字#C0C4CC
- 一级边框#DCDFE6
- 二级边框#E4E7ED
- 三级边框#EBEEF5
- 四级边框#F2F6FC
## 边框
- 实线 1px
- 虚线 2px
### 圆角
- 无圆角border-radius: 0px
- 小圆角border-radius: 2px
- 大圆角border-radius: 4px
- 圆形圆角border-radius: 30px
### 投影
- 基础投影 box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04)
- 浅色投影 box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1)
## QSS设置函数
feiyangqingyun-QWidgetDemo-master/FlatUI<br>
使用函数的方式来生成QSS代码与设置对应的控件。
```
QString BRUI::setPushButtonQss(QPushButton *btn, int radius, int padding,const QString &normalColor,const QString &normalTextColor,const QString &hoverColor,const QString &hoverTextColor,const QString &pressedColor,const QString &pressedTextColor)
{
QStringList list;
list.append(QString("QPushButton{border-style:none;padding:%1px;border-radius:%2px;color:%3;background:%4;}").arg(padding).arg(radius).arg(normalTextColor).arg(normalColor));
list.append(QString("QPushButton:hover{color:%1;background:%2;}").arg(hoverTextColor).arg(hoverColor));
list.append(QString("QPushButton:pressed{color:%1;background:%2;}").arg(pressedTextColor).arg(pressedColor));
QString qss = list.join("");
if(btn)
btn->setStyleSheet(qss);
return qss;
}
```
## 可以参考的UI工程
QSS生成工具StyleDemo。
给QWidget添加属性来控制样式
```
ui->widgetLeft->setProperty("nav", "left");
ui->widgetBottom->setProperty("form", "bottom");
ui->widgetTop->setProperty("nav", "top");
ui->widgetVideo->setProperty("video", true);
```
Qss
```
QWidget[nav="left"] QAbstractButton:checked,QWidget[nav="left"] QAbstractButton:pressed{
color:#386487;
border-style:solid;
border-width:0px 0px 0px 2px;
padding:4px 4px 4px 2px;
border-color:#00BB9E;
background-color:#EAF7FF;
}
```
### 切换器
ImageSwitch
### 日历
lunarcalendarwidget
### 导航按钮
NavButton
## StyleDemo中的CSS
flatwhite.css
lightblue.css
psblack.css

View File

@@ -0,0 +1,121 @@
## 前言
本人发现有关QML文件之间的通讯的资料不多而且都不太好找所以在这里总结一下方便后续的开发者
## 直接导入qml
目录结构:
```
myapp
|- mycomponents
|- CheckBox.qml
|- DialogBox.qml
|- Slider.qml
|- main
|- application.qml
```
那么可以:
```
import "../mycomponents"
DialogBox {
CheckBox {
// ...
}
Slider {
// ...
}
}
```
但是创建一个命名空间来导入会更好
```
import "../mycomponents" as MyComponents
MyComponents.DialogBox {
// ...
}
```
## qml文件间通讯
3.题回正传直接上代码以StackView管理页面为例
(1)page1.qml跳转到page2.qml传值
```
page1.qml
Rectangle
{
id:rect1
...
MouseArea {
id: maStartQuery
anchors.fill: parent
onClicked:
{
if(!stackView.busy)
stackView.push(Qt.resolvedUrl("qrc:///qml/page2.qml"),
{name:"张三"})//给page2.qml的name传值“张三”,name必须在page2.qml中定义成属性
}
}
...
}
page2.qml定义如下
Rectangle
{
id:rect2
...
property string name:""//要传的值
...
}
```
(2)page2.qml点击"确定"按钮时将结果返回给page1.qml
A.在page1.qml中增加一个函数clickedfunc当点击page2.qml中"确定"按钮时调用;
B.在page2.qml中增加一个属性containerqml用来记录page1.qml
C.在从page1.qml跳转到page2.qml时将rect1传给page2.qml的containerqml属性。
```
page1.qml
Rectangle
{
id:rect1
...
MouseArea {
id: maStartQuery
anchors.fill: parent
onClicked:
{
if(!stackView.busy)
stackView.push(Qt.resolvedUrl("qrc:///qml/page2.qml"),
{name:"张三",containerqml:rect1})
}
}
//当点击page2.qml中"确定"按钮时调用
  function clickedfunc(temp)
{
console.log("改成了:"+temp);
 stackView.pop();//返回到本页
}
...
}
page2.qml
Rectangle
{
id:rect2
...
property variant containerqml: null
property string name:""//要传的值
...
MouseArea {
id: btnOK
anchors.fill: parent
onClicked:
{
containerqml.clickedfunc("李四");//调用page1.qml中的函数实现了传返回值。
}
}
}
```
另一种就是信号与槽

View File

@@ -0,0 +1,163 @@
## color属性
可以使用"blue"、"#RRGGBB"、Qt.rgba()来赋值。具体可以参考QML Basic Type:color
## 手机的横屏模式与竖屏模式
需要修改AndroidManifest.xml中的activity元素的android:screenOrientation属性为"landscape"或"portrait"
## Item组件
1. Item是所有可视元素的基类。
2. 其中一个用处就是可以分组其他可见图元。
3. clip属性如果为true可以裁剪子组件。
4. 通过附加属性Keys来处理按键。详情Keys QML Type
## Text
可以使用HTML修饰过的文本
## Image
1. 如何设置了width与height图片可能会被拉伸此时fillMode属性就可以设置填充模式了。
2. Image默认是阻塞式加载可以通过把asynchronous设为true开启异步模式。可以先显示一个加载图标当status的值为Image.Ready时再显示。
3. source属性是url网络模式会默认开启异步加载此时Image的Progress0。0~1.0、status都会实时更新。
```
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Controls.Styles 1.4
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("App")
BusyIndicator{
id:busy;
running: true;
anchors.centerIn: parent;
z:2
}
Text{
id:stateLabel;
visible:false;
anchors.centerIn: parent;
z:3
}
Image{
id:imageViewer;
asynchronous: true;
cache:false;
anchors.fill: parent;
fillMode: Image.PreserveAspectFit;
onStatusChanged: {
if(imageViewer.status===Image.Loading){
busy.running=true;
stateLabel.visible=false;
}else if (imageViewer.status===Image.Ready){
busy.running=false;
}else if(imageViewer.status===Image.Error){
busy.running=false;
stateLabel.visible=true;
stateLabel.text="error";
}
}
}
Component.onCompleted: {
imageViewer.source="http://img.zcool.cn/community/01d881579dc3620000018c1b430c4b.JPG@3000w_1l_2o_100sh.jpg";
}
}
```
## Object类是所有ECMAScript类的基类它具有以下属性
1. constructor指向创建对象的函数对于Object类它指向object()函数。
2. prototype对该对象原型类型的引用可以在运行时改变原型。
3. hasOwnPropety是否拥有某个属性
4. isPrototypeOf判断对象是否为另一个对象的原型。
5. ptopertyIsEnumerable判断给定的杏树是否可以用for in进行枚举。
6. toString()
7. valueOf(),返回最适合该对象的原始值。
## Qt对象
Qt是QML提供的一个全局宿主对象整合了常用的属性、方法与枚举类型。
Qt.application 应用的全局状态
## Connections
适用对象:
1. 你需要将多个对象连接到同一个QML信号上。
2. 你需要在发出信号的对象的作用域之外来建立连接
3. c++导出对象
```
Connections{
target:area;//目标控件(它的信号触发)
on<Signal>:function or code block;//执行代码
}
```
## 连接信号的方式
```
Rectangle {
id: relay
signal messageReceived(string person, string notice)
//可以连接信号或者函数
Component.onCompleted: {
relay.messageReceived.connect(sendToPost)
relay.messageReceived.connect(sendToTelegraph)
relay.messageReceived.connect(sendToEmail)
relay.messageReceived("Tom", "Happy Birthday")
}
function sendToPost(person, notice) {
console.log("Sending to post: " + person + ", " + notice)
}
function sendToTelegraph(person, notice) {
console.log("Sending to telegraph: " + person + ", " + notice)
}
function sendToEmail(person, notice) {
console.log("Sending to email: " + person + ", " + notice)
}
}
```
## component
### 嵌入式组件:
```
Component {
id: redSquare
Rectangle {
color: "red"
width: 10
height: 10
}
}
```
只能包含一个顶层item与id<br>
### 单文件组件
单文件组件不需要加Component
#### 使用Loader
```
Item {
Component {
id: redSquare
Rectangle { color: "red"; width: 10; height: 10 }
}
Loader { sourceComponent: redSquare }
Loader { sourceComponent: redSquare; x: 10 }
}
```
通过sourceComponent加载组件source则可以通过url加载单文件组件。<br>
在加载完成后(onLoaded)后可以通过Loader的item操作已经加载的组件<br>
如果Loader加载的Item想要处理按键事件那么久必须将Loader对象的focus属性设置为true同时也需要对它加载的Item的accepted属性设置为true以免已经被吃掉的事件再传递给Loader。
### ECMAScript中动态创建对象
```
var newObject = Qt.createQmlObject('import QtQuick 2.0; Rectangle {color: "red"; width: 20; height: 20}',
parentItem,
"dynamicSnippet1");
```
或者
```
var component = Qt.createComponent("Button.qml");
if (component.status == Component.Ready)
component.createObject(parent, {"x": 100, "y": 100});
```

View File

@@ -0,0 +1,113 @@
## 前言
最近在为单位做一个简单的手机App基于Qt技术栈的选择了QtQuick来开发。不得不说QtQucik开发的确舒服很多东西都不用写就可以只用UI定义起来也比较自由。但是本人想通过cookie来作为登陆验证时就发现QtQuick实现起来相当麻烦。主要是没有文档资料只找到一篇qyvlik写的。我也不想直接用WebEngine
## 不带cookie
可以使用XMLHttpRequest比较坑的是官方竟然没有任何案例不过Api都是与js的XmlHttpRequest一样的以下是qyvlik封装的一套分辨操作函数
```
//通过Json对象输出url的query字符串
function urlQuery(jsonObject) {
var query = "";
var i = 0;
for(var iter in jsonObject) {
if(i > 0) {
query += "&";
}
query += iter +"=" + encodeURI(jsonObject[iter]);
i++;
}
// console.log("url query:", query);
return query;
}
//设置头
function setHeader(xhr, headers) {
//"Content-Type":"application/x-www-form-urlencoded"
for(var iter in headers) {
xhr.setRequestHeader(iter, headers[iter]);
}
}
//这里我修改了一下函数的形参从使用的角度来看回调函数一般都会有但是headers不一定要设置所以调换了一下位置
function ajax(method, url, callable,headers,data) {
headers = headers || {};
callable = callable || function(xhr) {
console.log("没有设置callable使用默认log函数")
console.log(xhr.status);
console.log(xhr.responseText);
}
var xhr = new XMLHttpRequest;
xhr.onreadystatechange = function() {
if(xhr.readyState == xhr.DONE) {
callable(xhr);
}
}
xhr.open(method, url);
setHeader(xhr, headers);
if("GET" === method) {
xhr.send();
} else {
xhr.send(data);
}
}
```
为了能够重复利用本人将这些代码都放入一个js文件中之后使用导入的方式重复利用。注意导入的命名控件首字母需要大写
```
import "xmlhttprequest.js" as XmlHttpRequest
```
使用:
```
var jsonObject={user:"admin",password:Qt.md5("123")};
XmlHttpRequest.ajax("GET","http://192.168.3.108:3000/landing"+"?"+XmlHttpRequest.urlQuery(jsonObject),function(xhr){
console.log(xhr.status);
console.log(xhr.responseText);
if(JSON.parse(xhr.responseText).message==="ok") {
stack.push("qrc:/resource/qml/listview.qml",{stack:stack,uifont:uifont});
}else{
message.show("用户名或者密码错误!",2000);
}
});
```
## 带cookie
因为XmlHttpRequest是不能带有cookie的所以只能通过c++导出QNetworkAccessManager、QNetworkRequest、QNetworkReply具体的代码可以参考https://github.com/qyvlik/QmlNetwork。qyvlik封装了一套QML对象我直接拿来用了很可惜他的使用说明写的不太详细有一些操作需要直接看他写的微信案例才能搞定。
```
NetworkAccessManager { id: manager }
NetworkResponse { id: response }
NetworkRequest { id: request }
Buffer { id: buffer }
function initWebWeiXinInfo() {
var url = "http://192.168.3.108:3000/landing";
var data = {
user:"admin",
password:"123"
};
if(buffer.isOpen()) {
buffer.close();
}
buffer.data = JSON.stringify(data);
if(!buffer.open(IODevice.ReadOnly)) {
console.log(buffer.errorString());
}
request.clear();
request.url = url;
// request.setHeader("Cookie", cookie);
request.setHeader("Content-Type", "application/json")
request.ioDevice = buffer;
connectSignalOnce(response.finished,function() {
console.log("data:", buffer.data)
var headers = response.getAllResponseHeaders();
for(var iter in headers) {
console.log(headers[iter]);
}
console.log(response.responseContent);
});
manager.post(request, response);
}
```
## 结语
感觉qyvlik封装的东西比较多需求上本人也就用用Get与Post以及cookie以后有时间会重新封装一个简单版本。

View File

@@ -0,0 +1,118 @@
## 自定义控件样式
请在Qt帮助索引中输入Customizing a Control进行查看<br>
不过实际用下来感觉除非你想自己实现一套效果复杂的UI或是创造一个全新控件比如给UI添加模糊、虚化等ShaderEffect效果。不然不推荐用这个。比如本人就是想把CheckBox的大小改小同时不改变显示样式这个就很难办到。<br>
## 系统自带的几种主题风格
1. Default Style
2. Fusion Style
3. Imagine Style
4. Material Style
5. Universal Style
其中Imagine Style是使用图片定制风格图片需要按照指定的命名来放置具体操作请看文档
http://doc.qt.io/qt-5/qtquickcontrols2-imagine.html
### 在c++中使用QQuickStyle
```
QQuickStyle::setStyle("Material");
```
具体内容请在帮助索引中搜索 QQuickStyle
### 命令行中设置
```
./app -style material
```
### 在Qt的环境变量中设置
```
QT_QUICK_CONTROLS_STYLE=universal ./app
```
### 使用配置文件
```
[Controls]
Style=Material
```
官方的gallery案例用的是这种。
网上有个哥们用的是:
```
if (sty == "mat") {
qputenv("QT_QUICK_CONTROLS_CONF", ":/qtquickcontrols2material.conf");
} else {
qputenv("QT_QUICK_CONTROLS_CONF", ":/qtquickcontrols2universal.conf");
}
```
本人用的是gallery案例中的方式感觉通过设置环境变量来指定对应的conf不太灵活所以上述方式仅供参考。<br>
想要看懂conf文件需要看以下两篇文档
## Qt Quick Controls 2 Configuration File
文档http://doc.qt.io/qt-5/qtquickcontrols2-configuration.html
在默认情况下将文件放置于:/qtquickcontrols2.conf也就是根目录就会生效需要设置QQuickStyle
### Controls Section
Style定义全局控件样式
### XXXX Section
对对应的style进行设置
### Font Configuration
设置字体,有以下几个属性:
1. Family
2. PointSize
3. PixelSize
4. StyleHint
5. Weight
6. Style
### Palette Configuration
Palette我不太清楚是干什么的
## Material Style
文档http://doc.qt.io/qt-5/qtquickcontrols2-material.html#material-theme-attached-prop<br>
你可以单独给某一些控件设置style,以下是对应的属性:
### accent
```
Button {
text: qsTr("Button")
highlighted: true
Material.accent: Material.Orange
}
```
### background
```
Button {
text: qsTr("Button")
highlighted: true
Material.background: Material.Teal
}
```
### elevation
控制阴影的属性
```
Pane {
width: 120
height: 120
Material.elevation: 6
Label {
text: qsTr("I'm a card!")
anchors.centerIn: parent
}
}
```
### foreground
```
Button {
text: qsTr("Button")
Material.foreground: Material.Pink
}
```
### primary
### theme
三个可选项:
1. Material.Light
2. Material.Dark
3. Material.System
```
Pane {
Material.theme: Material.Dark
Button {
text: qsTr("Button")
}
}
```
## 自定义主题
文档http://doc.qt.io/qt-5/qtquickcontrols2-customize.html#creating-a-custom-style
推荐http://www.cnblogs.com/Fuss/archive/2015/03/20/4353698.html<br>
代码没怎么看Control用的是1.0可以作为参考Github上有关这种UI定制的代码还是比较多建议先去知乎搜索 “请问有哪些优质又开源的qml应用”

View File

@@ -0,0 +1,291 @@
## 参考网址
https://blog.csdn.net/wsj18808050/article/details/54234956
Qt获取屏幕物理长度和宽度CM
https://blog.csdn.net/wsj18808050/article/details/54345537
Qt5.8新特新QtLite使用方法以及缩减应用体积的效果
https://blog.csdn.net/wsj18808050/article/details/55808104
QML 中的屏幕适配问题
https://blog.csdn.net/qyvlik/article/details/51241425
QT59HTTP POST GET COOKIE 网络编程
```c++
#include <QNetworkCookie> //单个cookie
#include <QNetworkCookieJar> //储存cookie
```
https://blog.csdn.net/qq_16234613/article/details/53783391
qt 获取部分的cookie信息 如何把获取的cookie转换为QString类型 正则表达式
https://blog.csdn.net/qq_22403265/article/details/51333226
### cookiebrowser使用webview载入cookie
```
m_store = m_webview->page()->profile()->cookieStore();
m_store->loadAllCookies();
```
### QML Settings 小的示例
Setting 可以存储一些变量就想配置文件一样
https://www.cnblogs.com/hbrw/p/6744094.html
### 直接导入qml
Importing QML Document Directories
目录结构:
```
myapp
|- mycomponents
|- CheckBox.qml
|- DialogBox.qml
|- Slider.qml
|- main
|- application.qml
```
那么可以:
```
import "../mycomponents"
DialogBox {
CheckBox {
// ...
}
Slider {
// ...
}
}
```
但是创建一个命名空间来导入会更好
```
import "../mycomponents" as MyComponents
MyComponents.DialogBox {
// ...
}
```
### qml文件间通讯
3.题回正传直接上代码以StackView管理页面为例
(1)page1.qml跳转到page2.qml传值
```
page1.qml
Rectangle
{
id:rect1
...
MouseArea {
id: maStartQuery
anchors.fill: parent
onClicked:
{
if(!stackView.busy)
stackView.push(Qt.resolvedUrl("qrc:///qml/page2.qml"),
{name:"张三"})//给page2.qml的name传值“张三”,name必须在page2.qml中定义成属性
}
}
...
}
page2.qml定义如下
Rectangle
{
id:rect2
...
property string name:""//要传的值
...
}
```
(2)page2.qml点击"确定"按钮时将结果返回给page1.qml
A.在page1.qml中增加一个函数clickedfunc当点击page2.qml中"确定"按钮时调用;
B.在page2.qml中增加一个属性containerqml用来记录page1.qml
C.在从page1.qml跳转到page2.qml时将rect1传给page2.qml的containerqml属性。
```
page1.qml
Rectangle
{
id:rect1
...
MouseArea {
id: maStartQuery
anchors.fill: parent
onClicked:
{
if(!stackView.busy)
stackView.push(Qt.resolvedUrl("qrc:///qml/page2.qml"),
{name:"张三",containerqml:rect1})
}
}
//当点击page2.qml中"确定"按钮时调用
  function clickedfunc(temp)
{
console.log("改成了:"+temp);
 stackView.pop();//返回到本页
}
...
}
page2.qml
Rectangle
{
id:rect2
...
property variant containerqml: null
property string name:""//要传的值
...
MouseArea {
id: btnOK
anchors.fill: parent
onClicked:
{
containerqml.clickedfunc("李四");//调用page1.qml中的函数实现了传返回值。
}
}
}
```
另一种就是信号与槽
### QtQuick 母版页
```
//~ Panel.qml
Item {
property alias headerHeight: headerLoader.height
property alias footerHeight: footerLoader.height
property Component headerComponent: null
readonly property Item headerItem: headerLoader.item
Loader {
id: headerLoader
width: parent.width
height: 40
sourceComponent: headerComponent
Binding {
target: headerLoader.item
property: "anchors.fill"
value: headerLoader
}
}
property Component footerComponent: null
readonly property Item footerItem: footerLoader.item
Loader {
id: footerLoader
width: parent.width
height: 40
anchors.bottom: parent.bottom
Binding {
target: footerLoader.item
property: "anchors.fill"
value: footerLoader
}
}
property Component contentComponent: null
readonly property Item contentItem: contentLoader.item
Loader {
id: contentLoader
width: parent.width
anchors.top: headerLoader.bottom
anchors.bottom: footerLoader.top;
Binding {
target: contentLoader.item
property: "anchors.fill"
value: contentLoader
}
}
}
Panel {
headerComponent: Rectangle {
color: "black"
}
footerComponent: Rectangle {
color: "black"
}
contentComponent: ListView {
delegate: Rectangle { width: parent.width; height: 40; color: "green" }
model: 10
}
}
```
### 全局单例模式
1. 入口文件的 id 和属性
2. 静态 JavaScript 文件
3. qml 单例QML 实现
4. qml 单例c++ 实现
5. 注册上下文属性
### 表单提交不包括cookies
```
function urlQuery(jsonObject) {
var query = "";
var i = 0;
for(var iter in jsonObject) {
if(i > 0) {
query += "&";
}
query += iter +"=" + encodeURI(jsonObject[iter]);
i++;
}
// console.log("url query:", query);
return query;
}
```
```
function setHeader(xhr, headers) {
//"Content-Type":"application/x-www-form-urlencoded"
for(var iter in headers) {
xhr.setRequestHeader(iter, headers[iter]);
}
}
function ajax(method, url, headers, data, callable) {
headers = headers || {};
callable = callable || function(xhr) {
console.log(xhr.responseText);
}
var xhr = new XMLHttpRequest;
xhr.onreadystatechange = function() {
if(xhr.readyState == xhr.DONE) {
callable(xhr);
}
}
xhr.open(method, url);
setHeader(xhr, headers);
if("GET" === method) {
xhr.send();
} else {
xhr.send(data);
}
}
```
如果带有指定名称的头部已经被指定了,这个头部的新值就是:之前指定的值,加上逗号、空白以及这个调用指定的值。
如果 open() 调用指定了认证资格XMLHttpRequest 自动发送一个适当的 Authorization 请求头部。但是,你可以使用 setRequestHeader() 来添加这个头部。类似地,如果 Web 服务器已经保存了和传递给 open() 的 URL 相关联的 cookie适当的 Cookie 或 Cookie2 头部也自动地包含到请求中。可以通过调用 setRequestHeader() 来把这些 cookie 添加到头部。XMLHttpRequest 也可以为 User-Agent 头部提供一个默认值。如果它这么做,你为该头部指定的任何值都会添加到这个默认值后面。
有些请求头部由 XMLHttpRequest 自动设置而不是由这个方法设置,以符合 HTTP 协议。这包括如下和代理相关的头部:
Host
Connection
Keep-Alive
Accept-charset
Accept-Encoding
If-Modified-Since
If-None-Match
If-Range
Range
### XMLHttpRequest设置cookie的问题
https://segmentfault.com/a/1190000004322487
### 如何获取指定objectName的QObject

View File

@@ -0,0 +1,23 @@
## 信号与槽
信号与槽都可以在qml中访问
### Q_INVOKABLE宏
在定义一个类的成员函数时使用Q_INVOKABLE宏来修饰就可以让该方法被元对象系统调用。也就是注册到元对象系统中
例如:
```
Q_INVOKABLE void setAlgorithm(GenerateAlgorithm algorithm);
```
### Q_ENUMS宏
使用Q_ENUMS(枚举名)的方式来注册枚举类型
### Q_PROPERTY宏
Q_PROPERTY宏用来定义可通过元对象系统访问的属性通过它定义的属性可以在QML中访问、修改也可以通过在属性变化时发射特定信号。
### 注册一个QML可用类型
1. 实现c++类
2. 注册QML类型
3. 在QML中导入类型
4. 在QML中创建由c++导出的类型的实例并使用
#### 注册QML类型
1. qmlRegisterSingletonType() 用来注册单例类型
2. qmlRegisterType() 注册非单例类型
3. qmlRegisterTypeNotAvailable() 注册一个类型用来占位
4. qmlRegisterUncreateableType() 注册具有附加属性的附加类型

View File

@@ -0,0 +1,24 @@
## subdirs
新建项目-其他项目-子目录项目。
### 手动填在已有项目
直接在
```
TEMPLATE = subdirs
SUBDIRS += \
AssetEncrypt \
OutlineTool \
TechnicalSupport
```
### 编译顺序
不推荐使用顺序构建命令,因为这会损失多线程构建的性能优势。官方推荐使用编译依赖:
```
# 创建编译依赖以控制编译顺序
TechnicalSupport.depends = AssetEncrypt
TechnicalSupport.depends = OutlineTool
```
## pri
用于管理公共common pri在Pro使用include(****.pri)引入。

103
07-Other/Qt/Qt总览.md Normal file
View File

@@ -0,0 +1,103 @@
## Qt大佬blog
专栏《Qt 实战一二三》
http://blog.csdn.net/column/details/qshare.html
qyvlik
https://blog.csdn.net/qyvlik/
## QT自定义精美换肤界面
http://www.cnblogs.com/feiyangqingyun/p/3915657.html
Qt之自定义界面窗体缩放-跨平台终极版)
http://blog.csdn.net/liang19890820/article/details/50557240
## QTableView Model
QTabelView根据一行记录中的内容自动调整列宽度
http://bbs.csdn.net/topics/390639311
Qt Model/View 学习笔记 (七)
http://www.cppblog.com/yuanyajie/archive/2007/06/19/26641.html
QT在QTableView中使用各种自定义委托
http://www.linuxidc.com/Linux/2012-07/66820.htm
QT中Qtableview视图表格中点击表头进行排序
http://www.cnblogs.com/googly/p/4584264.html
Qt之模型/视图(实时更新数据)
http://blog.sina.com.cn/s/blog_a6fb6cc90101hhse.html
## Qt读取excel文件
### 可以用QSqlDatabase::addDatabase("QODBC"),这个挺不错
```
QStringList referList;
QSqlDatabase db = QSqlDatabase::addDatabase("QODBC");
db.setDatabaseName("DRIVER={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)};DBQ=" + dir.filePath("test.xlsx"));
if(db.open())
{
QSqlQuery query("select * from [Sheet1$A:A]",db); // Select range, place A1:B5 after $
while (query.next())
{
QString dataStr= query.value(0).toString();
if(dataStr != "0")
{
referList << dataStr;
}
}
}
```
## 移植QT5.6到嵌入式开发板
http://blog.csdn.net/lizuobin2/article/details/52673494
## 判断release与debug
```
#ifdef QT_NO_DEBUG
qDebug() << "release mode";
#else
qDebug() << "debug mode";
#endif
```
## [Qt]新增UAC功能之requireAdministrator
https://blog.csdn.net/luols/article/details/49996369
https://blog.csdn.net/aqtata/article/details/17222691
## 如何把一个Qt项目拆成多个Qt子项目pro、pri使用
https://blog.csdn.net/lee353086/article/details/70808057
Qt Creator管理多个项目
https://blog.csdn.net/csxiaoshui/article/details/44102873
## 在Qt程序退出前时执行函数
https://stackoverflow.com/questions/8165487/how-to-do-cleaning-up-on-exit-in-qt
## Qt自定义委托在QTableView中绘制控件、图片、文字
http://blog.csdn.net/zhi379/article/details/28412189
## 加密你的SQLite
http://foggry.com/blog/2014/05/19/jia-mi-ni-de-sqlite/
## QML分辨率适配
QML 中的屏幕适配问题
http://blog.csdn.net/qyvlik/article/details/51241425
QML怎么适配不同的设备
http://blog.csdn.net/zhx6044/article/details/44180819
## QGraphicsView
QGraphicsView 框架学习(一)、图形元素的编辑
https://blog.csdn.net/wishfly/article/details/77817091?locationNum=3&fps=1
Qt基础——获取QGraphicsScene的缩略图即导出到图片
https://blog.csdn.net/lcl_data/article/details/8731892
Qt绘图之QGraphicsScene QGraphicsView QGraphicsItem详解
https://www.cnblogs.com/cy568searchx/p/3502242.html
Qt 之图形视图框架
https://blog.csdn.net/liang19890820/article/details/51966791
QGraphicsView 框架学习(一)
https://blog.csdn.net/firebolt2002/article/details/46583589
基于Qt QGraphicsView的多点触摸绘图
https://www.cnblogs.com/visonme/p/5435330.html
QGraphicsScene 管理 QGraphicsItem单击/选择/移动/缩放/删除)
https://blog.csdn.net/liang19890820/article/details/53504323

View File

@@ -0,0 +1,70 @@
## 前言
因为Sqlite的源代码中只提供了Sqlite3_key()的接口没有实现。所以Qt中的Sqlite没有密码功能。于是我找了一下资料并且总结一下思路。现成的方法在最后。
## SQLite历代版本与下载
所有ReleaseTag:https://www.sqlite.org/cgi/src/taglist
SQLite不提供明确的下载地址所以地址需要开发者去猜……也就是通过最新的地址以及你所需要的版本号去推
例如:
- sqlite-amalgamation-3240000https://www.sqlite.org/2018/sqlite-amalgamation-3240000.zip
- sqlite-dll-win32-x86-3240000.ziphttps://www.sqlite.org/2018/sqlite-dll-win32-x86-3240000.zip
- sqlite-dll-win64-x64-3240000.ziphttps://www.sqlite.org/2018/sqlite-dll-win64-x64-3240000.zip
- sqlite-amalgamation-3270200.ziphttps://www.sqlite.org/2019/sqlite-amalgamation-3270200.zip
- sqlite-dll-win64-x64-3270200.ziphttps://www.sqlite.org/2019/sqlite-dll-win64-x64-3270200.zip
注意:
- 年份
- 版本号的小数点位在百位上
## 解决思路与大致过程
编写一个QSQLDriver Plugins并且实现Sqlite3_key()。
### 大致步骤
- 在QtCreator中文件-新建文件或者项目-Library-c++Library来创建QSQLDriver Plugins库类型选择Qt Plugin
- 下载Qt源代码并将\qtbase\src\plugins\sqldrivers\sqlite下的qsql_sqlite_p.h与qsql_sqlite.cpp文件复制到新建的插件目录下。
- 修改2个文件中类名与创建插件的类名一致。并且修改open()函数(具体的请看参考资料)。
- 下载sqlite源代码并且实现Sqlite3_key()注意该函数被宏设置为不编译。可以通过在项目中设置来解决也可以在头文件中直接设置宏为1来解决。
- Sqlite3_key()的实现方法可以参考wxsqlite3或CipherSqlite。
## 现成的解决方法
国人编写的插件QtCipherSqlitePlugin。经测试5.14.2 MSVC2017 x64可以使用。使用起来很方便直接用Qt打开sqlitecipher文件夹中的工程直接切成release模式编译即可。
作者推荐的方法是将编译出lib与dll都放入源代码中的不过我个人还是喜欢使用在项目中直接加载Plugin的方式来实现。这样可以方便后续的项目迁移与后续维护。大致代码如下
```
QPluginLoader driverload(qApp->applicationDirPath()+"/plugin/sqldrivers/sqlitecipher.dll");
if(driverload.load())
{
QSqlDriverPlugin *plugin=qobject_cast<QSqlDriverPlugin*>(driverload.instance());
if(plugin)
{
QSqlDriver *driver=plugin->create("SQLITECIPHER");
QSqlDatabase db;
db=QSqlDatabase::addDatabase(driver);
db.setDatabaseName("mydatabase.db");
db.setPassword("123456");
if(db.open())
{
QSqlQuery qry(db);
qry.exec("create table t_trade(order_id varchar(100))");
qry.exec("insert into t_trade(order_id) values('10001')");
qry.exec("insert into t_trade(order_id) values('10002')");
qry.exec("select * from t_trade");
while(qry.next())
{
qDebug()<<qry.value(0).toString();
}
}
}
}
```
PS.debug与release的dll是不通用的所以需要编译两份dll并且载入时进行判断
- QtCipherSqlitePlugin地址
https://github.com/devbean/QtCipherSqlitePlugin
- 编译方法https://github.com/devbean/QtCipherSqlitePlugin/wiki/How-to-compile
- 使用方法https://github.com/devbean/QtCipherSqlitePlugin/wiki/How-to-use
## 参考资料
- https://www.devbean.net/2012/07/qt-sqlite-plugin-with-encryption/
- https://www.cnblogs.com/WushiShengFei/p/9707244.html

View File

@@ -0,0 +1,163 @@
## 前言
最近有个pdf的需求Qt竟然没有显示pdf的api着实令人郁闷。之后我尝试用了poppler但是光配置编译工程就相当麻烦了没有cmake等开源项目编译经验的人完全一脸懵逼。PDFium也是同理手头上没有vpn也无法尝试。感觉Mupdf编译器起来比较简单所以就来用了一下。
本人使用的版本是Mupdf1.12.0+Qt5.9.3+vs2015
## 下载Mupdf库
https://mupdf.com/downloads/
## 编译Mupdf
在mupdf-1.12.0-source\platform\win32目录下就有现成的mupdf.sln。
这里需要注意:这个工程默认使用的是/MT而Qt MSVC默认用的是/MD,所以需要修改编译工程设置。我们这里只需要在编译工程中修改就可以了。
以下是一些有关QMake中设置运行库属性 /md /md /mt /mtd 的相关参考
- http://blog.csdn.net/caoshangpa/article/details/51416077
- http://www.cnblogs.com/codingmylife/archive/2010/05/08/1730832.html
- http://www.voidcn.com/article/p-hhosrsia-hq.html
工程里默认生成的是都是静态库请注意根据测试需要的分别是libmupdf.lib、libresources.lib、libthirdparty.lib这三个库只使用了docs\examples\example.c的代码使用别的函数可能需要再编译别的工程
分别修改解决方案中的libmupdf、libthirdparty、libresources这三个工程将debug下改成/MDdrelease下改成/MD编译即可得到这3个文件。其中libresources只有release版本的所以debug模式下我也引用这个文件。
## 在Qt工程中引入Mupdf静态库
- 引入lib文件
- 新建一个工程在工程的图标上右键——添加库——外部库。平台只勾选windows链接选择静态之后选择对应的库就可以了。本人将debug与release编译的分别放在debug与release文件夹中
- 添加包含目录
- 将Mupdf目录中的include复制到工程目录下本人又新建了一个mupdf将所有文件都放在里面了
- 本人是这么写的,具体可以参考源代码:
- `INCLUDEPATH += $$PWD/mupdf/include/`
之后运行QMake。
## 编写代码进行测试
```c++
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QDebug>
#include <QImage>
#include <QPixmap>
#include <QLabel>
#include "mupdf/fitz.h"
#include "mupdf/pdf.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
char *input = const_cast< char* >("document.pdf");
float zoom, rotate;
int page_number, page_count;
fz_context *ctx;
fz_document *doc;
fz_pixmap *pix;
fz_matrix ctm;
int x, y;
//第一页为0
page_number=1;
//100%缩放比
zoom=100;
//旋转为0
rotate=0;
//创建上下文
ctx = fz_new_context(NULL, NULL, FZ_STORE_UNLIMITED);
if (!ctx)
{
qDebug()<<stderr<<"cannot create mupdf context";
return;
}
//注册文档控制
fz_try(ctx)
fz_register_document_handlers(ctx);
fz_catch(ctx)
{
qDebug()<<stderr<<"cannot register document handlers:"<< fz_caught_message(ctx);
fz_drop_context(ctx);
return;
}
//打开文档
fz_try(ctx)
doc = fz_open_document(ctx, input);
fz_catch(ctx)
{
qDebug()<<stderr<< "cannot open document:"<< fz_caught_message(ctx);
fz_drop_context(ctx);
return;
}
//取得总的页数
fz_try(ctx)
page_count = fz_count_pages(ctx, doc);
fz_catch(ctx)
{
qDebug()<<stderr<< "cannot count number of pages:"<< fz_caught_message(ctx);
fz_drop_document(ctx, doc);
fz_drop_context(ctx);
return;
}
if (page_number < 0 || page_number >= page_count)
{
qDebug()<<stderr<< "page number out of range: "<< page_number + 1<<"page count:"<<page_count;
fz_drop_document(ctx, doc);
fz_drop_context(ctx);
return;
}
//计算缩放以及旋转
fz_scale(&ctm, zoom / 100, zoom / 100);
fz_pre_rotate(&ctm, rotate);
//渲染pixmap
fz_try(ctx)
pix = fz_new_pixmap_from_page_number(ctx, doc, page_number, &ctm, fz_device_rgb(ctx), 0);
fz_catch(ctx)
{
qDebug()<<stderr<< "cannot render page: %s\n"<< fz_caught_message(ctx);
fz_drop_document(ctx, doc);
fz_drop_context(ctx);
return;
}
//渲染成图片
// unsigned char *samples = fz_pixmap_samples(ctx, pix);
unsigned char *samples = pix->samples;
int width = fz_pixmap_width(ctx, pix);
int height = fz_pixmap_height(ctx, pix);
QImage image(samples, width, height,QImage::Format_RGB888);
QLabel *label=new QLabel;
label->setPixmap(QPixmap::fromImage(image));
ui->layout->addWidget(label);
// if (!image.save("a.png")) {
// return;
// }
//回收内存
fz_drop_pixmap(ctx, pix);
fz_drop_document(ctx, doc);
fz_drop_context(ctx);
}
Widget::~Widget()
{
delete ui;
}
```
## 参考代码 
https://github.com/blueroseslol/QtMupdf
找到一个之前有人封装的库不过经过测试是无法成功编译的不过可以参考一下mupdf库的用法。
https://github.com/xiangxw/mupdf-qt
别的参考:
http://blog.csdn.net/chenyijun/article/details/42582977

View File

@@ -0,0 +1,44 @@
## Shader
- RWStructuredBuffer:可读写可用类型float1234、uint1234
- StructuredBuffer:对应的只读BUffer
## C#脚本
### 声明变量
CS与Buffer
```c#
[SerializeField]
ComputeShader computeShader = default;
ComputeBuffer positionBuffer;
```
### 绑定CS中的变量并且执行
声明Shader中变量id以用于绑定
```c#
static readonly int intpositionsId = Shader.PropertyToID("_Positions"),
resolutionId= Shader.PropertyToID("_Resolution"),
stepId= Shader.PropertyToID("_Step"),
timeId= Shader.PropertyToID("_Time");
```
给变量绑定数值
```c#
computeShader.SetInt(resolutionId, resolution);
computeShader.SetFloat(stepId, step);
computeShader.SetFloat(timeId, Time.time);
computeShader.SetBuffer(0, positionsId, positionBuffer);
```
执行
```c#
int groups = Mathf.CeilToInt(resolution / 8f);
computeShader.Dispatch(0, groups, groups, 1);
```
### 绘制GPU Instance命令
```c#
//需要传入边界盒以及绘制数量
var bounds=new Bounds(Vector3.zero,Vector3.one*(2f+2f/resolution));
Graphics.DrawMeshInstancedProcedural(mesh, 0, material,bounds,positionBuffer.count);
```
### 多kernel
CS可能拥有多个内核函数此时computeShader的SetBuffer()与Dispatch()可以通过第一个形参来设置kernel index。

View File

@@ -0,0 +1,234 @@
## 纯Shader
- Uniform代表该变量在顶点与片元着色器中值都是相同的。
- fixed:低精度数字它们以精度来换取移动设备上的速度。在台式机上fixed只是float的别名。
## 内置库
- UnityShaderVariables.cginc定义了渲染所需的一堆着色器变量例如变换相机和光照数据。这些都在需要时由Unity设置。
- HLSLSupport.cginc进行了设置因此无论代码针对的是哪个平台都可以使用相同的代码进行编写。无需担心使用特定于平台的数据类型等。
- UnityInstancing.cginc专门用于实例化支持这是一种减少绘制调用的特定渲染技术。尽管它不直接包含文件但依赖于UnityShaderVariables。
## 关键字
### PropertyType
- Int
- Float
- Range
- Color
- Vector
- 2D texture
- Cube texture
- 3D texture
### SubShader
针对不同性能的显卡适配不同的Shader。
```
SubShader{
[Tags]
[RenderSetup]
Pass{
}
}
```
### Fallback关键字
Fallback关键词用于处理匹配失败的情况指定一个用于处理这个情况的Pass或者直接跳过。
>FallBack Off
### CGPROGRAM与ENDCG
里面编写这里编写HLSL/CG对于VertexShader与PixelShader则写在SubShader的Pass关键字的{}中。
## 内置变量
### 矩阵
- UNITY_MATRIX_MVP
- UNITY_MATRIX_MV
- UNITY_MATRIX_P
- UNITY_MATRIX_VP
- UNITY_MATRIX_T_MV
- UNITY_MATRIX_IT_MV
- _Object2World
- _World2Object
- unity_WorldToObject
### 摄像机
- WorldSpaceCameraPos
- ProjectionParams
- ScreenParams
- ZBufferParams
- unity_OrthoParams
- unity_CameraProjection
- unity_CameraInvProjection
- unity_CameraWorldClipPlanes[6]
### 灯光
- UNITY_LIGHTMODEL_AMBIENT
- _WorldSpaceLightPos0
## 定义VS与PS名称
```c++
#pragma vertex vert
#pragma fragment frag
```
## UnityCG.cginc
### 常用结构体
- appdata_base float4 vertex : POSITION; float3 normal : NORMAL; float4 texcoord: TEXCOORD0;
- appdata_tan float4 vertex : POSITION; float4 tangent : TANGENT; float3 normal : NORMAL; float4 texcoord : TEXCOORD0;
- appdata_full float4 vertex : POSITION; float4 tangent : TANGENT; float3 normal : NORMAL; float4 texcoord : TEXCOORD0; float4 texcoord1 : TEXCOORD1; float4 texcoord2 : TEXCOORD2; float4 texcoord3 : TEXCOORD3; # if defined(SHADER_API_XBOX360) half4 texcoord4 : TEXCOORD4; half4 texcoord5 : TEXCOORD5; # endif fixed4 color : COLOR;
- appdata_img float4 vertex : POSITION; half2 texcoord : TEXCOORD0;
- v2f_img 裁剪空间中的位置、纹理坐标
### 常用函数
- float4 WorldSpaceViewDir(float4 v)输入一个模型空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向
- float4 UnityWorldSpaceViewDir(float4 v)输入一个世界空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向
- float4 ObjSpaceViewDir(float4 v)输入一个模型空间中的顶点位置,返回模型空间中从该店到摄像机的观察方向
- float4 WorldSpaceLightDir(flaot4 v)仅用于向前渲染。 输入一个模型空间中的顶点位置,返回世界空间中从该点到光源的光照方向。没有被归一化
- float4 ObjectSpaceLightDir(float4 v)仅用于向前渲染中,输入一个模型空间中的顶点位置, 返回模型空间中从该点到光源的光照方向。没有被归一化
- float4 UnityWorldSpaceLightDir(float4 v)仅用于向前渲染中,输入一个世界空间中的顶点位置, 返回世界空间中从该点到光源的光照方向。没有被归一化
- float3 UnityObjectToWorldNormal(float3 norm)把法线方向从模型空间中转换到世界空间中
- float3 UnityObjectToWorldDir(float3 dir)把方向矢量从模型空间中变换到世界空间中
- float3 Unity WorldToObjectDir(float3 dir)把方向矢量从世界空间变换到模型空间中
## 浮点格式
float32位
half16位 -60000~+60000
fixed11位 -2.0~+2.0
## 贴图
`Sampler2D _MainTex`使用类似`float4 _MainTex_ST`作为缩放与位移。
## 管线LightMode
定义在Pass内的Tag{}中。
- Always总是渲染但不计算任何光照。
- ForwardBase用于前向渲染该Pass会计算环境光、平行光、逐顶点SH与LightMap。
- ForwardAdd用于前向渲染该Pass会计算额外的逐像素光源每个Pass对应一个光源。
- Deferred用于延迟渲染该Pass会计算GBuffer。
- ShadowCaster把物体的深度信息渲染ShadowMap或是一张深度纹理中。
- PrepassBase用于遗留的延迟渲染该Pass会渲染法线和高光反射的指数部分。
- PrepassFinal用于遗留的延迟渲染该Pass通过合并纹理、光照和自发光来渲染得到最终的颜色。
- Vertex、VertexLMRGBM、VertexLM用于遗留的顶点光照渲染。
## 不透明物体的渲染顺序
1. 先渲染所有不透明物体,并开启深度测试与深度写入
2. 把半透明物体按它距离摄像机的远近进行排序 ,然后按照从后往前的顺序渲染这些半透明物体,并开启它们的深度测试,但关闭深度写入。
### Unity3d的解决方案
定义了5个渲染队列
- Backgrouond
- Geometry
- AlphaTest
- Transparent
- Overlay
可以在Tags定义队列
```
SubShader{
Tags{"Queue"="AlphaTest"}
Pass{
ZWrite Off
}
}
```
### Blend相关命令
BlendOff
BlendSrcFactorDstFactor
BlendSrcFactorDstFactor,SrcFactorA DstFactorA
BlendOp BlendOperation
### 混合操作
1. Add
2. Sub
3. RevSub
4. Min
5. Max
### 常见的混合类型
1. Blend SrcAlpha OneMinusSrcAlpha 正常
2. Blend OneMinusDstColor One 柔和相加
3. Blend DstColor Zero 正片叠底
4. Blend DstColor SrcColor 两倍相乘
5. BlendOp Min Blend One One 变暗
6. BlendOp Max Blend One One 变亮
7. Blend One One 线性减淡
### 解决半透明乱序问题
1. 使用两个Pass来渲染模型第一个Pass开启深度写入但不输出颜色。第二个Pass进行正常的透明度混合。
2. 双面渲染的透明效果
## 剔除命令
Cull Back | Front | Off
## 内置时间变量
- _Time场景加载开始到现在的时间4个分量为t/20,t,2t,3t
- _SinTime时间的正弦值4个分量为t/8,t/4,t/2,t
- _CosTime时间的余弦值4个分量为t/8,t/4,t/2,t
- untiy_DeltaTimedt为时间的增量4个分量为dt1/dt,SmoothDt,1/SmoothDt
## 预处理命令
### multi_compile
`multi_compile`定义的宏,如`#pragma multi_compile_fog``#pragma multi_compile_fwdbase`等基本上适用于大部分shader与shader自身所带的属性无关。
### shader_feature
`shader_feature`定义的宏多用于针对shader自身的属性。比如shader中有`_NormalMap`这个属性(Property),便可通`#pragma shader_feature _NormalMap`来定义宏用来实现这个shader在material有无`_NormalMap`时可进行不同的处理。
## 优化
减少Draw Call的方式有动态合批与静态合批。Unity中支持两种批处理方式一种是动态批处理一种是静态批处理。对于动态批处理来说有点是一切处理都是Unity自动完成的不需要我们自己做任何操作而且物体是可以移动的但缺点是限制很多可能一不小心就会破坏了这种机制导致Unity无法动态批处理一些使用了相同材质的物体。
而对于静态批处理来说,它的优点是自由度很高,限制很少;但缺点是可能会占用更多的内存,而且经过静态批出里的所有物体都不可以再移动了。
动态批处理的原理是每一帧把可以进行批处理的模型网格进行合并再把合并后模型数据传递给GPU然后使用同一个材质对其渲染。处理实现方便动态批处理的另一个好处是经过批处理的物体仍然可以移动这是由于在处理每帧时Unity都会重新合并一次网格。
### 共享材质
将多张纹理合并到一起,并且制作成材质。
### 动态合批
在使用同一个材质的情况下,满足的条件后就会被动态处理,每帧都会合并一次。条件:
- 能够进行动态批处理的网格顶点属性规模要小于900.例如如果Shader中需要使用顶点位置、法线和纹理坐标这3个顶点属性那么想要让模型能够被动态批处理它的顶点数目不能超过300。需要注意的是这个数字未来有可能会发生变化因此不要依赖这个数据。
- 一般来说所有对象都需要使用同一个缩放尺度。一个例外的情况是如果所有的物体都使用了不同的非统一缩放那么它们也是可以被动态批处理的。但在Unity 5 中,这种对模型缩放的限制已经不存在了。
- 使用光照纹理的物体需要格外小心处理。这些物体需要额外的渲染参数,例如,在光照纹理上的索引、偏移量和缩放信息等。因此,为了让这些物体可以被动态批处理,我们需要保证它们指向光照纹理中的同一个位置。
- 多Pass的Shader会中断批处理。在前向渲染中我们有时需要使用额外的Pass来为模型添加更多的光照效果但这样一来模型就会被动态批处理了。
### 静态合批
在运行开始阶段,把需要进行静态批处理的模型合并到一个新的网格结构中。
## GeometryShader
>ShaderModel必须4.0以上`#program target 4.0`如果低于这个目标u3d会自定提升至该级别。
- maxvertexcount定义输出顶点数如果只是处理三角形只需要设置为3即可。
- triangle为输入类型关键字。
- TriangleStream为输出流类型。
```
[maxvertexcount(3)]
void MyGeometryProgram (
triangle InterpolatorsVertex i[3],
inout TriangleStream<InterpolatorsGeometry> stream
)
```
### Flat线框效果CatLikeCoding中的案例
向三角形添加重心坐标的一种方法是使用网格的顶点颜色存储它们。每个三角形的第一个顶点变为红色,第二个顶点变为绿色,第三个顶点变为蓝色。但是,这将需要具有以此方式分配的顶点颜色的网格,并且无法共享顶点。我们想要一种适用于任何网格的解决方案。幸运的是,我们可以使用我们的几何程序添加所需的坐标。
由于网格不提供重心坐标因此顶点程序不了解它们。所以它们不属于InterpolatorsVertex结构。要使几何程序输出它们我们必须定义一个新结构。首先在MyGeometryProgram上方定义InterpolatorsGeometry。它应包含与InterpolatorsVertex相同的数据因此使用它作为其内容
```hlsl
struct InterpolatorsGeometry {
InterpolatorsVertex data;
CUSTOM_GEOMETRY_INTERPOLATORS
};
```
- MyGeometryProgram的作用为调整按照面法线调整顶点法线在调整barycentricCoordinates值最后塞入inout TriangleStream<InterpolatorsGeometry>中。
- GetAlbedoWithWireframe为线控效果控制最终会在My Lighting.cginc中以宏替换的方式整合至渲染流程中。
```hlsl
float3 GetAlbedoWithWireframe (Interpolators i) {
float3 albedo = GetAlbedo(i);
float3 barys;
barys.xy = i.barycentricCoordinates;
barys.z = 1 - barys.x - barys.y;
float3 deltas = fwidth(barys);
float3 smoothing = deltas * _WireframeSmoothing;
float3 thickness = deltas * _WireframeThickness;
barys = smoothstep(thickness, thickness + smoothing, barys);
float minBary = min(barys.x, min(barys.y, barys.z));
return lerp(_WireframeColor, albedo, minBary);
}
```

View File

@@ -0,0 +1,48 @@
## 公式
```c#
saturate((1.0 + ( (Set_ShadingGrade - (_1st_ShadeColor_Step-_1st_ShadeColor_Feather)) * (0.0 - 1.0) ) / (_1st_ShadeColor_Step - (_1st_ShadeColor_Step-_1st_ShadeColor_Feather)))); // Base and 1st Shade Mask
```
## ShadingGradeMap
```c#
float3 Set_BaseColor = lerp( (_MainTex_var.rgb*_BaseColor.rgb), ((_MainTex_var.rgb*_BaseColor.rgb)*Set_LightColor), _Is_LightColor_Base );
float3 _BaseColor_var = lerp(Set_BaseColor,_Is_LightColor_1st_Shade_var,Set_FinalShadowMask);
float Set_FinalShadowMask = saturate((1.0 + ( (Set_ShadingGrade - (_1st_ShadeColor_Step-_1st_ShadeColor_Feather)) * (0.0 - 1.0) ) / (_1st_ShadeColor_Step - (_1st_ShadeColor_Step-_1st_ShadeColor_Feather))));
float Set_ShadeShadowMask = saturate((1.0 + ( (Set_ShadingGrade - (_2nd_ShadeColor_Step-_2nd_ShadeColor_Feather)) * (0.0 - 1.0) ) / (_2nd_ShadeColor_Step - (_2nd_ShadeColor_Step-_2nd_ShadeColor_Feather))));
float3 Set_FinalBaseColor = lerp( _BaseColor_var,
lerp(_Is_LightColor_1st_Shade_var,
lerp( _2nd_ShadeMap_var.rgb*_2nd_ShadeColor.rgb,
((_2nd_ShadeMap_var.rgb*_2nd_ShadeColor.rgb)*Set_LightColor),
_Is_LightColor_2nd_Shade ),
Set_ShadeShadowMask),
Set_FinalShadowMask);
```
## Feather
```c#
_Set_1st_ShadePosition与 _Set_2st_ShadePosition默认为白色。
float3 Set_BaseColor = lerp( (_BaseColor.rgb*_MainTex_var.rgb), ((_BaseColor.rgb*_MainTex_var.rgb)*Set_LightColor), _Is_LightColor_Base );
float Set_FinalShadowMask = saturate(1.0 + lerp( _HalfLambert_var,
_HalfLambert_var*saturate(_SystemShadowsLevel_var),
_Set_SystemShadowsToBase ) - (_BaseColor_Step-_BaseShade_Feather) *
((1.0 - _Set_1st_ShadePosition_var.rgb).r - 1.0) //对应上面的 (0.0 - 1.0)项
/ (_BaseColor_Step - (_BaseColor_Step-_BaseShade_Feather)));
float3 Set_FinalBaseColor = lerp( Set_BaseColor,
lerp(Set_1st_ShadeColor,
Set_2nd_ShadeColor,
saturate((1.0 + ( (_HalfLambert_var - (_ShadeColor_Step-_1st2nd_Shades_Feather)) * ((1.0 - _Set_2nd_ShadePosition_var.rgb).r - 1.0) ) / (_ShadeColor_Step - (_ShadeColor_Step-_1st2nd_Shades_Feather))))
),
Set_FinalShadowMask); // Final Color
```
## 总结
可以看得出两者都使用了UTS的祖传公式进行插值。但不同点在于
- ShadingGradeMap工作模式的UTS公式插值对象为ShadingGradeMap之后使用1st、2stShadeColor对应的Step与Feather进行计算来获得2个ShadeMask。最后再使用2个Mask对BaseColor、1stShadeColor与2stShadeColor进行插值来获取最终结果。
- Feather工作模式的UTS公式插值对象为HalfLambert之后也使用2份Step与Feather进行计算得到来获得2个ShadeMask。最后再使用2个Mask对BaseColor、1stShadeColor与2stShadeColor进行插值来获取最终结果。还有一个区别在于里面增加了ShadePositionMap对UTS公式的分子项进行控制也就是使用`(1.0 - _Set_2nd_ShadePosition_var.rgb).r - 1.0)`来代替`(0.0 - 1.0)`

View File

@@ -0,0 +1,685 @@
## 各种定义
根据是否开启天使环渲染与`_MAIN_LIGHT_SHADOWS`来定义顶点输入与输出格式。
```c#
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 texcoord0 : TEXCOORD0;
#ifdef _IS_ANGELRING_OFF
float2 lightmapUV : TEXCOORD1;
#elif _IS_ANGELRING_ON
float2 texcoord1 : TEXCOORD1;
float2 lightmapUV : TEXCOORD2;
#endif
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct VertexOutput {
float4 pos : SV_POSITION;
float2 uv0 : TEXCOORD0;
//v.2.0.4
#ifdef _IS_ANGELRING_OFF
float4 posWorld : TEXCOORD1;
float3 normalDir : TEXCOORD2;
float3 tangentDir : TEXCOORD3;
float3 bitangentDir : TEXCOORD4;
//v.2.0.7
float mirrorFlag : TEXCOORD5;
DECLARE_LIGHTMAP_OR_SH(lightmapUV, vertexSH, 6);
#if defined(_ADDITIONAL_LIGHTS_VERTEX) || (VERSION_LOWER(12, 0))
half4 fogFactorAndVertexLight : TEXCOORD7; // x: fogFactor, yzw: vertex light
#else
half fogFactor : TEXCOORD7;
#endif
# ifndef _MAIN_LIGHT_SHADOWS
float4 positionCS : TEXCOORD8;
int mainLightID : TEXCOORD9;
# else
float4 shadowCoord : TEXCOORD8;
float4 positionCS : TEXCOORD9;
int mainLightID : TEXCOORD10;
# endif
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
//
#elif _IS_ANGELRING_ON
float2 uv1 : TEXCOORD1;
float4 posWorld : TEXCOORD2;
float3 normalDir : TEXCOORD3;
float3 tangentDir : TEXCOORD4;
float3 bitangentDir : TEXCOORD5;
//v.2.0.7
float mirrorFlag : TEXCOORD6;
DECLARE_LIGHTMAP_OR_SH(lightmapUV, vertexSH, 7);
#if defined(_ADDITIONAL_LIGHTS_VERTEX) || (VERSION_LOWER(12, 0))
half4 fogFactorAndVertexLight : TEXCOORD8; // x: fogFactor, yzw: vertex light
#else
half fogFactor : TEXCOORD8; // x: fogFactor, yzw: vertex light
#endif
# ifndef _MAIN_LIGHT_SHADOWS
float4 positionCS : TEXCOORD9;
int mainLightID : TEXCOORD10;
# else
float4 shadowCoord : TEXCOORD9;
float4 positionCS : TEXCOORD10;
int mainLightID : TEXCOORD11;
# endif
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
#else
LIGHTING_COORDS(7,8)
UNITY_FOG_COORDS(9)
#endif
//
};
//灯光数据
struct UtsLight
{
float3 direction;
float3 color;
float distanceAttenuation;
real shadowAttenuation;
int type;
};
```
根据宏定义宏:`_ADDITIONAL_LIGHTS`=>`REQUIRES_WORLD_SPACE_POS_INTERPOLATOR`,`_MAIN_LIGHT_SHADOWS`=>`REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR`。以及一些函数:
```c#
// RaytracedHardShadow
// This is global texture. what to do with SRP Batcher.
#define UNITY_PROJ_COORD(a) a
#define UNITY_SAMPLE_SCREEN_SHADOW(tex, uv) tex2Dproj( tex, UNITY_PROJ_COORD(uv) ).r
#define TEXTURE2D_SAMPLER2D(textureName, samplerName) Texture2D textureName; SamplerState samplerName
TEXTURE2D_SAMPLER2D(_RaytracedHardShadow, sampler_RaytracedHardShadow);
float4 _RaytracedHardShadow_TexelSize;
//function to rotate the UV: RotateUV()
//float2 rotatedUV = RotateUV(i.uv0, (_angular_Verocity*3.141592654), float2(0.5, 0.5), _Time.g);
float2 RotateUV(float2 _uv, float _radian, float2 _piv, float _time)
{
float RotateUV_ang = _radian;
float RotateUV_cos = cos(_time*RotateUV_ang);
float RotateUV_sin = sin(_time*RotateUV_ang);
return (mul(_uv - _piv, float2x2( RotateUV_cos, -RotateUV_sin, RotateUV_sin, RotateUV_cos)) + _piv);
}
//
fixed3 DecodeLightProbe( fixed3 N ){
return ShadeSH9(float4(N,1));
}
inline void InitializeStandardLitSurfaceDataUTS(float2 uv, out SurfaceData outSurfaceData)
{
outSurfaceData = (SurfaceData)0;
// half4 albedoAlpha = SampleAlbedoAlpha(uv, TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap));
half4 albedoAlpha = half4(1.0,1.0,1.0,1.0);
outSurfaceData.alpha = Alpha(albedoAlpha.a, _BaseColor, _Cutoff);
half4 specGloss = SampleMetallicSpecGloss(uv, albedoAlpha.a);
outSurfaceData.albedo = albedoAlpha.rgb * _BaseColor.rgb;
#if _SPECULAR_SETUP
outSurfaceData.metallic = 1.0h;
outSurfaceData.specular = specGloss.rgb;
#else
outSurfaceData.metallic = specGloss.r;
outSurfaceData.specular = half3(0.0h, 0.0h, 0.0h);
#endif
outSurfaceData.smoothness = specGloss.a;
outSurfaceData.normalTS = SampleNormal(uv, TEXTURE2D_ARGS(_BumpMap, sampler_BumpMap), _BumpScale);
outSurfaceData.occlusion = SampleOcclusion(uv);
outSurfaceData.emission = SampleEmission(uv, _EmissionColor.rgb, TEXTURE2D_ARGS(_EmissionMap, sampler_EmissionMap));
}
half3 GlobalIlluminationUTS(BRDFData brdfData, half3 bakedGI, half occlusion, half3 normalWS, half3 viewDirectionWS)
{
half3 reflectVector = reflect(-viewDirectionWS, normalWS);
half fresnelTerm = Pow4(1.0 - saturate(dot(normalWS, viewDirectionWS)));
half3 indirectDiffuse = bakedGI * occlusion;
half3 indirectSpecular = GlossyEnvironmentReflection(reflectVector, brdfData.perceptualRoughness, occlusion);
return EnvironmentBRDF(brdfData, indirectDiffuse, indirectSpecular, fresnelTerm);
}
```
## 顶点着色器
计算
- 顶点法线、切线、次级法线
- 裁剪过的顶点世界坐标
- 使用`ComputeFogFactor`(FOG_LINEAR、FOG_EXP与FOG_EXP2)计算`fogFactorAndVertexLight`或者`fogFactor`。
- shadowCoord
- mainLightID
如果开启天使环渲染则增加一个TexCoord1为天使环UV坐标。
```c#
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_TRANSFER_INSTANCE_ID(v, o);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
o.uv0 = v.texcoord0;
//v.2.0.4
#ifdef _IS_ANGELRING_OFF
//
#elif _IS_ANGELRING_ON
o.uv1 = v.texcoord1;
#endif
o.normalDir = UnityObjectToWorldNormal(v.normal);
o.tangentDir = normalize( mul( unity_ObjectToWorld, float4( v.tangent.xyz, 0.0 ) ).xyz );
o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w);
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
o.pos = UnityObjectToClipPos( v.vertex );
//v.2.0.7 Detection of the inside the mirror (right or left-handed) o.mirrorFlag = -1 then "inside the mirror".
float3 crossFwd = cross(UNITY_MATRIX_V[0].xyz, UNITY_MATRIX_V[1].xyz);
o.mirrorFlag = dot(crossFwd, UNITY_MATRIX_V[2].xyz) < 0 ? 1 : -1;
//
float3 positionWS = TransformObjectToWorld(v.vertex.xyz);
float4 positionCS = TransformWorldToHClip(positionWS);
half3 vertexLight = VertexLighting(o.posWorld.xyz, o.normalDir);
half fogFactor = ComputeFogFactor(positionCS.z);
OUTPUT_LIGHTMAP_UV(v.lightmapUV, unity_LightmapST, o.lightmapUV);
OUTPUT_SH(o.normalDir.xyz, o.vertexSH);
# if defined(_ADDITIONAL_LIGHTS_VERTEX) || (VERSION_LOWER(12, 0))
o.fogFactorAndVertexLight = half4(fogFactor, vertexLight);
#else
o.fogFactor = fogFactor;
#endif
o.positionCS = positionCS;
#if defined(_MAIN_LIGHT_SHADOWS) && !defined(_RECEIVE_SHADOWS_OFF)
#if SHADOWS_SCREEN
o.shadowCoord = ComputeScreenPos(positionCS);
#else
o.shadowCoord = TransformWorldToShadowCoord(o.posWorld.xyz);
#endif
o.mainLightID = DetermineUTS_MainLightIndex(o.posWorld.xyz, o.shadowCoord, positionCS);
#else
o.mainLightID = DetermineUTS_MainLightIndex(o.posWorld.xyz, 0, positionCS);
#endif
return o;
}
```
## 像素着色器
UTS的着色模式有两种分别封装在`UniversalToonBodyDoubleShadeWithFeather.hlsl`与`UniversalToonBodyShadingGradeMap`中。
```c#
float4 frag(VertexOutput i, fixed facing : VFACE) : SV_TARGET
{
#if defined(_SHADINGGRADEMAP)
return fragShadingGradeMap(i, facing);
#else
return fragDoubleShadeFeather(i, facing);
#endif
}
```
## 透明与裁剪
`ClippingMode`设置为非off后才会开启裁剪选项。拥有以下功能
- 裁剪Mask反转
- 使用BaseMap的Alpha通道作为Mask
- 裁剪强度与透明度强度
这个功能通常用来除了头发之类的透明物体。
## 完整代码
```c#
#if (SHADER_LIBRARY_VERSION_MAJOR ==7 && SHADER_LIBRARY_VERSION_MINOR >= 3) || (SHADER_LIBRARY_VERSION_MAJOR >= 8)
# ifdef _ADDITIONAL_LIGHTS
# ifndef REQUIRES_WORLD_SPACE_POS_INTERPOLATOR
# define REQUIRES_WORLD_SPACE_POS_INTERPOLATOR
# endif
# endif
#else
# ifdef _MAIN_LIGHT_SHADOWS
//# if !defined(_MAIN_LIGHT_SHADOWS_CASCADE)
# ifndef REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR
# define REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR
# endif
//# endif
# endif
# ifdef _ADDITIONAL_LIGHTS
# ifndef REQUIRES_WORLD_SPACE_POS_INTERPOLATOR
# define REQUIRES_WORLD_SPACE_POS_INTERPOLATOR
# endif
# endif
#endif
// RaytracedHardShadow
// This is global texture. what to do with SRP Batcher.
#define UNITY_PROJ_COORD(a) a
#define UNITY_SAMPLE_SCREEN_SHADOW(tex, uv) tex2Dproj( tex, UNITY_PROJ_COORD(uv) ).r
#define TEXTURE2D_SAMPLER2D(textureName, samplerName) Texture2D textureName; SamplerState samplerName
TEXTURE2D_SAMPLER2D(_RaytracedHardShadow, sampler_RaytracedHardShadow);
float4 _RaytracedHardShadow_TexelSize;
//function to rotate the UV: RotateUV()
//float2 rotatedUV = RotateUV(i.uv0, (_angular_Verocity*3.141592654), float2(0.5, 0.5), _Time.g);
float2 RotateUV(float2 _uv, float _radian, float2 _piv, float _time)
{
float RotateUV_ang = _radian;
float RotateUV_cos = cos(_time*RotateUV_ang);
float RotateUV_sin = sin(_time*RotateUV_ang);
return (mul(_uv - _piv, float2x2( RotateUV_cos, -RotateUV_sin, RotateUV_sin, RotateUV_cos)) + _piv);
}
//
fixed3 DecodeLightProbe( fixed3 N ){
return ShadeSH9(float4(N,1));
}
inline void InitializeStandardLitSurfaceDataUTS(float2 uv, out SurfaceData outSurfaceData)
{
outSurfaceData = (SurfaceData)0;
// half4 albedoAlpha = SampleAlbedoAlpha(uv, TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap));
half4 albedoAlpha = half4(1.0,1.0,1.0,1.0);
outSurfaceData.alpha = Alpha(albedoAlpha.a, _BaseColor, _Cutoff);
half4 specGloss = SampleMetallicSpecGloss(uv, albedoAlpha.a);
outSurfaceData.albedo = albedoAlpha.rgb * _BaseColor.rgb;
#if _SPECULAR_SETUP
outSurfaceData.metallic = 1.0h;
outSurfaceData.specular = specGloss.rgb;
#else
outSurfaceData.metallic = specGloss.r;
outSurfaceData.specular = half3(0.0h, 0.0h, 0.0h);
#endif
outSurfaceData.smoothness = specGloss.a;
outSurfaceData.normalTS = SampleNormal(uv, TEXTURE2D_ARGS(_BumpMap, sampler_BumpMap), _BumpScale);
outSurfaceData.occlusion = SampleOcclusion(uv);
outSurfaceData.emission = SampleEmission(uv, _EmissionColor.rgb, TEXTURE2D_ARGS(_EmissionMap, sampler_EmissionMap));
}
half3 GlobalIlluminationUTS(BRDFData brdfData, half3 bakedGI, half occlusion, half3 normalWS, half3 viewDirectionWS)
{
half3 reflectVector = reflect(-viewDirectionWS, normalWS);
half fresnelTerm = Pow4(1.0 - saturate(dot(normalWS, viewDirectionWS)));
half3 indirectDiffuse = bakedGI * occlusion;
half3 indirectSpecular = GlossyEnvironmentReflection(reflectVector, brdfData.perceptualRoughness, occlusion);
return EnvironmentBRDF(brdfData, indirectDiffuse, indirectSpecular, fresnelTerm);
}
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 texcoord0 : TEXCOORD0;
#ifdef _IS_ANGELRING_OFF
float2 lightmapUV : TEXCOORD1;
#elif _IS_ANGELRING_ON
float2 texcoord1 : TEXCOORD1;
float2 lightmapUV : TEXCOORD2;
#endif
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct VertexOutput {
float4 pos : SV_POSITION;
float2 uv0 : TEXCOORD0;
//v.2.0.4
#ifdef _IS_ANGELRING_OFF
float4 posWorld : TEXCOORD1;
float3 normalDir : TEXCOORD2;
float3 tangentDir : TEXCOORD3;
float3 bitangentDir : TEXCOORD4;
//v.2.0.7
float mirrorFlag : TEXCOORD5;
DECLARE_LIGHTMAP_OR_SH(lightmapUV, vertexSH, 6);
#if defined(_ADDITIONAL_LIGHTS_VERTEX) || (VERSION_LOWER(12, 0))
half4 fogFactorAndVertexLight : TEXCOORD7; // x: fogFactor, yzw: vertex light
#else
half fogFactor : TEXCOORD7;
#endif
# ifndef _MAIN_LIGHT_SHADOWS
float4 positionCS : TEXCOORD8;
int mainLightID : TEXCOORD9;
# else
float4 shadowCoord : TEXCOORD8;
float4 positionCS : TEXCOORD9;
int mainLightID : TEXCOORD10;
# endif
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
//
#elif _IS_ANGELRING_ON
float2 uv1 : TEXCOORD1;
float4 posWorld : TEXCOORD2;
float3 normalDir : TEXCOORD3;
float3 tangentDir : TEXCOORD4;
float3 bitangentDir : TEXCOORD5;
//v.2.0.7
float mirrorFlag : TEXCOORD6;
DECLARE_LIGHTMAP_OR_SH(lightmapUV, vertexSH, 7);
#if defined(_ADDITIONAL_LIGHTS_VERTEX) || (VERSION_LOWER(12, 0))
half4 fogFactorAndVertexLight : TEXCOORD8; // x: fogFactor, yzw: vertex light
#else
half fogFactor : TEXCOORD8; // x: fogFactor, yzw: vertex light
#endif
# ifndef _MAIN_LIGHT_SHADOWS
float4 positionCS : TEXCOORD9;
int mainLightID : TEXCOORD10;
# else
float4 shadowCoord : TEXCOORD9;
float4 positionCS : TEXCOORD10;
int mainLightID : TEXCOORD11;
# endif
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
#else
LIGHTING_COORDS(7,8)
UNITY_FOG_COORDS(9)
#endif
//
};
// Abstraction over Light shading data.
struct UtsLight
{
float3 direction;
float3 color;
float distanceAttenuation;
real shadowAttenuation;
int type;
};
///////////////////////////////////////////////////////////////////////////////
// Light Abstraction //
/////////////////////////////////////////////////////////////////////////////
real MainLightRealtimeShadowUTS(float4 shadowCoord, float4 positionCS)
{
#if !defined(MAIN_LIGHT_CALCULATE_SHADOWS)
return 1.0h;
#endif
ShadowSamplingData shadowSamplingData = GetMainLightShadowSamplingData();
half4 shadowParams = GetMainLightShadowParams();
#if defined(UTS_USE_RAYTRACING_SHADOW)
float w = (positionCS.w == 0) ? 0.00001 : positionCS.w;
float4 screenPos = ComputeScreenPos(positionCS/ w);
return SAMPLE_TEXTURE2D(_RaytracedHardShadow, sampler_RaytracedHardShadow, screenPos);
#endif
return SampleShadowmap(TEXTURE2D_ARGS(_MainLightShadowmapTexture, sampler_MainLightShadowmapTexture), shadowCoord, shadowSamplingData, shadowParams, false);
}
real AdditionalLightRealtimeShadowUTS(int lightIndex, float3 positionWS, float4 positionCS)
{
#if defined(UTS_USE_RAYTRACING_SHADOW)
float w = (positionCS.w == 0) ? 0.00001 : positionCS.w;
float4 screenPos = ComputeScreenPos(positionCS / w);
return SAMPLE_TEXTURE2D(_RaytracedHardShadow, sampler_RaytracedHardShadow, screenPos);
#endif // UTS_USE_RAYTRACING_SHADOW
#if !defined(ADDITIONAL_LIGHT_CALCULATE_SHADOWS)
return 1.0h;
#endif
ShadowSamplingData shadowSamplingData = GetAdditionalLightShadowSamplingData();
#if USE_STRUCTURED_BUFFER_FOR_LIGHT_DATA
lightIndex = _AdditionalShadowsIndices[lightIndex];
// We have to branch here as otherwise we would sample buffer with lightIndex == -1.
// However this should be ok for platforms that store light in SSBO.
UNITY_BRANCH
if (lightIndex < 0)
return 1.0;
float4 shadowCoord = mul(_AdditionalShadowsBuffer[lightIndex].worldToShadowMatrix, float4(positionWS, 1.0));
#else
float4 shadowCoord = mul(_AdditionalLightsWorldToShadow[lightIndex], float4(positionWS, 1.0));
#endif
half4 shadowParams = GetAdditionalLightShadowParams(lightIndex);
return SampleShadowmap(TEXTURE2D_ARGS(_AdditionalLightsShadowmapTexture, sampler_AdditionalLightsShadowmapTexture), shadowCoord, shadowSamplingData, shadowParams, true);
}
UtsLight GetUrpMainUtsLight()
{
UtsLight light;
light.direction = _MainLightPosition.xyz;
// unity_LightData.z is 1 when not culled by the culling mask, otherwise 0.
light.distanceAttenuation = unity_LightData.z;
#if defined(LIGHTMAP_ON) || defined(_MIXED_LIGHTING_SUBTRACTIVE)
// unity_ProbesOcclusion.x is the mixed light probe occlusion data
light.distanceAttenuation *= unity_ProbesOcclusion.x;
#endif
light.shadowAttenuation = 1.0;
light.color = _MainLightColor.rgb;
light.type = _MainLightPosition.w;
return light;
}
UtsLight GetUrpMainUtsLight(float4 shadowCoord, float4 positionCS)
{
UtsLight light = GetUrpMainUtsLight();
light.shadowAttenuation = MainLightRealtimeShadowUTS(shadowCoord, positionCS);
return light;
}
// Fills a light struct given a perObjectLightIndex
UtsLight GetAdditionalPerObjectUtsLight(int perObjectLightIndex, float3 positionWS,float4 positionCS)
{
// Abstraction over Light input constants
#if USE_STRUCTURED_BUFFER_FOR_LIGHT_DATA
float4 lightPositionWS = _AdditionalLightsBuffer[perObjectLightIndex].position;
half3 color = _AdditionalLightsBuffer[perObjectLightIndex].color.rgb;
half4 distanceAndSpotAttenuation = _AdditionalLightsBuffer[perObjectLightIndex].attenuation;
half4 spotDirection = _AdditionalLightsBuffer[perObjectLightIndex].spotDirection;
half4 lightOcclusionProbeInfo = _AdditionalLightsBuffer[perObjectLightIndex].occlusionProbeChannels;
#else
float4 lightPositionWS = _AdditionalLightsPosition[perObjectLightIndex];
half3 color = _AdditionalLightsColor[perObjectLightIndex].rgb;
half4 distanceAndSpotAttenuation = _AdditionalLightsAttenuation[perObjectLightIndex];
half4 spotDirection = _AdditionalLightsSpotDir[perObjectLightIndex];
half4 lightOcclusionProbeInfo = _AdditionalLightsOcclusionProbes[perObjectLightIndex];
#endif
// Directional lights store direction in lightPosition.xyz and have .w set to 0.0.
// This way the following code will work for both directional and punctual lights.
float3 lightVector = lightPositionWS.xyz - positionWS * lightPositionWS.w;
float distanceSqr = max(dot(lightVector, lightVector), HALF_MIN);
half3 lightDirection = half3(lightVector * rsqrt(distanceSqr));
half attenuation = DistanceAttenuation(distanceSqr, distanceAndSpotAttenuation.xy) * AngleAttenuation(spotDirection.xyz, lightDirection, distanceAndSpotAttenuation.zw);
UtsLight light;
light.direction = lightDirection;
light.distanceAttenuation = attenuation;
light.shadowAttenuation = AdditionalLightRealtimeShadowUTS(perObjectLightIndex, positionWS, positionCS);
light.color = color;
light.type = lightPositionWS.w;
// In case we're using light probes, we can sample the attenuation from the `unity_ProbesOcclusion`
#if defined(LIGHTMAP_ON) || defined(_MIXED_LIGHTING_SUBTRACTIVE)
// First find the probe channel from the light.
// Then sample `unity_ProbesOcclusion` for the baked occlusion.
// If the light is not baked, the channel is -1, and we need to apply no occlusion.
// probeChannel is the index in 'unity_ProbesOcclusion' that holds the proper occlusion value.
int probeChannel = lightOcclusionProbeInfo.x;
// lightProbeContribution is set to 0 if we are indeed using a probe, otherwise set to 1.
half lightProbeContribution = lightOcclusionProbeInfo.y;
half probeOcclusionValue = unity_ProbesOcclusion[probeChannel];
light.distanceAttenuation *= max(probeOcclusionValue, lightProbeContribution);
#endif
return light;
}
// Fills a light struct given a loop i index. This will convert the i
// index to a perObjectLightIndex
UtsLight GetAdditionalUtsLight(uint i, float3 positionWS,float4 positionCS)
{
int perObjectLightIndex = GetPerObjectLightIndex(i);
return GetAdditionalPerObjectUtsLight(perObjectLightIndex, positionWS, positionCS);
}
half3 GetLightColor(UtsLight light)
{
return light.color * light.distanceAttenuation;
}
#define INIT_UTSLIGHT(utslight) \
utslight.direction = 0; \
utslight.color = 0; \
utslight.distanceAttenuation = 0; \
utslight.shadowAttenuation = 0; \
utslight.type = 0
int DetermineUTS_MainLightIndex(float3 posW, float4 shadowCoord, float4 positionCS)
{
UtsLight mainLight;
INIT_UTSLIGHT(mainLight);
int mainLightIndex = MAINLIGHT_NOT_FOUND;
UtsLight nextLight = GetUrpMainUtsLight(shadowCoord, positionCS);
if (nextLight.distanceAttenuation > mainLight.distanceAttenuation && nextLight.type == 0)
{
mainLight = nextLight;
mainLightIndex = MAINLIGHT_IS_MAINLIGHT;
}
int lightCount = GetAdditionalLightsCount();
for (int ii = 0; ii < lightCount; ++ii)
{
nextLight = GetAdditionalUtsLight(ii, posW, positionCS);
if (nextLight.distanceAttenuation > mainLight.distanceAttenuation && nextLight.type == 0)
{
mainLight = nextLight;
mainLightIndex = ii;
}
}
return mainLightIndex;
}
UtsLight GetMainUtsLightByID(int index,float3 posW, float4 shadowCoord, float4 positionCS)
{
UtsLight mainLight;
INIT_UTSLIGHT(mainLight);
if (index == MAINLIGHT_NOT_FOUND)
{
return mainLight;
}
if (index == MAINLIGHT_IS_MAINLIGHT)
{
return GetUrpMainUtsLight(shadowCoord, positionCS);
}
return GetAdditionalUtsLight(index, posW, positionCS);
}
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_TRANSFER_INSTANCE_ID(v, o);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
o.uv0 = v.texcoord0;
//v.2.0.4
#ifdef _IS_ANGELRING_OFF
//
#elif _IS_ANGELRING_ON
o.uv1 = v.texcoord1;
#endif
o.normalDir = UnityObjectToWorldNormal(v.normal);
o.tangentDir = normalize( mul( unity_ObjectToWorld, float4( v.tangent.xyz, 0.0 ) ).xyz );
o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w);
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
o.pos = UnityObjectToClipPos( v.vertex );
//v.2.0.7 Detection of the inside the mirror (right or left-handed) o.mirrorFlag = -1 then "inside the mirror".用于判断是否是渲染镜子反射结果。
//[0]Right unit vector [1] Up unit vector [2] -1 * world space camera Forward unit vector
float3 crossFwd = cross(UNITY_MATRIX_V[0].xyz, UNITY_MATRIX_V[1].xyz);
o.mirrorFlag = dot(crossFwd, UNITY_MATRIX_V[2].xyz) < 0 ? 1 : -1;
//
float3 positionWS = TransformObjectToWorld(v.vertex.xyz);
float4 positionCS = TransformWorldToHClip(positionWS);
half3 vertexLight = VertexLighting(o.posWorld.xyz, o.normalDir);
half fogFactor = ComputeFogFactor(positionCS.z);
OUTPUT_LIGHTMAP_UV(v.lightmapUV, unity_LightmapST, o.lightmapUV);
OUTPUT_SH(o.normalDir.xyz, o.vertexSH);
# if defined(_ADDITIONAL_LIGHTS_VERTEX) || (VERSION_LOWER(12, 0))
o.fogFactorAndVertexLight = half4(fogFactor, vertexLight);
#else
o.fogFactor = fogFactor;
#endif
o.positionCS = positionCS;
#if defined(_MAIN_LIGHT_SHADOWS) && !defined(_RECEIVE_SHADOWS_OFF)
#if SHADOWS_SCREEN
o.shadowCoord = ComputeScreenPos(positionCS);
#else
o.shadowCoord = TransformWorldToShadowCoord(o.posWorld.xyz);
#endif
o.mainLightID = DetermineUTS_MainLightIndex(o.posWorld.xyz, o.shadowCoord, positionCS);
#else
o.mainLightID = DetermineUTS_MainLightIndex(o.posWorld.xyz, 0, positionCS);
#endif
return o;
}
#if defined(_SHADINGGRADEMAP)
#include "UniversalToonBodyShadingGradeMap.hlsl"
#else //#if defined(_SHADINGGRADEMAP)
#include "UniversalToonBodyDoubleShadeWithFeather.hlsl"
#endif //#if defined(_SHADINGGRADEMAP)
float4 frag(VertexOutput i, fixed facing : VFACE) : SV_TARGET
{
#if defined(_SHADINGGRADEMAP)
return fragShadingGradeMap(i, facing);
#else
return fragDoubleShadeFeather(i, facing);
#endif
}
```

View File

@@ -0,0 +1,204 @@
## 描边相关属性
| Property | Function |
| ------------------------- | ------------------------------------------------------------ |
| `OUTLINE MODE` | 指定背面挤出描边的方式。 你可以选择`NML`(正常倒置法)/`POS`(位置缩放法)。/ `POS`位置缩放法。在大多数情况下使用NML但如果是只由硬边组成的网格如立方体POS将防止轮廓被断开。
对简单的形状使用POS对人物和有复杂轮廓的东西使用NML会比较好。 |
| `Outline_Width` | 定义描边宽度 **注意: 这个值依赖于模型被导入Unity时的比例。** which means that you have to be careful if the scale is not 1. |
| `Farthest_Distance` | 轮廓的宽度将根据摄像机和物体之间的距离而改变。摄像机大于这个距离时轮廓宽度将为0。 |
| `Nearest_Distance` | 轮廓的宽度将根据摄像机和物体之间的距离而改变。摄像机小于这个距离时宽度将为`Outline_Width`。 |
| `Outline_Sampler` | 用于美术手动隐藏指定区域的轮廓? |
| `Outline_Color` | 轮廓颜色。 |
| `Is_BlendBaseColor` | 是否与BaseColor颜色融合。 |
| `Is_LightColor_Outline` | 是否受灯光颜色影响。 |
| `Is_OutlineTex` | 是否存在轮廓贴图。 |
| `OutlineTex` | 轮廓贴图,用于控制颜色。 |
| `Offset_Camera_Z` | 在摄像机空间Z坐标轴上偏移。可以用于调整模型尖端与模型交界处处效果。 大多数情况设置为0。 |
| `Is_BakedNormal` | 是否使用烘焙的法线贴图的法线值。 |
| `BakedNormal for Outline` | 指定的烘焙法线贴图。 |
## 调整轮廓强度
>`_OutlineMode`有两种模式NormalDirection与PositionScaling,除了一些简单基础几何体,其他一般使用法线方向偏移。
### Outline Sampler
![](https://github.com/unity3d-jp/UnityChanToonShaderVer2_Project/raw/release/urp/2.3.0/Documentation~/Images_jpg/0906-18_01.jpg)
黑色表示“无线条”,白色表示宽度为 100%。
### BakedNormal
采样烘焙的法线贴图,并将值赋予顶点法线。
### Offset_Camera_Z
>`Offset_Camera_Z`就是单纯的深度偏移,可以用于调整模型尖端与模型交界处处效果。但本人认为这个没有必要。
效果如图:
![](https://github.com/unity3d-jp/UnityChanToonShaderVer2_Project/raw/release/urp/2.3.0/Documentation~/Images_jpg/0205-11_01.jpg)
## 顶点着色器
主要用于偏移顶点坐标、采样法线贴图值并传递给顶点以及传递顶点数据。计算物体与摄像机距离,对设定的最近距离与最远距离插值得到距离因子。之后乘以`_Outline_Width*0.001`
>个人认为这里的比例存在问题,应该使用到摄像机矩阵参数作为比值会比较好。
```c#
float Set_Outline_Width = (_Outline_Width*0.001*smoothstep( _Farthest_Distance, _Nearest_Distance, distance(objPos.rgb,_WorldSpaceCameraPos) )*_Outline_Sampler_var.rgb).r;
//Transparent开启时`_ZOverDrawMode`为1否则为0。
Set_Outline_Width *= (1.0f - _ZOverDrawMode);
```
```c#
#ifdef _OUTLINE_NML
//v.2.0.4.3 baked Normal Texture for Outline
o.pos = UnityObjectToClipPos(lerp(float4(v.vertex.xyz + v.normal*Set_Outline_Width,1), float4(v.vertex.xyz + _BakedNormalDir*Set_Outline_Width,1),_Is_BakedNormal));
#elif _OUTLINE_POS
Set_Outline_Width = Set_Outline_Width*2;
float signVar = dot(normalize(v.vertex),normalize(v.normal))<0 ? -1 : 1;
o.pos = UnityObjectToClipPos(float4(v.vertex.xyz + signVar*normalize(v.vertex)*Set_Outline_Width, 1));
#endif
//v.2.0.7.5
o.pos.z = o.pos.z + _Offset_Z * _ClipCameraPos.z;
return o;
```
## 像素着色器
主要用来设置轮廓颜色,`轮廓颜色=灯光颜色 * BaseMap * BaseColor * (OutlineTex)`。同时提供了裁剪功能可以根据ClippingMask、BaseMap的Alpha通道进行裁剪。
>`_Is_LightColor_Outline`根据`ShaderGUI`的选项进行设置但这里感觉可以增加CaptureMat、World映射Ramp、环境探针以增加更多效果。
```c#
//计算灯光色小于0.05则使用环境色)、灯光亮度
half3 ambientSkyColor = unity_AmbientSky.rgb>0.05 ? unity_AmbientSky.rgb*_Unlit_Intensity : half3(0.05,0.05,0.05)*_Unlit_Intensity;
float3 lightColor = _LightColor0.rgb >0.05 ? _LightColor0.rgb : ambientSkyColor.rgb;
float lightColorIntensity = (0.299*lightColor.r + 0.587*lightColor.g + 0.114*lightColor.b);
lightColor = lightColorIntensity<1 ? lightColor : lightColor/lightColorIntensity;//灯光亮度小于1时对颜色亮度进行缩放。
lightColor = lerp(half3(1.0,1.0,1.0), lightColor, _Is_LightColor_Outline);
//计算BaseMap*BaseColor值、采样OutlineMap值。
float2 Set_UV0 = i.uv0;
float4 _MainTex_var = SAMPLE_TEXTURE2D(_MainTex,sampler_MainTex, TRANSFORM_TEX(Set_UV0, _MainTex));
float3 Set_BaseColor = _BaseColor.rgb*_MainTex_var.rgb;
float3 _Is_BlendBaseColor_var = lerp( _Outline_Color.rgb*lightColor, (_Outline_Color.rgb*Set_BaseColor*Set_BaseColor*lightColor), _Is_BlendBaseColor );
float3 _OutlineTex_var = tex2D(_OutlineTex,TRANSFORM_TEX(Set_UV0, _OutlineTex)).rgb;
#ifdef _IS_OUTLINE_CLIPPING_NO
float3 Set_Outline_Color = lerp(_Is_BlendBaseColor_var, _OutlineTex_var.rgb*_Outline_Color.rgb*lightColor, _Is_OutlineTex );
return float4(Set_Outline_Color,1.0);
#elif _IS_OUTLINE_CLIPPING_YES
//开启裁剪模式的状态下可以根据ClippingMask、BaseMap的Alpha通道进行裁剪。
float4 _ClippingMask_var = SAMPLE_TEXTURE2D(_ClippingMask, sampler_MainTex, TRANSFORM_TEX(Set_UV0, _ClippingMask));
float Set_MainTexAlpha = _MainTex_var.a;
float _IsBaseMapAlphaAsClippingMask_var = lerp( _ClippingMask_var.r, Set_MainTexAlpha, _IsBaseMapAlphaAsClippingMask );
float _Inverse_Clipping_var = lerp( _IsBaseMapAlphaAsClippingMask_var, (1.0 - _IsBaseMapAlphaAsClippingMask_var), _Inverse_Clipping );
float Set_Clipping = saturate((_Inverse_Clipping_var+_Clipping_Level));
clip(Set_Clipping - 0.5);
float4 Set_Outline_Color = lerp( float4(_Is_BlendBaseColor_var,Set_Clipping), float4((_OutlineTex_var.rgb*_Outline_Color.rgb*lightColor),Set_Clipping), _Is_OutlineTex );
return Set_Outline_Color;
#endif
}
```
## 完整代码
```c#
uniform float4 _LightColor0; // this is not set in c# code ?
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 texcoord0 : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct VertexOutput {
float4 pos : SV_POSITION;
float2 uv0 : TEXCOORD0;
float3 normalDir : TEXCOORD1;
float3 tangentDir : TEXCOORD2;
float3 bitangentDir : TEXCOORD3;
UNITY_VERTEX_OUTPUT_STEREO
};
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
o.uv0 = v.texcoord0;
float4 objPos = mul ( unity_ObjectToWorld, float4(0,0,0,1) ); //取得物体世界坐标
float2 Set_UV0 = o.uv0;
//TRANSFORM_TEX(v.texcoord,_MainTex);等价于o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
float4 _Outline_Sampler_var = tex2Dlod(_Outline_Sampler,float4(TRANSFORM_TEX(Set_UV0, _Outline_Sampler),0.0,0));//使用_Outline_Sampler的值进行缩放与偏移UV后在顶点着色器中对_Outline_Sampler进行采样。
//v.2.0.4.3 baked Normal Texture for Outline
//计算法线以及法向空间矩阵
o.normalDir = UnityObjectToWorldNormal(v.normal);
o.tangentDir = normalize( mul( unity_ObjectToWorld, float4( v.tangent.xyz, 0.0 ) ).xyz );
o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w);
float3x3 tangentTransform = float3x3( o.tangentDir, o.bitangentDir, o.normalDir);
//UnpackNormal() can't be used, and so as follows. Do not specify a bump for the texture to be used.
//采样_BakedNormal贴图值并解算烘焙法线方向
float4 _BakedNormal_var = (tex2Dlod(_BakedNormal,float4(TRANSFORM_TEX(Set_UV0, _BakedNormal),0.0,0)) * 2 - 1);
float3 _BakedNormalDir = normalize(mul(_BakedNormal_var.rgb, tangentTransform));
//end
float Set_Outline_Width = (_Outline_Width*0.001*smoothstep( _Farthest_Distance, _Nearest_Distance, distance(objPos.rgb,_WorldSpaceCameraPos) )*_Outline_Sampler_var.rgb).r;
Set_Outline_Width *= (1.0f - _ZOverDrawMode);
//v.2.0.7.5
//计算裁剪位置以及按照不同平台来设置_Offset_Z
float4 _ClipCameraPos = mul(UNITY_MATRIX_VP, float4(_WorldSpaceCameraPos.xyz, 1));
//v.2.0.7
#if defined(UNITY_REVERSED_Z)
//v.2.0.4.2 (DX)
_Offset_Z = _Offset_Z * -0.01;
#else
//OpenGL
_Offset_Z = _Offset_Z * 0.01;
#endif
//v2.0.4
//根据OutlineMode对顶线位置进行偏移。
#ifdef _OUTLINE_NML
//v.2.0.4.3 baked Normal Texture for Outline
o.pos = UnityObjectToClipPos(lerp(float4(v.vertex.xyz + v.normal*Set_Outline_Width,1), float4(v.vertex.xyz + _BakedNormalDir*Set_Outline_Width,1),_Is_BakedNormal));
#elif _OUTLINE_POS
Set_Outline_Width = Set_Outline_Width*2;
float signVar = dot(normalize(v.vertex),normalize(v.normal))<0 ? -1 : 1;
o.pos = UnityObjectToClipPos(float4(v.vertex.xyz + signVar*normalize(v.vertex)*Set_Outline_Width, 1));
#endif
//v.2.0.7.5
o.pos.z = o.pos.z + _Offset_Z * _ClipCameraPos.z;
return o;
}
float4 frag(VertexOutput i) : SV_Target{
//v.2.0.5
if (_ZOverDrawMode > 0.99f)
{
return float4(1.0f, 1.0f, 1.0f, 1.0f); // but nothing should be drawn except Z value as colormask is set to 0
}
_Color = _BaseColor;
float4 objPos = mul ( unity_ObjectToWorld, float4(0,0,0,1) );
//v.2.0.7.5
half3 ambientSkyColor = unity_AmbientSky.rgb>0.05 ? unity_AmbientSky.rgb*_Unlit_Intensity : half3(0.05,0.05,0.05)*_Unlit_Intensity;
float3 lightColor = _LightColor0.rgb >0.05 ? _LightColor0.rgb : ambientSkyColor.rgb;
float lightColorIntensity = (0.299*lightColor.r + 0.587*lightColor.g + 0.114*lightColor.b);
lightColor = lightColorIntensity<1 ? lightColor : lightColor/lightColorIntensity;
lightColor = lerp(half3(1.0,1.0,1.0), lightColor, _Is_LightColor_Outline);
float2 Set_UV0 = i.uv0;
float4 _MainTex_var = SAMPLE_TEXTURE2D(_MainTex,sampler_MainTex, TRANSFORM_TEX(Set_UV0, _MainTex));
float3 Set_BaseColor = _BaseColor.rgb*_MainTex_var.rgb;
float3 _Is_BlendBaseColor_var = lerp( _Outline_Color.rgb*lightColor, (_Outline_Color.rgb*Set_BaseColor*Set_BaseColor*lightColor), _Is_BlendBaseColor );
//
float3 _OutlineTex_var = tex2D(_OutlineTex,TRANSFORM_TEX(Set_UV0, _OutlineTex)).rgb;
//v.2.0.7.5
#ifdef _IS_OUTLINE_CLIPPING_NO
float3 Set_Outline_Color = lerp(_Is_BlendBaseColor_var, _OutlineTex_var.rgb*_Outline_Color.rgb*lightColor, _Is_OutlineTex );
return float4(Set_Outline_Color,1.0);
#elif _IS_OUTLINE_CLIPPING_YES
float4 _ClippingMask_var = SAMPLE_TEXTURE2D(_ClippingMask, sampler_MainTex, TRANSFORM_TEX(Set_UV0, _ClippingMask));
float Set_MainTexAlpha = _MainTex_var.a;
float _IsBaseMapAlphaAsClippingMask_var = lerp( _ClippingMask_var.r, Set_MainTexAlpha, _IsBaseMapAlphaAsClippingMask );
float _Inverse_Clipping_var = lerp( _IsBaseMapAlphaAsClippingMask_var, (1.0 - _IsBaseMapAlphaAsClippingMask_var), _Inverse_Clipping );
float Set_Clipping = saturate((_Inverse_Clipping_var+_Clipping_Level));
clip(Set_Clipping - 0.5);
float4 Set_Outline_Color = lerp( float4(_Is_BlendBaseColor_var,Set_Clipping), float4((_OutlineTex_var.rgb*_Outline_Color.rgb*lightColor),Set_Clipping), _Is_OutlineTex );
return Set_Outline_Color;
#endif
}
```

View File

@@ -0,0 +1,753 @@
## 总览
- `DoubleShadeWithFeather`UTS/UniversalToon 的标准工作流程模式。允许 2 种阴影颜色(双阴影颜色)和颜色之间的渐变(羽化)。
- `ShadingGradeMap`:更高级的工作流程模式。除了 DoubleShadeWithFeather 功能之外,此着色器还可以保存称为 ShadingGradeMap 的特殊贴图。
![](https://github.com/unity3d-jp/UnityChanToonShaderVer2_Project/raw/release/urp/2.3.0/Documentation~/Images_jpg/URP_image035.jpg)
### 案例文件
示例文件于`urp-2.2.0-notpackage`标签之后被移除所以可以切换到该commit将示例文件复制出来。
- ToonShader.unity Settings for an illustration-style shader.
- ToonShader_CelLook.unity Settings for a cel-style shader.
- ToonShader_Emissive.unity Settings for a shader with an emissive .
- ToonShader_Firefly.unity Multiple real-time point lights.
- AngelRing\AngelRing.unityAngel ring and ShadingGradeMap sample.
- Baked Normal\Cube_HardEdge.unityBaked Normal reference.
- BoxProjection\BoxProjection.unity Lighting a dark room using Box Projection.
- EmissiveAnimation\EmisssiveAnimation.unityEmissiveAnimation sample.
- LightAndShadows\LightAndShadows.unityComparison between the PBR shader and UTS2.
- MatCapMask\MatCapMask.unityMatcapMask sample.
- Mirror\MirrorTest.unity: Sample scene checking for a mirror object
- NormalMap\NormalMap.unity Tricks for using the normal map with UTS2.
- PointLightTest\PointLightTest.unitySample of cel-style content with point lights.
- Sample\Sample.unity Introduction to the basic UTS2 shaders.
- ShaderBall\ShaderBall.unityUTS2 settings on an example shader ball.
## 贴图
### 基础贴图
- Base Color
- 1st Shade Color
- 2nd Shade Color
除了基础的颜色纹理还可以接受多种自定义选项,例如
- High Color
- Rim Light
- MatCap
- Emissive
### 法线贴图
法线贴图一般在`UTS/UniversalToon`中用于平滑阴影渐变效果。
此外还通过与比例尺一起使用来调整皮肤纹理,以及使用`MatCap`表现头发高光效果。
| `选项` | 函数 | 属性 |
|:-------------------|:-------------------|:-------------------|
| NormalMap Effectiveness | 选择是否在每种颜色上反射法线贴图。如果按钮为Off则该颜色不会反映法线贴图而是由对象本身的几何形状评估。 | |
| `3 Basic Colors` | 当您希望法线贴图反映在颜色中时,请设置为活动。 | _Is_NormalMapToBase |
| `HighColor` | 当您希望法线贴图影响高颜色时,请设置活动。 | _Is_NormalMapToHighColor |
| `RimLight` | 当您希望法线贴图影响 RimLight 时请设置为Active。 | _Is_NormalMapToRimLight |
### 用于添加阴影区域的PositionMap
用于调整投射阴影的顶点位置。`UniversalToonBodyShadingGradeMap`模式下才能使用。
| `选项` | 函数 | 属性 |
|:-------------------|:-------------------|:-------------------|
| `1st Shade Position Map` |使用PositionMap强制修改`1st Shade Color``Position`, 与系统照明无关。指定需要进行投射阴影的区域。| _Set_1st_ShadePosition |
| `2nd Shade Position Map` |使用PositionMap强制修改`2st Shade Color``Position`, 与系统照明无关。指定需要进行投射阴影的区域。 (也会影响到`1st Shade Color``Position Map`). | _Set_2nd_ShadePosition |
为了独立于光照显示第二阴影颜色,请确保填充第一和第二阴影颜色位置图重叠的位置。这样,即使来自其他照明的阴影落在第二个阴影颜色区域,它也会继续显示。
### Shading Grade Map
基于光照来调整阴影亮度,允许在 UV 点级别控制第一和/或第二阴影颜色。
该贴图的精细控制使得“当光线照射到衣服上时隐藏褶皱”这样的效果成为可能。将诸如环境光遮蔽贴图之类的着色贴图应用到着色等级贴图可以使阴影更容易根据光照下降。这对于创建跟随头发刘海或衣服凹面部分的阴影非常有用。
### 边缘光效果(RimLight)
`RimLight``LightDirection_MaskOn``Add_Antipoden_RimLight`效果:
![](https://github.com/unity3d-jp/UnityChanToonShaderVer2_Project/raw/release/urp/2.3.0/Documentation~/Images_jpg/UT2018_UTS2_SuperTips_14.jpg)
RimLight 通常从相机的角度显示在对象边缘周围。在 UTS2 中,您可以相对于主灯的位置调整边缘灯的显示位置。('LightDirection 蒙版')
您还可以将 RimLight 设置为与光源相反的方向。您还可以使用“添加 Antipodean_RimLight”渲染“光反射”。
如果您只想在光源的相反方向上显示边缘光并沿光源方向切割边缘光,请将边缘光的光方向颜色指定为黑色 (0,0,0)。
![](https://github.com/unity3d-jp/UnityChanToonShaderVer2_Project/raw/release/urp/2.3.0/Documentation~/Images_jpg/UT2018_UTS2_SuperTips_15.jpg)
### 天使环
https://github.com/unity3d-jp/UnityChanToonShaderVer2_Project/blob/release/urp/2.3.0/Documentation~/index.md#-making-materials-for-angel-ring
制作天使环材质首先需要将头发模型正面投射到UV上做制作贴图。
## 颜色
高光颜色
通常色(被光照射部分)
1号阴影
2号阴影
## 模板功能
![](https://github.com/unity3d-jp/UnityChanToonShaderVer2_Project/raw/release/urp/2.3.0/Documentation~/Images_jpg/URP_image036.jpg)
可以用来制作睫毛挡住头发的效果。
## 半兰伯特模型
>有一个问题仍然存在。在光照无法到达的区域,模型的外观通常是全黑的,没有任何明暗变化,这会使模型的背光区域看起来就像一个平面一样,失去了模型细节表现。
为此Valve 公司在开发游戏《半条命》时提出了一种技术,由于该技术是在原兰伯特光照模型的基础上进行了一个简单的修改, 因此被称为半兰伯特光照模型。
$$Cdiffuse=(Clight · Mdiffuse) (0.5 (n· I) + 0.5)$$
把n·I 的结果范围从[-1, 1 映射到0, 1 范围内。也就是说对于模型的背光面在原兰伯特光照模型中点积结果将映射到同一个值即0 值处;而在半兰伯特模型中,背光面也可以有明暗变化,不同的点积结果会映射到不同的值上。
## 高光计算
使用半兰伯特模型计算高光Mask
```c#
//半影值计算高光Mask(半兰伯特模型)
float _Specular_var = 0.5*dot(halfDirection,lerp( i.normalDir, normalDirection, _Is_NormalMapToHighColor ))+0.5; // Specular
//Step(a,x) => x>=a ? 1 : 0,计算高光Mask
float _TweakHighColorMask_var = (saturate((_Set_HighColorMask_var.g+_Tweak_HighColorMaskLevel))*lerp( (1.0 - step(_Specular_var,(1.0 - pow(abs(_HighColor_Power),5)))), pow(abs(_Specular_var),exp2(lerp(11,1,_HighColor_Power))), _Is_SpecularToHighColor ));
```
高光颜色=高光贴图 * 高光颜色设定值 * 灯光颜色(可选)
```c#
float4 _HighColor_Tex_var = tex2D(_HighColor_Tex, TRANSFORM_TEX(Set_UV0, _HighColor_Tex));
float3 _HighColor_var = (lerp( (_HighColor_Tex_var.rgb*_HighColor.rgb), ((_HighColor_Tex_var.rgb*_HighColor.rgb)*Set_LightColor), _Is_LightColor_HighColor )*_TweakHighColorMask_var);
//Composition: 3 Basic Colors and HighColor as Set_HighColor
float3 Set_HighColor = (lerp(SATURATE_IF_SDR((Set_FinalBaseColor-_TweakHighColorMask_var)), Set_FinalBaseColor, lerp(_Is_BlendAddToHiColor,1.0,_Is_SpecularToHighColor) )+lerp( _HighColor_var, (_HighColor_var*((1.0 - Set_FinalShadowMask)+(Set_FinalShadowMask*_TweakHighColorOnShadow))), _Is_UseTweakHighColorOnShadow ));
```
## 边缘光
边缘光颜色=边缘光贴图 * 边缘光颜色设定值 * 灯光颜色(可选),其他调整参数有`_Ap_RimLight_Power`、``
```c#
float4 _Set_RimLightMask_var = tex2D(_Set_RimLightMask, TRANSFORM_TEX(Set_UV0, _Set_RimLightMask));
float3 _Is_LightColor_RimLight_var = lerp( _RimLightColor.rgb, (_RimLightColor.rgb*Set_LightColor), _Is_LightColor_RimLight );
//区域计算=1-dot(ViewDir,Normal),之后pow(_RimArea_var,exp2(lerp(3,0,_RimLight_Power)))最后使用UTS祖传公式计算
float _RimArea_var = abs(1.0 - dot(lerp( i.normalDir, normalDirection, _Is_NormalMapToRimLight ),viewDirection));
float _RimLightPower_var = pow(_RimArea_var,exp2(lerp(3,0,_RimLight_Power)));
float _Rimlight_InsideMask_var = saturate(lerp( (0.0 + ( (_RimLightPower_var - _RimLight_InsideMask) * (1.0 - 0.0) ) / (1.0 - _RimLight_InsideMask)), step(_RimLight_InsideMask,_RimLightPower_var), _RimLight_FeatherOff ));
float _VertHalfLambert_var = 0.5*dot(i.normalDir,lightDirection)+0.5;
float3 _LightDirection_MaskOn_var = lerp( (_Is_LightColor_RimLight_var*_Rimlight_InsideMask_var), (_Is_LightColor_RimLight_var*saturate((_Rimlight_InsideMask_var-((1.0 - _VertHalfLambert_var)+_Tweak_LightDirection_MaskLevel)))), _LightDirection_MaskOn );
float _ApRimLightPower_var = pow(_RimArea_var,exp2(lerp(3,0,_Ap_RimLight_Power)));
float3 Set_RimLight = (SATURATE_IF_SDR((_Set_RimLightMask_var.g+_Tweak_RimLightMaskLevel))*lerp( _LightDirection_MaskOn_var, (_LightDirection_MaskOn_var+(lerp( _Ap_RimLightColor.rgb, (_Ap_RimLightColor.rgb*Set_LightColor), _Is_LightColor_Ap_RimLight )*saturate((lerp( (0.0 + ( (_ApRimLightPower_var - _RimLight_InsideMask) * (1.0 - 0.0) ) / (1.0 - _RimLight_InsideMask)), step(_RimLight_InsideMask,_ApRimLightPower_var), _Ap_RimLight_FeatherOff )-(saturate(_VertHalfLambert_var)+_Tweak_LightDirection_MaskLevel))))), _Add_Antipodean_RimLight ));
//Composition: HighColor and RimLight as _RimLight_var
float3 _RimLight_var = lerp( Set_HighColor, (Set_HighColor+Set_RimLight), _RimLight );
```
## Matcap与头发的各向异性高光效果
UTS的老版本模型采用了Matcap给头发圈定高光颜色以及区域范围之后使用法线贴图来实现类似各向异性的效果
```c#
//Matcap
//v.2.0.6 : CameraRolling Stabilizer
//Mirror Script Determination: if sign_Mirror = -1, determine "Inside the mirror".
//v.2.0.7
fixed _sign_Mirror = i.mirrorFlag;
//
float3 _Camera_Right = UNITY_MATRIX_V[0].xyz;
float3 _Camera_Front = UNITY_MATRIX_V[2].xyz;
float3 _Up_Unit = float3(0, 1, 0);
float3 _Right_Axis = cross(_Camera_Front, _Up_Unit);
//Invert if it's "inside the mirror".
if(_sign_Mirror < 0){
_Right_Axis = -1 * _Right_Axis;
_Rotate_MatCapUV = -1 * _Rotate_MatCapUV;
}else{
_Right_Axis = _Right_Axis;
}
float _Camera_Right_Magnitude = sqrt(_Camera_Right.x*_Camera_Right.x + _Camera_Right.y*_Camera_Right.y + _Camera_Right.z*_Camera_Right.z);
float _Right_Axis_Magnitude = sqrt(_Right_Axis.x*_Right_Axis.x + _Right_Axis.y*_Right_Axis.y + _Right_Axis.z*_Right_Axis.z);
float _Camera_Roll_Cos = dot(_Right_Axis, _Camera_Right) / (_Right_Axis_Magnitude * _Camera_Right_Magnitude);
float _Camera_Roll = acos(clamp(_Camera_Roll_Cos, -1, 1));
fixed _Camera_Dir = _Camera_Right.y < 0 ? -1 : 1;
float _Rot_MatCapUV_var_ang = (_Rotate_MatCapUV*3.141592654) - _Camera_Dir*_Camera_Roll*_CameraRolling_Stabilizer;
//v.2.0.7
float2 _Rot_MatCapNmUV_var = RotateUV(Set_UV0, (_Rotate_NormalMapForMatCapUV*3.141592654), float2(0.5, 0.5), 1.0);
//V.2.0.6
float3 _NormalMapForMatCap_var = UnpackNormalScale(tex2D(_NormalMapForMatCap, TRANSFORM_TEX(_Rot_MatCapNmUV_var, _NormalMapForMatCap)), _BumpScaleMatcap);
//v.2.0.5: MatCap with camera skew correction
float3 viewNormal = (mul(UNITY_MATRIX_V, float4(lerp( i.normalDir, mul( _NormalMapForMatCap_var.rgb, tangentTransform ).rgb, _Is_NormalMapForMatCap ),0))).rgb;
float3 NormalBlend_MatcapUV_Detail = viewNormal.rgb * float3(-1,-1,1);
float3 NormalBlend_MatcapUV_Base = (mul( UNITY_MATRIX_V, float4(viewDirection,0) ).rgb*float3(-1,-1,1)) + float3(0,0,1);
float3 noSknewViewNormal = NormalBlend_MatcapUV_Base*dot(NormalBlend_MatcapUV_Base, NormalBlend_MatcapUV_Detail)/NormalBlend_MatcapUV_Base.b - NormalBlend_MatcapUV_Detail;
float2 _ViewNormalAsMatCapUV = (lerp(noSknewViewNormal,viewNormal,_Is_Ortho).rg*0.5)+0.5;
//
//v.2.0.7
float2 _Rot_MatCapUV_var = RotateUV((0.0 + ((_ViewNormalAsMatCapUV - (0.0+_Tweak_MatCapUV)) * (1.0 - 0.0) ) / ((1.0-_Tweak_MatCapUV) - (0.0+_Tweak_MatCapUV))), _Rot_MatCapUV_var_ang, float2(0.5, 0.5), 1.0);
//If it is "inside the mirror", flip the UV left and right.
if(_sign_Mirror < 0){
_Rot_MatCapUV_var.x = 1-_Rot_MatCapUV_var.x;
}else{
_Rot_MatCapUV_var = _Rot_MatCapUV_var;
}
float4 _MatCap_Sampler_var = tex2Dlod(_MatCap_Sampler, float4(TRANSFORM_TEX(_Rot_MatCapUV_var, _MatCap_Sampler), 0.0, _BlurLevelMatcap));
float4 _Set_MatcapMask_var = tex2D(_Set_MatcapMask, TRANSFORM_TEX(Set_UV0, _Set_MatcapMask));
```
## UniversalToonBodyShadingGradeMap
合成公式:
$$1-\frac{( HalfLambert - (Step-Feather))}{Feather}$$
### 初始化数据
初始化表面数据、InputData与Varyings并且填充数据。
### 灯光与环境光数据
```c#
//计算间接照明值使用间接Diffuse乘以SurfaceDiffuse间接Specular乘以SurfaceSpecular最后两者叠加。
half3 envColor = GlobalIlluminationUTS(brdfData, inputData.bakedGI, surfaceData.occlusion, inputData.normalWS, inputData.viewDirectionWS);
envColor *= 1.8f;
//取得主光参数direction、color、distanceAttenuation、shadowAttenuation、type
UtsLight mainLight = GetMainUtsLightByID(i.mainLightID, i.posWorld.xyz, inputData.shadowCoord, i.positionCS);
half3 mainLightColor = GetLightColor(mainLight);
real shadowAttenuation = 1.0;
# ifdef _MAIN_LIGHT_SHADOWS
shadowAttenuation = mainLight.shadowAttenuation;
# endif
```
### 开启裁剪功能
```c#
float4 _ClippingMask_var = SAMPLE_TEXTURE2D(_ClippingMask, sampler_MainTex, TRANSFORM_TEX(Set_UV0, _ClippingMask));
float Set_MainTexAlpha = _MainTex_var.a;
float _IsBaseMapAlphaAsClippingMask_var = lerp( _ClippingMask_var.r, Set_MainTexAlpha, _IsBaseMapAlphaAsClippingMask );
float _Inverse_Clipping_var = lerp( _IsBaseMapAlphaAsClippingMask_var, (1.0 - _IsBaseMapAlphaAsClippingMask_var), _Inverse_Clipping );
float Set_Clipping = saturate((_Inverse_Clipping_var+_Clipping_Level));
clip(Set_Clipping - 0.5);
```
### 计算灯光方向与灯光颜色
```c#
float3 defaultLightDirection = normalize(UNITY_MATRIX_V[2].xyz + UNITY_MATRIX_V[1].xyz);
//v.2.0.5
float3 defaultLightColor = saturate(max(half3(0.05,0.05,0.05)*_Unlit_Intensity,max(ShadeSH9(half4(0.0, 0.0, 0.0, 1.0)),ShadeSH9(half4(0.0, -1.0, 0.0, 1.0)).rgb)*_Unlit_Intensity));
//通过外部传入的_Offset_X_Axis_BLD、_Offset_Y_Axis_BLD、_Offset_Z_Axis_BLD来创建自定义灯光方向
float3 customLightDirection = normalize(mul( unity_ObjectToWorld, float4(((float3(1.0,0.0,0.0)*_Offset_X_Axis_BLD*10)+(float3(0.0,1.0,0.0)*_Offset_Y_Axis_BLD*10)+(float3(0.0,0.0,-1.0)*lerp(-1.0,1.0,_Inverse_Z_Axis_BLD))),0)).xyz);
//HLSL内置函数Any(),用于测试变量是否为非零值
float3 lightDirection = normalize(lerp(defaultLightDirection, mainLight.direction.xyz,any(mainLight.direction.xyz)));
lightDirection = lerp(lightDirection, customLightDirection, _Is_BLD);
//v.2.0.5:
half3 originalLightColor = mainLightColor.rgb;
float3 lightColor = lerp(max(defaultLightColor, originalLightColor), max(defaultLightColor, saturate(originalLightColor)), _Is_Filter_LightColor);
```
### 计算1、2、3 ShadowMask与插值后的BaseColor值之后合并到一起
### 计算高光并且合并到一起
### 计算天使环并且合并到一起
### 计算Matcap并且合成到一起
### 叠加自发光贴图结果
### 灯光循环
### 最终合成
```c#
finalColor = SATURATE_IF_SDR(finalColor) + (envLightColor*envLightIntensity*_GI_Intensity*smoothstep(1,0,envLightIntensity/2)) + emissive;
finalColor += pointLightColor;
```
## 完整代码
```c#
float4 fragShadingGradeMap(VertexOutput i, fixed facing : VFACE) : SV_TARGET
{
//计算世界法线值与摄像机朝向
i.normalDir = normalize(i.normalDir);
float3x3 tangentTransform = float3x3( i.tangentDir, i.bitangentDir, i.normalDir);
float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
float2 Set_UV0 = i.uv0;
//v.2.0.6
float3 _NormalMap_var = UnpackNormalScale(SAMPLE_TEXTURE2D(_NormalMap, sampler_MainTex, TRANSFORM_TEX(Set_UV0, _NormalMap)), _BumpScale);
float3 normalLocal = _NormalMap_var.rgb;
float3 normalDirection = normalize(mul( normalLocal, tangentTransform )); // Perturbed normals
// todo. not necessary to calc gi factor in shadowcaster pass.
//初始化表面数据、InputData与Varyings并且填充数据。
//input数据位于render-pipelines.universal包含positionWS、normalWS、viewDirectionWS、shadowCoord、fogCoord、vertexLighting、bakedGI,Varyings input用来计算InputData。Vrayings位于LitForwardPass中
//inputData主要用来计算envColorGI、主灯光数据与AdditionalUtsLight数据
SurfaceData surfaceData;
InitializeStandardLitSurfaceDataUTS(i.uv0, surfaceData);
InputData inputData;
Varyings input = (Varyings)0;
// todo. it has to be cared more.
UNITY_SETUP_INSTANCE_ID(input);
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
# ifdef LIGHTMAP_ON
# else
input.vertexSH = i.vertexSH;
# endif
input.uv = i.uv0;
# if defined(_ADDITIONAL_LIGHTS_VERTEX) || (VERSION_LOWER(12, 0))
input.fogFactorAndVertexLight = i.fogFactorAndVertexLight;
# else
input.fogFactor = i.fogFactor;
# endif
# ifdef REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR
input.shadowCoord = i.shadowCoord;
# endif
# ifdef REQUIRES_WORLD_SPACE_POS_INTERPOLATOR
input.positionWS = i.posWorld.xyz;
# endif
# ifdef _NORMALMAP
input.normalWS = half4(i.normalDir, viewDirection.x); // xyz: normal, w: viewDir.x
input.tangentWS = half4(i.tangentDir, viewDirection.y); // xyz: tangent, w: viewDir.y
# if (VERSION_LOWER(7, 5))
input.bitangentWS = half4(i.bitangentDir, viewDirection.z); // xyz: bitangent, w: viewDir.z
#endif //
# else
input.normalWS = half3(i.normalDir);
# if (VERSION_LOWER(12, 0))
input.viewDirWS = half3(viewDirection);
# endif //(VERSION_LOWER(12, 0))
# endif
//位于LitForwardPass中其中SAMPLE_GI()采样LightMap或者SH。
InitializeInputData(input, surfaceData.normalTS, inputData);
//位于Lighting中使用前几个参数初始化最后一个brdfData形参
BRDFData brdfData;
InitializeBRDFData(surfaceData.albedo,
surfaceData.metallic,
surfaceData.specular,
surfaceData.smoothness,
surfaceData.alpha, brdfData);
//计算间接照明值使用间接Diffuse乘以SurfaceDiffuse间接Specular乘以SurfaceSpecular最后两者叠加。
half3 envColor = GlobalIlluminationUTS(brdfData, inputData.bakedGI, surfaceData.occlusion, inputData.normalWS, inputData.viewDirectionWS);
envColor *= 1.8f;
//取得主光参数direction、color、distanceAttenuation、shadowAttenuation、type
UtsLight mainLight = GetMainUtsLightByID(i.mainLightID, i.posWorld.xyz, inputData.shadowCoord, i.positionCS);
half3 mainLightColor = GetLightColor(mainLight);
float4 _MainTex_var = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, TRANSFORM_TEX(Set_UV0, _MainTex));
//v.2.0.4
#ifdef _IS_TRANSCLIPPING_OFF
//
#elif _IS_TRANSCLIPPING_ON
//开启裁剪功能
float4 _ClippingMask_var = SAMPLE_TEXTURE2D(_ClippingMask, sampler_MainTex, TRANSFORM_TEX(Set_UV0, _ClippingMask));
float Set_MainTexAlpha = _MainTex_var.a;
float _IsBaseMapAlphaAsClippingMask_var = lerp( _ClippingMask_var.r, Set_MainTexAlpha, _IsBaseMapAlphaAsClippingMask );
float _Inverse_Clipping_var = lerp( _IsBaseMapAlphaAsClippingMask_var, (1.0 - _IsBaseMapAlphaAsClippingMask_var), _Inverse_Clipping );
float Set_Clipping = saturate((_Inverse_Clipping_var+_Clipping_Level));
clip(Set_Clipping - 0.5);
#endif
real shadowAttenuation = 1.0;
# ifdef _MAIN_LIGHT_SHADOWS
shadowAttenuation = mainLight.shadowAttenuation;
# endif
//v.2.0.4
float3 defaultLightDirection = normalize(UNITY_MATRIX_V[2].xyz + UNITY_MATRIX_V[1].xyz);
//v.2.0.5
float3 defaultLightColor = saturate(max(half3(0.05,0.05,0.05)*_Unlit_Intensity,max(ShadeSH9(half4(0.0, 0.0, 0.0, 1.0)),ShadeSH9(half4(0.0, -1.0, 0.0, 1.0)).rgb)*_Unlit_Intensity));
//通过外部传入的_Offset_X_Axis_BLD、_Offset_Y_Axis_BLD、_Offset_Z_Axis_BLD来创建自定义灯光方向
float3 customLightDirection = normalize(mul( unity_ObjectToWorld, float4(((float3(1.0,0.0,0.0)*_Offset_X_Axis_BLD*10)+(float3(0.0,1.0,0.0)*_Offset_Y_Axis_BLD*10)+(float3(0.0,0.0,-1.0)*lerp(-1.0,1.0,_Inverse_Z_Axis_BLD))),0)).xyz);
//HLSL内置函数Any(),用于测试变量是否为非零值
float3 lightDirection = normalize(lerp(defaultLightDirection, mainLight.direction.xyz,any(mainLight.direction.xyz)));
lightDirection = lerp(lightDirection, customLightDirection, _Is_BLD);
//v.2.0.5:
half3 originalLightColor = mainLightColor.rgb;
float3 lightColor = lerp(max(defaultLightColor, originalLightColor), max(defaultLightColor, saturate(originalLightColor)), _Is_Filter_LightColor);
////// Lighting:
//计算View与主Light的半向量
float3 halfDirection = normalize(viewDirection+lightDirection);
//v.2.0.5
_Color = _BaseColor;
#ifdef _IS_PASS_FWDBASE
//取得设置的LightColor、BaseColor(设置的灯光颜色or主贴图采样结果)、1st_ShadeMap_var最后计算_Is_LightColor_1st_Shade_var与半兰伯特值。
float3 Set_LightColor = lightColor.rgb;
float3 Set_BaseColor = lerp( (_MainTex_var.rgb*_BaseColor.rgb), ((_MainTex_var.rgb*_BaseColor.rgb)*Set_LightColor), _Is_LightColor_Base );
//v.2.0.5
float4 _1st_ShadeMap_var = lerp(SAMPLE_TEXTURE2D(_1st_ShadeMap,sampler_MainTex, TRANSFORM_TEX(Set_UV0, _1st_ShadeMap)),_MainTex_var,_Use_BaseAs1st);
//_1st_ShadeMap_var为确定区域颜色为设置的颜色。_1st_ShadeMap_var.rgb*_1st_ShadeColor.rgb*Set_LightColor可设置为不受LightColor影响。
float3 _Is_LightColor_1st_Shade_var = lerp( (_1st_ShadeMap_var.rgb*_1st_ShadeColor.rgb), ((_1st_ShadeMap_var.rgb*_1st_ShadeColor.rgb)*Set_LightColor), _Is_LightColor_1st_Shade );
float _HalfLambert_var = 0.5*dot(lerp( i.normalDir, normalDirection, _Is_NormalMapToBase ),lightDirection)+0.5; // Half Lambert
//v.2.0.6
float4 _ShadingGradeMap_var = tex2Dlod(_ShadingGradeMap, float4(TRANSFORM_TEX(Set_UV0, _ShadingGradeMap), 0.0, _BlurLevelSGM));
//the value of shadowAttenuation is darker than legacy and it cuases noise in terminaters.
#if !defined (UTS_USE_RAYTRACING_SHADOW)
shadowAttenuation *= 2.0f;
shadowAttenuation = saturate(shadowAttenuation);
#endif
//v.2.0.6
//Minmimum value is same as the Minimum Feather's value with the Minimum Step's value as threshold.
//_Tweak_SystemShadowsLevel控制 Unity 的系统阴影级别。默认为 0级别可调整为 ±0.5。
//使用_Tweak_SystemShadowsLevel来偏移ShadingGradeMap中定义的阴影范围
//最后计算ShadowMask:Set_ShadingGrade=_HalfLambert_var*saturate(_SystemShadowsLevel_var)的值。
float _SystemShadowsLevel_var = (shadowAttenuation *0.5)+0.5+_Tweak_SystemShadowsLevel > 0.001 ? (shadowAttenuation *0.5)+0.5+_Tweak_SystemShadowsLevel : 0.0001;
float _ShadingGradeMapLevel_var = _ShadingGradeMap_var.r < 0.95 ? _ShadingGradeMap_var.r+_Tweak_ShadingGradeMapLevel : 1;
float Set_ShadingGrade = saturate(_ShadingGradeMapLevel_var)*lerp( _HalfLambert_var, (_HalfLambert_var*saturate(_SystemShadowsLevel_var)), _Set_SystemShadowsToBase );
//float Set_ShadingGrade = saturate(_ShadingGradeMapLevel_var)*lerp( _HalfLambert_var, (_HalfLambert_var*saturate(1.0+_Tweak_SystemShadowsLevel)), _Set_SystemShadowsToBase );
//计算1、2、3 ShadowMask与插值后的BaseColor值之后合并到一起。
//lerp( (_2nd_ShadeMap_var.rgb*_2nd_ShadeColor.rgb), ((_2nd_ShadeMap_var.rgb*_2nd_ShadeColor.rgb)*Set_LightColor), _Is_LightColor_2nd_Shade )
//lerp(_Is_LightColor_1st_Shade_var,上一次插值结果Set_ShadeShadowMask)
//lerp(_BaseColor_var,上一次插值结果,Set_FinalShadowMask)
//合成方式为:
float Set_FinalShadowMask = saturate((1.0 + ( (Set_ShadingGrade - (_1st_ShadeColor_Step-_1st_ShadeColor_Feather)) * (0.0 - 1.0) ) / (_1st_ShadeColor_Step - (_1st_ShadeColor_Step-_1st_ShadeColor_Feather)))); // Base and 1st Shade Mask
float3 _BaseColor_var = lerp(Set_BaseColor,_Is_LightColor_1st_Shade_var,Set_FinalShadowMask);
//v.2.0.5
float4 _2nd_ShadeMap_var = lerp(SAMPLE_TEXTURE2D(_2nd_ShadeMap,sampler_MainTex, TRANSFORM_TEX(Set_UV0, _2nd_ShadeMap)),_1st_ShadeMap_var,_Use_1stAs2nd);
float Set_ShadeShadowMask = saturate((1.0 + ( (Set_ShadingGrade - (_2nd_ShadeColor_Step-_2nd_ShadeColor_Feather)) * (0.0 - 1.0) ) / (_2nd_ShadeColor_Step - (_2nd_ShadeColor_Step-_2nd_ShadeColor_Feather)))); // 1st and 2nd Shades Mask
//Composition: 3 Basic Colors as Set_FinalBaseColor
float3 Set_FinalBaseColor = lerp(_BaseColor_var,lerp(_Is_LightColor_1st_Shade_var,lerp( (_2nd_ShadeMap_var.rgb*_2nd_ShadeColor.rgb), ((_2nd_ShadeMap_var.rgb*_2nd_ShadeColor.rgb)*Set_LightColor), _Is_LightColor_2nd_Shade ),Set_ShadeShadowMask),Set_FinalShadowMask);
//采样高光Mask
float4 _Set_HighColorMask_var = tex2D(_Set_HighColorMask, TRANSFORM_TEX(Set_UV0, _Set_HighColorMask));
//半影值计算高光Mask(半兰伯特模型)
float _Specular_var = 0.5*dot(halfDirection,lerp( i.normalDir, normalDirection, _Is_NormalMapToHighColor ))+0.5; // Specular
//Step(a,x) => x>=a ? 1 : 0,计算高光Mask
float _TweakHighColorMask_var = (saturate((_Set_HighColorMask_var.g+_Tweak_HighColorMaskLevel))*lerp( (1.0 - step(_Specular_var,(1.0 - pow(abs(_HighColor_Power),5)))), pow(abs(_Specular_var),exp2(lerp(11,1,_HighColor_Power))), _Is_SpecularToHighColor ));
float4 _HighColor_Tex_var = tex2D(_HighColor_Tex, TRANSFORM_TEX(Set_UV0, _HighColor_Tex));
float3 _HighColor_var = (lerp( (_HighColor_Tex_var.rgb*_HighColor.rgb), ((_HighColor_Tex_var.rgb*_HighColor.rgb)*Set_LightColor), _Is_LightColor_HighColor )*_TweakHighColorMask_var);
//Composition: 3 Basic Colors and HighColor as Set_HighColor
float3 Set_HighColor = (lerp(SATURATE_IF_SDR((Set_FinalBaseColor-_TweakHighColorMask_var)), Set_FinalBaseColor, lerp(_Is_BlendAddToHiColor,1.0,_Is_SpecularToHighColor) )+lerp( _HighColor_var, (_HighColor_var*((1.0 - Set_FinalShadowMask)+(Set_FinalShadowMask*_TweakHighColorOnShadow))), _Is_UseTweakHighColorOnShadow ));
//开始计算边缘光
float4 _Set_RimLightMask_var = tex2D(_Set_RimLightMask, TRANSFORM_TEX(Set_UV0, _Set_RimLightMask));
float3 _Is_LightColor_RimLight_var = lerp( _RimLightColor.rgb, (_RimLightColor.rgb*Set_LightColor), _Is_LightColor_RimLight );
float _RimArea_var = abs(1.0 - dot(lerp( i.normalDir, normalDirection, _Is_NormalMapToRimLight ),viewDirection));
float _RimLightPower_var = pow(_RimArea_var,exp2(lerp(3,0,_RimLight_Power)));
float _Rimlight_InsideMask_var = saturate(lerp( (0.0 + ( (_RimLightPower_var - _RimLight_InsideMask) * (1.0 - 0.0) ) / (1.0 - _RimLight_InsideMask)), step(_RimLight_InsideMask,_RimLightPower_var), _RimLight_FeatherOff ));
float _VertHalfLambert_var = 0.5*dot(i.normalDir,lightDirection)+0.5;
float3 _LightDirection_MaskOn_var = lerp( (_Is_LightColor_RimLight_var*_Rimlight_InsideMask_var), (_Is_LightColor_RimLight_var*saturate((_Rimlight_InsideMask_var-((1.0 - _VertHalfLambert_var)+_Tweak_LightDirection_MaskLevel)))), _LightDirection_MaskOn );
float _ApRimLightPower_var = pow(_RimArea_var,exp2(lerp(3,0,_Ap_RimLight_Power)));
float3 Set_RimLight = (SATURATE_IF_SDR((_Set_RimLightMask_var.g+_Tweak_RimLightMaskLevel))*lerp( _LightDirection_MaskOn_var, (_LightDirection_MaskOn_var+(lerp( _Ap_RimLightColor.rgb, (_Ap_RimLightColor.rgb*Set_LightColor), _Is_LightColor_Ap_RimLight )*saturate((lerp( (0.0 + ( (_ApRimLightPower_var - _RimLight_InsideMask) * (1.0 - 0.0) ) / (1.0 - _RimLight_InsideMask)), step(_RimLight_InsideMask,_ApRimLightPower_var), _Ap_RimLight_FeatherOff )-(saturate(_VertHalfLambert_var)+_Tweak_LightDirection_MaskLevel))))), _Add_Antipodean_RimLight ));
//Composition: HighColor and RimLight as _RimLight_var
float3 _RimLight_var = lerp( Set_HighColor, (Set_HighColor+Set_RimLight), _RimLight );
//Matcap
//v.2.0.6 : CameraRolling Stabilizer
//Mirror Script Determination: if sign_Mirror = -1, determine "Inside the mirror".
//v.2.0.7
fixed _sign_Mirror = i.mirrorFlag;
//
float3 _Camera_Right = UNITY_MATRIX_V[0].xyz;
float3 _Camera_Front = UNITY_MATRIX_V[2].xyz;
float3 _Up_Unit = float3(0, 1, 0);
float3 _Right_Axis = cross(_Camera_Front, _Up_Unit);
//Invert if it's "inside the mirror".
if(_sign_Mirror < 0){
_Right_Axis = -1 * _Right_Axis;
_Rotate_MatCapUV = -1 * _Rotate_MatCapUV;
}else{
_Right_Axis = _Right_Axis;
}
float _Camera_Right_Magnitude = sqrt(_Camera_Right.x*_Camera_Right.x + _Camera_Right.y*_Camera_Right.y + _Camera_Right.z*_Camera_Right.z);
float _Right_Axis_Magnitude = sqrt(_Right_Axis.x*_Right_Axis.x + _Right_Axis.y*_Right_Axis.y + _Right_Axis.z*_Right_Axis.z);
float _Camera_Roll_Cos = dot(_Right_Axis, _Camera_Right) / (_Right_Axis_Magnitude * _Camera_Right_Magnitude);
float _Camera_Roll = acos(clamp(_Camera_Roll_Cos, -1, 1));
fixed _Camera_Dir = _Camera_Right.y < 0 ? -1 : 1;
float _Rot_MatCapUV_var_ang = (_Rotate_MatCapUV*3.141592654) - _Camera_Dir*_Camera_Roll*_CameraRolling_Stabilizer;
//v.2.0.7
float2 _Rot_MatCapNmUV_var = RotateUV(Set_UV0, (_Rotate_NormalMapForMatCapUV*3.141592654), float2(0.5, 0.5), 1.0);
//V.2.0.6
float3 _NormalMapForMatCap_var = UnpackNormalScale(tex2D(_NormalMapForMatCap, TRANSFORM_TEX(_Rot_MatCapNmUV_var, _NormalMapForMatCap)), _BumpScaleMatcap);
//v.2.0.5: MatCap with camera skew correction
float3 viewNormal = (mul(UNITY_MATRIX_V, float4(lerp( i.normalDir, mul( _NormalMapForMatCap_var.rgb, tangentTransform ).rgb, _Is_NormalMapForMatCap ),0))).rgb;
float3 NormalBlend_MatcapUV_Detail = viewNormal.rgb * float3(-1,-1,1);
float3 NormalBlend_MatcapUV_Base = (mul( UNITY_MATRIX_V, float4(viewDirection,0) ).rgb*float3(-1,-1,1)) + float3(0,0,1);
float3 noSknewViewNormal = NormalBlend_MatcapUV_Base*dot(NormalBlend_MatcapUV_Base, NormalBlend_MatcapUV_Detail)/NormalBlend_MatcapUV_Base.b - NormalBlend_MatcapUV_Detail;
float2 _ViewNormalAsMatCapUV = (lerp(noSknewViewNormal,viewNormal,_Is_Ortho).rg*0.5)+0.5;
//
//v.2.0.7
float2 _Rot_MatCapUV_var = RotateUV((0.0 + ((_ViewNormalAsMatCapUV - (0.0+_Tweak_MatCapUV)) * (1.0 - 0.0) ) / ((1.0-_Tweak_MatCapUV) - (0.0+_Tweak_MatCapUV))), _Rot_MatCapUV_var_ang, float2(0.5, 0.5), 1.0);
//If it is "inside the mirror", flip the UV left and right.
if(_sign_Mirror < 0){
_Rot_MatCapUV_var.x = 1-_Rot_MatCapUV_var.x;
}else{
_Rot_MatCapUV_var = _Rot_MatCapUV_var;
}
float4 _MatCap_Sampler_var = tex2Dlod(_MatCap_Sampler, float4(TRANSFORM_TEX(_Rot_MatCapUV_var, _MatCap_Sampler), 0.0, _BlurLevelMatcap));
float4 _Set_MatcapMask_var = tex2D(_Set_MatcapMask, TRANSFORM_TEX(Set_UV0, _Set_MatcapMask));
//
//MatcapMask
float _Tweak_MatcapMaskLevel_var = saturate(lerp(_Set_MatcapMask_var.g, (1.0 - _Set_MatcapMask_var.g), _Inverse_MatcapMask) + _Tweak_MatcapMaskLevel);
float3 _Is_LightColor_MatCap_var = lerp( (_MatCap_Sampler_var.rgb*_MatCapColor.rgb), ((_MatCap_Sampler_var.rgb*_MatCapColor.rgb)*Set_LightColor), _Is_LightColor_MatCap );
//v.2.0.6 : ShadowMask on Matcap in Blend mode : multiply
float3 Set_MatCap = lerp( _Is_LightColor_MatCap_var, (_Is_LightColor_MatCap_var*((1.0 - Set_FinalShadowMask)+(Set_FinalShadowMask*_TweakMatCapOnShadow)) + lerp(Set_HighColor*Set_FinalShadowMask*(1.0-_TweakMatCapOnShadow), float3(0.0, 0.0, 0.0), _Is_BlendAddToMatCap)), _Is_UseTweakMatCapOnShadow );
//
//v.2.0.6
//Composition: RimLight and MatCap as finalColor
//Broke down finalColor composition
float3 matCapColorOnAddMode = _RimLight_var+Set_MatCap*_Tweak_MatcapMaskLevel_var;
float _Tweak_MatcapMaskLevel_var_MultiplyMode = _Tweak_MatcapMaskLevel_var * lerp (1, (1 - (Set_FinalShadowMask)*(1 - _TweakMatCapOnShadow)), _Is_UseTweakMatCapOnShadow);
float3 matCapColorOnMultiplyMode = Set_HighColor*(1-_Tweak_MatcapMaskLevel_var_MultiplyMode) + Set_HighColor*Set_MatCap*_Tweak_MatcapMaskLevel_var_MultiplyMode + lerp(float3(0,0,0),Set_RimLight,_RimLight);
float3 matCapColorFinal = lerp(matCapColorOnMultiplyMode, matCapColorOnAddMode, _Is_BlendAddToMatCap);
//v.2.0.4
#ifdef _IS_ANGELRING_OFF
float3 finalColor = lerp(_RimLight_var, matCapColorFinal, _MatCap);// Final Composition before Emissive
//
#elif _IS_ANGELRING_ON
//计算天使环
float3 finalColor = lerp(_RimLight_var, matCapColorFinal, _MatCap);// Final Composition before AR
//v.2.0.7 AR Camera Rolling Stabilizer
//计算UV将表面法线转换到视角坐标将(-1,1)=》(0,1)之后,按照摄像机的 -(_Camera_Dir*_Camera_Roll)旋转坐标后,采样贴图
float3 _AR_OffsetU_var = lerp(mul(UNITY_MATRIX_V, float4(i.normalDir,0)).xyz,float3(0,0,1),_AR_OffsetU);
float2 AR_VN = _AR_OffsetU_var.xy*0.5 + float2(0.5,0.5);
float2 AR_VN_Rotate = RotateUV(AR_VN, -(_Camera_Dir*_Camera_Roll), float2(0.5,0.5), 1.0);
float2 _AR_OffsetV_var = float2(AR_VN_Rotate.x, lerp(i.uv1.y, AR_VN_Rotate.y, _AR_OffsetV));
float4 _AngelRing_Sampler_var = tex2D(_AngelRing_Sampler,TRANSFORM_TEX(_AR_OffsetV_var, _AngelRing_Sampler));
float3 _Is_LightColor_AR_var = lerp( (_AngelRing_Sampler_var.rgb*_AngelRing_Color.rgb), ((_AngelRing_Sampler_var.rgb*_AngelRing_Color.rgb)*Set_LightColor), _Is_LightColor_AR );
float3 Set_AngelRing = _Is_LightColor_AR_var;
float Set_ARtexAlpha = _AngelRing_Sampler_var.a;
float3 Set_AngelRingWithAlpha = (_Is_LightColor_AR_var*_AngelRing_Sampler_var.a);
//Composition: MatCap and AngelRing as finalColor
finalColor = lerp(finalColor, lerp((finalColor + Set_AngelRing), ((finalColor*(1.0 - Set_ARtexAlpha))+Set_AngelRingWithAlpha), _ARSampler_AlphaOn ), _AngelRing );// Final Composition before Emissive
#endif
//v.2.0.7
#ifdef _EMISSIVE_SIMPLE
float4 _Emissive_Tex_var = tex2D(_Emissive_Tex,TRANSFORM_TEX(Set_UV0, _Emissive_Tex));
float emissiveMask = _Emissive_Tex_var.a;
emissive = _Emissive_Tex_var.rgb * _Emissive_Color.rgb * emissiveMask;
#elif _EMISSIVE_ANIMATION
//v.2.0.7 Calculation View Coord UV for Scroll
float3 viewNormal_Emissive = (mul(UNITY_MATRIX_V, float4(i.normalDir,0))).xyz;
float3 NormalBlend_Emissive_Detail = viewNormal_Emissive * float3(-1,-1,1);
float3 NormalBlend_Emissive_Base = (mul( UNITY_MATRIX_V, float4(viewDirection,0)).xyz*float3(-1,-1,1)) + float3(0,0,1);
float3 noSknewViewNormal_Emissive = NormalBlend_Emissive_Base*dot(NormalBlend_Emissive_Base, NormalBlend_Emissive_Detail)/NormalBlend_Emissive_Base.z - NormalBlend_Emissive_Detail;
float2 _ViewNormalAsEmissiveUV = noSknewViewNormal_Emissive.xy*0.5+0.5;
float2 _ViewCoord_UV = RotateUV(_ViewNormalAsEmissiveUV, -(_Camera_Dir*_Camera_Roll), float2(0.5,0.5), 1.0);
//鏡の中ならUV左右反転.
if(_sign_Mirror < 0){
_ViewCoord_UV.x = 1-_ViewCoord_UV.x;
}else{
_ViewCoord_UV = _ViewCoord_UV;
}
float2 emissive_uv = lerp(i.uv0, _ViewCoord_UV, _Is_ViewCoord_Scroll);
//
float4 _time_var = _Time;
float _base_Speed_var = (_time_var.g*_Base_Speed);
float _Is_PingPong_Base_var = lerp(_base_Speed_var, sin(_base_Speed_var), _Is_PingPong_Base );
float2 scrolledUV = emissive_uv + float2(_Scroll_EmissiveU, _Scroll_EmissiveV)*_Is_PingPong_Base_var;
float rotateVelocity = _Rotate_EmissiveUV*3.141592654;
float2 _rotate_EmissiveUV_var = RotateUV(scrolledUV, rotateVelocity, float2(0.5, 0.5), _Is_PingPong_Base_var);
float4 _Emissive_Tex_var = tex2D(_Emissive_Tex,TRANSFORM_TEX(Set_UV0, _Emissive_Tex));
float emissiveMask = _Emissive_Tex_var.a;
_Emissive_Tex_var = tex2D(_Emissive_Tex,TRANSFORM_TEX(_rotate_EmissiveUV_var, _Emissive_Tex));
float _colorShift_Speed_var = 1.0 - cos(_time_var.g*_ColorShift_Speed);
float viewShift_var = smoothstep( 0.0, 1.0, max(0,dot(normalDirection,viewDirection)));
float4 colorShift_Color = lerp(_Emissive_Color, lerp(_Emissive_Color, _ColorShift, _colorShift_Speed_var), _Is_ColorShift);
float4 viewShift_Color = lerp(_ViewShift, colorShift_Color, viewShift_var);
float4 emissive_Color = lerp(colorShift_Color, viewShift_Color, _Is_ViewShift);
emissive = emissive_Color.rgb * _Emissive_Tex_var.rgb * emissiveMask;
#endif
//
//v.2.0.6: GI_Intensity with Intensity Multiplier Filter
float3 envLightColor = envColor.rgb;
float envLightIntensity = 0.299*envLightColor.r + 0.587*envLightColor.g + 0.114*envLightColor.b <1 ? (0.299*envLightColor.r + 0.587*envLightColor.g + 0.114*envLightColor.b) : 1;
float3 pointLightColor = 0;
#ifdef _ADDITIONAL_LIGHTS
int pixelLightCount = GetAdditionalLightsCount();
// determine main light inorder to apply light culling properly
// when the loop counter start from negative value, MAINLIGHT_IS_MAINLIGHT = -1, some compiler doesn't work well.
// for (int iLight = MAINLIGHT_IS_MAINLIGHT; iLight < pixelLightCount ; ++iLight)
for (int loopCounter = 0; loopCounter < pixelLightCount - MAINLIGHT_IS_MAINLIGHT; ++loopCounter)
{
int iLight = loopCounter + MAINLIGHT_IS_MAINLIGHT;
if (iLight != i.mainLightID)
{
float notDirectional = 1.0f; //_WorldSpaceLightPos0.w of the legacy code.
UtsLight additionalLight = GetUrpMainUtsLight(0,0);
if (iLight != MAINLIGHT_IS_MAINLIGHT)
{
additionalLight = GetAdditionalUtsLight(iLight, inputData.positionWS, i.positionCS);
}
half3 additionalLightColor = GetLightColor(additionalLight);
float3 lightDirection = additionalLight.direction;
//v.2.0.5:
float3 addPassLightColor = (0.5*dot(lerp(i.normalDir, normalDirection, _Is_NormalMapToBase), lightDirection) + 0.5) * additionalLightColor.rgb;
float pureIntencity = max(0.001, (0.299*additionalLightColor.r + 0.587*additionalLightColor.g + 0.114*additionalLightColor.b));
float3 lightColor = max(0, lerp(addPassLightColor, lerp(0, min(addPassLightColor, addPassLightColor / pureIntencity), notDirectional), _Is_Filter_LightColor));
float3 halfDirection = normalize(viewDirection + lightDirection); // has to be recalced here.
//v.2.0.5:
_1st_ShadeColor_Step = saturate(_1st_ShadeColor_Step + _StepOffset);
_2nd_ShadeColor_Step = saturate(_2nd_ShadeColor_Step + _StepOffset);
//
//v.2.0.5: If Added lights is directional, set 0 as _LightIntensity
float _LightIntensity = lerp(0, (0.299*additionalLightColor.r + 0.587*additionalLightColor.g + 0.114*additionalLightColor.b), notDirectional);
//v.2.0.5: Filtering the high intensity zone of PointLights
//计算灯光颜色
float3 Set_LightColor = lerp(lightColor, lerp(lightColor, min(lightColor, additionalLightColor.rgb*_1st_ShadeColor_Step), notDirectional), _Is_Filter_HiCutPointLightColor);
//计算BaseColor与采样_1st_ShadeMap_var与_2nd_ShadeMap_varMask。之后计算Set_FinalShadowMask与Set_ShadeShadowMask用于一次灯光循环的插值。
float3 Set_BaseColor = lerp((_BaseColor.rgb*_MainTex_var.rgb*_LightIntensity), ((_BaseColor.rgb*_MainTex_var.rgb)*Set_LightColor), _Is_LightColor_Base);
//v.2.0.5
float4 _1st_ShadeMap_var = lerp(SAMPLE_TEXTURE2D(_1st_ShadeMap, sampler_MainTex,TRANSFORM_TEX(Set_UV0, _1st_ShadeMap)), _MainTex_var, _Use_BaseAs1st);
float3 Set_1st_ShadeColor = lerp((_1st_ShadeColor.rgb*_1st_ShadeMap_var.rgb*_LightIntensity), ((_1st_ShadeColor.rgb*_1st_ShadeMap_var.rgb)*Set_LightColor), _Is_LightColor_1st_Shade);
//v.2.0.5
float4 _2nd_ShadeMap_var = lerp(SAMPLE_TEXTURE2D(_2nd_ShadeMap, sampler_MainTex,TRANSFORM_TEX(Set_UV0, _2nd_ShadeMap)), _1st_ShadeMap_var, _Use_1stAs2nd);
float3 Set_2nd_ShadeColor = lerp((_2nd_ShadeColor.rgb*_2nd_ShadeMap_var.rgb*_LightIntensity), ((_2nd_ShadeColor.rgb*_2nd_ShadeMap_var.rgb)*Set_LightColor), _Is_LightColor_2nd_Shade);
float _HalfLambert_var = 0.5*dot(lerp(i.normalDir, normalDirection, _Is_NormalMapToBase), lightDirection) + 0.5;
// float4 _Set_2nd_ShadePosition_var = tex2D(_Set_2nd_ShadePosition, TRANSFORM_TEX(Set_UV0, _Set_2nd_ShadePosition));
// float4 _Set_1st_ShadePosition_var = tex2D(_Set_1st_ShadePosition, TRANSFORM_TEX(Set_UV0, _Set_1st_ShadePosition));
// //v.2.0.5:
// float Set_FinalShadowMask = saturate((1.0 + ((lerp(_HalfLambert_var, (_HalfLambert_var*saturate(1.0 + _Tweak_SystemShadowsLevel)), _Set_SystemShadowsToBase) - (_1st_ShadeColor_Step - _1st_ShadeColor_Feather)) * ((1.0 - _Set_1st_ShadePosition_var.rgb).r - 1.0)) / (_1st_ShadeColor_Step - (_1st_ShadeColor_Step - _1st_ShadeColor_Feather))));
//SGM
//v.2.0.6
float4 _ShadingGradeMap_var = tex2Dlod(_ShadingGradeMap, float4(TRANSFORM_TEX(Set_UV0, _ShadingGradeMap), 0.0, _BlurLevelSGM));
//v.2.0.6
//Minmimum value is same as the Minimum Feather's value with the Minimum Step's value as threshold.
//float _SystemShadowsLevel_var = (attenuation*0.5)+0.5+_Tweak_SystemShadowsLevel > 0.001 ? (attenuation*0.5)+0.5+_Tweak_SystemShadowsLevel : 0.0001;
float _ShadingGradeMapLevel_var = _ShadingGradeMap_var.r < 0.95 ? _ShadingGradeMap_var.r + _Tweak_ShadingGradeMapLevel : 1;
//float Set_ShadingGrade = saturate(_ShadingGradeMapLevel_var)*lerp( _HalfLambert_var, (_HalfLambert_var*saturate(_SystemShadowsLevel_var)), _Set_SystemShadowsToBase );
float Set_ShadingGrade = saturate(_ShadingGradeMapLevel_var)*lerp(_HalfLambert_var, (_HalfLambert_var*saturate(1.0 + _Tweak_SystemShadowsLevel)), _Set_SystemShadowsToBase);
//
float Set_FinalShadowMask = saturate((1.0 + ((Set_ShadingGrade - (_1st_ShadeColor_Step - _1st_ShadeColor_Feather)) * (0.0 - 1.0)) / (_1st_ShadeColor_Step - (_1st_ShadeColor_Step - _1st_ShadeColor_Feather))));
float Set_ShadeShadowMask = saturate((1.0 + ((Set_ShadingGrade - (_2nd_ShadeColor_Step - _2nd_ShadeColor_Feather)) * (0.0 - 1.0)) / (_2nd_ShadeColor_Step - (_2nd_ShadeColor_Step - _2nd_ShadeColor_Feather)))); // 1st and 2nd Shades Mask
//SGM
// //Composition: 3 Basic Colors as finalColor
// float3 finalColor =
// lerp(
// Set_BaseColor,
// lerp(
// Set_1st_ShadeColor,
// Set_2nd_ShadeColor,
// saturate(
// (1.0 + ((_HalfLambert_var - (_2nd_ShadeColor_Step - _2nd_Shades_Feather)) * ((1.0 - _Set_2nd_ShadePosition_var.rgb).r - 1.0)) / (_2nd_ShadeColor_Step - (_2nd_ShadeColor_Step - _2nd_Shades_Feather))))
// ),
// Set_FinalShadowMask); // Final Color
//Composition: 3 Basic Colors as finalColor
float3 finalColor =
lerp(
Set_BaseColor,
//_BaseColor_var*(Set_LightColor*1.5),
lerp(
Set_1st_ShadeColor,
Set_2nd_ShadeColor,
Set_ShadeShadowMask
),
Set_FinalShadowMask);
//v.2.0.6: Add HighColor if _Is_Filter_HiCutPointLightColor is False
float4 _Set_HighColorMask_var = tex2D(_Set_HighColorMask, TRANSFORM_TEX(Set_UV0, _Set_HighColorMask));
float _Specular_var = 0.5*dot(halfDirection, lerp(i.normalDir, normalDirection, _Is_NormalMapToHighColor)) + 0.5; // Specular
float _TweakHighColorMask_var = (saturate((_Set_HighColorMask_var.g + _Tweak_HighColorMaskLevel))*lerp((1.0 - step(_Specular_var, (1.0 - pow(abs(_HighColor_Power), 5)))), pow(abs(_Specular_var), exp2(lerp(11, 1, _HighColor_Power))), _Is_SpecularToHighColor));
float4 _HighColor_Tex_var = tex2D(_HighColor_Tex, TRANSFORM_TEX(Set_UV0, _HighColor_Tex));
float3 _HighColor_var = (lerp((_HighColor_Tex_var.rgb*_HighColor.rgb), ((_HighColor_Tex_var.rgb*_HighColor.rgb)*Set_LightColor), _Is_LightColor_HighColor)*_TweakHighColorMask_var);
finalColor = finalColor + lerp(lerp(_HighColor_var, (_HighColor_var*((1.0 - Set_FinalShadowMask) + (Set_FinalShadowMask*_TweakHighColorOnShadow))), _Is_UseTweakHighColorOnShadow), float3(0, 0, 0), _Is_Filter_HiCutPointLightColor);
//
finalColor = SATURATE_IF_SDR(finalColor);
pointLightColor += finalColor;
// pointLightColor += lightColor;
}
}
#endif // _ADDITIONAL_LIGHTS
//
//Final Composition
finalColor = SATURATE_IF_SDR(finalColor) + (envLightColor*envLightIntensity*_GI_Intensity*smoothstep(1,0,envLightIntensity/2)) + emissive;
finalColor += pointLightColor;
#endif
//v.2.0.4
#ifdef _IS_TRANSCLIPPING_OFF
fixed4 finalRGBA = fixed4(finalColor,1);
#elif _IS_TRANSCLIPPING_ON
float Set_Opacity = SATURATE_IF_SDR((_Inverse_Clipping_var+_Tweak_transparency));
fixed4 finalRGBA = fixed4(finalColor,Set_Opacity);
#endif
return finalRGBA;
}
```

View File

@@ -0,0 +1,105 @@
## 资料
- [ ] 游戏诞生之日09 - 美术篇 卡通渲染着色器 UTS2 https://zhuanlan.zhihu.com/p/137288013
- [ ] MMD联动Unity学习笔记 Vol.42 UTS2进阶慎入长篇多图预警https://www.bilibili.com/read/cv3347514
- [ ] 官方文档https://github.com/unity3d-jp/UnityChanToonShaderVer2_Project/blob/release/urp/2.3.0/Documentation~/index.md
- [ ] 官方文档——属性解释https://github.com/unity3d-jp/UnityChanToonShaderVer2_Project/blob/release/urp/2.3.0/Documentation~/Props_en.md
## 总览
没有后处理效果
两种着色方式:
DoubleShadeWithFeatherUTS/UniversalToon 的标准工作流程模式。允许 2 种阴影颜色(双阴影颜色)和颜色之间的渐变(羽化)。
ShadingGradeMap更高级的工作流程模式。除了 DoubleShadeWithFeather 功能之外,此着色器还可以保存称为 ShadingGradeMap 的特殊贴图。
- UniversalToonInput.hlsl定义各种变量与资料实现采样AO贴图`SampleOcclusion()`与初始化Lit表面数据`InitializeStandardLitSurfaceData()`
- UniversalToonHead.hlsl定义了一些宏与函数雾、坐标转换、线性空间与GammaSpace转换相关。
## 光照
## Pass
顺序为:
1. Outline
2. ForwardLit
3. ShadowCaster
4. DepthOnly
模板测试语法:
```c#
Stencil
{
Ref[_StencilNo] //设置渲染的模板缓存值0~255
Comp[_StencilComp] //模板测试的通过条件有除了equal还有Greater、Less、Always、Never等类似ZTest。
Pass[_StencilOpPass] //表示通过模板测试和Z测试注意是都通过的像素怎么处置它的模板值。
Fail[_StencilOpFail] //表示通过了模板测试但没通过Z测试的像素怎么处置它的模板值。
}
```
### Outline
```c#
Tags {"LightMode" = "SRPDefaultUnlit"}:使用这个LightMode标签值在渲染物体时绘制一个额外的Pass。也是URP光照模式的默认值。
Cull [_SRPDefaultUnlitColMode]
ColorMask [_SPRDefaultUnlitColorMask]
Blend SrcAlpha OneMinusSrcAlpha
Stencil
{
Ref[_StencilNo]
Comp[_StencilComp]
Pass[_StencilOpPass]
Fail[_StencilOpFail]
}
```
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "UniversalToonHead.hlsl"
#include "UniversalToonOutline.hlsl"
### ForwardLit
```c#
Tags{"LightMode" = "UniversalForward"}:URP前向渲染
ZWrite[_ZWriteMode]
Cull[_CullMode]
Blend SrcAlpha OneMinusSrcAlpha
Stencil {
Ref[_StencilNo]
Comp[_StencilComp]
Pass[_StencilOpPass]
Fail[_StencilOpFail]
}
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
#include "Packages/com.unity.render-pipelines.universal/Shaders/LitForwardPass.hlsl"
#include "UniversalToonHead.hlsl"
#include "UniversalToonBody.hlsl"
```
### ShadowCaster
渲染阴影贴图
```c#
Name "ShadowCaster"
Tags{"LightMode" = "ShadowCaster"}
ZWrite On
ZTest LEqual
Cull[_CullMode]
#include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
#include "Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl"
```
### DepthOnly
渲染深度缓存
```c#
Tags{"LightMode" = "DepthOnly"}
ZWrite On
ColorMask 0
Cull[_CullMode]
#include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
#include "Packages/com.unity.render-pipelines.universal/Shaders/DepthOnlyPass.hlsl"
```

View File

@@ -0,0 +1,71 @@
# Unity通用渲染管线URP系列——自定义渲染管线
## 逻辑分布
- CustomRenderPipelineAsset.cs定义渲染管线Asset最后创建并且返回定义的管线实例。
- CustomRenderPipeline.cs整个渲染管线逻辑。考虑到多视图渲染的关系还需要为摄像机类实现对应的逻辑。
- CameraRenderer.cs摄像机对应的渲染逻辑。
## CustomRenderPipeline
重写`void Render(ScriptableRenderContext context,Camera[] cameras)`,使用自定义的`CameraRenderer`类型的变量renderer,调用`renderer.Render(context,camera)`为每个摄像机(视图)进行渲染。
## CameraRenderer
大部分逻辑都集中在这个类中。
重写`void Render(ScriptableRenderContext context,Camera camera)`
裁剪之前会进行
- `PrepareBuffer()`根据摄像机来设置缓存名称
- `PrepareForSceneWindow()`绘制UI准备
1. [裁剪](#Cull)
2. [设置初始化参数](#Setup)
3. [绘制可见多边形](#DrawVisibleGeometry)
4. [绘制Gizmos]()
5. [提交](#Submit)
全代码
```
```
###
#### Command Buffers
自定义的渲染功能需要设置Command Buffers以存储这个功能所有的渲染命令创建新的Command Buffers需要设置名称案例中使用“Render Camera”。
使用`buffer.BeginSample(bufferName)``buffer.EndSample(bufferName)`给Profiler与帧调试器识别到该渲染功能。
执行`Command Buffers`需要在`context`上调用ExecuteCommandBuffer。这会从缓冲区复制命令但并不会清除它如果要重用它的话就必须在之后明确地执行该操作。因为执行和清除总是一起完成的所以添加同时执行这两种方法的方法很方便。
#### 编辑器相关功能
使用`partial`关键字来建立局部类将Editor相关的代码转移到另一个文件中。
### Cull()
尝试通过Camera获取剪裁变量之后存储裁剪后的结果。
### lighting.Setup(context,cullingResults)
使用`CullingResults`向Shader传递`DirectionalLight`信息,支持多个方向光。
### Setup()
设置初始变量以及做一些初始化操作:
1. `SetupCameraProperties`设置摄像相关属性比如投影矩阵
2. `buffer.ClearRenderTarget`清屏操作。根据清屏标志进行对应的处理。为Depth只会清空深度缓存颜色的话会清空深度与颜色缓存如果项目设置为线性颜色则使用线性颜色进行清屏。
### DrawVisibleGeometry()
根据`SortingSettings``DrawingSettings``FilteringSettings`,之后调用
` context.DrawRenderers(cullingResults,ref drawingSettings,ref filteringSettings)`绘制模型。
`DrawinSettings`的构造函数中,`SortingSettings`起到确定基于正焦还是基于透视的应用排序,还可以用来设置绘制顺序,`criteria = SortingCriteria.CommonOpaque`设置绘制顺序为从近到远。
绘制还需要需要对应着色器的Id案例中使用`static ShaderTagId unlitShaderTagId = new ShaderTagId("SRPDefaultUnlit")`获取。
`FilteringSettings`用于确定哪一些渲染队列是被允许渲染。
以及`context.DrawSkybox(camera)`来绘制天空盒。
#### 绘制顺序
### lighting.Clearup()
渲染完阴影后,清理阴影图集。
### Submit()
提交给渲染队列。

View File

@@ -0,0 +1,71 @@
## BRDF
案例中使用了迪士尼模型。
## 混合模式
`SrcBlend``One``DetBlend``OneMinusSrc`
## Shader GUI
`Shader`{}内添加`CustomEditor“ CustomShaderGUI”`。之后添加脚本:
```c#
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
public class CustomShaderGUI : ShaderGUI {
MaterialEditor editor;
Object[] materials;
MaterialProperty[] properties;
public override void OnGUI (
MaterialEditor materialEditor, MaterialProperty[] properties
) {
base.OnGUI(materialEditor, properties);
editor = materialEditor;
materials = materialEditor.targets;
this.properties = properties;
}
}
```
之后实现
```c#
bool HasProperty (string name) =>
FindProperty(name, properties, false) != null;
void SetProperty (string name, string keyword, bool value) {
if (SetProperty(name, value ? 1f : 0f)) {
SetKeyword(keyword, value);
}
}
bool SetProperty (string name, float value) {
MaterialProperty property = FindProperty(name, properties, false);
if (property != null) {
property.floatValue = value;
return true;
}
return false;
}
void SetKeyword (string keyword, bool enabled) {
if (enabled) {
foreach (Material m in materials) {
m.EnableKeyword(keyword);
}
}
else {
foreach (Material m in materials) {
m.DisableKeyword(keyword);
}
}
}
```
就可以添加若干自定义Shader属性的`Set`函数了。添加按钮逻辑如下:
```c#
bool PresetButton (string name) {
if (GUILayout.Button(name)) {
editor.RegisterPropertyChangeUndo(name);
return true;
}
return false;
}
```

View File

@@ -0,0 +1,185 @@
## 建立自己的管线Shader以及ShaderLibrary
## Shader宏分支控制
### multi_complie
常用的两种做法:
1. 使用multi_complie或shader_feature来定义宏根据不同的宏指令编译出多套ShaderUnity内建shader大体也是这么做的。
2. 有外部传入参数在shader内部if判断选择执行哪部分运算。
因为在shader种使用if、for很影响效率所以第二种方法使用较少用于case较少的时候。
这两个宏一般与`Shader.EnableKeyword("宏名");``Shader.DisableKeyword("宏名");`一起使用。
它会无脑的进行组合编译如果宏指令太多会产生非常多的variant。
`#pragma multi_compile Red Green Blue`
会产生三个variant因为你定义了三个宏
```c#
#pragma multi_compile Red Green Blue
#pragma multi_compile Pink Yellow
```
会产生6个variantRedPinkRedYellowGreenPinkGreenYellowBluePinkBlueYellow),因为他们之间会两两组合。
### shader_feature
该指令的效果和用法基本都与`multi_complie`一样,都是用来添加宏。同时它就是为了multi_compile打包时的爆炸编译的问题。
但如果是需要同时存在两种宏分支的功能就不适合用`shader_feature`了。
## URP ShaderLibrary
### render-pipelines.core
`Packages/com.unity.render-pipelines.core/ShaderLibrary/SpaceTransforms.hlsl`中定义了若干空间转换函数,但因为没有定义宏,所以还需要在用之前定义缺少宏,相关矩阵变量使用`UnityInput.hlsl`进行定义。下面的宏声明可能会有bug最好手动复制错误信息中的变量。估计是字符集的问题
`Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl`包含了若干基础类型定义目前只用于定义real类型。
`Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl`为了实现GPUInstancing所需的库。
`Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl`实现了迪士尼BRDF模型函数。比如`PerceptualSmoothnessToPerceptualRoughness`、`PerceptualRoughnessToRoughness`
```c#
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "UnityInput.hlsl"
#define UNITY_MATRIX_M untiy_ObjectToWorld
#define UNITY_MATRIX_I_M untiy_WorldToObject
#define UNITY_MATRIX_V untiy_MatrixV
#define UNITY_MATRIX_VP unity_MatrixVP
#define UNITY_MATRIX_P glstate_matrix_projection
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/SpaceTransforms.hlsl"
```
### render-pipelines.universal 7.3.1
Unity-Chan使用URP7.3.1
#### Core.hlsl
`Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl`
- 定义了顶点输入、顶点法线输入输入以及初始化函数。
- 定义`UNITY_Z_0_FAR_FROM_CLIPSPACE`宏。
- 返回`UnityInput.hlsl`中定义的`_WorldSpaceCameraPos`,以及`_ScaledScreenParams`。
- 一些常用函数
#### Lighting
`Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl`
实现了灯光相关逻辑(包括阴影)
- 灯光衰减函数
- 灯光数据结构体以及结构体数据填充与计算函数
- BRDF Functions
- Global Illumination主要是球谐结果
- 光照计算LightingLambert、LightingSpecular、LightingPhysicallyBased、VertexLighting
- Fragment FunctionsUniversalFragmentPBR、UniversalFragmentBlinnPhong、LightweightFragmentPBR
#### LitInput
`Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl`
- 定义了PBR所需的`CBUFFER`
- `SampleMetallicSpecGloss()`、`SampleOcclusion()`、`InitializeStandardLitSurfaceData()`
#### LitForwardPass
`Packages/com.unity.render-pipelines.universal/Shaders/LitForwardPass.hlsl`
URP前向管线具体实现文件。像素着色器最终调用`UniversalFragmentPBR()`来计算最后颜色。
## 减少Draw Call的方法
- SRP批处理器:批处理是组合Drawcall的过程可减少CPU和GPU之间的通信时间。最简单的方法是启用SRP batcher。SRP batcher不会减少Draw Call的数量而是使其更精简。它在GPU上缓存了材质属性因此不必在每次绘制调用时都将其发送出去。
- GPU Instancing:使用GPU Instancing可使用少量绘制调用一次绘制或渲染同一网格的多个副本。
### SRP批处理器
遇到错误`SRP Batcher Material property is found in another cbuffer`这个是因为const buffer的名称不正确造成的。
官方文档有如下一句话:
>For a Shader to be compatible with SRP:
All built-in engine properties must be declared in a single CBUFFER named “UnityPerDraw”. For example, unity_ObjectToWorld, or unity_SHAr.
All Material properties must be declared in a single CBUFFER named “UnityPerMaterial”.
翻译成白话来说Shader中所有的内置属性例如unity_ObjectToWorldunity_SHAr等都要在一个名为UnityPerDraw的CBUFFER中声明而所有的Material属性都要在一个名为UnityPerMaterial的CBUFFER中声明。
```c#
cbuffer UntiyPreMaterial
{
float4 _BaseColor;
}
CBUFFER_START(UntiyPreMaterial)
float4 _BaseColor;
CBUFFER_END
```
之后在管线的构造函数中添加设置:
```c#
public CustomRenderPipeline()
{
GraphicsSettings.useScriptableRenderPipelineBatching = true;
}
```
### GPU Instancing
大致步骤:
1. 在Shader文件中添加`#pragma multi_compile_instancing`,这将使Unity生成我们的着色器的两个变体一个具有GPU实例化支持一个不具有GPU实例化支持。材质检查器中还出现了一个切换选项使我们可以选择每种材质要使用的版本。
2. 支持GPU实例化需要更改方法还需要包含`UnityInstancing.hlsl`,作用是重新定义这些宏来访问实例数据数组。但是要进行这项工作需要知道当前正在渲染的对象的索引。索引是通过顶点数据提供的因此需要使其可用。UnityInstancing.hlsl定义了宏来简化此过程但是它假定顶点函数具有struct参数。
3. 声明一个用于传递定点数据的结构体使用GPU实例化时对象索引也可用作顶点属性。我们可以在适当的时候通过简单地将`UNITY_VERTEX_INPUT_INSTANCE_ID`放在属性中来添加它。
4. 在`VertexShader`中添加添加`UNITY_SETUP_INSTANCE_ID(input)`来提取实例的顶点索引数据这足以使GPU实例化进行工作了。
5. 因为SRP批处理程序拥有优先权所以还需要使用`UNITY_INSTANCING_BUFFER_START`替换`CBUFFER_START`以及用`UNITY_INSTANCING_BUFFER_END`替换`CBUFFER_END`,再将内部属性使用`UNITY_DEFINE_INSTANCED_PROP`进行包裹。
6. 为了将顶点索引实例传递至`PixelShader`,还需要再创建一个结构体并添加UNITY_VERTEX_INPUT_INSTANCE_ID。
7. 最后在`PixelShader`中添加`UNITY_SETUP_INSTANCE_ID(input)`来访问实例。使用`UNITY_ACCESS_INSTANCED_PROP`来访问材质中的属性数据。
```cg
UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
UNITY_DEFINE_INSTANCED_PROP(float4,_BaseColor)
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)
struct Attributes
{
float3 positionOS : POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct Varyings
{
float4 positionCS: SV_POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
Varyings UnlitPassVertex(Attributes input)
{
Varyings output;
UNITY_SETUP_INSTANCE_ID(input);
UNITY_TRANSFER_INSTANCE_ID(input,output);
float3 positionWS = TransformObjectToWorld(input.positionOS);
output.positionCS=TransformWorldToHClip(positionWS);
return output;
}
float4 UnlitPassFragment(Varyings input) : SV_TARGET
{
UNITY_SETUP_INSTANCE_ID(input);
return UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial,_BaseColor);
}
```
### 动态合批
减少DC的第三种方法称为动态批处理。这是一种古老的技术它将共享相同材质的多个小网格合并为一个较大的网格而该网格被绘制。但如果使用逐对象材质属性per-object material properties会失效。 较大的网格一般按需生成,所以动态合批仅适用于较小的网格。球体还是太大了,但立方体可以使用。
一般来说GPU实例化优于动态批处理。该方法也有一些注意事项例如当涉及不同的比例时不能保证较大网格的法线向量为单位长度。此外绘制顺序也将更改因为它现在是单个网格而不是多个。 还有静态批处理它的工作原理类似但是会提前标记为静态批处理的对象。除了需要更多的内存和存储空间之外它没有任何注意事项。RP不关心这个因此使用起来不用过多担心。
大致步骤:
1. 在`CameraRenderer.DrawVisibleGeometry`中将`enableDynamicBatching`与`enableInstancing`设置为true。
2. 在`CustomRenderPipeline`将`GraphicsSettings.useScriptableRenderPipelineBatching = useSPRBatcher`。
### 给RP添加变量控制
大致步骤:
- `CameraRenderer`中给`DrawVisibleGeometry`与`Render`添加`useDynamicBatching`与`useGPUInstancing`形参,这两变量将用来设置`DrawVisibleGeometry`中的`enableuseDynamicBatching`与`enableInstancing`。
- `CustomRenderPipeline`中添加`useDynamicBatching`与`useGPUInstancing`变量,并且给构造函数添加`useSRPBatcher`、`useDynamicBatching`与`useGPUInstancing`形参,用于修改上述变量以及设置是否启用`SRPBatcher`,并且给`Render`中渲染函数添加上述形参。
- `CustomRenderPipelineAsset`添加`useSRPBatcher`、`useDynamicBatching`与`useGPUInstancing`变量,并修改对应函数的形参。
## 在Shader中添加渲染设置
在`Properties`中
```cg
[Enum(UnityEngine.Rendering.BlendedMode)]
_SrcBlend("Src Blend",Float)=1
[Enum(UnityEngine.Rendering.BlendedMode)]
_DstBlend("Src Blend",Float)=0
[Enum(Off,0,On,1)] _ZWrite ("Z Write",Float)=1
```
在`Pass`中添加
```cg
Blend [_SrcBlend] [_DstBlend]
ZWrite [_ZWrite]
```
## 添加纹理
1. 在`Properties`中添加` _BaseMap("Texture",2D)="white" {}`
2. 因为要支持GPUInstancing的关系代码比较复杂后续步骤见git。

View File

@@ -0,0 +1,103 @@
## Post-FX Stack
为了效率跳过。
`void Draw(RenderTargetIdentifier from,RenderTargetIdentifier to,Pass pass)`
![](https://pic2.zhimg.com/80/v2-9e0c020362ba48d9652de9507acd876d_720w.jpg)
to代表绘制的RT id通过`buffer.SetRenderTarget()`来设置。from为原始渲染结果使用`buffer.SetGlobalTexture()`来向Shader传递贴图资源。
## Bloom
对图像进行双线性采样以生成分辨率不断对半分的Bloom金字塔。
![](https://pic1.zhimg.com/80/v2-263834811ad5f64ae6674ebaae360bc8_720w.jpg)
`_BloomPyramid1`~`_BloomPyramid16`传递id。
![](https://pic1.zhimg.com/80/v2-a2dfea38523980ad31c5e656a181735c_720w.jpg)
创建一个DoBloom方法。首先将摄像机的像素宽度和高度减半然后选择默认的渲染纹理格式。最初我们将从源复制到金字塔中的第一个纹理。追踪那些标识符。
![](https://pic4.zhimg.com/80/v2-8946f3551e373b2a5cc399a360c1e34b_720w.jpg)
## OnBloom
循环整个级别的贴图,并调用`Draw()`绘制之后计算后一级别的贴图的数据准备下一次循环直到循环完成或者贴图分辨为1*1时候。
### Draw
```c#
void Draw (
RenderTargetIdentifier from, RenderTargetIdentifier to, Pass pass
) {
buffer.SetGlobalTexture(fxSourceId, from);
buffer.SetRenderTarget(
to, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store
);
buffer.DrawProcedural(
Matrix4x4.identity, settings.Material, (int)pass,
MeshTopology.Triangles, 3
);
}
```
##
```c#
Pass {
BloomCombine,
BloomHorizontal,
BloomPrefilter,
BloomVertical,
Copy
}
```
Bloom循环的主要内逻辑为
1. copy之前的渲染结果。
2. 进行一次预处理。
3. 取得2个RT首先进行水平高斯模糊之后进行垂直高斯模糊。因为水平模糊已经进行一次采样所以垂直采样的次数可以减半了。
4. 释放中间产生水平高斯的RT。
5. 进行反向叠加产生结果循环。(垂直高斯模糊后的结果)
6. 释放垂直高斯的RT。
## 预处理
Bloom通常在艺术上用于仅使某些东西发光但是我们的效果目前适用于所有对象不管它有多亮。尽管从物理上讲没有意义但是我们可以通过引入亮度阈值来限制影响效果的因素。实际上就是提取亮度高的区域。
c# DoBloom():
```c#
Vector4 threshold;
threshold.x = Mathf.GammaToLinearSpace(bloom.threshold);
threshold.y = threshold.x * bloom.thresholdKnee;
threshold.z = 2f * threshold.y;
threshold.w = 0.25f / (threshold.y + 0.00001f);
threshold.y -= threshold.x;
buffer.SetGlobalVector(bloomThresholdId, threshold);
```
Shader:
```c#
float3 ApplyBloomThreshold (float3 color) {
float brightness = Max3(color.r, color.g, color.b);
float soft = brightness + _BloomThreshold.y;
soft = clamp(soft, 0.0, _BloomThreshold.z);
soft = soft * soft * _BloomThreshold.w;
float contribution = max(soft, brightness - _BloomThreshold.x);
contribution /= max(brightness, 0.00001);
return color * contribution;
}
float4 BloomPrefilterPassFragment (Varyings input) : SV_TARGET {
float3 color = ApplyBloomThreshold(GetSource(input.fxUV).rgb);
return float4(color, 1.0);
}
```
## 解决白色辉光显得块状化问题
使用在Core RP Library的Filtering include文件中定义的SampleTexture2DBicubic函数。
```c#
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Filtering.hlsl"
float4 GetSourceBicubic (float2 fxUV) {
return SampleTexture2DBicubic(
TEXTURE2D_ARGS(_PostFXSource, sampler_linear_clamp), fxUV,
_PostFXSource_TexelSize.zwxy, 1.0, 0.0
);
}
```
之后再合并结果的时候对低分辨的贴图使用该函数进行采样。在案例中被设置为可开启项。
## 强制控制
在`BloomCombinePassFragment`中的给低分辨率结果乘以强度之后在于高分辨率结果叠加。

View File

@@ -0,0 +1,14 @@
## HDR渲染纹理
HDR渲染仅与后处理结合使用才有意义因为我们无法更改最终的帧缓冲区格式。因此当我们在CameraRenderer.Setup中创建自己的中间帧缓冲区时我们将在适当的时候使用默认的HDR格式`buffer.GetTemporarayRT()`中使用`RenderTextureFormat.DefaultHDR`(后处理效果的RT格式),也就是R16G16B16A16_SFloat而不是LDR的常规默认格式。
逐步执行DrawCall时你会注意到场景看起来比最终结果要暗。发生这种情况是因为这些步骤存储在HDR纹理中。由于线性颜色数据按原样显示因此看起来很暗它错误地解释为sRGB。
![](https://pic1.zhimg.com/80/v2-a5c936841f88bc9a44cd4af65cb940c0_720w.jpg)
![](https://pic2.zhimg.com/80/v2-d528a8301bc04fa50aa97d7692b4fc59_720w.jpg)
为什么亮度会变化?
sRGB格式使用非线性传递函数。显示器会为此调整执行所谓的伽马校正。伽玛调节函数通常用c的2.2次方和c原色近似但实际传递函数略有不同。
## 解决因为高亮区域过小而导致的闪烁问题
<video src="https://vdn1.vzuu.com/SD/d8b32ae4-48e8-11eb-9c34-7640c864ad74.mp4?disable_local_cache=1&auth_key=1634485320-0-0-203192af877eba6f1b1ae62e0e6d165b&f=mp4&bu=pico&expiration=1634485320&v=hw"
</video>

View File

@@ -0,0 +1,323 @@
## 级联阴影
阴影贴图的缺点:阴影边缘的锯齿严重。原因是阴影贴图的分辨率低,在对阴影贴图采样时,多个不同的顶点对同一个像素采样,导致生成锯齿。为了解决这种问题,我们使用多张阴影贴图,离相机近的地方使用精细的阴影贴图,离相机远的地方使用粗糙的阴影贴图,这样不仅优化了阴影效果,还保证了渲染效率因此,级联阴影的关键就是生成和使用不同精细度的阴影贴图
阴影贴图的原理:在灯光方向架一台摄像机获取深度图然后再正常渲染自己的场景再在正常渲染场景的时候把fragment转换到光源空间把它和之前渲染的Shadowmap中高度深度作比较看它是否在影子里如果是就返回0不是就返回1
主要的步骤是取得场景中灯光的设置并且传递到Shader中之后在`RenderDirectionalShadows`中取得`GetTemporaryRT`在设置完绘制属性后将渲染结果传递至RT而不是摄像机上通过`RenderDirectionalShadows`绘制各个方向光的阴影。
`RenderDirectionalShadows`主要是通过`cullingResults.ComputeDirectionalShadowMatricesAndCullingPrimitives`来计算视图矩阵、投影矩阵与ShadowSplitData结构该函数第一个参数是可见光指数。接下来的三个参数是两个整数和一个Vector3它们控制阴影级联。稍后我们将处理级联因此现在使用零一和零向量。然后是纹理尺寸我们需要使用平铺尺寸。第六个参数是靠近平面的阴影我们现在将其忽略并将其设置为零。之后操作为
```c#
shadowSettings.splitData = splitData;
//设置矩阵
buffer.SetViewProjectionMatrices(viewMatrix,projectionMatrix);
ExecuteBuffer();
//根据ShadowDrawingSettings绘制阴影
context.DrawShadows(ref shadowSettings);
```
之后开始往`Lit.Shader`添加绘制阴影Pass
```c#
Pass{
Tags {
"LightMode" = "ShadowCaster"
}
ColorMask 0
HLSLPROGRAM
#pragma target 3.5
#pragma shader_feature _CLIPPING
#pragma multi_compile_instancing
#pragma vertex ShadowCasterPassVertex
#pragma fragment ShadowCasterPassFragment
#include "ShadowCasterPass.hlsl"
ENDHLSL
}
```
其中FragmentShader只负责裁剪
```c#
#ifndef CUSTOM_SHADOW_CASTER_PASS_INCLUDED
#define CUSTOM_SHADOW_CASTER_PASS_INCLUDED
#include "../ShaderLibrary/Common.hlsl"
TEXTURE2D(_BaseMap);
SAMPLER(sampler_BaseMap);
UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
UNITY_DEFINE_INSTANCED_PROP(float4, _BaseMap_ST)
UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)
UNITY_DEFINE_INSTANCED_PROP(float, _Cutoff)
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)
struct Attributes {
float3 positionOS : POSITION;
float2 baseUV : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct Varyings {
float4 positionCS : SV_POSITION;
float2 baseUV : VAR_BASE_UV;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
Varyings ShadowCasterPassVertex (Attributes input) {
Varyings output;
UNITY_SETUP_INSTANCE_ID(input);
UNITY_TRANSFER_INSTANCE_ID(input, output);
float3 positionWS = TransformObjectToWorld(input.positionOS);
output.positionCS = TransformWorldToHClip(positionWS);
float4 baseST = UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _BaseMap_ST);
output.baseUV = input.baseUV * baseST.xy + baseST.zw;
return output;
}
void ShadowCasterPassFragment (Varyings input) {
UNITY_SETUP_INSTANCE_ID(input);
float4 baseMap = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.baseUV);
float4 baseColor = UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _BaseColor);
float4 base = baseMap * baseColor;
#if defined(_CLIPPING)
clip(base.a - UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _Cutoff));
#endif
}
#endif
```
`clip()`是HLSL内置函数当传入数值小于0时丢弃当前像素。`_Cutoff`是一个设定的浮点值默认为0。不是0就是1。在`ShadowCaster`Pass中是为了正确渲染Alpha物体阴影。
`ShadowCaster`Pass是用来渲染阴影贴图灯光空间的深度贴图如果相机空间物体表面深度大于阴影贴图中深度则代表物体处于阴影。取得阴影值在`GetDirectionalShadowAttenuation()=>FilterDirectionalShadow()`里面,采样完阴影后`GetDirectionalShadowAttenuation()`里进行一次插值,`return lerp(1.0,shadow,directional.strength);`,最后在`GetLighting()`取得计算完的阴影值。
在`FilterDirectionalShadow()`中调用了`SAMPLE_TEXTURE2D_SHADOW`宏。`SAMPLE_TEXTURE2D_SHADOW`宏本质是`SampleCmpLevelZero()`函数会对指定的纹理坐标进行采样将采样的结果与传入的参数z当前texel在光源空间的深度进行比较小于等于z视为通过说明此texel没被遮挡否则视为不通过说明此texel位于阴影中
另外注意`SamplerComparisonState`是用来进行深度采样比较的采样器。需要与`SampleCmpLevelZero`一起使用。
### 添加级联效果
由于定向光会影响最大阴影距离范围内的所有物体,因此它们的阴影贴图最终会覆盖较大的区域。由于阴影贴图使用正交投影,因此阴影贴图中的每个纹理像素都具有固定的世界空间大小。如果该尺寸太大,则清晰可见单个阴影纹理,从而导致锯齿状的阴影边缘和小的阴影可能消失。可以通过增加图集大小来缓解这种情况,但仅限于一定程度。
### 添加设置
#### ShadowSettings类
添加`ShadowSettings`类:
```c#
using UnityEngine;
[System.Serializable]
public class ShadowSettings
{
[Min(0f)]
public float maxDistance = 100f;
public enum TextureSize
{
_256=256,_512=512,_1024=1024,_2048=2048,_4096=4096,_8192=8192
}
[System.Serializable]
public struct Directional
{
public TextureSize atlasSize;
}
public Directional directional = new Directional { atlasSize = TextureSize._1024};
}
```
往`CustomRenderPipelineAsset`、`CustomRenderPipeline`、`CameraRenderer` 、`Lighting`依次添加`ShadowSettings`变量以及对应函数中的形参。
#### Shadow类
```c#
public class Shadows
{
const int maxShadowedDirectionalLightCount = 1;
int ShadowedDirectionalLightCount;
const string bufferName = "shadows";
CommandBuffer buffer = new CommandBuffer
{
name = bufferName
};
ScriptableRenderContext context;
CullingResults cullingResults;
ShadowSettings settings;
struct ShadowedDirectionalLight
{
public int visibleLightIndex;
}
private ShadowedDirectionalLight[] ShadowedDirectionalLights =
new ShadowedDirectionalLight[maxShadowedDirectionalLightCount];
public void Setup(
ScriptableRenderContext context,CullingResults cullingResults,
ShadowSettings settings
)
{
ShadowedDirectionalLightCount = 0;
this.context = context;
this.cullingResults = cullingResults;
this.settings = settings;
}
void ExecuteBuffer()
{
context.ExecuteCommandBuffer(buffer);
buffer.Clear();
}
/*
* 存储方向光阴影信息
* 灯光处于阴影有效且处于可见状态时将信息存储在ShadowedDirectionalLights[]中
*/
public void ReserveDirectionalShadows(Light light, int visibleLightIndex)
{
if (ShadowedDirectionalLightCount < maxShadowedDirectionalLightCount &&
light.shadows!=LightShadows.None && light.shadowStrength>0f &&
cullingResults.GetShadowCasterBounds(visibleLightIndex,out Bounds b))
{
ShadowedDirectionalLights[ShadowedDirectionalLightCount++] = new ShadowedDirectionalLight() { visibleLightIndex = visibleLightIndex};
}
}
}
```
- 之后在`Lighting`类中添加`Shadow`类变量shadow并且在`Setup`中添加`shadows.Setup(context,cullingResults,shadowSettings);`。以及在`SetupDirectionalLight`中添加`shadows.ReserveDirectionalShadows(visibleLight.light,index);`。
### 渲染
#### 阴影图集
```c#
TEXTURE2D_SHADOW(_DirectionalShadowAtlas);
#define SHADOW_SAMPLER sampler_linear_clamp_compare
SAMPLER_CMP(SHADOW_SAMPLER);
```
#### 3 级联阴影贴图
现在终于得到阴影,但它们看起来很糟糕。不应被阴影化的表面最终会被形成像素化带的阴影伪影所覆盖。这些是由于阴影贴图的有限分辨率导致的自我阴影化。使用不同的分辨率会更改伪影模式,但不会消除它们。
![](https://pic1.zhimg.com/80/v2-7b1bc2d23fcaaf10112496200536f770_720w.jpg)
#### 球形剔除
Unity通过为其创建一个选择球来确定每个级联覆盖的区域。由于阴影投影是正交的且呈正方形因此它们最终会紧密契合其剔除球但还会覆盖周围的一些空间。这就是为什么可以在剔除区域之外看到一些阴影的原因。同样光的方向与球无关因此所有定向光最终都使用相同的剔除球。
剔除球是ComputeDirectionalShadowMatricesAndCullingPrimitives函数中计算出的`ShadowSplitData`分离出的数据。传递`_CascadeCount`级联数以及`_CascadeCullingSpheres`以及剔除球的位置数据。
创建`ShadowData`结构体以传递级联级别以及阴影硬度。将会在`GetLighting`中通过像素的世界坐标是否在球体内来计算`ShadowData`结构体中的级联级别。
#### 最大距离
此时阴影会在超过最后一个剔除球后消失,为了解决这个问题,会设置一个最大距离,超过最大距离阴影才会消失。具体操作是在`GetShadowData`中比较像素深度以及阴影最大距离值,如果超过则将`strength`设置为0。
#### 给阴影添加衰减与级联渐变
阴影衰减见git
![](https://pic3.zhimg.com/80/v2-3d3ad5abdfc204e2b640ffab7329554a_720w.jpg)
级联衰减公式:
![](https://pic2.zhimg.com/80/v2-01588a58eec8923422b5ea114f2000e5_720w.jpg)
其中f为下面式子的倒数
![](https://pic2.zhimg.com/80/v2-5739eecbfd934de957734ad3d9913ad9_720w.jpg)
在`RenderDirectionalShadows`中计算fade因子后传递给`_ShadowDistanceFade`
#### 清除阴影的摩尔纹
##### 简单的清除方法
1. 最简单的方法是向阴影投射器的深度添加恒定的偏差,这虽然会产生不精确的阴影但可以消除摩尔纹。
2. 另一种方法是应用斜率比例偏差方法是对SetGlobalDepthBias的第二个参数使用非零值。
##### 法线偏差
新建级联数据变量用来传递级级联数据x为剔除球半径倒数y为使用`√2*2f*cullingSphere.w/tileSize`算出来的级联纹理大小,因为最坏的情况是以像素对角方向进行偏移,所以前面有乘以`√2`,存入shader后乘以Normal取得偏移值之后对`surfaceWS`进行偏移。
##### 可配置的偏差
从剔除数据中获取Light以及其shadowBias之后传递给`ShadowedDirectionalLight`。在绘制阴影前将偏移值传递给`buffer.SetGlobalDepthBias(0,light.slopeScaleBiase);`从剔除数据中获取Light以及其normalBias传递到Shader中乘以上一步中的`normalBias`。
##### 解决阴影裁剪问题
当摄像机处于物体中间时,会出现阴影被裁剪的问题。解决方法是在顶点着色器中添加:
```c#
#if UNITY_REVERSED_Z
output.positionCS.z=min(output.positionCS.z,output.positionCS.w*UNITY_NEAR_CLIP_VALUE);
#else
output.positionCS.z=max(output.positionCS.z,output.positionCS.w*UNITY_NEAR_CLIP_VALUE);
#endif
```
原理是当物体z坐标小于近剪裁面时将顶点挤压or贴在近剪裁面上。
对于大三角产生问题的原因不明白。解决方法是取得灯光的`ShadowNearPlane`之后传递给计算剔除球形参中。
##### PCF过滤
到目前为止我们仅对每个片段采样一次阴影贴图且使用了硬阴影。阴影比较采样器使用特殊形式的双线性插值在插值之前执行深度比较。这被称为百分比紧密过滤percentage closer filtering 简称PCF因为其中包含四个纹理像素所以一般指是2×2 PCF过滤器。
- 添加2x2、3x3、5x5,3种过滤枚举、传递shadowAtlasSize到Shader以及对应的Shader宏并且修改`SetKeywords()`。
- 为每种过滤器设置不同的采样次数与宏设置
`DIRECTIONAL_FILTER_SETUP`为SampleShadow_ComputeSamples_Tent_xxx通过Size与positionSTS.xy计算权重以及uv。之后对阴影进行对应次数的采样。
```c#
float FilterDirectionalShadow (float3 positionSTS)
{
#if defined(DIRECTIONAL_FILTER_SETUP)
float weights[DIRECTIONAL_FILTER_SAMPLES];
float2 positions[DIRECTIONAL_FILTER_SAMPLES];
float4 size = _ShadowAtlasSize.yyxx;
DIRECTIONAL_FILTER_SETUP(size, positionSTS.xy, weights, positions);
float shadow = 0;
for (int i = 0; i < DIRECTIONAL_FILTER_SAMPLES; i++) {
shadow += weights[i] * SampleDirectionalShadowAtlas(
float3(positions[i].xy, positionSTS.z)
);
}
return shadow;
#else
return SampleDirectionalShadowAtlas(positionSTS);
#endif
}
```
在`Shadows.cs`中修改`SetCascadeData()`。增大滤镜大小可使阴影更平滑但也会导致粉刺再次出现。我们需要增加法向偏置以匹配滤波器尺寸。可以通过将纹理像素大小乘以1加上SetCascadeData中的过滤器模式来自动执行此操作。
```c#
void SetCascadeData(int index, Vector4 cullingSphere, float tileSize)
{
float texelSize = 2f * cullingSphere.w / tileSize;
float filterSize = texelSize * ((float)settings.directional.filter + 1f);
cullingSphere.w -= filterSize;
cullingSphere.w *= cullingSphere.w;
cascadeCullingSpheres[index] = cullingSphere;
cascadeData[index] = new Vector4(
1f / cullingSphere.w,
filterSize*1.4142136f);
}
```
##### 级联过渡
在`GetShadowData()`中根据距离计算fade如果不是最后一个球就把fade赋值给cascadeBlend。最后一个球的`·`strength=strength*fade`
```c#
for (i=0;i<_CascadeCount;i++)
{
float4 sphere=_CascadeCullingSpheres[i];
float distanceSqr=DistanceSquared(surfaceWS.position,sphere.xyz);
if(distanceSqr<sphere.w)
{
if(i== _CascadeCount-1)
{
data.strength*=FadedShadowStrength(distanceSqr,_CascadeData[i].x,_ShadowDistanceFade.z);
}
break;;
}
}
```
##### 过渡抖动
提高级联过渡效果。在`LitPassFragment`中给像素计算抖动值
```c#
surface.dither=InterleavedGradientNoise(input.positionCS.xy,0);
```
当使用抖动混合时,如果我们不在上一个级联中,则当混合值小于抖动值时,跳到下一个级联。
```c#
#if defined(_CASCADE_BLEND_DITHER)
else if(data.cascadeBlend < surfaceWS.dither)
{
i+=1;
}
#endif
```
##### 其他功能效果
- 透明度
- 阴影模式
- 裁切阴影
- 抖动阴影
- 无阴影
- 不受光阴影投射器
- 接受阴影

View File

@@ -0,0 +1,204 @@
## ToonShader
书中案例使用了2个Pass来实现效果。
### OutlinePass
模型外面边
```
Pass {
NAME "OUTLINE"
Cull Front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float _Outline;
fixed4 _OutlineColor;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
};
v2f vert (a2v v) {
v2f o;
float4 pos = mul(UNITY_MATRIX_MV, v.vertex);
//IT_MV rotates normals from object to eye space
float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
normal.z = -0.5;
pos = pos + float4(normalize(normal), 0) * _Outline;
o.pos = mul(UNITY_MATRIX_P, pos);
return o;
}
float4 frag(v2f i) : SV_Target {
return float4(_OutlineColor.rgb, 1);
}
ENDCG
}
```
### ToonShader
颜色计算:
`Ramp颜色=tex2D(_Ramp, float2(dot(worldNormal, worldLightDir))).rgb`
`diffuse=贴图颜色*指定颜色*灯光颜色*(Ramp贴图)`
```
fixed4 c = tex2D (_MainTex, i.uv);
fixed3 albedo = c.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
fixed diff = ;
diff = (diff * 0.5 + 0.5) * atten;
fixed3 diffuse = _LightColor0.rgb * albedo * tex2D(_Ramp, float2(diff, diff)).rgb;
```
高光计算:
使用fwidth与smooth主要是用于抗锯齿。https://blog.csdn.net/candycat1992/article/details/44673819
```
fixed spec = dot(worldNormal, worldHalfDir);
fixed w = fwidth(spec) * 2.0;
fixed3 specular = _Specular.rgb * lerp(0, 1, smoothstep(-w, w, spec + _SpecularScale - 1)) * step(0.0001, _SpecularScale);
```
Pass代码
```
Pass {
Tags { "LightMode"="ForwardBase" }
Cull Back
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
#include "UnityShaderVariables.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _Ramp;
fixed4 _Specular;
fixed _SpecularScale;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 tangent : TANGENT;
};
struct v2f {
float4 pos : POSITION;
float2 uv : TEXCOORD0;
float3 worldNormal : TEXCOORD1;
float3 worldPos : TEXCOORD2;
SHADOW_COORDS(3)
};
v2f vert (a2v v) {
v2f o;
o.pos = mul( UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(_Object2World, v.vertex).xyz;
TRANSFER_SHADOW(o);
return o;
}
float4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 worldHalfDir = normalize(worldLightDir + worldViewDir);
fixed4 c = tex2D (_MainTex, i.uv);
fixed3 albedo = c.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
fixed diff = dot(worldNormal, worldLightDir);
diff = (diff * 0.5 + 0.5) * atten;
fixed3 diffuse = _LightColor0.rgb * albedo * tex2D(_Ramp, float2(diff, diff)).rgb;
fixed spec = dot(worldNormal, worldHalfDir);
fixed w = fwidth(spec) * 2.0;
fixed3 specular = _Specular.rgb * lerp(0, 1, smoothstep(-w, w, spec + _SpecularScale - 1)) * step(0.0001, _SpecularScale);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
```
### 素描风格
通过`hatchFactor=max(0,dot(worldLightDir,worldNormal))*7`获取因子。之后计算各个图层的混合因子:
```
if (hatchFactor > 6.0) {
// Pure white, do nothing
} else if (hatchFactor > 5.0) {
o.hatchWeights0.x = hatchFactor - 5.0;
} else if (hatchFactor > 4.0) {
o.hatchWeights0.x = hatchFactor - 4.0;
o.hatchWeights0.y = 1.0 - o.hatchWeights0.x;
} else if (hatchFactor > 3.0) {
o.hatchWeights0.y = hatchFactor - 3.0;
o.hatchWeights0.z = 1.0 - o.hatchWeights0.y;
} else if (hatchFactor > 2.0) {
o.hatchWeights0.z = hatchFactor - 2.0;
o.hatchWeights1.x = 1.0 - o.hatchWeights0.z;
} else if (hatchFactor > 1.0) {
o.hatchWeights1.x = hatchFactor - 1.0;
o.hatchWeights1.y = 1.0 - o.hatchWeights1.x;
} else {
o.hatchWeights1.y = hatchFactor;
o.hatchWeights1.z = 1.0 - o.hatchWeights1.y;
}
```
之后在片元着色器中将因子与6张素描贴图的采样结果相乘以及计算白色区域光照最后相加
```
fixed4 hatchTex0 = tex2D(_Hatch0, i.uv) * i.hatchWeights0.x;
fixed4 hatchTex1 = tex2D(_Hatch1, i.uv) * i.hatchWeights0.y;
fixed4 hatchTex2 = tex2D(_Hatch2, i.uv) * i.hatchWeights0.z;
fixed4 hatchTex3 = tex2D(_Hatch3, i.uv) * i.hatchWeights1.x;
fixed4 hatchTex4 = tex2D(_Hatch4, i.uv) * i.hatchWeights1.y;
fixed4 hatchTex5 = tex2D(_Hatch5, i.uv) * i.hatchWeights1.z;
fixed4 whiteColor = fixed4(1, 1, 1, 1) * (1 - i.hatchWeights0.x - i.hatchWeights0.y - i.hatchWeights0.z -
i.hatchWeights1.x - i.hatchWeights1.y - i.hatchWeights1.z);
fixed4 hatchColor = hatchTex0 + hatchTex1 + hatchTex2 + hatchTex3 + hatchTex4 + hatchTex5 + whiteColor;
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
return fixed4(hatchColor.rgb * _Color.rgb * atten, 1.0);
```

View File

@@ -0,0 +1,313 @@
## BrightnessSaturationAndContrast
### 给摄像机添加脚本
添加2个Meta让其可以在编辑器模式下运行并且只能绑定Camera组件。
```
[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
```
实现基础类
```
using UnityEngine;
[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class PostEffectsBase : MonoBehaviour
{
protected void CheckResources()
{
bool isSupported = CheckSupport();
if (isSupported == false)
{
NotSupported();
}
}
protected bool CheckSupport()
{
if (SystemInfo.supportsImageEffects == false || SystemInfo.supportsRenderTextures == false)
{
Debug.LogWarning("Not Supported!");
return false;
}
return true;
}
protected void NotSupported()
{
enabled = false;
}
protected Material CheckShaderAndCreateMaterial(Shader shader, Material material)
{
if (shader == null)
return null;
if (shader.isSupported && material && material.shader == shader)
return material;
if (!shader.isSupported)
return null;
else
{
material = new Material(shader);
material.hideFlags = HideFlags.DontSave;
if (material)
return material;
else
return null;
}
}
protected void Start()
{
CheckResources();
}
}
```
之后根据需求在子类中添加变量:
```
using UnityEngine;
public class BrightnessSaturationAndContrast : PostEffectsBase
{
[Range(0.0f, 3.0f)]
public float brightness = 1.0f;
[Range(0.0f, 3.0f)]
public float saturation = 1.0f;
[Range(0.0f, 3.0f)]
public float contrast = 1.0f;
public Shader briSatConShader;
private Material briSatConMaterial;
public Material material
{
get
{
briSatConMaterial = CheckShaderAndCreateMaterial(briSatConShader, briSatConMaterial);
return briSatConMaterial;
}
}
private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (material != null)
{
material.SetFloat("_Brightness", brightness);
material.SetFloat("_Saturation", saturation);
material.SetFloat("_Contrast", contrast);
Graphics.Blit(src, dest, material);
}
else
{
Graphics.Blit(src, dest);
}
}
}
```
最后在OnRenderImage中调用Graphics.Blit()进行渲染。
### 添加后处理Shader
```
Shader "PostProcess/BrightnessSaturationAndContrast" {
Properties {
_MainTex ("Base", 2D) = "white" {}
_Brightness("Brightness",Float)=1
_Saturation("Saturation",Float)=1
_Contrast("Constrast",Float)=1
}
SubShader {
Pass{
ZTest Always Cull Off ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
half _Brightness;
half _Saturation;
half _Contrast;
struct v2f{
float4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
};
v2f vert(appdata_img v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv=v.texcoord;
return o;
}
fixed4 frag(v2f i) : SV_Target{
fixed4 renderTex=tex2D(_MainTex,i.uv);
fixed3 finalColor=renderTex.rgb * _Brightness;
fixed luminance=0.2125*renderTex.r+0.7154*renderTex.g+0.0721*renderTex.b;
fixed3 luminanceColor=fixed3(luminance,luminance,luminance);
finalColor =lerp(luminanceColor,finalColor,_Saturation);
fixed3 avgColor=fixed3(0.5,0.5,0.5);
finalColor=lerp(avgColor,finalColor,_Contrast);
return fixed4(finalColor,renderTex.a);
}
ENDCG
}
}
Fallback Off
}
```
## 高斯模糊
与之前不同这里利用RenderTexture.GetTemporary函数分配了一块与屏幕图像大小相同的缓冲区。这是因为高斯模糊需要调用两个Pass我们需要使用一块中间缓存来存储第一个Pass执行完毕后得到的模糊结果。`RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);`我们首先调用`Graphics.Blit(src, buffer, material, 0),`使用Shader中的第一个pass对src进行处理并将结果存储在了buffer中。然后在调用`Graphics.Blit(src, buffer, material, 1)`,使用Shader中的第二个Pass对buffer进行处理返回最终的屏幕图像。最后我们还需要调用`RenderTexture.ReleaseTemporary`来释放之前分配的缓存。
```
private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (material != null)
{
int rtW = src.width;
int rtH = src.height;
RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
Graphics.Blit(src, buffer, material, 0);
Graphics.Blit(buffer, dest, material, 1);
RenderTexture.Release(buffer);
}
else
{
Graphics.Blit(src, dest);
}
}
```
实现降采样:
```
private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (material != null)
{
int rtW = src.width / downSample;
int rtH = src.height / downSample;
RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
buffer.filterMode=FilterMode.Bilinear;
Graphics.Blit(src, buffer, material, 0);
Graphics.Blit(buffer, dest, material, 1);
RenderTexture.Release(buffer);
}
else
{
Graphics.Blit(src, dest);
}
}
```
### CGINCLUDE
使用CGINCLUDE与ENDCG关键字将通用部分引用给其他Pass
```
Shader "Unity Shaders Book/Chapter 12/Gaussian Blur" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_BlurSize ("Blur Size", Float) = 1.0
}
SubShader {
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
half4 _MainTex_TexelSize;
float _BlurSize;
struct v2f {
float4 pos : SV_POSITION;
half2 uv[5]: TEXCOORD0;
};
v2f vertBlurVertical(appdata_img v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv;
o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
return o;
}
v2f vertBlurHorizontal(appdata_img v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv;
o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
return o;
}
fixed4 fragBlur(v2f i) : SV_Target {
float weight[3] = {0.4026, 0.2442, 0.0545};
fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];
for (int it = 1; it < 3; it++) {
sum += tex2D(_MainTex, i.uv[it*2-1]).rgb * weight[it];
sum += tex2D(_MainTex, i.uv[it*2]).rgb * weight[it];
}
return fixed4(sum, 1.0);
}
ENDCG
ZTest Always Cull Off ZWrite Off
Pass {
NAME "GAUSSIAN_BLUR_VERTICAL"
CGPROGRAM
#pragma vertex vertBlurVertical
#pragma fragment fragBlur
ENDCG
}
Pass {
NAME "GAUSSIAN_BLUR_HORIZONTAL"
CGPROGRAM
#pragma vertex vertBlurHorizontal
#pragma fragment fragBlur
ENDCG
}
}
FallBack "Diffuse"
}
```

View File

@@ -0,0 +1,96 @@
## EPIC商城
https://epicgames.hyperwallet.com/hw2web/landing.xhtml?faces-redirect=true&refreshme=true
lou jiajie
Lou JiaJie
owner
INDIVIDUAL
## 向日葵
blueroseslol
orkj694780U9t7r1
ljj199221
## 光猫
useradmin
nhg9h
telecomadmin60087438
使用老爸的账号可以查看账号密码。
## Github Token
- gitea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
- 图床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
## youtube视频下载
https://qdownloader.net/youtube-video-downloader
https://www.y2mate.com/youtube/
## PS4 DNS
- 119.29.29.29
- 114.114.114.114
香港DNS
- 202.181.202.140
- 202.181.224.2
全网通
- 首选119.29.29.29
- 备选182.254.116.116
## Switch DNS
- 主要DNS112.106.53.22
- 备用DNS112.106.53.34
- 主要DNS203.112.2.4
- 备用DNS203.112.2.5
- 主要DNS168.126.63.1
- 备用DNS168.126.63.2
## 百度网盘账号:
- blueroseslol
- ljj88483649
- 18767165526
- ljj199221
## 使用SSR给PS4作为代理
https://mikublog.com/shadowsocks/609
-----------------------------------------------------------------------------------------------------------------------
## 达瓦的UDN账号
blakeguy@163.com
Zhuzw6270668
## Steam 118错误解决
如果没有行政部门背锅,那么就是电信、网通、移动利用垄断地位进行了违法活动。
现在发现的症状基本是DNS污染。
解决方法是把DNS服务器换成208.67.222.222或208.67.220.220 o(* ̄︶ ̄*)o
## 搬瓦工
- 378100977@qq.com
- ljj199221
被墙检测https://wivwiv.com/post/ssr-v2ray-trojan/
## SpeedTest
https://www.speedtest.net/
## ip测速
https://tools.ipip.net/newping.php
## 检查Ip是否被墙
https://bandwagonhoster.com/653.html
## 中心阿里云账号
子账户zjsjcpzljczx@1042965787832287
密码:123456&*()LlL
123456&*()LlL
登录地址http://signin.aliyun.com/1042965787832287/login.htm
### 远程桌面
116.62.69.44
administrator
zaq1@XSW2

View File

@@ -0,0 +1,5 @@
## 参考
https://zhuanlan.zhihu.com/p/413730301
## 步骤
win10以管理员模式打开PowerShell输入`CheckNetIsolation.exe loopbackexempt -a -p=S-1-15-2-1609473798-1231923017-684268153-4268514328-882773646-2760585773-1760938157`之后运行。

BIN
07-Other/技术演示.pptx Normal file

Binary file not shown.

76
07-Other/生活/租房.md Normal file
View File

@@ -0,0 +1,76 @@
## 平台
- 链家
- 自如
## 注意点:
- 马桶
- 马桶水压够不够
- 公寓:马桶是粗管还是细管
- 费用
- 水电费是自己交还是给房东
- 水电表在哪看
- 房子有没有网,能不能拉网?哪些公司的网可以啦?
- 要不要物业费?物业费多少?
- 房租是押几付几
- 合同
- 合同写明多几个沙发几个电视以及物品损坏赔偿事宜,如果损坏了如何赔偿
- 能不能换锁
- 退房手续,退房是不是退了之后押金马上到账,需写明多少天到账
- 清洁费
- 身份证复印件**仅供本次租房使用**
- 需要房东出示房产证与身份证,需要拍照
- 因为第一次租没啥经验,而且又是学校的家属区,就自以为很安全,不会有什么幺蛾子,就没看她房产证,结果这房子是她前夫的,她俩离婚的时候就判给他前夫了,然后没办法我又去找他前夫协商,乱了差不多一个多月才搞定,真是无语了
- 合同注意
- 如何付款
- 水表、电表数值写在合同上
- 确认合同期限
- 租房的地址写在合同上
- 电费、水费写在合同上,并且确认上一个租客的水电费是否结清
- WC卫生、水压
- 维修相关
- 马桶堵了
- 灯泡坏了
- 热水器不出热水了
- 违约了怎么
- 房东提前收回违约
- 房客提前退租违约
- 签完合同之后
- 去房子里拍摄全景视频,记录所有的破损包括电器损坏、墙面脱落、灯泡与电器损坏、家具、玻璃破碎。之后发给中介或者房东,没让他们去解决
- 检查所有插座面板、空调(制冷制热)、灯、冰箱、抽油烟机、地漏是否可以正常工作
- 洗手池水槽是否够深是否正常
- 卧室查看床是否结实
- 确定以上问题解决之后,再付押金与租金
- 附近设施:菜场、超市、快递柜
# 需要的东西
- 洗漱
- [x] 牙刷、牙杯、牙膏
- [x] 3块毛巾
- [x] 洗面奶/肥皂/洗发水/沐浴露
- [x] 洗澡球
- [x] 剃须刀/泡沫
- [x] 晾衣杆/衣架/无印良品同款衣物(内衣)收纳盒/换洗衣服 收纳(竹子)
- [x] 洗衣粉(洗衣球)
- [x] 脸盆
- [x] 吹风机
- 衣服
- 内裤/衣服/被子/垫被
- 烹饪
- [x] 3~4个碗/2个盘子
- [x] 洗碗布/洗洁精
- [x] 烧水壶
- 卫生清洁
- [x] 洁厕灵
- [x] 抹布2块/扫帚/拖把
- [x] 垃圾桶/垃圾袋
- 电脑相关
- [x] 显示器
- [x] 台灯
- [x] 耳塞
- 其他
- [ ] 玫瑰花茶
- [ ] 电磁炉
- [ ] 蒸煮锅
- [ ] 水饺/牛肉卷/汉堡
- [ ] 哑铃
- [ ] 健身用衣服

View File

@@ -0,0 +1,186 @@
```markmap {scale=1.1}
## 对于项目管理的了解
### PSD通用格式
灼华互娱
主要使用ABC缓存结算头发与布料但ABC缓存很可能因为过大而导致导入崩溃。所以灼华互娱对DCC的ABC导出插件进行魔改从而减少ABC缓存大小。但个人认为最佳的解决方案还是重新实现导出缓存格式不过这需要实现DCC导出插件与Unreal4导入插件引擎没有必要使用高精度数据工作相对来说会大一些。
Niagara与Houdini结合
灼华互娱采用的方案是: 使用Niagara制作普通粒子特效。 对于需要解算巨量粒子则使用Houdini生成粒子缓存导入Ue4中。在Ue4中生成模型Instance作为粒子。
#### 个人想法
- 多版本美术资产浏览与比对功能。
### 聊聊自搭建服务器以及Gitlab、Gitea
- 小巧耗资源少但功能有限。貌似邮件系统还有bug。
#### GitLFS与UE4
- 二进制文件臃肿与git清理功能
#### GitHook与WebHook
- GitHook执行Git操作所会调用的钩子。
- WebHook是GitHook的一种通过WebSocket通知其他软件与平台的钩子。
### 自动化集成系统
- 多操作系统开发需要
- 构建与发布产品(不同环境)
- 构建DLL并且上传到哨兵程序中
- 构建自定义引擎
- 运行自动测试程序(UE4内置Session Frontend或者通过自动化集成系统直接与UE4自动测试框架进行交互)
- 检测代码与分支的有效性
- 为每个设置构建对应的版本
#### 好处
- 减少不必要的加班时间
- 可以及时发现bug即使不写自动测试代码
#### 相关的CL工具
##### 因为是个人开发所以,仅仅是大致了解一下。
- TeamCity
- Jenkins
- Bamboo
#### 为单位写个一个简单的全流程数据生产平台,功能为数据录入、展示、查询、以及打包成单机版程序
## 渲染知识
- 我个人认为渲染的本质是:通过对场景与光源的采样,计算传递到摄像机与实际情况接近的光辐射量。
### 光谱
- 光通常指的是人类眼睛可以见的电磁波可见光视知觉就是对于可见光的知觉。可见光只是电磁波谱上的某一段频谱一般是定义为波长介于400至750奈nm之间的电磁波。实际生活中我们看到的光由很多不同强度和波长的光混合而成。
- PBRT中会使用光谱值进行采样与计算最后再将光谱值转化为RGB值输出。
### 采样
根据场景不同,存在不同的采样方式。
#### 采样器基本原理
事先生成若干组采样点样本,之后进行乱序操作。在进行逐像素采样时,使用伪随机的方式选取随机序号的采样点组。
#### 采样分布
##### 分层采样
对采样区域进行分割,各个子区域单独生成样本。
##### 低差异序列
- Van der Corput根据底数将指定长度的正整数序列转换为指定进制的数序列之后以小数符号为分界线将每个数左边翻转到右边去。
- HAMMERSLEY 哈默斯利:使用底数为 样本序号/样本总数 的Van der Corput序列。
- Halton 霍尔顿使用底数为质数序列的Van der Corput序列。
#### 贴图采样
- 双线性插值
- ISOTROPIC TRIANGLE FILTER 各项同性三角形过滤 然无法生成高质量的结果,但速度比较快。该滤波器因为各项同性的关系不支持非正方形或非轴对称的范围。该滤波器的主要缺点是:在斜角度观察纹理时图像容易变模糊。因为不同的角度会导致采样率不一致。
- ELLIPTICALLY WEIGHTED AVERAGE 椭圆权重均值
#### 抗锯齿
- SSAA
- MSAA
- FXAA
- TAA使用了低差异序列作为采样点
#### 采样偏差以及信号重建没看
### 材质
- 材质分为金属(导体)、非金属(绝缘体)、半导体。
#### 反射模型概念BRDF、BTDF、BSDF、BSSSDF
#### 菲尼尔
- 近似函数Schlick施利克
- 菲涅尔方程描述了光线接触到表面后反射与透射的比它实际上Maxwell方程在光滑表面上的求解。 因为在现实环境中光的偏振现象较少所以在PBRT假设光不偏振。
- 一般情况下,计算计算机图形学中的常见操作都会忽略色散现象,以此极大地简化光线传输计算(色散:折射率随着光的波长而变化)。
#### 微表面模型
与普通的着色模型的区别在于,普通的着色模型假设着色的区域是一个平滑的表面,表面的方向可以用一个单一的法线向量来定义来定义。
通过统计学方式来模拟这种微观结构分布。假设物体表面由大量微观几何表面组成,且每个表面都是绝对光滑的。必须保证每个微表面都能反射。因此只能用一个概率分布函数来计算任意方向的微小表面在着色区域中存在的概率。
#### 漫反射模型
- LAMBERTIAN漫反射辐射度/π)
- ORENNAYAR奥伦-纳亚尔)漫反射模型(基于微表面模型)
#### 半向量(位于入射法线与观察法线中间的向量)
#### 微表面分布函数(法线分布函数)
- 法线分布函数由粗糙度决定,主要影响高光
- BeckmannSpizzichino贝克曼-斯皮兹奇诺)
- Phong
- GGX
#### 几何衰减因子Shadowing和Masking
- TrowbridgeReitz特罗布里奇 瑞兹)
#### 金属微表面模型BRDF
- TORRANCESPARROW托伦斯-斯派若)
- Cook-Torrance托伦斯
- $$f(\omega_{o},\omega_{i})=\frac{D(\omega_{h})F(\omega_{o})G(\omega_{o},\omega_{i})}{4 cos\theta_o cos\theta_i}$$
#### 迪士尼PBR模型
##### 漫反射
- 用两个Fresnel项来增加入射角度低时反射的光线能量强度来模拟这种效果
float DisneyDiffuse(Vector3 In, Vector3 Out)
{
float oneMinusCosL = 1.0f - AbsCosTheta(In);
float oneMinusCosLSqr = oneMinusCosL * oneMinusCosL;
float oneMinusCosV = 1.0f - AbsCosTheta(Out);
float oneMinusCosVSqr = oneMinusCosV * oneMinusCosV;
// Roughness是粗糙度IDotH的意思会在下一篇讲Microfacet模型时提到
float IDotH = Dot(In, Normalize(In + Out));
float F_D90 = 0.5f + 2.0f * IDotH * IDotH * Roughness;
return INV_PI * (1.0f + (F_D90 - 1.0f) * oneMinusCosLSqr * oneMinusCosLSqr * oneMinusCosL) *
(1.0f + (F_D90 - 1.0f) * oneMinusCosVSqr * oneMinusCosVSqr * oneMinusCosV);
}
#### FourierBSDF
#### 曲线拟合BSDF-使用真实测量值进行曲线拟合FF15
### 光线追踪框架
#### 求交加速框架BVH、KD树、SBVH
- BVH在一个轴上对两个图元进行分割。分割过程中通过公式计算最佳分割点最佳的表面积与体积步骤1、计算每个图元的边界信息并且存储在数组中2、使用指定的方法构建树3、优化树使得树更加紧凑
- KD树经过空间分割的图元数。
- SBVHNvidia改进过的BVH结构先对图元进行空间分割最后使用BVH方式分割。 https://www.nvidia.com/object/nvidia_research_pub_012.html
#### 蒙特卡洛方法与重要性采样(忘记了)
- 逆推法、舍选法
#### 路径追踪
##### 双向路径追踪
## UE4灯光相关
### 物理灯光:使用现实世界的测量值作为标准进行灯光设置
- 之前版本中Unreal4的灯光使用的是虚幻单位这是的之前版本的灯光没有一个物理值的衡量标准。你很可能会因为后处理空间、曝光等因素影响而无法设置出正确的亮度值。例如你已经将一个区域的效果调整的很自然但是别的区域就会很奇怪bloom、阴影、光照等效果互相不匹配。 而前人已经积累了大量的摄影知识与经验,所以我们没有必要光靠感觉来设置灯光亮度与曝光度,而需要使用摄影标准来设置灯光,从而使得场景更加真实。
- 大晴天 15~16
- 多云 13
- 晚上月光 -6~-2
### UE4的曝光问题
- 因为UE4的渲染框架都是以EV100 为0设计的所以需要将经验上使用的灯光强度从当前曝光值下转换到EV100 0不然会出现若干bug比如默认的天空盒。
### 流程
#### 场景初始化
- 禁用自动曝光
- 禁用SSAO以及SSR
- 保持默认Tone Mapping
- 确认天光与方向光为默认数值。天光亮度为1 方向光亮度为3.141593(π)
- 设置测试用球(与色卡) 使用亮度为3.141593(Pi)的方向光此时灰球的亮度为0.5。使用Color Picker、或者截图使用Photoshop查看
- 确定你的材质是物理正确的。BaseColor太暗或者太亮 Metallic应该为0或者1。黑色或者白色Mask Specular应该为0~1范围的浮点数。不应该为颜色值。
#### 后续流程
- 设置曝光相关属性。
- 将所有灯光都是设置为Stationary类型。并开始设置方向光与其他主光源的基础属性方向、亮度等以及天光与反弹次数
- 设置LightMass属性使用预览级对场景进行测试渲染。
- 开启LightingOnly模式对光照进行检查。
- 给场景中的带有反射属性的物体添加反射球并调整属性。(命令行全局的反射质量、与抗锯齿质量
- 调整场景雾气
- 后处理调色
#### 静态方案
- lightMasslightMap
- VolumeLightMap二阶球谐与旧版区别在于可以使用流加载以及手机使用
#### 动态方案
- 级联阴影
- 胶囊阴影
- 接触阴影(后处理)
- 距离场GI废弃
- 距离场阴影与AO
- 4.25屏幕空间GI
```

View File

@@ -0,0 +1,173 @@
## RDG
### FrameGraph
首先需要了解Frame Graphe的设计思想为了速成看了知乎大佬 奔驰 的文章。但也因为速成必然会在一些地方有所误解还请见谅。这里说一下我的理解Frame Graphe思想可以理解为一帧中执行逻辑的图没有限定是渲染还是Gameplay逻辑但不管怎么说其重点在于图。
大致操作为将逻辑以最小功能为标准封装成单独的PassPass与Pass之间是变量或者可以理解为资源的传递的关系因此就可以把整个逻辑关系转换成一个Graphe。
UE4的RDG全名为Render Dependency Graph感觉是为了能更好的管理资源的加载回收尤其是RT拯救一下显卡上珍贵的显存以及IO带宽。同时方便程序员理解代码不会写出毛线团一样的代码。至于生成的图没有看过在哪估计在4.25加在Insight调试系统里吧。
### 大致流程
通过FRDGBuilder对象构建RDG渲染流程。之后塞入定义的RDG Uniform以及Shader后运行这个流程。RT会使用外部的IPooledRenderTarget接口取得比如各种GBuffer当然也可以自己创建之后绑定到RDG Uniform中的RenderTargets[0]数组中。最后根据是ComputShader还是PixelShader选择分发或是绘制即可。但源码里也没有RDG的PixelShader的代码95%都是ComputeShader的经过试验RDG的PixelShader的使用方法还是和旧版的GloalShader比较像的。
既然说到这个,之前我也是看了虚幻开放日的技术分享,知道了两者的区别。
### ComputeShader与PixelShader对比
ComputeShader对于PixelShader的优势
1. PixelShader只能处理当前ShaderComputeShader是任意位置可写。可以用于编写屏幕空间反射等需要将效果写入任意位置的效果。
2. 可以更好地利用显卡的并线单元。
3. 共享内存:举个例子模糊、等需要多次采样各个像素的算法,使用共享内存就可以减少采样次数与消耗。
PixelShader对于ComputeShader的优势
1. PixelShader可以预加载贴图并且缓存UV使得读取贴图的效率会非常高。而ComputeShader需要计算出UV所以做不到这点。
2. PixelShader支持FrameBuffer压缩减少带宽压力。
3. PixelShader支持更多的贴图格式。
ComputeShader还存在无法读取UAV比如把RT转化成SRV后才能读取问了EPIC的luoshuang说是历史遗留问题UE5会解决。
## 制作玉石效果
使用寒霜的FastSSS方案这里我下载了演讲的PPT。厚度贴图可以使用Substance Painter烘焙。算法是反转法线后使用AO算法在像素对应的三维坐标球形区域内生成若干采样点计算模型内部的点与总点数的比。之后就是直接套寒霜公式了。
## 光线追踪
《Ray Tracing in One Weekend》只是尝个鲜学习《Ray Tracing from the Ground Up》后拥有了一个基础的Ray Tracing框架。然后就是PBRT了相当不错的教科书。因为单线程渲染效率太低所以使用了直接使用Inter的TBB库但最后发现写的渲染器
会出现样本混乱的情况的。同时因为没有写并行库的基础所以之后的后面几章就没有去写代码了。如果有时间会去看《c++并发编程实战》的英文版来学习并行编程。
### PBRT
1. pbrt采用了FLOAT宏来调整浮点类型不过我记得VS的设置就可以直接调整。并且手动实现了EFLoat类来消除浮点数在四则运算后积累的误差。
2. pbrt采用c++11的线程编写没使用任何并行库。
3. 实现了一个场景描述文件格式可以将dcc软件制作的场景导入渲染。
### 一束光线
我们从眼睛中看到的图形可以理解为视觉细胞接收到场景中辐射与反射的光辐射信息的结果光线追踪就是计算机通过模拟这一过程所发明出的算法。根据光的波长不同将其排列成为光谱。但受限于人眼感光细胞人眼无法看到所有颜色对于可见光人们建立了不同色域范围的色彩标准。常用的是rgb、sRGB等。
#### 光源
PBRT为了将光谱值转换为RGB值实现了SampledSpectrum采样频谱与RGBSpectrumRPG频谱这里我没看懂好像是通过对3条曲线采样求积分得到3轴的比值。
#### 采样
除非直视光源不然人眼接收到的光全都是经过N次反射的因为我们可以把人眼接收到的图像理解成一个N阶多项式方程。同时这也相当于将一个自然现象模拟问题转化为一个数学问题可以用其他的数学工具来解决问题。但该方程无法直接求解所以会使用蒙特卡洛方法求近似值。
#### 蒙特卡洛方法
是一种以概率统计理论为基础的数值计算方法。它的核心思想就是使用随机数(或更常见的伪随机数)来解决一些复杂的计算问题。通过对大量抽样样本进行统计,计算出其分布规律。
概率密度函数 pdf 一个描述这个随机变量的输出值,在某个确定的取值点附近的可能性的函数。
累积分布函数 cdf 是概率密度函数的积分能完整描述一个实随机变量X的概率分布。
有关抽样:
#### 逆转法
#### 舍选法
某些函数可能无法通过对其进行积分从而获得pdf或者无法通过计算获得逆cdf。舍选法是一种无需进行以上任意一个步骤就能根据函数分布生成样本的技术。本质上它是一种投飞镖法假设我们想从某个函数f(x)中抽取样本但是我们有一个pdf p(x)满足f(x)< c p(x),假设我们知道如何从p中获取样本
这个过程反复选取一对随机变量(X,ξ)如果点(X,ξc p(X))是在f(X)下面,那么接受样本X否则它将被拒绝并再次选取一个新的样本对这个方法的效率取决于f(x)与cp(x)的接近程度越相似收敛地越快而且适用于任意维度的函数
#### 重要性采样
选择一个与目标概率密度函数具有相同形状的分布函数进行抽样来减少方差形状越接近收敛越快Raytracing in weekend中对方形面光源运行了重要性采样
#### 分层采样
对采样区域进行分割各个子区域单独生成样本
#### 低差异序列
低差异序列可以非常高效的生成分布非常均匀的高质量样本集合相比伪随机数极大的提高蒙特卡洛积分收敛的效率并且它们的实现都不复杂
- Van der Corput根据底数将指定长度的正整数序列转换为指定进制的数序列之后以小数符号为分界线将每个数左边翻转到右边去
- HAMMERSLEY 哈默斯利使用底数为 样本序号/样本总数 的Van der Corput序列
- Halton 霍尔顿使用底数为质数序列的Van der Corput序列
UE4中的TAA好像就是用的低差异序列生成的样本
#### 使用均值样本会产生 混叠现象,也就是摩尔纹
在信号处理以及相关领域中走样混叠在对不同的信号进行采样时导致得出的信号相同的现象它也可以指信号从采样点重新信号导致的跟原始信号不匹配的瑕疵它分为时间走样比如数字音乐以及在电影中看到车轮倒转等和空间走样两种摩尔纹)。这里我们不详细展开
#### 采样器基本原理
事先生成若干组采样点样本之后进行乱序操作在进行逐像素采样时使用伪随机的方式选取随机序号的采样点组
#### 抗锯齿
因为场景的定义在三维空间中是连续的而最终显示的像素则是一个离散的二维数组所以判断一个点到底没有被某个像素覆盖的时候单纯是一个或者没有"问题丢失了连续性的信息导致锯齿
SSAA超采样抗锯齿高分辨渲染之后取平均值结果
MSAA在光栅化阶段判断一个三角形是否被像素覆盖的时候会计算多个覆盖样本Coverage sample最后根据权重样本占比所在的片元进行着色不兼容延迟渲染管线
FXAA边缘检测抗锯齿是一种后处理抗锯齿技术
TAA时间抗锯齿将采样放到时间轴上的抗锯齿技术对一个像素使用抖动效果之后再将所有的采样混合起来他需要解决判断每帧中这个像素的移动位置但在一些运动较大或者帧数较低的情况会出现 重影的问题
#### 贴图采样
- 双线性插值
- ISOTROPIC TRIANGLE FILTER 各项同性三角形过滤 然无法生成高质量的结果但速度比较快该滤波器因为各项同性的关系不支持非正方形或非轴对称的范围该滤波器的主要缺点是在斜角度观察纹理时图像容易变模糊因为不同的角度会导致采样率不一致
- ELLIPTICALLY WEIGHTED AVERAGE 椭圆权重均值
#### 偏差分析与重采样
没学过相关知识无法继续
#### 加速结构
如果光线每次与物体交互都需要遍历所有三角形那效率将会非常低为了提高求交速度我们可以在渲染前实现构建加速结构常用的有BVHKD
BVH
1. 计算每个图元的边界信息并且存储在数组中图元号包围盒以及中心点
2. 使用指定的方法构建树中间对半切的方法SAH首先计算图元位置将其分配到12个桶中并且计算每个桶最大边界盒再遍历每个桶边界盒的Min与Max坐标计算最佳切分点通过 切分后两个边界盒的表面积/当前包围盒的表面积来计算求交效率子图元中质心距离最大的轴向作为分割方向
KD树对3个轴的所有的切分方案进行尝试最终计算出最优的空间分割方案
KD树的分割每个轴遍历所有分割方案与BVHSAH分割每个轴12次分割更加精确BVH保证每个图元只有一次引用内存占用较少构建较为简单而KD树并不保证所以内存占用较多因此KD树的求交效率要比BVH好也导致了渲染时需要花费比BVH更多的时间在构建KD树上 同时KD树在求交时会计算与光线与分割面的相交位置是否在[min,max]中来判断是否需要与茎节点中的两个节点一一求交以此来减少求交计算量而BVH通过深度优先遍历树进行求交的 总结BVH是基于物体分割的KD数是基于空间分割的
早几年NVIDIA研发出将两者结合的方案SBVH这种算法可以节约因为图元分布大小不均匀而造成的BVH树求交效率低下
1. 找到一个对象分割候选者使用SAH算法构建一个BVH
2. 找到一个空间分割候选者类似构建KD树算法
3. 选择获胜者基于SAH选择消耗最小的作为最后结果如果不满足叶子节点生成条件则继续分割
我记得是前几次进行空间分割后续使用对象分割
### 材质模型
接下来就是处理光线与物体交互的结果了PBRT把材质分开金属非金属与半导体其中半导体较为复杂Pbrt里没有介绍
根据材质性质不同可以分为一下表面分布函数
BRDF双向反射分布函数在单位微表面上反射辐亮度与入射辐射照度的比值
BTDF双向透射分布函数在单位微表面上折射辐亮度与入射辐射照度的比值
与两者结合的BSDF对于半透明材质则使用BSSSDF双向表面散射分布函数
#### 微表面模型
本来对于一个不确定表面可能需要使用采样要求积分因为物体的微观表面参差不齐无法通过分析的方法精准模拟但微表面模型假定物体由大量微小的绝对光滑表面组成之后就可以根据统计学计算出模拟出看上去正确的结果
现在用得较多的Burley模型迪士尼模型也是UE4使用的就是基于这个微表面模型
#### BRDF公式
对于各项同性的BRDF公式就是 漫反射 +菲尼尔函数 * 法线分布项 D * 几何项G/(4 cos入射角 * cos出射角)
Cook-Torrance
##### 菲尼尔
- 一般都会使用近似近似函数Schlick施利克
- 菲涅尔方程描述了光线接触到表面后反射与透射的比它实际上Maxwell方程在光滑表面上的求解 因为在现实环境中光的偏振现象较少所以在PBRT假设光不偏振
- 一般情况下计算计算机图形学中的常见操作都会忽略色散现象以此极大地简化光线传输计算色散折射率随着光的波长而变化)。
##### 微表面法线分布函数 (法线分布项 D)
- 法线分布函数由粗糙度决定主要影响高光
- BeckmannSpizzichino贝克曼-斯皮兹奇诺
- Phong
- TrowbridgeReitz特罗布里奇 瑞兹ggx
- 迪士尼改进了 TrowbridgeReitz 将其命名为GTR
##### 几何衰减因子Shadowing和Masking几何项G
- Smith阴影函数
- 迪士尼对Smith 改造smith-ggx
##### 漫反射
- pbrt中使用 ORENNAYAR奥伦-纳亚尔漫反射模型基于微表面模型
Raytracing in weekend会使用LAMBERTIAN漫反射 辐射度/π 作为漫反射项即不论任何角度都会反射出一样的辐射度
Lambert漫反射模型在边缘上通常太暗Disney开发了一种用于漫反射的新的经验模型以在光滑表面的漫反射菲涅尔阴影和粗糙表面之间进行平滑过渡思路是使用了2个Schlick Fresnel近似相乘作为调节因子
##### 测量BRDF
pbrt中还介绍了一种通过测量来拟合brdf的方法ff15中用到这种方法
##### BTDF
没看过应该乘以 菲尼尔来计算的
##### BSSDF
没看过UE4好像通过生成均值采样点之后再通过距离进行近似计算的
##### 迪士尼模型
根据透明度参数对透明渲染结果与绝缘体渲染结果混合之后再根据金属度与金属渲染结果混合
### 路径追踪
从摄像机发射采样光线最后把这条光线若干次反射过程中结果累加起来
### 优化
#### 光子映射
通过让光源发射光子来生成光子贴图这样就能让收敛速度变快
#### 双向路径追踪追踪
同时从光源与摄像机发射采样光线一起构建光路之后将各个反射点直接连接起来这样就能快速构建多条光路了大大加快收敛速度当然这些光路需要进行可见性测试

84
07-Other/简历/成果.md Normal file
View File

@@ -0,0 +1,84 @@
```markmap {scale=1.1}
## 美术流程
### Ue4中的场景物体融合方式与优化
1. World aligned texturing 世界坐标映射贴图
2. Pixel depth offset 像素深度偏移
3. Distance field mesh blending 距离场模型融合
4. Runtime virtual texturing 实时虚拟贴图
### Layered Material
1. 尝试外包一个角色并让外包以Material Mask的方式使用Substance Painter完成贴图。最后走完EPIC的Layered Material流程。
### UE4头发方案研究
1. 学习Ornatrix for Maya插件并且尝试制作头发。
2. 学习Ornatrix制作面片头发方案并且制作出成品。
3. 走通UE4 Niagara Hair流程。
### UE4物理灯光流程
1. 如何使用物理灯光系统
2. 流程包括:初始化场景(关闭若干后处理效果,并且固定曝光)=》场景检查(贴图饱和度与亮度)=》按照当前环境对应的现实状况设置曝光度与天光HDRI因为每个HDRI的亮度范围不一样还可以在环境球材质中调整=》主要自然光源完成后烘焙二阶球谐GI=》按照场景发光物体进行布光=》烘焙阴影并检查是否有错误阴影=》逐个开启后处理效果并检查(反射=》AO=》
3. 了解UE4静态光照与动态光照所有相关的功能与用法。
### 动画蓝图
1. 学习物理模拟动画节点
2. 学习AdvancedLocomotionV3/V4 项目,以此对UE4动画系统有了一定了解。FootIk、8方向运动、状态切换逻辑
3. 为了复现AdvancedLocomotionV4项目中动画的动画曲线这些曲线会控制状态机以及其他融合动画效果使用AnimModifier编写生成逻辑最终复现了Run、Walk等移动动画的曲线。
4. 学习动画蓝图的动态加载功能,解决当动画蓝图逻辑较多时的性能问题。对于每个武器拥有不同攻击动画与逻辑的情况,有很大的作用。
### UE4FaceARSample Iphone面部方案学习
## 程序
### GameplayAbilitySystem GAS
1. 学习GAS系统了解Ability、Effect、AttributeSet、Tasks等类。
1. 完全学习ActionRPG项目并且将物品系统封装成组件放在插件中以备后续使用。
2. 解决只使用一个Ability类实现一套连招技能。
3. 学习GameplayAbilitySystem中配套的Debug方法
### 编写Slate控件
1. 实现整数版本的SpinBox
2. 实现类似血源诅咒那种带有虚血的进度条
3. 实现通过函数调用C++、蓝图通用的MessageBox顺便了解了UMG、Slate、GameViewportClient之间的大致关系
### DCC插件
1. 调用Maya的HumanIK插件实现骨骼批量重定向功能将Max的Bip骨骼转换成Ue4骨骼。后续可以再加入其它定制功能。
2. 实现在选中父骨骼后批量生指向若干选中顶点的之骨骼并且生成蒙皮权重。大致作用是将低模角色的BlenderShape权重传递到高精度角色上。
### 项目管理
1. 和他人合作Demo时在租用的VPS上搭建Gitea以实现版本管理包括二进制文件
2. 学习UE4的自动测试框架。
## 图形学效果
### 渲染框架
1. 通过学习EPIC的PPT以及源代码最后撰写全网第一份RenderDependencyGraph(RDG)的使用教程以及HelloWorld案例。案例已更新至4.26版本)
2. 通过学习源代码,了解4.25版本传统渲染框架GlobalShader的变动并撰写文章。
### Shader
1. UE4后处理材质各种卷积描边、各种模糊等。
2. 在Ue4中实现对二次元模型进行透视校正让模型更加二次元顺便学习了BasePass中的顶点数据处理过程。
3. 使用Gerstner波制作海洋效果水体透射使用水面深度-水底深度来得到深度值之后对使用2种颜色差值进行得到最后颜色白沫使用基于Saturate高度的方法。后面如果再做打算使用houdini通过粒子模拟的方式来生成白沫贴图。
4. 学习EPIC TA的RayMarching 溶球效果使用了SDF
5. 学习EPIC TA的RayMarching 2D 3D云效果。
6. 修改ShaderModel以实现卡通渲染材质。
### 自定义图元类
1. 在不修改管线的情况下,以插件的方式实现物体外描边效果
2. 在不修改管线的情况下以插件的方式实现UE4剖面效果
### 离线渲染学习
1. 学习完《An Introduction to Ray Tracing》 系列3本书并且渲染出对应效果的图片。
2. 学习《Ray Tracing from the Ground Up》并且使用Qt重新实现一遍离线渲染器单核渲染
3. 学习《pbrt》至第10章后续因为没有多线程库编写能力只能大致看了看。
## 其他效果
### 面试J3时的面试题
1. 看gif图实现枪支表面的能量效果使用一张Noise贴图分别使用平移与旋转2种移动UV方式叠加到一起。2种移动方式的贴图各自乘以了不同的颜色。
2. 在不是用UE4 SSS ShaderModel情况下使用寒霜的FastSSS方案实现了玉石枪支的效果。因为家里电脑性能以及面试周期问题没有通过添加ShaderModel的方式来修改所以能量不够守恒。
3. 使用Python为Maya编写一个生成圆柱体的插件点、面、法线都是手动计算的可以任意设置大小、横纵以及圆弧段数UV采用Maya内置的映射命令。 使用Qt编写了插件界面。
4. 实现了一个在屏幕空间宽度恒定的“赛博朋克”风格网格材质,通过使用了透视矩阵 对宽度进行校正来实现。
5. 在不改变模型拓扑结构或者动态改变模型结构的情况下设计一种基于Vertex Program的算法动态改变毒圈模型的顶点位置。使得玩家走近毒圈边缘时看到尽可能平滑的弧线。——最后因为时间不够只实现了核心逻辑使用一个没有封口的圆柱体输入2个角度让圆柱体像扇子那样按照角度进行折叠也就是将顶点挤压到一起。最后只差通过摄像机方向、位置、fov、圆心位置计算2个角度以及威风口处的旋转位置将威风口藏在摄像机背后
## 代码
1. GAS
2. 网络联机
3.
```

52
07-Other/简历/技能.md Normal file
View File

@@ -0,0 +1,52 @@
## UE4
使用Custom节点实现后处理效果 (https://www.cnblogs.com/blueroses/p/6361517.html、
https://www.cnblogs.com/blueroses/p/6511751.html)
学习Ryan Brucks的溶球视频教程https://www.cnblogs.com/blueroses/p/6391995.html
学习Ue4的GlobalShader与Computer写法https://zhuanlan.zhihu.com/p/66514192、
https://zhuanlan.zhihu.com/p/66841824
在不修改源代码的情况下实现多Pass效果https://zhuanlan.zhihu.com/p/69139579、
https://zhuanlan.zhihu.com/p/69156465
学习GameplayAbility系统、ActionRPG项目https://zhuanlan.zhihu.com/p/76395757、
https://zhuanlan.zhihu.com/p/75987858等
学习UE4的灯光流程https://zhuanlan.zhihu.com/p/89270442、https://zhuanlan.zhihu.com/p/92456949等
探索UE4头发制作方法https://zhuanlan.zhihu.com/p/98751524、https://zhuanlan.zhihu.com/p/99345059
Ue4动画系统学习https://zhuanlan.zhihu.com/
### Slate
扩展Slate的进度条以实现血源诅咒中的虚血效果。
### 渲染
Shader
灯光流程
渲染框架
### 动画系统
### 编程
GAS
## 其他语言与框架
Qt、Python、JavaScript、VUE全家桶、Node.js。
## DCC
1. 对Maya比较熟悉编写过2个Maya插件分为调用HumanIK对动作文件进行批量重定向、批量生成指向选中顶点的骨骼并赋予蒙皮用于表情
2. 目前正在学习Houdini。
## 能力佐证
- 知乎https://www.zhihu.com/people/blueroseslol/
- Github: https://github.com/blueroseslol
- Bloghttp://blueroses.top
- Blog(已废弃):https://www.cnblogs.com/blueroses
## 工作经历
国家锁具产品质量监督检验中心 2013年07月~至今
- 使用Node.js为公司编写内网数据管理系统具有数据汇总、展示、打包单机版等功能。
- 使用Electron为公司编写数据查询软件。
- 使用Qt为公司编写其他工具产品。

BIN
07-Other/背景.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@@ -0,0 +1,33 @@
- CPU AMD 7950x ¥3899.00
- https://item.jd.com/100039537667.html
- 板U套装 https://item.jd.com/100040434245.html#none
- 机箱
- 追风者P500A黑中塔EATX(前/顶360水冷侧透版 ¥579.00
- https://item.m.jd.com/product/2285652.html?utm_user=plusmember&gx=RnExy2RbYGXRmtRP--txX5kFp3gD3KX1Scsq&ad_od=share&utm_source=androidapp&utm_medium=appshare&utm_campaign=t_335139774&utm_term=Wxfriends
- 追风者(PHANTEKS)G500A白P500A ¥889.00
- https://item.m.jd.com/product/2285652.html?utm_user=plusmember&gx=RnExy2RbYGXRmtRP--txX5kFp3gD3KX1Scsq&ad_od=share&utm_source=androidapp&utm_medium=appshare&utm_campaign=t_335139774&utm_term=Wxfriends
- 主板
- ROG CROSSHAIR X670E GENE ¥ 3999.00
- https://item.jd.com/100038989087.html
- 白色ROG Strix X670E-A ¥2999
- https://item.jd.com/100038989095.html#crumb-wrap
- ROG Strix X670E-E ¥3999
- https://item.jd.com/100038989121.html#crumb-wrap
- 硬盘
- 致态ZhiTai长江存储 2TB TiPlus7100 ¥1099 * 4
- https://item.m.jd.com/product/100049404395.html?utm_user=plusmember&gx=RnExy2RbYGXRmtRP--txX5kFp3gD3KX1Scsq&ad_od=share&utm_source=androidapp&utm_medium=appshare&utm_campaign=t_335139774&utm_term=QQfriends
- 电源
- 海韵SEASONIC 金牌全模FOCUS GX1000W ¥1299
- https://item.m.jd.com/product/100004187691.html?utm_user=plusmember&gx=RnExy2RbYGXRmtRP--txX5kFp3gD3KX1Scsq&ad_od=share&utm_source=androidapp&utm_medium=appshare&utm_campaign=t_335139774&utm_term=QQfriends
- 内存
- 芝奇G.SKILL64GB(32Gx2) DDR5 6000 https://item.jd.com/100044083045.html
- 散热
- 瓦尔基里(VALKYRIEE360 LOKI VK 一体式 ¥948
- https://item.m.jd.com/product/100047920915.html?utm_user=plusmember&gx=RnExy2RbYGXRmtRP--txX5kFp3gD3KX1Scsq&ad_od=share&utm_source=androidapp&utm_medium=appshare&utm_campaign=t_335139774&utm_term=QQfriends
- 显卡
- 七彩虹Colorful战斧 GeForce RTX 4060 Ti ¥ 3199
- https://item.jd.com/100058595037.html
- 风扇 8块一个
- https://item.taobao.com/item.htm?id=528014114456&price=8&sourceType=item&sourceType=item&suid=acb41b6f-9be2-4b61-9fd4-2e6c5dcaba8e&shareUniqueId=20688680961&ut_sk=1.Y9fmoWP2y8sDADFVchIQ9X6F_21646297_1679985712096.Copy.1&un=da848ce999c44d6e0f8768acc41474bb&share_crt_v=1&un_site=0&spm=a2159r.13376460.0.0&sp_abtk=gray_1_code_simpleAndroid2&tbSocialPopKey=shareItem&sp_tk=ZFZMY2Q5Z3h2VnM%3D&cpp=1&shareurl=true&short_name=h.UIy0Zm8&bxsign=scdQS6K-k4f5NIJlD-U8U_OvYntdUFj3Bz89wSvxWAiOdJ6XGqMJsWuATqex2bMX6ByYm_6Ee0JtsNO5OqdeVAYWgAU6WoqwMyFkme0PL0abkeFweBQOQP7R2LyLRw9hn2j&tk=dVLcd9gxvVs&app=chrome
24000