目录

任务背景

反复无效的沟通

豆包也无济于事

不经意尝试

一张丑图的反思

多模态AI

让高维信息无损传递

信息传达的本质

AI时代的视觉素养

总结


最近我在使用 Cursor开发一个 3D 分块翻转着色器效果,这个过程让我深刻体会到:在某些场景下:千言万语,比不过 一张丑陋的草图。

任务背景

我想实现一个类似 PPT 或 WPF 中的 3D 翻转效果,比如下面是从PPT录制的效果(WPS中的PPT翻页效果之一):

需要把一张图片分成 M×N 的网格,每个小方块像卡片一样绕 Y 轴旋转,有透视效果,呈现真实的 3D 空间感。看起来很简单对吧?我也这么想,因为WPF里面实现一个3D翻转就非常简单的几句代码就能搞定,区别就是我们要实现更多小方块的3D翻转,比如:

 <Grid>
        <!-- 带 3D 投影的元素 -->
        <Border x:Name="card" Background="DodgerBlue" Width="200" Height="150"
                CornerRadius="10" BorderBrush="White" BorderThickness="2">
            <Border.Projection>
                <PlaneProjection x:Name="projection" RotationY="0"/>
            </Border.Projection>
            <TextBlock Text="点击翻转" FontSize="24" Foreground="White"
                       HorizontalAlignment="Center" VerticalAlignment="Center"/>
        </Border>
    </Grid>
    
    <Window.Resources>
        <!-- 翻转动画 -->
        <Storyboard x:Key="FlipAnimation">
            <DoubleAnimation Storyboard.TargetName="projection"
                             Storyboard.TargetProperty="RotationY"
                             From="0" To="180" Duration="0:0:0.6">
                <DoubleAnimation.EasingFunction>
                    <CubicEase EasingMode="EaseInOut"/>
                </DoubleAnimation.EasingFunction>
            </DoubleAnimation>
        </Storyboard>
    </Window.Resources>
    

反复无效的沟通

但是使用着色器实现这个效果,却需要从头起步,这可把我难坏了,我与AI进行了反复且艰难的对话:

我:把图片分为指定的行列数量,比如 5行七列,然后对每个小块 沿着这个小块的 x中心的y轴旋转,相当于flip翻转,从看不见到看得见,角度变化

我:你处理的不正确,完全看不到3d翻转的空间感,完全像是对宽度的改变的动画,应该像是我发的图片这样有空间感

我:也不正确,它不是以正面视角,而是有一定的仰望角度导致画面倾斜,而且我希望所有的小区块的翻转实际都有些时间差异,不要太整齐,

我:这个翻转效果还是不太对,我希望它像 wpf里面的 storyboard 对 Y 的翻转效果,有空间感

我:我看到了改进,但是需要按照以下进行修复...balabala...

反复的调整,也出现了五八门的错误,而且好像越调整越乱套的感觉:


豆包也无济于事

我感觉自己的表达能力可能有问题,于是又去跟豆包沟通,让它重新整理了需求,看起来非常地精准和专业,并且尽量地消除歧义:

我:你得处理仍然不正确,请按照下面的任务重新处理:3D透视翻转图片分块动画 - 任务描述

核心目标

实现一张图片的3D透视分块翻转动画,每个小块从垂直于屏幕翻转至平行于屏幕,同时伴随透明度变化。

具体需求分解

1. 图片分块

· 输入:一张图片
· 将图片划分为 M行 × N列 的均匀网格
· 每个网格块作为一个独立的3D平面进行动画

2. 动画属性(每个块同时变化)

· 透明度动画:从 0(完全透明)→ 1(完全不透明)
· 3D旋转动画:从 90°(垂直于屏幕)→ 0°(平行于屏幕)
  · 旋转轴:Y轴(水平轴)
  · 起始:块平面垂直于屏幕(像扑克牌侧面)
  · 结束:块平面平行于屏幕(正常显示图片)

3. 关键:3D透视效果(重要!)

· 不是简单的2D宽度缩放,必须有空间透视感
· 每个块在旋转过程中要有梯形变形(梯形失真)
· 上边缘和下边缘在旋转时倾斜角度不同:
· 远离摄像头的边缘看起来更短
· 靠近摄像头的边缘看起来更长
· 不同行有不同的透视强度:
· 靠近画面顶部的行(假设摄像头在上方)透视变形更明显
· 远离摄像头的行透视变形较小

