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;//升高一半平面iHeightif( ( 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.05float SedimentCapacityFactor;// 沙容量= 4float MinSedimentCapacity; // 沙容量= 0.01float EvaporateSpeed; // 蒸发速度 = 0.01float Gravity; // 重力=4float Resistance; // 阻力=0.1float InitialWater; // 初始雨滴水量= 1float InitialSpeed; // 初始雨滴速度= 1Inertia = 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;//权重之和为1pt->weight[j] = weights[j] / weightSum;}}}const int DropNum = 1+mapSize*mapSize*mount;// 迭代步骤:// 增加水滴。均匀随机分布// 计算地形坡度。确定水滴方向和速度。// 计算泥沙容量。受坡度,水速和水量的影响。// 侵蚀或沉积。 携沙量>容量则沉积,否则侵蚀。// 更新水滴位置下山。// 蒸发。//const int mapSize_2 = ;//快速单水滴独立模拟,没有使用pch流体模拟vec3 heightAndGradient;vec3 newHeightAndGradient;int dropIndex;//posindexvec2 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 40Clamp( divide,1,MaxVoronoi-1);static vec2 points[MaxVoronoi][MaxVoronoi];float vorinoiCellWidth = m_vertexNum.x/(divide-1);//float Up = (vorinoiCellWidth*vorinoiCellWidth)/4;//=200float Up = (vorinoiCellWidth*vorinoiCellWidth);//=200for (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 13 */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 waterwaterHeight += (inflow[0] +inflow[1] +inflow[2] +inflow[3] );waterHeight += RainSpeed; //rainwaterHeight = (waterHeight>0)?waterHeight:0;pixel->waterDepth = waterHeight;pixel->totalHeight = pixel->terrainHeight + waterHeight;pixel++;}}}//DepthCutoffpixel = 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 !GLSLSHADERin 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.); //模型单位大小统一后 可去除//叠加涟漪2float 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 ~100dist = 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 D3DDRIVERtexCoord.y = 1 - texCoord.y;
#endiffloat3 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 FlowMapfloat2 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 FlowMapfloat 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);
#elsefloat2 texCoordN1 = inPosW.xz * 0.005*rendScale.xz + flowDir2*waveTime*0.1235;//+texCoordOffsetfloat2 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.;
#endifvertexNormal.xyz = Bx2(colorNormal.rgb);//TBN1//{// vertexNormal.xyz = vertexNormal.rbg; //}{//TBN2float3 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 FlowMapfloat 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.);
#elsefloat4 noiseColor = tex2D(texWaterNoise, noiseCoord + flowDir2*waveTime);
#endiffloat2 texCoordOffset = (noiseColor.rg - float2(0.3,0.3))* fDistScale *amplitude; // 幅度float2 refrTexCoord = texCoord + texCoordOffset;//<<//折射色方法一: todo根据水的穿透深度调整折射幅度 水的近似垂直深度=vertexPos.y-水平面yfloat4 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)//高光//{//phongfloat3 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 FlowMapfloat 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 );
#elsefloat4 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"
#endifuniform 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 121 5 9 132 6 10 143 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.xmap(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/wfloat depthvalue = inDepth.x/inDepth.y; //[0, 1]//不允许 target1、2单独设置混合模式//ab: am di//rg: sp lvfloat2 materialNum_;materialNum_.x = EncodeFloat2Color1_RGBA32(materialColor.ab); //am dimaterialNum_.y = EncodeFloat2Color1_RGBA32(materialColor.rg); //sp lv//gbuffer parsegeometryColor1 = 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