rpg游戏中的地形一般使用高度图的形式来绘制。写了几个随机生成高度图的算法。
最常见的是基于分形算法生成高度图,网上有很多资料,这里不再介绍。
一种生成断层效果高度图的算法
//!生成断层效果的高度图
void TerrainData::FillFaultSurface(float minHeight, float maxHeight , int iterNum,float fSmooth)
{
const int size = m_vertexNum.x*m_vertexNum.y;
TerrainVertex* d = m_vertices;
for (int i=0; i<size; i++)
{
d->y = 0;
d++;
}
int iRandX1, iRandZ1;
int iRandX2, iRandZ2;
int iDirX1, iDirZ1;
int iDirX2, iDirZ2;
int iHeight;
float daltaHeight = 256/iterNum;
for(int it=0; it<iterNum; it++ )
{
//插值升高的高度
iHeight= 256 - daltaHeight*it;
{
//选择两个不重合的随机点
iRandX1= Rand()%m_vertexNum.x;
iRandZ1= Rand()%m_vertexNum.y;
do
{
iRandX2= Rand()%m_vertexNum.x;
iRandZ2= Rand()%m_vertexNum.y;
} while ( iRandX2==iRandX1 && iRandZ2==iRandZ1 );
//
iDirX1= iRandX2-iRandX1;
iDirZ1= iRandZ2-iRandZ1;
for(int z=0; z<m_vertexNum.y; z++ )
{
for(int x=0; x<m_vertexNum.x; x++ )
{
//iDirX2, iDirZ2 is a vector from iRandX1, iRandZ1 to the current point (in the loop)
iDirX2= x-iRandX1;
iDirZ2= z-iRandZ1;
//升高一半平面iHeight
if( ( iDirX2*iDirZ1 - iDirX1*iDirZ2 )>0 )
m_vertices[( z*m_vertexNum.x )+x].y += ( float )iHeight;
}
}
}
//
GaussBlur2D( &m_vertices->y,sizeof(TerrainVertex)/4,m_vertexNum.x,m_vertexNum.y,fSmooth);
}
GetMinMaxAvg(&m_vertices->y,sizeof(TerrainVertex)/4,m_vertexNum.x*m_vertexNum.y,&m_bottomHeight,&m_topHeight,&m_avgHeight);
RescaleHeight(minHeight, maxHeight);
}
对高度图进行降雨腐蚀的算法
//MaxDropLife = 30; // 最大雨滴寿命
//ErodeSpeed = 0.3f; // 侵蚀速度
//DepositSpeed = 0.3f; // 沉积速度
//ErosionRadius = 3; // 侵蚀半径
void TerrainData::ErosionSurface(float mount,int MaxDropLife,float ErodeSpeed,float DepositSpeed,int ErosionRadius)
{
float Inertia; // 惯性= 0.05
float SedimentCapacityFactor;// 沙容量= 4
float MinSedimentCapacity; // 沙容量= 0.01
float EvaporateSpeed; // 蒸发速度 = 0.01
float Gravity; // 重力=4
float Resistance; // 阻力=0.1
float InitialWater; // 初始雨滴水量= 1
float InitialSpeed; // 初始雨滴速度= 1
Inertia = 0.05f;
SedimentCapacityFactor = 4;
MinSedimentCapacity = 0.01f;
if (ErosionRadius==1) //防止无限腐蚀和沉积 (腐蚀了1单位高度,导致速度增加n单位,容沙量增加m单位,又导致降低>1单位高度,无限循环)
{
SedimentCapacityFactor = 0.05f;
Gravity = 0.9f;
}
else if (ErosionRadius==2)
{
SedimentCapacityFactor = 4;
Gravity = 4;
}
else if (ErosionRadius==3)
{
SedimentCapacityFactor = 4;
Gravity = 4;
}
EvaporateSpeed = 0.01f;
MaxDropLife = 30;
InitialWater = 1;
InitialSpeed = 1;
Resistance = 0.1f; //影响不大
const int mapSize = m_vertexNum.x;
struct BrushPt
{
int num;
float* py[49];
float weight[49];
};
BrushPt* BrushWeights;
{
BrushWeights = new BrushPt[mapSize * mapSize];
int indexNum = 0;
int indexs[256];
float weights[256];
float weightSum = 0;
const int num = mapSize * mapSize;
const float devRadius = 1.0f/ErosionRadius;
const float radiusSq = ErosionRadius*ErosionRadius;
for(int i = 0; i < num; i++)
{
int centerX = i % mapSize;
int centerY = i / mapSize;
weightSum = 0;
indexNum = 0;
for(int y = -ErosionRadius; y <= ErosionRadius; y++)
{
for(int x = -ErosionRadius; x <= ErosionRadius; x++)
{
float dstSq = x * x + y * y;
if(dstSq < radiusSq)
{
int iX = centerX + x;
int iY = centerY + y;
if(iX >= 0 && iX < mapSize && iY >= 0 && iY < mapSize)
{
float weight = 1 - sqrt(dstSq)*devRadius;
weightSum += weight;
weights[indexNum] = weight;
indexs[indexNum] = y* mapSize + x + i;
indexNum++;
}
}
}
}
BrushPt* pt = &BrushWeights[i];
pt->num = indexNum;
for(int j = 0; j < indexNum; j++)
{
pt->py[j] = &m_vertices[indexs[j]].y;
//权重之和为1
pt->weight[j] = weights[j] / weightSum;
}
}
}
const int DropNum = 1+mapSize*mapSize*mount;
// 迭代步骤:
// 增加水滴。均匀随机分布
// 计算地形坡度。确定水滴方向和速度。
// 计算泥沙容量。受坡度,水速和水量的影响。
// 侵蚀或沉积。 携沙量>容量则沉积,否则侵蚀。
// 更新水滴位置下山。
// 蒸发。
//const int mapSize_2 = ;
//快速单水滴独立模拟,没有使用pch流体模拟
vec3 heightAndGradient;
vec3 newHeightAndGradient;
int dropIndex;//posindex
vec2 dropPos;
vec2 dropDir;
float dropSpeed;
float dropWater;
float dropSediment;
for(int d = 0; d < DropNum; d++)
{
dropPos.x = RandRange(0.0f, mapSize - 1.0f);//
dropPos.y = RandRange(0.0f, mapSize - 1.0f);
dropDir.x = 0;
dropDir.y = 0;
dropSpeed = InitialSpeed;
dropWater = InitialWater;
dropSediment = 0;
//
for(int life = 0; life < MaxDropLife; life++)
{
int x0 = int(dropPos.x);
int y0 = int(dropPos.y);
dropIndex = y0 * mapSize + x0;
//计算水滴高度 梯度(流动方向)
CalHeightAndGradientLf(dropPos.x, dropPos.y,heightAndGradient);
dropDir.x = (dropDir.x * Inertia - heightAndGradient.x * (1 - Inertia));
dropDir.y = (dropDir.y * Inertia - heightAndGradient.z * (1 - Inertia));
NormalizeFastVec2(dropDir);
//移动一个地形格子,与速度无关
dropPos.x += dropDir.x;
dropPos.y += dropDir.y;
//到达边界 有水土流失
if(dropPos.x < 0 || dropPos.x >= mapSize - 2 || dropPos.y < 0 || dropPos.y >= mapSize - 2)
break;
//蒸发干净
if(dropWater<= 0)
break;
//到达平地,静止(惯性衰减到0),留在原地继续蒸发殆尽影响不大
if(dropDir.x==0 && dropDir.y==0)
{
if(dropSediment>0.1f)
int a = 0;
break;//有水土流失 这里break影响不大
}
//新高度
float newHeight = CalHeightAndGradientLf(dropPos.x, dropPos.y,newHeightAndGradient);//GetHeightLf(dropPos.x, dropPos.y);
//下降的高度
float deltaHeight = newHeight - heightAndGradient.y;
//容沙量 (与坡度 速度 水量相关)
float sedimentCapacity = -deltaHeight * dropSpeed * dropWater * SedimentCapacityFactor;
if (sedimentCapacity<MinSedimentCapacity)
sedimentCapacity = MinSedimentCapacity;
if(dropSediment > sedimentCapacity //携沙量 > 容沙量
|| deltaHeight > 0 // 上山
)
{
//沉积:
float toDeposit;
if(deltaHeight > 0)
{
toDeposit = Min(deltaHeight, dropSediment);//上山 fill up填平当前位置
}
else
{
toDeposit = (dropSediment - sedimentCapacity) * DepositSpeed;
//if(toDeposit > -deltaHeight)
// toDeposit = -deltaHeight;//沉积高度不超过下降高度->避免出现尖刺
}
//画刷内部 按权重沉积
int ptNum = BrushWeights[dropIndex].num;
BrushPt* brushWeight = &BrushWeights[dropIndex];
for(int b = 0; b < ptNum; ++b)
{
float weighedDeposit = toDeposit * brushWeight->weight[b];
float delta = weighedDeposit;
float maxDelta = heightAndGradient.y + weighedDeposit - (*brushWeight->py[b]);
if(delta> maxDelta)//邻边非常高时不沉积,防止产生尖刺
delta = maxDelta;
if(delta>0)
{
(*brushWeight->py[b]) += delta;
dropSediment -= delta;//权重之和不一定为1
}
}
}
else
{
//腐蚀:腐蚀高度不超过下降高度->避免出现空洞
float toErode = (sedimentCapacity - dropSediment) * ErodeSpeed;
if(toErode>-deltaHeight)
toErode = -deltaHeight;//腐蚀高度不超过下降高度->避免出现空洞
//画刷内部
int ptNum = BrushWeights[dropIndex].num;
BrushPt* brushWeight = &BrushWeights[dropIndex];
for(int b = 0; b < ptNum; ++b)
{
float weighedErode = toErode * brushWeight->weight[b];
float delta = weighedErode;
float maxDelta = (*brushWeight->py[b]) - (heightAndGradient.y - weighedErode);
if(delta> maxDelta)//邻边非常低时不腐蚀,防止产生尖井
delta = maxDelta;
if (delta>0)
{
(*brushWeight->py[b]) -= delta;
dropSediment += delta;
}
}
}
//if(deltaHeight<0)
{
dropSpeed = sqrt(dropSpeed * dropSpeed + (-deltaHeight) * Gravity - Resistance); //重力和阻力做功
}
dropWater *= (1 - EvaporateSpeed);
}
}
SafeDeleteArray(BrushWeights);
}
梯田化地形 风吹形成山脊
//!风吹沙地形成山脊线 divide点数 首尾在边界
void TerrainData::BlowSurface(const vec3& windDir_,int divide,float weight,float variation)
{
//方法一 使用Voronoi图
//1,生成10*10个整齐排列的点,
//2,每个点随机便移一个位置
//3,以每个点为中心画逐渐扩大的圆,交线为voronoi的边
//4,voronoi的每个晶格内使用球挖除地表
//地表上每个点的挖除深度仅有voronoi中距离最近的点决定 使用格子划分提高查找速度
#define MaxVoronoi 40
Clamp( divide,1,MaxVoronoi-1);
static vec2 points[MaxVoronoi][MaxVoronoi];
float vorinoiCellWidth = m_vertexNum.x/(divide-1);
//float Up = (vorinoiCellWidth*vorinoiCellWidth)/4;//=200
float Up = (vorinoiCellWidth*vorinoiCellWidth);//=200
for (int z=0;z<divide;z++)
{
for (int x=0;x<divide;x++)
{
points[z][x] = vec2(x*vorinoiCellWidth,z*vorinoiCellWidth);
points[z][x] += vec2(RandRange(-1.0f,1.0f),RandRange(-1.0f,1.0f))*variation*vorinoiCellWidth;
}
}
//
const int size = m_vertexNum.x*m_vertexNum.y;
vec2 vpos;
TerrainVertex* v = m_vertices;
for(int z=0; z<m_vertexNum.y; z++ )
{
v = m_vertices+z*m_vertexNum.x;
vpos.y = z;
for(int x=0; x<m_vertexNum.x; x++ )
{
vpos.x = x;
float distsq = MaxFloat;
int ox = x/vorinoiCellWidth;
int oz = z/vorinoiCellWidth;
for (int cx=ox-2;cx<=ox+2;cx++)
{
for (int cz=oz-2;cz<=oz+2;cz++)
{
if ( cx>=0&&cx<divide
&&cz>=0&&cz<divide)
{
float t = (points[cz][cx]-vpos).LengthSq();
if (distsq>t)
{
distsq = t;
}
}
}
}
if (distsq>999999)
{
distsq = 0;
}
if (distsq>Up+10)
{
int a = 0;
}
//
v->y -= (Up-distsq)*weight;
v++;
}
}
}
集成到一个流程图编辑器
给腐蚀后的地面加入自动生成的降雨水流湖面,术语好像叫地表径流。
struct WaterPixel
{
float terrainHeight;
float waterDepth;
float totalHeight;
//float sediment;
//float flowSpeed;//(for rendering)
WaterPixel* neighbour[4];
//float inFlow; // sum of water inflow
float outflow[4];
};
int TerrainData::GenTerrainWaterMap(TextureData& srcTexData,TextureData& dstTexData,
const int IterNum/*=100*/,const float RainSpeed/*=0.05f*/,const float StartWater/*=0.01f*/,
const float AlphaScale/*=0.1f*/,const float DepthCutoff/*=45*/,
const float edgeWidth/* = 10*/,const bool Sharp/*=false*/)
{
const float FlowDamp = 0.95f;
const float FlowSpeed = 0.2f;
if (srcTexData.GetWidth()!=dstTexData.GetWidth() || srcTexData.GetHeight()!=dstTexData.GetHeight())
{
return 0;
}
// //精确求解较复杂 需要多次分段计算 所有的水加起来从最低处单根灌起 然后到第二低开始灌两根
// /*
// ~
// # ~
// # ~ ~
// # # ~
// ----- # --- # ~-------
// # # ~
// # ~ ~ # ~
// # ~ # # ~
// # # # # #
// # # # # #
// */
vec2I waterSize = m_tileNum;
vec2I waterSize_1 = vec2I(waterSize.x-1,waterSize.y-1);
WaterPixel* waterData = new WaterPixel[waterSize.x*waterSize.y];
memset(waterData,0,sizeof(WaterPixel)*waterSize.x*waterSize.y);
WaterPixel* pixel = waterData;
vec2 scale;
scale.x = m_vertexNum.x/float(waterSize.x);
scale.y = m_vertexNum.y/float(waterSize.y);
for (int z=0;z<waterSize.y;++z)
{
float zf = z*scale.y;
for (int x=0;x<waterSize.x;++x)
{
pixel->terrainHeight = GetHeightLf(x*scale.x,zf);///256 +0.5f;
pixel->waterDepth = StartWater;
/*
2
0 1
3
*/
int index0 = z*waterSize.x + x;
int index;
{
index = (z==0)?index0:(index0-waterSize.x);
pixel->neighbour[2] = &waterData[index];
index = (z==waterSize_1.y)?(index0):(index0+waterSize.x);
pixel->neighbour[3] = &waterData[index];
index = (x==0)?(index0):(index0-1);
pixel->neighbour[0] = &waterData[index];
index = (x==waterSize_1.x)?(index0):(index0+1);
pixel->neighbour[1] = &waterData[index];
}
pixel++;
}
}
//
const float devWaterSize = 1.0f/waterSize.x;
for(int t=0;t<IterNum;t++)
{
WaterPixel* pixel = waterData;
for (int z=0;z<waterSize.y;++z)
{
for (int x=0;x<waterSize.x;++x)
{
float totalHeight = pixel->totalHeight;
float waterHeight = pixel->waterDepth;
//
float diffHeight[4] =
{
totalHeight - pixel->neighbour[0]->totalHeight,
totalHeight - pixel->neighbour[1]->totalHeight,
totalHeight - pixel->neighbour[2]->totalHeight,
totalHeight - pixel->neighbour[3]->totalHeight
};
//
pixel->outflow[0] = pixel->outflow[0]*FlowDamp + diffHeight[0] * FlowSpeed ;
pixel->outflow[1] = pixel->outflow[1]*FlowDamp + diffHeight[1] * FlowSpeed ;
pixel->outflow[2] = pixel->outflow[2]*FlowDamp + diffHeight[2] * FlowSpeed ;
pixel->outflow[3] = pixel->outflow[3]*FlowDamp + diffHeight[3] * FlowSpeed ;
if(pixel->outflow[0]<_EPSILON)pixel->outflow[0]=0;
if(pixel->outflow[1]<_EPSILON)pixel->outflow[1]=0;
if(pixel->outflow[2]<_EPSILON)pixel->outflow[2]=0;
if(pixel->outflow[3]<_EPSILON)pixel->outflow[3]=0;
//
float sum = (pixel->outflow[0] +
pixel->outflow[1] +
pixel->outflow[2] +
pixel->outflow[3]);
float outflowScale = 0;
if(sum>0)
{
outflowScale = waterHeight / sum;
outflowScale = (outflowScale<1)?outflowScale:1;
}
pixel->outflow[0] *= outflowScale;
pixel->outflow[1] *= outflowScale;
pixel->outflow[2] *= outflowScale;
pixel->outflow[3] *= outflowScale;
pixel++;
}
}
pixel = waterData;
for (int z=0;z<waterSize.y;++z)
{
for (int x=0;x<waterSize.x;++x)
{
float waterHeight = pixel->waterDepth;
float inflow[4] =
{
pixel->neighbour[0]->outflow[1] - pixel->outflow[0],
pixel->neighbour[1]->outflow[0] - pixel->outflow[1],
pixel->neighbour[2]->outflow[3] - pixel->outflow[2],
pixel->neighbour[3]->outflow[2] - pixel->outflow[3]
};
//update water
waterHeight += (
inflow[0] +
inflow[1] +
inflow[2] +
inflow[3]
);
waterHeight += RainSpeed; //rain
waterHeight = (waterHeight>0)?waterHeight:0;
pixel->waterDepth = waterHeight;
pixel->totalHeight = pixel->terrainHeight + waterHeight;
pixel++;
}
}
}
//DepthCutoff
pixel = waterData;
for (int z=0;z<waterSize.y;++z)
{
for (int x=0;x<waterSize.x;++x)
{
float waterHeight = pixel->waterDepth;
waterHeight -= DepthCutoff;
//if(waterHeight<0)
// waterHeight = 0.0f;
pixel->waterDepth = waterHeight;
pixel++;
}
}
{
int dstSizeX = m_vertexNum.x;
int dstSizeY = m_vertexNum.y;
vec2 heightScale;
heightScale.x = (float(waterSize.x))/dstSizeX;
heightScale.y = (float(waterSize.y))/dstSizeY;
TerrainVertex* dstVert = m_vertices;
int indexs[4];
float weights[4];
float depths[4];
for (int h=0;h<dstSizeY;h++)
{
float zf = h*heightScale.y;
for (int w=0;w<dstSizeX;w++)
{
float xf = w*heightScale.x;
Lerp_Bilinear(xf,zf,waterSize_1.x,waterSize_1.y,waterSize.x,indexs,weights);
depths[0] = waterData[indexs[0]].waterDepth;
depths[1] = waterData[indexs[1]].waterDepth;
depths[2] = waterData[indexs[2]].waterDepth;
depths[3] = waterData[indexs[3]].waterDepth;
float depth =
weights[0] * depths[0]
+ weights[1] * depths[1]
+ weights[2] * depths[2]
+ weights[3] * depths[3];
dstVert->yWater = dstVert->y + depth;
dstVert ++;
}
}
}
//==================^_^
{
int dstSizeX = dstTexData.GetWidth();
int dstSizeY = dstTexData.GetWidth();
vec2 heightScale;
heightScale.x = (float(waterSize.x))/dstSizeX;
heightScale.y = (float(waterSize.y))/dstSizeY;
float color[3];
color[0] = 48;
color[1] = 52;
color[2] = 108;
int bits = dstTexData.HasAlpha()?4:3;
unsigned char* srcImageData = srcTexData.GetImageData();
unsigned char* dstImageData = dstTexData.GetImageData();
int indexs[4];
float weights[4];
for (int h=0;h<dstSizeY;h++)
{
float zf = h*heightScale.y;
for (int w=0;w<dstSizeX;w++)
{
float xf = w*heightScale.x;
Lerp_Bilinear(xf,zf,waterSize_1.x,waterSize_1.y,waterSize.x,indexs,weights);
float depth =
weights[0] * waterData[indexs[0]].waterDepth
+ weights[1] * waterData[indexs[1]].waterDepth
+ weights[2] * waterData[indexs[2]].waterDepth
+ weights[3] * waterData[indexs[3]].waterDepth;
if (depth>0)
{
float blend = depth;
blend *= AlphaScale;
if(blend<0) blend = 0;
else if(blend>1) blend = 1;
if(Sharp)
blend *= blend;
float blendd = 1-blend;
//河边描边 河底 都是黄色沙滩
dstImageData[0] = 255*blend + srcImageData[0]*blendd;
dstImageData[1] = srcImageData[1]*blendd;
dstImageData[2] = srcImageData[2]*blendd;
dstImageData[3] = srcImageData[3]*blendd;
}
else if (depth>-edgeWidth)
{
float blend = 1+depth/edgeWidth;
//blend *= AlphaScale;
if(blend<0) blend = 0;
else if(blend>1) blend = 1;
//if(Sharp)
// blend *= blend;
float blendd = 1-blend;
//河边描边 河底 都是黄色沙滩
dstImageData[0] = srcImageData[0]*blendd;
dstImageData[1] = srcImageData[1]*blendd;
dstImageData[2] = srcImageData[2]*blendd;
dstImageData[3] = 255*blend + srcImageData[3]*blendd;
}
else
{
dstImageData[0] = srcImageData[0];
dstImageData[1] = srcImageData[1];
dstImageData[2] = srcImageData[2];
dstImageData[3] = srcImageData[3];
}
srcImageData += bits;
dstImageData += bits;
}
}
}
SafeDeleteArray(waterData);
return 0;
}
为水面加上shader
#if !GLSLSHADER
#include "data/shader/ps_common.h"
#include "data/shader/math.h"
#endif
#define texWaterNoise baseTexture
//uniform sampler2D texWaterNoise _TEX0;
uniform sampler2D tex1Refract _TEX1;
uniform sampler2D tex2Reflect _TEX2;
uniform sampler2D texGeometry1 _TEX3;
uniform sampler2D texFoam _TEX4;
uniform sampler2D texNormalMap _TEX5;
uniform sampler2D texFlowMap _TEX6;
uniform float waveTime;
uniform float amplitude;
uniform float4x4 matViewPrjInverse;
uniform float3 vEyePos ;
//uniform float3 vLightPos;
uniform float3 vLightDir;
//插值水面 模拟积水变干
uniform vec4 ripplePos[10];
uniform vec4 ripplePower[10];
//uniform float2 worldSize;// = float2(2000, 2000);
//uniform int typeDrive;
//==================^_^==================^_^==================^_^==================^_^
#if GLSLSHADER
#define inPosW gl_TexCoord[0]
#define inPosWVP gl_TexCoord[1]
#define inNormal gl_TexCoord[2]
#endif
#ifdef ps_water
PSMAIN ps_water(
#if !GLSLSHADER
in float4 inPosW : TEXCOORD0,
in float4 inPosWVP : TEXCOORD1,
in float3 inNormal : TEXCOORD2,
out float4 outColor : COLOR
#endif
)
{
float4 WaterColor = float4(1.0, 1.0, 0.05,1);
float3 rendScale = float3(1.,1.,1.); //模型单位大小统一后 可去除
//叠加涟漪2
float rippleHeight = 0;
{
//10个正弦波叠加出涟漪 波长固定
//ripplePos.xyz=pos ripplePos.w=相移
//ripplePower.x=最近 ripplePower.y=最远
float dist = 0.;
float height = 0.;
float amp = 0;
for(int i=0;i<10;i++)
{
//距离增大 幅度平方衰减 10 ~100
dist = length(inPosW.xyz - ripplePos[i].xyz);
if(dist>ripplePower[i].x && dist<ripplePower[i].y)
{
amp = min(10/dist,1);
rippleHeight += amp* sin(3.1415*0.8*dist+ripplePos[i].w);
}
}
}
float2 texCoord = inPosWVP.xy / inPosWVP.w * 0.5 + float2( 0.5, 0.5 );
#if D3DDRIVER
texCoord.y = 1 - texCoord.y;
#endif
float3 dirToLight = - vLightDir;
float3 dirToEye = normalize( vEyePos - inPosW.xyz );
//float disToEye = length( vEyePos - inPosW.xyz );
//水体通透感:视线深度不同
float4 vertexPos;
UnprojectPosNormal32(tex2D(texGeometry1,texCoord.xy),inPosWVP,matViewPrjInverse,vertexPos);
float3 waterThrough = vertexPos.xyz - inPosW.xyz;
float waterDepth = length(waterThrough*rendScale) + rippleHeight;
float disToEye = length( (inPosW.xyz-vEyePos)*rendScale );
//float fDistScale = waterDepth/50.;//[0~1]
float fDistScale = 1-pow(disToEye,0.1)/4;//[0~1]
fDistScale = clamp(fDistScale,0,1);//[0~1]
#define FlowMap
float2 flowDir2 = float2(0.,1.); //必须恒定 否则floawmapoffset随wavetime增大变得夸张并无限大
float2 flowDir = float2(0.,-1.);
//inNormal = inNormal*2. - 1.;
if(abs(inNormal.y)>0.99999)
{
//极其水平面 随大流 而不是静止或乱流
//flowDir.xy = inNormal__.xz*5.;
}
else
{
//垂直面
向低势面流动,平滑着色模式 差一点角度 距离差很远
//flowDir.xy = inNormal__.xz;
flowDir.xy = inNormal.xz;
flowDir.xy = normalize(flowDir);
//flowDir = tex2D(texFlowMap, inPosW.xz*0.01);
}
float3 vertexNormal;
//法线
{
#ifdef FlowMap
float HalfMaxNOffset = 0.02;
float flowOffsetN1 = fmod(waveTime*0.1235,HalfMaxNOffset*2.);
float flowOffsetN2 = fmod(waveTime*0.1235+HalfMaxNOffset,HalfMaxNOffset*2.);
float2 texCoordN1 = inPosW.xz * 0.005*rendScale.xz - flowDir*flowOffsetN1;
float2 texCoordN2 = inPosW.xz * 0.00613*rendScale.xz - flowDir*flowOffsetN2;
float4 colorNormal = lerp(tex2D(texNormalMap, texCoordN1),tex2D(texNormalMap, texCoordN2),abs(flowOffsetN1-HalfMaxNOffset)/HalfMaxNOffset);
#else
float2 texCoordN1 = inPosW.xz * 0.005*rendScale.xz + flowDir2*waveTime*0.1235;//+texCoordOffset
float2 texCoordN2 = inPosW.xz * 0.00613*rendScale.xz + flowDir2*waveTime*0.233 + float2(20.,0);//错开0.5u,防止周期性重叠
float4 colorNormal = (tex2D(texNormalMap, texCoordN1)+tex2D(texNormalMap, texCoordN2))/2.;
#endif
vertexNormal.xyz = Bx2(colorNormal.rgb);
//TBN1
//{
// vertexNormal.xyz = vertexNormal.rbg;
//}
{//TBN2
float3 inTangent2 = float3(1.,0.,0.);
//TBN矩阵变换
vec3 bitangent = cross(inNormal.xyz,inTangent2.xyz);
inTangent2 = cross(bitangent,inNormal.xyz);
mat3 TBN = mat3(inTangent2.xyz, bitangent, inNormal.xyz);
TBN = transpose(TBN); //效率?
vertexNormal.xyz = mul( TBN, vertexNormal).xyz;
}
vertexNormal = normalize(vertexNormal);
}
//贴于地表
float2 noiseCoord = inPosW.xz / float2(125.0,125)*rendScale.xz;//波频
//叠加涟漪
noiseCoord *= (1. + rippleHeight*0.1);
#ifdef FlowMap
float HalfMaxOffset = 0.1;
float flowOffset1 = fmod(waveTime*0.8,HalfMaxOffset*2);
float flowOffset2 = fmod(waveTime*0.8+HalfMaxOffset,HalfMaxOffset*2);
float4 noiseColor1 = tex2D(texWaterNoise, noiseCoord - flowDir*flowOffset1);
float4 noiseColor2 = tex2D(texWaterNoise, noiseCoord - flowDir*flowOffset2);
float4 noiseColor = lerp(noiseColor1, noiseColor2, abs(flowOffset1-HalfMaxOffset)/HalfMaxOffset );
//noiseColor = float4(0.,0.,0.,0.);
#else
float4 noiseColor = tex2D(texWaterNoise, noiseCoord + flowDir2*waveTime);
#endif
float2 texCoordOffset = (noiseColor.rg - float2(0.3,0.3))* fDistScale *amplitude; // 幅度
float2 refrTexCoord = texCoord + texCoordOffset;
//<<
//折射色方法一: todo根据水的穿透深度调整折射幅度 水的近似垂直深度=vertexPos.y-水平面y
float4 colorRefract = tex2D(tex1Refract, refrTexCoord.xy);
//通透感:水越深折射色越淡 也可以根据深度采样纹理条 这里直接计算
//float weightRefract = 1.- pow(waterDepth,1.0)/100.;//fDistScale;
float weightRefract = 1.- pow(waterDepth,0.5)/10.;//fDistScale; //类似fog的指数衰减?
weightRefract = clamp(weightRefract,0.,1.);
weightRefract = 0.3 + weightRefract*0.7;
//折射色方法二:
//todo屏幕空间折射: 折射光线步进raymarching 深度大于depthbuffer即认为触底 由于水雾的存在 只需步进很短的距离(深水无折射)
//混合折射色和水色
outColor = lerp(WaterColor,colorRefract,weightRefract);
//>>
//反射色方法二:以水体上边界(可能是曲线)作为对称点采样折射贴图 ,二分查找上边界效率?(水中小岛可能不正确?)
//屏幕空间反射 效果通常不好? 有可能追不到颜色(颜色未绘制到屏幕上)且有误差带, 180度转动摄像机渲染两遍场景后 在两个屏幕空间结合光追?
//反射色
float4 colorReflect = tex2D(tex2Reflect, refrTexCoord.xy);
//float NdotL = max(dot(dirToEye, vertexNormal), 0.0);
float weightReflect = 0.5;//NdotL;
//float weightReflect = 1.0 - NdotL;
//混合反射色和水色
outColor = lerp(outColor,colorReflect,weightReflect);
//次表面散射(Sub-Surface Scattering,SSS)
//高光
//{
//phong
float3 dirRelfect = reflect(vLightDir, vertexNormal);
float EdotR = dot(dirToEye,dirRelfect);
float3 specularColor = float3(0.5,0.7,0.7) * pow(max(EdotR, 0.0), 5);
outColor.rgb += specularColor*0.6;
//}
//泡沫
#ifdef FlowMap
float HalfMaxFOffset = 0.6;
float flowOffsetF1 = fmod(waveTime*5.1235,HalfMaxFOffset*2);
float flowOffsetF2 = fmod(waveTime*5.1235+HalfMaxFOffset,HalfMaxFOffset*2);
float2 texCoordF1 = inPosW.xz * 0.16*rendScale.xz - flowDir*flowOffsetF1;
float2 texCoordF2 = inPosW.xz * 0.16*rendScale.xz - flowDir*flowOffsetF2;
float4 colorFoam1 = tex2D(texFoam, texCoordF1);
float4 colorFoam2 = tex2D(texFoam, texCoordF2);
float4 colorFoam = mix(colorFoam1,colorFoam2,abs(flowOffsetF1-HalfMaxFOffset)/HalfMaxFOffset );
#else
float4 colorFoam1 = tex2D(texFoam, inPosW.xz*0.16*rendScale.xz+texCoordOffset.xy*3. + float2(0.,waveTime)*0.1);
float4 colorFoam2 = tex2D(texFoam, inPosW.xz*0.26*rendScale.xz+texCoordOffset.xy*3. + float2(5.,waveTime)*0.1);
float4 colorFoam = mix(colorFoam1,colorFoam2,sin(waveTime*3.23)*0.3+0.7);
#endif
//边缘浪花 水的近似垂直深度=vertexPos.y-水平面y 浅的地方为边缘
//waterDepth = abs(inPosW.y - vertexPos.y);
//float3 foamBlend = float3(1.,1.,1.) - float3(waterDepth,waterDepth,waterDepth)/float3(1.,3.,10.);
//foamBlend = clamp(foamBlend,float3(0.,0.,0.),float3(1.,1.,1.));
float3 foamBlend = float3(waterDepth,waterDepth,waterDepth);
foamBlend = smoothstep(float3(0.,0.,0.),float3(0.4,0.8,1.),foamBlend) - smoothstep(float3(1.0,2.5,4.0),float3(2.,5.,10.),foamBlend);
//双正弦叠加
float waveBlendNoise = dot(sin(inPosW.xz*0.4*rendScale.xz),float2(1.,1.));
//foamHeight = sin(深度+ time)
float waveBlend = sin(pow(waterDepth,0.5)*9+waveTime*4. + waveBlendNoise )*0.4+0.6;
float foamLiumi = dot(colorFoam.rgb,foamBlend) * waveBlend ;
//交互泡沫
float3 foamBlendRip = float3(rippleHeight,rippleHeight,rippleHeight)*0.2;
//foamBlendRip = smoothstep(float3(0.,0.,0.),float3(0.4,0.2,0.1),foamBlendRip);
foamBlendRip = clamp(foamBlendRip,float3(0.,0.,0.),float3(1.,1.,1.));
foamLiumi += dot(colorFoam.rgb,foamBlendRip);
//浪尖泡沫
{
//float4 colorJaco = lerp(tex2D(texWaterNoise, texCoord1),tex2D(texWaterNoise, texCoord2),abs(flowOffsetN1-HalfMaxNOffset)/HalfMaxNOffset);
foamBlendRip = noiseColor.bbb - 0.3; //bba也不能增加交错感需要不同的速率
foamBlendRip = clamp(foamBlendRip,float3(0.,0.,0.),float3(1.,1.,1.));
foamLiumi += dot(colorFoam.rgb,foamBlendRip);
}
outColor.rgb += float3(foamLiumi,foamLiumi,foamLiumi);
outColor.a = 1.;
}
#endif
为水体加入 flowmap 这里简单使用梯度来代替flowmap
地形的纹理使用四层纹理混合,使用法线贴图、高光贴图强化细节。
地形刷子可以使用自定义形状刷子,等高线刷子等。可以设置刷子遮罩。
#if !GLSLSHADER
#include "data/shader/ps_common.h"
#include "data/shader/math.h"
#endif
uniform float BlendingType;
uniform float2 materialNum; //对任意uniform初始化话导致cg+d3d正常,但hlsl+d3d时只有target生效
#if GLSLSHADER
#define inTangent gl_SecondaryColor//[1]
#define inDepth gl_TexCoord[1]
#define inNormal gl_TexCoord[2]
#define inPosW gl_TexCoord[3]
//#define gl_FragColor gl_FragData[0] //glDrawBuffers输出的数据数组。不能与gl_FragColor结合使用。
#define outColor_ gl_FragData[0]
#define geometryColor1 gl_FragData[1]
#define geometryColor2 gl_FragData[2]
#endif
#define texTitles baseTexture
uniform sampler2D texBlendMap _TEX1;
uniform sampler2D texNormalMap _TEX2;
uniform sampler2D texMaterialMap _TEX3;
uniform float2 worldSize;// = float2(2000, 2000);
/*
0 4 8 12
1 5 9 13
2 6 10 14
3 7 11 15
*/
vec2 cornerUV(int corner,vec2 uv)
{
uv = (fract(uv)*0.498+0.001);//[0~1]=>[0~.5] 256
//uv += vec2(fmod(corner,2.),floor(corner/2.))*0.5;
uv += vec2(mod(corner,2),(corner/2))*0.5;
return uv;
}
float noise(in vec2 uv)
{
return sin(uv.x)+cos(uv.y);
}
float terrain(in vec2 uv)
{
int octaves = 7;
float height = 0.;
float amplitude = 2./3.;
float freq = .5;
float n1 = 0.;
for (int i = 0; i < octaves; i++)
{
uv += vec2(amplitude,freq);
n1 = (noise((uv) * freq)-n1*height);
height += n1 * amplitude;
freq *= 2.1-amplitude;
amplitude *= 1./3.;
uv = uv.yx-n1/freq;
}
return height;
}
vec2 map(vec3 p, int octaves) {
float d;
float mID = -1.0;
float h = terrain(p.xz);
d = p.y - h;
return vec2(d, mID);
}
vec3 calcNormal(vec3 p,vec3 inNormal)
{
int octaves = 7;
p*=0.3;
const vec3 eps = vec3(0.002, 0.0, 0.0);
return normalize( vec3(map(p+eps.xyy, octaves).x - map(p-eps.xyy, octaves).x,
map(p+eps.yxy, octaves).x - map(p-eps.yxy, octaves).x,//2. * eps.x
map(p+eps.yyx, octaves).x - map(p-eps.yyx, octaves).x) );
}
float caclAO(vec3 p,vec3 inNormal)
{
int octaves = 7;
p*=0.3;
const vec3 eps = vec3(0.002, 0.0, 0.0);
float h = map(p, octaves).x;
float4 dif = float4( map(p+eps.xyy, octaves).x-h,
map(p-eps.xyy, octaves).x-h,
map(p+eps.yyx, octaves).x-h,
map(p-eps.yyx, octaves).x-h);
dif *= 300;
dif = clamp(dif,0.,0.3);
float ao = dot( dif,float4(1.,1.,1.,1.));
ao = clamp(ao,0.,0.5);
return ao;
}
#ifdef ps_multarget_terrain
PSMAIN ps_multarget_terrain(
#if !GLSLSHADER
//in float4 position : POSITION, //无法取得,dx11 可以取得SV_POSITION表示像素位置,坐标为视口大小
in float4 inColor : COLOR0,
in float3 inTangent : COLOR1,
in float2 inTexCoord : TEXCOORD0,
in float2 inDepth : TEXCOORD1,
in float3 inNormal : TEXCOORD2,
in float3 inPosW : TEXCOORD3,
out float4 outColor_ : COLOR0,
out float4 geometryColor1 : COLOR1,
out float4 geometryColor2 : COLOR2
#endif
)
{
float2 inTexCoord_ = mul(matTexture,float4(inTexCoord.xy,1,1)).xy;
//Shader Model 3.0 不支持纹理数组 , 0~4号纹理拼在tile0中
//防止垂直的部分纹理拉伸
float3 normalSq = inNormal.xyz*inNormal.xyz;
float3 inPosWWrap = inPosW.xyz*16.0/worldSize.x; //16重
//默认2层 平层+陡峭层 或按高度分三层 都可以在编辑器搞定,这里只需混合
outColor_.rgb = vec3(0.,0.,0.);
float4 blendColor = tex2D(texBlendMap,inTexCoord_);
float3 outNormal = inNormal.xyz;
float3 normalColor = float3(0.,0.,0.);
float4 materialColor = float4(0.,0.,0.,0.);
//叠加4层 0~16号纹理拼在tile0中
float4 color[3];
float weightSum = 0.;
for(int id=0;id<4;id++)
{
float weight = blendColor.r;
if(weight>0.)
{
//if(dot(inNormal.xyz,vec3(0.,1.,0.))<0.999)
//{
// //垂直面
// vec2 coord1 = cornerUV(id,inPosWWrap.xy);
// vec2 coord2 = cornerUV(id,inPosWWrap.yz);
// vec2 coord3 = cornerUV(id,inPosWWrap.zx);
// color[0] = tex2D(texTitles,coord1)*normalSq.z;
// color[1] = tex2D(texTitles,coord2)*normalSq.x;
// color[2] = tex2D(texTitles,coord3)*normalSq.y;
// outColor_.rgb += (color[0]+color[1]+color[2]).rgb * weight;
// color[0] = tex2D(texNormalMap,coord1)*normalSq.z;
// color[1] = tex2D(texNormalMap,coord2)*normalSq.x;
// color[2] = tex2D(texNormalMap,coord3)*normalSq.y;
// normalColor.rgb += (color[0]+color[1]+color[2]).rgb * weight;
// materialColor.rgba += tex2D(texMaterialMap ,coord3).rgba * weight;
//}
//else
{
vec2 coord = cornerUV(id,inPosWWrap.xz);
//color[0] = tex2D(texTitles,cornerUV(id,inPosWWrap.xy))*normalSq.z;
//color[1] = tex2D(texTitles,cornerUV(id,inPosWWrap.yz))*normalSq.x;
//color[2] = tex2D(texTitles,cornerUV(id,inPosWWrap.zx))*normalSq.y;
//outColor_.rgb += (color[0]+color[1]+color[2]).rgb * weight;
outColor_.rgb += tex2D(texTitles ,coord).rgb * weight;
normalColor.rgb += tex2D(texNormalMap ,coord).rgb * weight;
materialColor.rgba += tex2D(texMaterialMap ,coord).rgba * weight;
}
weightSum += weight;
}
blendColor.rgba = blendColor.gbar;
}
//此处不需要细节纹理,细节纹理要采用不同的wrap比例才有效果
if(weightSum!=0) //weightSum可能<1
{
normalColor /= weightSum;
materialColor /= weightSum;
}
//如果不做TBN矩阵变换,则只有模型面正好面向z正时法线显示正确
normalColor = normalize(normalColor * 2.0 - 1.0);
vec3 tangent = inTangent.xyz;//vec3(1,0,0);
vec3 bitangent = cross(tangent,inNormal.xyz);
//mat3 TBN = mat3(1,0,0, 0,0,1, 0,1,0);
mat3 TBN = mat3(tangent, bitangent, inNormal.xyz);
TBN = transpose(TBN);
outNormal.xyz = mul( TBN, normalColor).xyz;
//{
// //模拟层页岩纹理 xy + zy 采样两次纹理图后混合 (只靠法线贴图不行,需要ao光照图)
// outNormal.y += pow((sin(inPosW.y*10)-1)*0.2,0.5);
// outNormal = calcNormal(inPosW.xyz,outNormal);
outColor_.rgb *= (1.-caclAO(inPosW.xyz,outNormal));
//}
outNormal.xyz = normalize( outNormal.xyz);//必须 否则噪点
//{
// //模拟积雪纹理 下雪的方向 阳光融化方向 凹凸性 https://www.shadertoy.com/view/MlGBD1 https://www.shadertoy.com/view/lsKGW3
// //雪花纹理https://www.shadertoy.com/view/Xsd3zf
// //float snowHeight = step(0.7,outNormal.y);
// float snowHeight = smoothstep(0.7,1.0,outNormal.y);
// outColor_.rgb += snowHeight;
//}
outColor_.a = 1.;
//if(BlendingType==0)//Filter
{
//clip之后的顶点坐标(x, y, z, w),在OpenGL顶点经过viewport变换写入深缓的z是(z/w + 1) / 2,D3D上是z/w
float depthvalue = inDepth.x/inDepth.y; //[0, 1]
//不允许 target1、2单独设置混合模式
//ab: am di
//rg: sp lv
float2 materialNum_;
materialNum_.x = EncodeFloat2Color1_RGBA32(materialColor.ab); //am di
materialNum_.y = EncodeFloat2Color1_RGBA32(materialColor.rg); //sp lv
//gbuffer parse
geometryColor1 = float4(depthvalue,materialNum_.x,materialNum_.y,1);
geometryColor2 = float4(EncodeNormal(outNormal),0,1);
//简单处理 alpha为0 不影响深度 alpha不为0混合影响深度 todo 最后单独绘制一般?
geometryColor1.a = outColor_.a;
geometryColor2.a = outColor_.a;
}
}
#endif