Volume texture ray marching performance issues

Started by
1 comment, last by cgrant 9 months, 2 weeks ago

Hi guys, I'm new to graphics programming, recently I'm trying to implement ray marching on 3d textures, I want to use it in my voxel engine. Here is the code.

Vertex Shader:

#version 300 es

precision highp float;

layout(location=0) in vec3 aVertexPosition;

layout(location=1) in vec4 aVertexColor;



uniform mat4 uModelMatrix;

uniform mat4 uViewMatrix;

uniform mat4 uProjectionMatrix;

uniform vec3 eye_pos;



out lowp vec4 vColor;

out vec3 ray;

flat out vec3 eye;



void main(void) {

gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aVertexPosition, 1.0);

vColor = aVertexColor;

ray = aVertexPosition - eye_pos;

eye = eye_pos - vec3(-32.0, -32.0, -32.0);

}

Fragment Shader:

#version 300 es
    precision highp float;
    in lowp vec4 vColor;
    in vec3 ray;
    flat in vec3 eye;

    uniform highp sampler3D volume;

    out vec4 fragColor;

    int FRONT = 1, BACK = 2, LEFT = 3, RIGHT = 4, TOP = 5, BOTTOM = 6, NONE = 0;

    float minLength = -1.0;
    bool hit(inout int face, int plane, float planeCoord) {
        vec3 ray_dir = normalize(ray);
        if (plane == 1) {
            float mag = (planeCoord - eye.x) / ray_dir.x;
            if (mag < 0.1) {
                return false;
            }
            vec3 hitPoint = eye + ray_dir * mag;
            hitPoint.x = planeCoord;
            if (hitPoint.y > 64.0 || hitPoint.y < 0.0 || hitPoint.z > 64.0 || hitPoint.z < 0.0) {
                return false;
            }
            if (minLength < 0.0 || mag < minLength) {
                if (ray_dir.x < 0.0 && texture(volume, vec3((hitPoint.x - 1.0) / 64.0, floor(hitPoint.y) / 64.0, floor(hitPoint.z) / 64.0)).w == 1.0) {
                    face = RIGHT;
                    minLength = mag;
                    return true;
                }
                if (ray_dir.x > 0.0 && texture(volume, vec3(hitPoint.x / 64.0, floor(hitPoint.y) / 64.0, floor(hitPoint.z) / 64.0)).w == 1.0) {
                    face = LEFT;
                    minLength = mag;
                    return true;
                }
            } else {
                return true;
            }
        }
        if (plane == 2) {
            float mag = (planeCoord - eye.y) / ray_dir.y;
            if (mag < 0.1) {
                return false;
            }
            vec3 hitPoint = eye + ray_dir * mag;
            hitPoint.y = planeCoord;
            if (hitPoint.x > 64.0 || hitPoint.x < 0.0 || hitPoint.z > 64.0 || hitPoint.z < 0.0) {
                return false;
            }
            if (minLength < 0.0 || mag < minLength) {
                if (ray_dir.y < 0.0 && texture(volume, vec3(floor(hitPoint.x) / 64.0, (hitPoint.y - 1.0) / 64.0, floor(hitPoint.z) / 64.0)).w == 1.0) {
                    face = FRONT;
                    minLength = mag;
                    return true;
                }
                if (ray_dir.y > 0.0 && texture(volume, vec3(floor(hitPoint.x) / 64.0, hitPoint.y / 64.0, floor(hitPoint.z) / 64.0)).w == 1.0) {
                    face = BACK;
                    minLength = mag;
                    return true;
                }
            } else {
                return true;
            }
        }
        if (plane == 3) {
            float mag = (planeCoord - eye.z) / ray_dir.z;
            if (mag < 0.1) {
                return false;
            }
            vec3 hitPoint = eye + ray_dir * mag;
            hitPoint.z = planeCoord;
            if (hitPoint.x > 64.0 || hitPoint.x < 0.0 || hitPoint.y > 64.0 || hitPoint.y < 0.0) {
                return false;
            }
            if (minLength < 0.0 || mag < minLength) {
                if (ray_dir.z < 0.0 && texture(volume, vec3(floor(hitPoint.x) / 64.0, floor(hitPoint.y) / 64.0, (hitPoint.z - 1.0) / 64.0)).w == 1.0) {
                    face = TOP;
                    minLength = mag;
                    return true;
                }
                if (ray_dir.z > 0.0 && texture(volume, vec3(floor(hitPoint.x) / 64.0, floor(hitPoint.y) / 64.0, hitPoint.z / 64.0)).w == 1.0) {
                    face = BOTTOM;
                    minLength = mag;
                    return true;
                }
            } else {
                return true;
            }
        }
        return false;
    }

    void main(void) {
        int face = 0;
        if (ray.x > 0.0) {
            for (float i = 0.0; i < 64.0; ++i) {
                if (hit(face, 1, i)) {
                    break;
                }
            }
        } else {
            for (float i = 64.0; i > 0.0; --i) {
                if (hit(face, 1, i)) {
                    break;
                }
            }
        }
        if (ray.y > 0.0) {
            for (float i = 0.0; i < 64.0; ++i) {
                if (hit(face, 2, i)) {
                    break;
                }
            }
        } else {
            for (float i = 64.0; i > 0.0; --i) {
                if (hit(face, 2, i)) {
                    break;
                }
            }
        }
        if (ray.z > 0.0) {
            for (float i = 0.0; i < 64.0; ++i) {
                if (hit(face, 3, i)) {
                    break;
                }
            }
        } else {
            for (float i = 64.0; i > 0.0; --i) {
                if (hit(face, 3, i)) {
                    break;
                }
            }
        }

        if (face == LEFT) {
            fragColor = vec4(1.0, 0.0, 0.0, 1.0);
            return;
        }
        if (face == RIGHT) {
            fragColor = vec4(0.0, 1.0, 0.0, 1.0);
            return;
        }
        if (face == TOP) {
            fragColor = vec4(0.0, 0.0, 1.0, 1.0);
            return;
        }
        if (face == BOTTOM) {
            fragColor = vec4(1.0, 1.0, 0.0, 1.0);
            return;
        }
        if (face == FRONT) {
            fragColor = vec4(0.0, 1.0, 1.0, 1.0);
            return;
        }
        if (face == BACK) {
            fragColor = vec4(1.0, 0.0, 1.0, 1.0);
            return;
        }
        discard;
    }

