Rpg游戏地形生成

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/216531.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue2.0与Vue3.0的区别

一、Vue2和Vue3的数据双向绑定原理发生了改变 Vue2的双向数据绑定是利用ES5的一个API&#xff0c;Object.definePropert()对数据进行劫持 结合 发布 订阅模式的方式来实现的。通过Object.defineProperty来劫持数据的setter&#xff0c;getter&#xff0c;在数据变动时发布消息…

如何写好亚马逊listing页面?助你提高转化率!(上)

亚马逊listing页面就是商品介绍页&#xff0c;我们可以将listing分成这几个模块&#xff1a;图片、标题、五点描述、产品描述、后台关键词、Q&A、产品评论。本篇讲解如何写好图片、标题以及五点描述这三个模块。 一、图片 亚马逊详情页一般可以放入7张图片&#xff0c;1张…

三季度付费用户持续增加,知乎的“吸引力法则”是什么?

在过去的12年里&#xff0c;知乎一直是一个问答社区&#xff0c;通过“一问多答”形成了可以进行专业讨论的社区氛围&#xff0c;并累计完成了上亿次这样的专业讨论&#xff0c;同时还借助平台一问多答的形式打造了网文社区&#xff0c;依托于平台专业职场人的资源池打造了职业…

练习十一:简单卷积器的设计

简单卷积器的设计 1&#xff0c;任务目的&#xff1a;2&#xff0c;明确设计任务2.1,目前这部分代码两个文件没找到&#xff0c;见第5、6节&#xff0c;待解决中。 &#xff0c;卷积器的设计&#xff0c;RTL&#xff1a;con1.v4&#xff0c;前仿真和后仿真&#xff0c;测试信号…

Tubulysin C 微管蛋白C 205304-88-7

Tubulysin C 微管蛋白C 205304-88-7 英文名称&#xff1a;Tubulysin C 中文名称&#xff1a;微管蛋白C 化学名称&#xff1a;(2S,4R)-4-[[2-[(1R,3R)-1-乙酰氧基-4-甲基-3-[[(2S,3S)-3-甲基-2-[[(2R)-1 -甲基哌啶-2-羰基]氨基]戊酰基]-(丙酰氧基甲基)氨基]戊基]-1,3-噻唑-4-羰基…

【南昌大学主办、往届均已实现EI、Scopus双检索】第三届电子信息工程与数据处理国际学术会议(EIEDP 2024)

第三届电子信息工程与数据处理国际学术会议&#xff08;EIEDP 2024&#xff09; 2024 3rd International Conference on Electronic Information Engineering and Data Processing 第三届电子信息工程与数据处理国际学术会议&#xff08;EIEDP 2024&#xff09;将于2024年3月1…

【开发问题解决方法记录】03.dian

登录提示 ERR-1002 在应用程序 "304" 中未找到项 "ROLE_ID" 的项 ID。 一开始找错方向了&#xff0c;以为是代码错误&#xff0c;但是后来在蒋老师的提醒下在共享组件-应用程序项 中发现设的项不是ROLE_ID而是ROLEID&#xff0c;怪不得找不到ORZ 解决方法…

夜莺专业版网络设备功能介绍

网络设备采集简介 网络设备的问题通常会产生较大范围的影响&#xff0c;因此采集监控网络设备是一项常见的任务。不同公司在实施网络设备采集时可能采用不同的方案&#xff0c;主要有三类&#xff1a; SNMP&#xff08;Simple Network Management Protocol&#xff09;&#x…

如何使用gdb调试fork程序

代码示例 #include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<stdlib.h> #include<sys/wait.h>int main(int argc, const char* argv[]) {pid_t pid -1;int status 0;int ret -1; // 创建子进程。若创建成功&#xff0c;…

前端项目中CDN的一些问题【性能优化篇】

1. CDN的概念 CDN&#xff08;Content Delivery NetWork&#xff0c;内容分发网络&#xff09;&#xff0c;是指利用最靠近每位用户的服务区&#xff0c;更快的将资源发送给用户。 提高用户的访问速度减轻服务器压力提高网站的稳定性和安全性 2. CDN的作用 CDN一般用来托管…

