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;//升高一半平面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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

全网最新最全的自动化测试教程:python+pytest接口自动化-requests发送post请求

简介 在HTTP协议中&#xff0c;与get请求把请求参数直接放在url中不同&#xff0c;post请求的请求数据需通过消息主体(request body)中传递。 且协议中并没有规定post请求的请求数据必须使用什么样的编码方式&#xff0c;所以其请求数据可以有不同的编码方式&#xff0c;服务…

初试占比7成!只考一门数据结构+学硕复录比1:1的神仙学校,大连交通大学考情分析

大连工业大学 考研难度&#xff08;☆&#xff09; 内容&#xff1a;23考情概况&#xff08;拟录取和复试分析&#xff09;、院校概况、24专业目录、23复试详情、各专业考情分析、各科目考情分析。 正文1014字&#xff0c;预计阅读&#xff1a;3分钟 2023考情概况 大连工业…

SpringCloud笔记

一、SpringCloud初阶篇 1、从面试题开始 1.1什么是微服务&#xff1f; 1.2微服务之间是如何独立通讯的&#xff1f; 1.3SpringCloud和Dubbo有哪些区别&#xff1f; 1.4通信机制&#xff1a;Dubbo是通过RPC远程过程调用&#xff0c;微服务Cloud是基于rest调用 1.5SpringBo…

【vue】vue-slick-carousel插件,实现横向滚动列表手动左右滚动(也可设置为自动滚动)

需求&#xff1a;图片列表横向滚动的时候&#xff0c;隐藏原始滚动条&#xff0c;通过左右箭头控制滚动条往左右按一定的步长移动。 el-carousel走马灯一滚动就是一屏&#xff0c;不适合我的需求 在npm官网搜vue-slick-carousel&#xff0c;查看更详细的配置 vue-slick-caro…

GO基础之运算符

运算符 Go 语言内置的运算符有&#xff1a; 1.算术运算符 2.关系运算符 3.逻辑运算符 4.位运算符 5.赋值运算符 算术运算符 注意&#xff1a; &#xff08;自增&#xff09;和–&#xff08;自减&#xff09;在Go语言中是单独的语句&#xff0c;并不是运算符。 关系运算符 …

Pico VR眼镜(XR) Unity开发环境部署及打包教程

创建项目 我这里选择的是URP项目。URP对移动端性能比较友好&#xff0c;另外VR平台也不支持HDRP渲染管线。 然后进入unity工具栏->File -> Build Settings 点击 Android后&#xff0c;点就Switch Platform将项目转为Android项目 安装依赖包 在unity的工具栏中点击Wi…

12.4作业

#include <iostream>using namespace std;class Sofa { private:string sit;int *nub; public:Sofa(){cout << "Sofa::无参构造函数" << endl;}Sofa(string sit,int nub):sit(sit),nub(new int(nub)){cout << "Sofa::有参构造函数"…

前缀和例题:子矩阵的和AcWing796-Java版

//前缀和模板提,在读入数据的时候就可以先算好前缀和的大小 //计算前缀的时候用:g[i][j] g[i][j-1] g[i-1][j] - g[i-1][j-1] Integer.parseInt(init[j-1]); //计算结果的时候用:g[x2][y2] - g[x1 - 1][y2]- g[x2][y1-1] g[x1 -1][y1 - 1] "\n" //一些重复加的地…

拼多多股价为什么可以创下两年新高并一举超越阿里巴巴?

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 拼多多再次证明了它是全球电商领域中不可忽视的力量 过去两年&#xff0c;由于某些众所周知的原因&#xff0c;很多中概股的股价都很疲软&#xff0c;甚至半死不活的&#xff0c;很多投资中概股的朋友也一直承受着很大的…

浅谈Django之单元测试

一、什么是单元测试 单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。如果测试通过则说明我们这个函数或功能能够正常工作&#xff0c;如果失败要么测试用例不正确&#xff0c;要么函数有bug需要修复。 二、如何使用单元测试 from django.test imp…

练习十二:利用SRAM设计一个FIFO

利用SRAM设计一个FIFO 1&#xff0c;任务目的2&#xff0c;设计要求3&#xff0c;FIFO接口的设计思路4&#xff0c;FIFO接口的测试&#xff0c;top.v5&#xff0c;FIFO接口的参考设计&#xff0c;fifo_interface.v6&#xff0c;SRAM模型&#xff0c;sram.v代码7&#xff0c;viv…

Linux下快速创建大文件的4种方法

1、使用 dd 命令创建大文件 dd 命令用于复制和转换文件&#xff0c;它最常见的用途是创建实时 Linux USB。dd 命令是实际写入硬盘&#xff0c;文件产生的速度取决于硬盘的读写速度&#xff0c;根据文件的大小&#xff0c;该命令将需要一些时间才能完成。 假设我们要创建一个名…

智能优化算法应用:基于未来搜索算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于未来搜索算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于未来搜索算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.未来搜索算法4.实验参数设定5.算法结果6.参考…

提升--21---JMM(Java内存模型)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 JMM--Java Memory ModelJMM 定义JMM规则&#xff1a;线程间通信的步骤&#xff1a; JMM的三大特性&#xff1a;原子性&#xff08;Atomicity&#xff09;可见性&…

微软 Power Platform 零基础 Power Pages 网页搭建实际案例实践(三)

微软 Power Platform 零基础 Power Pages 网页搭建教程之案例实践学习&#xff08;三&#xff09;结合Power Apps和Power Automate Power Pages 实际案例学习 微软 Power Platform 零基础 Power Pages 网页搭建教程之案例实践学习&#xff08;三&#xff09;结合Power Apps和Po…

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

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

Python入门07循环及常见的数据结构

目录 1 循环的语法结构2 break和continue的示例3 可迭代对象4 列表5 元组6 列表和元组的应用场景7 集合8 字典9 生成器 1 循环的语法结构 在Python中&#xff0c;循环是一种控制结构&#xff0c;用于重复执行一段代码&#xff0c;直到满足特定条件。Python中有两种循环结构&am…

Spring切面编程

切面编程 代码地址&#xff1a; orbit-hub/spring-boot-samples at master (github.com)https://github.com/orbit-hub/spring-boot-samples/tree/master 切面执行顺序 Spring5以后顺序就一切正常 正常&#xff1a;前置通知目标方法返回通知后置通知 异常: 前置通知目标方…

C++12.4

沙发床的多继承 多继承代码实现沙发床沙发床继承于沙发和床 代码&#xff1a; #include <iostream>using namespace std;//封装 沙发 类 class Sofa { private:string sitting;double *size; public://无参构造函数Sofa() {cout << "Sofa::无参构造函数&quo…

京东运营数据分析(京东数据采集):2023年10月京东护肤行业品牌销售排行榜

鲸参谋监测的京东平台10月份护肤市场销售数据已出炉&#xff01; 鲸参谋数据显示&#xff0c;2023年10月份&#xff0c;京东平台上护肤市场的销量为2000万&#xff0c;环比增长约28%&#xff0c;同比降低约26%&#xff1b;销售额为25亿&#xff0c;环比增长约24%&#xff0c;同…