ShaderLab:线条几何体旋转

最终效果如下所示,其中包含18个立方体、8个圆形线框的球形,在围绕中心点随时间不断旋转并更改颜色:

如何实现上述效果呢?

首先,在unity中如果要在屏幕上绘制这样的几何体线框并不像在C++等其他语言中一样,需要自己配置渲染管线、操作buffer等,因为这些东西是unity内部已经做好的东西。我们既然不能直接在屏幕上绘制这样的效果,那么怎样才能在unity上实现呢。这时候就想到,屏幕是一个平面,我们在unity中的摄像机屏幕也是屏幕,我们直接把画面绘制到摄像机屏幕上就好了。

所以,可以把shader材质赋给一个平面,再将这个平面正对摄像机就好,这样shader中产生的图像会通过平面来呈现在屏幕上。

部分代码功能解析:

**顶点着色器:**实现将平面上的顶点坐标映射到裁剪空间并输出,再将点映射到屏幕空间输出。

cpp 复制代码
struct appdata
{
    float4 vertex : POSITION; // 顶点输入
};

struct v2f
{
    float4 pos : SV_POSITION; // 顶点裁剪空间的输出
    float4 screenPos : TEXCOORD0; // 顶点屏幕空间的位置
};

v2f vert(appdata v)
{
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex); // 计算顶点在裁剪空间中的位置
    o.screenPos = ComputeScreenPos(o.pos); // 计算顶点在屏幕上的位置(没有完成透视除法)
    return o;
}

片元着色器:负责渲染屏幕上每一个像素的颜色,这里负责绘制所有线框图形,包含18个线框立方体和8个线框球体。

