博客文章

shader生成水波纹特效

作者: andy.      时间: 2019-06-16 22:10:45

    好久没有写博文了,两年多了吧。因为工作确实有点儿太忙了....

    这次的文章可能依旧写得比较水,但是内容都还算是干货。如何生成一个投石头的水波纹。

    代码放在了我的GitHub上面:https://github.com/andyzhangyb/OpenGLEffects/tree/master/WaterStreak

    完成后的效果长这个样子:

    先说说大概思路:

        1、生成一个不平整的水面,获得水面每个位置的高度。因为石头投在水里面后会有一圈一圈的波峰和波谷。

        2、渲染水面,通过第一步,我们可以拿到点的高度,就可以求得该点的法向量。也就可以计算得到光线通过水面折射后应该采样的点。

        3、根据时间和距离的衰减,水波向外扩散并逐渐减弱,最后消失。

    要实现上面说的效果,先进行一次渲染,计算水面每个点的高度,存入FrameBuffer,然后在渲染水面的时候把水面的高度传进去就行了。

    计算水面高度,加入简单的时间和距离衰减:

#version 330 core
out vec4 FragColor;

in vec2 position;
in vec2 texPosition;

layout(std140) uniform ClickPointsBlock
{
    float currentTime;
    vec3 points[256];
};

void main() {
    float waveHight = 0;
    float frequency = 80;
    float speed = 3.14 / 10;
    
    float dbDistance = 0.04;
    float dbOut = 0.3;
    float dbIn = 0.1;
    
    for (int i = 0; i < 256; i++) {
        vec3 point = points[i];
        if (point.x == 0.f && point.y == 0.f && point.z == 0.f)
            break;
        float timeDiff = currentTime - point.z;
        float distanceOfClick = distance(position, point.xy);
        float waveRadius = timeDiff * speed;
        
        float v = cos(frequency * (distanceOfClick - waveRadius)) / 50 + 1 / 50;
        if (v < 0)
            v = -v;
        if (distanceOfClick > waveRadius) { // 圈外
            v = v - (distanceOfClick - waveRadius) * dbOut - dbDistance * waveRadius;
        } else { //圈内
            v = v - (waveRadius - distanceOfClick) * dbIn - dbDistance * waveRadius;
        }
        if (v < 1 / 50)
            v = 1 / 50;
        
        waveHight += v;
    }
    FragColor = vec4(texPosition.xy, waveHight, 1.f);
}

最后渲染水面的shader:

#version 330 core
out vec4 FragColor;

in vec3 ourColor;
in vec2 texCoord;

uniform sampler2D textureBg;
uniform sampler2D textureNormal;

void main() {
    vec3 centerPoint = texture(textureNormal, texCoord).xyz;
    vec3 left = texture(textureNormal, texCoord - vec2(1.f / 100.0, 0)).xyz;
    vec3 right = texture(textureNormal, texCoord + vec2(1.f / 100.0, 0)).xyz;
    vec3 up = texture(textureNormal, texCoord + vec2(0, 1.f / 100.0)).xyz;
    vec3 down = texture(textureNormal, texCoord - vec2(0, 1.f / 100.0)).xyz;
    vec3 normal = normalize(cross(left - right, down - up));
    vec3 R = refract(vec3(0, 0, -1), normal, 1.333);

    FragColor = texture(textureBg, texCoord.xy + R.xy);
}

贴几个有意思的地方的代码:

1、创建一个UniformBuffer,不直接通过定义Uniform来传值,毕竟Uniform的数量是有数量限制的。

	unsigned int uboSize = MaxPointsCount * 16 + 16;
	glGenBuffers(1, &uboPoints);
	glBindBuffer(GL_UNIFORM_BUFFER, uboPoints);
	glBufferData(GL_UNIFORM_BUFFER, uboSize, NULL, GL_STATIC_DRAW);
	glBindBuffer(GL_UNIFORM_BUFFER, 0);
	glBindBufferRange(GL_UNIFORM_BUFFER, 0, uboPoints, 0, uboSize);

	unsigned int uniformBlockIndex = glGetUniformBlockIndex(shaderStreak.ProgramID, "ClickPointsBlock");
	glUniformBlockBinding(shaderStreak.ProgramID, uniformBlockIndex, 0);

2、对UniformBuffer进行赋值。

void copyUniformToGPU() {
    float time = glfwGetTime();
    glBindBuffer(GL_UNIFORM_BUFFER, uboPoints);
    glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(float), &time);
    for (int i = 0; i < points.size(); i++) {
        if (i >= MaxPointsCount)
            break;
        int offset = i * 16 + 16;
        if (points[i].x == 0 && points[i].y == 0 && points[i].z == 0) {
            glBufferSubData(GL_UNIFORM_BUFFER, offset, sizeof(glm::vec3), glm::value_ptr(points[i]));
            break;
        } else
            glBufferSubData(GL_UNIFORM_BUFFER, offset, sizeof(glm::vec3), glm::value_ptr(points[i]));
    }
    glBindBuffer(GL_UNIFORM_BUFFER, 0);
}

    因为整体比较简单,后面没有做太多的阐述,如果有问题的话,直接Email就行。因为用的第三方留言板好像挂了很久了。