[OpenGL] 动态积雪效果

默认
动态生成积雪

 

        对于游戏中模拟雪天而言,除了天上飘落的雪花外,我们通常还希望能够在一个现成的场景地图上,生成场景物体被积雪覆盖的效果。让美术为每个场景物件单独做雪效果的材质可以得到比较细腻的效果,但这会成倍地增加美术制作成本。一个比较的常见的思路是,直接将雪的贴图从上往下“叠加”到场景物体上,通过这样的方式,就能快速地让场景中的物体都被雪覆盖。

        

实现思路

        我们认为雪是从天上落下的,不考虑风向的话,可利用物体表面的法线和向上向量(vector(0,1,0))的余弦值,如果大于0,则表面被雪覆盖,且余弦值越大,积雪程度越大。根据积雪程度,我们混合原纹理和雪纹理。

       由于直接使用余弦值作为积雪程度会使得整体雪过于稀疏,我们可以对其做一个简单的映射(如果想要得到雪更密的效果,可以调小param的值或者使用别的映射函数)

float param = 0.3;
float snowRatio = max(dot(normal,vec3(0,1,0)),0);
snowRatio = snowRatio/(snowRatio + param);

       以下是使用映射min(snowRatio * 1.2 /(snowRatio + 0.3),1),不添加法线贴图得到的球体积雪效果:

       

       得到了积雪程度后,我们开始考虑从雪纹理中采样,因此我们需要确认采样纹理的uv坐标。根据一开始的设想,我们是从(y轴方向)上到下向整个场景投影了一张雪的纹理,也就相当于,我们可以直接用物体的世界坐标x和z分量作为uv坐标。该过程适用于前向渲染和延迟渲染,但对于前者,相当于要在每个场景对象的着色器最后加入积雪采样的部分,着色器很多时编写和维护比较麻烦。因此,此处是在延迟渲染的屏幕空间完成这一操作的。

        此处根据自己的需要对纹理坐标进行缩放,采样雪的颜色和法线纹理:

float scale = 40;
vec3 snowColor = vec3(texture2D(Snow_D, vec2(worldPos.x,worldPos.z)/scale));
vec3 snowNormal = UnpackNormal(vec3(texture2D(Snow_N, vec2(worldPos.x,worldPos.z)/scale)));

        特别要注意的是法线的解码部分,在延迟渲染中,我们通常只有解码后的物体法线信息,而没有存储TBN矩阵中的N、T等信息,而由于雪法线纹理的特殊性(我们可以粗略认为它的N就是沿着y轴向上的),我们还是可以构造出TBN矩阵:

vec3 UnpackNormal(vec3 n)
{
    vec3 N = vec3(0,1,0);
    vec3 T = vec3(1,0,0);
    vec3 B = cross(N, T);//vec3(0,0,1);
    mat3 TBN = mat3(T,B,N);
    n = normalize(2 * n - 1);
    n = normalize(TBN * n);
    return n;
}

       之后根据比例对原颜色和积雪颜色,原法线和积雪法线进行混合(此处混合的法线是世界空间的法线,不确定是否能够这么直接做线性插值,但看起来表现没什么问题):

albedo = mix(albedo,snowColor,snowRatio);
normal = mix(normal,snowNormal,snowRatio);

      现在我们已经基本得到积雪的效果了。但如果我们的场景再丰富一些的话,比如有一些亭子之类的物件,我们会发现漏雪现象——被屋顶遮挡的地面也出现了大片的积雪。

      此处的修改想法是,先预处理一张从上往下视角观察的深度图(正交投影即可),然后在计算像素的积雪填充颜色时,比较物体深度和采样得到的深度大小,如果比较相近,说明是积雪覆盖的区域,否则被遮挡。

      正交投影的上下左右参数可依据场景在x,z上的范围来决定,远近裁剪面由场景的高度范围决定。

      此外,还有一种思路是,动态的根据视锥体计算正交投影的范围,获取其AABB包围盒(直接求得视锥体的8个点,然后取每个分量的最小值和最大值)

       

      我们需要构造两个矩阵。一个是积雪方向观察的视图矩阵,lookAt方向沿y轴向下;另一个是我们刚才提到的正交投影矩阵。

      之后,我们根据这两个矩阵写入积雪的深度:


#version 450 core

uniform mat4 OrthoMatrix;
uniform mat4 SnowMatrix;
uniform mat4 ModelMatrix;

attribute vec4 a_position;

varying vec2 v_depth;

void main()
{
    gl_Position = ModelMatrix * a_position;
    gl_Position = SnowMatrix * gl_Position;
    gl_Position = OrthoMatrix * gl_Position;

    v_depth = gl_Position.zw;
}
#version 450 core

varying vec2 v_depth;

void main()
{
    float fColor =  (v_depth.x/v_depth.y + 1)/2;
    gl_FragColor.x = fColor;
}

        然后在屏幕空间做深度比较,对得到的结果生成一个积雪比例,并与之前的积雪比例相乘,得到最终的积雪比例。此处从相邻的深度采样做混合,得到比较平滑的积雪边缘过渡效果:

float GetSnowCoverRatio(vec4 worldPos)
{
    vec4 snowPos = OrthoMatrix * (SnowMatrix * worldPos);
    float fDistance = (snowPos.z + 1)/2;
    float fSnow = 0;
    vec2 uv = snowPos.xy / snowPos.w * 0.5 + vec2(0.5, 0.5);

    uv.x = clamp(uv.x, 0, 1);
    uv.y = clamp(uv.y, 0, 1);

    for(int i = -1; i <= 1; i++)
    {
        for(int j = -1; j <= 1; j++)
        {
            float fDistanceMap = texture2D(SnowDepth, uv + vec2(5.0 * i / ScreenX, 5.0 * j / ScreenY)).x;
            fSnow += fDistance - 0.001 > fDistanceMap ? 0 : 1.0;
        }
    }
    fSnow /= 9;
    return fSnow;
}
if(bSnow)
{
    float coverRatio = GetSnowCoverRatio(vec4(worldPos,1));
    float snowRatio = max(dot(normal,vec3(0,1,0)),0);
    // ...
    snowRatio *= coverRatio;
    // ...
}

 

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
OpenGL自发光效果是一种在渲染图形时给物体表面添加自然光亮效果的技术。它模拟了物体表面受光照激发后发出自己光亮的效果。 在OpenGL中实现自发光效果的关键是使用光照模型和材质属性。首先,我们需要设置光源的位置和光照强度。常见的光照模型有环境光、漫反射和镜面反射。然后,我们还需设置物体的材质属性,例如反射率(ambient、diffuse和specular)和发光颜色。 在渲染物体时,我们需要使用光照模型计算出每个像素的亮度。通过在顶点着色器中计算每个顶点的法向量,并通过插值在片段着色器中获取每个片段的法向量,我们可以使用光照模型计算出每个片段的亮度。然后,我们将物体的材质属性与光照模型结果相乘,得到片段的最终颜色。 为了实现自发光效果,我们可以在片段着色器中添加额外的操作。例如,我们可以通过在计算最终颜色时添加一个自发光因子,将物体发出的光与表面反射的光相叠加。这样,物体就会具有自发光的效果了。 另外,我们还可以使用纹理贴图来增强自发光效果。通过在纹理贴图中使用较亮的颜色和高亮部分,我们可以让物体的发光部分更加明亮醒目。 总之,OpenGL自发光效果通过使用光照模型和材质属性来模拟物体表面的自然光亮效果。通过在片段着色器中添加额外的操作,如自发光因子和纹理贴图,我们可以实现更加逼真的自发光效果
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值