赞
踩
嗨,大家好,我是新发。
记得初中的时候语文课本里有一篇名字叫《最后一片叶子》的文章,作者是著名的短篇小说家欧·亨利。
《最后一片叶子》又叫《最后一片常春藤叶》,文中讲述了老画家贝尔曼为了鼓励贫病交加的青年画家顽强地活下去,在风雨之夜挣扎着往墙上画了一片永不凋零的常春藤叶。他为此用生命绘制的杰作付出了生命的代价,但青年画家却因此获得勇气而活了下来的故事。
常春藤寓意着希望、朝气蓬勃,也象征着忠诚,还代表着和不朽的青春,因为它一年四季都是常绿的,所以很多人喜欢在阳台上种常春藤,显得格外有生气。本文,我就来证明一下常春藤的魔力,我将在Unity
中演示制作常春藤的过程,并讲解其中涉及到的一些技术知识点。
场景,我希望是在一个优美的岛屿城堡,我找到了一个不错的场景,如下:
注:喜欢这个城堡场景资源的同学,可以自行从这里下载:https://assetstore.unity.com/packages/3d/props/exterior/low-poly-brick-houses-131899
在窗户上绘制生成常春藤,如下:
对比下前后效果,是不是有了常春藤之后,瞬间生机盎然了~
我使用的是Github
的这个工具:hedera,这个工具可以很方便地在场景中制作并生成常春藤一样的植物,感兴趣的同学可以下载从GitHub
上下载来学习。
GitHub
地址:https://github.com/radiatoryang/hedera
点击菜单Hedera / Create / Create New lvy GameObject
,
此时会生成一个lvy Group
节点,它身上会带一个lvyBehavior
组件,我们下面生成的常春藤就是在这个节点之下生成的。
上面我们可以看到,lvyBehavior
组件需要指定一个配置文件,这个配置文件用于配置常春藤生成的规则与相关参数。
工具已经帮我们做好了几个配置,在Runtime/lvyProfiles
目录中,
为了演示,我创建一个新的,点击Create new lvy Profile Asset...
按钮,
将其保存到Runtime/lvyProfiles
目录中,
生成后选中它,可以在Inspector
视图中看到配置的参数,
参数说明:
参数 | 说明 |
---|---|
Length | 生成长度,可以设置上下限,从这个范围内进行随机 |
Branch Chance % | 生成分支的概率 |
Random Spread % | 随机分布率 |
Branch Thickness | 根茎的粗度 |
Leaf Size Radius | 叶子大小 |
Leaf Density % | 叶子密度 |
Leaf Colors | 叶子颜色 |
Brahcn Material | 根茎的材质 |
Leaf Material | 叶子的材质 |
我们需要先准备常春藤的贴图(包括根茎+叶子),例:
制作根茎和叶子的材质球:
材质球设置如下(根茎+叶子):
给lvy Profile Asset
设置根茎和叶子的材质球,
选中lvy Group
,点击Start Painting lvy
按钮,
然后把鼠标移到Scene
视图中,即可看到有个蓝紫色的圈圈投射在物体表面上,
此时按住鼠标滑动即可生成常春藤,
我们看到绘制出来的叶子颜色是 白/绿/黄 的,
这是因为在lvy Profile Asset
中设置的叶子颜色是这样的:
我们可以将其修改成我们想要的其他颜色,比如改成这样:
重新绘制出来的叶子颜色如下:
调整Leaf Size Radius
可以修改叶子的大小,
我们把叶子大小调小,调整参数后可以点击Re-mesh Visible
按钮,就会根据调整后的参数重新运算~
调整前是这样:
调整后是这样:
调整Leaf Density %
可以修改叶子密度,
我们把叶子密度调大,如下:
我们觉得根茎有点粗,
想调细一点,调整Branch Thickness
,把根茎调细,
如下:
调整Length
可以修改生长长度,
我们测试下最小值和最大值的效果,调整为最小值,此时绘制常春藤不会自动继续生长,
效果如下:
现在,我们把Lehgth
调为最大值,
因为它生长力太强了,所以我在地面上演示,感受一下,
我们看到生长过程中的分支概率比较低,我们可以调整Branch Chance %
来修改分支概率,
我们把分支概率调到最大值,感受一下,
假设我们要删除这条常春藤,并不是直接delete
它的GameObject
,
而是先选中它所在的Group
,
然后点击对应的垃圾桶按钮,
如果一个Group
下有多条常春藤,则会会显示多个item
,
我们点开常春藤的节点,可以看到一个根茎节点和一个叶子节点,
工具帮我们生成了根茎和叶子的Mesh
,这些Mesh
文件会自动存放在场景所在目录的同名目录中,
它是序列化在一个.assset
文件中的,
根茎的Mesh
,
叶子的Mesh
,
思考:它是如何将网格资源序列化到asset文件中的?
解答:
需要序列化的类继承ScriptableObject
,例:
public class IvyDataAsset : ScriptableObject
{
//...
}
通过ScriptableObject.CreateInstance
创建序列化文件,例:
public static IvyDataAsset CreateNewDataAsset(string mainFolder, string sceneName, string path)
{
if ( !AssetDatabase.IsValidFolder(path) ) {
var folderGUID = AssetDatabase.CreateFolder( mainFolder, sceneName );
path = AssetDatabase.GUIDToAssetPath(folderGUID);
}
IvyDataAsset asset = ScriptableObject.CreateInstance<IvyDataAsset>();
AssetDatabase.CreateAsset(asset, path + "/HederaData.asset");
AssetDatabase.SaveAssets();
return asset;
}
然后在IvyDataAsset
中定义需要序列化的内容,
// IvyDataAsset.cs public class IvyDataAsset : ScriptableObject { public IvyDictionary meshList = new IvyDictionary(); [System.Serializable] public class IvyDictionary : SerializableDictionary<long, Mesh> { } [System.Serializable] public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, ISerializationCallbackReceiver { [SerializeField] private List<TKey> keys = new List<TKey>(); [SerializeField] private List<TValue> values = new List<TValue>(); // ... } }
关于Unity的类的序列化,还可以参见我早先写的这篇文章:《unity 类的序列化》
如果我们想要自定义Inspector
界面的内容,我们还可以写对应的Editor
类,重写OnInspectorGUI
函数,例:
// IvyDataAssetEditor.cs
[CustomEditor( typeof(IvyDataAsset))]
public class IvyDataAssetEditor : Editor
{
public override void OnInspectorGUI()
{
var data = (IvyDataAsset)target;
// ...
}
}
选中HederaData
文件,点击Cleanup Unreferenced Meshes
按钮,接口自动清理无用的Mesh
资源。
对应的逻辑:
// IvyDataAssetEditor.cs
if ( GUILayout.Button(content) ) {
var allReferencedMeshes = data.meshList.Values.ToList();
for ( int i=0; i<allSubassets.Length; i++ ) {
if (!allReferencedMeshes.Contains((Mesh)allSubassets[i])) {
Object.DestroyImmediate(allSubassets[i], true);
}
}
EditorUtility.SetDirty(data);
AssetDatabase.SaveAssets();
}
上面我们看到,鼠标移到Scene
视图中,可以看到有个蓝紫色的圈圈投射在物体表面上,这个是如何实现的呢~
用的是射线检测接口:
public static bool Raycast(
Vector3 origin,
Vector3 direction,
out RaycastHit hitInfo,
float maxDistance,
int layerMask,
QueryTriggerInteraction queryTriggerInteraction);
例:
// IvyEditor.cs public void MousePosition () { // 射线 Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition); RaycastHit hit; // 射线检测 if (Physics.Raycast(ray.origin, ray.direction, out hit, Mathf.Infinity, ivyBehavior.profileAsset.ivyProfile.collisionMask, QueryTriggerInteraction.Ignore)) { mousePos = hit.point + hit.normal * 0.05f; mouseNormal = hit.normal; Handles.color = Color.blue; // 绘制线圈 DrawThiccDisc(mousePos, hit.normal, Mathf.Max(0.1f, ivyBehavior.profileAsset.ivyProfile.ivyStepDistance)); // 绘制法线 Handles.DrawLine(mousePos, mousePos + hit.normal * 0.25f); } } // 绘制线圈 void DrawThiccDisc(Vector3 mousePos, Vector3 normal, float radius) { var originalColor = Handles.color; Handles.color = new Color( originalColor.r, originalColor.g, originalColor.b, 0.4f); Handles.DrawSolidDisc( mousePos, normal, radius); Handles.color = originalColor; Handles.DrawWireDisc(mousePos, normal, radius - 0.01f ); Handles.DrawWireDisc(mousePos, normal, radius ); Handles.DrawWireDisc(mousePos, normal, radius + 0.01f ); }
注:我之前写了一篇文章,《使用Unity ShaderGraph实现在模型上涂鸦的效果,那么,纹个手吧》,里面也用到了射线检测,感兴趣的同学可以打开阅读以下~
先创建一个空白的平面(Plane
)作为水底,
再创建一个平面作为水面,
为水面创建一个材质球,
材质球设置为半透明模式(Transparent
),设置一下颜色,提高光滑度(Smoothness
),
这样,水面就具有半透明效果,同时也可以反射天空的影像,我们创建个Cube
测试一下,
场景中的天空是用天空盒做的,我们需要先准备720
度天空全景图,可以把自己想象成坐在一个正方体的内部,这六张图就是对应正方体里面的6
个面(前后左右上下),
创建一个材质球,shader
使用Skybox/6 Sided
,然后设置前后左右上下6
个面的贴图,
最后,把材质球拖到Scene
视图空白处即可,或者点击菜单Window / Rendering / Lighting
,
点击Environment
标签页,设置天空盒材质球即可,
我上一篇文章《[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)》里面也用到了天空盒:
更多免费天空盒资源下载:
https://assetstore.unity.com/packages/2d/textures-materials/sky/fantasy-skybox-free-18353
https://assetstore.unity.com/packages/2d/textures-materials/sky/8k-skybox-pack-free-150926
https://assetstore.unity.com/packages/2d/textures-materials/sky/customizable-skybox-174576
https://assetstore.unity.com/packages/vfx/shaders/free-skybox-extended-shader-107400
https://assetstore.unity.com/packages/2d/textures-materials/sky/farland-skies-cloudy-crown-60004
场景中的草一摇一摇的,这是怎么做的呢~
这里用到了Unity
的地形编辑器Terrain
,草是地形的一部分,我们把环境中其他物体隐藏起来,只留下地形,如下:
所以我们需要先创建一个地形,在Hierarchy
视图空白处右键鼠标,点击菜单3D Object / Terrain
,
此时就可以创建一个地形物体,
它身上会带一个Terrain
组件,我们点击Place Details
按钮(第四个按钮,我圈出来那个),我们就可以在地形上刷出细节物体,通常用来做草。
不过这个时候我们什么也刷不出来,这是因为我们还没有设置笔刷图片,
我们先准备一张草的图片,如下:
点击Edit Details...
按钮,
点击Add Grass Texture
按钮,
设置Detail Texture````为刚刚的草的图片,点击
Add```按钮,
其他参数说明:
参数 | 说明 |
---|---|
Detail Texture | 选择一张花或者草的贴图 |
Min Width、Max Width、Min Height、Max Height | 单个草物体的最大最小的宽高值 |
Noise Spread | 添加一点分布随机度 |
Healthy Color | 草的健康颜色(会被tint到贴图的上部) |
Dry Color | 草的干枯颜色(会被tint到贴图的底部) |
Billboard | 是否以永远面向摄像机的单面形式生成单个草物体,如果选否的话就会以十字交叉双平面方式来生成单个草物体 |
这样草的笔刷图片就制作好了,
这样就可以刷出草地啦~
如果想要删除草地,只需要按住Ctrl
键不放,鼠标一刷,就可以把对应的草地删除了~
这些草之所以会摇啊摇,是因为有风,Terrain
组件的设置里可以设置风的大小等参数,如下:
细心的朋友应该注意到了,这个草堆并不是地形刷出来的,为什么它也会摇啊摇,
这个是通过shader
的顶点着色器来控制的,例:
// FoliageShader.shader float random(float3 p) { return frac(43758.5453 * sin(dot(p, float3(12.9898, 78.233, 45.5432)) % 3.14159)); } // 顶点着色器 void vert(inout appdata_full v) { // 随机偏移 float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; float3 offset = _Intensity * (sin(worldPos.xyz + _Time.y * _WindSpeed) + _Randomness * random(worldPos)); // 设置顶点坐标偏移 v.vertex.xyz += offset; }
好了,就先写这么多吧,如果有什么疑问,欢迎留言或私信~
最后,晒下我小屋的常春藤~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。