4. 视觉参考

· 类似WPF的3D翻转动画,或CSS 3D变换的透视效果
· 像多张卡片从侧面翻转到正面的过程,但由于视角原因,卡片边缘呈现梯形而非矩形

技术实现要点

每个像素需要计算:

1. 块归属判断:像素属于哪个网格块
2. 动画进度:该块的当前旋转角度和透明度
3. 透视投影计算:
   · 根据像素在块内的位置
   · 根据块的3D旋转角度
   · 根据摄像头视角位置(假设在屏幕正前方偏上方)
   · 计算像素应有的梯形变形
4. 最终颜色:原图像素颜色 × 透明度

透视计算的关键公式(概念):

```
投影坐标 = 3D世界坐标 × 旋转矩阵 × 透视投影矩阵
```

· 每个块有自己的局部坐标系
· 旋转改变块的3D朝向
· 透视投影矩阵根据摄像头位置产生梯形变形

预期视觉效果

· 图片像由多个小卡片组成
· 卡片从侧面(看不见)翻转到正面(完全可见)
· 翻转过程中,卡片有明显的3D空间感,不是扁平的
· 不同行的卡片翻转时透视角度不同,形成立体感

避免的误解

· 不是简单的2D宽度动画
· 不是所有行透视效果相同
· 不是理想的正面视角旋转(那样只有宽度变化)
· 不是平面翻转,必须有深度感

这总能搞定了吧?结果还是不行。

我:你现在这个处理效果是不对的,因为这个效果我从单个小方块的感觉来描述,我当前看到的效果。好像是当前的这个小方块。仅仅是宽度,从很小变成了铺满的宽度,高度没有发生任何的变化...balabala...

我:我现在看到发生了一些变化,有一些变化是正确的,但是有一些。仍然是错误的,正确的变化是,我现在看到的右半侧的上边缘的效果是正确的和左半侧的下边缘的...balabala...

我:你现在又改成了严重错误的状态了,因为我看到整体的顶部始终是一条横平的直线,整体的底部始终是一条横平的直线,现在又变成了我眼睛看到的,好像只有宽度发生了。的变化,完全又没有了空间感


不经意尝试

效果仍然让人失望,来回在前面的几种状态下反复,当我快要放弃的时候,打算换一个思路,给它个效果帧的采样,它是否可能会理解呢?试试看。

我:仍然不正确,它们旋转过程中的形态应该像是我附件中发的图片中这样的:

然后,惊喜地一幕出现了:成功了!而且没有多余的任何解释,就直接正确了!!

我调整了效果的变换实际,让整体呈现从左到右的渐变感,因此越向右列时间延迟越大;并且让同列的行也呈现有序感,不同行递增时间延迟,整体呈现出来一种波浪推进的感觉,很酷。

然后基于正确的效果,我进行了方块数量的调整:

然后又进行了方块随机延迟的调整:

然后又进行了多图切换的调整:

以上的一系列的效果都非常棒。部分算法的核心代码(DirectX 12 + ComputeSharp + Win32 ):

