当前位置:网站首页>Unity 分析内置地形(Terrain)的渲染并做一些有意思的事情
Unity 分析内置地形(Terrain)的渲染并做一些有意思的事情
2022-06-10 19:07:00 【Luo Xiao C】
绘制草地纹理的同时绘制草:
Shader "LSQ/Terrain Rendering/Custom Terrain/Grass Paint Terrain VFS"
{
Properties
{
[Enum(Splat0,0,Splat1,1,Splat2,2,Splat3,3)] _GrassLayerIndex("Grass Layer Index", int) = 1
_GrassTex("Grass Texture", 2D) = "white"{
}
_TopColor("Grass Top Color", Color) = (1,1,1,1)
_BottomColor("Grass Bottom Color", Color) = (1,1,1,1)
_Height("Grass Height", float) = 3
_Width("Grass Width", range(0, 1)) = 0.05
[Toggle]_FacingCamera("Facing Camera", int) = 1
[Toggle]_WindToggle("Use Wind", int) = 0
_WindStrength("Wind Strength", float) = 1
}
SubShader
{
Tags {
"RenderType" = "Opaque" }
Pass //Terrain
{
Tags {
"LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float3 normal : NORMAL;
//对阴影纹理采样的坐标(参数为存储的纹理通道)
SHADOW_COORDS(2)
};
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.normal = UnityObjectToWorldNormal(v.normal);
//计算阴影纹理坐标
TRANSFER_SHADOW(o);
return o;
}
sampler2D _Control;
// Textures
sampler2D _Splat0, _Splat1, _Splat2, _Splat3;
float4 _Splat0_ST, _Splat1_ST, _Splat2_ST, _Splat3_ST;
fixed4 frag (v2f i) : SV_Target
{
fixed4 splatControl = tex2D(_Control, i.uv);
fixed4 col = splatControl.r * tex2D (_Splat0, i.uv * _Splat0_ST.xy);
col += splatControl.g * tex2D(_Splat1, i.uv * _Splat1_ST.xy);
col += splatControl.b * tex2D (_Splat2, i.uv * _Splat2_ST.xy);
col += splatControl.a * tex2D (_Splat3, i.uv * _Splat3_ST.xy);
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * col;
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 diffuse = _LightColor0.rgb * saturate(dot(i.normal, lightDir)) * col;
return fixed4(ambient + diffuse * atten, 1.0);
}
ENDCG
}
Pass //Grass
{
Tags {
"LightMode"="ForwardBase" }
Cull OFF
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma geometry geom
#include "UnityCG.cginc"
#include "Lighting.cginc"
#pragma target 4.0
sampler2D _GrassTex;
fixed4 _TopColor;
fixed4 _BottomColor;
float _Height;
float _Width;
bool _FacingCamera;
bool _WindToggle;
float _WindStrength;
sampler2D _Control;
int _GrassLayerIndex;
struct v2g
{
float4 pos : SV_POSITION;
float3 norm : NORMAL;
float2 uv : TEXCOORD0;
};
struct g2f
{
float4 pos : SV_POSITION;
float3 norm : NORMAL;
float2 uv : TEXCOORD0;
};
v2g vert(appdata_full v)
{
v2g o;
o.pos = v.vertex;
o.norm = v.normal;
o.uv = v.texcoord;
return o;
}
float3 WindEffect(float3 root, float height, float random)
{
const float oscillateDelta = 0.05;
float2 wind = float2(sin(_Time.x * UNITY_PI * 5), sin(_Time.x * UNITY_PI * 5));
wind.x += (sin(_Time.x + root.x / 25) + sin((_Time.x + root.x / 15) + 50)) * 0.5;
wind.y += cos(_Time.x + root.z / 80);
wind *= lerp(0.7, 1.0, 1.0 - random);
float oscillationStrength = 2.5f;
float sinSkewCoeff = random;
float lerpCoeff = (sin(oscillationStrength * _Time.x + sinSkewCoeff) + 1.0) / 2;
float2 leftWindBound = wind * (1.0 - oscillateDelta);
float2 rightWindBound = wind * (1.0 + oscillateDelta);
wind = lerp(leftWindBound, rightWindBound, lerpCoeff);
float randomAngle = lerp(-UNITY_PI, UNITY_PI, random);
float randomMagnitude = lerp(0, 1., random);
float2 randomWindDir = float2(sin(randomAngle), cos(randomAngle));
wind += randomWindDir * randomMagnitude;
float windForce = length(wind);
root.xz += wind.xy * height * _WindStrength;
root.y -= windForce * height * 0.8 * _WindStrength;
return root;
}
g2f createGSOut()
{
g2f output;
output.pos = float4(0, 0, 0, 0);
output.norm = float3(0, 0, 0);
output.uv= float2(0, 0);
return output;
}
[maxvertexcount(30)]
void geom(point v2g points[1], inout TriangleStream<g2f> triStream)
{
float grassFade = tex2Dlod(_Control, fixed4(points[0].uv, 0, 0))[_GrassLayerIndex];
if (grassFade > 0.5)
{
const int vertexCount = 12;
g2f v[vertexCount] =
{
createGSOut(), createGSOut(), createGSOut(), createGSOut(),
createGSOut(), createGSOut(), createGSOut(), createGSOut(),
createGSOut(), createGSOut(), createGSOut(), createGSOut()
};
float3 root = points[0].pos.xyz;
float random = sin(UNITY_HALF_PI * frac(root.x) + UNITY_HALF_PI * frac(root.z));
float width = _Width + (random / 50);
float height = _Height + (random / 5);
height *= grassFade;
float currentV = 0;
float offsetV = 1.f / (vertexCount * 0.5 - 1);
float currentHeightOffset = 0;
float currentVertexHeight = 0;
float3 right = float3(1,0,0);
float3 up = float3(0,1,0);
if(_FacingCamera)
{
right = UNITY_MATRIX_IT_MV[0].xyz;
up = UNITY_MATRIX_IT_MV[1].xyz;
}
for (int i = 0; i < vertexCount; i++)
{
v[i].norm = fixed3(0, 1, 0);
if (_WindToggle)
{
root = WindEffect(root, currentV, random);
}
if (fmod(i, 2) == 0)
{
v[i].pos = float4(root - width * right + currentVertexHeight * up, 1);
v[i].uv = float2(0, currentV);
}
else
{
v[i].pos = float4(root + width * right + currentVertexHeight * up, 1);
v[i].uv = float2(1, currentV);
currentV += offsetV;
currentVertexHeight = currentV * height;
}
v[i].pos = UnityObjectToClipPos(v[i].pos);
}
for (int p = 0; p < (vertexCount - 2); p++)
{
triStream.Append(v[p]);
triStream.Append(v[p + 2]);
triStream.Append(v[p + 1]);
}
}
}
half4 frag(g2f IN) : COLOR
{
fixed4 tex = tex2D(_GrassTex, IN.uv);
fixed4 color = tex * lerp(_BottomColor, _TopColor, IN.uv.y);
half3 worldNormal = UnityObjectToWorldNormal(IN.norm);
fixed3 ambient = ShadeSH9(half4(worldNormal, 1));
fixed3 diffuseLight = saturate(dot(worldNormal, UnityWorldSpaceLightDir(IN.pos))) * _LightColor0;
fixed3 halfVector = normalize(UnityWorldSpaceLightDir(IN.pos) + WorldSpaceViewDir(IN.pos));
fixed3 specularLight = pow(saturate(dot(worldNormal, halfVector)), 15) * _LightColor0;
fixed3 light = ambient + diffuseLight + specularLight;
clip(tex.a - 0.99);
return fixed4(color.rgb * light, 1);
}
ENDCG
}
}
}
运行时的地形编辑:
using System;
using System.Collections;
using UnityEngine;
public class TerrainUtil
{
#region 地形高度笔刷
/// <summary>
/// 升高Terrain上某点的高度。
/// </summary>
/// <param name="terrain">Terrain</param>
/// <param name="point">世界坐标点</param>
/// <param name="opacity">升高的高度</param>
/// <param name="size">笔刷大小</param>
/// <param name="amass">当笔刷范围内其他点的高度已经高于笔刷中心点时是否同时提高其他点的高度</param>
public static void Rise(Terrain terrain, Vector3 point, float opacity, int size)
{
TerrainData tData = terrain.terrainData;
//设置笔刷大小
Vector2Int index = GetHeightmapIntIndex(terrain, point);
int bound = size / 2;
int xBase = index.x - bound >= 0 ? index.x - bound : 0;
int yBase = index.y - bound >= 0 ? index.y - bound : 0;
int width = xBase + size <= tData.heightmapResolution ? size : tData.heightmapResolution - xBase;
int height = yBase + size <= tData.heightmapResolution ? size : tData.heightmapResolution - yBase;
//获取笔刷范围内的高度图以及将高度归一化到[0, 1]
float[,] heights = tData.GetHeights(xBase, yBase, width, height);
float initHeight = tData.GetHeight(index.x, index.y) / tData.size.y;
float deltaHeight = opacity / tData.size.y;
//应用笔刷
ApplyBrush(heights, deltaHeight, initHeight, height, width);
tData.SetHeights(xBase, yBase, heights);
}
/// <summary>
/// 降低Terrain上某点的高度。
/// </summary>
/// <param name="terrain">Terrain</param>
/// <param name="point">世界坐标点</param>
/// <param name="opacity">降低的高度</param>
/// <param name="size">笔刷大小</param>
/// <param name="amass">当笔刷范围内其他点的高度已经低于笔刷中心点时是否同时降低其他点的高度</param>
public static void Sink(Terrain terrain, Vector3 point, float opacity, int size)
{
TerrainData tData = terrain.terrainData;
//设置笔刷大小
Vector2Int index = GetHeightmapIntIndex(terrain, point);
int bound = size / 2;
int xBase = index.x - bound >= 0 ? index.x - bound : 0;
int yBase = index.y - bound >= 0 ? index.y - bound : 0;
int width = xBase + size <= tData.heightmapResolution ? size : tData.heightmapResolution - xBase;
int height = yBase + size <= tData.heightmapResolution ? size : tData.heightmapResolution - yBase;
//获取笔刷范围内的高度图以及将高度归一化到[0, 1]
float[,] heights = tData.GetHeights(xBase, yBase, width, height);
float initHeight = tData.GetHeight(index.x, index.y) / tData.size.y;
float deltaHeight = -opacity / tData.size.y;
//应用笔刷
ApplyBrush(heights, deltaHeight, initHeight, height, width);
tData.SetHeights(xBase, yBase, heights);
}
/// <summary>
/// 根据笔刷四角的高度来平滑Terrain,该方法不会改变笔刷边界处的Terrain高度。
/// </summary>
/// <param name="terrain">Terrain</param>
/// <param name="point">世界坐标点</param>
/// <param name="opacity">平滑灵敏度,值介于 [0.05,1] 之间</param>
/// <param name="size">笔刷大小</param>
public static void Smooth(Terrain terrain, Vector3 point, float opacity, int size)
{
TerrainData tData = terrain.terrainData;
if (opacity > 1 || opacity <= 0)
{
opacity = Mathf.Clamp(opacity, 0.05f, 1);
Debug.LogError("Smooth方法中的opacity参数的值应该介于 [0.05,1] 之间,强制将其设为:" + opacity);
}
// 取出笔刷范围内的HeightMap数据数组
Vector2Int index = GetHeightmapIntIndex(terrain, point);
int bound = size / 2;
int xBase = index.x - bound >= 0 ? index.x - bound : 0;
int yBase = index.y - bound >= 0 ? index.y - bound : 0;
int width = xBase + size <= tData.heightmapResolution ? size : tData.heightmapResolution - xBase;
int height = yBase + size <= tData.heightmapResolution ? size : tData.heightmapResolution - yBase;
float[,] heights = tData.GetHeights(xBase, yBase, width, height);
// 利用笔刷4角的高度来计算平均高度
float avgHeight = (heights[0, 0] + heights[0, width - 1] + heights[height - 1, 0] + heights[height - 1, width - 1]) / 4;
Vector2 center = new Vector2((float)(height - 1) / 2, (float)(width - 1) / 2);
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
// 点到矩阵中心点的距离
float toCenter = Vector2.Distance(center, new Vector2(i, j));
float diff = avgHeight - heights[i, j];
// 判断点在4个三角形区块上的位置
// 利用相似三角形求出点到矩阵中心点与该点连线的延长线与边界交点的距离
float d = 0;
if (i == height / 2 && j == width / 2) // 中心点
{
d = 1;
toCenter = 0;
}
else if (i >= j && i <= size - j) // 左三角区
{
d = toCenter * j / ((float)width / 2 - j);
}
else if (i <= j && i <= size - j) // 上三角区
{
d = toCenter * i / ((float)height / 2 - i);
}
else if (i <= j && i >= size - j) // 右三角区
{
d = toCenter * (size - j) / ((float)width / 2 - (size - j));
}
else if (i >= j && i >= size - j) // 下三角区
{
d = toCenter * (size - i) / ((float)height / 2 - (size - i));
}
// 进行平滑时对点进行升降的比例
float ratio = d / (d + toCenter);
heights[i, j] += diff * ratio * opacity;
}
}
tData.SetHeights(xBase, yBase, heights);
}
/// <summary>
/// 压平Terrain并提升到指定高度。
/// </summary>
/// <param name="terrain">Terrain</param>
/// <param name="height">高度</param>
public static void Flatten(Terrain terrain, float height)
{
TerrainData tData = terrain.terrainData;
float scaledHeight = height / tData.size.y;
float[,] heights = new float[tData.heightmapResolution, tData.heightmapResolution];
for (int i = 0; i < tData.heightmapResolution; i++)
{
for (int j = 0; j < tData.heightmapResolution; j++)
{
heights[i, j] = scaledHeight;
}
}
tData.SetHeights(0, 0, heights);
}
/* Terrain的HeightMap坐标原点在左下角 * y * ↑ * 0 → x */
/// <summary> 返回某世界坐标点在Terrain的HeightMap索引 </summary>
private static Vector2Int GetHeightmapIntIndex(Terrain terrain, Vector3 point)
{
TerrainData tData = terrain.terrainData;
int x = (int)((point.x - terrain.GetPosition().x) / tData.size.x * tData.heightmapResolution);
int y = (int)((point.z - terrain.GetPosition().z) / tData.size.z * tData.heightmapResolution);
return new Vector2Int(x, y);
}
/// <summary> 返回某世界坐标点在Terrain的HeightMap索引 </summary>
private static Vector2 GetHeightmapFloatIndex(Terrain terrain, Vector3 point)
{
TerrainData tData = terrain.terrainData;
float x = (float)((point.x - terrain.GetPosition().x) / tData.size.x * tData.heightmapResolution);
float y = (float)((point.z - terrain.GetPosition().z) / tData.size.z * tData.heightmapResolution);
return new Vector2(x, y);
}
/// <summary> 返回GameObject在Terrain上的相对位置 </summary>
private static Vector3 GetRelativePosition(Terrain terrain, GameObject go)
{
return go.transform.position - terrain.GetPosition();
}
/// <summary> 返回世界坐标系下指定点在Terrain上的高度。 </summary>
private static float GetPointHeight(Terrain terrain, Vector3 point, bool vertex = false)
{
// 对于水平面上的点来说,vertex参数没有影响
if (vertex)
{
// GetHeight得到的是离点最近的顶点的高度
Vector2Int index = GetHeightmapIntIndex(terrain, point);
return terrain.terrainData.GetHeight(index.x, index.y);
}
else
{
// SampleHeight得到的是点在斜面上的实际高度
return terrain.SampleHeight(point);
}
}
/// <summary>
/// 返回Terrain的HeightMap,这是一个 height*width 大小的二维数组,并且值介于 [0.0f,1.0f] 之间。
/// </summary>
/// <param name="terrain">Terrain</param>
/// <param name="xBase">检索HeightMap时的X索引起点</param>
/// <param name="yBase">检索HeightMap时的Y索引起点</param>
/// <param name="width">在X轴上的检索长度</param>
/// <param name="height">在Y轴上的检索长度</param>
/// <returns></returns>
public static float[,] GetHeightMap(Terrain terrain, int xBase = 0, int yBase = 0, int width = 0, int height = 0)
{
if (xBase + yBase + width + height == 0)
{
width = terrain.terrainData.heightmapResolution;
height = terrain.terrainData.heightmapResolution;
}
return terrain.terrainData.GetHeights(xBase, yBase, width, height);
}
/// <summary>
/// 改变heightmap中的值
/// </summary>
/// <param name="heights">HeightMap</param>
/// <param name="deltaHeight">高度变化量[-1,1]</param>
/// <param name="initHeight">笔刷中心点的初始高度</param>
/// <param name="row">HeightMap行数</param>
/// <param name="column">HeightMap列数</param>
/// <param name="amass">当笔刷范围内其他点的高度已经高于笔刷中心点时是否同时提高其他点的高度</param>
private static void ApplyBrush(float[,] heights, float deltaHeight, float initHeight, int row, int column)
{
// 高度限制
float limit = initHeight + deltaHeight;
for (int i = 0; i < row; i++)
{
for (int j = 0; j < column; j++)
{
if (deltaHeight > 0) // 升高地形
{
heights[i, j] = heights[i, j] >= limit ? heights[i, j] : heights[i, j] + deltaHeight;
}
else // 降低地形
{
heights[i, j] = heights[i, j] <= limit ? heights[i, j] : heights[i, j] + deltaHeight;
}
}
}
}
/// <summary>
/// 设置Terrain的HeightMap。
/// </summary>
/// <param name="terrain">Terrain</param>
/// <param name="heights">HeightMap</param>
/// <param name="xBase">X起点</param>
/// <param name="yBase">Y起点</param>
private static void SetHeights(Terrain terrain, float[,] heights, int xBase = 0, int yBase = 0)
{
terrain.terrainData.SetHeights(xBase, yBase, heights);
}
#endregion
#region 地形贴图笔刷
#endregion
}
边栏推荐
- [C language] accidentally write a bug? Mortals teach you how to write good code [explain debugging skills in vs]
- 腾讯Libco协程开源库 源码分析(三)---- 探索协程切换流程 汇编寄存器保存 高效保存协程环境
- Analyse du code source de Tencent libco CO CO - Process open source library
- 用 Plotly 绘制了几张精湛的图表,美翻了!!
- [C language] still don't understand the structure? Take a look at this article to give you a preliminary understanding of structure
- 基于改进SEIR模型分析上海疫情
- Tencent libco collaboration open source library source code analysis full series summary blog
- 2022.05.26 (lc_1143_longest common subsequence)
- Bit operation topic
- LLDP协议编写要点
猜你喜欢

