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

11 KiB
Raw Blame History

title, date, excerpt, tags, rating
title date excerpt tags rating
正交投影混合 2026-05-03 00:00:00 OrthoBlend 透视校正技术,实现动画风格的透视扁平化效果
ARC
Rendering
VertexFactory
Toon

正交投影混合

返回 Rendering

概述

正交投影混合OrthoBlend是 ARC 引擎实现动画风格透视扁平化的核心技术。在动画/格斗游戏中,角色需要减少透视畸变以保持美术控制的外观,这通过在透视投影和正交投影之间进行混合来实现。

引擎提供了两个版本:

  • V1 (USES_ORTHO_BLEND_POSITION):简单的 X 轴正交混合
  • V2 (USES_ORTHO_BLEND_POSITION2):基于物体中心距离的全透视校正("Purse correction"

实现细节

V1X 轴正交混合

Common.ush 中定义:

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全透视校正

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 材质属性控制每个材质的混合权重:

// MaterialTemplate.ush
float GetMaterialOrthoBlendWeight(FMaterialVertexParameters Parameters)
{
    %s;  // 由材质图生成
}

C++ 端支持

  • SceneRendering.cpp:计算 OrthoBlendParameter 并写入 ViewUniformBuffer
  • SceneTypes.hMP_OrthoBlendWeight 枚举值
  • HLSLMaterialTranslator.cpp:材质编译支持

关联文档

完整代码解析

Common.ush — V1X轴正交混合

// 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"

// 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 — 调用逻辑

// 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++ 端实现

// 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;
}
// 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 参数