public float4 Execute()
{
    int2 xy = ThreadIds.XY;
    float2 resolution = (float2)DispatchSize.XY;

    float2 uv = ((float2)xy + 0.5f) / resolution;

    // 图像适配屏幕
    float imageAspect = imageWidth / imageHeight;
    float screenAspect = screenWidth / screenHeight;
    float2 sampleUv = uv;
    if (imageAspect > screenAspect)
    {
        sampleUv.Y = (uv.Y - 0.5f) * (screenAspect / imageAspect) + 0.5f;
    }
    else
    {
        sampleUv.X = (uv.X - 0.5f) * (imageAspect / screenAspect) + 0.5f;
    }
    sampleUv = Hlsl.Clamp(sampleUv, 0f, 1f);

    // 网格划分
    float frows = (float)rows;
    float fcols = (float)cols;
    float cellY = sampleUv.Y * frows;
    float cellX = sampleUv.X * fcols;
    int row = (int)Hlsl.Floor(cellY);
    int col = (int)Hlsl.Floor(cellX);
    row = Hlsl.Clamp(row, 0, rows - 1);
    col = Hlsl.Clamp(col, 0, cols - 1);

    float localX = Hlsl.Frac(cellX);
    float localY = Hlsl.Frac(cellY);

    // 从左到右按列延迟 + 行随机延迟
    float colFactor = (float)col / Hlsl.Max(fcols - 1f, 1f);
    float colDelay = colFactor * ColumnDelayRatio;
    float rowRandomDelay = Hash((float)row, (float)col) * RowRandomDelayRange;
    
    float delay = colDelay + rowRandomDelay;
    float span = Hlsl.Max(1f - ColumnDelayRatio - RowRandomDelayRange, 0.2f);
    float progressCell = Hlsl.Saturate((progress - delay) / span);

    // === 关键:两图切换的角度计算(无透明度变化) ===
    // 
    // progressCell: 0 → 0.5 → 1
    // 
    // 前半部分(progressCell < 0.5):显示 A 图,从 0° 转到 90°(正面→侧面)
    // 后半部分(progressCell >= 0.5):显示 B 图,从 90° 转到 0°(侧面→正面)
    
    bool showImageA = progressCell < 0.5f;
    
    float angle;
    
    if (showImageA)
    {
        // A 图:0° → 90°(正面翻转到侧面)
        float t = progressCell * 2f;  // 0 → 1
        angle = t * PiOver2;          // 0° → 90°
    }
    else
    {
        // B 图:90° → 0°(侧面翻转到正面)
        float t = (progressCell - 0.5f) * 2f;  // 0 → 1
        angle = PiOver2 - t * PiOver2;         // 90° → 0°
    }

    float cosA = Hlsl.Cos(angle);
    float sinA = Hlsl.Sin(angle);

    // 当角度接近 90° 时,cos 接近 0,方块几乎不可见
    if (cosA < 0.01f)
        return default;

    // 透视参数(根据行号变化)
    float rowFactor = frows > 1f ? (float)row / (frows - 1f) : 0.5f;
    float d = CameraDistMin + rowFactor * (CameraDistMax - CameraDistMin);

    // 块中心坐标
    float sx = localX - 0.5f;
    float sy = localY - 0.5f;

    // === X 方向:完整的透视投影 ===
    float denomLeft = Hlsl.Max(d - 0.5f * sinA, 0.01f);
    float denomRight = Hlsl.Max(d + 0.5f * sinA, 0.01f);
    float projLeftX = -0.5f * cosA * d / denomLeft;
    float projRightX = 0.5f * cosA * d / denomRight;

    if (sx < projLeftX - 0.001f || sx > projRightX + 0.001f)
        return default;

    // 逆透视 x
    float denom = cosA * d - sx * sinA;
    if (Hlsl.Abs(denom) < 0.001f)
        return default;

    float x3d = sx * d / denom;

    if (x3d < -0.5f - 0.001f || x3d > 0.5f + 0.001f)
        return default;

    // === Y 方向:线性梯形效果(向右收缩) ===
    float zRight = 0.5f * sinA;
    float rightEdgeScale = d / (d + zRight);
    
    float xNorm = (x3d + 0.5f);
    float yScale = 1f - xNorm * (1f - rightEdgeScale);
    
    float y3d = sy / yScale;

    if (y3d < -0.5f - 0.001f || y3d > 0.5f + 0.001f)
        return default;

    // 纹理坐标
    float texU_cell = x3d + 0.5f;
    float texV_cell = y3d + 0.5f;

    float texU = ((float)col + texU_cell) / fcols;
    float texV = ((float)row + texV_cell) / frows;
    texU = Hlsl.Clamp(texU, 0f, 1f);
    texV = Hlsl.Clamp(texV, 0f, 1f);

    // 根据当前阶段选择图片
    Float4 texColor = showImageA 
        ? imageA.Sample(new float2(texU, texV))
        : imageB.Sample(new float2(texU, texV));

    // V6: 透明度始终为 1,不进行淡入淡出
    float finalAlpha = texColor.W;

    return new float4(texColor.X * finalAlpha, texColor.Y * finalAlpha, texColor.Z * finalAlpha, finalAlpha);
}

一张丑图的反思

一张简单丑陋的图片,解决了10 轮左右文字沟通都无法解决的问题。

不禁让人反思:为什么图片如此有效?

1. 文字的歧义性

回顾我的描述:
"上下边缘平行" —— 是互相平行?还是都保持水平?
"有空间感" —— 是透视收缩?还是倾斜?还是深度感?
"像 3D 翻转" —— 3D 翻转有无数种可能的实现