3D Texture:

const texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_3D, texture);

    gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

    // Assuming data is a typed array containing texture data
    const width = 64; // Replace with your desired width
    const height = 64; // Replace with your desired height
    const depth = 64; // Replace with your desired depth
    const data = new Uint8Array(width * height * depth * 4);

    for (let z = 0; z < depth; z++) {
        for (let y = 0; y < height; y++) {
            for (let x = 0; x < width; x++) {
                // Calculate texture value based on procedural generation algorithms
                const value = Math.round(Math.random());
                const offset = (z * width * height + y * width + x) * 4;
                data[offset] = 0; // Red channel
                data[offset + 1] = 0; // Green channel
                data[offset + 2] = 0; // Blue channel
                data[offset + 3] = 255 * value; // Alpha channel (set to 255 for opaque texture)
            }
        }
    }

    gl.texImage3D(
        gl.TEXTURE_3D,
        0, // level
        gl.RGBA, // internalFormat
        width,
        height,
        depth,
        0, // border
        gl.RGBA, // format
        gl.UNSIGNED_BYTE, // type
        data // your texture data (e.g., a typed array)
    );

Well it works, but does not work well, the FPS is low. Basically I'm just traversing through each plane in the volume texture towards the 3 axis and check whether the ray hit anything, is there anything wrong with my algorithm or my code? Any advice is appreciated, thanks!

Advertisement

Seems like a lot of shader work for something that should be a little simpler. A volumetric texture is sampled using 3-D texture coordinates. So assuming your have a single volumetric texture, which for simplicity we'll say can be mapped to your world space. What I mean by this is the entire volume texture is a representation of your ‘world’ and the texture volume is some scaled representation of a world-space cube. Given this simplification, you ray-march in ‘world-space' and then mapped the generated ray to texture coordinates and then sample your 3D texture. A simple ray-cube intersection to find the starting point of your ray-marching. If there is no hit, then you early out. If hit, then given the ray starting point converted to texture coordinates, and the ray dir converted to texture coordinate steps( in all 3 dimensions). Perform your ray-march until any of the texture coordinate values exceed the normalized texture coordinate value of 1( assuming clamp mode). The step in the X-Y-Z direction is just the reciprocal of the dimension in that directions. So
dx = 1.0 / volume width
dy = 1.0/volume height
dz = 1.0/num slice
These are the scale factors that should be applied to the normalize ray-direction, ie. your step size.
There really shouldn't be any need to do a per-face march.

This topic is closed to new replies.

Advertisement