cpp 复制代码
fixed4 frag(v2f i) : SV_Target
{
    float2 iResolution = _ScreenParams.xy; // 获取屏幕的分辨率
    float time = _Time.y * 0.31415; // 将时间乘一个系数,适当缩减变化速率

    // 将屏幕像素左边转化为标准的、考虑宽高比的UV坐标
    float2 fragCoord = (i.screenPos.xy / max(i.screenPos.w, 1e-6)) * iResolution; // 执行透视除法,将裁剪空间坐标转换到NDC
    float2 uv = fragCoord / iResolution; // 归一化到[0,1]
    uv = uv * 2.0 - 1.0; // 映射到[-1,1]
    uv.x *= iResolution.x / iResolution.y; // 修正宽高比

    // 设置一个基础底色,lerp是一个线性差值函数
    float3 c = lerp(float3(0.19, 0.13, 0.1), float3(1.0, 1.0, 1.0), 0.5 * pow(length(uv) * 0.5, 2.0));

    // 获取旋转角度,与时间有关
    float3 rotAngles;
    if (_LinearRotation > 0.5)
    {
        rotAngles = float3(time, time * 0.86, time * 0.473);
    }
    else
    {
        float p = 0.08;
        rotAngles = float3(
            time + sin(time * 30.0) * p,
            time * 0.86 + sin(time * 20.0) * p * 1.24,
            time * 0.473 + sin(time * 10.0) * p
        );
    }

    // 18个立方体的实例位置
    float3 instancesA[18] = {
        float3( 0.0, 0.0,-1.0),
        float3(-1.0, 0.0,-1.0),
        float3( 1.0, 0.0,-1.0),
        float3( 0.0, 1.0,-1.0),
        float3( 0.0,-1.0,-1.0),
        float3(-1.0, 0.0, 0.0),
        float3( 1.0, 0.0, 0.0),
        float3( 0.0, 1.0, 0.0),
        float3( 0.0,-1.0, 0.0),
        float3(-1.0,-1.0, 0.0),
        float3( 1.0, 1.0, 0.0),
        float3(-1.0, 1.0, 0.0),
        float3( 1.0,-1.0, 0.0),
        float3( 0.0, 0.0, 1.0),
        float3(-1.0, 0.0, 1.0),
        float3( 1.0, 0.0, 1.0),
        float3( 0.0, 1.0, 1.0),
        float3( 0.0,-1.0, 1.0)
    };

    // unroll告诉GPU将下面的循环拆开
    [unroll]
    // 循环绘制18个立方体
    for (int dip = 0; dip < 18; dip++)
    {
        // 每个立方体实例的8个顶点的位置
        float3 v0 = transformPoint(float3(-1.0,-1.0, 1.0), instancesA[dip], rotAngles);
        float3 v1 = transformPoint(float3(-1.0, 1.0, 1.0), instancesA[dip], rotAngles);
        float3 v2 = transformPoint(float3( 1.0, 1.0, 1.0), instancesA[dip], rotAngles);
        float3 v3 = transformPoint(float3( 1.0,-1.0, 1.0), instancesA[dip], rotAngles);
        float3 v4 = transformPoint(float3(-1.0,-1.0,-1.0), instancesA[dip], rotAngles);
        float3 v5 = transformPoint(float3(-1.0, 1.0,-1.0), instancesA[dip], rotAngles);
        float3 v6 = transformPoint(float3( 1.0, 1.0,-1.0), instancesA[dip], rotAngles);
        float3 v7 = transformPoint(float3( 1.0,-1.0,-1.0), instancesA[dip], rotAngles);

        // 每个立方体顶点的透视后的坐标
        v0.z = 1.0 / max(v0.z, 1e-5); v0.xy *= v0.z;
        v1.z = 1.0 / max(v1.z, 1e-5); v1.xy *= v1.z;
        v2.z = 1.0 / max(v2.z, 1e-5); v2.xy *= v2.z;
        v3.z = 1.0 / max(v3.z, 1e-5); v3.xy *= v3.z;
        v4.z = 1.0 / max(v4.z, 1e-5); v4.xy *= v4.z;
        v5.z = 1.0 / max(v5.z, 1e-5); v5.xy *= v5.z;
        v6.z = 1.0 / max(v6.z, 1e-5); v6.xy *= v6.z;
        v7.z = 1.0 / max(v7.z, 1e-5); v7.xy *= v7.z;

        // 依次计算出当前像素距离每个立方体的12条线的距离,并转换成系数
        float edge = 0.0;
        edge += lineRaster(uv, v0.xy, v1.xy, _LineWidth, iResolution);
        edge += lineRaster(uv, v1.xy, v2.xy, _LineWidth, iResolution);
        edge += lineRaster(uv, v2.xy, v3.xy, _LineWidth, iResolution);
        edge += lineRaster(uv, v3.xy, v0.xy, _LineWidth, iResolution);
        edge += lineRaster(uv, v4.xy, v5.xy, _LineWidth, iResolution);
        edge += lineRaster(uv, v5.xy, v6.xy, _LineWidth, iResolution);
        edge += lineRaster(uv, v6.xy, v7.xy, _LineWidth, iResolution);
        edge += lineRaster(uv, v7.xy, v4.xy, _LineWidth, iResolution);
        edge += lineRaster(uv, v0.xy, v4.xy, _LineWidth, iResolution);
        edge += lineRaster(uv, v1.xy, v5.xy, _LineWidth, iResolution);
        edge += lineRaster(uv, v2.xy, v6.xy, _LineWidth, iResolution);
        edge += lineRaster(uv, v3.xy, v7.xy, _LineWidth, iResolution);

        // 将影响当前像素颜色的系数转换为颜色
        c += fragmentColorFromPos(v0, time) * min(edge, 1.0);
    }

    // 8个球形实例位置
    float3 instancesB[8] = {
        float3(-1.0, 1.0,-1.0),
        float3( 1.0, 1.0,-1.0),
        float3(-1.0,-1.0,-1.0),
        float3( 1.0,-1.0,-1.0),
        float3(-1.0, 1.0, 1.0),
        float3( 1.0, 1.0, 1.0),
        float3(-1.0,-1.0, 1.0),
        float3( 1.0,-1.0, 1.0)
    };

    [unroll]
    for (int dip2 = 0; dip2 < 8; dip2++)
    {
        float3 v = transformPoint(float3(0.0, 0.0, 0.0), instancesB[dip2], rotAngles); // 旋转后的球心坐标
        v.z = 1.0 / max(v.z, 1e-5);
        v.xy *= v.z; // 透视除法
        c += fragmentColorFromPos(v, time) * circleRaster(uv, v.xy, v.z, _LineWidth, iResolution);
    }

    return float4(c, 1.0);
}

**线条抗锯齿函数:**同时负责计算该线条对当前像素的影响因子