每一个词都有多种理解方式,AI 选择了其中一种,但不一定是我想要的那种。

2. 图像的精确性

而一张图片:
明确展示了变形的**方向**(向右收缩)
明确展示了变形的**形状**(梯形,不是沙漏,不是平行四边形)
明确展示了**边缘的关系**(左高右低,渐变收缩)

图像直接传递了"结果",而文字只能传递"描述"。

3. 3D 效果的复杂性

3D 透视变换涉及:
旋转角度
摄像机位置
透视强度
边缘变形方式
上下/左右的对称性

用文字精确描述这些参数的组合效果,几乎是不可能的任务。空间理解力是ai的一大弱项。


多模态AI

基于这次经历,我总结了几条使用多模态 AI 的建议:

1. 视觉效果优先用图片

涉及到:
UI 设计
动画效果
图形变换
布局调整

先找一张参考图,或者画一个草图,比写 500 字的描述更有效。

2. 图片 + 文字的组合最强

发送图片时,附上简短说明:
"像这张图一样,但颜色要蓝色"
"效果像这个,但速度要慢一点"

3. 用对比来消除歧义

与其说"我要 A 效果",不如说:
"我要像 X 一样,但不要 Y"
"参考这张图,但把 Z 改成 W"

4. 及时止损,换个方式

如果文字沟通 2-3 轮后仍然没有进展,立刻停下来,考虑:
能否找一张参考图?
能否画一个简单的草图?
能否用对比的方式描述?


退一步,升一维。


让高维信息无损传递

文字作为一种低维的离散符号系统,天生就有其表达边界。当我们试图用语言描述视觉、空间、动态这类高维信息时,就像用摩尔斯电码描绘一幅画——即便词汇再精准,也难免丢失拓扑结构、空间关系这些核心信息。我以为的“详细描述”,在AI看来只是一堆孤立的关键词,它无法从文字中还原我脑海里的“空间直觉”,就像我们无法仅凭文字想象出从未见过的三维物体。
 
而那三张丑陋的示意图,虽然粗糙,却承载了文字无法传递的高维信息:旋转的方向、透视的强度、物体各部分的相对位置,这些信息以并行的方式直接呈现,AI无需猜测,只需捕捉其中的视觉规律。这让我深刻体会到,多模态的强大,不是“会看图说话”,而是它打破了模态壁垒,让高维信息得以无损传递,实现了“视觉意图”到“代码实现”的直接映射。

多模态的本质:让高维信息无损传递


信息传达的本质

“一图胜千言”,其实背后是信息维度的差异:视觉是二维空间加时间的高维载体,每一个像素都包含着位置、颜色、关系等多重信息,其带宽是文字的成千上万倍;而文字是一维的串行符号,需要大脑进行二次解码和重构,自然容易产生歧义。
 
这个经历也让我对“维度”有了更实际的理解。以前总觉得“升维”是抽象的哲学概念,现在才明白,所谓升维,有时只是切换信息的表征方式——从文字切换到图像,从抽象描述切换到具体示意。对于视觉类开发、设计、沟通等场景,这种切换不是“锦上添花”,而是“降维打击”般的颠覆性效率。


AI时代的视觉素养

现在很多人担心AI绘画会取代设计师、艺术家,但事实恰恰相反:AI解决了“画得像”的执行问题,却无法替代“想得对”的创意与判断。当图像成为更高效的信息载体,“用图像表达思想”不再是艺术家的专属技能,而是每个人都需要的基础能力。我们不需要成为专业画师,但需要具备把想法转化为关键帧、示意图的视觉抽象能力,需要具备判断“哪种视觉表达更有效”的审美素养——这些能力,在多模态时代只会越来越重要。
 
当再遇到视觉类需求,先画草图再写文字;复杂逻辑用流程图替代纯文本描述。这种“图像定形,文字定规”的模式,让协作效率大大提升。


总结

这次看似失败的AI合作,成了一次宝贵的认知升级。它让我明白,与AI协作的核心,不是提升我们的文字描述能力,而是找到最适合信息传递的模态。在这个多模态日益强大的时代,真正的高效协作,是让合适的信息在合适的维度里流动。看似“丑陋”的示意图,实际上是跨维度壁垒的极高效的捷径。

最后,再来看看这个漂亮的效果吧:

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