如何通过Geometry Shader来实现草海渲染

如何通过Geometry Shader来实现草海渲染

【博物纳新】是UWA旨在为开发者推荐新颖、易用、有趣的开源项目,帮助大家在项目研发之余发现世界上的热门项目、前沿技术或者令人惊叹的视觉效果,并探索将其应用到自己项目的可行性。很多时候,我们并不知道自己想要什么,直到某一天我们遇到了它。

更多精彩内容请关注:lab.uwa4d.com


导读

随着3D技术的发展,人们期待游戏中的环境表现出更加自然的视觉效果。玩家们不再仅仅满足于简单的地形贴图,而是要求场景更加真实生动。今天介绍的文章是一篇利用Geometry Shader创建草叶细节的教程。它通过添加参数修改模型,逐步丰富草叶的细节表现。文章内容深入浅出,对于初学者而言值得收藏反复阅读。

开源库链接:https://lab.uwa4d.com/lab/5ca12a1972745c25a88dfe31
作者Blog:https://roystan.net/articles/grass-shader.html

简介:本文利用Geometry Shader在输入网格的每个顶点处生成一片草叶。这些草叶的大小,转向都是随机生成的,叶片也会被风吹动。给定平行光源时,它们也能够产生和接收阴影。最后使用Tessellation细分曲面技术输入网格,控制草地的密度。


Geometry Shader

首先创建一个Geometry Shader的实例geo,它接收顶点作为输入,输出一个三角形来代表叶片。

struct geometryOutput
{
       float4 pos : SV_POSITION;
};
[maxvertexcount(3)]
void geo(triangle float4 IN[3] : SV_POSITION, inout TriangleStream<geometryOutput> triStream)
{
}

三角形的三个顶点通过对输入点的位置的给定偏移量来得到。

o.pos = UnityObjectToClipPos(pos + float3(0.5, 0, 0));
…
o.pos = UnityObjectToClipPos(pos + float3(-0.5, 0, 0));
…
o.pos = UnityObjectToClipPos(pos + float3(0, 1, 0));
…

这样我们可以得到下图所示的效果。


Tangent space

为了让草叶的随机方向不受它所生长的表面的法向影响,我们先在每个顶点建立一个正切空间,它的基向量定义为vTangent, vNormal和vBinormal。在这个空间中生成随机叶片的位置,然后再利用坐标变换把它转换成网格空间中的位置。其中关键的tangentToLocal矩阵定义如下:

        float3x3 tangentToLocal = float3x3(
               vTangent.x, vBinormal.x, vNormal.x,
               vTangent.y, vBinormal.y, vNormal.y,
               vTangent.z, vBinormal.z, vNormal.z
               );

使用这个矩阵左乘正切空间中三角形顶点的坐标,就可以实现正切空间到网格空间的转换。


Grass look

  • 叶片上的颜色渐变

规定了叶片顶端和底部颜色后,我们可以采用插值来实现从上到下颜色的渐变。在代表叶片的三角形上建立UV坐标系,使用lerp函数进行颜色的插值。

lerp(_BottomColor, _TopColor, i.uv.y);

效果如下图所示:

  • 叶片随机朝向

将叶片转向调整到随机的角度,同样可以采用一个旋转变换矩阵。它的定义如下:

  float3x3 AngleAxis3x3(float angle, float3 axis)
    {
        float c, s;
        sincos(angle, s, c);
 
        float t = 1 - c;
        float x = axis.x;
        float y = axis.y;
        float z = axis.z;

        return float3x3(
            t * x * x + c, t * x * y - s * z, t * x * z + s * y,
            t * x * y + s * z, t * y * y + c, t * y * z - s * x,
            t * x * z - s * y, t * y * z + s * x, t * z * z + c
            );
    }

旋转角度由生成叶片的顶点的位置来决定,保证其随机性。

  • 叶片随机前倾角度