cpp 复制代码
// 线条抗锯齿函数,基于点到线段的距离计算权重
// p: 当前像素位置,p0和p1: 线段的两个端点,w: 线宽参数,iResolution: 屏幕分辨率
float lineRaster(float2 p, float2 p0, float2 p1, float w, float2 iResolution)
{
    float2 d = p1 - p0; // 线段方向
    float denom = max(dot(d, d), 1e-6); // 线段长度的平方
    float t = saturate(dot(d, p - p0) / denom); // 投影参数,限制在[0,1]范围内
    float2 proj = p0 + d * t; // 线段上最近点的坐标
    float distVal = length(p - proj); // 点到线段的距离
    float weight = 3.0 / iResolution.x; // 保证在不同分辨率下,线条的视觉粗细一致
    distVal = (1.0 / max(distVal, 1e-5)) * weight * w; // 距离越小,权重越大,乘以线宽参数
    return min(distVal * distVal, 1.0); // 权重平方以获得更平滑的抗锯齿效果,最大值为1.0
}

球形抗锯齿函数:基于点到圆心的距离计算权重

cpp 复制代码
// p: 当前像素位置,c: 圆心位置,r: 圆半径,w: 线宽参数,iResolution: 屏幕分辨率
float circleRaster(float2 p, float2 c, float r, float w, float2 iResolution)
{
    float distVal = abs(length(p - c) - r);
    float weight = 3.0 / iResolution.x;
    distVal = (1.0 / max(distVal, 1e-5)) * weight * w;
    return min(distVal * distVal, 1.0);
}

旋转函数:3维空间中的旋转函数

cpp 复制代码
float3 rotateXYZ(float3 v, float3 a)
{
    float sx = sin(a.x), cx = cos(a.x);
    float sy = sin(a.y), cy = cos(a.y);
    float sz = sin(a.z), cz = cos(a.z);

    float3x3 rx = float3x3(
        1, 0, 0,
        0, cx, -sx,
        0, sx, cx
    );

    float3x3 ry = float3x3(
        cy, 0, sy,
        0, 1, 0,
        -sy, 0, cy
    );

    float3x3 rz = float3x3(
        cz, -sz, 0,
        sz, cz, 0,
        0, 0, 1
    );

    return mul(rz, mul(ry, mul(rx, v)));
}

颜色混合函数:根据t参数在a、b、c三种颜色之间进行平滑过渡

cpp 复制代码
float3 mix3(float3 a, float3 b, float3 c, float t)
{
     return (t > 0.5) ? lerp(b, c, t * 2.0 - 1.0) : lerp(a, b, t * 2.0);
}

片段颜色函数:从顶点位置和时间获取片段颜色

cpp 复制代码
float3 fragmentColorFromPos(float3 p, float time)
{
    float t = sin(p.x * 0.8 + time * 0.5) * 0.5 + 0.5;
    float fog = min(pow(p.z, 3.0) * 400.0, 1.0);
    return mix3(RED, GREEN, BLUE, t) * fog;
}

顶点旋转函数:根据旋转角度和实例位置进行旋转,顺序为缩放、旋转、平移

cpp 复制代码
float3 transformPoint(float3 localPos, float3 instancePos, float3 rotAngles)
{
    float3 p = localPos + instancePos * 4.0;
    p = rotateXYZ(p, rotAngles);
    p += float3(0.0, 0.0, 10.0);
    return p;
}

完整代码链接:https://pan.baidu.com/s/1A2xs5PbxWrtnbPtPQcHfZA?pwd=9527 提取码: 9527

原文章地址:https://www.shadertoy.com/view/MsjSzz

相关推荐
小贺儿开发2 小时前
【MediaPipe】Unity3D 指间游鱼互动演示
游戏·unity·人机交互·摄像头·手势识别·互动·康复训练
mxwin5 小时前
Unity Shader中CastShadows 和 ReceiveShadows 在代码中的区分
unity·游戏引擎·shader
努力长头发的程序猿8 小时前
Unity2D当中的A*寻路算法
算法·unity·c#
RReality19 小时前
【Unity Shader URP】Matcap 材质捕捉实战教程
java·ui·unity·游戏引擎·图形渲染·材质
魔士于安19 小时前
unity urp材质球大全
游戏·unity·游戏引擎·材质·贴图·模型
南無忘码至尊1 天前
Unity学习90天 - 第 6 天 -学习物理 Material + 重力与阻力并实现弹跳球和冰面滑动效果
学习·unity·游戏引擎
mxwin1 天前
Unity 单通道立体渲染(Single Pass Instanced)对 Shader 顶点布局的特殊要求
unity·游戏引擎·shader
魔士于安1 天前
unity 低多边形 无人小村 木质建筑 晾衣架 盆子手推车,桌子椅子,罐子,水井
游戏·unity·游戏引擎·贴图·模型
RReality1 天前
【Unity Shader URP】简易卡通着色(Simple Toon)实战教程
ui·unity·游戏引擎·图形渲染·材质