🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

What is the best way to draw lines like the game The Witness?

Started by
9 comments, last by EBZ 3 years, 8 months ago

Hello friends, I'm trying let the player draw lines from one point to another like the game “The Witness”.

This system would also also draw lines without user input. A good way of seeing it would be like health bars that fill up but are more than just straight rectangles, they take lefts, rights…you know like paths?

But I'd like mine to be part of a UI and not in perspective projection. Not sure if that makes sense. I've never done this before but wanted to know if you all have any suggestions or if my approaches are way off:

Approach 1: I thought of batching up a bunch of a vertex points (DynamicDraw) that will get rendered as GL_LINES in one single draw call. Afterwards I could give it a nice effect in the fragment shader?

Approach 2: Same as Approach 1 but using quads.

Approach 3: Jump out of the window if I don't figure it out. Just kidding :P

Hope I made sense and thanks again!

Advertisement

Approach 1 will just fine.

All the best ?

Do you have the code for an orthographic projection shader program?

ddlox said:

Approach 1 will just fine.

All the best ?

Thank you very much for your interaction, I had a feeling that seemed like the right thing.

taby said:

Do you have the code for an orthographic projection shader program?

I do actually, let me share it with you. It's nothing out of the ordinary so please don't laugh. :D

This is vertex shader. For the MVP, I'm passing a mat4 which is only the model matrix. I tried passing a projection matrix, but I didn't get it to work. The way you see it below, it's working okay

#version 330 core
layout (location = 0) in vec3 vertex_position;
layout (location = 2) in vec2 uv_position;

out vec2 uv;

uniform mat4 model;
uniform mat4 sprite_matrix;

void main () {
    uv = (sprite_matrix * vec4(uv_position, 0, 1)).xy;
    gl_Position = model * vec4(vertex_position.xy, 0.0, 1.0);
}

And now the fragment shader, I'm just using a color or a texture.

#version 330 core
in vec2 uv;
out vec4 composition;

uniform bool uses_texture;
uniform vec4 color;
uniform sampler2D tex;

void main() {    

    if (uses_texture) {
        composition = texture(tex, uv);
    }
    else {
        composition = color;
    }

}

If you need any more info, please feel free to let me know.

You know that you can set alpha transparency in a shader like any other value when calculating the final output on screen. This way you can for example blend alpha conditionally from the center of a texture to have it fade out to the edges or just give a threshold value that determines which pixels should be visible in the end. The same can be done for your board, just render a quad all over the area in which you want to draw the lines and either use a blend mask or a collection of points in which the texture should be visible.

Instead of a texture blending, you could also set the original texture's color for certain points so that the white lines are “drawn directly to the base texture” of the game board. It's the same as drawing a heat map or something like in SimCity

Hmm. Perhaps I'm not doing things right, but here is my code in any case:


// http://www.songho.ca/opengl/gl_transform.html

complex<float> get_window_coords_from_ndc_coords(size_t viewport_width, size_t viewport_height, complex<float>& src_coords)
{
	float x_w = viewport_width / 2.0f * src_coords.real() + viewport_width / 2.0f;
	float y_w = viewport_height / 2.0f * src_coords.imag() + viewport_height / 2.0f;

	return complex<float>(x_w, y_w);
}

complex<float> get_ndc_coords_from_window_coords(size_t viewport_width, size_t viewport_height, complex<float>& src_coords)
{
	float x_ndc = (2.0f * src_coords.real() / viewport_width) - 1.0f;
	float y_ndc = (2.0f * src_coords.imag() / viewport_height) - 1.0f;

	return complex<float>(x_ndc, y_ndc);
}


complex<float> v0w;
complex<float> v1w;
complex<float> v2w;
complex<float> v3w;	

complex<float> v0ndc;
complex<float> v1ndc;
complex<float> v2ndc;
complex<float> v3ndc;

float alpha = 1.0f - (elapsed.count() / 2000.0f);

alpha = pow(alpha, 1.0f/5.0f);