前倾和转向可以使用同一个旋转变换矩阵,只不过围绕的轴发生了变化。转向绕Z轴旋转,而前倾是绕X轴旋转。

  • 叶片宽度和高度

叶片线度的调整只需要把固定的1个单位宽度和1个单位高度调整为基准值加上随机值即可。通过调整基准值,使得叶片更加符合现实中的大小比例

float height = (rand(pos.zyx) * 2 - 1) * _BladeHeightRandom + _BladeHeight;
float width = (rand(pos.xzy) * 2 - 1) * _BladeWidthRandom + _BladeWidth;

Tessellation

细分曲面技术本来是渲染管线中的一个可选程序,它的作用是把一个单一的输入面细分成许多顶点组成的近似模型,得到的结果输出给Geometry Shader。为了能够在Geometry Shader 内部实现细分曲面功能,我们引用了CustomTessellation.cginc。通过实现自定义的hull和domain Shader,完成细分的功能。调整参数Tessellation Uniform的值,我们就可以控制草地的密度。

#include "Shaders/CustomTessellation.cginc"
#pragma hull hull
#pragma domain domain


Wind

风的模拟是通过采样变形材质实现的。这种材质只有红绿两个通道,可以作为X和Y方向上的风向。而整体风的大小则是由_Windstrength控制。这样就形成了一个二维的风场。

float2 windSample = (tex2Dlod(_WindDistortionMap, float4(uv, 0, 0)).xy * 2 - 1) * _WindStrength;

为了体现风对草叶的影响,只需要把风场的大小,方向当作旋转变换矩阵的角度和轴向参数,生成对应的旋转矩阵。

float3x3 windRotation = AngleAxis3x3(UNITY_PI * windSample, wind);

Blade curvature

至此,叶片仍然是一个单独的三角形构成,当我们放大去看的时候就会发现叶片在风的作用下其实没有弯折,说是叶片但其实更像是一块铁板。要想实现叶片的弯曲,需要用多个三角形或者说多个顶点来重新构建叶片的模型。这里我们引入参数BLADE_SEFMENTS来控制新的叶片模型有多少段。叶片重新构建完成后,变换矩阵作用的对象就从原来的一片叶子变成了一片叶子上的一个小三角区域,从而实现了在风的作用下的弯曲。

float3x3 transformMatrix = i == 0 ? transformationMatrixFacing : transformationMatrix;
 
triStream.Append(GenerateGrassVertex(pos, segmentWidth, segmentHeight, float2(0, t), transformMatrix));
triStream.Append(GenerateGrassVertex(pos, -segmentWidth, segmentHeight, float2(1, t), transformMatrix));


Lighting and shadows

投射阴影可以调用函数SHADOW_CASTER_FRAGMENG()。接收阴影则是调用SHADOW_ATTENUATION()。光照则是由阴影,平行光源,环境光相加得到。

float4 lightIntensity = NdotL * _LightColor0 + float4(ambient, 1);
float4 col = lerp(_BottomColor, _TopColor * lightIntensity, i.uv.y);


Conclusion

本文运用Geometry Shader实现了较为复杂的草地场景,其中草叶的建模和转换矩阵的重复使用非常精彩,值得品鉴。


适用性测试

文章介绍的Grass Shader在Windows平台上可以正常渲染,但是在Android和iOS平台上都出现了错误,不能从细分的顶点建立起草叶的模型。具体报错信息是:

Shader is not supported on this GPU (none of subshaders/fallbacks are suitable)

查看具体渲染过程时发现,Mesh Renderer出现了错误,具体原因还有待进一步探究。


快用UWA Lab合辑Mark好项目!

请输入图片描述

今天的推荐就到这儿啦,或者它可直接使用,或者它需要您的润色,或者它启发了您的思路......

请不要吝啬您的点赞和转发,让我们知道我们在做对的事。当然如果您可以留言给出宝贵的意见,我们会越做越好。