草庐IT

UnityShader[4]几何着色器与可交互草地

仓鼠毛吉 2023-11-27 原文

GeometryShader执行顺序在顶点着色器之后,片元着色器以前。GeometryShader以一个/多个顶点组成的图元为输入,开发人员可以修改/添加顶点,修改为完全不同的网格,得到更多好看的效果。
缺点:并行困难,对移动端不友好,需要ShaderModel4.0以上
定义一个几何着色器,首先需要在声明模块添加几何着色器的声明;添加顶点着色器向几何着色器输出的结构体;修改ShaderModel版本为4.0以上

#pragma vertex vert
#pragma geometry geo
#pragma fragment frag

#include "UnityCG.cginc"
#pragma target 5.0

struct appdata
{
    float4 vertex : POSITION;
    float2 uv : TEXCOORD0;
};

struct v2g
{
    float4 vertex : POSITION;
    float2 uv : TEXCOORD0;
};

struct g2f
{
    float2 uv : TEXCOORD0;
    float4 vertex : SV_POSITION;
};

然后编写geometryShader主体:

[maxvertexcount(3)] // 最多调用3个顶点
// 输入:point / line / lineadj / triangle / triangleadj
// 输出:LineStream / PointStream / TriangleStream 
void geo(triangle v2g input[3], inout PointStream<g2f> outStream)
{
    g2f o;
    o.vertex = input[1].vertex;
    o.uv = input[1].uv;
    outStream.Append(o);
}
  • [maxvertexcount(value)] 代表告诉Shader该几何着色器最多单次调用多少个顶点
  • triangle v2g input[3] 参数代表以一个三角形图元为单位进行输入,包含3个顶点;
  • inout PointStream outStream 参数代表以一个点(PointStream)为单位进行输出;
  • outStream.Append(o) 代表将o点添加到outStream中;

可以看到几何着色器是以流为单位进行输入输出的,输入和输出关键字的区别会让流的解析发生改变,例如输出选择了PointStream,该类型的流会认为给定的数据中包含一个顶点,然后进行解析,将这个顶点输出;而选择了TriangStream则会认为给定的数据包含三个顶点,进行解析时会将三个顶点合成为一个三角形输出
上述代码:输入一个三角形,输出该三角形中的2号顶点(数组中顶点编号0,1,2代表三角形顶点编号1,2,3)。可以得到一个点阵Shader:

[maxvertexcount(3)]
void geo(triangle v2g input[3], inout LineStream<g2f> outStream)
{
    g2f o;
    for(int i=0; i<2; i++)
    {
        o.vertex = input[i].vertex;
        o.uv = input[i].uv;
        outStream.Append(o);
    }
}

上述代码:输入一个三角形,输出该三角形的0顶点、1顶点连接成的线段。(网格效果)

几何着色器还可以根据已有顶点生成新的顶点并构建图形,达到一些其他效果。此处尝试给每个三角形生成中心点:

[maxvertexcount(9)]
void geo(triangle v2g input[3], inout TriangleStream<g2f> outStream)
{
    g2f o;

    // 获取中心顶点
    float3 centerPos = (input[0].vertex + input[1].vertex + input[2].vertex) / 3;
    float2 centerUV = (input[0].uv + input[1].uv + input[2].uv) / 3;

    for(uint i=0; i<3; i++)
    {
        o.vertex = UnityObjectToClipPos(input[i].vertex);
        o.uv = input[i].uv;
        outStream.Append(o);

        uint j = (i + 1) % 3;
        o.vertex = UnityObjectToClipPos(input[j].vertex);
        o.uv = input[j].uv;
        outStream.Append(o);

        o.vertex = UnityObjectToClipPos(float4(centerPos, 1.0));
        o.uv = centerUV;
        outStream.Append(o);
                    
        // 重置剥
        outStream.RestartStrip();
    }
}

此时要适当提高控制的顶点数(9个,因为会输出3个三角面)。算法计算得出中心点在模型空间中的位置、uv等参数,将三个顶点(0号、1号、中心点)合成一个三角面添加进入outStream,直到将中心点分割得到的三个三角面均添加进入outStream,最后输出:

需要注意的是,如果先进行了顶点着色,即进入几何着色步骤时顶点已经转换到齐次裁剪空间下,会导致中心顶点计算错误,法线不匹配等问题。解决的方法就是将空间变换算法移动到中心顶点计算之后进行,如上述算法就是将UnityObjectToClipPos写到中心顶点计算完成之后(VertexShader只进行了数据转移操作)。
之后让中心点根据自身法线进行外扩,获取法线方向,然后对中心点坐标进行移动:

float3 edgeA = input[1].vertex - input[0].vertex;
float3 edgeB = input[2].vertex - input[0].vertex;
float3 normal = normalize(cross(edgeA, edgeB));
// 中心点向外挤出
centerPos += normal.xyz * _Length;

可以获得刺球效果:

生成大面积草地

几何着色器可用于生成网格,如果在其中增加一些随机数,就能让网格获得不同的旋转角度、弯曲程度、高度、摆动速度…足够生成一片随机的草地,而且因为没有使用预设的网格/实例,性能也变得可观。
参考:https://zhuanlan.zhihu.com/p/29632347
首先利用CPU生成大量随机位置,然后GPU在其上生成网格,渲染为草。
草的网格:

偶数顶点在左侧、奇数顶点在右侧,方便遍历和计算uv。
Shader需要根据给定顶点产生多个上述网格:

[maxvertexcount(30)]
void geo(point v2g input[1], inout TriangleStream<g2f> outStream)
{
    const uint vertexCount = 12; // 顶点数量
    g2f o[vertexCount];

    float currentVertexHeight = 0; // 当前顶点距离生成位置的高度
    float currentV = 0; // 当前顶点在uv中的v坐标
    float offsetV = 1.0 / (vertexCount / 2.0 - 1.0); // uv中v轴向相邻顶点之间的v值差距
    float4 root = input[0].vertex; // 定义草起始点

    for(uint i = 0; i < vertexCount; i++)
    {
        o[i].vertex = float4(0.0, 0.0, 0.0, 1.0);
        o[i].normal = float3(0.0, 0.0, 1.0);
        o[i].uv = float2(0.0, 0.0);
        if(fmod(i, 2) == 0) // 处理偶数号顶点
        {
            o[i].vertex = float4(root.x - _Width, root.y + currentVertexHeight, root.z, 1);
            o[i].uv = float2(0, currentV);
        }
        else // 处理奇数号顶点
        {
            o[i].vertex = float4(root.x + _Width, root.y + currentVertexHeight, root.z, 1);
            o[i].uv = float2(1, currentV);

            // 抬升uv和坐标
            currentV += offsetV;
            currentVertexHeight += currentV * _Height;
        }

        o[i].vertex = UnityObjectToClipPos(o[i].vertex);
    }

    for(uint j = 0; j < vertexCount-2; j++)
    {
        outStream.Append(o[j]);
        outStream.Append(o[j+2]);
        outStream.Append(o[j+1]);
    }
}

技术要点:

  • 以point为输入,即获取网格的所有顶点,在其上进行操作
  • 根据上个顶点的uv、位置,增量推导出下一个顶点的uv、位置

此时可以根据给定的网格,每个顶点生成一个草网格

然后需要对生成的草进行随机处理,如宽高、角度、摆动等

// 对草的宽高进行随机增减(基于平面坐标xz)
float random = sin(UNITY_HALF_PI * frac(root.x) + UNITY_HALF_PI * frac(root.z));
_Width += random/50.0;
_Height += random/5.0;

进行渲染:

SubShader
{
    Tags { "RenderType"="Transparent" "Queue"="AlphaTest" "IgnoreProjector" = "True"}
    Pass
    {
        Cull off
        Tags {"LightMode"="ForwardBase"}
        AlphaToMask On
        ...
        #include "UnityLightingCommon.cginc"
          
        fixed4 frag (g2f i) : SV_Target
    {
        half lDirWS = _WorldSpaceLightPos0.xyz;
        half hDirWS = normalize(lDirWS + (_WorldSpaceCameraPos.xyz - i.posWS));

        fixed3 diffuse = saturate(dot(i.normal, lDirWS)) * _LightColor0;
        fixed3 specular = pow(saturate(dot(i.normal, hDirWS)), _PhongPow) * _LightColor0;

        fixed4 var_MainTex = tex2D(_MainTex, i.uv);

        fixed3 finalRGB = (diffuse + specular) * var_MainTex.rgb;
        fixed alpha = var_MainTex.a;

        return fixed4(finalRGB, alpha);
    }
}


之后就需要生成草地网格,有两种生成方式:**按照给定高度图生成地形与草地、直接按照给定的模型生成覆于其上的草地。**此处使用第二种方式。
首先根据灰度图生成一个错落有致的网格:

并且根据给定贴图生成顶点色,rgb通道规定草地的颜色,a通道规定草的高度:


具体代码:

public Texture2D heightMap;
public Texture2D ColorMap;
public float terrainHeight;
public int terrainSize = 250;
public Material terrainMat;

private void GenerateTerrain()
{
    List<Vector3> verts = new List<Vector3>();
    List<int> tris = new List<int>();

    for(int i=0;i<terrainSize;i++)
    {
        for(int j=0;j<terrainSize;j++)
        {
            // 添加顶点
            verts.Add(new Vector3(i, heightMap.GetPixel(i, j).grayscale * terrainHeight, j));
            colors.Add(ColorMap.GetPixel(i,j));
            if(i == 0 || j == 0) continue;
            // 添加三角形
            tris.Add(terrainSize * i + j);
            tris.Add(terrainSize * i + j - 1);
            tris.Add(terrainSize * (i - 1) + j - 1);
            tris.Add(terrainSize * (i - 1) + j - 1);
            tris.Add(terrainSize * (i - 1) + j);
            tris.Add(terrainSize * i + j);
        }
    }
  
    // 地形游戏对象
    GameObject plane = new GameObject("TerrainPlane");
    plane.AddComponent<MeshFilter>();
    MeshRenderer renderer = plane.AddComponent<MeshRenderer>(); // 渲染器
    renderer.material = terrainMat;

    // 地形网格
    Mesh terrainMesh = new Mesh();
    terrainMesh.vertices = verts.ToArray();
    terrainMesh.triangles = tris.ToArray();
    terrainMesh.RecalculateNormals();
    terrainMesh.colors = colors.ToArray();
    plane.GetComponent<MeshFilter>().mesh = terrainMesh;

    verts.Clear();
}

生成地形(Terrain)网格后(或从外部导入网格也可以,保证网格的顶点色符合需求即可),需要给Terrain网格添加脚本在其上生成草坪(Grass)网格,也就是在Terrain网格上生成随机顶点构成新的Grass网格,经由Grass网格上的几何着色器生成草地。
对于Terrain网格上的每个三角形,都可以生成0个或多个顶点,以便适配网格密度小/大的不同情况。设三角形的三个顶点为ABC,首先记录ABC三点的坐标,将B点和C点的坐标位置改为从A点出发到B/C的偏移量(这样方便获取AB、AC的方向以便生成随机点)

然后以A为起点,加上[0,1]随机数与AB、AC偏移量的乘积就能得到三角形附近的随机点(大致)。

具体代码:

public Material grassMat;
public Texture2D ColorMap;

private List<Vector3> verts = new List<Vector3>(); // 顶点集
private List<Color> colors = new List<Color>(); // 顶点颜色集
private List<Vector3> norm = new List<Vector3>(); // 顶点法线集
private Vector3[] vertices;
private Vector3[] normals;
private Color[] col;
private int[] tris;
private Random random;
void Start()
{
    random = new Random();
    Mesh mesh = GetComponent<MeshFilter>().sharedMesh;
    vertices = mesh.vertices;
    normals = mesh.normals;
    tris = mesh.triangles;
    col = mesh.colors;
    GenerateField(1); // 1为期望在生成草的草单元内生成1个草
}

private void GenerateField(int patchGrassCount)
{
    List<int> indices = new List<int>();
    for(int i=0; i<65000; i++)
    {
        indices.Add(i);
    }

    // 生成草单元
    // 此处因为网格比较密集,所以让每四个三角形生成一个草单元,也就是每隔12个顶点
    for(int x = 0; x < tris.Length; x+=12) 
    {
        GenerateGrass(x, patchGrassCount);
    }

    GameObject grass;
    MeshFilter filter;
    MeshRenderer renderer;
    Mesh mesh;

    // 顶点数过多时需要拆分部分顶点到新建的草网格
    while(verts.Count > 65000)
    {
        mesh = new Mesh();
        mesh.vertices = verts.GetRange(0, 65000).ToArray(); // 设定顶点
        mesh.SetIndices(indices.GetRange(0, 65000).ToArray(), MeshTopology.Points, 0); // 设定点间关系
        mesh.colors = colors.GetRange(0, 65000).ToArray();
        mesh.normals = norm.GetRange(0, 65000).ToArray();
        // 生成网格
        grass = new GameObject("Grass");
        filter = grass.AddComponent<MeshFilter>();
        renderer = grass.AddComponent<MeshRenderer>();
        renderer.material = grassMat;
        filter.mesh = mesh;
        verts.RemoveRange(0, 65000);
    }
    // 顶点数小于65000则正常生成
    mesh = new Mesh();
    mesh.vertices = verts.ToArray();
    mesh.SetIndices(indices.GetRange(0, verts.Count).ToArray(), MeshTopology.Points, 0);
    mesh.colors = colors.GetRange(0, verts.Count).ToArray();
    mesh.normals = norm.GetRange(0, verts.Count).ToArray();
    grass = new GameObject("Grass");
    filter = grass.AddComponent<MeshFilter>();
    renderer = grass.AddComponent<MeshRenderer>();
    renderer.sharedMaterial = grassMat;
    filter.mesh = mesh;
}

草单元生成函数:

// index 草单元起始点编号
// count 草单元内草数量
private void GenerateGrass(int index, int count)
{
    Vector3 pointA = vertices[tris[index]];
    Vector3 pointB = vertices[tris[index+1]] - pointA;
    Vector3 pointC = vertices[tris[index+2]] - pointA;

    Color colA = col[tris[index]];
    Color colB = col[tris[index+1]] - colA;
    Color colC = col[tris[index+2]] - colA;

    Vector3 normalA = normals[tris[index]];
    Vector3 normalB = normals[tris[index+1]] - normalA;
    Vector3 normalC = normals[tris[index+2]] - normalA;

    for(var i=0; i<count; i++)
    {
        // nextDouble()用于生成介于0~1之间的double值,需要使用头文件using Random = System.Random;
        float randomAB = (float)random.NextDouble();
        float randomAC = (float)random.NextDouble();

        Vector3 randomNormalAB = randomAB * normalB;
        Vector3 randomNormalAC = randomAC * normalC;
        Vector3 normal = normalA + randomNormalAB + randomNormalAC;

        Vector3 randomVertAB = randomAB * pointB;
        Vector3 randomVertAC = randomAC * pointC;

        Color randomColAB = randomAB * colB;
        Color randomColAC = randomAC * colC;
        Color col = colA + randomColAB + randomColAC;

        // 生成单元内随机坐标
        Vector3 pos = pointA + randomVertAB + randomVertAC;

        if (col.a <= 0.2f) continue; // a值太小则舍弃,节省顶点数
        verts.Add(pos);
        colors.Add(col);
        norm.Add(normal);
    }
}

在给定网格上生成了很多顶点作为草坪网格:

为草坪网格添加草地着色器即可,之后进行草地优化。
草地会随着风进行规律性摆动,所以在草地的动画方面,需要营造一种个体上随机,整体上一致的动画效果。首先需要根据顶点在平面中的位置生成具有规律的系数:

// 系数(基于平面坐标xz)
float random = sin(UNITY_HALF_PI * frac(root.x) + UNITY_HALF_PI * frac(root.z));
// 对草的宽高进行增减
_Width = (_Width + random / 50.0) * input[0].color.a;
_Height = (_Height + random / 5.0) * input[0].color.a;

三角函数能够很好的限制系数范围并带来规律性。将其用于草地摆动就可以得到整体上一致的摆动效果。

float2 wind = float2(sin(_Time.x * UNITY_PI * 5), cos(_Time.x * UNITY_PI * 5));
wind.x += sin(_Time.y + root.x / _WindRoute); // 风周期
wind.y += cos(_Time.y + root.z / _WindRoute);
wind *= random;

此外除了规律性的摆动,还需要个体上的摇曳效果,摇曳效果往往是快速的小角度的运动:

// 草摆动
float lerpEffect = (sin(_Time.y + random));
float left = wind * (1.0 - _OscInt);
float right = wind * (1.0 + _OscInt);
wind += lerp(left, right, lerpEffect) * _Height * 0.3;

float randomAngle = lerp(-UNITY_PI, UNITY_PI, random); // 随机角度
float2 randomWindDir = float2(sin(randomAngle), cos(randomAngle)); // 随机风向
wind += randomWindDir * random;

将顶点动画效果加入草,另外,风对草中比较高的部位影响更大,所以需要使用windEffect变量判断当前顶点的高度:

float windEffect = 0.0;

o[i].vertex.xz += wind.xy * windEffect;
o[i].vertex.y -= wind * windEffect * 0.2;

if(fmod(i, 2) == 1)    windEffect += offsetV;

完毕之后对处理完成的顶点进行输出:

o[i].vertex = UnityObjectToClipPos(o[i].vertex);
o[i].normal = UnityObjectToWorldNormal(o[i].normal);
o[i].posWS = mul(unity_ObjectToWorld, o[i].vertex);
// 顶点色分层处理 用于草颜色
o[i].color = ((i > 7) ? input[0].color : input[0].color - 0.15) - random / 10; 

...

for(uint j = 0; j < vertexCount-2; j++)
{
    outStream.Append(o[j]);
    outStream.Append(o[j+2]);
    outStream.Append(o[j+1]);
}

此处草渲染只传递一张透明度贴图控制草形状,草颜色由网格顶点色产生:
这种方法渲染的草只会占用很少的DrawCall。

同时为了能给草地提供更多不同明度的颜色,这里将顶点的法线也传递给草地,产生不同的受光效果。


草地交互

踩踏草地时,部分草地会被压扁,附近的草地会发生偏移。所以在Grass游戏对象上挂载脚本控制草地受到碰撞时的反应。
这里为草地添加了Box碰撞体并勾选isTrigger,需要引发交互的物体添加Sphere碰撞体(是不是触发器没有关系)并添加刚体。
当交互物体进入草地碰撞体后,传递自身坐标到草地原点的相对位置进入材质。

[ExecuteAlways]
public class GrassReaction : MonoBehaviour
{
    private Vector3 pos;
    private Material material;
    private int ModelPosPropID;

    private void Start()
    {
    material = GetComponent<MeshRenderer>().sharedMaterial;
    ModelPosPropID = Shader.PropertyToID("_ModelPos");
    }

    private void OnTriggerStay(Collider other) 
    {
    if(other.tag == "Player")
    {
    pos = other.transform.position;
    pos -= transform.position;
    material.SetVector(ModelPosPropID, new Vector4(pos.x, pos.y, pos.z, 0.0f));
    }
}

在着色器中,根据得到的坐标,在每个草进行渲染时读取该坐标并计算距离,小于某个范围则受到影响,距离越近草的宽高越小(这里通过控制顶点色a通道值控制草的宽高)。在此范围内距离越远的草受到偏转的影响越大

float4 _ModelPos;
float _ModelWidth;

// 距离
float3 distance = root.xyz - _ModelPos.xyz;

// 距离衰减
float drag = sqrt((distance.x * distance.x) + (distance.y * distance.y) + (distance.z * distance.z)) / _ModelWidth;
drag = drag < -1 ? -1 : drag;
drag = drag > 1 ? 1 : drag;
input[0].color.a = min(input[0].color.a,saturate(drag));

// 对草的宽高进行增减
_Width = (_Width + random / 50.0) * input[0].color.a;
_Height = (_Height + random / 5.0) * input[0].color.a;

// 风力计算完成后
o[i].vertex.xz += (1.0 - drag) * distance * windEffect;


草地还有很多可拓展性,如:
将草地交互点进行记录,让草地延迟恢复,这项功能类似于雪地的延迟恢复;
在生成草地网格阶段写一个画笔工具,实时在网格上绘制顶点和顶点色,控制草的生成位置、宽高和颜色;
根据模型的不同、游戏特效产生的风压对交互效果产生更进一步的控制;
引燃效果等。

有关UnityShader[4]几何着色器与可交互草地的更多相关文章

  1. ruby-on-rails - 如何在 ruby​​ 交互式 shell 中有多行? - 2

    这可能是个愚蠢的问题。但是,我是一个新手......你怎么能在交互式ruby​​shell中有多行代码?好像你只能有一条长线。按回车键运行代码。无论如何我可以在不运行代码的情况下跳到下一行吗?再次抱歉,如果这是一个愚蠢的问题。谢谢。 最佳答案 这是一个例子:2.1.2:053>a=1=>12.1.2:054>b=2=>22.1.2:055>a+b=>32.1.2:056>ifa>b#Thecode‘if..."startsthedefinitionoftheconditionalstatement.2.1.2:057?>puts"f

  2. 旋转矩阵的几何意义 - 2

    点向量坐标矩阵的几何意义介绍旋转矩阵的几何含义之前,先介绍一下点向量坐标矩阵的几何含义点:在一维空间下就是一个标量,如同一条直线上,以任意某一个位置为0点,以一定的尺度间隔为1,2,3...,相反方向为-1,-2,-3...;如此就形成了一维坐标系,这时候任何一个点都可以用一个数值表示,如点p1=5,即即从原点出发沿着x轴正方向移动5个尺度;点p2=-3,负方向移动3个尺度;     在一维坐标系上过原点做垂直于一维坐标系的直线,则形成了二维坐标系,此时描述一个点需要两个数值来表示点p3=(3,2),即从原点出发沿着x轴正方向移动3个尺度,在此基础上沿着y轴正方向移动两个尺度的位置就是点p3。

  3. ruby - 如何与 Ruby 中的 Perl 程序交互? - 2

    据我了解,在Ruby和Perl之间没有“桥梁”可以让您直接从Ruby调用Perl函数。据我了解,要从Ruby调用Perl程序,只需将其放在反引号中(即result=`./helloWorld.pl`)。但是,这不允许与Perl程序交互(即您不能与提示交互或提供输入)。我的问题如下:有没有什么方法可以从Ruby向Perl程序提供输入(除了参数)?Ruby和Perl之间没有桥梁,我错了吗?在导航提示时与程序的标准输入交互似乎是错误的方式,我正在处理的程序设计良好,并且具有包含适当Perl函数的库。 最佳答案 有Inline::Ruby模

  4. ruby - IRb:如何使用预加载类启动交互式 ruby​​ session - 2

    在我采用Ruby语言的过程中,我花了很多时间在IRb中。太棒了!但是,由于我不是很清楚它的功能,并且对Ruby仍然是个“笨蛋”,所以我想知道以下内容:如何在不重新启动IRb的情况下“刷新”session(或者这是不可能的)。如何配置IRb加载一堆源文件“hello.rb”和“hello_objects.rb”,即在启动时?我在这些方面投入了大量工作,如果知道加载这些类的速记,而无需再次为每个类手动键入“加载”,那就太好了。 最佳答案 我不确定是否可以“刷新”session。但是,您可以像这样加载您的类:irb-r'hello.rb'

  5. ruby - Lisp - 是否适合网络编程/应用程序(交互式)? ruby 的方式是? php的方式是? - 2

    Lisp是否适合Web编程/应用程序(交互式),就像ruby​​和php一样?需要考虑的事情是:易于使用可部署性难度(尤其是对于编程初学者而言)(编辑)在阅读PaulGraham'sessay之后,我特别提到了CommonLisp.将是我的第一门编程语言。在这方面。这样做合适吗?我听说Clojure的宏功能不如CommonLisp的强大,这就是我尝试学习Clojure的原因。它教授编程并且非常强大。 最佳答案 Lisp是一个语系,而不是单一的语言。为了稍微回答您的问题,是的,存在用于各种Lisp方言的Web框架,例如用于Common

  6. ruby - MacVim 命令窗口文本着色帮助(Rspec 输出) - 2

    我正在尝试为ruby​​开发人员过渡到MacVim。我遇到的一个难题是快速运行规范并轻松获得结果(通过/失败)。当我运行:Rake(或:!rspec%)时,它会运行当前文件中的规范。输出显示在命令窗口中。如果我在常规终端中运行它,我会得到彩色输出。也就是说,点是绿色的,失败的是红色的。在MacVim中,我得到了这些奇怪的[32m和[0m标记。这是一个例子:关于如何解决这个问题有什么想法吗? 最佳答案 Gvim(我假设Macvim就是那个有品牌名称的:))背后没有真正的终端,所以这就是为什么你会得到这些“奇怪”的标记——它们是真正的转

  7. BigData/Cloud Computing:基于阿里云技术产品的人工智能与大数据/云计算/分布式引擎的综合应用案例目录来理解技术交互流程 - 2

    BigData/CloudComputing:基于阿里云技术产品的人工智能与大数据/云计算/分布式引擎的综合应用案例目录来理解技术交互流程目录一、云计算网站建设:部署与发布网站建设:简单动态网站搭建云服务器管理维护云数据库管理与数据迁移云存储:对象存储管理与安全超大流量网站的负载均衡二、大数据MOOC网站日志分析搭建企业级数据分析平台基于LBS的热点店铺搜索基于机器学习PAI实现精细化营销基于机器学习的客户流失预警分析使用DataV制作实时销售数据可视化大屏使用MaxCompute进行数据质量核查使用Quick BI制作图形化报表使用时间序列分解模型预测商品销量三、云安全云平台使用安全云上服务

  8. ruby-on-rails - 是否有针对 Rails、Ruby、Erb 的 Xcode 语法着色?如果没有,我怎么能自己写一个? - 2

    Xcode的语法着色充其量只是很差,而textmate看起来不错,但我喜欢Xcode,因为我也用C++编程。我想将所有内容都放在一个地方并利用其他Xcode功能。有没有人已经这样做过或者有没有简单的方法来做到这一点? 最佳答案 你可以去Editor>SyntaxColoring>CheckHTMLanddoitagainforRuby 关于ruby-on-rails-是否有针对Rails、Ruby、Erb的Xcode语法着色?如果没有,我怎么能自己写一个?,我们在StackOverflo

  9. ruby-on-rails - 如何从 Ruby 与 CalDAV 服务器交互? - 2

    关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。关闭4年前。Improvethisquestion我需要在Ruby(准确地说是Rails)应用程序中使用CalDAV在日历服务器上创建事件。我看过一些不同的图书馆并进行了一些谷歌搜索。我查看了ri-cal(http://ri-cal.rubyforge.org/rdoc/),但不确定它是否支持将数据发送到服务器,或者我是否必须自己这样做,这看起来很有希望http://www.local-guru.net/blog/p

  10. ruby - 在 ruby​​ 中运行系统命令并与之交互 - 2

    我需要在命令行上运行一个命令来请求用户响应。如果它有帮助,命令是:gpg--recipient"SomeName"--encrypt~/some_file.txt当你运行它时,它会发出警告然后询问:Usethiskeyanyway?(y/N)响应“y”让它正确完成。我一直在尝试使用open4gem但我无法让它正确指定“y”。这是我尝试过的:Open4::popen4(cmd)do|pid,stdin,stdout,stderr|stdin.puts"y"stdin.closeputs"pid:#{pid}"puts"stdout:#{stdout.read.strip}"puts"st

随机推荐