这里先贴一个我实现的云彩效果,使用Direct3D11,并在pixel shader中使用RayMatching方法实现的,感兴趣的可以下载源代码查看,绘制效果如下:
实现过程主要使用perlin noise来进行云彩建模,然后根据云层厚度和高度决定云彩的颜色,最后使用ray matching算法以及体渲染原理进行颜色的累积。
关于perlin noise的简单介绍可以参考这里;
###云彩绘制部分
由于代码中有很多与云彩绘制无关的部分,所以我这里记录以下云彩绘制部分代码的介绍:
1. 代码实现主要在pixel shader的Get3DCloudColor函数里,大致为:
float3 Get3DCloudColor(float2 xy)
{
return cloudColor;
}
xy为屏幕坐标,xy中的y要提前进行归一化,范围为(-1,1),主要是一般屏幕的宽度都大于高度;
该函数返回屏幕某一像素的颜色;
2. 首先设置相机矩阵
float3 eye = float3(0, 0, -30);
float3 at = float3(0, 20, 0);
matrix View = SetViewMatrix(eye, at);
我这里将相机矩阵计算放在了SetViewMatrix函数里,该函数使用eye、at以及默认沿Y轴向上的up向量来计算。
3. 沿像素投射光线
//set ray
Ray ray;
ray.dir = normalize(float3(xy, 1.732)); //y方向fov为60度
ray.pos = float3(0, 0, 0);
ray.dir = mul(float4(ray.dir, 0.0), View).xyz; //变换射线位置和方向
ray.pos = mul(float4(ray.pos, 1.0), View).xyz;
其中Ray为包含pos、dir两个float3类型的结构体。首先构造出从原点发射的射线,然后使用View矩阵将射线变换至相机位置。
4. 设置云层高度,并测试射线是否与云层相交
//cloud height range: (6, 12)
//采用相交测试,对未相交像素,返回背景色
float2 range = float2(6, 12);
float len = 0;
float3 bgcolor = GetSkyColor(xy, ray.dir, sunDir);
if (abs(ray.dir.y) <= DELTA) //与云层平行
{
return bgcolor;
}
len = (range.x - ray.pos.y) / ray.dir.y;
if (len < 0) //视线远离云层
{
return bgcolor;
}
ray.pos += len *ray.dir; //将射线移动到云层底部,后面在云层内进行步进运算
云层高度为(6,12),若射线远离云层或与云层平行,则返回背景色,即太阳与蓝天的颜色。
5. 使用步进方法进行云层颜色的累积
//在云层内进行步进
float3 sumCol = (float3)0;
float2 mid_width = float2((range.x + range.y) / 2, range.y - range.x);
float t = 1.0;
float transmittance = 1.0f;
for (int i = 0; i < 16; i++)
{
if (ray.pos.y > range.y ) //穿出云层
{
break;
}
else //步进累计颜色
{
ray.pos += ray.dir*t;
float density = GetLocalDensity(ray.pos, mid_width);
float3 localCol = GetLocalLight(ray.pos, density, mid_width, sunCol) * (1.0f - exp(-density*t));
sumCol += localCol * transmittance;
transmittance *= exp(-t*density);
}
}
sumCol += bgcolor*transmittance;
- 由于是从空气到云层密度是有一个渐变的过程的,因此我们对云层的密度进行处理,密度的计算过程在函数
GetLocalDensity(ray.pos, mid_width);
中,大体思路就是采用云层的高度对噪音模型采样的密度进行一个线性控制。 - 整个循环过程就是光线在云层内步进的过程,而云层内的光照计算则采用了简单的体渲染技术,即步进的指数衰减。
- 函数
GetLocalLight(ray.pos, density, mid_width, sunCol)
内部则计算步进点处当地的光照计算。
体渲染的具体技术可以参考这里,这是寒霜引擎将要引人的体渲染技术,里面下载连接里有很多介绍。
6. 将根据距离进行雾效处理
float3 fogColor = float3(1.0, 1.00, 0.94);
float factor = clamp(0, 1, (distance(eye, ray.pos) - 64.0)/ 100.0);
sumCol = lerp(sumCol, fogColor, factor);
这里使用简单的线性雾效进行处理,最后形成最终的像素颜色。