v0w = complex<float>(static_cast<float>(0), static_cast<float>(window_height / 2 - 20));
v1w = complex<float>(static_cast<float>(0), static_cast<float>(window_height / 2 + 36));
v2w = complex<float>(static_cast<float>(win_x), static_cast<float>(window_height / 2 + 36));
v3w = complex<float>(static_cast<float>(win_x), static_cast<float>(window_height / 2 - 20));

v0ndc = get_ndc_coords_from_window_coords(win_x, win_y, v0w);
v1ndc = get_ndc_coords_from_window_coords(win_x, win_y, v1w);
v2ndc = get_ndc_coords_from_window_coords(win_x, win_y, v2w);
v3ndc = get_ndc_coords_from_window_coords(win_x, win_y, v3w);

#version 400
layout(location = 0) in vec3 position;
layout(location = 1) in vec2 texcoord;

out vec2 ftexcoord;

void main()
{
    ftexcoord = texcoord;
    gl_Position = vec4(position, 1.0);
}

You know you can just attache RenderDoc or Intels GPA and see what that drawcall looks like in that game right?

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion

Shaarigan said:

You know that you can set alpha transparency in a shader like any other value when calculating the final output on screen. This way you can for example blend alpha conditionally from the center of a texture to have it fade out to the edges or just give a threshold value that determines which pixels should be visible in the end. The same can be done for your board, just render a quad all over the area in which you want to draw the lines and either use a blend mask or a collection of points in which the texture should be visible.

Instead of a texture blending, you could also set the original texture's color for certain points so that the white lines are “drawn directly to the base texture” of the game board. It's the same as drawing a heat map or something like in SimCity

Thank you very much for these suggestions! I need to wrap my mind around it and give it a few tries, but I'm sure I'll get it. Thanks again.

taby said:

Hmm. Perhaps I'm not doing things right, but here is my code in any case:


// http://www.songho.ca/opengl/gl_transform.html

complex<float> get_window_coords_from_ndc_coords(size_t viewport_width, size_t viewport_height, complex<float>& src_coords)
{
	float x_w = viewport_width / 2.0f * src_coords.real() + viewport_width / 2.0f;
	float y_w = viewport_height / 2.0f * src_coords.imag() + viewport_height / 2.0f;

	return complex<float>(x_w, y_w);
}

complex<float> get_ndc_coords_from_window_coords(size_t viewport_width, size_t viewport_height, complex<float>& src_coords)
{
	float x_ndc = (2.0f * src_coords.real() / viewport_width) - 1.0f;
	float y_ndc = (2.0f * src_coords.imag() / viewport_height) - 1.0f;

	return complex<float>(x_ndc, y_ndc);
}


complex<float> v0w;
complex<float> v1w;
complex<float> v2w;
complex<float> v3w;	

complex<float> v0ndc;
complex<float> v1ndc;
complex<float> v2ndc;
complex<float> v3ndc;

float alpha = 1.0f - (elapsed.count() / 2000.0f);

alpha = pow(alpha, 1.0f/5.0f);

v0w = complex<float>(static_cast<float>(0), static_cast<float>(window_height / 2 - 20));
v1w = complex<float>(static_cast<float>(0), static_cast<float>(window_height / 2 + 36));
v2w = complex<float>(static_cast<float>(win_x), static_cast<float>(window_height / 2 + 36));
v3w = complex<float>(static_cast<float>(win_x), static_cast<float>(window_height / 2 - 20));

v0ndc = get_ndc_coords_from_window_coords(win_x, win_y, v0w);
v1ndc = get_ndc_coords_from_window_coords(win_x, win_y, v1w);
v2ndc = get_ndc_coords_from_window_coords(win_x, win_y, v2w);
v3ndc = get_ndc_coords_from_window_coords(win_x, win_y, v3w);

#version 400
layout(location = 0) in vec3 position;
layout(location = 1) in vec2 texcoord;

out vec2 ftexcoord;

void main()
{
    ftexcoord = texcoord;
    gl_Position = vec4(position, 1.0);
}

Hey thanks for the samples! This is perfect so I can learn quicker, I'll 100% try to implement this into my project. Thanks again for taking the time for writing some code, time is very valuable, so I appreciate it. Salute.