java--抽象类的常见应用场景:模板方法设计模式

1.模板方法设计模式解决了什么问题&#xff1f; ①解决方法中存在重复代码的问题。 2.模板方法设计模式的写法 1、定义一个抽象类。 2、在里面定义2个方法 ①一个是模板方法&#xff1a;把相同代码放里面去。 ②一个是抽象方法&#xff1a;具体实现交给子类完成。 分析&…

【有机化学(药学类)】醛和酮3

第一题 思考 格氏试剂与不饱和醛酮的亲核加成反应&#xff0c;主要发生1,2加成&#xff08;注意&#xff1a;氧原子算是1&#xff09; 第二题 思考 叶立德反应&#xff0c;看到磷原子就应该想到这个&#xff01; 第三题 思考 涉及到两个反应&#xff1a; 亲核加成反应&…

操作系统·设备管理

I/O系统是计算机系统的重要组成部分&#xff0c;是OS中最复杂且与硬件密切相关的部分 I/O系统的基本任务是完成用户提出的I/O请求&#xff0c;提高I/O速率以及改善I/O设备的利用率&#xff0c;方便高层进程对IO设备的使用 I/O系统包括用于实现信息输入、输出和存储功能的设备和…

使用Pytorch从零开始实现CLIP

生成式建模知识回顾: [1] 生成式建模概述 [2] Transformer I&#xff0c;Transformer II [3] 变分自编码器 [4] 生成对抗网络&#xff0c;高级生成对抗网络 I&#xff0c;高级生成对抗网络 II [5] 自回归模型 [6] 归一化流模型 [7] 基于能量的模型 [8] 扩散模型 I, 扩散模型 II…

网络协议与 IP 编址

网络协议与 IP 编址 之前大概了解过了网络的一些基础概念&#xff0c;见文章&#xff1a; 网络基础概念。 之前简单了解OSI模型分层&#xff1a; TCP/IP模型OSI模型TCP/IP对等模型应用层应用层表示层应用层会话层主机到主机层传输层传输层因特网层网络层网络层网络接入层数据链…

iOS17苹果备忘录怎么设置提醒?

在我们快节奏的生活中&#xff0c;苹果备忘录成了记录灵感、任务和重要事项的得力助手&#xff0c;面对着一个让人头疼的问题——备忘录竟然不能设置提醒&#xff01;突然感觉我的备忘录只是个寂寞的清单&#xff0c;没有提醒的陪伴。 于是&#xff0c;我着手寻找解决之道&…

前后端验证码分析(字母计算)

样式&#xff1a; 前端&#xff1a; login.vue <template> <view class"normal-login-container"> <view class"login-form-content"> <view class"input-item flex align-center"> <view class"iconfont ic…

【EI会议投稿】第四届物联网与智慧城市国际学术会议(IoTSC 2024)

第四届物联网与智慧城市国际学术会议 2024 4th International Conference on Internet of Things and Smart City 继IoTSC前三届的成功举办&#xff0c;第四届物联网与智慧城市国际学术会议&#xff08;IoTSC 2024&#xff09;将于2024年3月22-24日在河南洛阳举办。 智慧城市的…

新版IDEA中,module模块无法被识别,类全部变成咖啡杯无法被识

新版IDEA中&#xff0c;module模块无法被识别&#xff0c;类全部变成咖啡杯无法被识 如下图&#xff1a; 解决方法&#xff1a;java的Directory文件没有被设置为根目录&#xff0c;解决方法如下&#xff1a; 这是方法之一&#xff0c;还有很多的原因 可能的原因&#xff1a; …

深入了解Java Period类,对时间段的精确控制

阅读建议 嗨&#xff0c;伙计&#xff01;刷到这篇文章咱们就是有缘人&#xff0c;在阅读这篇文章前我有一些建议&#xff1a; 本篇文章大概2900多字&#xff0c;预计阅读时间长需要3分钟。本篇文章的实战性、理论性较强&#xff0c;是一篇质量分数较高的技术干货文章&#x…