Files
BlueRoseNote/03-UnrealEngine/卡通渲染相关资料/渲染功能/ARC/Rendering/正交投影混合.md

306 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: 正交投影混合
date: 2026-05-03 00:00:00
excerpt: OrthoBlend 透视校正技术,实现动画风格的透视扁平化效果
tags:
- ARC
- Rendering
- VertexFactory
- Toon
rating: ⭐
---
# 正交投影混合
返回 [[Rendering]]
## 概述
正交投影混合OrthoBlend是 ARC 引擎实现**动画风格透视扁平化**的核心技术。在动画/格斗游戏中,角色需要减少透视畸变以保持美术控制的外观,这通过在透视投影和正交投影之间进行混合来实现。
引擎提供了两个版本:
- **V1** (`USES_ORTHO_BLEND_POSITION`):简单的 X 轴正交混合
- **V2** (`USES_ORTHO_BLEND_POSITION2`):基于物体中心距离的全透视校正("Purse correction"
## 实现细节
### V1X 轴正交混合
`Common.ush` 中定义:
```hlsl
float4 GetOrthoBlendPosition(
float4 WorldPosition,
float4x4 ViewProjectionMatrix,
float weight)
{
float4 ScreenPosition = mul(WorldPosition, ViewProjectionMatrix);
// 计算正交投影的 X 坐标
float OrthoScreenPositionX = dot(
ResolvedView.OrthoViewProjectionX, WorldPosition);
// 在透视和正交之间混合 X 轴
ScreenPosition.x = lerp(
ScreenPosition.x,
OrthoScreenPositionX * ScreenPosition.w,
weight * ResolvedView.OrthoBlendParameter);
return ScreenPosition;
}
```
`OrthoBlendParameter` 从 C++ 端通过 `ViewUniformShaderParameters` 传入,全局控制混合强度。`weight` 来自材质属性 `MP_OrthoBlendWeight`,允许逐材质控制。
### V2全透视校正
```hlsl
float4 GetOrthoBlendPosition2(
float4 WorldPosition,
float4x4 ViewProjectionMatrix,
float weight,
float3 ObjWorldPosition)
{
// 计算顶点相对于物体中心的偏移
float3 offset = WorldPosition.xyz - ObjWorldPosition.xyz;
// 物体中心到相机的距离(沿视线方向)
float dist = abs(dot(
ObjWorldPosition.xyz - ResolvedView.WorldCameraOrigin.xyz,
ResolvedView.ViewForward.xyz));
// 将顶点"拍平"到与物体中心相同距离的平面
float3 origin = ResolvedView.WorldCameraOrigin.xyz
+ ResolvedView.ViewForward.xyz * dist;
WorldPosition.xy = origin.xy + offset.xy;
// ... 后续投影变换
}
```
V2 的原理:将所有顶点投影到与物体中心等距的平面上,消除了前后肢体因透视导致的大小差异。
### 全 VertexFactory 覆盖
两个版本均在所有顶点着色器中实现:
| 顶点着色器 | 文件 |
|-----------|------|
| BasePass | `BasePassVertexShader.usf` |
| DepthOnly | `DepthOnlyVertexShader.usf` |
| PositionOnlyDepth | `PositionOnlyDepthVertexShader.usf` |
| HitProxy | `HitProxyVertexShader.usf` |
| DebugViewMode | `DebugViewModeVertexShader.usf` |
覆盖的 VertexFactory
- `LocalVertexFactory.ush`
- `GpuSkinVertexFactory.ush`
- `LandscapeVertexFactory.ush`
- `MeshParticleVertexFactory.ush`
- 所有 Particle VertexFactory
## 材质属性
通过 `MP_OrthoBlendWeight` 材质属性控制每个材质的混合权重:
```cpp
// MaterialTemplate.ush
float GetMaterialOrthoBlendWeight(FMaterialVertexParameters Parameters)
{
%s; // 由材质图生成
}
```
## C++ 端支持
- `SceneRendering.cpp`:计算 `OrthoBlendParameter` 并写入 ViewUniformBuffer
- `SceneTypes.h``MP_OrthoBlendWeight` 枚举值
- `HLSLMaterialTranslator.cpp`:材质编译支持
## 关联文档
- [[自定义材质属性]] — `MP_OrthoBlendWeight` 的定义
- [[屏幕对齐网格]] — 另一种屏幕空间变换方案
- [[局部位置缩放]] — 局部空间的顶点变换
## 完整代码解析
### Common.ush — V1X轴正交混合
```hlsl
// ASW Change : 2016/03/29 11:30:55 Takuro.K
// V1X轴正交混合
// 在透视投影的X轴坐标和正交投影的X轴坐标之间做 lerp
// weight 来自材质属性 MP_OrthoBlendWeight
// ResolvedView.OrthoBlendParameter 是全局混合强度
float4 GetOrthoBlendPosition( float4 WorldPosition, float4x4 ViewProjectionMatrix, float weight )
{
#if USES_ORTHO_BLEND_POSITION
float4 ScreenPosition = mul(WorldPosition, ViewProjectionMatrix);
// 用正交投影矩阵的X行点乘世界坐标得到正交投影下的X坐标
float OrthoScreenPositionX = dot(ResolvedView.OrthoViewProjectionX, WorldPosition);
// 在透视X和正交X之间混合
// 注意乘以 ScreenPosition.w 是为了从 NDC 空间转回裁剪空间
ScreenPosition.x = lerp(
ScreenPosition.x, // 透视X
OrthoScreenPositionX * ScreenPosition.w, // 正交X转回裁剪空间
weight * ResolvedView.OrthoBlendParameter ); // 混合权重
return ScreenPosition;
#else
return mul(WorldPosition, ViewProjectionMatrix);
#endif
}
```
### Common.ush — V2全透视校正"Purse correction"
```hlsl
// ASW Change : 2019/01/11 13:53:00 Takeshi.N
// V2全透视校正"Purse correction"
// 原理:将所有顶点投影到与物体中心等距的平面上
// 消除前后肢体因透视导致的大小差异
float4 GetOrthoBlendPosition2( float4 WorldPosition, float4x4 ViewProjectionMatrix,
float weight, float3 ObjWorldPosition )
{
#if USES_ORTHO_BLEND_POSITION2
#if PARTICLE_FACTORY && !PARTICLE_MESH_FACTORY
// 非网格粒子不做透视校正,直接返回标准投影
return mul(WorldPosition, ViewProjectionMatrix);
#else
// 保存原始裁剪空间位置(用于最终 lerp
float4 ClipSpacePositionOrigin = mul(WorldPosition, ViewProjectionMatrix);
// 计算顶点相对于物体中心的偏移
float3 offset = WorldPosition.xyz - ObjWorldPosition.xyz;
// 物体中心到相机的距离(沿视线方向的投影距离)
float dist = abs(dot(
ObjWorldPosition.xyz - ResolvedView.WorldCameraOrigin.xyz,
ResolvedView.ViewForward.xyz));
// 在视线方向上,距离相机 dist 处的点
float3 origin = ResolvedView.WorldCameraOrigin.xyz
+ ResolvedView.ViewForward.xyz * dist;
// 关键步骤将顶点XY"拍平"到与物体中心等距的平面
// 这样所有顶点在相机方向上的距离相同,消除透视缩放差异
WorldPosition.xy = origin.xy + offset.xy;
float4 ClipSpacePosition = mul(WorldPosition, ViewProjectionMatrix);
// X轴补偿物体中心可能不在视线正前方
// 需要补偿因"拍平"导致的X轴偏移
float4x1 VPM_X = float4x1( ViewProjectionMatrix._11_21_31_41 );
float x1 = mul(float4(origin, 1), VPM_X); // "拍平"参考点的X
float x2 = mul(float4(ObjWorldPosition, 1), VPM_X); // 物体中心的X
ClipSpacePosition.x += ClipSpacePosition.w * (x2 - x1) / dist;
// 根据全局参数和材质权重,在原始位置和校正位置之间混合
// step(0.01, weight) 确保 weight 接近0时完全不应用校正
return lerp( ClipSpacePositionOrigin, ClipSpacePosition,
ResolvedView.OrthoBlendParameter * step(0.01, weight) );
#endif
#else
return mul(WorldPosition, ViewProjectionMatrix);
#endif
}
```
### BasePassVertexShader.usf — 调用逻辑
```hlsl
// BasePassVertexShader.usf 中的调用逻辑
// 按优先级选择V2 > V1 > 原始投影
#if USES_ORTHO_BLEND_POSITION2
// V2 透视校正:传入物体世界坐标作为参考点
ClipSpacePosition = GetOrthoBlendPosition2(
RasterizedWorldPosition,
ResolvedView.TranslatedWorldToClip,
GetMaterialOrthoBlendWeight( VertexParameters ), // 材质图中设置的权重
GetActorWorldPosition(VertexParameters.PrimitiveId) // 物体世界坐标
);
#elif USES_ORTHO_BLEND_POSITION
// V1 简单X轴混合
ClipSpacePosition = GetOrthoBlendPosition(
RasterizedWorldPosition,
ResolvedView.TranslatedWorldToClip,
GetMaterialOrthoBlendWeight( VertexParameters )
);
#else
// 原始透视投影
ClipSpacePosition = INVARIANT(mul(RasterizedWorldPosition, ResolvedView.TranslatedWorldToClip));
#endif
```
### C++ 端实现
```cpp
// SceneRendering.cpp — 正交投影混合参数计算
// ASW Change : 2016/03/29 Takuro.K
// 计算正交投影矩阵的 X 行(用于 Shader 中 dot 计算正交 X 坐标)
static FVector4 CalcOrthoBlendParameter(
const FViewMatrices& ViewMatrices,
const FMatrix& EffectiveTranslatedViewMatrix)
{
const FVector2D FOVTheta = ViewMatrices.ComputeHalfFieldOfViewPerAxis();
float distance = ViewMatrices.GetOrthoBlendBaseDistance();
float w = distance * FMath::Tan(FOVTheta.X);
float h = distance * FMath::Tan(FOVTheta.Y);
// 构建正交投影矩阵,提取 X 行
const FMatrix orthoMat = FOrthoMatrix(w, h, 0, 1);
const FMatrix ViewOrthoMatrix = EffectiveTranslatedViewMatrix * orthoMat;
return FVector4(
ViewOrthoMatrix.M[0][0],
ViewOrthoMatrix.M[1][0],
ViewOrthoMatrix.M[2][0],
ViewOrthoMatrix.M[3][0]);
}
// 计算混合权重(基于相机朝向的衰减)
static float CalcOrthoBlendWeight(
const FViewMatrices& ViewMatrices,
const FMatrix& EffectiveTranslatedViewMatrix)
{
float weight = ViewMatrices.GetOrthoBlendWeight();
// 相机正面朝向时权重最大,侧面时衰减
float face = ViewMatrices.GetOrthoBlendBaseRot()
.RotateVector(FVector(1, 0, 0)).X;
if (weight < 1.0f)
{
static const float THRESHOLD_MIN = 0.995f;
static const float RANGE = 1.0f - THRESHOLD_MIN;
face = FMath::Max((face - THRESHOLD_MIN) / RANGE, 0.0f);
weight *= face * face; // 平方衰减
}
return weight;
}
```
```cpp
// SceneRendering.cpp — 写入 ViewUniformShaderParameters
ViewUniformShaderParameters.OrthoViewProjectionX =
CalcOrthoBlendParameter(ViewMatrices, TranslatedViewMatrix);
ViewUniformShaderParameters.OrthoBlendParameter =
CalcOrthoBlendWeight(ViewMatrices, TranslatedViewMatrix);
```
## 代码修改情况
| 文件路径 | 行号 | 修改类型 | 修改内容 |
|---------|------|---------|---------|
| `Shaders/Private/Common.ush` | L1739~L1758 | 新增 | `GetOrthoBlendPosition` V1 X轴正交混合函数 |
| `Shaders/Private/Common.ush` | L1761~L1800 | 新增 | `GetOrthoBlendPosition2` V2 全透视校正函数 |
| `Shaders/Private/BasePassVertexShader.usf` | L94~L114 | 新增 | V2/V1 投影分支调用逻辑 |
| `Shaders/Private/DepthOnlyVertexShader.usf` | L152~L220 | 新增 | 同上DepthOnly Pass |
| `Shaders/Private/PositionOnlyDepthVertexShader.usf` | L49~L59 | 新增 | 同上PositionOnlyDepth |
| `Shaders/Private/HitProxyVertexShader.usf` | L147~L169 | 新增 | 同上HitProxy Pass |
| `Shaders/Private/DebugViewModeVertexShader.usf` | L162~L219 | 新增 | 同上DebugViewMode Pass |
| `Shaders/Private/MaterialTemplate.ush` | L2235~L2240 | 新增 | `GetMaterialOrthoBlendWeight` 材质访问函数 |
| `Source/Runtime/Renderer/Private/SceneRendering.cpp` | L1014~L1067 | 新增 | `CalcOrthoBlendParameter` / `CalcOrthoBlendWeight` 函数 |
| `Source/Runtime/Renderer/Private/SceneRendering.cpp` | L1717~L1758 | 新增 | 写入 `ViewUniformShaderParameters` OrthoBlend 参数 |