尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

自由学习记录(220)

自由学习记录(220) AC_FaceReferenceFrameToCustomPrimitiveData然后左侧My Blueprint面板找到Functions。点击右侧。命名UpdateFaceReferenceFrameCustomPrimitiveData选中函数入口节点。在 Details 中设置Access Specifier PublicPure false不需要勾选Call In EditorCall In Editor是在 Details 面板生成手动按钮不是让函数自动随编辑器 Tick 执行。源码里UActorComponent有C // ActorComponent.h /** Should this component be ticked in the editor */ uint8 bTickInEditor : 1;注意它前面没有UPROPERTY(...)。这意味着它虽然是 C public 成员但没有进入 UE Reflection System因此普通 Blueprint Actor Component 不能直接在 Details 里把它设成true。C Component 则可以直接C UFaceReferenceFrameToCustomPrimitiveData:: UFaceReferenceFrameToCustomPrimitiveData() { PrimaryComponentTick.bCanEverTick true; bTickInEditor true; }于是变成C ActorComponent └─ bTickInEditor true └─ 自己直接获得 Editor Viewport Tick └─ TickComponent() └─ 更新 FaceReferenceFrame而更接近共同底层 UWorld::Tick(...) └─ Tick Task / TickFunction framework └─ FActorComponentTickFunction └─ UActorComponent::TickComponent()没 PIE、普通 Level Editor ViewportEditor World └─ UWorld::Tick(LEVELTICK_ViewportsOnly, DeltaTime) └─ Component TickFunction 被调度 └─ ExecuteTickHelper() └─ 遇到 ViewportsOnly └─ 检查 bTickInEditor OR Owner-ShouldTickIfViewportsOnly()而 PIEPIE World └─ UWorld::Tick(LEVELTICK_All, DeltaTime) └─ 同样的 TickFunction framework └─ ExecuteTickHelper() └─ TickType ! ViewportsOnly true ↓ 直接放行 ↓ TickComponent()“Tick 本身一直执行只是进不同分支”也不成立。比如 Blueprint Editor Preview Viewport 关闭 Realtime 后源码会变成C PreviewScene-GetWorld()-Tick( LEVELTICK_TimeOnly, DeltaSeconds );而UWorld::Tick()里面明确C bool bDoingActorTicks (TickType ! LEVELTICK_TimeOnly) !bIsPaused ...所以LEVELTICK_TimeOnly ↓ bDoingActorTicks false ↓ Actor / Component TickFunctions 根本不会进入正常 actor tick 调度没有TickType接口只能是C World-Tick(DeltaSeconds);那么UWorld::Tick()一旦被调用就很难区分我只想更新时间 我想更新 Editor Viewport但不要完整 Gameplay 我想正常运行整个游戏世界 我现在处于暂停相关的 Tick于是 UE 把这个“本次 Tick 的推进策略”作为参数传进去C World-Tick(TickType, DeltaSeconds);目前核心几个值就是C LEVELTICK_TimeOnly LEVELTICK_ViewportsOnly LEVELTICK_All LEVELTICK_PauseTick你可以把它理解成一个World Tick execution policy。同一个 Actor/Blueprint 下的多个 ComponentTick 顺序不是靠 Components 面板里的排列顺序而是靠 Tick prerequisites / TickGroup。你这个“必须等 Skeletal Mesh 更新完骨骼再读 Socket”的场景优先应该用 prerequisite。“我的 Component Tick 必须发生在这个 Prerequisite Component 的 Tick 之后。”比如你的 FaceReferenceFrame Component 必须等 SkeletalMeshComponent 先更新C FaceComponent-AddTickPrerequisiteComponent(SkeletalMeshComponent);如果是在FaceComponent自己内部写C AddTickPrerequisiteComponent(TargetSkeletalMeshComponent);源码最终就是C PrimaryComponentTick.AddPrerequisite( PrerequisiteComponent, PrerequisiteComponent-PrimaryComponentTick );Slate 源码↓ compile很多 Slate .obj↓ link┌─────────────────────────────┐│ UnrealEditor-Slate.dll │ ← 真正的 Slate 机器码在这里└─────────────────────────────┘┌─────────────────────────────┐│ UnrealEditor-Slate.lib │ ← 告诉别的模块“Slate 对外有哪些符号”└─────────────────────────────┘微软的 linker 正常构建一个带 exports 的 DLL 时就会顺便生成对应的 import library之后其他程序在链接这个 DLL 时使用这个.lib。learn.microsoft.com/en-us/cpp/build/reference/working-with-import-libraries-and-export-files假设你的插件写C FSlateApplication::Get();编译器先只是生成MyPlugin.cpp ↓ MyPlugin.obj 里面有 “我要调用 FSlateApplication::Get() 但我这里没有它的实现”然后 linker 开始工作MyPlugin.obj │ │ 我需要 FSlateApplication::Get() ↓ UnrealEditor-Slate.lib │ │ 告诉 linker │ “有这个 symbol 来自 UnrealEditor-Slate.dll” ↓ UnrealEditor-MyPlugin.dll注意此时并没有把FSlateApplication::Get()的实现从.lib复制进你的插件。最后运行时MyPlugin.dll │ │ 调用 ↓ Windows Import Address Table │ ↓ UnrealEditor-Slate.dll │ ↓ 真正执行 FSlateApplication::Get()Windows 运行 UnrealEditor 时会把它映射进进程地址空间UnrealEditor.exe process 0x00007FF... UnrealEditor-Core.dll 0x00007FF... UnrealEditor-Engine.dll 0x00007FF... UnrealEditor-Slate.dll 0x00007FF... UnrealEditor-YourPlugin.dll这就是一个真正的 runtime module。现在缺.lib时出现的是 linker 问题compile ✅ MyPlugin.cpp → MyPlugin.obj link ❌ 找不到 UnrealEditor-Slate.lib但你的UnrealEditor-Slate.dll明明还活得好好的。通过 UE 自己的FProperty反射系统让 Monolith 读取关卡实例中AglinaMesh的 UPROPERTYUPrimitiveComponent::CustomPrimitiveDataInternal它返回Data( 0, 0, 1, 0, // CPD 0–3Forward -1, 0, 0, 0, // CPD 4–7Right 0, -1, 0, 0 // CPD 8–11Up )读取链路是编辑器关卡中的 BP_AglinaFaceRenderingPreview → AglinaMesh → CustomPrimitiveDataInternal → Data 数组这是只读反射查询不是扫描进程内存。它读到的是 CPU 侧、准备提交给渲染系统的 CPD 数据。CVar 更适合控制全局渲染功能或输出调试信息不适合读取某个具体 Primitive 的 12 个自定义浮点数。材质预览颜色则是在 GPU 侧验证“材质是否正确消费这些值”两种验证互补反射读取确认 C 组件确实写入了什么数值。材质可视化确认数值经过 Primitive Scene Proxy 后确实到达材质。原理是 Windows DLL 自带导出表例如UnrealEditor-Slate.dll → 导出 4272 个符号 → 生成 .def → lib.exe 生成 UnrealEditor-Slate.libimport library 主要包含DLL 名称导出符号名称链接跳转信息。真正的 Slate、RigVM 等实现仍在原有同版本 DLL 里。因此由这些 DLL 自己的导出表重建.lib它一定对应当前 DLL 的公开导出符号不涉及重新实现模块。读取 DLL 发生在修复.lib时。DLL 虽然不是自然语言文本但它是有明确格式的 Windows PE 二进制文件。工具可以按照 PE 格式解析其中的结构化数据。使用的是 Visual Studio 自带的dumpbin.exedumpbin /exports UnrealEditor-Slate.dll它读取 DLL 的 PE Export Table并输出类似ordinal hint RVA name 1 0 00123456 ExportedSymbolA 2 1 00124567 ?DecoratedCppSymbol...其中ordinal导出编号RVA函数或数据在 DLL 中的相对虚拟地址name链接器使用的导出符号名C 符号通常是经过 name mangling 的名称不要求人类理解其语义随后把符号名整理为.defLIBRARY UnrealEditor-Slate.dll EXPORTS ExportedSymbolA ?DecoratedCppSymbol...再由微软的库管理工具生成 import librarylib.exe /DEF:UnrealEditor-Slate.def /OUT:UnrealEditor-Slate.lib /MACHINE:X64因此这里的“读取”不是让我把 DLL 当文本理解也不是反编译函数实现而是DLL 二进制 → 按 PE 标准解析导出表 → 得到结构化符号列表 → 重新生成链接索引这类似于读取 PNG 的宽高或 ZIP 的文件目录文件本身是二进制但格式规范定义了元数据存放位置专用工具能可靠解析。.def甚至不是 binary它就是一张文本清单这个反而最简单。.def Module Definition File它可以长这样LIBRARY UnrealEditor-Slate EXPORTS SomeFunction AnotherFunction SomeGlobalVariable DATA本质就是告诉工具这个 DLL 叫 UnrealEditor-Slate 它对外公开 ├─ SomeFunction ├─ AnotherFunction └─ SomeGlobalVariable微软定义的.def就是一个 text file其中LIBRARY指定 DLLEXPORTS列出 DLL 对外 exports。learn.microsoft.com/zh-cn/cpp/build/exporting-from-a-dll-using-def-files然后Slate.def ↓ lib.exe /DEF:Slate.def ↓ UnrealEditor-Slate.lib微软的LIB /DEF正是专门用来从 export specification 创建 import library 的。learn.microsoft.com/en-us/cpp/build/reference/building-an-import-library-and-export-file所以.def回答“我要声明这个 DLL 对外提供什么”它自己不会运行也不会进 Editor 内存执行。现在把三者摆在一条时间线上你应该会突然有实感BUILD TIME ──────────────────────────────────────────── Slate.cpp ↓ compiler Slate.obj ↓ ↓ linker ↓ ┌──────────────────────┐ │ Slate.dll │ ← implementation └──────────────────────┘ │ └──── exports ───→ Slate.lib ↑ │ linker 用 YourPlugin.cpp ↓ compiler YourPlugin.obj│ │ Slate.lib↓ linker YourPlugin.dllRUN TIME ──────────────────────────────────────────── YourPlugin.dll │ │call FSlateApplication::Get() ↓ Slate.dll│ ↓ 真正 CPU 执行机器码.def则是在 build-time 旁边的一个辅助描述文件Slate.dll 的公开 exports ↑ .def它甚至未必长期存在。UE 正常 build 不代表一定要给你留下一个.def文件__declspec(dllexport)本身也可以让 linker 知道该导出什么。微软也明确说明.def和__declspec(dllexport)是两种指定 exports 的方式。learn.microsoft.com/en-us/cpp/build/importing-and-exporting​​Cache 又完全是另一条轴如果你说的是 UE 经常出现的DerivedDataCache DDC那它甚至不是“C build pipeline 中间产物”。它主要服务于Asset / Shader / Texture / Mesh ↓ 针对当前平台加工 ↓ Derived Data ↓ DDCEpic 对 DDC 的定义很明确很多 UE asset 在真正使用之前需要产生 derived data如果缓存不存在UE就重新生成然后放进 DDC。DDC 的内容原则上是 disposable可以从原始 asset 重新生成。dev.epicgames.com/documentation/en-us/unreal-engine/using-derived-data-cache-in-unreal-engine例如Material.uasset ↓ shader compilation ↓ 某个平台对应的 shader derived data ↓ DDC或者Texture.uasset ↓ BC7 / ASTC 等平台压缩 ↓ Derived Data ↓ DDC​类似于读取PNG 的宽高或ZIP 的文件目录文件本身是二进制但格式规范定义了元数据存放位置专用工具能可靠解析。新的和旧的底层都继承AActor但组织方式不同。旧结构AActor └─ AViewportTickEnabledActor └─ BP_DirectionalLightDirectionToMPC ├─ 蓝图变量 SourceDirectionalLight └─ 蓝图运行时逻辑旧 C 父类通过反射查找蓝图变量属于为了让蓝图在编辑器 Tick 而形成的通用父类。新结构AActor └─ ADirectionalLightToDirectionToLightWSMPCActor ├─ SourceDirectionalLight ├─ TargetMaterialParameterCollection ├─ TargetVectorParameterName └─ 完整的编辑器与运行时更新逻辑新 Actor 的类型名称、输入、输出和职责完全一致不再通过反射猜测蓝图里是否存在某个变量。技术上当然可以把灯光更新组件挂到BP_AglinaFaceRenderingPreview但不适合灯光 → MPC 是场景级全局服务。Aglina Preview 是角色级实例。如果场景没有 Aglina灯光数据仍应更新。如果以后放多个 Aglina每个实例都会重复写同一个 MPC。角色预览销毁或隐藏不应影响全局灯光参数。只有进入反射体系的类型和成员比如UCLASS、USTRUCT、UENUM、UPROPERTY、UFUNCTION才会被 UHT 生成注册代码。模块加载后这些信息会以UClass、FProperty、UFunction等 reflection descriptors 存在于内存里。运行时可以通过StaticClass()获取已知类型的UClass或者用TObjectIteratorUClass遍历当前已加载的类拿到UClass后用TFieldIteratorFProperty和TFieldIteratorUFunction枚举属性和函数。FProperty还能读取 flags、metadata、类型信息并通过ContainerPtrToValuePtr()定位某个 UObject 实例中对应成员的实际内存。他认为 sports 是真正让 prediction market 爆发的东西。Robinhood 的立场是Prediction markets 是 CFTC 监管的 federal products。所以CFTC / federal law应该 preempt state law。preemption 上位的联邦法律排除州法律适用但一些州认为这些 sports event contracts 本质很接近 sports betting所以州政府有权监管甚至禁止。他认为即使 Supreme Court 最终支持 states也不会变成prediction markets are gone而更可能是法院draw some line划定联邦和州分别管到哪里。最后 12:00 后谈政治捐款。他说 Robinhood 有 PAC而且 Democrat / Republican 两边都会捐。选择标准声称不是党派而是候选人是否支持broadly distributed ownership of high-quality American assets也就是希望更多普通人持有美国股票、private companies 等优质资产。Selig 本人在美国 crypto / prediction-market / novel derivatives 上属于 structural tailwind。但这个 tailwind 有层级。最强的是Congressional legislation。因为一旦 codified into law下一任民主党政府也很难仅靠换 CFTC 主席把整个 framework 撤掉。其次CFTC rulemaking。仍然重要但下一届政府理论上可以重新 rulemaking而且可能遭遇法院挑战。最弱的是guidance / no-action relief / enforcement discretion。这些最容易被下一届政府改变。​socket的坐标转换的稳定性不取决于人物模型最开始是y还是-y在viewport里朝前朝后你当前这个蓝图里AglinaMesh是 Root Component所以现在旋转它看起来就等于旋转整个 Actor。当前结构是BP_AglinaFaceRenderingPreview └─ AglinaMeshRoot HairEyeReveal FaceReferenceFrameCustomPrimitiveDataRoot Component 的世界 Transform 就代表 Actor Transform因此没有独立的“Actor 朝向”和“Mesh 相对朝向”。如果希望分开需要改成BP_AglinaFaceRenderingPreview └─ SceneRoot └─ AglinaMesh Relative Rotation ±90°Yaw HairEyeReveal FaceReferenceFrameCustomPrimitiveData这样旋转 Actor/SceneRoot整个角色以及所有功能一起旋转。UE 允许不同资产拥有不同的局部坐标约定但当它们参与场景计算时通常都能通过 Transform API 得到统一的世界空间结果例如ActorGetActorForwardVector()Scene ComponentGetForwardVector()骨骼/SocketGetSocketTransform(..., RTS_World)相机相机组件的世界 Transform灯光灯光 Actor/Component 的世界方向材质顶点数据通过坐标变换节点转换到 World Space当前 FaceSDF 不能表达顶光造成的眼窝/鼻下阴影变化。也不能表达底光造成的恐怖照明结构。HorizontalLightMagnitude≈0时没有可靠的水平朝向。acos() 的输入范围必须是 [-1,1]否则浮点误差可能导致acos(1.000001) → NaNacos()Dot Product 和夹角关系dot(A,B)cos(θ)所以hlsl θ acos(dot)把 DRAM dies 疊成 HBM​當輸入值太大或太小時函數曲線變得平坦其導數會接近 0。這會導致深層神經網路在反向傳播時無法有效更新權重。非零中心化輸出值總是正數大於 0會讓後續層的梯度更新方向受限降低最佳化效率。这些东西被暴露出来是不是因为 NVIDIA / AMD / Intel 都对这些操作有共识并且硬件专门优化答案是有一部分是但不能这么一概而论。完整链路更像HLSL ↓ DXC ↓ DXIL ↓ AMD / NVIDIA / Intel driver compiler ↓ 各家 GPU ISADXIL 本身就是为了把 HLSL 降成更低层、方便厂商 driver compiler 再针对具体 GPU 做 JIT/优化。微软的 DXIL 设计文档明确描述了这个层级。github.com/Microsoft/DirectXShaderCompiler/blob/main/docs/DXIL.rst不能说GPU 有一个 SmoothStep 单元。通常没有必要这样理解。value可以是 -11smoothstep的輸出固定是 01key Transform、Camera、Animation是 Sequencer Track 的工作。Director BP 的價值是時間軸走到某一幀時讓 Sequencer 開始執行任意 Blueprint 邏輯範例就是 Sequencer → Director BP → Level Blueprint →Activate Niagara System。重要的是它和 Sequencer 的時間軸官方定義就是「一個 keyframe 評估一次」。dev.epicgames.com/documentation/unreal-engine/cinematic-event-track-in-unreal-engineSequence 是 30 fps就會依 Sequence frame rate 持續執行改成 60 fpsRepeater 的 evaluation rate 也跟著改。dev.epicgames.com/documentation/unreal-engine/cinematic-event-track-in-unreal-engine你在 Sequencer 里Event Track └─ 添加 Event Key ↓ Director Blueprint Event ↓ 执行一次这是最常见的 cinematic 用法。默认用的是Trigger Event。他自己付費買example.com然後設定node1.example.com node2.example.com node3.example.com給大家測試。他的成本域名費約一年幾美元幾十美元 DNS免費 Cloudflare免費層所以維護幾百個子域名成本可能很低。為什麼有人願意付這個錢因為成本很小但收益可能存在技術展示例如「我搭了一套全球節點管理系統」這本身就是作品展示。EdgeTunnel 本质上是一段要运行在 Cloudflare 边缘服务器上的应用代码不是普通电脑软件。“Create app”就是进入 Cloudflare 的应用部署入口。在里面我们会选择使用 Pages/静态资源直传方式上传已经准备好的 ZIP创建一个免费的*.pages.dev地址左侧菜单的Compute展开后进入Workers PagesCloudflare 解决的是└─隐藏源站 IP└─DDoS 防护└─全球节点接入但是它不保证└─ 用户 → Cloudflare 节点 这段一定快测速大量 Cloudflare IP ↓ 找最快的几个 ↓ 让 DNS 指向这些 IP这叫优选 IPIP optimization优选 API 是什么API 是自动化接口。例如每天API ↓ 测速 Cloudflare 2000 个 IP ↓ 排序 ↓ 返回最快 10 个 IP ↓ 自动更新 DNS为什么 Cloudflare 不直接解决因为 Cloudflare 的目标是全球平均最优不是某个地区某个运营商最优CFData 做的事情就是从你当前宽带反复尝试这些 IP并记录“运营商最终把连接送到了哪个 Cloudflare 机房”。它无法修改 BGP只是在现有路由结果中挑选表现较好的 IP。不是Internet is down而是a very large part of the application layer is down.这很像 AWSus-east-1大规模故障Internet 本身没坏但你打开十个 App七八个各种功能报错于是用户主观体验就是“网炸了”。而 Cloudflare 自己过去一年已经给过几个很好的现实实验。2025 年 12 月 5 日一次配置变更导致 Cloudflare 部分网络出现重大故障。受影响客户对应大约Cloudflare 所服务 HTTP traffic 的 28%持续约 25 分钟。blog.cloudflare.com/5-december-2025-outage大多数设备默认使用 ISP 指定的 resolver只有你自己、路由器、浏览器或管理员配置成1.1.1.1时DNS 查询才会送到 Cloudflare。developers.cloudflare.com/1.1.1.1/faq历史上1.1.1.1这个地址由 APNIC 的研究部门持有。2018 年 Cloudflare 找到 APNIC 合作由 APNIC 提供这个地址Cloudflare 使用它运行公共 DNS resolver。现在 Cloudflare 官方仍然描述为Cloudflare 与 APNIC 合作运营 1.1.1.1。developers.cloudflare.com/1.1.1.1/privacy/public-dns-resolver实际链路是Chrome 中的 ChatGPT Cookie ↓ 登录时证明“你是谁” 浏览器 OAuth 页面 ↓ localhost:1455 回调 Codex 获得自己的凭据 ↓ C:\Users\86134\.codex\auth.json ↓ Codex CLI 与 VS Code 扩展共同使用登录时共享 Chrome 的现有登录状态Codex 不必读取 Chrome 的Cookies数据库。它只需让 Chrome 打开登录网址Chrome 自动向chatgpt.com携带 Cookie然后网站把一次性授权结果通过localhost:1455交给 Codex。Codex CLI 和 VS Code 扩展共享auth.json这是你本机明确存在的C:\Users\86134\.codex\auth.json避免用简单乘法制造暗部简单做法DarkAlbedo OriginalAlbedo × 0.5只能压低亮度容易得到灰黑、发脏的肤色。Skin LUT 则允许根据原始 Albedo 产生专门设计的暗部颜色OriginalAlbedo → Skin LUT → CorrectedDarkAlbedo它可以包含暗部色相偏移。不同肤色对应不同暗部颜色。饱和度与明度的非线性修正。避免所有颜色按同一比例变黑。guinea pig本來是天竺鼠但非常常用來表示實驗品、被拿來試驗的人apparent在新聞裡很常見 看來是、疑似因為媒體通常不能在司法完全確認前直接斷言。「took his own life」 自殺。29 states are taking on Meta. 29 個州正在聯手挑戰起訴 Meta。「in the relentless pursuit of profit」新聞和政治演講很愛這種寫法。「fueled a youth mental health crisis」take the stand 出庭作證登上證人席。所以Mark Zuckerberg is set to take the stand.法律和新聞超高頻unsubstantiated allegationunsubstantiated claimsubstantiate an accusation注意不是直接等於「假的」。stand by its record of protecting teenagers≈ Meta 表示仍然堅持認為自己過去在保護青少年方面的紀錄經得起檢驗。法律裡damages是專門含義損害賠償金The company was ordered to pay $10 million in damages.原本肤色Albedo RGB (0.80, 0.55, 0.45)普通做暗部可能只是hlsl Dark Albedo * 0.65;得到(0.52, 0.3575, 0.2925)它只是“整体变暗”。但美术往往希望阴影里的皮肤亮度下降 更红一点 少一点黄 某些中间色稍微提高饱和度Epic 自己介绍 LUT workflow 时也是类似思路先拿代表性场景截图做 color adjustment再把这个 transformation 制作成 LUT。dev.epicgames.com/documentation/unreal-engine/using-lookup-tables-for-color-grading-in-unreal-engineLUT 不知道“这是鼻子还是脸颊”。但数据量是立方增长LUTtexel 数量RGBA8 原始大小16³4,09616 KB32³32,768128 KB64³262,1441 MB128³2,097,1528 MB256³16,777,21664 MB如果用RGBA16F256³就大约是128 MB。LUT 通常希望非常小可以频繁 lookup 并很好地待在 texture cache 里。32³很小256³已经是一个真正的大型纹理资源了。尤其皮肤 LUT 更明显。你的 LUT 虽然覆盖整个 RGB cube但角色皮肤实际只会访问其中一个很小的 color gamut整个 RGB cube ┌────────────────────────────┐ │ │ │ ░░ │ │ ░██░ ← 肤色实际 │ │ ░ │ │ │ └────────────────────────────┘Texture3D 的 Max Volume Extent 是 2048所以真正的256×256×256 Texture3D在规格上反而没问题。这在 D3D12 下直接超过普通 2D texture 的最大单轴尺寸16384。既然最终只是把 Albedo 调成一个“暗部版本”为什么不直接美术做一张DarkAlbedo这是一个很合理的问题。答案是可以直接做而且有些项目确实这么做。LUT 的价值主要不是“能做到别人做不到的颜色”而是把“空间纹理”和“颜色变换规则”分离。Base Albedo ├─ Light branch │ └─ 原本颜色 │ └─ Dark branch └─ Skin LUT(Base Albedo) └─ 得到暗部调色后的颜色LUT 的输入不是“经过 SDF / Ramp / 光照之后的 RGB”而是更早阶段的 material color基本就是当前albedoColor。lerp(DarkAlbedo, LightAlbedo, FaceLitMask) × RampRGB × LightColor × Shadow到了Mask0.3都会变成同一个(0.4, 0.2, 0.25)。BaseColor 的信息几乎没了。而如果采用乘法hlsl FinalColor BaseColor * RampColor;那么同一个 RampRamp (0.8, 0.6, 0.7)作用在不同颜色上浅肤色 × Ramp → 浅肤色的暖阴影 深肤色 × Ramp → 深肤色的暖阴影 红衣服 × Ramp → 红衣服自己的阴影随意缩放会让lut采样时,二维过滤跨过切片边界​Unity 生成 identity 3D LUT 的官方示例本质上也是直接C Color( r / 31, g / 31, b / 31 )把整个 RGB cube 均匀铺满。docs.unity3d.com/ru/530/Manual/class-Texture3D.html同一个输入 RGB 必须始终得到同一个输出 RGB。LUT 只能表达确定的逐颜色函数。uniform LUT vs non-uniform LUT / shaper LUT作者意识到了这个问题但把规则留给 LUT 制作流程决定了。Colour Science和OpenColorIO都不会自动做Original.png PS_Graded.png ↓ 自动求 F它们主要做的是已知 F ↓ 采样整个 RGB cube ↓ 生成 / 保存 LUT确实存在开源工具直接做类似事情。例如MLSColorTransfer可以接受 source image 和 target image并生成.cube3D LUT它明确要求图像基本对齐否则结果会很差。github.com/OpusGang/MLSColorTransfer直接让同一套 Adjustment Layers 作用于 Identity LUTIdentity LUT ↓ 同一套 Curves ↓ 同一套 Hue/Saturation ↓ 同一套 Color Balance ↓ 同一套 Selective Color ↓ Modified Identity LUT然后导出.cube。Photoshop 本身就提供这个功能File └─ Export └─ Color Lookup Tables...官方流程就是背景图上建立你需要的 adjustment layers然后File Export Color Lookup Tables可以直接导出 CUBE/3DL/CSP并指定 Grid Points。helpx.adobe.com/ca/photoshop/using/export-color-lookup-tables.html情况 APSD 还在Adjustment Layers 还在 └─ 最好 └─ 直接把已有调色 transform bake 成 LUT └─ 不需要 infer F​​补一句**UE 并不是只能用 2D LUT。**你自己写材质/Shader 时也可以使用Texture3D那就可以直接保持.cube → 32×32×32 Texture3D → Sample(RGB)反而逻辑更自然。专门给 LUT 留了TEXTUREGROUP_ColorLookupTable注释直接是C /** No compression, no mips. */ TEXTUREGROUP_ColorLookupTableTexture.cpp会对这个组强制MipGenSettings NoMipmaps SRGB false No Compression true官方文档对 Color Grading LUT 也明确要求Mip Gen Settings NoMipMaps、Texture Group Color Lookup Table。dev.epicgames.com/documentation/en-us/unreal-engine/using-lookup-tables-for-color-grading-in-unreal-engine刚好等于截图里的Resource Size: 128 KB。这说明现在 GPU 上基本就是每个 LUT texel └─ B8G8R8A8 └─ 每通道 8 bit └─ 没有 BC1/DXT1 的 4×4 block compression所以和上一张截图相比之前 Format DXT1 Mips 11 Resource 22 KB ↓ 明显经过有损压缩 现在 Format B8G8R8A8 Mips 1 Resource 128 KB ↓ RGBA8 原值保存这一步已经解决了你担心的“LUT 格子里的数值被压坏”。而且你虽然看到Compression Settings Default (BC1 or BC3 with A)但不用被这个 UI 欺骗。你现在Texture Group ColorLookupTableUE 5.8 源码会把这个 group 强制作为 uncompressed texture build。Texture.cpp里明确把TEXTUREGROUP_ColorLookupTable放进bNoCompression条件同时这个组也会强制NoMipmaps和SRGBfalse。所以最终真正应该看右上角Format: B8G8R8A8而不是只看下拉框里的Compression Settings Default。第一是Filter。这点尤其重要因为 UE 的ColorLookupTableTexture Group 默认倾向于NearestUE 5.8 源码自己甚至有注释C // eg. Nearest for TEXTUREGROUP_ColorLookupTable对于 UE 自己传统的 Color Grading LUT这是它自己的采样体系。第二是 Address ModeX Clamp Y Clamp不要 Wrap。尤其 strip LUTB0 | B1 | B2 | ... | B31虽然我们的 half-texel 算法本来就应该避免采出边界但 Clamp 是正确的安全契约。ColorLookupTable ├─ NoMipmaps ← Group 会自动处理 ├─ sRGB Off ← Group 会自动处理 └─ Uncompressed ← Group 会自动处理一张 2D atlas 来模拟 3D LUT 的 trilinear interpolation。先算所以这个 RGB 在 3D LUT 里位于B slice 9 和 B slice 10 之间 30%于是 Shader 做的是同一个 R,G │ ├─ 去 slice 9 的位置采一次 → C0 │ └─ 去 slice 10 的位置采一次 → C1 然后 C lerp(C0, C1, 0.3)也就是实际有两个 2D sampling coordinateshlsl float3 C0 LUT.Sample(Sampler, UV_of_slice9).rgb; float3 C1 LUT.Sample(Sampler, UV_of_slice10).rgb; float3 C lerp(C0, C1, 0.3);而你刚设置的Bi-linear又负责另一层插值。每一次hlsl LUT.Sample(...)虽然你只给 GPU一个 UV coordinate但 Bilinear sampler 会自动取那个 UV 周围的 4 个 texel然后插值成一个颜色。Microsoft 的 HLSL 文档也明确说明bilinear sampling 涉及相邻的四个 samples。learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-to-gather脸部先算SDF漫反射然后又单独算一次脸部Lambert漫反射原因就是如果脸完全用 SDF会和脖子的 Skin Shader 明暗不匹配所以在脸/脖子交界处重新使用皮肤那套 NoL 逻辑。DXT5 的 RGB 部分采用块压缩每个4×4texel 块共享有限的颜色端点。SDF 数据本身的精度与压缩。HLSL 如何把连续场切成边界。光方向变化时边界如何移动。从截图看目前最明显的是第一项被第二项放大。贴图本身Face SDF 源图是1024×1024单看分辨率并不算很低。脸部 UV 实际只占贴图的一部分但仍不至于单纯因为 1024 就出现这么明显的阶梯。更值得怀疑的是当前导入格式PF_DXT5 sRGB falseHLSL 是否制造了锯齿当前核心操作是smoothstep( Cutoff - Width, Cutoff Width, SDFValue );它不会凭空创造贴图中不存在的几何锯齿但会放大 SDF 数值误差。可以这样理解原始 SDF 0.47, 0.48, 0.49, 0.51, 0.52看起来只是平缓灰度。经过窄阈值后0, 0, 0.1, 0.9, 1原本不明显的量化和压缩块就变成了清晰边界。远处角色只占100×100 pixels如果 GPU 仍然采2048×2048 原始细节会产生1. 高频闪烁例如黑白黑白黑白非空间纹理(lut)不需要mipUE 默认假设你导入的是“视觉纹理”。​“一台机不如一张网”的路线。​开了 sRGBRGB 走 sRGB、A 一般仍是线性AO 这样存是对的。Chroma / Saturation彩度、飽和度有多鮮豔純脸上每个点只有 0 或 1Ramp 的 U 也几乎只踩两端中间那截桃色几乎采不到所以橙边细得像描线。256 比 4096 小很多但 256 只包一个人4096 要包整片场景。分辨率花在谁身上比贴图有多大更重要。旧办法再加一台沿光方向的正交相机或者把 CSM 第一级只画角色。小场景还行范围一大同一张图还是糊。他用的是现在更常见的 Per-Object Shadow星穹铁道一类也在用1. 主相机视锥 距离挑最近大约 10 个角色2. 按每个角色的包围盒沿光方向重做视图/投影只包这个人3. 每人画进 atlas 里一小块他写的是 256×256最多大约 16 人拼 4×44. 地上采样时用这个角色自己的矩阵去对应那一块评论里的质疑也对每人多画一次、地面可能要查多块、人叠在一起会浪费。他用 Rendering Layer 把人隔开避免一张 tile 里窜进别人。UE 里类似的是 Contact Shadow / Inset Shadow思路一样坑也类似。不是一个角色用 4×4 像素。原文是每个角色一块 256×256最多 16 个人拼成4×4 的图集。平行光用正交,离光源最近那个片元的深度值​1. 拿到源码Release zip 或 git clone两种都是源码2. 打开 renderdoc.sln或按文档编3. 编译出 qrenderdoc.exe / renderdoc.dll4. 改逻辑就改 .cpp / .h再编一次你现在缺的不是源码是 还没拿这份源码编出一版新的二进制。college researcher 里那份仍是官网装好的 1.45不是从 UE 这份源码编出来的。用 1.45 的 zip 编和用现在这份 v1.x 编都是合法改源码。差的只是zip 对齐官方 1.45你这份略新、还没发版。要和安装包完全同一代就下 v1.45 那个 Source code zip或 git checkout v1.45。
返回列表