NightCreature83 said:

You know you can just attache RenderDoc or Intels GPA and see what that drawcall looks like in that game right?

Hey NightCreature, thanks for replying to my thread! You know, I use RenderDoc to debug my own work, but it never occurred to me to try it out with another gamer XD. Now I'm curious. But you're right, maybe it's a good way to see how all this is done. Thanks!

I have an idea to create rectangles for every segment of path and scale them. I wrote this example from scratch in WebGL 1.0 and JavaScript ES5:

Playground

index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Rectangle Path. WebGL 1.0, JS5</title>
    <script src="https://cdn.jsdelivr.net/npm/gl-matrix@3.3.0/gl-matrix-min.js"></script>
</head>

<body>
    <canvas id="renderCanvas" width="400" height="400"></canvas>

    <script>
        const vertexShaderSource = [
            "attribute vec3 aPosition;",
            "uniform mat4 uProjMatrix;",
            "uniform mat4 uModelMatrix;",
            "void main()",
            "{",
            "    gl_Position = uProjMatrix * uModelMatrix * vec4(aPosition, 1.0);",
            "}"
        ].join("\n");

        const fragmentShaderSource = [
            "precision mediump float;",
            "uniform vec3 uColor;",
            "void main()",
            "{",
            "    gl_FragColor = vec4(uColor, 1.0);",
            "}"
        ].join("\n");

        const canvas = document.getElementById("renderCanvas");
        const gl = canvas.getContext("webgl");

        const program = gl.createProgram();
        const vShader = CreateShader(vertexShaderSource, gl.VERTEX_SHADER);
        const fShader = CreateShader(fragmentShaderSource, gl.FRAGMENT_SHADER);
        gl.attachShader(program, vShader);
        gl.attachShader(program, fShader);
        gl.linkProgram(program);
        gl.useProgram(program);

        gl.bindAttribLocation(program, 0, "aPosition");
        const vertPositions = new Float32Array([
            0, 0.5, 0.0,
            0, -0.5, 0.0,
            1, 0.5, 0.0,
            1, -0.5, 0.0
        ]);
        const vertPositionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, vertPositionBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, vertPositions, gl.STATIC_DRAW);
        gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(0);

        const uColorLocation = gl.getUniformLocation(program, "uColor");
        gl.uniform3fv(uColorLocation, glMatrix.vec3.fromValues(0.764, 0.156, 0.203));

        const uProjMatrixLocation = gl.getUniformLocation(program, "uProjMatrix");
        const projMatrix = glMatrix.mat4.create();
        glMatrix.mat4.ortho(projMatrix, -10, 10, -10, 10, 100, -100);
        gl.uniformMatrix4fv(uProjMatrixLocation, false, projMatrix);

        const uModelMatrixLocation = gl.getUniformLocation(program, "uModelMatrix");
        const modelMatrix = glMatrix.mat4.create();

        gl.clearColor(0.901, 0.949, 0.976, 1);

        let scaleX = 2;
        // setInterval(mainLoop, 30);
        mainLoop();
        function mainLoop() {
            requestAnimationFrame(mainLoop);
            gl.clear(gl.COLOR_BUFFER_BIT);
            glMatrix.mat4.identity(modelMatrix);
            glMatrix.mat4.translate(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(-10, 0, 0));
            glMatrix.mat4.scale(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(scaleX, 2, 1));
            scaleX += 0.05;
            gl.uniformMatrix4fv(uModelMatrixLocation, false, modelMatrix);
            gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
        }

        function CreateShader(source, type) {
            const shader = gl.createShader(type);
            gl.shaderSource(shader, source);
            gl.compileShader(shader);
            const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
            if (!success)
            {
                console.log(gl.getShaderInfoLog(shader));
                console.log(source);
                return null;
            }
            return shader;
        }
    </script>
</body>

</html>

@8Observer8 Good one! Thanks for sharing, I'm actually still working on this so this is also very helpful. Thank you very much for sharing your code with me. Salute!

This topic is closed to new replies.

Advertisement