-
-
Notifications
You must be signed in to change notification settings - Fork 29
3D curl noise animation
Romain Milbert edited this page Aug 28, 2023
·
10 revisions
As the time of writing (2022-12-08, commit b3646d3), the following files give the result shown in this video:
Please note that this program uses compute shaders; to run it, you need to have a GPU supporting at least OpenGL 4.3 or the GL_ARB_compute_shader
extension.
#include <RaZ/Application.hpp>
#include <RaZ/Data/Mesh.hpp>
#include <RaZ/Math/Transform.hpp>
#include <RaZ/Render/Camera.hpp>
#include <RaZ/Render/Material.hpp>
#include <RaZ/Render/MeshRenderer.hpp>
#include <RaZ/Render/RenderSystem.hpp>
using namespace Raz::Literals;
using namespace std::literals;
static constexpr int textureSize = 128;
static constexpr int textureDepth = 128;
int main() {
Raz::Application app;
Raz::World& world = app.addWorld(2);
auto& render = world.addSystem<Raz::RenderSystem>(1280, 720, "RaZ");
if (!Raz::Renderer::checkVersion(4, 3) && !Raz::Renderer::isExtensionSupported("GL_ARB_compute_shader")) {
throw std::runtime_error("Error: Compute is only available with an OpenGL 4.3 context or above, or with the 'GL_ARB_compute_shader' extension; "
"please update your graphics drivers or try on another computer");
}
Raz::Window& window = render.getWindow();
Raz::Entity& camera = world.addEntityWithComponent<Raz::Transform>(Raz::Vec3f(-175.f, -200.f, -175.f));
camera.addComponent<Raz::Camera>(window.getWidth(), window.getHeight(), Raz::Degrees(45.f), 0.1f, 100'000.f).setCameraType(Raz::CameraType::LOOK_AT);
window.addKeyCallback(Raz::Keyboard::ESCAPE, [&app] (float) noexcept { app.quit(); });
window.setCloseCallback([&app] () noexcept { app.quit(); });
const auto perlinTexture = Raz::Texture3D::create(textureSize, textureSize, textureDepth, Raz::TextureColorspace::GRAY, Raz::TextureDataType::FLOAT16);
Raz::Renderer::bindImageTexture(0, perlinTexture->getIndex(), 0, true, 0, Raz::ImageAccess::READ_WRITE, Raz::ImageInternalFormat::R16F);
const auto curlTexture = Raz::Texture3D::create(textureSize, textureSize, textureDepth, Raz::TextureColorspace::RGBA, Raz::TextureDataType::FLOAT16);
Raz::Renderer::bindImageTexture(1, curlTexture->getIndex(), 0, true, 0, Raz::ImageAccess::WRITE, Raz::ImageInternalFormat::RGBA16F);
const auto particlesPos = Raz::Texture3D::create(textureSize, textureSize, textureDepth, Raz::TextureColorspace::RGBA, Raz::TextureDataType::FLOAT16);
Raz::Renderer::bindImageTexture(2, particlesPos->getIndex(), 0, true, 0, Raz::ImageAccess::READ_WRITE, Raz::ImageInternalFormat::RGBA16F);
Raz::ComputeShaderProgram perlinNoise(Raz::ComputeShader("perlin_noise_3d.comp"));
perlinNoise.use();
perlinNoise.sendUniform("uniNoiseFactor", 0.05f); // 0.1f gives an even more impressive result
perlinNoise.execute(textureSize, textureSize, textureDepth);
Raz::ComputeShaderProgram curlNoise(Raz::ComputeShader("curl_noise_3d.comp"));
curlNoise.execute(textureSize, textureSize, textureDepth);
Raz::ComputeShaderProgram particlesUpdate(Raz::ComputeShader::loadFromSource(R"(
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(rgba16f, binding = 1) uniform readonly image3D uniCurlNoiseMap;
layout(rgba16f, binding = 2) uniform image3D uniParticlesPos;
void main() {
ivec3 pixelCoords = ivec3(gl_GlobalInvocationID.xyz);
ivec3 imgDims = imageSize(uniParticlesPos);
vec3 currentPos = imageLoad(uniParticlesPos, pixelCoords).xyz;
// Assigning the particles' initial positions
if (currentPos == vec3(0.0))
currentPos = vec3(pixelCoords) - vec3(imgDims) / 2.0;
ivec3 posCoords = ivec3(currentPos * 0.5 + vec3(imgDims) / 2.0);
vec3 curlNoise = imageLoad(uniCurlNoiseMap, posCoords).xyz;
imageStore(uniParticlesPos, pixelCoords, vec4(currentPos + curlNoise * 3.0, 1.0));
}
)"sv));
Raz::Mesh mesh;
mesh.addSubmesh().getVertices().resize(textureSize * textureSize * textureDepth);
Raz::Entity& particles = world.addEntity();
auto& particlesTrans = particles.addComponent<Raz::Transform>();
Raz::RenderShaderProgram& particlesProgram = particles.addComponent<Raz::MeshRenderer>(mesh, Raz::RenderMode::POINT).getMaterials().front().getProgram();
particlesProgram.setShaders(Raz::VertexShader::loadFromSource(R"(
layout(std140) uniform uboCameraInfo {
mat4 uniViewMat;
mat4 uniInvViewMat;
mat4 uniProjectionMat;
mat4 uniInvProjectionMat;
mat4 uniViewProjectionMat;
vec3 uniCameraPos;
};
layout(std140) uniform uboModelInfo {
mat4 uniModelMat;
};
uniform sampler3D uniParticlesPos;
layout(location = 0) out vec3 vertColor;
void main() {
ivec3 imgDims = textureSize(uniParticlesPos, 0);
ivec3 particleCoords;
particleCoords.x = gl_VertexID % imgDims.x;
particleCoords.y = (gl_VertexID / imgDims.x) % imgDims.y;
particleCoords.z = (gl_VertexID / imgDims.x) / imgDims.y;
vertColor = vec3(vec3(particleCoords) / vec3(imgDims));
vec3 vertPos = texelFetch(uniParticlesPos, particleCoords, 0).xyz;
gl_Position = uniViewProjectionMat * uniModelMat * vec4(vertPos, 1.0);
}
)"sv), Raz::FragmentShader::loadFromSource(R"(
layout(location = 0) in vec3 vertColor;
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = vec4(vertColor, 1.0);
}
)"sv));
particlesProgram.clearAttributes();
particlesProgram.clearTextures();
particlesProgram.setTexture(particlesPos, "uniParticlesPos");
app.run([&] (float deltaTime) {
particlesTrans.rotate(10_deg * deltaTime, Raz::Vec3f(0.707106769f, 0.f, 0.707106769f));
particlesUpdate.execute(textureSize, textureSize, textureDepth);
});
return EXIT_SUCCESS;
}
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(r16f, binding = 0) uniform writeonly image3D uniNoiseMap;
uniform float uniNoiseFactor = 0.01;
uniform int uniOctaveCount = 1;
const int permutations[512] = int[](
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142,
8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117,
35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71,
134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41,
55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89,
18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226,
250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182,
189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43,
172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97,
228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239,
107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254,
138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180,
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142,
8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117,
35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71,
134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41,
55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89,
18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226,
250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182,
189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43,
172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97,
228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239,
107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254,
138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180
);
// Only 12 gradients are necessary; however, 16 are defined to avoid dividing by 12. These form a regular tetrahedron, thus no bias is introduced
const vec3 gradients3D[16] = vec3[](
vec3(0.7071067691, 0.7071067691, 0.0), vec3(-0.7071067691, 0.7071067691, 0.0),
vec3(0.7071067691, -0.7071067691, 0.0), vec3(-0.7071067691, -0.7071067691, 0.0),
vec3(0.7071067691, 0.0, 0.7071067691), vec3(-0.7071067691, 0.0, 0.7071067691),
vec3(0.7071067691, 0.0, -0.7071067691), vec3(-0.7071067691, 0.0, -0.7071067691),
vec3( 0.0, 0.7071067691, 0.7071067691), vec3( 0.0, -0.7071067691, 0.7071067691),
vec3( 0.0, 0.7071067691, -0.7071067691), vec3( 0.0, -0.7071067691, -0.7071067691),
vec3(0.7071067691, 0.7071067691, 0.0), vec3(-0.7071067691, 0.7071067691, 0.0),
vec3( 0.0, -0.7071067691, 0.7071067691), vec3( 0.0, -0.7071067691, -0.7071067691)
);
float smootherstep(float value) {
return value * value * value * (value * (value * 6.0 - 15.0) + 10.0);
}
vec3 recoverGradient3D(int x, int y, int z) {
return gradients3D[permutations[permutations[permutations[x] + y] + z] % gradients3D.length()];
}
float computePerlin(vec3 coords) {
// Recovering integer coordinates on the cube
int intX = int(coords.x);
int intY = int(coords.y);
int intZ = int(coords.z);
int x0 = intX & 255;
int y0 = intY & 255;
int z0 = intZ & 255;
// Recovering pseudo-random gradients at each corner of the quad
vec3 leftBotBackGrad = recoverGradient3D(x0, y0, z0 );
vec3 leftBotFrontGrad = recoverGradient3D(x0, y0, z0 + 1);
vec3 rightBotBackGrad = recoverGradient3D(x0 + 1, y0, z0 );
vec3 rightBotFrontGrad = recoverGradient3D(x0 + 1, y0, z0 + 1);
vec3 leftTopBackGrad = recoverGradient3D(x0, y0 + 1, z0 );
vec3 leftTopFrontGrad = recoverGradient3D(x0, y0 + 1, z0 + 1);
vec3 rightTopBackGrad = recoverGradient3D(x0 + 1, y0 + 1, z0 );
vec3 rightTopFrontGrad = recoverGradient3D(x0 + 1, y0 + 1, z0 + 1);
// Computing the distance to the coordinates
// _____________
// / /|
// / / |
// /___________/ X|
// | |/ |
// | xWeight / zWeight
// |---------X | /
// | | yWeight
// |_________|_|/
float xWeight = coords.x - float(intX);
float yWeight = coords.y - float(intY);
float zWeight = coords.z - float(intZ);
float leftBotBackDot = dot(vec3(xWeight, yWeight , zWeight ), leftBotBackGrad);
float leftBotFrontDot = dot(vec3(xWeight, yWeight , zWeight - 1.0), leftBotFrontGrad);
float rightBotBackDot = dot(vec3(xWeight - 1.0, yWeight , zWeight ), rightBotBackGrad);
float rightBotFrontDot = dot(vec3(xWeight - 1.0, yWeight , zWeight - 1.0), rightBotFrontGrad);
float leftTopBackDot = dot(vec3(xWeight, yWeight - 1.0, zWeight ), leftTopBackGrad);
float leftTopFrontDot = dot(vec3(xWeight, yWeight - 1.0, zWeight - 1.0), leftTopFrontGrad);
float rightTopBackDot = dot(vec3(xWeight - 1.0, yWeight - 1.0, zWeight ), rightTopBackGrad);
float rightTopFrontDot = dot(vec3(xWeight - 1.0, yWeight - 1.0, zWeight - 1.0), rightTopFrontGrad);
float smoothX = smootherstep(xWeight);
float smoothY = smootherstep(yWeight);
float smoothZ = smootherstep(zWeight);
float botBackCoeff = mix(leftBotBackDot, rightBotBackDot, smoothX);
float botFrontCoeff = mix(leftBotFrontDot, rightBotFrontDot, smoothX);
float topBackCoeff = mix(leftTopBackDot, rightTopBackDot, smoothX);
float topFrontCoeff = mix(leftTopFrontDot, rightTopFrontDot, smoothX);
float backCoeff = mix(botBackCoeff, topBackCoeff, smoothY);
float frontCoeff = mix(botFrontCoeff, topFrontCoeff, smoothY);
return mix(backCoeff, frontCoeff, smoothZ);
}
float computeFbm(vec3 coords, int octaveCount) {
float frequency = 1.0;
float amplitude = 1.0;
float total = 0.0;
for (int i = 0; i < octaveCount; ++i) {
total += computePerlin(coords * frequency) * amplitude;
frequency *= 2.0;
amplitude *= 0.5;
}
return (total + 1.0) / 2.0;
}
void main() {
float noise = computeFbm(vec3(gl_GlobalInvocationID.xyz) * uniNoiseFactor, uniOctaveCount);
ivec3 pixelCoords = ivec3(gl_GlobalInvocationID.xyz);
imageStore(uniNoiseMap, pixelCoords, vec4(vec3(noise), 1.0));
}
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(r16f, binding = 0) uniform readonly image3D uniInputNoiseMap;
layout(rgba16f, binding = 1) uniform writeonly image3D uniOutputNoiseMap;
vec3 computeCurl(ivec3 pixelCoords) {
float xPosDiff = imageLoad(uniInputNoiseMap, ivec3(pixelCoords.x + 1, pixelCoords.y, pixelCoords.z)).r;
float xNegDiff = imageLoad(uniInputNoiseMap, ivec3(pixelCoords.x - 1, pixelCoords.y, pixelCoords.z)).r;
float xGrad = (xPosDiff - xNegDiff) * 0.5;
float yPosDiff = imageLoad(uniInputNoiseMap, ivec3(pixelCoords.x, pixelCoords.y + 1, pixelCoords.z)).r;
float yNegDiff = imageLoad(uniInputNoiseMap, ivec3(pixelCoords.x, pixelCoords.y - 1, pixelCoords.z)).r;
float yGrad = (yPosDiff - yNegDiff) * 0.5;
float zPosDiff = imageLoad(uniInputNoiseMap, ivec3(pixelCoords.x, pixelCoords.y, pixelCoords.z + 1)).r;
float zNegDiff = imageLoad(uniInputNoiseMap, ivec3(pixelCoords.x, pixelCoords.y, pixelCoords.z - 1)).r;
float zGrad = (zPosDiff - zNegDiff) * 0.5;
return vec3(yGrad - zGrad, zGrad - xGrad, xGrad - yGrad);
}
void main() {
ivec3 pixelCoords = ivec3(gl_GlobalInvocationID.xyz);
vec3 noise = computeCurl(pixelCoords);
imageStore(uniOutputNoiseMap, pixelCoords, vec4(noise, 1.0));
}
- Home
- How to build RaZ
- Getting started
- General usage knowledge
- Some examples...
- Playground
- Tutorials
- File formats
- Modules
- Debug