One article explains in detail the exploration and practice of eventmesh landing on Huawei cloud

Batch detection of specified ports of different URLs (py script)

Zabbix Server Trapper远程代码执行漏洞(CVE-2017-2824)

On the development trend of enterprise storage: cold thoughts on open source storage

Computer: successfully teach you how to use one trick to retrieve the previous password (the password once saved but currently displayed as ******)

如何查询 SAPGUI 屏幕上某个字段对应的数据库表存储

How to add aggregation hotspots in VR panorama? How to add a content module?

During the college entrance examination this year, all examination sites were in good order, and no sensitive cases affecting safety occurred

I drew several exquisite charts with plotly, which turned out to be beautiful!!

This article introduces you to j.u.c's futuretask, fork/join framework and BlockingQueue
随机推荐
4.35V锂电充电IC
Bit operation topic
Easily learn pytoch full convolution neural network to realize expression recognition
Congratulations | Najie research group of Medical College revealed the function of junB in the process of differentiation of artificial blood progenitor cells in vitro through multi group analysis
Soft deletion of data - when? How to realize it?
[C language] have you mastered these classic questions? Learn these questions in one article
After the college entrance examination, VR panoramic tour will show you the beautiful scenery of the scenic spot
Nature Biotechnol | 李家洋/余泓团队利用平铺删除策略打破性状连锁,突破水稻产量瓶颈
软件测试月薪10K如何涨到30K,只有自动化测试能做到
MySQL backup and manual execution of shell scripts are OK, and crontab scheduled execution fails
高考后选择哪所学校?VR全景校园全方位展示
详细解读TPH-YOLOv5 | 让目标检测任务中的小目标无处遁形
Rmarkdown 轻松录入数学公式
推开混合云市场大门,Lenovo xCloud的破局之道
基于改进SEIR模型分析上海疫情
frp reverse proxy
恭喜 | 医学院那洁课题组通过多组学分析揭示JUNB在体外分化人造血祖细胞过程中的功能
PDU会话流程
[advanced C language] advanced pointer [Part 1]
深入理解LightGBM